
IPTVnator M3U 集合场景下的 DASH/ClearKey DRM 播放修复深度解析【免费下载链接】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 的变更记录 .changes/m3u-collection-drm.md 展开深入讲解 M3U 播放列表中 DASH.mpd频道包括 ClearKey 加密流如何在“最近观看”与“收藏”集合中稳定播放以及为什么旧导入的播放列表无需重新导入即可保留 DRM 密钥。读完本文你将掌握 IPTVnator 的 KODIPROP DRM 元数据解析管线、内置 Shaka 播放引擎的 ClearKey 接入方式、DASH 频道的播放器路由策略以及对应的端到端测试验证方法。变更内容一次集合场景的 DRM 播放修复该变更记录type: fixarea: m3u关联 issue #1590的核心表述非常简短但背后涉及完整的功能链路DASH channels, including ClearKey-protected streams, now play from Recently Viewed and Favorites using the compatible built-in player. Existing imports keep their DRM keys without needing to re-import the playlist.翻译为技术要点本次修复包含两个层面播放器路由从“最近观看Recently Viewed”和“收藏Favorites”集合入口打开 DASH 频道包括带 ClearKey 加密的流时不再进入无法处理 DRM 的外部播放器而是强制使用内置的兼容播放引擎DRM 密钥持久化无论是新导入还是旧导入导入时尚未支持 DRM 元数据的播放列表其频道条目的 DRM 密钥都能在播放时被正确恢复用户无需删除并重新导入播放列表。下面我们逐层剖析这两点是如何在源码中实现的。问题背景为什么 DASH/ClearKey 频道必须走内置播放器在 M3U 播放列表生态中DASH 流以.mpd结尾的 Manifest和受 DRM 保护的流通常由 Kodi 体系下的#KODIPROP扩展行描述。IPTVnator 对这类流的播放路线有明确约束这一点在 dash.utils.ts 的模块注释中写得很清楚DASH streams must always play in a Shaka-capable built-in web engine: external MPV/VLC cannot receive the KODIPROP ClearKey configuration, and Video.js has no DASH bridge yet.原因可拆解为三条外部播放器无法接收密钥MPV/VLC 发起的是自己的 HTTP 请求KODIPROP 中的 ClearKey 配置无法传递过去external-player-payload.util.ts 中shouldAutoLaunchExternalPlayer明确排除了 DASH 频道Video.js 暂无 DASH 桥接内置播放器队列中 Video.js 缺少 DASH 支持Shaka 引擎是唯一内置 DASH 能力者IPTVnator 内置的 Shaka 播放引擎既能解析 MPD也能通过configure({ drm: { clearKeys } })注入本地解密密钥。因此DASH 频道的播放路线被设计为一律走内置引擎忽略外部播放器设置。DASH 频道如何被识别isDashStreamUrl 通过统一归一化的扩展名检测判断 URL 是否为 DASHexport const isDashStreamUrl (url: string | undefined): boolean !!url getPlaybackMediaExtensionFromUrl(url) mpd; export const isDashChannel ( channel: PickChannel, url | null | undefined ): boolean isDashStreamUrl(channel?.url);关键设计是它与播放引擎使用的扩展名检测是同一套逻辑因此stream.MPD、?formatmpd、?extmpd等变体都会被一致识别路由与引擎选择永远不会产生分歧。在 effects.ts 的openWithConfiguredExternalPlayer中播放开始前就有一道 DASH 闸门if (isDashStreamUrl(playbackUrl) || isDashChannel(activeChannel)) { return; // 不启动外部播放器 }同样external-player-payload.util.ts 中的shouldAutoLaunchExternalPlayer也把!isDashChannel(channel)作为自动启动外部播放器的必要条件之一。KODIPROP DRM 元数据解析从原始 M3U 块到类型化结构要让 Shaka 引擎拿到 ClearKey 密钥IPTVnator 首先要把播放列表条目中的#KODIPROP原始行解析成结构化的 DRM 配置。这一层实现在 kodiprop.utils.ts。数据模型channel-drm.interface.ts 定义了两种类型/** Key id (32 lowercase hex chars) mapped to its key (32 lowercase hex chars). */ export interface ChannelDrmClearKeys { [kidHex: string]: string; } export interface ChannelDrm { /** Normalized license type, e.g. clearkey or com.widevine.alpha. */ licenseType: string; /** True only for ClearKey entries with at least one successfully parsed key. */ supported: boolean; clearKeys?: ChannelDrmClearKeys; }语义要点supported为true仅当该条目是 ClearKey 且至少解析出一对密钥其他许可证类型如 Widevine/PlayReady的 licenseType 会原样保留但supported为false这样播放器可以给出有意义的 DRM 诊断信息而不是静默失败。支持的#KODIPROP属性解析器collectKodipropValueskodiprop.utils.ts只认以下三个属性大小写不敏感属性名作用inputstream.adaptive.license_type许可证类型如clearkey、org.w3.clearkey、com.widevine.alphainputstream.adaptive.license_key许可证密钥/地址ClearKey 为密钥本身inputstream.adaptive.drm_legacyKodi 旧式写法license type\|license key当上面两项缺失时回退解析其中drm_legacy的回退逻辑在extractDrmFromRawkodiprop.utils.ts中按|拆分首段作为类型其余部分拼接为密钥。ClearKey 密钥的四种格式parseClearKeyskodiprop.utils.ts支持以下格式单个kid:key十六进制对00112233...:ffeeddcc...逗号分隔的多对密钥kid1:key1,kid2:key2W3C ClearKey 许可证 JSON{keys:[{kty:oct,k:...,kid:...}]}base64url 编码普通 JSON 映射{kid: key}若密钥值以{开头则走 JSON 解析分支parseClearKeysFromJson否则走kid:key对解析分支parseClearKeysFromPairs。任何一对密钥格式不合法整个解析返回undefined最终得到supported: false防止部分密钥导致解密异常。密钥组件归一化normalizeKeyComponentkodiprop.utils.ts会把密钥组件统一为32 位小写十六进制接受四种输入形态纯十六进制32 位小写UUID 风格带连字符的十六进制自动去掉-base64urlW3C 许可证的标准编码-/_还原为//标准 Base64部分播放列表导出器使用如 PWA 场景无填充的 base64见 e2e 中的回归用例 #1466。解码实现decodeBase64同时兼容浏览器atob与 Node Worker 的Buffer保证解析逻辑在渲染进程与后台 Worker 中行为一致。两段式密钥保障导入期持久化 播放期兜底解析本次变更中“Existing imports keep their DRM keys without needing to re-import”的实现依赖导入时持久化与播放时兜底的双保险策略。第一段导入时把 DRM 写进频道数据在 playlist.utils.ts 的createPlaylistObject中每个解析出的条目都会在导入时立即提取 DRM 并挂载到频道对象上items: playlist.items.map((item: ParsedPlaylistItem) { const drm extractDrmFromRaw(item.raw); return { ...item, id: createRandomId(), ...(drm ? { drm } : {}), }; }),其中item.raw是解析器保留的原始 M3U 块包括#EXTINF前后的#KODIPROP行。因此新导入的播放列表在落库时每个频道的drm字段对应 channel.interface.ts 的可选drm?: ChannelDrm就已就位。第二段播放时对旧数据兜底解析对于在 DRM 功能上线前导入的播放列表频道上没有drm字段。播放链路在这里做了兜底从channel.raw现场重新提取。这一逻辑出现在两条独立的解析路径中统一集合的流解析器 stream-resolver.service.tsdrm: channel.drm ?? extractDrmFromRaw(channel.raw),M3U 播放器组件 video-player.component.ts其注释明确指出“Playlists imported before the DRM feature carry no drm field”drm: playbackTarget.drm ?? extractDrmFromRaw(playbackTarget.raw),两条路径都遵循“已有字段优先缺失时从 raw 重建”的原则。由于频道在导入时始终保留了原始 M3U 块raw旧数据即使没有drm字段也能在播放瞬间重建出完整的 ClearKey 配置——这正是“无需重新导入”的机制保障也让从“最近观看/收藏”持久化集合冷启动加载的频道同样受益。最近观看与收藏的持久化链“最近观看”条目在 video-player.component.ts 的persistRecentlyViewedChannel中写入M3uRecentlyViewedItem收藏则在各集合视图上标记。端到端测试证明这些持久化频道在冷加载后依然携带可用的 DRM 配置详见下文测试章节。播放器路由DASH 频道如何被强制导向内置引擎除了外部播放器闸门M3U 播放器组件还做了三层 DASH 路由保证。1. 活跃频道 DASH 判定video-player.component.ts 用activeChannelIsDash同时检查有效播放 URL可能来自 catch-up 时移与频道 URL避免 DASH 会话最终落到无可用播放器的境地readonly activeChannelIsDash computed( () isDashStreamUrl(this.activePlaybackUrl() ?? undefined) || isDashChannel(this.activeChannel()) );2. DASH 播放器覆盖dashPlayerOverridevideo-player.component.ts决定 DASH 频道的最终引擎用户设置的是 ArtPlayer → 保留 ArtPlayer其内置 Shaka 源引擎支持 DASH其他任何选择Video.js、内嵌 MPV、外部 MPV、VLC→ 全部回退到 HTML5 播放器内置 Shaka 引擎宿主。3. 内联播放强制shouldShowInlinePlayervideo-player.component.ts对 DASH 频道无条件返回true注释沿用了 radio 频道的先例“MPV/VLC cannot receive the KODIPROP ClearKey configuration. Checked on the effective (possibly catch-up) URL.”这三层共同保证无论用户配置了何种播放器偏好DASH/ClearKey 频道在“全部频道、分组、最近观看、收藏”任何一个入口激活时都会进入内置的 Shaka 引擎。Shaka 引擎内的 ClearKey 注入与 DRM 诊断最终解密发生在 Shaka 会话层 shaka-video-session.ts。不支持类型的诊断路径start方法shaka-video-session.ts首先检查supported标志当drm !drm.supported时直接终止播放并发出createUnsupportedDrmDiagnostic携带drm.licenseType展示“This player does not support the streams DRM configuration”的诊断横幅而不是黑屏或静默失败。ClearKey 密钥注入在startPlayback中当drm.clearKeys存在时通过 Shaka 的 DRM 配置接口直接注入本地密钥shaka-video-session.tsif (drm?.clearKeys) { player.configure({ drm: { clearKeys: drm.clearKeys } }); }之后player.attach(video)与player.load(url)完成 MPD 加载与解密播放。值得注意的是整个加载过程被封装在enqueue操作链与generation代数检查中一旦会话被 stop/destroy 或切换频道陈旧的加载结果会被丢弃避免竞态下旧视频元素被复用。端到端测试本次修复的可验证证据本次变更对应的自动化验证集中在 apps/web-e2e/src/dash-clearkey.e2e.tsElectron 桌面端有同构用例 apps/electron-backend-e2e/src/dash-clearkey.e2e.ts。测试清单M3U 样例e2e 中的DASH_PLAYLIST是理解 KODIPROP 写法的真实样例#EXTM3U #EXTINF:-1 tvg-idck-dash group-titleDASH,ClearKey DASH #KODIPROP:inputstream.adaptive.license_typeclearkey #KODIPROP:inputstream.adaptive.license_key{keys:[{kty:oct,kid:...,k:...}],type:temporary} https://dash-fixture.local/clearkey.mpd #EXTINF:-1 tvg-idclear-dash group-titleDASH,Clear DASH https://dash-fixture.local/clear.mpd #EXTINF:-1 tvg-idwv-dash group-titleDASH,Widevine DASH #KODIPROP:inputstream.adaptive.license_typecom.widevine.alpha #KODIPROP:inputstream.adaptive.license_keyhttps://license.example.com/wv https://dash-fixture.local/clearkey.mpd?widevine1三个核心用例ClearKey and clear DASH channels play inline导入后依次播放 ClearKey 加密流与无加密流断言视频currentTime 0.5且无诊断横幅ClearKey reopens from recent and favorites collections这正是本次变更的回归测试——依次访问{playlistUrl}/recent、{playlistUrl}/favorites、/workspace/global-recent、/workspace/global-favorites四个集合入口完整页面导航cold load后点击 ClearKey 频道验证均能正常播放该用例还包含收藏标记star的断言unsupported DRM shows the encryption diagnostic播放 Widevine 条目断言诊断横幅出现并显示“DRM declared by source”详情验证supported: false路径。离线 DASH/ClearKey 测试夹具为了不依赖网络且确定性复现fixtures/dash/README.md 记录了整套离线夹具的设计文件内容clearkey-video.mp4VP9 视频CENCcencAES-CTRsubsample加密clearkey-audio.mp4Opus 音频CENC 加密clearkey.mpd加密对对应的静态点播 MPDclear-video.mp4/clear-audio.mp4/clear.mpd无加密对照组clear-DASH 回归场景固定测试凭据为KID: 00112233445566778899aabbccddeeff、KEY: ffeeddccbbaa99887766554433221100显然是合成的可安全入库与 e2e 播放列表中的#KODIPROP:...license_keyKID:KEY值对应。README 中还解释了三个值得注意的工程决策为何用 VP9OpusPlaywright 自带的 Chromium 不含专有编解码器无 H.264/AAC而免版税的 VP9/Opus 在 Chromium 与 Electron 中均可解码一套夹具同时服务两套 e2e为何用 Shaka Packager 加密ffmpeg 的 mp4 muxer 只写senc元数据Chromium demuxer 需要saiz/saio且无法产出 VP9 CENC 绑定要求的 subsample 加密Shaka Packager 能产出规范合规的输出回归生成node apps/web-e2e/src/fixtures/dash/generate-fixture.mjs可重新生成夹具依赖 ffmpeg 7.x 的libvpx-vp9/libopus与 Shaka Packager但已提交文件为准不追求跨工具版本的字节级一致。测试通过 Playwright 路由拦截把虚拟的https://dash-fixture.local主机完全映射到本地夹具文件并支持 HTTP Range 请求Shaka 通过字节区间抓取 init segment 与 sidx。总结与使用启示把这次 fix 的完整链路串起来看导入时解析器保留#KODIPROP原始块channel.raw并在落库时通过extractDrmFromRaw生成channel.drm播放时无论数据新旧channel.drm ?? extractDrmFromRaw(channel.raw)都能恢复 ClearKey 密钥DASH 判定.mpd把频道强制导向内置 Shaka 引擎Shaka 内clearKeys通过player.configure({ drm: { clearKeys } })注入不支持的 DRM 类型走诊断横幅集合入口最近观看/收藏的持久化频道在冷启动后依然携带原始块与 DRM 字段因此能直接播放。对最终用户而言这意味着如果你的 M3U 播放列表包含带#KODIPROPClearKey 配置的 DASH 频道无论从哪个入口全部频道、分组、最近观看、全局/局部收藏打开都能得到一致的解密播放体验而历史上导入的老播放列表也会在播放瞬间自动恢复密钥完全不需要删除重导。对开发者而言本次修复值得借鉴的三点设计是导入期持久化 播放期兜底的双保险数据策略、所有播放器路由共享同一 DASH 判定函数避免路由与引擎选择不一致以及不支持的 DRM 显式诊断而非静默失败的降级哲学。相关实现入口解析层 kodiprop.utils.ts、路由层 video-player.component.ts 与 effects.ts、解密层 shaka-video-session.ts、验证层 dash-clearkey.e2e.ts。【免费下载链接】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),仅供参考