Skip to content

Jellyfin 源码阅读指南

  • 依据 jellyfin 仓库: branch master

学习前先克隆项目:

cd external
git clone --depth 1 https://github.com/jellyfin/jellyfin.git

项目概述

Jellyfin 是一个用 C# (.NET) 编写的开源媒体服务器,是 Emby 的一个 fork。它可以让你收集、管理和流式传输你的媒体文件。

  • GitHub: https://github.com/jellyfin/jellyfin
  • 本地路径: external/jellyfin

项目结构

jellyfin/
├── Jellyfin.Server/                   # 入口点,Web 主机 (ASP.NET Core)
│   ├── Program.cs                    # Main() 入口
│   ├── CoreAppHost.cs               # 应用主机实现
│   ├── Startup.cs                   # ASP.NET Core 启动配置
│   └── Migrations/                  # 数据库迁移
├── Jellyfin.Api/                    # REST API 层 (60+ 控制器)
│   ├── Controllers/                 # API 控制器
│   ├── Auth/                        # 认证中间件
│   ├── Middleware/                  # 中间件
│   └── WebSocketListeners/          # WebSocket 支持
├── Jellyfin.Data/                   # 数据访问层 (Entity Framework)
├── Jellyfin.Server.Implementations/ # 服务端实现
├── MediaBrowser.Controller/         # 核心接口/抽象层
├── MediaBrowser.Model/              # 数据模型和 DTO
├── MediaBrowser.Common/             # 公共类型和接口
├── MediaBrowser.Providers/          # 元数据提供者 (IMDB, TMDB, TVDB 等)
├── MediaBrowser.MediaEncoding/      # 媒体编码 (FFmpeg 集成)
├── MediaBrowser.LocalMetadata/      # 本地元数据解析
├── MediaBrowser.XbmcMetadata/       # XBMC/Kodi 格式元数据
├── Emby.Server.Implementations/     # 核心实现 (实际逻辑)
│   ├── ApplicationHost.cs          # 应用主机核心 (DI, 插件, 生命周期)
│   ├── Library/                    # 媒体库管理
│   ├── Session/                    # 会话管理
│   ├── Plugins/                    # 插件系统
│   ├── HttpServer/                 # HTTP 服务
│   ├── IO/                         # 文件 I/O
│   └── Updates/                    # 自动更新
├── Emby.Naming/                     # 文件命名解析
├── Emby.Photos/                     # 照片处理
└── tests/                           # 测试项目

学习阶段

阶段 1: 理解架构分层

Jellyfin 采用接口-实现分离的分层架构:

┌─────────────────────────────────────────┐
│          Jellyfin.Api (REST API)        │  ← 表现层
├─────────────────────────────────────────┤
│       MediaBrowser.Controller           │  ← 接口/抽象层
├─────────────────────────────────────────┤
│   Emby.Server.Implementations           │  ← 核心实现层
├─────────────────────────────────────────┤
│ Jellyfin.Data / MediaBrowser.Model      │  ← 数据层
└─────────────────────────────────────────┘
  1. 理解入口点
  2. 阅读 external/jellyfin/Jellyfin.Server/Program.cs — 了解启动流程
  3. 阅读 external/jellyfin/Jellyfin.Server/CoreAppHost.cs — 应用主机
  4. 阅读 external/jellyfin/Jellyfin.Server/Startup.cs — ASP.NET Core 配置

阶段 2: 理解核心机制

  1. 应用主机 (ApplicationHost)

  2. external/jellyfin/Emby.Server.Implementations/ApplicationHost.cs — 核心生命周期管理

  3. 了解 DI 容器注册流程
  4. 了解插件加载机制

  5. 依赖注入

  6. external/jellyfin/Emby.Server.Implementations/ApplicationHost.cs 中的 RegisterServices() 方法

  7. 使用 ASP.NET Core 内置 DI (Microsoft.Extensions.DependencyInjection)

  8. 插件系统

  9. external/jellyfin/MediaBrowser.Common.Plugins.IPlugin — 插件接口

  10. external/jellyfin/Emby.Server.Implementations/Plugins/ — 插件加载和发现
  11. external/jellyfin/MediaBrowser.Common.Plugins.BasePlugin — 插件基类

