IPTVnator 离线 DASH ClearKey 测试固件:VP9/Opus 加密资产构造、再生与 e2e 验证全解
2026/9/17 11:37:13 网站建设 项目流程

IPTVnator 离线 DASH ClearKey 测试固件:VP9/Opus 加密资产构造、再生与 e2e 验证全解

【免费下载链接】iptvnator:tv: Cross-platform IPTV player application with multiple features, such as support of m3u and m3u8 playlists, favorites, TV guide, TV archive/catchup and more.项目地址: https://gitcode.com/GitHub_Trending/ip/iptvnator

IPTVnator 是跨平台 IPTV 播放器(支持 m3u/m3u8 播放列表、电视指南、时移回看等)。为了保证 DASH + ClearKey 加密播放链路在 Web 与 Electron 双端的稳定性,仓库在apps/web-e2e/src/fixtures/dash/下维护了一套完全离线、确定性内容的 DASH 固件(fixtures):4 秒时长的 VP9 视频 + Opus 音频,分别打包为 CENC(ClearKey)加密版与明文对照版。本文以 fixtures/dash/README.md 为主线,结合固件生成脚本 generate-fixture.mjs、Web 端 dash-clearkey.e2e.ts 与 Electron 端 dash-clearkey.e2e.ts 的源码实现,讲透这套固件"为什么这样设计、如何被播放、如何重新生成"。

读完本文,你将掌握:IPTVnator 如何用离线资产做加密与非加密 DASH 播放的端到端回归;为什么选 VP9/Opus 与 Shaka Packager(而不是 ffmpeg 加密);固定 ClearKey 凭据如何映射到 m3u 播放列表的#KODIPROP指令;以及如何用一条命令完整再生整套固件。

固件全景:六个文件覆盖"加密 / 明文"两条播放链路

固件目录apps/web-e2e/src/fixtures/dash/下共六个文件,全部为静态点播(on-demand)DASH 资产,服务于 Web 与 Electron 两套 e2e 套件:

文件内容用途
clearkey-video.mp4VP9 视频,CENC(cencAES-CTR,subsample)加密加密视频轨道
clearkey-audio.mp4Opus 音频,CENC 加密加密音频轨道
clearkey.mpd静态 on-demand MPD,指向加密对加密流清单
clear-video.mp4VP9 视频,未加密明文回归用例(clear-DASH)视频轨道
clear-audio.mp4Opus 音频,未加密明文回归用例音频轨道
clear.mpd静态 on-demand MPD,指向明文对明文流清单

两套资产内容同源(同一段测试视频与音频),差异仅在是否经过 Shaka Packager 加密。这样设计让 e2e 可以在同一个测试文件里对比"加密流能否播放"与"明文流能否播放",还能为 DRM 诊断、段加载失败等场景构造可控输入。

固定 ClearKey 测试凭据:KID/KEY 与 m3u 指令的映射

固件使用固定、明显合成(synthetic)的 128 位 ClearKey 测试凭据,可安全提交到仓库:

  • KID00112233445566778899aabbccddeeff
  • KEYffeeddccbbaa99887766554433221100

这两个值在 generate-fixture.mjs 中以导出常量定义,并被加密步骤使用;在 e2e 侧,dash-clearkey.e2e.ts 同样硬编码了这对凭据,用来构造CLEARKEY_LICENSEJSON。

在 IPTVnator 的播放列表生态中,DRM 凭据通过 Kodi 风格的#KODIPROP指令注入 m3u。README 明确指出这对凭据对应于:

#KODIPROP:inputstream.adaptive.license_key=KID:KEY

Web e2e 实际构造的播放列表验证了这一点(见 dash-clearkey.e2e.ts):license_type声明为clearkeylicense_key则是把 KID/KEY 编码为无填充 Base64 的 JSONkty: octtype: temporary)。之所以用 Base64 JSON 而非裸KID:KEY,是因为该用例同时承担了对 issue #1466 的回归验证——PWA 下必须接受无填充(去掉=)的标准 Base64 JSON 格式:

const CLEARKEY_LICENSE = JSON.stringify({ keys: [ { kty: 'oct', kid: Buffer.from(CLEARKEY_KID, 'hex').toString('base64').replace(/=+$/, ''), k: Buffer.from(CLEARKEY_KEY, 'hex').toString('base64').replace(/=+$/, ''), }, ], type: 'temporary', });

