我试图了解 DRM 系统是如何工作的,所以我的旅程开始于尝试使用 Clear Key DRM 系统播放 cenc 加密的 mp4 视频,而不使用 dash.js 或 Shaka Player 等任何库。
我遇到的第一个问题是我并不总是收到“加密”事件。我只在 Safari 上收到“加密”,但在 Google Chrome 和 Firefox 上都没有。
有趣的是,我确实在 Google Chrome 和 Safari 上收到了“waitingforkey”,但在 Firefox 上却没有。
这个事实最让我困惑,因为如果谷歌浏览器知道它需要一个密钥,我假设它必须知道媒体是加密的,那么为什么它不触发“加密”事件呢?
您可以在下面找到我使用的代码。我正在使用一些便利功能。我希望很清楚他们做了什么。如果不是,您可以在此处查看它们的定义。我的示例也在这里在线供您在浏览器中进行测试和调试。
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
}
}
}
我拥有的 cenc encrpyted mp4 文件来自 dash.js 示例页面,所以我认为这不是我问题的根源。
总结一下我的主要问题是:为什么没有触发“加密”事件,或者我认为应该触发它的假设是错误的?
我还认为我花哨的 util 函数可能是问题的原因。可悲的是,情况并非如此。您可以在此处查看没有 utils 文件的我的版本。它的行为就像其他版本一样。
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() {
// video urls are from here https://reference.dashif.org/dash.js/nightly/samples/drm/clearkey.html
// and here https://media.axprod.net/TestVectors/v7-MultiDRM-SingleKey/Manifest_ClearKey.mpd
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()