Skip to content

Instantly share code, notes, and snippets.

@joeyparrish
Created April 17, 2015 16:12
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save joeyparrish/1ad5e53b87953f9c289f to your computer and use it in GitHub Desktop.
Save joeyparrish/1ad5e53b87953f9c289f to your computer and use it in GitHub Desktop.
Demo of MSE bug in Chrome (http://crbug.com/478151)
<script src="mse_bug.js"></script>
var videoInfo = {
buf: null,
url: 'http://yt-dash-mse-test.commondatastorage.googleapis.com/media/car-20120827-85.mp4',
init: [0, 671],
segments: [
[1148,171873],
[171874,341273],
[341274,510958],
[510959,680645],
[680646,850583],
[850584,1022789],
[1022790,1193041],
[1193042,1363045],
[1363046,1532644],
[1532645,1701248],
[1701249,1871395],
[1871396,2041046],
[2041047,2210795],
[2210796,2380198],
[2380199,2549524],
[2549525,2718759],
[2718760,2888765],
[2888766,3060318],
[3060319,3229793],
[3229794,3400203],
[3400204,3570529],
[3570530,3741610],
[3741611,3911736],
[3911737,4081630],
[4081631,4252016],
[4252017,4423280],
[4423281,4592599],
[4592600,4761492],
[4761493,4931337],
[4931338,5101772],
[5101773,5270235],
[5270236,5439912],
[5439913,5571173],
[5571174,5740890],
[5740891,5910860],
[5910861,6000693],
[6000694,6015000]
]
};
var audioInfo = {
buf: null,
url: 'http://yt-dash-mse-test.commondatastorage.googleapis.com/media/car-20120827-8c.mp4',
init: [0, 591],
segments: [
[852,159831],
[159832,318918],
[318919,477538],
[477539,636700],
[636701,795505],
[795506,954032],
[954033,1113101],
[1113102,1272061],
[1272062,1430569],
[1430570,1589429],
[1589430,1748577],
[1748578,1906922],
[1906923,2065912],
[2065913,2224917],
[2224918,2383359],
[2383360,2542405],
[2542406,2701219],
[2701220,2859901],
[2859902,2884571]
]
};
function fetch(info, idx) {
console.log('Fetching segment', idx);
var resolve, reject;
var p = new Promise(function(resolveCallback, rejectCallback) {
resolve = resolveCallback;
reject = rejectCallback;
});
var range = idx == null ? info.init : info.segments[idx];
var begin = range[0];
var end = range[1];
var xhr = new XMLHttpRequest();
xhr.open('GET', info.url, true);
xhr.responseType = 'arraybuffer';
xhr.onload = function() {
console.log('Appending segment', idx);
info.buf.appendBuffer(xhr.response);
resolve();
};
xhr.onerror = function() {
console.error('XHR error', xhr);
reject();
};
var rangeString = begin + '-' + (end != null ? end : '');
xhr.setRequestHeader('Range', 'bytes=' + rangeString);
xhr.send();
return p;
}
function wait(s) {
return function() {
return new Promise(function(resolve) { setTimeout(resolve, s * 1000); });
};
}
function fetchA(idx) {
return fetch.bind(null, audioInfo, idx);
}
function fetchV(idx) {
return fetch.bind(null, videoInfo, idx);
}
function flattenBuffered(buffered) {
var ranges = [];
for (var i = 0; i < buffered.length; ++i) {
ranges.push([buffered.start(i), buffered.end(i)]);
}
return ranges;
}
function isBuffered(buffered, time) {
for (var i = 0; i < buffered.length; ++i) {
if (time >= buffered.start(i) && time <= buffered.end(i)) {
return true;
}
}
return false;
}
function breakEverything() {
video = document.createElement('video');
video.controls = true;
document.body.appendChild(video);
mediaSource = new MediaSource();
video.addEventListener('seeking', function() {
console.log('Seek to', video.currentTime);
// clear both buffers if we seek outside the buffered range.
if (!isBuffered(video.buffered, video.currentTime)) {
console.log('Clearing buffers');
abuf.remove(0, Number.POSITIVE_INFINITY);
vbuf.remove(0, Number.POSITIVE_INFINITY);
}
});
mediaSource.addEventListener('sourceopen', function() {
mediaSource.duration = 181.63;
vbuf = mediaSource.addSourceBuffer('video/mp4; codecs="avc1.640028"');
abuf = mediaSource.addSourceBuffer('audio/mp4; codecs="mp4a.40.2"');
videoInfo.buf = vbuf;
audioInfo.buf = abuf;
Promise.resolve().
// get init and first segments
then(fetchV(null)).then(fetchA(null)).then(fetchV(0)).then(fetchA(0)).
// play for two seconds
then(function() { video.play(); }).then(wait(2.0)).
// seek
then(function() { video.currentTime = 40; }).
// get segments for the new position
then(fetchV(7)).then(fetchV(8)).
then(fetchA(3)).then(fetchA(4)).
// play for two seconds
then(wait(2.0)).
// seek two video segments backward
then(function() { video.currentTime = 30; }).
// get segments for new position, around 10s ahead of this position
then(fetchV(5)).then(fetchV(6)).then(fetchV(7)).then(fetchV(8)).
then(fetchA(2)).then(fetchA(3)).
// play for five seconds
then(wait(5.0)).
// now we are stuck!
then(function() {
console.log('=====');
console.log('currentTime', video.currentTime);
console.assert(video.currentTime > 31, 'video is stuck!');
console.log('seeking', video.seeking);
console.assert(video.seeking == false, 'should be done seeking!');
console.log('readyState', video.readyState);
console.assert(video.readyState >= HTMLVideoElement.HAVE_FUTURE_DATA,
'video should have some future data at least!');
console.log('buffered',
JSON.stringify(flattenBuffered(video.buffered)));
console.assert(isBuffered(video.buffered, video.currentTime),
'currentTime is buffered, so this passes.');
if (video.currentTime < 31) {
console.info('video.currentTime += 0.001 to unstick the video.');
} else {
console.info('video did not get stuck!');
}
});
});
video.src = URL.createObjectURL(mediaSource);
}
document.addEventListener('DOMContentLoaded', breakEverything);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment