使用Media Source Extensions在IE / Edge中进行视频缓冲

时间:2015-08-26 13:20:18

标签: javascript video-streaming internet-explorer-11 microsoft-edge media-source

我们正在尝试使用MSE(媒体来源扩展程序)在网站上显示实时视频。我们正在通过websocket发送帧,并尽最大努力保持延迟。 我们目前的原型在IE,Edge,Chrome,Safari等流媒体上非常流畅。 我们遇到的问题是IE和Edge坚持在开始播放视频前大约3-5秒进行缓冲。这在我们的使用案例中是不可接受的(来自安全摄像机的实时视频)。 我们想知道是否有一些属性或类似(我们已经尝试设置preload = none,但没有成功)删除了这个缓冲? 当第一帧添加到sourceBuffer时,所有其他浏览器开心地开始播放,并且我们希望IE / Edge具有相同的行为。 您可以为我们建议吗?

这些框架位于ISO BMFF format

这是我创建的reproducing example,它测量从第一帧附加到视频开始播放的时间。它使用间隔来欺骗通过websocket到达的数据。

结果:

Browser       Delay(ms)
-----------------------
Chrome:           ~300
Safari @ Mac:       ~7
Chrome @ Android:  ~30
IE11 @ Win10:    ~3200
Edge:            ~3200

Here是mp4文件,如果你想检查它。

2 个答案:

答案 0 :(得分:8)

当您将视频提供给IE或Edge时,请使用以下Javascript。它对我有用。 Here it is in GitHub作为this MSDN example的简化版本。在我的电脑上,视频几乎立即播放。

  • 下载GPAC安装程序here
  • 运行它并安装mp4box。
  • 运行mp4box -dash 10000 -frag 1000 -rap path\to\ie_5s.mp4

现在,您将在原始.mp4旁边放置一堆文件。

ie_5s.mp4
ie_5s_dash.mpd
ie_5s_dashinit.mp4
out_ie_5s.mp4

.mpd文件重命名为.xml文件。

然后创建一个新的.html文件是同一个目录。粘贴以下代码:

<!DOCTYPE html>
<html>
<!-- Media streaming example
  Reads an .mpd file created using mp4box and plays the file
-->
<head>
    <meta charset="utf-8" />
    <title>Media streaming example</title>
</head>
<body>
    <input type="text" id="filename" value="ie_5s_dash.xml" />
    <button id="load">Play</button>
    <br />
    <video id="myVideo" autoplay="autoplay">No video available</video>
    <script src="index.js"></script>
</body>
</html>

还在同一目录中创建一个新的.js文件。

/*globals window, console, XMLHttpRequest, document, Uint8Array, DOMParser, URL*/