阶段 3: 理解核心业务

  1. 媒体库系统

  2. external/jellyfin/Emby.Server.Implementations/Library/ — 媒体库扫描和管理

  3. external/jellyfin/MediaBrowser.Controller.Entities/ — 媒体实体 (Movie, Series, Episode 等)

  4. API 层

  5. external/jellyfin/Jellyfin.Api/Controllers/ItemsController.cs — 媒体项 API

  6. external/jellyfin/Jellyfin.Api/Controllers/UserController.cs — 用户管理
  7. external/jellyfin/Jellyfin.Api/Controllers/VideosController.cs — 视频流

  8. 元数据提供者

  9. external/jellyfin/MediaBrowser.Providers/ — 从 TMDB, IMDB 等获取元数据

  10. external/jellyfin/MediaBrowser.LocalMetadata/ — 从本地文件 (NFO) 解析元数据

阶段 4: 深入功能

  1. 媒体编码和流媒体

  2. external/jellyfin/MediaBrowser.MediaEncoding/ — FFmpeg 集成

  3. external/jellyfin/Jellyfin.Api/Controllers/DynamicHlsController.cs — HLS 流

  4. 会话和播放状态

  5. external/jellyfin/Emby.Server.Implementations/Session/ — 会话管理

  6. external/jellyfin/Jellyfin.Api/Controllers/PlaystateController.cs — 播放状态上报

关键概念

概念 说明
ApplicationHost 应用主机,管理 DI、插件、生命周期
IPlugin 插件接口,所有功能模块都通过插件集成
BaseItem 媒体实体基类 (Movie, Series, Episode 等)
Resolver 媒体文件识别器,从文件名/目录结构确定媒体类型
Provider 元数据提供者,从外部获取/本地解析媒体信息
DLNA 数字生活网络联盟协议,流媒体发现和播放
HLS HTTP Live Streaming,动态转码和分段传输
Trickplay 视频缩略图预览条
SyncPlay 多人同步播放

核心流程图

启动流程

Program.Main()
StartApp()
SetupServer.RunAsync()    → 初始化网络、数据库
ApplyStartupMigrationAsync() → 数据库迁移
StartServer()
new CoreAppHost()         → 创建应用主机
appHost.Init()            → 注册 DI、加载插件、初始化服务
WebHostBuilder.Run()      → 启动 ASP.NET Core

媒体扫描流程

媒体文件夹扫描
Resolver 识别文件 (从文件名/目录结构)
Provider 获取元数据 (TMDB/IMDB/NFO)
创建/更新 BaseItem 实体
存入数据库 (SQLite/PostgreSQL)
用户可以通过 API 访问

视频播放流程

Jellyfin 使用 HLS (HTTP Live Streaming) 协议向浏览器传输视频。

整体架构

浏览器请求视频
GET /Videos/{itemId}/{master|main}.m3u8
StreamingHelpers.GetStreamingState() → 决定 Direct Play / Remux / Transcode
需要转码? → transcodeManager.StartFfMpeg() → FFmpeg 转码 → HLS 分段输出
不需要   → 直接返回原始文件
返回 .m3u8 playlist 给浏览器
浏览器逐段请求 .ts/.mp4 片段

三种播放模式

模式 说明 触发条件
Direct Play 原文件直接输出 浏览器支持原始编码格式
Direct Stream 重新封装容器,不重新编码 编码兼容但容器不兼容
Transcode FFmpeg 实时转码 编码/分辨率/码率不兼容

核心端点

Endpoint 用途
GET /Videos/{id}/master.m3u8 自适应码率主播放列表 (多码率)
GET /Videos/{id}/main.m3u8 单一码率播放列表
GET /Videos/{id}/live.m3u8 Live 模式播放列表 (低延迟)
GET /Audio/{id}/universal 音频通用播放
GET /Videos/{id}/hls1/{segId} HLS 分段数据 (.ts/.mp4)

详细播放流程

1. 浏览器请求: GET /Videos/abc123/master.m3u8
2. DynamicHlsHelper.GetMasterHlsPlaylist()
   - 解析设备能力 (User-Agent, codec 支持)
   - 创建 StreamState
   - 决定编码参数 (分辨率/码率/编码器)