对应的播放列表片段为:

#EXTINF:-1 tvg-id="ck-dash" group-title="DASH",ClearKey DASH #KODIPROP:inputstream.adaptive.license_type=clearkey #KODIPROP:inputstream.adaptive.license_key=<Base64 JSON> https://dash-fixture.local/clearkey.mpd

在播放器侧,DRM 凭据最终落到 Shaka Player 的drm.clearKeys配置。见 shaka-video-session.ts:

if (drm?.clearKeys) { player.configure({ drm: { clearKeys: drm.clearKeys } }); }

这里 ClearKey 走的是clearKeys(本地密钥注入,无需 license server);而同一个会话类也会对不支持的 license 类型(如 Widevine)直接发射 DRM 诊断而不再启动引擎(shaka-video-session.ts),这正好被 e2e 的"Widevine DASH 频道显示加密诊断横幅"用例覆盖。

为什么选 VP9 + Opus:一套固件服务两个运行时

README 给出的理由是:Playwright 捆绑的 Chromium 不包含专有编解码器(无 H.264/AAC),而免版税的 VP9/Opus 在 Playwright Chromium 与 Electron 里都能解码——于是一套固件同时喂饱 Web 与 Electron 两套 e2e 套件,无需维护两份资产。

这一选择也体现在生成脚本的参数里(generate-fixture.mjs):视频用testsrc2测试图源 +libvpx-vp9,音频用sine正弦波 +libopus

ffmpeg -y \ -f lavfi -i "testsrc2=duration=4:size=320x180:rate=24" \ -f lavfi -i "sine=frequency=440:duration=4" \ -c:v libvpx-vp9 -b:v 150k -g 24 \ -c:a libopus -b:a 48k \ content-master.tmp.mp4

320×180、24fps、4 秒、150kbps 视频 + 48kbps 音频——资产刻意做得小,让 e2e 在 CI 上保持轻量;testsrc2sine是 ffmpeg 内置信号源,保证内容确定性可合成(无需任何外部媒体素材)。

实际 MPD 中的编解码器标识也印证了这一点。加密版 clearkey.mpd 的视频 Representation 声明codecs="vp09.00.11.08.01.02.02.02.00"mimeType="video/mp4",音频声明codecs="opus"audioSamplingRate="48000"。这个 vp09 codec 字符串在 e2e 里还被断言用于诊断面板的编解码器回显(见 dash-clearkey.e2e.ts)。

为什么用 Shaka Packager 加密:sencsaiz/saio的合规性鸿沟

README 用两个具体失败现象解释了"加密为何必须交给 Shaka Packager,ffmpeg 做不到":

  1. ffmpeg 的 mp4 muxer 只写sencsample-encryption 元数据。Chromium 的解复用器(demuxer)要求saiz/saiobox,缺失时直接以CHUNK_DEMUXER_ERROR_APPEND_FAILED: Sample encryption info is not available失败。也就是说,ffmpeg 产出的"加密 mp4"在 Chromium 里根本无法进入解码流程。

  2. ffmpeg 无法产生 VP9 CENC 绑定(binding)所要求的 subsample 加密。对 VP9 而言,CENC 规范强制使用 subsample 模式(仅加密样本中特定字节范围),整轨全量加密的 VP9 最终会以MEDIA_ERR_DECODE收场。

Shaka Packager 对两者都能产出规范合规的输出。生成脚本中加密变体的完整调用(generate-fixture.mjs):

packager \ "in=content-master.tmp.mp4,stream=video,output=clearkey-video.mp4,drm_label=CK" \ "in=content-master.tmp.mp4,stream=audio,output=clearkey-audio.mp4,drm_label=CK" \ --enable_raw_key_encryption \ --keys "label=CK:key_id=00112233445566778899aabbccddeeff:key=ffeeddccbbaa99887766554433221100" \ --clear_lead 0 \ --protection_scheme cenc \ --mpd_output clearkey.mpd

