You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在浏览器中播放Widevine加密音频?已完成许可证等配置

解决加密音频直接加载播放的问题

核心思路

既然你已经完成DRM相关配置(许可证、服务器证书等),且不想实现分段加载/拖动追加逻辑,核心方案是通过MediaSource一次性加载完整加密音频文件,同时正确关联DRM会话,让浏览器完成解密播放。

具体实现步骤

    1. 初始化DRM会话并绑定到audio元素
      通过navigator.requestMediaKeySystemAccess获取对应DRM系统权限(如Widevine/PlayReady),创建MediaKeys并关联到audio元素,同时处理许可证请求逻辑。
    1. 一次性加载完整加密音频
      用fetch拉取完整音频文件转成ArrayBuffer,等待SourceBuffer就绪后一次性追加数据,无需分段处理。
    1. 完成播放准备
      数据追加完成后结束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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 21:53:18