DRM视频在Google Cast(CAF)接收器上播放异常问题咨询
CAF(Cast接收器)DRM加密视频播放故障分析
故障现象
- 1个DRM频道可触发播放流程,但仅输出正常音频,无视频画面;其余DRM频道完全无法启动播放
- 运行时报错:
[Cast.framework.media.shakaplayer] Category: 7 Code: 7000 - 控制台网络面板仅完成MPD清单文件加载,未发起任何音视频分片的网络请求
- 故障发生时的接收器端实现代码:
const context = cast.framework.CastReceiverContext.getInstance(); const playerManager = context.getPlayerManager(); const playbackConfig = new cast.framework.PlaybackConfig(); const ContentType = { DASH: 'application/dash+xml', HLS: 'application/x-mpegurl' }; const mediaFormatID = { DASH: 2, HLS: 4 }; playerManager.setMessageInterceptor( cast.framework.messages.MessageType.LOAD, request => { return new Promise((resolve, _reject) => { // Configure player to parse DASH content if (request.media.metadata.mediaFormatID == mediaFormatID.DASH) { request.media.contentUrl = request.media.contentUrl; request.media.contentType = ContentType.DASH; // Customize the license url for playback if (request.media.metadata.licenseUrl) { playbackConfig.licenseUrl = request.media.metadata.licenseUrl; playbackConfig.protectionSystem = cast.framework.ContentProtection.WIDEVINE; let token = request.media.metadata.token; let contentId = request.media.metadata.contentId; playbackConfig.licenseRequestHandler = requestInfo => { let body = { token: token, drm_info: Array.apply(null, new Uint8Array(requestInfo.content)), contentId: contentId }; body = JSON.stringify(body); requestInfo.content = body; requestInfo.headers["Content-Type"] = "application/json"; }; } } else { request.media.contentType = ContentType.HLS; request.media.contentUrl = request.media.contentUrl; request.media.hlsSegmentFormat = cast.framework.messages.HlsSegmentFormat.FMP4; request.media.hlsVideoSegmentFormat = cast.framework.messages.HlsVideoSegmentFormat.FMP4; } // Add metadata let metadata = new cast.framework.messages.GenericMediaMetadata(); metadata.title = request.media.metadata.title; metadata.subtitle = request.media.metadata.subtitle; request.media.metadata = metadata; resolve(request); }); }); playbackConfig.licenseHandler = data => { return new Promise((resolve, _reject) => { resolve(new Uint8Array(data)); }); }; context.start({playbackConfig: playbackConfig});
故障根因
报错Category:7 Code:7000对应Shaka Player内置错误码LICENSE_REQUEST_FAILED,即DRM授权流程未正常完成,导致播放器解析完MPD清单后无法进入分片拉取环节,结合代码实现具体问题点如下:
- PlaybackConfig配置不生效:
context.start()调用时会一次性读取传入的playbackConfig完成播放器初始化,后续在LOAD拦截器中直接修改全局playbackConfig变量的属性不会被播放器感知,DRM授权地址、请求处理逻辑等核心配置完全没有加载到播放器实例中。 - License响应解析逻辑错误:当前实现中
licenseHandler直接将授权接口返回的完整响应转为Uint8Array返回,而你在licenseRequestHandler中已经将请求格式改为JSON,绝大多数DRM服务端对应返回的也是JSON格式响应(通常会把二进制license做base64编码放在固定字段中),直接转格式会导致播放器无法识别有效license内容,部分流音频轨道DRM校验逻辑容错性较高可正常播放,视频轨道校验严格直接失败,就会出现有音无画的现象。 - 参数作用域风险:LOAD拦截器中声明的token、contentId为块级作用域变量,连续切换不同DRM频道时容易出现参数串用,导致授权请求带上旧频道的鉴权参数,直接鉴权失败。
- 多密钥场景配置缺失:绝大多数Widevine加密流会对音频、视频轨道使用不同密钥加密,当前实现未配置DRM安全等级(robustness)参数,也未开启多密钥支持,会导致单轨道解密成功、另一轨道解密失败。
- 元数据覆盖风险:拦截器中完全重建GenericMediaMetadata替换原有metadata对象,会抹除发送端传入的DRM相关自定义字段,导致部分DRM参数丢失。
修复方案
- 不要在
context.start()前定义静态全局playbackConfig,在LOAD拦截器中根据当前请求的媒体信息动态生成playbackConfig,调用playerManager.setPlaybackConfig()实时更新到播放器实例,保证DRM配置生效。 - 修改
licenseHandler逻辑,先解析接口返回的JSON响应,提取出真正的license二进制数据(如果服务端返回base64编码的license需要先做解码)再返回给播放器。 - 补充Widevine DRM的robustness配置,适配多密钥加密场景。
- 不要全量替换metadata对象,仅在原有metadata基础上更新title、subtitle字段,避免丢失原有参数。
修复后的核心代码参考:
const context = cast.framework.CastReceiverContext.getInstance(); const playerManager = context.getPlayerManager(); const ContentType = { DASH: 'application/dash+xml', HLS: 'application/x-mpegurl' }; const mediaFormatID = { DASH: 2, HLS: 4 }; playerManager.setMessageInterceptor( cast.framework.messages.MessageType.LOAD, request => { return new Promise((resolve) => { if (request.media.metadata.mediaFormatID == mediaFormatID.DASH) { request.media.contentType = ContentType.DASH; const licenseUrl = request.media.metadata.licenseUrl; if (licenseUrl) { const playbackConfig = new cast.framework.PlaybackConfig(); playbackConfig.licenseUrl = licenseUrl; playbackConfig.protectionSystem = cast.framework.ContentProtection.WIDEVINE; // 配置DRM安全等级,适配多密钥场景 playbackConfig.keySystemConfig = [ { keySystem: cast.framework.ContentProtection.WIDEVINE, robustness: 'SW_SECURE_CRYPTO' } ]; // 固定当前请求的鉴权参数,避免作用域串值 const token = request.media.metadata.token; const contentId = request.media.metadata.contentId; playbackConfig.licenseRequestHandler = requestInfo => { const body = JSON.stringify({ token: token, drm_info: Array.from(new Uint8Array(requestInfo.content)), contentId: contentId }); requestInfo.content = body; requestInfo.headers["Content-Type"] = "application/json"; }; playbackConfig.licenseHandler = (responseData) => { return new Promise((resolve) => { // 按实际服务端返回结构解析,示例为提取license字段并做base64解码 const res = JSON.parse(new TextDecoder().decode(responseData)); const licenseBin = Uint8Array.from(atob(res.license), c => c.charCodeAt(0)); resolve(licenseBin); }); }; // 动态更新播放器配置 playerManager.setPlaybackConfig(playbackConfig); } } else { request.media.contentType = ContentType.HLS; request.media.hlsSegmentFormat = cast.framework.messages.HlsSegmentFormat.FMP4; request.media.hlsVideoSegmentFormat = cast.framework.messages.HlsVideoSegmentFormat.FMP4; } // 仅更新元数据字段,不替换整个对象 if (request.media.metadata) { request.media.metadata.title = request.media.metadata.title || ''; request.media.metadata.subtitle = request.media.metadata.subtitle || ''; } resolve(request); }); } ); context.start();
内容的提问来源于stack exchange,提问作者Алексей Обронов
相关产品推荐
相关产品推荐

