You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用MSE与mp4box.js播放多视频轨道遇阻求助

问题

开发Web应用时,使用Media Source Extensions (MSE)和MP4Box.js从单个MP4文件播放两个独立视频轨道,遇到以下问题:

  • 视频元素无任何内容显示
  • 已成功追加初始化段和媒体段,但SourceBuffer.buffered.length始终为0
  • 控制台无错误日志输出,确认MIME类型匹配视频编码且浏览器支持该类型

已遵循标准流程:为每个视频元素初始化MediaSource和SourceBuffer,通过MP4Box.js处理文件、提取轨道,先追加初始化段再追加媒体段,但仍无法正常播放。

实现代码

<!DOCTYPE html>
<html>

<head>
    <title>Video tracks player</title>
    <script src="https://gpac.github.io/mp4box.js/dist/mp4box.all.js"></script>
</head>

<body>
    <video id="video1" controls width="500"></video>
    <video id="video2" controls width="500"></video>

    <script>
        const video1 = document.getElementById('video1');
        const video2 = document.getElementById('video2');
        const assetURL = 'video.mp4'; // Replace with your video file path

        function initVideoPlayer(video, trackIndex) {
            console.log('Initializing video player', { video, trackIndex });

            const mediaSource = new MediaSource();
            video.src = URL.createObjectURL(mediaSource);
            console.log('MediaSource and video source set', { mediaSource, videoSrc: video.src });

            mediaSource.addEventListener('sourceopen', () => {
                console.log('MediaSource opened', { readyState: mediaSource.readyState });

                fetch(assetURL)
                    .then(response => {
                        console.log('Video file fetched', { assetURL, status: response.status });
                        return response.arrayBuffer();
                    })
                    .then(arrayBuffer => {
                        console.log('\n\nArrayBuffer obtained from fetch', { byteLength: arrayBuffer.byteLength });

                        const mp4boxFile = MP4Box.createFile();
                        console.log('MP4Box file created');

                        mp4boxFile.onReady = (info) => {
                            console.log('MP4Box file is ready', { info });

                            const videoTracks = info.tracks.filter(track => track.type === "video");
                            console.log('Video tracks filtered', { videoTracks });

                            if (videoTracks[trackIndex]) {
                                const track = videoTracks[trackIndex];
                                console.log('Processing track', { trackIndex, track });

                                const mime = `video/mp4; codecs="${track.codec}"`;
                                const sourceBuffer = mediaSource.addSourceBuffer(mime);
                                console.log('SourceBuffer created', { mime, sourceBuffer });

                                // Set segment options
                                mp4boxFile.setSegmentOptions(track.id, sourceBuffer, { nbSamples: track.nb_samples });
                                console.log('Segment options set', { trackId: track.id, nbSamples: track.nb_samples });

                                // Append initialization segments first
                                const initSegs = mp4boxFile.initializeSegmentation();
                                initSegs.forEach((initSeg) => {
                                    if (initSeg.id === track.id) {
                                        sourceBuffer.appendBuffer(initSeg.buffer);
                                        console.log('Appended initialization segment', { initSeg }, initSeg.user.buffered);
                                    }
                                });

                                sourceBuffer.addEventListener('updateend', () => {
                                    console.log('updateend event after appending init segment', { sourceBuffer });
                                });
                                sourceBuffer.addEventListener('error', () => {
                                    console.error('Error while appending init segment', { sourceBuffer });
                                });

                                // Handle media segments
                                mp4boxFile.onSegment = (id, user, buffer) => {
                                    console.log('Received segment', { trackId: id, bufferByteLength: buffer.byteLength });
                                    if (!sourceBuffer.updating) {
                                        sourceBuffer.appendBuffer(buffer);
                                        console.log('Appended media segment to SourceBuffer', { buffer });
                                    }
                                };

                                mp4boxFile.start();
                                console.log('MP4Box file start called');
                            }
                        };

                        arrayBuffer.fileStart = 0;
                        mp4boxFile.appendBuffer(arrayBuffer);
                        console.log('Appended ArrayBuffer to MP4Box file', { fileStart: 0 });
                    })
                    .catch(error => {
                        console.error('Error while fetching video:', error);
                    });
            });
        }

        initVideoPlayer(video1, 0); // Initialize first track
        initVideoPlayer(video2, 1); // Initialize second track
    </script>
</body>
</html>

控制台日志

test.html:22 Initializing video player {video: video#video1, trackIndex: 0}
test.html:26 MediaSource and video source set {mediaSource: MediaSource, videoSrc: 'blob:http://127.0.0.1:5500/8b52067c-260e-4ec5-bf56-a25e06493e6d'}
test.html:22 Initializing video player {video: video#video2, trackIndex: 1}
test.html:26 MediaSource and video source set {mediaSource: MediaSource, videoSrc: 'blob:http://127.0.0.1:5500/8da1916c-ade5-4c8d-a97f-5de14c21e36f'}
test.html:29 MediaSource opened {readyState: 'open'}
test.html:29 MediaSource opened {readyState: 'open'}
test.html:33 Video file fetched {assetURL: 'video.mp4', status: 200}
test.html:33 Video file fetched {assetURL: 'video.mp4', status: 200}
test.html:31 Fetch finished loading: GET "http://127.0.0.1:5500/video.mp4".
(anonymous) @ test.html:31
test.html:31 Fetch finished loading: GET "http://127.0.0.1:5500/video.mp4".
(anonymous) @ test.html:31
test.html:37 

ArrayBuffer obtained from fetch {byteLength: 8542253}
test.html:40 MP4Box file created
test.html:43 MP4Box file is ready {info: {…}}
test.html:46 Video tracks filtered {videoTracks: Array(2)}
test.html:50 Processing track {trackIndex: 0, track: {…}}
test.html:54 SourceBuffer created {mime: 'video/mp4; codecs="avc1.4d4028"', sourceBuffer: SourceBuffer}mime: "video/mp4; codecs=\"avc1.4d4028\""sourceBuffer: SourceBufferappendWindowEnd: InfinityappendWindowStart: 0buffered: TimeRanges {length: 0}mode: "segments"onabort: nullonerror: nullonupdate: nullonupdateend: nullonupdatestart: nulltimestampOffset: 0updating: false[[Prototype]]: SourceBuffer[[Prototype]]: Object
test.html:58 Segment options set {trackId: 1, nbSamples: 298}
test.html:65 Appended initialization segment {initSeg: {…}} TimeRanges {length: 0}
test.html:78 Received segment {trackId: 1, bufferByteLength: 5241615}
test.html:86 MP4Box file start called
test.html:92 Appended ArrayBuffer to MP4Box file {fileStart: 0}
test.html:37 

ArrayBuffer obtained from fetch {byteLength: 8542253}
test.html:40 MP4Box file created
test.html:43 MP4Box file is ready {info: {…}}
test.html:46 Video tracks filtered {videoTracks: Array(2)}
test.html:50 Processing track {trackIndex: 1, track: {…}}
test.html:54 SourceBuffer created {mime: 'video/mp4; codecs="avc1.4d4028"', sourceBuffer: SourceBuffer}
test.html:58 Segment options set {trackId: 2, nbSamples: 290}
test.html:65 Appended initialization segment {initSeg: {…}} TimeRanges {length: 0}
test.html:78 Received segment {trackId: 2, bufferByteLength: 3355562}
test.html:86 MP4Box file start called
test.html:92 Appended ArrayBuffer to MP4Box file {fileStart: 0}
test.html:70 updateend event after appending init segment {sourceBuffer: SourceBuffer}
test.html:70 updateend event after appending init segment {sourceBuffer: SourceBuffer}
解决方案

问题出在文件加载逻辑、分段追加时机和轨道关联这几个关键点上,以下是修复方案:

关键问题分析

  1. 重复加载文件:两个播放器独立fetch整个MP4文件,不仅浪费资源,还会导致MP4Box.js处理时的状态冲突
  2. 分段追加时机错误:未等待初始化段的updateend事件完成就开始追加媒体段,MSE无法正确解析初始化信息
  3. 轨道关联不严谨:onSegment事件未严格验证轨道ID与SourceBuffer的对应关系,可能导致分段追加错误

修改后的完整代码

<!DOCTYPE html>
<html>

<head>
    <title>Video tracks player</title>
    <script src="https://gpac.github.io/mp4box.js/dist/mp4box.all.js"></script>
</head>

<body>
    <video id="video1" controls width="500"></video>
    <video id="video2" controls width="500"></video>

    <script>
        const video1 = document.getElementById('video1');
        const video2 = document.getElementById('video2');
        const assetURL = 'video.mp4'; // 替换为你的视频文件路径

        // 仅加载一次视频文件,共享给两个播放器
        fetch(assetURL)
            .then(response => response.arrayBuffer())
            .then(arrayBuffer => {
                const mp4boxFile = MP4Box.createFile();
                mp4boxFile.onReady = (info) => {
                    const videoTracks = info.tracks.filter(track => track.type === "video");
                    if (videoTracks.length >= 2) {
                        initVideoPlayer(video1, videoTracks[0], mp4boxFile);
                        initVideoPlayer(video2, videoTracks[1], mp4boxFile);
                    }
                };
                arrayBuffer.fileStart = 0;
                mp4boxFile.appendBuffer(arrayBuffer);
                mp4boxFile.start(); // 统一启动分段处理
            })
            .catch(error => {
                console.error('加载视频文件失败:', error);
            });

        function initVideoPlayer(video, track, mp4boxFile) {
            const mediaSource = new MediaSource();
            video.src = URL.createObjectURL(mediaSource);

            mediaSource.addEventListener('sourceopen', () => {
                const mime = `video/mp4; codecs="${track.codec}"`;
                const sourceBuffer = mediaSource.addSourceBuffer(mime);

                // 为当前轨道设置分段选项,关联到对应的SourceBuffer
                mp4boxFile.setSegmentOptions(track.id, sourceBuffer, { nbSamples: track.nb_samples });

                // 先追加初始化段
                const initSegs = mp4boxFile.initializeSegmentation();
                initSegs.forEach((initSeg) => {
                    if (initSeg.id === track.id) {
                        sourceBuffer.appendBuffer(initSeg.buffer);
                    }
                });

                // 等待初始化段追加完成后,再处理媒体段
                sourceBuffer.addEventListener('updateend', () => {
                    mp4boxFile.onSegment = (id, user, buffer) => {
                        // 确保只处理当前轨道的分段,且SourceBuffer未在更新状态
                        if (id === track.id && user === sourceBuffer && !sourceBuffer.updating) {
                            sourceBuffer.appendBuffer(buffer);
                        }
                    };
                });

                sourceBuffer.addEventListener('error', (e) => {
                    console.error('SourceBuffer错误:', e);
                });
            });
        }
    </script>
</body>
</html>

修复点说明

  • 共享文件加载:仅执行一次fetch请求,避免重复加载和MP4Box.js的状态冲突
  • 调整追加顺序:等待初始化段的updateend事件触发后,再注册媒体段处理逻辑,确保MSE完成初始化信息解析
  • 严格轨道关联:在onSegment中验证轨道ID和SourceBuffer的匹配关系,避免分段追加到错误的播放器
  • 统一启动分段:在文件加载完成后统一调用mp4boxFile.start(),避免多次启动导致的异常

内容的提问来源于stack exchange,提问作者louislefebvre

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.04 15:45:58