MediaSource,视频缓冲

MediaSource, video buffering

我想做一个低延迟的视频流服务和开源(以改进我的网络开发)。 我去年 2 月开始了这个项目,但我没有太多时间继续它。项目 该项目名为 twitch-with-nodejs。反正。 我不清楚 MediaSources 是如何工作的(我认为)。

我的代码在一个客户端中使用 MediaRecorded 并停止它以每 2 秒发送一次视频流量。然后,服务器将它放在内存中 30 秒,然后准备 return 它作为一个流。

另一个客户端想要观看流,因此他每 2 秒向服务器请求一次视频流量。然后,它适用于第一次调用,但不适用于以下调用。

这是我的代码示例: 第一个客户(主播):

this.media = await navigator.mediaDevices[what]({
            audio: this.config.audio,
            video: this.config.video ? {
                width: this.config.width,
                height: this.config.height,
                frameRate: this.config.fps,
                facingMode: (this.config.front ? "user" : "environment")
            } : false
        }).catch(e => {
            console.log(e.name + ": " + e.message);
            this.can = false;
        });

        let chunks = [];

        const video = document.querySelector('video');
        video.srcObject = this.media;
        video.onloadedmetadata = () => {
            video.play()
        }


        let mediaRecorder = new MediaRecorder(this.media, {"mimeType": "video/webm;"});
        mediaRecorder.ondataavailable = function(ev) {
            chunks.push(ev.data);
        }

        setInterval(() => {
            mediaRecorder.stop();
            mediaRecorder.start();
        }, 2000);

        mediaRecorder.onstop = () => {
            let blob = new Blob(chunks, {'type': 'video/webm;'});
            chunks = [];
            const data = new FormData();
            data.append('file', blob);
            fetch("/postStream", {
                "method": "post",
                "body": data
            });
        }

服务器端:

app.post("/postStream", async (req, res) => {
            const fileProperty = req?.files?.file;
            if(!fileProperty?.data || fileProperty.mimetype !== 'video/webm') return res.json(false);
            this.chunks.push({ data: fileProperty?.data, id: i++, date: Date.now() });
            return res.json(true);
        });

        app.get("/playVideo", (req, res) => {
            if(this?.chunks?.length < 1) return res.json(false);
            const buffer = new Buffer.from(this.chunks?.reverse()?.[0]?.data, 'base64')
            const stream = this.bufferToStream(buffer);
            res.setHeader("content-type", "video/webm");
            stream.pipe(res);
        });

还有我的第二个客户,他想看视频 :

const mediaSource = new MediaSource();
            videoPlaying.src = URL.createObjectURL(mediaSource);

            mediaSource.addEventListener('sourceopen', sourceOpen);
            var sourceBuffer;
            const mime = "video/webm; codecs=\"vp8, vorbis\"";

            function fetchSegment(){
                return fetch("/playVideo").then(res => res.arrayBuffer());
            }

            async function sourceOpen(){
                let data = await fetchSegment();
                sourceBuffer = mediaSource.addSourceBuffer(mime);
                sourceBuffer.appendBuffer(data);
                sourceBuffer.addEventListener('updateend', onUpdateEnd);
                videoPlaying.play();
            }

            function onUpdateEnd(){
                //mediaSource.endOfStream();
                //clearInterval(intervalSegment);
            }

            var intervalSegment = setInterval(async () => {
                /* Here I catch the following error :
An attempt was made to use an object that is not, or is no longer, usable
*/
                console.log(mediaSource.readyState, mediaSource.duration); // => Open, Infinit
                let data = await fetchSegment();
                sourceBuffer.appendBuffer(data)

                console.log("Clearing memory...")
                sourceBuffer.remove(0, 2);
            }, 2000)

那么,为什么我会收到错误消息“试图使用一个不可用或不再可用的对象”?如何正确读取流?

好的,对于任何想知道正确遮阳篷的人来说,因为了解遮阳篷很酷。

我试图改变我的代码的工作方式。所以,观看视频的第二个客户有这个代码(现在):

function fetchSegment(){
                return fetch("/playVideo").then(res => res.arrayBuffer());
            }

            var intervalSegment = setInterval(async () => {
                if(sourceBuffer.updating) return;
                let data = await fetchSegment();
                if (queue.length > 0) {
                    queue.push(data);
                } else {
                    sourceBuffer.appendBuffer(data);
                    videoPlaying.play();
                }
            }, 2000)

            async function sourceOpen(){
                let data = await fetchSegment();
                queue.push(data);
                sourceBuffer = mediaSource.addSourceBuffer(mime);
                sourceBuffer.mode = 'sequence';
                sourceBuffer.addEventListener('updateend', onUpdateEnd);
                videoPlaying.play();
                sourceBuffer.appendBuffer(queue.shift());
            }

            function onUpdateEnd(){
                if (queue.length && !sourceBuffer.updating) {
                    sourceBuffer.appendBuffer(queue.shift())
                }else{
                    setTimeout(() => {
                        onUpdateEnd();
                    }, 500)
                }
            }

所以现在,sourceBuffer 的模式是sequence 而不是segments,如果用户更新chunck,则视频不会被推送。而且,如果队列是空的,我不会推入它,而是直接将段添加到 sourceBuffer。