[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"project-92595":3},{"id":4,"name":5,"fullName":6,"owner":7,"repo":5,"description":8,"homepage":9,"htmlUrl":9,"language":10,"languages":9,"totalLinesOfCode":9,"stars":11,"forks":12,"watchers":13,"openIssues":14,"contributorsCount":15,"subscribersCount":15,"size":15,"stars1d":15,"stars7d":15,"stars30d":16,"stars90d":15,"forks30d":15,"starsTrendScore":15,"compositeScore":17,"rankGlobal":9,"rankLanguage":9,"license":9,"archived":18,"fork":18,"defaultBranch":19,"hasWiki":20,"hasPages":18,"topics":21,"createdAt":9,"pushedAt":9,"updatedAt":22,"readmeContent":23,"aiSummary":24,"trendingCount":15,"starSnapshotCount":15,"syncStatus":25,"lastSyncTime":26,"discoverSource":27},92595,"dv-hls-gateway","iptvorganization\u002Fdv-hls-gateway","iptvorganization","Live stream gateway",null,"Rust",58,15,51,1,0,7,41.31,false,"main",true,[],"2026-07-22 04:02:06","# DV-HLS Gateway\n\nDV-HLS Gateway 是一个纯净的手动推流器，用于把 DASH(MPD) 或 HLS(M3U8) 输入实时转封装为明文 HLS-TS 输出。\n\n它不依赖 ffmpeg \u002F mp4decrypt，运行时只做解密、MP4 解析、TS 封装和直播窗口发布；HEVC、Dolby Vision RPU、HDR 元数据、AAC \u002F AC-3 \u002F EC-3 音频都会尽量原样透传。\n\n## 功能\n\n- 支持 MPD 和 M3U8 输入，统一输出 HLS-TS。\n- 支持 VOD 和 Live，Live 会贴近 live edge、滚动发布并做内存 GC。\n- 支持 HEVC、H.264、Dolby Vision Profile 5\u002F8、HDR10、HLG、SDR。\n- 支持 AAC-LC、AC-3、EC-3，前端可显示语言、codec 和码率。\n- 支持 DASH CENC AES-CTR、HLS AES-128、HLS fMP4 SAMPLE-AES \u002F cbcs。\n- 支持固定 key 和动态取 key。\n- 支持字幕推流，开启字幕时输出 master playlist + media playlist + subtitle playlist。\n- 支持持续转化和按需启停，按需任务默认 5 分钟无播放请求后暂停。\n- 分片只缓存在内存中，不写磁盘。\n- 输出 TS 分片伪装为 `.jpeg` 且响应 `Content-Type: image\u002Fjpeg`，便于 CDN 缓存。\n\n## 目录\n\n- `src\u002F`：主程序源码。\n- `src\u002Ffrontend\u002Findex.html`：内置 Web UI。\n- `examples\u002F`：辅助示例。\n- `examples\u002Fkey_api.php`：动态取 key 接口 PHP 示例。\n- `dv-hls-gateway.example.json`：运行配置示例。\n- `.github\u002Fworkflows\u002Fci.yml`：CI，运行格式检查和测试。\n- `.github\u002Fworkflows\u002Frelease.yml`：多平台二进制构建。\n\n## 构建\n\n### 本地构建\n\n```bash\ncargo build --release\n```\n\n运行：\n\n```bash\n.\u002Ftarget\u002Frelease\u002Fdv-hls-gateway\n```\n\n默认读取二进制同目录的 `dv-hls-gateway.json`。如果文件不存在，程序首次启动会自动生成一个模板。\n\n也可以显式指定配置文件和端口：\n\n```bash\n.\u002Ftarget\u002Frelease\u002Fdv-hls-gateway --config .\u002Fdv-hls-gateway.json --host 0.0.0.0 --port 37201\n```\n\n### GitHub Actions 构建\n\n仓库内置 `Build Release Binaries` workflow，可手动运行，也可推送 `v*` tag 自动构建并发布附件。\n\n手动运行：\n\n1. 打开 GitHub Actions。\n2. 选择 `Build Release Binaries`。\n3. 点击 `Run workflow`。\n4. 填入 `release_tag`，例如 `v0.1.0`。\n\n手动运行会创建或更新对应 tag 的 Release。推送 tag 也会自动发布：\n\n固定产物名：\n\n| 产物 | 运行平台 | Rust target |\n|---|---|---|\n| `dv-hls-gateway-linux-amd64-musl` | Linux x86_64 | `x86_64-unknown-linux-musl` |\n| `dv-hls-gateway-linux-armv7-musl` | Linux ARMv7 hard-float | `armv7-unknown-linux-musleabihf` |\n| `dv-hls-gateway-linux-arm64-musl` | Linux ARM64 | `aarch64-unknown-linux-musl` |\n| `dv-hls-gateway-windows-amd64-musl.exe` | Windows x86_64 | `x86_64-pc-windows-msvc` |\n| `dv-hls-gateway-macos-amd64-musl` | macOS Intel | `x86_64-apple-darwin` |\n| `dv-hls-gateway-macos-arm64-musl` | macOS Apple Silicon | `aarch64-apple-darwin` |\n\n说明：`musl` 是 Linux 目标使用的 libc；Windows\u002FmacOS 没有 musl ABI，项目仍按固定产物名输出，实际 target 以表格为准。\n\n命令行打 tag 触发构建：\n\n```bash\ngit tag v0.1.0\ngit push origin v0.1.0\n```\n\n下载后运行：\n\n```bash\nchmod +x .\u002Fdv-hls-gateway-linux-amd64-musl\n.\u002Fdv-hls-gateway-linux-amd64-musl --config .\u002Fdv-hls-gateway.json --host 0.0.0.0 --port 37201\n```\n\nWindows：\n\n```powershell\n.\\dv-hls-gateway-windows-amd64-musl.exe --config .\\dv-hls-gateway.json --host 0.0.0.0 --port 37201\n```\n\nmacOS：\n\n```bash\nchmod +x .\u002Fdv-hls-gateway-macos-arm64-musl\n.\u002Fdv-hls-gateway-macos-arm64-musl --config .\u002Fdv-hls-gateway.json --host 0.0.0.0 --port 37201\n```\n\n## 配置\n\n推荐先复制示例配置：\n\n```bash\ncp dv-hls-gateway.example.json dv-hls-gateway.json\n```\n\n示例：\n\n```json\n{\n  \"server\": {\n    \"host\": \"0.0.0.0\",\n    \"port\": 37201\n  },\n  \"auth\": {\n    \"key\": \"change-me\"\n  },\n  \"key_api\": {\n    \"url\": \"http:\u002F\u002F127.0.0.1:45689\u002Fkeys\",\n    \"token\": \"change-this-token\",\n    \"attempts\": 12,\n    \"retry_base_ms\": 400,\n    \"retry_max_ms\": 8000\n  }\n}\n```\n\n字段说明：\n\n| 字段 | 说明 |\n|---|---|\n| `server.host` | HTTP 监听地址，常用 `0.0.0.0` |\n| `server.port` | HTTP 监听端口 |\n| `auth.key` | Web 面板和 `\u002Fapi\u002F*` 的访问密钥 |\n| `key_api.url` | 动态取 key 接口 URL |\n| `key_api.token` | 动态取 key 接口的 `X-Token` |\n| `key_api.attempts` | 动态取 key 最大尝试次数 |\n| `key_api.retry_base_ms` | 取 key 失败后的递增重试基础延迟 |\n| `key_api.retry_max_ms` | 单次取 key 重试最大延迟 |\n\n如果旧配置没有 `auth.key`，程序启动时会自动补一个随机密钥并写回配置文件。\n\n## PM2\n\n从配置文件读取端口：\n\n```bash\npm2 start .\u002Fdv-hls-gateway-linux-amd64-musl --name dv-hls-gateway\n```\n\n临时覆盖端口：\n\n```bash\npm2 start .\u002Fdv-hls-gateway-linux-amd64-musl --name dv-hls-gateway -- --port 37201\n```\n\n指定配置文件：\n\n```bash\npm2 start .\u002Fdv-hls-gateway-linux-amd64-musl --name dv-hls-gateway -- --config .\u002Fdv-hls-gateway.json\n```\n\n## Web UI\n\n1. 打开 `http:\u002F\u002F127.0.0.1:37201`。\n2. 输入 `auth.key`。\n3. 填入 MPD \u002F M3U8 URL。\n4. 选择 key 模式：\n   - 固定 key：在 `KEYS` 输入框填一行或多行 `KID:KEY`。\n   - 动态取 key：勾选“动态取 Key”，程序会解析 KID 并调用 `key_api.url`。\n5. 点“解析轨道”。\n6. 选择视频轨、音频轨，可选字幕轨。\n7. 选择“持续转化”或“按需启停”。\n8. 点“启动转封装”，复制 `\u002Fp\u002F\u003Ctask-id>` 播放地址。\n\n没有勾选“推流字幕”时，`\u002Fp\u002F\u003Ctask-id>` 直接返回单层 media playlist。勾选字幕后，`\u002Fp\u002F\u003Ctask-id>` 返回 master playlist，音视频 playlist 伪装为 `\u002Fp\u002F\u003Ctask-id>\u002Fapi`，字幕 playlist 伪装为 `\u002Fp\u002F\u003Ctask-id>\u002Fxyz`。\n\n## 动态取 Key\n\n动态取 key 用于 KID 会变化或输入源存在多 KID 的场景。程序会从 MPD \u002F HLS manifest、init segment、加密信息中解析需要的 KID；本地 key store 中缺少某个 KID 时，会调用配置里的取 key 接口。\n\n### 请求\n\n程序会发起 HTTP POST：\n\n```http\nPOST \u002Fkeys HTTP\u002F1.1\nContent-Type: application\u002Fjson\nX-Token: \u003Ckey_api.token>\n```\n\n请求体必须是 JSON object，字段 `kid` 是字符串数组：\n\n```json\n{\n  \"kid\": [\n    \"00112233445566778899aabbccddeeff\",\n    \"11223344556677889900aabbccddeeff\"\n  ]\n}\n```\n\nKID 格式：\n\n- 32 位十六进制字符串。\n- 可以大小写混用，程序会归一化为小写。\n- 接口实现建议同时兼容带连字符 UUID，内部去掉 `-` 后匹配。\n\n### 响应\n\n响应必须是 JSON 字符串数组，每个元素都是：\n\n```text\nKID:KEY\n```\n\n示例：\n\n```json\n[\n  \"00112233445566778899aabbccddeeff:ffeeddccbbaa99887766554433221100\",\n  \"11223344556677889900aabbccddeeff:00112233445566778899aabbccddeeff\"\n]\n```\n\n响应要求：\n\n- HTTP 状态码必须是 2xx。\n- 响应体必须能解析为 JSON string array。\n- 数组不能为空。\n- 每个元素都必须包含 `:`。\n- `KID` 必须是合法 16 字节十六进制。\n- `KEY` 必须是合法 16 字节十六进制。\n- 响应必须覆盖请求中的所有 KID。少一个 KID 都会被视为失败。\n\n错误响应示例：\n\n```json\n{\n  \"error\": \"missing key\",\n  \"missing\": [\"00112233445566778899aabbccddeeff\"]\n}\n```\n\n程序不会接受这种错误对象作为成功结果，因为成功结果必须是 JSON 字符串数组。\n\n### 重试策略\n\n如果取 key 接口出现以下情况，程序会认为本次取 key 失败并重试：\n\n- 网络连接失败。\n- 请求超时。\n- HTTP 非 2xx。\n- 响应不是 JSON 字符串数组。\n- 数组为空。\n- 某个元素不是 `KID:KEY`。\n- KID \u002F KEY 不是 16 字节十六进制。\n- 返回结果没有覆盖全部请求 KID。\n\n重试次数和延迟由配置控制：\n\n```json\n{\n  \"key_api\": {\n    \"attempts\": 12,\n    \"retry_base_ms\": 400,\n    \"retry_max_ms\": 8000\n  }\n}\n```\n\n延迟是递增的：第 1 次失败等待约 `retry_base_ms`，第 2 次失败等待约 `retry_base_ms * 2`，最高不超过 `retry_max_ms`。\n\n也可以用环境变量临时覆盖：\n\n| 变量 | 作用 |\n|---|---|\n| `DVHLS_KEY_API_URL` | 覆盖 `key_api.url` |\n| `DVHLS_KEY_API_TOKEN` | 覆盖 `key_api.token` |\n| `DVHLS_KEY_API_ATTEMPTS` | 覆盖最大尝试次数 |\n| `DVHLS_KEY_API_RETRY_BASE_MS` | 覆盖基础延迟 |\n| `DVHLS_KEY_API_RETRY_MAX_MS` | 覆盖最大延迟 |\n\n### PHP 示例接口\n\n示例脚本位于：\n\n```text\nexamples\u002Fkey_api.php\n```\n\n准备 key store：\n\n```bash\ncat > keys.json \u003C\u003C'JSON'\n{\n  \"00112233445566778899aabbccddeeff\": \"ffeeddccbbaa99887766554433221100\",\n  \"11223344556677889900aabbccddeeff\": \"00112233445566778899aabbccddeeff\"\n}\nJSON\n```\n\n启动 PHP 内置服务器：\n\n```bash\nDVHLS_KEY_API_TOKEN='change-this-token' \\\nDVHLS_KEY_STORE='.\u002Fkeys.json' \\\nphp -S 127.0.0.1:45689 examples\u002Fkey_api.php\n```\n\n测试接口：\n\n```bash\ncurl -X POST 'http:\u002F\u002F127.0.0.1:45689\u002Fkeys' \\\n  -H 'content-type: application\u002Fjson' \\\n  -H 'X-Token: change-this-token' \\\n  -d '{\n    \"kid\": [\n      \"00112233445566778899aabbccddeeff\",\n      \"11223344556677889900aabbccddeeff\"\n    ]\n  }'\n```\n\n期望返回：\n\n```json\n[\n  \"00112233445566778899aabbccddeeff:ffeeddccbbaa99887766554433221100\",\n  \"11223344556677889900aabbccddeeff:00112233445566778899aabbccddeeff\"\n]\n```\n\n然后在 `dv-hls-gateway.json` 中配置：\n\n```json\n{\n  \"key_api\": {\n    \"url\": \"http:\u002F\u002F127.0.0.1:45689\u002Fkeys\",\n    \"token\": \"change-this-token\",\n    \"attempts\": 12,\n    \"retry_base_ms\": 400,\n    \"retry_max_ms\": 8000\n  }\n}\n```\n\n## API\n\n所有 `\u002Fapi\u002F*` 都需要 `X-Auth-Key`：\n\n```bash\nK=\"X-Auth-Key: $(jq -r .auth.key .\u002Fdv-hls-gateway.json)\"\n```\n\n解析轨道：\n\n```bash\ncurl -X POST http:\u002F\u002F127.0.0.1:37201\u002Fapi\u002Fparse \\\n  -H \"$K\" \\\n  -H 'content-type: application\u002Fjson' \\\n  -d '{\"mpd\":\"\u003CMPD_OR_M3U8_URL>\"}'\n```\n\n创建固定 key 任务：\n\n```bash\ncurl -X POST http:\u002F\u002F127.0.0.1:37201\u002Fapi\u002Ftasks \\\n  -H \"$K\" \\\n  -H 'content-type: application\u002Fjson' \\\n  -d '{\n    \"name\": \"Live task\",\n    \"mpd\": \"\u003CMPD_OR_M3U8_URL>\",\n    \"key_mode\": \"static\",\n    \"keys\": \"00112233445566778899aabbccddeeff:ffeeddccbbaa99887766554433221100\",\n    \"run_mode\": \"always\",\n    \"video_rep_id\": \"\u003Cvideo-rep-id>\",\n    \"audio_rep_id\": \"\u003Caudio-rep-id>\",\n    \"enable_subtitles\": false,\n    \"subtitle_rep_id\": null,\n    \"window\": 6,\n    \"target_duration\": 7\n  }'\n```\n\n创建动态 key 任务：\n\n```bash\ncurl -X POST http:\u002F\u002F127.0.0.1:37201\u002Fapi\u002Ftasks \\\n  -H \"$K\" \\\n  -H 'content-type: application\u002Fjson' \\\n  -d '{\n    \"name\": \"Dynamic key live\",\n    \"mpd\": \"\u003CMPD_OR_M3U8_URL>\",\n    \"key_mode\": \"dynamic\",\n    \"keys\": \"\",\n    \"run_mode\": \"always\",\n    \"video_rep_id\": \"\u003Cvideo-rep-id>\",\n    \"audio_rep_id\": \"\u003Caudio-rep-id>\",\n    \"enable_subtitles\": true,\n    \"subtitle_rep_id\": \"\u003Csubtitle-rep-id>\",\n    \"window\": 6,\n    \"target_duration\": 7\n  }'\n```\n\n任务控制：\n\n```bash\ncurl -X POST -H \"$K\" http:\u002F\u002F127.0.0.1:37201\u002Fapi\u002Ftasks\u002F\u003Ctask-id>\u002Fpause\ncurl -X POST -H \"$K\" http:\u002F\u002F127.0.0.1:37201\u002Fapi\u002Ftasks\u002F\u003Ctask-id>\u002Fstart\ncurl -X POST -H \"$K\" http:\u002F\u002F127.0.0.1:37201\u002Fapi\u002Ftasks\u002F\u003Ctask-id>\u002Fstop\ncurl -X DELETE -H \"$K\" http:\u002F\u002F127.0.0.1:37201\u002Fapi\u002Ftasks\u002F\u003Ctask-id>\n```\n\n播放地址：\n\n```bash\nffplay \"http:\u002F\u002F127.0.0.1:37201\u002Fp\u002F\u003Ctask-id>\"\nvlc \"http:\u002F\u002F127.0.0.1:37201\u002Fp\u002F\u003Ctask-id>\"\n```\n\n## 输出与缓存\n\n默认播放入口：\n\n```text\n\u002Fp\u002F\u003Ctask-id>\n```\n\n未启用字幕：\n\n```text\n\u002Fp\u002F\u003Ctask-id>                    media playlist\n\u002Fp\u002F\u003Ctask-id>\u002Fpicture-\u003Cseq>.jpeg TS 分片\n```\n\n启用字幕：\n\n```text\n\u002Fp\u002F\u003Ctask-id>                    master playlist\n\u002Fp\u002F\u003Ctask-id>\u002Fapi                media playlist\n\u002Fp\u002F\u003Ctask-id>\u002Fxyz                subtitle playlist\n\u002Fp\u002F\u003Ctask-id>\u002Fxyz-\u003Cseq>.txt      WebVTT 字幕内容，txt 后缀与 text\u002Fplain 响应头\n\u002Fp\u002F\u003Ctask-id>\u002Fpicture-\u003Cseq>.jpeg TS 分片\n```\n\nLive 任务的内存保留上限约为：\n\n```text\nLive 窗口段数 + publish_delay 段数 + grace 段数\n```\n\n默认 `window=6`、`publish_delay=1`、`grace=3`，单任务默认最多保留约 `10` 个最终输出分片。VOD 任务使用 `window=0`，会保留全部已产段直到任务删除。\n\n## CDN 缓存规则\n\n分片长缓存：\n\n```text\nhttp.request.uri.path contains \"\u002Fpicture-\" and ends_with(http.request.uri.path, \".jpeg\")\n```\n\n字幕短缓存：\n\n```text\nhttp.request.uri.path contains \"\u002Fxyz-\" and ends_with(http.request.uri.path, \".txt\")\n```\n\n分片与字幕合并缓存表达式：\n\n```text\n(http.request.uri.path contains \"\u002Fpicture-\" and ends_with(http.request.uri.path, \".jpeg\")) or (http.request.uri.path contains \"\u002Fxyz-\" and ends_with(http.request.uri.path, \".txt\"))\n```\n\nplaylist 微缓存或不缓存：\n\n```text\nstarts_with(http.request.uri.path, \"\u002Fp\u002F\") and not (http.request.uri.path contains \"\u002Fpicture-\") and not (http.request.uri.path contains \"\u002Fxyz-\")\n```\n\n规则顺序建议：\n\n1. `.jpeg` 分片长缓存。\n2. `xyz-*.txt` 字幕短缓存。\n3. playlist 微缓存或 bypass。\n\n## 环境变量\n\n| 变量 | 默认 | 作用 |\n|---|---:|---|\n| `MPD_HLS_PUBLISH_DELAY_SEGMENTS` | `1` | Live 输出隐藏最新 N 个已产段 |\n| `MPD_HLS_SHORT_PUBLISH_DELAY_SEGMENTS` | `1` | 短源分片场景 publish delay |\n| `MPD_HLS_SHARED_DOWNLOAD_CONCURRENCY` | `10` | 常规源共享下载并发 |\n| `MPD_HLS_SHORT_SHARED_DOWNLOAD_CONCURRENCY` | `10` | 短分片源共享下载并发 |\n| `MPD_HLS_SEGMENT_FETCH_CONCURRENCY` | `10` | 常规源分片抓取并发 |\n| `MPD_HLS_SHORT_SEGMENT_FETCH_CONCURRENCY` | `10` | 短分片源分片抓取并发 |\n| `MPD_HLS_ADAPTIVE_FETCH` | `1` | 是否启用任务级自适应并发 |\n| `MPD_HLS_ON_DEMAND_IDLE_TIMEOUT_SECS` | `300` | 按需任务空闲暂停秒数 |\n| `DVHLS_KEY_API_URL` | 配置文件 | 覆盖动态 key 接口 URL |\n| `DVHLS_KEY_API_TOKEN` | 配置文件 | 覆盖动态 key 接口 token |\n| `DVHLS_KEY_API_ATTEMPTS` | 配置文件 | 覆盖取 key 尝试次数 |\n| `DVHLS_KEY_API_RETRY_BASE_MS` | 配置文件 | 覆盖取 key 基础重试延迟 |\n| `DVHLS_KEY_API_RETRY_MAX_MS` | 配置文件 | 覆盖取 key 最大重试延迟 |\n\n## 测试\n\n```bash\ncargo fmt -- --check\ncargo test\n```\n\n可选 golden 测试支持外部样本：\n\n```bash\nDVHLS_GOLDEN_SEG_DIR='.\u002Fsample-segments' \\\nDVHLS_GOLDEN_VIDEO_KEY='00112233445566778899aabbccddeeff:ffeeddccbbaa99887766554433221100' \\\nDVHLS_GOLDEN_AUDIO_KEY='11223344556677889900aabbccddeeff:00112233445566778899aabbccddeeff' \\\ncargo test --test cenc_golden\n```\n\n## 注意事项\n\n- 任务和输出分片都在内存里，服务重启后旧 `\u002Fp\u002F\u003Ctask-id>` 会失效。\n- 删除任务会释放该任务的内存分片队列。\n- 浏览器通常不适合播放 HEVC \u002F Dolby Vision，请使用 IINA、VLC、ffplay 或硬件播放器测试。\n- 动态 key 模式下，取 key 接口必须稳定可达；如果接口长期不可用，任务会等待重试而不是发布无法解密的错误分片。\n","DV-HLS Gateway 是一个轻量级实时流媒体网关，将 DASH（MPD）或 HLS（M3U8）源流实时转封装为明文 HLS-TS 输出。核心特点是纯 Rust 实现、零依赖 ffmpeg\u002Fmp4decrypt，支持 AES-CTR\u002FCENC、SAMPLE-AES 等多种解密方式，并原样透传 HEVC、Dolby Vision RPU、HDR 元数据及 AAC\u002FAC-3\u002FEC-3 音频；分片仅驻留内存、伪装为 JPEG 响应以适配 CDN 缓存。适用于需要低延迟、高兼容性、合规解密与 CDN 友好部署的 IPTV、OTT 直播分发场景。",2,"2026-07-09 02:30:27","CREATED_QUERY"]