网站搜索

javascript - 媒体源扩展无法工作


我正在尝试使用 MediaSource API 将不同的 WebM 视频附加到同一个源。

我发现了一个试图做同样事情的 Github 项目,其中加载了一个 WebM 播放列表,每个 WebM 都被附加为一个 SourceBuffer。但是它最后一次提交是在一年前,因此与当前规范不同步。所以我将其分叉,并更新到最新的 API 属性/方法,同时进行了一些重构。现有的许多代码直接取自规范的示例和 Eric Bidelman 的测试页面。

然而,我无法让它按预期工作。我在两个浏览器上进行测试,都是在 Mac OS X 10.9.2 上:Chrome 35 稳定版(写本文时的最新版本)和 Firefox 30 测试版,在 about:config 中将标志 media.mediasource.enabled 设置为 true(此功能将要到 FF 25 才引入,目前稳定版是 24)。

这里是我遇到的问题。

两个浏览器

我希望最终的视频是由这11个WebM(00.webm, 01.webm, …, 10.webm)组成的一个长视频。现在,每个浏览器只播放视频的一个片段。

谷歌浏览器

行为极不一致。似乎无法可靠地重现这些错误。

  • 有时视频是空白的,或者中间有一条高高的黑条,并且无法播放。
  • 有时视频会加载并停在 01.webm 的第一帧上。
  • 有时,视频会播放 02.webm 的几帧然后暂停,只加载了前三个片段。
  • 播放按钮最初是灰色的。
  • 按下灰色的播放按钮会产生非常不一致的行为。有时候,它会加载一个黑屏、无法播放的视频。其他时候,它会播放第一段,然后,当你播放到结尾时,它会停止,当你再次按下播放/暂停时,它会加载下一段。即便如此,它有时会跳过某些段落,并卡在 04.webm 上。无论如何,它从未播放最后一段,即使控制台报告已经经过了所有缓冲区。

说实话,每次都不一样。我无法在这里列出所有情况。

已知注意事项:Chrome目前尚未实现sourceBuffer.mode,不过我不知道这可能会产生什么影响。

火狐

  1. 只能播放 00.webm。总运行时间是 0:08,即该视频的长度。
  2. 视频跳转无效。(这是预期的行为,因为在 onSeeking 事件处理程序中实际上没有任何操作。)
  3. 视频一旦播放完毕就无法重新开始。

我最初的理论是,这与 mediaSource.sourceBuffers[0].timestampOffset=duration 和 duration=mediaSource.duration 有关。但我似乎无法从 mediaSource.duration 得到任何值,除了 NaN,即使我正在追加新的片段。

完全迷失了。非常感谢指导。

编辑:我取消了代码中持续时间部分的注释,并在所有视频上运行了 Aaron Colwell 的媒体源扩展工具中的 mse_webm_remuxer(感谢 Adam Hart 的提示)。瞧,Chrome 中不再出现不可预测的故障了!但可惜的是,当一个媒体片段结束时,它仍然会暂停,即使按下播放,有时也会卡在一帧上。

在 Firefox Beta 中,它无法播放第一个片段之后的内容,并显示以下响应:

TypeError: 分配给 SourceBuffer.timestampOffset 的值不是有限的浮点数。

记录 duration 的值返回 NaN(但仅在 FF 中)。

答案#1

主要问题出在视频文件上。如果你打开 chrome://media-internals/,你会看到 error Media segment did not begin with keyframe. 使用格式正确的视频,比如 Eric Bidelman 示例中的视频(希望他不会因为我不断直接链接到那个视频而生气,但这是我找到的唯一可用示例视频),你的代码在 appendNextMediaSegment() 中进行如下更改后是可以工作的:

duration = mediaSource.duration;
mediaSource.sourceBuffers[0].timestampOffset = duration;
mediaSource.sourceBuffers[0].appendBuffer(mediaSegment);

你可以尝试使用Aaron Colwell的媒体来源扩展工具来尝试让你的视频正常工作,但我的成功有限。

在你添加片段之前查看 onProgress 事件似乎也有点奇怪,但我猜如果你只想在视频实际播放时追加,这样做是可行的。由于视频长度未知,这可能会让进度条表现得奇怪,不过无论如何这都可能是个问题。

答案#2

我同意Adam Hart说的观点。对于一个webm文件,我尝试实现一个像 http://html5-demos.appspot.com/static/media-source.html 那样的示例,然后得出结论,问题是由我使用的源文件引起的。

如果你还有一个箭头,为什么不尝试使用在 https://developer.mozilla.org/en-US/docs/Web/HTML/DASH_Adaptive_Streaming_for_HTML_5_Video 中介绍的“samplemuxer”。在我看来,samplemuxer 是像 FFMPEG 一样的编码器之一。

我发现转换后的文件可以使用 MediaSource API。如果你也看到它能工作,请告诉我。

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>MediaSource API Demo</title>
</head>
<body>

<h3>Appending .webm video chunks using the Media Source API</h3>

<section>
    <video controls autoplay width="320" height="240"></video>
    <pre id="log"></pre>
</section>


