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

向SourceBuffer输入cenc加密mp4数据时encrypted事件未触发问题咨询

Clear Key DRM播放cenc加密MP4时encrypted事件触发异常问题

问题表现

  • 仅在Safari浏览器上能触发encrypted事件,Chrome和Firefox浏览器均无法触发该事件
  • Chrome和Safari上能收到waitingforkey事件,但Firefox上无法收到该事件
  • 已确认加密片源来自dash.js官方示例,排除片源本身问题;已排除自定义工具函数的影响,无工具函数依赖的版本表现完全一致

核心疑问

Chrome能识别到媒体播放需要密钥,说明浏览器已经判断出媒体处于加密状态,为什么不会触发encrypted事件?

测试代码

带工具函数版本

async function playClearkeyVideoFromUrls(videoElement, initUrl, urls) {
    // for debugging purposes
    videoElement.addEventListener(`waitingforkey`, () => console.log(`Event: waitingforkey`))
    videoElement.addEventListener(`encrypted`, () => console.log(`Event: encrypted`))
    videoElement.addEventListener(`error`, function () {
        console.log(`Event: HTMLMediaElement.onerror`)
        console.log(this.error)
    })

    // we create a MediaSource
    const mediaSource = new MediaSource()

    // attach the MediaSource to the Video tag, only then it will fire the "sourceopen" event
    videoElement.src = URL.createObjectURL(mediaSource)

    // add a SourceBuffer to the MediaSource, we need to specify the MIME type of the video we want to play
    const sourceBuffer = await mediaSource.asyncAddSourceBuffer(`video/mp4;codecs="avc1.64001f"`)

    // for debugging purposes
    sourceBuffer.addEventListener(`error`, e => {
        console.log(`Event: SourceBuffer.onerror`);
        console.log(e)
    })

    // append the first (init) segment
    console.log(`Appending the first (init) segment`)
    await sourceBuffer.asyncAppendBuffer(await fetchArrayBuffer(initUrl), videoElement)

    // here I expect the "encrypted" AND "waitingforkey" event to fire

    // now append the rest of the segments
    for (let i = 0; i < urls.length; i++) {
        const url = urls[i]
        console.log(`Appending a segment ...`)
        if (!await sourceBuffer.asyncAppendBuffer(await fetchArrayBuffer(url), videoElement)) {
            console.log(`Canceling playback as an error has occurred.`)
            console.log(videoElement.error)
            break
        }
    }
}

无工具函数依赖版本

let initUrl
let urls
let segmentIndex = 0


Number.prototype.toStringPadded = function(size) {
    let thisString = this.toString();
    while (thisString.length < size) thisString = "0" + thisString;
    return thisString;
}

async function fetchArrayBuffer(url) {
    return await (await (await fetch(url)).blob()).arrayBuffer()
}

async function updateend() {
    console.log(`Event: updateend`)

    this.appendBuffer(await fetchArrayBuffer(urls[segmentIndex]))
    segmentIndex++
    if (segmentIndex === urls.length) {
        this.removeEventListener(`updateend`, updateend)
    }
    console.log(`Appended segment with id ${segmentIndex}.`)
}

async function sourceopen() {
    console.log(`Event: sourceopen`)

    // add a SourceBuffer to the MediaSource, we need to specify the MIME type of the video we want to play
    const sourceBuffer = this.addSourceBuffer(`video/mp4;codecs="avc1.64001f"`)

    // for debugging purposes
    sourceBuffer.addEventListener(`error`, e => {
        console.log(`Event: SourceBuffer.onerror`);
        console.log(e)
    })

    sourceBuffer.addEventListener(`updateend`, updateend)
    sourceBuffer.appendBuffer(await fetchArrayBuffer(initUrl))
}

async function playClearkeyVideoFromUrls(videoElement) {
    // for debugging purposes
    videoElement.addEventListener(`waitingforkey`, (event) => {
        console.log(`Event: waitingforkey`)
        console.log(event)
    })
    videoElement.addEventListener(`encrypted`, (mediaEncryptedEvent) => {
        console.log(`Event: encrypted`)
        console.log(mediaEncryptedEvent)
    })
    videoElement.addEventListener(`error`, function () {
        console.log(`Event: HTMLMediaElement.onerror`)
        console.log(this.error)
    })

    // we create a MediaSource
    const mediaSource = new MediaSource()
    mediaSource.addEventListener(`sourceopen`, sourceopen)

    // attach the MediaSource to the Video tag, only then it will fire the "sourceopen" event
    videoElement.src = URL.createObjectURL(mediaSource)
}

async function testPlayClearkeyVideoFromUrls() {
    const streamId = 1
    initUrl = `https://media.axprod.net/TestVectors/v7-MultiDRM-SingleKey/${streamId}/init.mp4`
    const videoUrlPrefix = `https://media.axprod.net/TestVectors/v7-MultiDRM-SingleKey/${streamId}/`
    const videoUrlSuffix = `.m4s`
    const numberOfSegments = 4

    // first we generate our urls we will download
    urls = []
    for (let i = 0; i < numberOfSegments; i++) {
        const url = `${videoUrlPrefix}${(i + 1).toStringPadded(4)}${videoUrlSuffix}`
        urls.push(url)
    }
    const videoElement = document.querySelector(`video`)

    await playClearkeyVideoFromUrls(videoElement)
}

testPlayClearkeyVideoFromUrls()

问题原因及解决方案

触发异常的核心原因

Chrome、Firefox的EME(加密媒体扩展)实现有明确的前置要求:必须提前为video元素绑定MediaKeys实例,浏览器才会触发encrypted事件。Safari的EME实现没有这个前置限制,所以仅在Safari上能正常收到事件。
另外Firefox的waitingforkey事件触发要求MIME类型中明确声明加密方式,缺失声明的情况下会忽略该事件。

修复步骤

  1. 初始化阶段提前调用navigator.requestMediaKeySystemAccess获取Clear Key的访问权限,创建MediaKeys实例后通过videoElement.setMediaKeys()方法绑定到video元素上,不要等待事件触发后再创建实例。
  2. 补充SourceBuffer的MIME类型声明,将原MIME类型修改为video/mp4;codecs="avc1.64001f";encryption=cenc,明确告知浏览器当前流采用cenc加密。
  3. Firefox环境下确认about:config中的media.eme.enabled配置项为开启状态,默认状态为开启,部分定制版浏览器可能会关闭该配置。

完成以上修改后即可在三大浏览器上稳定触发encrypted和waitingforkey事件。

内容的提问来源于stack exchange,提问作者Blubberlase

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.23 23:57:05