3. 返回主播放列表 (master.m3u8):
   #EXTM3U
   #EXT-X-STREAM-INF:BANDWIDTH=5000000,RESOLUTION=1920x1080
   main.m3u8?videoCodec=h264,h265&audioCodec=aac,ac3
4. 浏览器请求: GET /Videos/abc123/main.m3u8?videoCodec=h264
5. StreamingHelpers.GetStreamingState()
   - 找到原始媒体文件 (MediaSource)
   - 比较原始编码 vs 请求编码
   - 决定是否需要启动 FFmpeg
6a. 不需要转码 (Direct Play):
    返回静态播放列表,指向原始文件

6b. 需要转码 (Transcode):
    transcodeManager.StartFfMpeg() 生成 FFmpeg 命令:
    ffmpeg -i input.mkv -map 0:v -map 0:a \
      -c:v libx264 -preset veryfast -b:v 5000k \
      -c:a aac -b:a 128k \
      -f hls -hls_time 3 -hls_segment_type mpegts \
      -hls_playlist_type event \
      output%d.ts output.m3u8
7. 返回变体播放列表 (main.m3u8):
   #EXTINF:3.000,
   hls/output0.ts
   #EXTINF:3.000,
   hls/output1.ts
   ...
8. 浏览器逐段请求:
   GET /Videos/abc123/hls1/0.ts
   GET /Videos/abc123/hls1/1.ts
   GET /Videos/abc123/hls1/2.ts
   ...

FFmpeg 命令生成

关键代码: Jellyfin.Api/Controllers/DynamicHlsController.cs:1574-1651

private string GetCommandLineArguments(outputPath, state, isEventPlaylist, startNumber)
{
    var videoCodec = _encodingHelper.GetVideoEncoder(state, _encodingOptions);
    var threads = EncodingHelper.GetNumberOfThreads(state, ...);

    // 分段格式选择: mpegts (.ts) 或 fmp4 (.mp4)
    if (segmentContainer == "ts")    "mpegts"
    if (segmentContainer == "mp4")   "fmp4"

    // 生成完整 FFmpeg 命令
    return $"{inputModifier} {inputArg} -threads {threads} {mapArgs}
             {videoArgs} {audioArgs}
             -f hls -hls_time {segmentLength}
             -hls_segment_type {segmentFormat}
             -hls_playlist_type {vod|event}
             output%d{ext} output.m3u8";
}

自适应码率 (ABR)

当客户端请求 master playlist 时,Jellyfin 生成多个码率变体:

#EXTM3U
#EXT-X-STREAM-INF:BANDWIDTH=2000000,RESOLUTION=854x480
main.m3u8?videoBitRate=2000000&maxWidth=854

#EXT-X-STREAM-INF:BANDWIDTH=5000000,RESOLUTION=1280x720
main.m3u8?videoBitRate=5000000&maxWidth=1280

#EXT-X-STREAM-INF:BANDWIDTH=8000000,RESOLUTION=1920x1080
main.m3u8?videoBitRate=8000000&maxWidth=1920

浏览器根据网络状况自动切换码率。

浏览器兼容性

编码格式 Chrome Firefox Safari Edge
H.264 + AAC + .ts
H.265 + fMP4 部分
VP9
AV1

Jellyfin 通过设备配置文件自动选择浏览器支持的编码格式,不兼容时自动启动 FFmpeg 转码。

Rust 最小播放原型

核心思路

最简单的方案:不转码,只做 Direct Play — 先把视频预转成 HLS 格式,用 Rust HTTP 服务提供 .m3u8.ts 文件。

第一步:准备 HLS 视频

用 FFmpeg 把视频预转成 HLS 格式:

ffmpeg -i input.mp4 \
  -c:v libx264 -preset veryfast -b:v 3000k \
  -c:a aac -b:a 128k \
  -f hls -hls_time 5 -hls_segment_type mpegts \
  -hls_playlist_type vod \
  output/playlist.m3u8

生成的文件:

output/
├── playlist.m3u8     # 播放列表
├── playlist0.ts      # 分段 0
├── playlist1.ts      # 分段 1
└── playlist2.ts      # 分段 2

第二步:最小 Rust HTTP 服务

Cargo.toml

[package]
name = "mini-streamer"
version = "0.1.0"
edition = "2021"