(function () { /* code */

    'use strict';

    // Global Parameters from .mpd file
    var file;  // MP4 file
    var width;  //  Native width and height
    var height;

    // Elements
    var videoElement = document.getElementById('myVideo');
    var playButton = document.getElementById("load");
    videoElement.poster = "poster.png";

    // Description of initialization segment, and approx segment lengths
    var initialization;

    // Video parameters
    var bandwidth; // bitrate of video

    // Parameters to drive segment loop
    var index = 0; // Segment to get
    var segments;

    // Source and buffers
    var mediaSource;
    var videoSource;

    // Parameters to drive fetch loop
    var segCheck;
    var lastTime = 0;
    var bufferUpdated = false;

    // Flags to keep things going
    var lastMpd = "";
    var requestId = 0;

    //  Logs messages to the console
    function log(s) {
        //  send to console
        //    you can also substitute UI here
        console.log(s);
    }

    //  Clears the log
    function clearLog() {
        console.clear();
    }

    function timeToDownload(range) {
        var vidDur = range.split("-");
        // Time = size * 8 / bitrate
        return (((vidDur[1] - vidDur[0]) * 8) / bandwidth);
    }

    //  Play segment plays a byte range (format nnnn-nnnnn) of a media file
    function playSegment(range, url) {
        var xhr = new XMLHttpRequest();
        if (range || url) { // Make sure we've got incoming params
            xhr.open('GET', url);
            xhr.setRequestHeader("Range", "bytes=" + range);
            xhr.send();
            xhr.responseType = 'arraybuffer';
            try {
                xhr.addEventListener("readystatechange", function () {
                    if (xhr.readyState === xhr.DONE) { //wait for video to load
                        //  Calculate when to get next segment based on time of current one
                        segCheck = (timeToDownload(range) * 0.8).toFixed(3); // Use point eight as fudge factor
                        // Add received content to the buffer
                        try {
                            videoSource.appendBuffer(new Uint8Array(xhr.response));
                        } catch (e) {
                            log('Exception while appending', e);
                        }
                    }
                }, false);
            } catch (e) {
                log(e);
                return; // No value for range
            }
        }
    }

    //  Get video segments
    function fileChecks() {
        // If we're ok on the buffer, then continue
        if (bufferUpdated === true) {
            if (index < segments.length) {
                // Loads next segment when time is close to the end of the last loaded segment
                if ((videoElement.currentTime - lastTime) >= segCheck) {
                    playSegment(segments[index].getAttribute("mediaRange").toString(), file);
                    lastTime = videoElement.currentTime;
                    index++;
                }
            } else {
                videoElement.removeEventListener("timeupdate", fileChecks, false);
            }
        }
    }

    //  Play our file segments
    function getStarted(url) {

        //  Start by loading the first segment of media
        playSegment(segments[index].getAttribute("mediaRange").toString(), url);

        // Display current index
        index++;

        //  Continue in a loop where approximately every x seconds reload the buffer
        videoElement.addEventListener("timeupdate", fileChecks, false);

    }

    function updateFunct() {
        //  This is a one shot function, when init segment finishes loading,
        //    update the buffer flag, call getStarted, and then remove this event.
        bufferUpdated = true;
        getStarted(file); // Get video playback started
        //  Now that video has started, remove the event listener
        videoSource.removeEventListener("update", updateFunct);
    }

    //  Load video's initialization segment
    function initVideo(range, url) {
        var xhr = new XMLHttpRequest();
        if (range || url) { // make sure we've got incoming params

            // Set the desired range of bytes we want from the mp4 video file
            xhr.open('GET', url);
            xhr.setRequestHeader("Range", "bytes=" + range);
            segCheck = (timeToDownload(range) * 0.8).toFixed(3); // use point eight as fudge factor
            xhr.send();
            xhr.responseType = 'arraybuffer';
            try {
                xhr.addEventListener("readystatechange", function () {
                    if (xhr.readyState === xhr.DONE) { // wait for video to load
                        // Add response to buffer
                        try {
                            videoSource.appendBuffer(new Uint8Array(xhr.response));
                            // Wait for the update complete event before continuing
                            videoSource.addEventListener("update", updateFunct, false);

                        } catch (e) {
                            log('Exception while appending initialization content', e);
                        }
                    }
                }, false);
            } catch (e) {
                log(e);
            }
        } else {
            return; // No value for range or url
        }
    }

    // Create mediaSource and initialize video
    function setupVideo() {
        clearLog(); // Clear console log

        //  Create the media source
        if (window.MediaSource) {
            mediaSource = new window.MediaSource();
        } else {
            log("mediasource or syntax not supported");
            return;
        }
        var url = URL.createObjectURL(mediaSource);
        videoElement.pause();
        videoElement.src = url;
        videoElement.width = width;
        videoElement.height = height;

        // Wait for event that tells us that our media source object is
        //   ready for a buffer to be added.
        mediaSource.addEventListener('sourceopen', function (e) {
            try {
                videoSource = mediaSource.addSourceBuffer('video/mp4');
                initVideo(initialization, file);
            } catch (ex) {
                log('Exception calling addSourceBuffer for video', ex);
                return;
            }
        }, false);

        // Handler to switch button text to Play
        videoElement.addEventListener("pause", function () {
            playButton.innerText = "Play";
        }, false);

        // Handler to switch button text to pause
        videoElement.addEventListener("playing", function () {
            playButton.innerText = "Pause";
        }, false);
    }

    // Retrieve parameters from our stored .mpd file
    function getFileType(data) {
        try {
            file = data.querySelectorAll("BaseURL")[0].textContent.toString();
            var rep = data.querySelectorAll("Representation");
            width = rep[0].getAttribute("width");
            height = rep[0].getAttribute("height");
            bandwidth = rep[0].getAttribute("bandwidth");

            var ini = data.querySelectorAll("Initialization");
            initialization = ini[0].getAttribute("range");
            segments = data.querySelectorAll("SegmentURL");

        } catch (er) {
            log(er);
            return;
        }
    }

    // Gets the mpd file and parses it
    function getData(url) {
        if (url !== "") {
            var xhr = new XMLHttpRequest(); // Set up xhr request
            xhr.open("GET", url, true); // Open the request
            xhr.responseType = "text"; // Set the type of response expected
            xhr.send();

            //  Asynchronously wait for the data to return
            xhr.onreadystatechange = function () {
                if (xhr.readyState === xhr.DONE) {
                    var tempoutput = xhr.response;
                    var parser = new DOMParser(); //  Create a parser object

                    // Create an xml document from the .mpd file for searching
                    var xmlData = parser.parseFromString(tempoutput, "text/xml", 0);
                    log("parsing mpd file");

                    // Get and display the parameters of the .mpd file
                    getFileType(xmlData);

                    // Set up video object, buffers, etc
                    setupVideo();
                }
            };

            // Report errors if they happen during xhr
            xhr.addEventListener("error", function (e) {
                log("Error: " + e + " Could not load url.");
            }, false);
        }
    }

    // Click event handler for load button
    playButton.addEventListener("click", function () {
        //  If video is paused then check for file change
        if (videoElement.paused === true) {
            // Retrieve mpd file, and set up video
            var curMpd = document.getElementById("filename").value;
            //  If current mpd file is different then last mpd file, load it.
            if (curMpd !== lastMpd) {
                //  Cancel display of current video position
                window.cancelAnimationFrame(requestId);
                lastMpd = curMpd;
                getData(curMpd);
            } else {
                //  No change, just play
                videoElement.play();
            }
        } else {
            //  Video was playing, now pause it
            videoElement.pause();
        }
    }, false);

    // Do a little trickery, start video when you click the video element
    videoElement.addEventListener("click", function () {
        playButton.click();
    }, false);

    // Event handler for the video element errors
    document.getElementById("myVideo").addEventListener("error", function (e) {
        log("video error: " + e.message);
    }, false);

}());

当我将index.html文件从Web服务器提供给Edge或IE 11+时,视频会立即显示。时间允许,如果您有兴趣,我会现场主持演示,供您查看。

答案 1 :(得分:8)

通过查看MP4 TRUN框中的采样持续时间来完成IE缓冲。您可以通过添加约5秒的假数据来解决它,然后在视频开始播放后将其删除。