首页 > 解决方案 > 使用 mp4 文件的 Uint8Array() 块调用 appendBuffer() 后,SourceBuffer 对象上触发错误事件

问题描述

我正在尝试创建一个视频播放器,我想在其中使用服务器的 Range 标头下载 MP4 文件的片段/块(我在客户端 React.js 应用程序和我的应用程序的实际资源服务器之间有一个代理服务器特定的安全原因)。

我正在正确接收缓冲区中的 mp4 文件块,我尝试在控制台上打印数据。但是一旦第一次使用文件的第一个块调用 appendBuffer() 方法,SourceBuffer 就会引发错误事件。

这是代码:

loadSegment = async () => {
        if (!this.state.videoEnded) {
            fetch(`http://localhost:5008/video?id=${this.state.sessionID}&byteCursor=${this.state.byte}&tok=${this.state.token}`, {
                method: "GET"
            }).then((res) => {
                res.arrayBuffer().then(buf => {
                    console.log('buf', buf)
                    let status = res.status
                    console.log('status', status)
                    let newTime = this.state.time + (buf.byteLength / (this.state.contentLength / this.state.videoDuration))
                    let newByte = this.state.byte + buf.byteLength
                    this.setState({
                        time: newTime,
                        byte: newByte
                    }, () => {
                        videoSourceBuffer.appendBuffer(new Uint8Array(buf));
                        console.log('buffer appended')
                        if (status == 200) {
                            console.log('end of stream')
                            this.setState({
                                videoEnded: true
                            })
                        } else if (status == 206) {
                            let video = document.getElementById("video")
                            if (video.paused) {
                              // start playing after first chunk is appended
                              video.play();
                              console.log('video was paused; played')
                            }
                            console.log("this.state.token before incr", this.state.token)
                            
                            this.setState({
                                token: parseInt(this.state.token) + 1,
                                time: newTime
                            }, () => {
                                console.log('token incremented; loadSegment called again')
                                console.log("this.state.token after incr", this.state.token)
                            })
                        }
                    })
                    
                }).catch(e => {
                    console.log(e)
                })
            }).catch(e => {
                console.log(e)
            })
        }
        
    }

如代码所示,我将 API 响应转换为 ArrayBuffer,然后将其转换为 Uint8Array,该 Uint8Array 将传递给 SourceBuffer 对象上的 appendBuffer() 方法。

这些是我的 SourceBuffer 的 updateend 和 MediaSource 的 sourceopen 事件处理程序:


    mediaSourceOpenEventListener = () => {
        console.log("media source open")
        if (MediaSource.isTypeSupported('video/mp4; codecs="avc1.42E01E, mp4a.40.2"')) {
            console.log("mp4 codec supported");
        } else {
            console.log("mp4 codec not supported");
        }
        videoSourceBuffer =
          ms.addSourceBuffer('video/mp4; codecs="avc1.64001E"');
        videoSourceBuffer.mode = 'sequence';
        console.log("created source buffer")
        videoSourceBuffer.addEventListener('updateend', this.videoSourceBufferUpdateEndListener);
        videoSourceBuffer.addEventListener('error', (e) => {
            console.log("Video Source Error: ", e)
        });
        ms.addEventListener('sourceended', (e) => {
            console.log("MS sourceended event: ", e)
        });
        ms.addEventListener('sourceclose', (e) => {
            console.log("MS sourceclose event: ", e)
        });
        this.loadSegment()
    }

    videoSourceBufferUpdateEndListener = () => {
        if (this.state.videoEnded) {
            videoSourceBuffer.onupdateend = null
            ms.onsourceopen = null
            videoSourceBuffer.abort()
        }   
        // videoSourceBuffer.timestampOffset = this.state.time
        console.log('Finished updating buffer');
        this.loadSegment()
    }

最后,在我的 render() JSX 中,我像这样安装了视频标签

<video loop="loop" id="video" height="320px" width="640px" controls="1" onError={(e) => {console.log(e)}}/>

这些是我得到的日志

Services.js:78 Create ms object
Services.js:80 Create ms url object
Services.js:82 got video tag
Services.js:84 src assigned to video tag
Services.js:100 media source open
Services.js:102 mp4 codec supported
Services.js:109 created source buffer
Services.js:140 buf ArrayBuffer(256001) {}
Services.js:142 status 206
Services.js:150 buffer appended
Services.js:161 video was paused; played
Services.js:163 this.state.token before incr 213843
Services.js:169 token incremented; loadSegment called again
Services.js:170 this.state.token after incr 213844
Services.js:112 Video Source Error:  Event {isTrusted: true, type: "error", target: SourceBuffer, currentTarget: SourceBuffer, eventPhase: 2, …}
Services.js:130 Finished updating buffer
Services.js:115 MS sourceended event:  Event {isTrusted: true, type: "sourceended", target: MediaSource, currentTarget: MediaSource, eventPhase: 2, …}
Services.js:373 SyntheticEvent {dispatchConfig: {…}, _targetInst: FiberNode, _dispatchInstances: FiberNode, nativeEvent: Event, _dispatchListeners: ƒ, …}
index.js:1 video tag error  Event {isTrusted: true, type: "error", target: video#video, currentTarget: video#video, eventPhase: 2, …}

SourceBuffer 正在触发错误事件,然后立即触发 updateend 事件。我无法理解这段代码的实际问题是什么。我可能做错了什么?

标签: javascriptreactjsvideomedia-source

解决方案


推荐阅读