6

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

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

但是,我无法让它按预期工作。我正在两个浏览器中进行测试,都在 Mac OS X 10.9.2 上:Chrome 35 stable(撰写本文时最新)和 Firefox 30 beta,其标志media.mediasource.enabled设置为trueabout:config(此功能要到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.durationexcept 中得到任何回报NaN,即使我正在附加新的段。

完全迷失在这里。指导非常感谢。

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

在 Firefox Beta 中,它不会超过第一段,响应:

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

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

4

2 回答 2

6

主要问题是视频文件。如果您打开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有点奇怪,但我想如果您只想在视频实际播放时附加,这可能会起作用。由于视频长度未知,它可能会使搜索栏变得奇怪,但这在任何情况下都可能是一个问题。

于 2014-06-13T18:45:57.757 回答
0

我同意亚当哈特所说的观点。使用 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>

谢谢。

于 2016-04-05T20:16:01.033 回答