参数要点:

  • --enable_raw_key_encryption+--keys label=...:key_id=...:key=...:使用 RAW key 直接指定 KID/KEY(即固件那对固定凭据),无需 license server;
  • --protection_scheme cenc:采用cenc(AES-CTR,subsample)加密方案,这是与 Shaka/Chromium 兼容性最好的方案;
  • --clear_lead 0没有明文引导段(clear lead),整个轨道从头加密;
  • --mpd_output:由 Packager 顺带生成 MPD,保证sidx/Initialization/indexRange与实际分片严格对齐。

加密版 MPD 中可以看到标准cenc保护声明(clearkey.mpd):

<ContentProtection value="cenc" schemeIdUri="urn:mpeg:dash:mp4protection:2011" cenc:default_KID="00112233-4455-6677-8899-aabbccddeeff"/> <ContentProtection schemeIdUri="urn:uuid:1077efec-c0b2-4d02-ace3-3c1e52e2fb4b"> <cenc:pssh>AAAANHBzc2gBAAAAEHfv7MCyTQKs4zweUuL7SwAAAAEAESIzRFVmd4iZqrvM3e7/AAAAAA==</cenc:pssh> </ContentProtection>

urn:mpeg:dash:mp4protection:2011声明了 CENC 方案与 default_KID,1077efec-c0b2-4d02-ace3-3c1e52e2fb4b则是 ClearKey 的 UUID(PlayReady 体系内也是该 UUID),cenc:pssh内嵌了 PSSH box 供 EME 直接消费。

MPD 结构:静态 on-demand profile 与字节范围寻址

两份 MPD 都采用profiles="urn:mpeg:dash:profile:isoff-on-demand:2011"type="static"mediaPresentationDuration="PT4S",属于单文件点播型 DASH:不切分 ts/m4s 分片,而是用SegmentBase+ 字节范围(byte range)从单个 mp4 中按需读取初始化段和媒体段。

以加密版为例(clearkey.mpd):

<Representation id="0" bandwidth="149370" codecs="vp09.00.11.08.01.02.02.02.00" mimeType="video/mp4" sar="1:1"> <BaseURL>clearkey-video.mp4</BaseURL> <SegmentBase indexRange="939-982" timescale="12288"> <Initialization range="0-938"/> </SegmentBase> </Representation>

这里indexRange="939-982"指向 mp4 中的sidxbox,Initialization range="0-938"指向moov等初始化数据。播放器(Shaka)拿到 MPD 后,会通过HTTP Range 请求精确抓取这些字节区间——这正是 e2e 的"虚拟固件主机"必须实现 Range 响应(206 Partial Content)的根本原因。

e2e 如何消费固件:虚拟主机 + 路由拦截

固件不依赖真实网络:e2e 通过 Playwright 路由拦截把https://dash-fixture.local虚拟主机上的每个请求(含 Range 请求)转发到本地固件文件(dash-clearkey.e2e.ts):

  • Range头 → 200 +Accept-Ranges: bytes
  • Range头 → 解析bytes=start-end,返回 206 +Content-Range,并用body.subarray(start, end+1)切出真实字节。

同时测试用serviceWorkers: 'block'关闭 Angular Service Worker——因为经过 SW 的请求会绕过 Playwright 路由拦截,虚拟主机将永远无法解析(见 dash-clearkey.e2e.ts 注释)。另外还通过--autoplay-policy=no-user-gesture-required允许无手势自动播放。

Web 套件覆盖了四类场景(dash-clearkey.e2e.ts):

  1. ClearKey 与明文 DASH 频道都能内联播放:验证currentTime > 0.5且无诊断横幅;
  2. ClearKey 从"最近播放 / 收藏"集合重开:冷加载后从 recent、favorites、全局 recent、全局 favorites 四个入口点播,验证持久化频道可复播;
  3. 不支持的 DRM(Widevine)显示加密诊断:断言横幅文案 "This player does not support the stream's DRM configuration."、DRM 声明为 Widevine,且无 MPV/VLC 回退推荐;
  4. 段加载失败 / 清单 DRM 声明:对clear-video.mp4返回 403,断言诊断包含 "Media segment loading"、HTTP 403 与 codec 信息,并验证"复制诊断"到剪贴板的 JSON 结构(engine: 'shaka'stage: 'segment'httpStatus: 403videoCodecs: [...]),且不泄露私有标记、虚拟主机地址或sourceUrl

