# Core Modules of the Bilistream System: Architecture and Implementation Guide

> Explore the 7 core Rust modules of the Bilistream system including main.rs config.rs and push.rs Understand its architecture and implementation for Bilibili streaming.

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

---

**Bilistream's architecture consists of seven specialized Rust modules—main.rs, config.rs, push.rs, and four plugin files—that handle configuration parsing, platform abstraction, status polling, and FFmpeg streaming to Bilibili.**

Bilistream is a Rust-based automation tool that monitors live streams from YouTube, Twitch, or Bilibili preview channels and forwards them to Bilibili live rooms. Understanding the **core modules of the bilistream system** is essential for developers looking to extend platform support or debug streaming workflows. The codebase follows a clean modular design with clear separation between configuration management, platform-specific adapters, and the streaming pipeline.

## Overview of the Bilistream Architecture

The system operates through a coordinated pipeline: configuration loading → platform selection → status polling → URL extraction → Bilibili control → FFmpeg streaming → optional notification. Each phase is handled by a dedicated module with well-defined interfaces, making the system extensible for additional platforms.

The architecture relies on the **`Live`** trait defined in [`src/plugins/live.rs`](https://github.com/limitcool/bilistream/blob/main/src/plugins/live.rs) to abstract platform differences, allowing [`src/main.rs`](https://github.com/limitcool/bilistream/blob/main/src/main.rs) to orchestrate the workflow without knowing implementation details of YouTube versus Twitch.

## The Seven Core Modules

### 1. [`src/main.rs`](https://github.com/limitcool/bilistream/blob/main/src/main.rs) – Runtime Orchestrator

The primary entry point initializes the tracing system, loads YAML configuration, selects the appropriate platform implementation, and drives the main event loop. This module contains the **FFmpeg wrapper** and Bilibili start/stop logic.

Key functions include:
- `main()` – Initializes the async runtime and enters the polling loop
- `select_live()` – Factory function that instantiates the correct platform adapter
- `bili_start_live()` / `bili_stop_live()` – Control Bilibili streaming via REST API
- `ffmpeg()` – Launches external FFmpeg processes to push streams

```rust
#[tokio::main]
async fn main() {
    tracing_subscriber::registry()
        .with(fmt::layer())
        .init();

    let cfg = load_config(Path::new("./config.yaml")).unwrap();
    let mut r = select_live(cfg.clone()).await.unwrap();

    loop {
        if r.get_status().await.unwrap_or(false) {
            if let Some(ref gotify_config) = cfg.gotify {
                send_gotify_notification(gotify_config,
                                         &format!("{}开始直播", r.room()),
                                         "bilistream").await;
            }
            // Bilibili stream handling and FFmpeg execution...
        }
        tokio::time::sleep(Duration::from_secs(cfg.interval)).await;
    }
}

```

### 2. [`src/config.rs`](https://github.com/limitcool/bilistream/blob/main/src/config.rs) – Typed Configuration Management

This module defines the configuration schema using Serde and handles YAML deserialization. The **`Config`** struct maps directly to the configuration file structure with explicit renaming for field names.

Key components:
- **`Config`** – Root struct containing `BiliLive`, `Twitch`, `Youtube`, `Platform`, and optional `Gotify` sections
- **`load_config()`** – Reads and parses the YAML configuration file

```rust
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Config {
    #[serde(rename = "BiliLive")] pub bililive: BiliLive,
    #[serde(rename = "Twitch")]  pub twitch: TwitchC,
    #[serde(rename = "Interval")] pub interval: u64,
    #[serde(rename = "Youtube")] pub youtube: YoutubeC,
    #[serde(rename = "Platform")] pub platform: String,
}

```

### 3. [`src/push.rs`](https://github.com/limitcool/bilistream/blob/main/src/push.rs) – Gotify Notification Helper

Handles optional push notifications when streams go live. The module provides a thin wrapper around the Gotify client for alerting users to streaming events.

Key function:
- **`send_gotify_notification()`** – Sends messages to a Gotify server with configurable URL and token

```rust
pub async fn send_gotify_notification(
    config: &GotifyConfig,
    message: &str,
    title: &str,
) {
    match GotifyClient::new(config.url.as_str(), &config.token) {
        Ok(client) => match client.create_message(message).with_title(title).await {
            Ok(_) => tracing::info!("Gotify通知发送成功"),
            Err(e) => tracing::error!("Gotify通知发送失败: {}", e),
        },
        Err(e) => tracing::error!("Gotify客户端创建失败: {}", e),
    }
}

```

### 4. [`src/plugins/live.rs`](https://github.com/limitcool/bilistream/blob/main/src/plugins/live.rs) – The Live Trait and Factory

The heart of the platform abstraction layer. This module defines the **`Live`** trait that all streaming platforms must implement, providing a uniform interface for status checking and URL extraction.

Key components:
- **`Live` trait** – Requires `get_status()`, `room()`, `get_real_m3u8_url()`, and `set_room()` methods
- **`select_live()`** – Factory function that returns a boxed `dyn Live` based on the `platform` configuration value

```rust
#[async_trait]
pub trait Live {
    async fn get_status(&self) -> Result<bool, Box<dyn Error>>;
    fn room(&self) -> &str;
    async fn get_real_m3u8_url(&self) -> Result<String, Box<dyn Error>>;
    fn set_room(&mut self, room: &str);
}

```

### 5. [`src/plugins/twitch.rs`](https://github.com/limitcool/bilistream/blob/main/src/plugins/twitch.rs) – Twitch Platform Adapter

Implements the `Live` trait for Twitch streams. Uses the Twitch GraphQL API for status checks and `yt-dlp` for media URL extraction when native token-based methods are disabled.

Key implementation details:
- Status checking via `https://gql.twitch.tv/gql` with specific SHA256 hashes
- **`Twitch::get_status()`** – Returns `true` when `res["data"]["user"]["stream"]["type"] == "live"`
- **`Twitch::get_real_m3u8_url()`** – Invokes `yt-dlp` to extract the actual m3u8 URL

```rust
async fn get_status(&self) -> Result<bool, Box<dyn Error>> {
    let j = json!({
        "operationName":"StreamMetadata",
        "variables":{"channelLogin": &self.room},
        "extensions":{"persistedQuery":{ "version":1,
            "sha256Hash":"1c719a40e481453e5c48d9bb585d971b8b372f8ebb105b17076722264dfa5b3e"}}
    });
    let res: serde_json::Value = self.client
        .post("https://gql.twitch.tv/gql")
        .header("Client-ID", "kimne78kx3ncx6brgo4mv6wki5h1ko")
        .json(&j)
        .send()
        .await?
        .json()
        .await?;
    Ok(res["data"]["user"]["stream"]["type"] == "live")
}

```

### 6. [`src/plugins/youtube.rs`](https://github.com/limitcool/bilistream/blob/main/src/plugins/youtube.rs) – YouTube Platform Adapter

Provides YouTube-specific implementations of the `Live` trait. Delegates status checking to HTML parsing functions and uses `yt-dlp` for URL extraction.

Key implementation details:
- **`Youtube::get_status()`** – Delegates to `get_youtube_live_status()` for HTML parsing
- **`Youtube::get_real_m3u8_url()`** – Uses `yt-dlp` to obtain the m3u8 stream URL

```rust
async fn get_status(&self) -> Result<bool, Box<dyn Error>> {
    get_youtube_live_status(&self.room).await
}

```

### 7. [`src/plugins/mod.rs`](https://github.com/limitcool/bilistream/blob/main/src/plugins/mod.rs) and [`src/plugins/response.rs`](https://github.com/limitcool/bilistream/blob/main/src/plugins/response.rs) – Module Organization

- **[`src/plugins/mod.rs`](https://github.com/limitcool/bilistream/blob/main/src/plugins/mod.rs)** – Re-exports public symbols from the plugin submodules, providing a clean import interface (`use bilistream::plugins::select_live`)
- **[`src/plugins/response.rs`](https://github.com/limitcool/bilistream/blob/main/src/plugins/response.rs)** – Currently a placeholder module reserved for future response handling logic

## Practical Implementation Examples

### Selecting a Platform at Runtime

The `select_live` function in [`src/plugins/live.rs`](https://github.com/limitcool/bilistream/blob/main/src/plugins/live.rs) enables dynamic platform selection based on configuration without compile-time coupling:

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

#[tokio::main]
async fn main() {
    let cfg = load_config(Path::new("./config.yaml")).unwrap();
    // Returns Box<dyn Live> - could be Twitch, Youtube, or Bilibili preview
    let live = select_live(cfg.clone()).await.unwrap();

    println!("Monitoring platform: {}", cfg.platform);
    println!("Room/Channel ID: {}", live.room());
}

```

### Sending Gotify Notifications

Integrate the notification module to alert external systems when streams start:

```rust
use bilistream::push::send_gotify_notification;
use bilistream::config::GotifyConfig;

#[tokio::main]
async fn main() {
    let gotify = GotifyConfig {
        url: "https://example.com/gotify".into(),
        token: "YOUR_TOKEN".into(),
    };
    send_gotify_notification(&gotify, "Stream started!", "Bilistream")
        .await;
}

```

### Executing the FFmpeg Push Pipeline

The FFmpeg wrapper in [`src/main.rs`](https://github.com/limitcool/bilistream/blob/main/src/main.rs) handles the actual streaming process with automatic restart logic:

```rust
pub fn ffmpeg(rtmp_url: String, rtmp_key: String,
              m3u8_url: String, ffmpeg_proxy: Option<String>) {
    let cmd = format!("{}{}", rtmp_url, rtmp_key);
    let mut command = Command::new("ffmpeg");
    if let Some(proxy) = ffmpeg_proxy {
        command.arg("-http_proxy").arg(proxy);
    }
    command.arg("-re")
        .arg("-i").arg(m3u8_url)
        .arg("-vcodec").arg("copy")
        .arg("-acodec").arg("aac")
        .arg("-f").arg("flv")
        .arg(cmd);
    // Execution and retry logic follows...
}

```

## Summary

The **core modules of the bilistream system** form a cohesive pipeline for automated stream forwarding:

- **[`src/main.rs`](https://github.com/limitcool/bilistream/blob/main/src/main.rs)** orchestrates the runtime loop, FFmpeg execution, and Bilibili API interactions
- **[`src/config.rs`](https://github.com/limitcool/bilistream/blob/main/src/config.rs)** provides type-safe YAML configuration loading with Serde
- **[`src/push.rs`](https://github.com/limitcool/bilistream/blob/main/src/push.rs)** enables optional Gotify notifications for monitoring alerts
- **[`src/plugins/live.rs`](https://github.com/limitcool/bilistream/blob/main/src/plugins/live.rs)** defines the `Live` trait and `select_live` factory for platform abstraction
- **[`src/plugins/twitch.rs`](https://github.com/limitcool/bilistream/blob/main/src/plugins/twitch.rs)** implements Twitch-specific status checking via GraphQL and `yt-dlp` URL extraction
- **[`src/plugins/youtube.rs`](https://github.com/limitcool/bilistream/blob/main/src/plugins/youtube.rs)** handles YouTube stream detection and URL resolution
- **[`src/plugins/mod.rs`](https://github.com/limitcool/bilistream/blob/main/src/plugins/mod.rs)** organizes public exports while [`response.rs`](https://github.com/limitcool/bilistream/blob/main/response.rs) reserves extension points

This modular architecture allows developers to add new streaming platforms by implementing the `Live` trait without modifying the core orchestration logic in [`main.rs`](https://github.com/limitcool/bilistream/blob/main/main.rs).

## Frequently Asked Questions

### What is the purpose of the Live trait in bilistream?

The **`Live` trait** defined in [`src/plugins/live.rs`](https://github.com/limitcool/bilistream/blob/main/src/plugins/live.rs) provides a platform-agnostic interface that abstracts the differences between YouTube, Twitch, and Bilibili preview streams. It requires four methods: `get_status()` for checking if a stream is live, `room()` for getting the channel identifier, `get_real_m3u8_url()` for extracting the actual stream URL, and `set_room()` for updating the channel. This abstraction allows [`src/main.rs`](https://github.com/limitcool/bilistream/blob/main/src/main.rs) to handle all platforms uniformly through dynamic dispatch via `Box<dyn Live>`.

### How does bilistream handle different streaming platforms?

Bilistream uses a **factory pattern** implemented in the `select_live()` function within [`src/plugins/live.rs`](https://github.com/limitcool/bilistream/blob/main/src/plugins/live.rs). This function inspects the `platform` field from the configuration and instantiates the appropriate concrete implementation—either `Twitch` from [`src/plugins/twitch.rs`](https://github.com/limitcool/bilistream/blob/main/src/plugins/twitch.rs), `Youtube` from [`src/plugins/youtube.rs`](https://github.com/limitcool/bilistream/blob/main/src/plugins/youtube.rs), or a Bilibili preview adapter. Each platform implements the `Live` trait methods differently: Twitch uses GraphQL API calls, while YouTube uses HTML parsing, but both expose the same interface to the main runtime loop.

### Where is the FFmpeg streaming logic implemented?

The **FFmpeg wrapper** is implemented directly in [`src/main.rs`](https://github.com/limitcool/bilistream/blob/main/src/main.rs) within the `ffmpeg()` function. This function constructs a `std::process::Command` that invokes the external FFmpeg binary with arguments for RTMP pushing, including options for HTTP proxy configuration, codec copying (`-vcodec copy`), AAC audio encoding, and FLV formatting. The function includes recursive retry logic to restart the stream automatically if FFmpeg exits with a non-zero status code.

### How is configuration loaded in the bilistream system?

Configuration loading is handled by **[`src/config.rs`](https://github.com/limitcool/bilistream/blob/main/src/config.rs)**, which uses the Serde library to deserialize YAML files into strongly-typed Rust structs. The `load_config()` function reads the file from a specified path (typically [`./config.yaml`](https://github.com/limitcool/bilistream/blob/main/./config.yaml)) and maps it to the `Config` struct, which uses Serde attributes like `#[serde(rename = "BiliLive")]` to match YAML keys to Rust field names. This approach provides compile-time safety for configuration access throughout the application.