如何在浏览器中播放Widevine加密音频?已完成许可证等配置
解决加密音频直接加载播放的问题
核心思路
既然你已经完成DRM相关配置(许可证、服务器证书等),且不想实现分段加载/拖动追加逻辑,核心方案是通过MediaSource一次性加载完整加密音频文件,同时正确关联DRM会话,让浏览器完成解密播放。
具体实现步骤
- 初始化DRM会话并绑定到audio元素
通过navigator.requestMediaKeySystemAccess获取对应DRM系统权限(如Widevine/PlayReady),创建MediaKeys并关联到audio元素,同时处理许可证请求逻辑。
- 初始化DRM会话并绑定到audio元素
- 一次性加载完整加密音频
用fetch拉取完整音频文件转成ArrayBuffer,等待SourceBuffer就绪后一次性追加数据,无需分段处理。
- 一次性加载完整加密音频
- 完成播放准备
数据追加完成后结束MediaSource流,触发音频播放。
- 完成播放准备
代码示例
const audio = document.createElement('audio'); audio.controls = true; document.body.appendChild(audio); // 初始化DRM会话 async function initDRM() { const keySystem = 'com.widevine.alpha'; // 替换为你实际使用的DRM系统标识 const supportedConfigs = await navigator.requestMediaKeySystemAccess(keySystem, [ { initDataTypes: ['cenc'], audioCapabilities: [{ contentType: 'audio/mp4; codecs="mp4a.40.2"' }], serverCertificate: YOUR_SERVER_CERTIFICATE_BUFFER // 替换为你的服务器证书ArrayBuffer } ]); const mediaKeys = await supportedConfigs.createMediaKeys(); await audio.setMediaKeys(mediaKeys); const keySession = mediaKeys.createSession(); // 处理许可证请求 keySession.addEventListener('message', async (e) => { const license = await fetchLicense(e.message); await keySession.update(license); }); return keySession; } // 加载并播放完整加密音频 async function loadAndPlayAudio(audioUrl, keySession) { const mediaSource = new MediaSource(); audio.src = URL.createObjectURL(mediaSource); mediaSource.addEventListener('sourceopen', async () => { const sourceBuffer = mediaSource.addSourceBuffer('audio/mp4; codecs="mp4a.40.2"'); sourceBuffer.mode = 'sequence'; // 关闭分段模式,确保完整加载 // 获取完整音频文件 const response = await fetch(audioUrl); const arrayBuffer = await response.arrayBuffer(); // 等待SourceBuffer就绪后追加数据 await new Promise(resolve => { sourceBuffer.updating ? sourceBuffer.addEventListener('updateend', resolve, { once: true }) : resolve(); }); sourceBuffer.appendBuffer(arrayBuffer); // 数据追加完成后结束流并播放 sourceBuffer.addEventListener('updateend', () => { if (!sourceBuffer.updating && mediaSource.readyState === 'open') { mediaSource.endOfStream(); audio.play(); } }, { once: true }); // 提取初始化数据触发许可证请求 const initData = extractInitData(arrayBuffer); await keySession.generateRequest('cenc', initData); }); } // 提取MP4中的PSSH初始化数据(用于DRM许可证请求) function extractInitData(arrayBuffer) { const view = new DataView(arrayBuffer); let offset = 0; while (offset < view.byteLength) { const size = view.getUint32(offset); const type = String.fromCharCode(...Array.from({length:4}, (_,i) => view.getUint8(offset+4+i))); if (type === 'pssh') { return arrayBuffer.slice(offset, offset + size); } offset += size; } throw new Error('未找到PSSH加密初始化数据'); } // 许可证请求逻辑(替换为你的实际接口) async function fetchLicense(message) { const response = await fetch('/your-license-endpoint', { method: 'POST', body: message, headers: { 'Content-Type': 'application/octet-stream' } }); return response.arrayBuffer(); } // 启动流程 initDRM().then(keySession => { loadAndPlayAudio('your-encrypted-audio-url.mp4', keySession); });
关键注意事项
- MIME类型必须精准:需包含音频编码格式(如
mp4a.40.2对应AAC),否则SourceBuffer无法创建。 - 避免分段逻辑:设置
sourceBuffer.mode = 'sequence'后,整个音频文件会被完整加载到缓冲区,无需处理拖动时的动态追加。 - 许可证触发时机:手动提取音频文件中的PSSH初始化数据触发许可证请求,比浏览器自动触发更可靠。
- 大文件报错修复:你之前遇到的长音频报错、开头内容丢失问题,大概率是未等待
updateend事件就追加数据导致的,上述代码通过等待SourceBuffer就绪再追加,可解决该问题。
为什么直接设置audio.src无效?
浏览器不会为直接指定的加密媒体URL自动创建DRM解密会话,必须通过MediaKeys绑定到媒体元素,并通过MediaSource加载内容,才能将解密逻辑与播放流程关联。
内容的提问来源于stack exchange,提问作者shy
相关产品推荐
相关产品推荐

