Javascript 无法使用带有socket.io的媒体源从媒体记录器跳入流

Javascript 无法使用带有socket.io的媒体源从媒体记录器跳入流,javascript,socket.io,video-streaming,media-source,web-mediarecorder,Javascript,Socket.io,Video Streaming,Media Source,Web Mediarecorder,以下代码在先加载视频观察客户端,然后再加载网络摄像头客户端时工作正常。但是,如果切换顺序或以任何方式中断流(例如刷新任一客户端),流将失败,媒体源将将其就绪状态更改为关闭 我的假设是,在启动时接收的视频需要初始化头来启动,因为流是在中间读取的,所以它永远不会得到初始化头。我甚至不知道如何将这样的头添加到webm文件中 我试图改变源缓冲区上的序列模式,但没有任何效果。我已经尝试重新启动录像机,这是可行的,但我的最终计划是有多个观察客户端,录像机在每次重新连接时重新启动不是最佳的 摄像机客户端 ma

以下代码在先加载视频观察客户端,然后再加载网络摄像头客户端时工作正常。但是,如果切换顺序或以任何方式中断流(例如刷新任一客户端),流将失败,媒体源将将其就绪状态更改为关闭

我的假设是,在启动时接收的视频需要初始化头来启动,因为流是在中间读取的,所以它永远不会得到初始化头。我甚至不知道如何将这样的头添加到webm文件中

我试图改变源缓冲区上的序列模式,但没有任何效果。我已经尝试重新启动录像机,这是可行的,但我的最终计划是有多个观察客户端,录像机在每次重新连接时重新启动不是最佳的

摄像机客户端

main();
function main() {
    if (hasGetUserMedia()) {
        const constraints = {
            video: {
                facingMode: 'environment',
                frameRate: {
                    ideal: 10,
                    max: 15
                }
            },
            audio: true
        };

        navigator.mediaDevices.getUserMedia(constraints).
        then(stream => {
            setupRecorder(stream);
        });
    }
}

function setupRecorder(stream) {
    let mediaRecorder = new MediaRecorder(stream, {
        mimeType: 'video/webm; codecs="opus, vp9"'
    });

    mediaRecorder.ondataavailable = e => {
        var blob = e.data;
        socket.emit('video', blob);
    }

    mediaRecorder.start(500);
}
服务器只是广播接收到的任何内容

观察客户

var sourceBuffer;
var queue = [];
var mediaSource = new MediaSource();
mediaSource.addEventListener('sourceopen', sourceOpen, false);
main();

socket.on('stream', data => {
    if (mediaSource.readyState == "open") {
        if (sourceBuffer.updating || queue.length > 0) {
            queue.push(data.video);
        } else {
            sourceBuffer.appendBuffer(data.video);
        }
    }
});

function main() {
    videoElement = document.querySelector('#video');
    videoElement.src = URL.createObjectURL(mediaSource);
}

function sourceOpen(e) {
    console.log('open');
    sourceBuffer = mediaSource.addSourceBuffer('video/webm; codecs="opus, vp9"');
    sourceBuffer.addEventListener('updateend', () => {
        console.log(sourceBuffer.updating, mediaSource.readyState);

        if (queue.length > 0 && !sourceBuffer.updating) {
            sourceBuffer.appendBuffer(queue.shift());
        }
    });
}

因此,实际上,代码的工作方式是不正确的,因此套接字发送服务器没有问题。它或者与MediaRecorder或者MediaSource有关

我的假设是,在启动时接收的视频需要初始化头来启动,因为流是在中间读取的,所以它永远不会得到初始化头

要解决这个问题,您需要了解一点WebM格式。WebM只是Matroska(MKV)的一个子集。是在EBML中存储媒体的模式规范。是一种二进制文件格式,可包含任意块。将其视为二进制XML

这意味着您可以使用诸如检查WebM文件之类的工具,并参考Matroska规范来了解发生了什么。例如:

这是对预先录制的WebM文件的检查。它将在浏览器中运行良好。您会注意到有些元素是嵌套的

在每个WebM文件中都有两个顶级元素
EBML
,它定义了这个二进制文件,以及
Segment
,它包含了后面的所有内容

段中
有几个元素对您很重要。其中之一是
曲目
。您会注意到该文件有两个曲目,一个用于Opus中的音频,另一个用于VP9中的视频。另一个重要的块是
Info
,它包含有关时间刻度的信息和有关muxer的一些元数据

在所有这些元数据之后,您可以找到
Cluster
Cluster
Cluster
这些位置可以剪切WebM流,前提是每个
都以关键帧开始

换句话说,您的代码应该执行以下操作:

  • 将第一个
    群集之前的所有数据保存为“初始化数据”
  • 然后在
    群集上拆分
播放时:

  • 使用之前保存的“初始化数据”作为加载的第一件事
  • Cluster
    s中开始加载,然后在流中的任意位置开始加载
现在,谁需要一个关键帧位很重要。据我所知,没有办法配置MediaRecorder来实现这一点,浏览器对此特别挑剔。至少,您必须重新使用服务器端。。。你甚至可能需要重新编码。另见:

将媒体源与socket.io一起使用


我要指出的是,你甚至不需要MediaSource。您肯定不需要Socket.IO。它可以像通过普通HTTP流输出数据一样简单。这可以直接在
元素中加载。(如果您想获得更多控制权,请务必使用MediaSource,但这不是必须的。)

感谢您的回复,Brad,这非常激动人心。我确实尝试过将我的视频加载到视频元素中,但这并不是无缝的导致我使用MediaSource,但我将尝试您推荐的内容。还有一个问题是,我该如何提取元数据来解析集群?@JustinFernald您必须在代码中解析WebM。这在JavaScript中是完全可行的。WebM很容易解码。我想上一次我这么做的时候,我最终写了我自己的demuxer,但看起来NPM上至少有一个软件包可能是你的起点:真的,不过,花一个下午的时间和Matroska规范和十六进制编辑器在一起。一旦你得到它,它就相当简单了!老实说,我一直在研究FFMPEG、Dash和Matrosta,但我离我的目标还远着呢。我对如何将视频发送到FFMPEG,然后接收输出并发送给观察者感到非常困惑。我最初认为,如果我只发送初始化段,然后发送数据,它就会工作,但是,在一些测试之后,流中的任何中断都会破坏流。我知道这有点奇怪,但是布拉德,我很想有机会和你谈谈,好好想想你的想法,因为我对这一切都很陌生。文档帮助还不够。@JustinFernald当然,给我发封电子邮件到brad@audiopump.co当你有空的时候。我可以聘请顾问,但也很乐意免费简短地聊天,看看我是否能为您指出正确的方向。@AdamMarsh我不知道任何现成的demuxer。EBML查看器会向您发送该错误消息,因为您的文件是用于流式传输的,这意味着它的长度不确定。上面的示例只是为了显示这些类型的文件中包含的内容。您仍然需要编写解复用器并使用十六进制编辑器进行调试。你真的只需要在第一个集群上进行拆分。。。所以
0x1f43b675
。。。当您看到这一点时,就有了集群的开始。在此之前的一切本质上都是初始化数据。