Web 端播放链路本身由 shaka-video-session.ts 支撑:Shaka Player 模块在首次播放时才懒加载并缓存(loadModule+polyfill.installAll()),start()/stop()为同步入口,内部用generation 计数器 + 操作链保证切台时不会复活过期引擎;加载失败会被分类为诊断问题(classifyShakaPlaybackIssue),player.destroy()会中断在途load()(以LOAD_INTERRUPTED拒绝),避免卡死的清单请求阻塞后续操作。

Electron 端:打包file://渲染器下的真实 ClearKey 证明

固件不止服务 Web 套件。Electron e2e(dash-clearkey.e2e.ts)直接以仓库路径引用同一份apps/web-e2e/src/fixtures/dash目录(DASH_FIXTURE_DIR),用本地 HTTP server 伺服这些文件,在真实 Electron 运行时验证:ClearKey EME 在打包后file://渲染器(安全上下文)下依然可用。这补上了 Playwright 端的一个已知空白——README 与 e2e 代码均注明,ClearKey EME 仅在捆绑的 Chromium 中行为确定(WebKit 缺乏 ClearKey),所以桌面真实运行时由 Electron 套件单独把关。

再生成固件:一条命令与三档 Packager 解析策略

固件可按需重新生成,命令为:

node apps/web-e2e/src/fixtures/dash/generate-fixture.mjs

前置依赖:

  • ffmpeg(README 注明以 7.x 测试),需编译了libvpx-vp9libopus
  • Shaka Packager,按以下顺序解析(见 generate-fixture.mjs):
    1. 环境变量SHAKA_PACKAGER指向的packager二进制;
    2. 已安装的shaka-packagernpm 包(通过createRequire(...).resolve('shaka-packager')找到 launcher,以 Node 运行);
    3. 否则脚本通过npm pack shaka-packager --silent一次性拉取官方 npm 包(内含各平台预编译二进制:packager-linux-x64packager-osx-arm64packager-win-x64.exe等)到临时目录解压并chmod 755后调用;若当前平台/架构没有对应二进制,则报错并提示手动设置SHAKA_PACKAGER

脚本流程(generate-fixture.mjs)三步走:

  1. 用 ffmpeg 合成 4 秒清晰母版content-master.tmp.mp4(VP9+Opus,muxed);
  2. 用 Shaka Packager 对母版做 CENC 加密,产出clearkey-video.mp4/clearkey-audio.mp4/clearkey.mpd
  3. 对母版做明文打包,产出clear-video.mp4/clear-audio.mp4/clear.mpd

最后finally中清理临时母版文件。

可复现性边界:README 明确声明,不同工具版本间不保证字节级一致(byte-exact)——提交进仓库的固件文件才是 CI 的"事实来源"(source of truth)。因此实际工作中应把再生成视为"更新基线"操作:生成后需人工比对差异并重新提交,而不是在 CI 里每次运行都现生成。

实战要点小结

  • 加密 DASH 的 e2e 覆盖需要合规的容器加密senc元数据不足,Chromium 需要saiz/saio;VP9 强制 subsample CENC,这两点都指向 Shaka Packager 而非 ffmpeg;
  • 免版税编解码器(VP9/Opus)是让一套离线固件同时服务 Playwright Chromium 与 Electron 的关键选型;
  • 固定、合成、可提交的 ClearKey 凭据让 e2e 无需 license server 即可完成 EME 全链路验证,license_key既支持裸KID:KEY,也支持无填充 Base64 JSON(PWA 回归用例);
  • 离线固件通过 Playwright 路由拦截 + 虚拟主机 + 完整 Range 语义模拟真实 DASH 请求,保证测试确定性、零外网依赖;
  • 再生成固件是"一次性基线更新"操作:node apps/web-e2e/src/fixtures/dash/generate-fixture.mjs,依赖 ffmpeg(libvpx-vp9 + libopus)与 Shaka Packager(环境变量 / npm 包 / npm pack 三档解析)。

如需深入,可继续阅读:fixtures/dash/README.md、generate-fixture.mjs、Web e2e 用例、Electron e2e 用例、Shaka 引擎实现。

【免费下载链接】iptvnator:tv: Cross-platform IPTV player application with multiple features, such as support of m3u and m3u8 playlists, favorites, TV guide, TV archive/catchup and more.项目地址: https://gitcode.com/GitHub_Trending/ip/iptvnator

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询