如何单独播放由 MediaRecorder 创建的 WEBM 文件

How to play WEBM files individually which are created by MediaRecorder

为了录制音频和视频,我正在 MediaRecorder API 的 ondataavailable 下创建 webm 文件。我必须单独播放每个创建的 webm 文件。

Mediarecorder api 仅将 header 信息插入第一个块(webm 文件),因此如果没有 header 信息,其余块将无法单独播放。

按照建议 link 1 and link 2,我已经从第一个块

中提取了 header 信息
// for the most regular webm files, the header information exists
// between 0 to 189 Uint8 array elements

const headerIinformation = arrayBufferFirstChunk.slice(0, 189); 

并将此 header 信息附加到第二个块中,第二个块仍然无法播放,但这次浏览器显示视频的海报(单帧)和两个块的总时长,eg:10秒;每个块的持续时间为 5 秒。

我用十六进制编辑器完成的相同 header-information 事情。我在编辑器中打开 webm 文件并从第一个 webm 文件复制前 190 个元素并将其放入第二个文件,如下图所示,即使这次,第二个 webm 文件也无法播放,结果与前面的示例相同。

红色表示 header 信息:

这次我从第一个 webm 文件中复制了 header 和集群信息,并将其放入第二个文件中,如下图所示,但没有成功,

问题

我哪里做错了?

有什么方法可以单独播放 webm files/chunks 吗?

注意:我无法使用 MediaSource 来播放这些块。

编辑 1

正如@Brad 所建议的,我想将第一个集群之前的所有内容插入到后面的集群中。我有几个持续时间为 5 秒的 webm 文件。深入研究文件后,我才知道,几乎每个备用文件都没有簇点(没有 0x1F43B675)。

在这里我很困惑,我必须在每个文件的开头或每个第一个簇的开头插入 header 信息(初始化数据)?如果我选择后面的选项,那么没有任何簇的webm文件将如何播放?

或者,首先我需要制作每个 webm 文件,使其在一开始就有集群,这样我就可以在这些文件的集群之前添加 header 信息?

编辑 2

经过一些挖掘和阅读 this ,我得出的结论是每个 webm 文件都需要 header 信息、集群和实际数据。

// for the most regular webm files, the header information exists

// between 0 to 189 Uint8 array elements

在没有看到实际文件数据的情况下很难说,但这可能是错误的。 “header 信息”需要是第一个 Cluster element 之前的所有内容。也就是说,您希望保留从文件开头到看到 0x1F43B675 之前的所有数据,并将其视为初始化数据。这个 can/will 因文件而异。在我的测试文件中,这发生在 1 KB 之后。

and perpended this header information into second chunk, still the second chunk could not play, but this time the browser is showing poster (single frame) of video and duration of sum of two chunks, eg:10 seconds; duration of each chunk is 5 second.

从 MediaRecorder 输出的块与分段无关,并且可以在不同时间出现。您实际上想要拆分 Cluster 元素。这意味着您需要解析此 WebM 文件,至少要在其标识符 0x1F43B675 出现时拆分出集群。

Is there any way that we can play the webm files/chunks individually ?

您的方向是正确的,只需将第一个集群之前的所有内容添加到后面的集群。

一旦你完成了这项工作,你可能会遇到的下一个问题是你将无法仅对任何集群执行此操作。第一个集群 必须 以关键帧开头,否则浏览器将无法对其进行解码。 Chrome 会跳到下一个集群,跳到某个点,但它不可靠。遗憾的是,无法使用 MediaRecorder 配置关键帧放置。如果你足够幸运能够处理这个视频 server-side,下面是使用 FFmpeg 处理的方法:https://whosebug.com/a/45172617/362536

好吧,看起来这并不像您必须扫描 blob 以找到魔法值那么容易。

let offset = -1;
let value = 0;
const magicNumber = parseInt("0x1F43B675".match(/[a-fA-F0-9]{2}/g).reverse().join(''), 16)

while(value !== magicNumber) {
  offset = offset + 1;

  try {
    const arr = await firstChunk.slice(offset, offset + 4).arrayBuffer().then(buffer => new Int32Array(buffer));
    value = arr[0];
  }
  catch(error) {
    return;
  }
}

offset = offset + 4;

答案是193199

const header = firstChunk.slice(0, offset);
const blobType = firstChunk.type;
const blob = new Blob([header, chunk], { type: blobType });

好了。现在的问题是我是怎么得到这个号码的?为什么不是 42 的倍数?

暴力破解

嗯,逻辑很简单,录制视频,收集块,切片第一个块,计算新的 blob 并尝试用 HTMLVideoElement 播放它。如果失败增加偏移量。

(async() => {

    const microphoneAudioStream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true });

    const mediaRecorder = new MediaRecorder(microphoneAudioStream);
    let chunks = [];

    mediaRecorder.addEventListener('dataavailable', (event) => {
        const blob = event.data;
        chunks = [...chunks, blob];
    });

    mediaRecorder.addEventListener("stop", async () => {
        const [firstChunk, ...restofChunks] = chunks;
        const [secondBlob] = restofChunks;
        const blobType = firstChunk.type;

        let index = 0;
        const video = document.createElement("video");

        while(index < 1000) {
            const header = firstChunk.slice(0, index);
            const blob = new Blob([header, secondBlob], { type: blobType });
            const url = window.URL.createObjectURL(blob);

            try {
                video.setAttribute("src", url);
                await video.play();
                console.log(index);
                break;
            }
            catch(error) {

            }

            window.URL.revokeObjectURL(url);

            index++;
        }
    })

    mediaRecorder.start(200);

    const stop = () => {
        mediaRecorder.stop();
    }

    setTimeout(stop, 400)

})();

我注意到对于 MediaRecorder.start 中较小的 timeslice 参数和 setTimeout 中的超时参数,header 偏移量变为 1 . 可惜还没42.