24

Chúng tôi đang cố gắng sử dụng MSE (Tiện ích nguồn phương tiện) để hiển thị video thời gian thực trên trang web. Chúng tôi đang gửi khung qua websocket và cố gắng hết sức để giữ độ trễ. Nguyên mẫu hiện tại của chúng tôi đang phát trực tuyến rất tốt trong IE, Edge, Chrome, Safari v.v. Vấn đề chúng tôi gặp phải là IE và Edge khẳng định việc lưu vào bộ đệm khoảng 3-5 giây trước khi bắt đầu phát video. Điều này là không thể chấp nhận được trong trường hợp sử dụng của chúng tôi (video trực tiếp từ camera an ninh). Chúng tôi đang tự hỏi nếu có một số tài sản hoặc tương tự (chúng tôi đã cố gắng thiết lập preload = none, không có thành công) mà loại bỏ đệm này? Tất cả các trình duyệt khác đều vui vẻ bắt đầu chơi khi frame đầu tiên được thêm vào sourceBuffer, và chúng ta muốn cùng một hành vi từ IE/Edge. Có cách nào khác để bạn có thể đề xuất cho chúng tôi không?Video đệm trong IE/Edge bằng cách sử dụng Tiện ích mở rộng nguồn phương tiện

Các khung đang trong ISO BMFF format

Đây là một reproducing example Tôi đã tạo ra mà đo thời gian từ frame đầu tiên được nối vào video bắt đầu playing.It sử dụng một khoảng thời gian để giả mạo dữ liệu đến hơn một WebSocket.

Kết quả:

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

Here là file mp4, nếu bạn muốn kiểm tra nó.

+1

liên kết mẫu là 404 – totaam

+0

@totaam Tôi đã chuyển các tệp từ ổ google sang github và cập nhật liên kết. Nó sẽ hoạt động trở lại – oskbor

Trả lời

7

Khi bạn phân phối video tới IE hoặc tới Edge, hãy sử dụng Javascript sau. Nó làm việc cho tôi. Here it is in GitHub dưới dạng phiên bản đơn giản của this MSDN example. Trên máy tính của tôi, video phát gần như ngay lập tức.

  • Tải xuống trình cài đặt GPAC here.
  • Chạy và cài đặt mp4box.
  • Run mp4box -dash 10000 -frag 1000 -rap path\to\ie_5s.mp4

Bây giờ bạn sẽ có một loạt các tập tin cùng gốc .mp4 của bạn.

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

Đổi tên tệp .mpd thành tệp .xml.

Sau đó, tạo tệp .html mới là cùng một thư mục. Dán mã sau đây:

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

Cũng tạo một tệp mới .js trong cùng một thư mục.

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

}()); 

Khi tôi phân phối tệp index.html từ máy chủ web tới Edge hoặc IE 11+ video hiển thị ngay lập tức. Thời gian cho phép và nếu bạn quan tâm, tôi sẽ lưu trữ bản trình diễn trực tiếp để bạn xem.

+2

Xin chào, tôi vừa xem qua ví dụ của bạn nhưng sẽ không có thời gian để thiết lập nó ngay hôm nay.Vì tất cả các phân đoạn đều có trên máy chủ, không phải là lý do video bắt đầu chơi đơn giản là IE/Edge nhanh chóng có thể đủ bộ đệm để bắt đầu phát không? Im có một thời gian khó khăn sau những gì đang xảy ra vì vậy tôi có thể sai :) Trong trường hợp của tôi các khung đang đến trên websocket trong thời gian thực vì vậy không có phân đoạn trong tương lai để lấy từ máy chủ. – oskbor

+0

"... không phải là lý do khiến video bắt đầu chơi đơn giản là IE/Edge nhanh chóng có thể đủ bộ đệm để bắt đầu chơi không?" Bạn cũng có thể đúng. Hmm. Tôi cũng không hiểu lắm. :) –

+0

"... Trong trường hợp của tôi, khung hình sẽ xuất hiện trên websocket trong thời gian thực ..." Thật tuyệt vời khi có một ví dụ về điều đó để kiểm tra. Bạn có thể đăng thêm chi tiết về cách tái tạo thiết lập của mình không. [Ví dụ tái tạo] (https://ea7e83c432c066554d3ef3e6580aa1af9bd594a1.googledrive.com/host/0BwqnHXhKkJ7mfjF5clFzSUFSY3pjSzhxZlA0TnpIeEo2bHlaY2FZd3RhWDFHY1o2eXZsYXc/ie_5s_buffer.html) mà bạn đã đăng không hoạt động đối với tôi trên bất kỳ trình duyệt nào. Tôi nghĩ rằng những gì bạn đang nói là chúng ta cần phải sử dụng một nguồn cấp dữ liệu video trực tiếp thay vì một mp4. Trong tò mò, tại sao bạn đăng mp4 ở nơi đầu tiên? –

8

Quá trình lưu vào bộ đệm IE được thực hiện bằng cách xem thời lượng mẫu trong hộp MP4 TRUN. Bạn có lẽ có thể giải quyết nó bằng cách thêm ~ 5 giây dữ liệu giả mạo và sau đó xóa dữ liệu đó khi video bắt đầu phát.

+2

Điều đó có thể hiệu quả, nhưng cảm thấy giống như một giải pháp rất hacky. Ý tưởng tuyệt vời mặc dù! – oskbor

+2

Bạn có liên kết tham chiếu không? – aergistal

+2

Thời lượng mẫu được đặt trong hộp TRUN: https://msdn.microsoft.com/en-us/library/ff469478.aspx – Lekoaf

Các vấn đề liên quan