[dependencies]
axum = "0.7"
tokio = { version = "1", features = ["full"] }
tower-http = { version = "0.5", features = ["fs", "cors"] }

src/main.rs

use axum::{Router, routing::get, response::IntoResponse, http::header};
use tower_http::{services::ServeDir, cors::CorsLayer};

async fn playlist() -> impl IntoResponse {
    let content = tokio::fs::read_to_string("output/playlist.m3u8")
        .await
        .unwrap();
    (
        [(header::CONTENT_TYPE, "application/vnd.apple.mpegurl")],
        content,
    )
}

async fn segment(
    axum::extract::Path(seg): axum::extract::Path<String>,
) -> impl IntoResponse {
    let path = format!("output/{}", seg);
    let content = tokio::fs::read(&path).await.unwrap();
    (
        [(header::CONTENT_TYPE, "video/mp2t")],
        content,
    )
}

#[tokio::main]
async fn main() {
    let app = Router::new()
        .route("/playlist.m3u8", get(playlist))
        .route("/{seg}", get(segment))
        .layer(CorsLayer::permissive());

    println!("Server running at http://localhost:3000");
    let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
    axum::serve(listener, app).await.unwrap();
}

第三步:浏览器测试

<!DOCTYPE html>
<html>
<head><title>Mini Streamer</title></head>
<body>
  <video controls width="800">
    <source src="http://localhost:3000/playlist.m3u8"
      type="application/vnd.apple.mpegurl">
  </video>
  <script src="https://cdn.jsdelivr.net/npm/hls.js@latest"></script>
  <script>
    if (Hls.isSupported()) {
      var video = document.querySelector("video");
      var hls = new Hls();
      hls.loadSource("http://localhost:3000/playlist.m3u8");
      hls.attachMedia(video);
    }
  </script>
</body>
</html>

注意:Chrome/Firefox 不原生支持 HLS,需要 hls.js 库。

进阶:集成 FFmpeg 实时转码

use std::process::Command;

fn start_transcode(input_path: &str, output_dir: &str) {
    std::fs::create_dir_all(output_dir).unwrap();

    let output = Command::new("ffmpeg")
        .args([
            "-i", input_path,
            "-c:v", "libx264",
            "-preset", "veryfast",
            "-b:v", "3000k",
            "-c:a", "aac",
            "-b:a", "128k",
            "-f", "hls",
            "-hls_time", "5",
            "-hls_segment_type", "mpegts",
            "-hls_playlist_type", "vod",
            "-hls_segment_filename",
            &format!("{}/segment%d.ts", output_dir),
            &format!("{}/playlist.m3u8", output_dir),
        ])
        .spawn()
        .expect("Failed to start ffmpeg");

    output.wait_with_output().unwrap();
}

与 Jellyfin 的架构对比

维度 Jellyfin 最小原型
浏览器请求 REST API Controller Axum route handler
播放列表生成 DynamicHlsHelper 预生成 .m3u8
分段管理 transcodeManager FFmpeg 输出到磁盘
转码 FFmpeg 子进程 FFmpeg 子进程
设备兼容检测 DLNA Profile 跳过 (假设 H.264)
认证 JWT / API Key 跳过

关键注意事项

  • hls.js — Chrome/Firefox 不原生支持 HLS,必须用此 JS 库
  • CORS — 后端必须设置 Access-Control-Allow-Origin,否则浏览器阻止
  • Content-Type.m3u8application/vnd.apple.mpegurl.tsvideo/mp2t
  • FFmpeg — 需要安装在系统 PATH 中

关键文件

文件 说明
Jellyfin.Server/Program.cs 程序入口
Jellyfin.Server/CoreAppHost.cs 应用主机
Emby.Server.Implementations/ApplicationHost.cs 核心 DI/插件/生命周期
Jellyfin.Api/Controllers/ItemsController.cs 媒体项 API
Jellyfin.Api/Controllers/VideosController.cs 视频流 API
Jellyfin.Api/Controllers/UserController.cs 用户管理 API
Emby.Server.Implementations/Library/ 媒体库管理
Emby.Server.Implementations/Session/ 会话管理
MediaBrowser.Controller/Entities/ 媒体实体模型
MediaBrowser.Controller/Plugins/ 插件接口定义
MediaBrowser.Providers/ 元数据提供者
MediaBrowser.MediaEncoding/ FFmpeg 编码集成
MediaBrowser.Model/ 数据模型和 DTO

