# How Bilistream Handles Different Streaming Platforms: Trait-Based Architecture Explained

> Discover how Bilistream's trait-based architecture unifies Twitch and YouTube streaming by abstracting platforms behind a single Live trait for uniform API access.

- Repository: [InitCool/bilistream](https://github.com/limitcool/bilistream)
- Tags: architecture
- Published: 2026-03-06

---

**Bilistream abstracts every supported streaming service behind a single `Live` trait, using configuration-driven platform selection to instantiate concrete implementations while exposing uniform methods for status checks, room management, and HLS URL retrieval across Twitch and YouTube.**

The open-source Rust application **limitcool/bilistream** demonstrates how to monitor multiple streaming platforms through clean architectural abstraction. By examining how Bilistream handles different streaming platforms, developers can see how trait objects enable platform-agnostic code that treats Twitch and YouTube identically despite their fundamentally different APIs and authentication requirements.

## The Live Trait Abstraction

At the core of Bilistream's multi-platform support lies the **`Live`** trait defined in **[[`src/plugins/live.rs`](https://github.com/limitcool/bilistream/blob/main/src/plugins/live.rs)](https://github.com/limitcool/bilistream/blob/main/src/plugins/live.rs#L23-L30)**. This interface declares the minimal operations required for any streaming service:

- `async fn get_status(&self) -> Result<bool, Box<dyn Error>>` – Checks whether the target channel is currently broadcasting.
- `fn room(&self) -> &str` – Returns the channel identifier.
- `async fn get_real_m3u8_url(&self) -> Result<String, Box<dyn Error>>` – Retrieves the actual HLS stream URL suitable for playback or re-streaming.
- `fn set_room(&mut self, room: &str)` – Updates the target channel at runtime.

Any type implementing `Live` can be boxed and passed through the application as `Box<dyn Live>`, allowing the rest of the codebase to remain completely ignorant of whether it is monitoring Twitch, YouTube, or future platforms.

## Configuration-Driven Platform Selection

Platform selection occurs at startup through the **`select_live`** function in **[[`src/plugins/live.rs`](https://github.com/limitcool/bilistream/blob/main/src/plugins/live.rs)](https://github.com/limitcool/bilistream/blob/main/src/plugins/live.rs#L30-L55)**. The function inspects the `platform` field from the **`Config`** struct (defined in **[[`src/config.rs`](https://github.com/limitcool/bilistream/blob/main/src/config.rs)](https://github.com/limitcool/bilistream/blob/main/src/config.rs)**) and constructs the appropriate concrete implementation:

```rust
pub async fn select_live(cfg: Config) -> Result<Box<dyn Live>, Box<dyn Error>> {
    match cfg.platform.as_str() {
        "Youtube" => Ok(Box::new(Youtube::new(/* … */))),
        "Twitch" => Ok(Box::new(Twitch::new(/* … */))),
        "YoutubePreviewLive" => { /* Converts channel name to live video ID */ }
        _ => Err("unknown platform".into()),
    }
}

```

This factory pattern returns a boxed trait object, ensuring that downstream components interact with a consistent interface regardless of the underlying platform. The configuration structure stores platform-specific settings including optional cookie files for authenticated requests.

## Platform-Specific Implementations

While the `Live` trait provides the contract, each platform implements its own networking logic for status detection and URL extraction.

### Twitch Implementation

The Twitch driver resides in **[[`src/plugins/twitch.rs`](https://github.com/limitcool/bilistream/blob/main/src/plugins/twitch.rs)](https://github.com/limitcool/bilistream/blob/main/src/plugins/twitch.rs)** and handles both live detection and stream URL retrieval.

**Live-status verification** uses a GraphQL query against `https://gql.twitch.tv/gql`. The implementation posts a JSON payload with a hardcoded `Client-ID` header (`kimne78kx3ncx6brgo4mv6wki5h1ko`) and inspects the response for the `"type":"live"` indicator:

```rust
let res: serde_json::Value = self
    .client
    .post("https://gql.twitch.tv/gql")
    .header("Client-ID", "kimne78kx3ncx6brgo4mv6wki5h1ko")
    .json(&j)
    .send()
    .await?
    .json()
    .await?;
if res["data"]["user"]["stream"]["type"] == "live" { /* stream is online */ }

```

**Stream URL extraction** delegates to **yt-dlp** rather than manually parsing Twitch's internal API. The implementation constructs a command with the `-g` flag to retrieve the direct URL, optionally appending cookie arguments if configured:

```rust
let mut command = Command::new("yt-dlp");
command.arg("-g");
if let Some(cookies) = &self.config.cookies {
    command.arg("--cookies").arg(cookies);
}
command.arg(format!("https://www.twitch.tv/{}", self.room));
let output = command.output()?;

```

### YouTube Implementation

The YouTube driver in **[[`src/plugins/youtube.rs`](https://github.com/limitcool/bilistream/blob/main/src/plugins/youtube.rs)](https://github.com/limitcool/bilistream/blob/main/src/plugins/youtube.rs)** uses HTML scraping to avoid API key dependencies.

**Live-status detection** fetches the channel's `/live` page and extracts embedded JSON from a `<script>` tag to check the `isLive` flag. The helper function `get_youtube_live_status` in **[[`src/plugins/live.rs`](https://github.com/limitcool/bilistream/blob/main/src/plugins/live.rs)](https://github.com/limitcool/bilistream/blob/main/src/plugins/live.rs#L67-L92)** performs this parsing, allowing the YouTube implementation to determine broadcast status without the YouTube Data API.

**Stream URL retrieval** follows the same yt-dlp pattern as Twitch, targeting `https://www.youtube.com/channel/{}/live`. The output undergoes regex cleaning to remove warning lines before returning the clean HLS URL.

## Unified Workflow

Bilistream orchestrates platform handling through a consistent five-step process:

1. **Load configuration** via `load_config` in [`src/config.rs`](https://github.com/limitcool/bilistream/blob/main/src/config.rs), parsing the `platform` string and optional authentication cookies.
2. **Instantiate the platform driver** by awaiting `select_live`, which returns a `Box<dyn Live>` containing either a `Twitch` or `Youtube` struct.
3. **Monitor status** by calling `live.get_status().await`, which executes the platform-specific GraphQL or HTML scraping logic.
4. **Retrieve stream URLs** via `live.get_real_m3u8_url().await` when the status indicates the channel is live.
5. **Runtime reconfiguration** using `live.set_room()` to switch channels without restarting the application.

## Practical Integration Example

The following example demonstrates how to use Bilistream's platform abstraction in your own async Rust code:

```rust
use bilistream::config::load_config;
use bilistream::plugins::select_live;
use std::path::Path;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Load configuration including platform selection
    let cfg = load_config(Path::new("config.yaml"))?;
    
    // Obtain platform-agnostic Live instance
    let mut live = select_live(cfg).await?;
    
    println!("Monitoring: {}", live.room());
    
    // Check live status uniformly across platforms
    if live.get_status().await? {
        let url = live.get_real_m3u8_url().await?;
        println!("Stream URL: {}", url);
    }
    
    // Switch channels at runtime
    live.set_room("different_channel");
    Ok(())
}

```

## Summary

- **Bilistream uses a trait-based abstraction** where the `Live` trait in [`src/plugins/live.rs`](https://github.com/limitcool/bilistream/blob/main/src/plugins/live.rs) defines the contract for all streaming platforms.
- **Platform selection is configuration-driven** via the `select_live` factory function, which instantiates `Twitch` or `Youtube` structs based on the `Config::platform` string.
- **Each platform implements its own networking logic**: Twitch uses GraphQL queries against `gql.twitch.tv`, while YouTube uses HTML scraping to avoid API keys.
- **Both platforms delegate URL extraction to yt-dlp**, with optional cookie authentication support configured through the `Config` struct.
- **Runtime channel switching** is supported through the `set_room` method on the trait object, enabling dynamic monitoring without process restarts.

## Frequently Asked Questions

### What streaming platforms does Bilistream currently support?

Bilistream officially supports **Twitch**, **YouTube** (via channel ID), and **YouTubePreviewLive** (which converts channel names to live video IDs). The architecture allows adding new platforms by implementing the `Live` trait and adding a corresponding match arm in the `select_live` function within [`src/plugins/live.rs`](https://github.com/limitcool/bilistream/blob/main/src/plugins/live.rs).

### How does Bilistream extract HLS stream URLs without using official APIs?

Rather than parsing complex internal APIs directly, Bilistream invokes **yt-dlp** as a subprocess with the `-g` flag to retrieve direct stream URLs. This approach works uniformly across Twitch and YouTube, handling authentication via optional cookie files passed through the `Config` struct, and avoids the rate limits and key requirements of official REST APIs.

### Can I switch between streaming platforms at runtime without restarting Bilistream?

While you cannot change the platform type (Twitch vs. YouTube) at runtime without reconstructing the `Live` object, you can switch **channels** within the same platform using the `set_room` method defined in the `Live` trait. To change platforms entirely, you would need to call `select_live` again with a modified `Config` instance containing a different `platform` string.

### Where is the platform selection logic implemented in the source code?

The platform selection logic resides in the **`select_live`** function in **[[`src/plugins/live.rs`](https://github.com/limitcool/bilistream/blob/main/src/plugins/live.rs)](https://github.com/limitcool/bilistream/blob/main/src/plugins/live.rs#L30-L55)**. This function matches on the `cfg.platform` string and returns a `Box<dyn Live>` containing the appropriate concrete implementation. The `Config` struct that drives this selection is defined in **[[`src/config.rs`](https://github.com/limitcool/bilistream/blob/main/src/config.rs)](https://github.com/limitcool/bilistream/blob/main/src/config.rs)**.