26

我们正在尝试使用 MSE(媒体源扩展)在网站上显示实时视频。我们正在通过 websocket 发送帧,并尽最大努力降低延迟。我们目前的原型在 IE、Edge、Chrome、Safari 等中流式传输非常好。我们遇到的问题是 IE 和 Edge 坚持在开始播放视频之前缓冲大约 3-5 秒。这在我们的用例(来自安全摄像头的实时视频)中是不可接受的。我们想知道是否有一些属性或类似的(我们尝试设置 preload=none,但没有成功)消除了这种缓冲?当第一帧添加到 sourceBuffer 时,所有其他浏览器都会愉快地开始播放,我们希望 IE/Edge 具有相同的行为。您可以为我们建议任何其他解决方法吗?

帧采用ISO BMFF 格式

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

结果:

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

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

4

2 回答 2

10

When you serve the video to IE or to Edge, use the following Javascript. It worked for me. Here it is in GitHub as a simplified version of this MSDN example. On my computer, the video plays almost instantly.

  • Download GPAC installers here.
  • Run it and install mp4box.
  • Run mp4box -dash 10000 -frag 1000 -rap path\to\ie_5s.mp4

You will now have a bunch of files alongside your original .mp4.

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

Rename the .mpd file to a .xml file.

Then create a new .html file is the same directory. Paste the following code:

<!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>

Also create a new .js file in the same directory.

/*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);

}());

When I serve the index.html file from a Web server to Edge or IE 11+ the video displays instantly. Time permitting and if you are interested, I will host the demo live for you to see.

于 2015-09-03T01:07:26.440 回答
9

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

于 2015-09-04T13:09:47.183 回答