技术栈

技术 用途
ASP.NET Core Web 框架
Entity Framework Core ORM (SQLite/PostgreSQL)
FFmpeg 媒体转码和编码
SkiaSharp 图片处理 (缩略图生成)
Serilog 日志框架
CommandLine CLI 参数解析
Backlinks (1)
flowchart LR
  n0["AI"]
  n1["Database"]
  n2["Dev Tools"]
  n3["Emoji"]
  n4["Frontend"]
  n5["Game Dev"]
  n6["Collection"]
  n7["Languages"]
  n8["Maps"]
  n9["Media"]
  n10["Monitor"]
  n11["Plans"]
  n12["对象存储基本用法(Bucket / Object / 常用操作)"]
  n13["对象存储数据迁移(R2 → MinIO / 跨厂商搬迁)"]
  n14["Object Storage"]
  n15["挂载 Bucket 为本地文件系统(FUSE Mount)"]
  n16["对象存储签名 URL(Signed URL)原理与实战"]
  n17["对象存储供应商对比(S3 / R2 / OSS / Supabase / MinIO)"]
  n18["Prototypes"]
  n19["Research"]
  n20["Better Auth 源码阅读指南"]
  n21["DuckDB 环境与基本使用"]
  n22["DuckDB 实战研究"]
  n23["DuckDB 模拟数据"]
  n24["PostgreSQL 数据用 DuckDB 加速查询"]
  n25["HK 角色阅读"]
  n26["HK 台词阅读"]
  n27["Hollow Knight 英语主题"]
  n28["HK 物品阅读"]
  n29["HK 地点阅读"]
  n30["HK 世界观 / Lore 阅读"]
  n31["HK 英语学习素材清单"]
  n32["英语学习 Dashboard"]
  n33["English Scraps Archive"]
  n34["English Scraps 使用指南"]
  n35["Jellyfin 源码阅读指南"]
  n36["Lux 资料整理"]
  n37["Nest Commander 学习资料"]
  n38["NestJS 源码阅读指南"]
  n39["Protomaps 自建底图研究"]
  n40["自制 PMTiles 地图(最简单例子)"]
  n41["MapLibre 集成 Protomaps"]
  n42["PMTiles 格式与工具链"]
  n43["上海地区底图项目"]
  n44["Redash 源码阅读指南"]
  n45["Rust 学习计划"]
  n46["shadcn/ui 源码阅读指南"]
  n47["TRIP 项目核心原理与代码阅读指南"]
  n1 --> n22
  n6 --> n0
  n6 --> n1
  n6 --> n2
  n6 --> n3
  n6 --> n4
  n6 --> n5
  n6 --> n7
  n6 --> n8
  n6 --> n9
  n6 --> n10
  n6 --> n11
  n6 --> n19
  n12 --> n13
  n12 --> n16
  n12 --> n17
  n12 --> n18
  n13 --> n12
  n13 --> n15
  n13 --> n16
  n13 --> n17
  n14 --> n12
  n14 --> n13
  n14 --> n15
  n14 --> n16
  n14 --> n17
  n15 --> n12
  n15 --> n16
  n15 --> n17
  n16 --> n18
  n17 --> n12
  n17 --> n13
  n17 --> n16
  n17 --> n18
  n18 --> n12
  n18 --> n15
  n18 --> n16
  n18 --> n17
  n18 --> n39
  n19 --> n20
  n19 --> n22
  n19 --> n32
  n19 --> n35
  n19 --> n36
  n19 --> n37
  n19 --> n38
  n19 --> n39
  n19 --> n44
  n19 --> n45
  n19 --> n46
  n19 --> n47
  n21 --> n23
  n22 --> n1
  n22 --> n19
  n22 --> n21
  n22 --> n23
  n22 --> n24
  n23 --> n21
  n23 --> n24
  n24 --> n22
  n24 --> n23
  n27 --> n25
  n27 --> n26
  n27 --> n28
  n27 --> n29
  n27 --> n30
  n27 --> n31
  n27 --> n34
  n31 --> n26
  n32 --> n27
  n32 --> n33
  n32 --> n34
  n34 --> n32
  n34 --> n33
  n39 --> n8
  n39 --> n40
  n39 --> n41
  n39 --> n42
  n39 --> n43
  n40 --> n42
  n41 --> n18
  n41 --> n43
  n42 --> n40
  n42 --> n43
  n43 --> n41
  n43 --> n42
  click n0 "../../../collection/ai/" "AI"
  click n1 "../../../collection/database/" "Database"
  click n2 "../../../collection/dev-tools/" "Dev Tools"
  click n3 "../../../collection/emoji/" "Emoji"
  click n4 "../../../collection/frontend/" "Frontend"
  click n5 "../../../collection/game-dev/" "Game Dev"
  click n6 "../../../collection/" "Collection"
  click n7 "../../../collection/languages/" "Languages"
  click n8 "../../../collection/maps/" "Maps"
  click n9 "../../../collection/media/" "Media"
  click n10 "../../../collection/monitor/" "Monitor"
  click n11 "../../../collection/scraps/plans/" "Plans"
  click n12 "../../../knowledge/infrastructure/cloud/object-storage/basic-usage/" "对象存储基本用法(Bucket / Object / 常用操作)"
  click n13 "../../../knowledge/infrastructure/cloud/object-storage/data-migration/" "对象存储数据迁移(R2 → MinIO / 跨厂商搬迁)"
  click n14 "../../../knowledge/infrastructure/cloud/object-storage/" "Object Storage"
  click n15 "../../../knowledge/infrastructure/cloud/object-storage/mount-bucket/" "挂载 Bucket 为本地文件系统(FUSE Mount)"
  click n16 "../../../knowledge/infrastructure/cloud/object-storage/signed-url/" "对象存储签名 URL(Signed URL)原理与实战"
  click n17 "../../../knowledge/infrastructure/cloud/object-storage/vendors-comparison/" "对象存储供应商对比(S3 / R2 / OSS / Supabase / MinIO)"
  click n18 "../../../prototypes/" "Prototypes"
  click n19 "../../" "Research"
  click n20 "../better-auth/" "Better Auth 源码阅读指南"
  click n21 "../duckdb/basic-usage/" "DuckDB 环境与基本使用"
  click n22 "../duckdb/" "DuckDB 实战研究"
  click n23 "../duckdb/mock-data/" "DuckDB 模拟数据"
  click n24 "../duckdb/postgresql-acceleration/" "PostgreSQL 数据用 DuckDB 加速查询"
  click n25 "../english/hollow-knight/characters/" "HK 角色阅读"
  click n26 "../english/hollow-knight/dialogues/" "HK 台词阅读"
  click n27 "../english/hollow-knight/" "Hollow Knight 英语主题"
  click n28 "../english/hollow-knight/items/" "HK 物品阅读"
  click n29 "../english/hollow-knight/locations/" "HK 地点阅读"
  click n30 "../english/hollow-knight/lore/" "HK 世界观 / Lore 阅读"
  click n31 "../english/hollow-knight/resources/" "HK 英语学习素材清单"
  click n32 "../english/" "英语学习 Dashboard"
  click n33 "../english/scraps/archive/" "English Scraps Archive"
  click n34 "../english/scraps/" "English Scraps 使用指南"
  click n35 "./" "Jellyfin 源码阅读指南"
  click n36 "../lux/" "Lux 资料整理"
  click n37 "../nest-commander/" "Nest Commander 学习资料"
  click n38 "../nestjs/" "NestJS 源码阅读指南"
  click n39 "../protomaps/" "Protomaps 自建底图研究"
  click n40 "../protomaps/make-own-map/" "自制 PMTiles 地图(最简单例子)"
  click n41 "../protomaps/maplibre/" "MapLibre 集成 Protomaps"
  click n42 "../protomaps/pmtiles/" "PMTiles 格式与工具链"
  click n43 "../protomaps/shanghai-map/" "上海地区底图项目"
  click n44 "../redash/" "Redash 源码阅读指南"
  click n45 "../rust/" "Rust 学习计划"
  click n46 "../shadcn-ui/" "shadcn/ui 源码阅读指南"
  click n47 "../trip/" "TRIP 项目核心原理与代码阅读指南"