<script>
    //ORIGINAL CODE http://html5-demos.appspot.com/static/media-source.html

    var FILE = 'IU_output2.webm';

    var NUM_CHUNKS = 5;
    var video = document.querySelector('video');

    var mediaSource = new MediaSource();

    video.src = window.URL.createObjectURL(mediaSource);

    function callback(e) {
        var sourceBuffer = mediaSource.addSourceBuffer('video/webm; codecs="vorbis,vp8"');

        logger.log('mediaSource readyState: ' + this.readyState);

        GET(FILE, function(uInt8Array) {
            var file = new Blob([uInt8Array], {type: 'video/webm'});
            var chunkSize = Math.ceil(file.size / NUM_CHUNKS);

            logger.log('num chunks:' + NUM_CHUNKS);
            logger.log('chunkSize:' + chunkSize + ', totalSize:' + file.size);

            // Slice the video into NUM_CHUNKS and append each to the media element.
            var i = 0;

            (function readChunk_(i) {
                var reader = new FileReader();

                // Reads aren't guaranteed to finish in the same order they're started in,
                // so we need to read + append the next chunk after the previous reader
                // is done (onload is fired).
                reader.onload = function(e) {

                    try {
                        sourceBuffer.appendBuffer(new Uint8Array(e.target.result));
                        logger.log('appending chunk:' + i);
                    }catch(e){
                        console.log(e);
                    }

                    if (i == NUM_CHUNKS - 1) {
                        if(!sourceBuffer.updating)
                             mediaSource.endOfStream();
                    } else {
                        if (video.paused) {
                            video.play(); // Start playing after 1st chunk is appended.
                        }

                        sourceBuffer.addEventListener('updateend', function(e){
                            if( i < NUM_CHUNKS - 1 )
                                readChunk_(++i);
                        });
                    } //end if
                };

                var startByte = chunkSize * i;
                var chunk = file.slice(startByte, startByte + chunkSize);

                reader.readAsArrayBuffer(chunk);
            })(i);  // Start the recursive call by self calling.
        });
    }

    mediaSource.addEventListener('sourceopen', callback, false);
//    mediaSource.addEventListener('webkitsourceopen', callback, false);
//
//    mediaSource.addEventListener('webkitsourceended', function(e) {
//        logger.log('mediaSource readyState: ' + this.readyState);
//    }, false);

    function GET(url, callback) {
        var xhr = new XMLHttpRequest();
        xhr.open('GET', url, true);
        xhr.responseType = 'arraybuffer';
        xhr.send();

        xhr.onload = function(e) {
            if (xhr.status != 200) {
                alert("Unexpected status code " + xhr.status + " for " + url);
                return false;
            }
            callback(new Uint8Array(xhr.response));
        };
    }
</script>
<script>
    function Logger(id) {
        this.el = document.getElementById('log');
    }
    Logger.prototype.log = function(msg) {
        var fragment = document.createDocumentFragment();
        fragment.appendChild(document.createTextNode(msg));
        fragment.appendChild(document.createElement('br'));
        this.el.appendChild(fragment);
    };

    Logger.prototype.clear = function() {
        this.el.textContent = '';
    };

    var logger = new Logger('log');
</script>
</body>
</html>

另一个测试代码

<!DOCTYPE html>
<html>
<head>
    <title>MediaSource API Demo</title>
</head>
<body>

<h3>Appending .webm video chunks using the Media Source API</h3>

<section>
    <video controls autoplay width="320" height="240"></video>
    <pre id="log"></pre>
</section>


<script>
    //ORIGINAL CODE http://html5-demos.appspot.com/static/media-source.html

    var FILE = 'IU_output2.webm';
    //    var FILE =  'test_movie_output.webm';

    var NUM_CHUNKS = 10;
    var video = document.querySelector('video');

    var mediaSource = new MediaSource();

    video.src = window.URL.createObjectURL(mediaSource);

    function callback(e) {
        var sourceBuffer = mediaSource.addSourceBuffer('video/webm; codecs="vorbis,vp8"');

        logger.log('mediaSource readyState: ' + this.readyState);

        GET(FILE, function(uInt8Array) {
            logger.log('byteLength:' + uInt8Array.byteLength );

            sourceBuffer.appendBuffer(uInt8Array);

        });
    }

    mediaSource.addEventListener('sourceopen', callback, false);
    //    mediaSource.addEventListener('webkitsourceopen', callback, false);
    //
    //    mediaSource.addEventListener('webkitsourceended', function(e) {
    //        logger.log('mediaSource readyState: ' + this.readyState);
    //    }, false);

    function GET(url, callback) {
        var xhr = new XMLHttpRequest();
        xhr.open('GET', url, true);
        xhr.responseType = 'arraybuffer';
        xhr.send();

        xhr.onload = function(e) {
            if (xhr.status != 200) {
                alert("Unexpected status code " + xhr.status + " for " + url);
                return false;
            }
            callback(new Uint8Array(xhr.response));
        };
    }
</script>
<script>
    function Logger(id) {
        this.el = document.getElementById('log');
    }
    Logger.prototype.log = function(msg) {
        var fragment = document.createDocumentFragment();
        fragment.appendChild(document.createTextNode(msg));
        fragment.appendChild(document.createElement('br'));
        this.el.appendChild(fragment);
    };

    Logger.prototype.clear = function() {
        this.el.textContent = '';
    };

    var logger = new Logger('log');
</script>
</body>
</html>

谢谢。