我们正在尝试使用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文件,如果你想检查它。
答案 0 :(得分:8)
当您将视频提供给IE或Edge时,请使用以下Javascript。它对我有用。 Here it is in GitHub作为this MSDN example的简化版本。在我的电脑上,视频几乎立即播放。
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秒的假数据来解决它,然后在视频开始播放后将其删除。