2633a46d by David LaPalomento

Ensure overlapping buffered edges are not interpreted as updates

If the old and new buffered ranges have a shared start or end point, that edge should not be interpreted as a new buffered boundary. Fix up a number of the tests. Some tests are still failing.
1 parent 5ee4363a
......@@ -187,20 +187,20 @@
* active media playlist. When called with a single argument,
* triggers the playlist loader to asynchronously switch to the
* specified media playlist. Calling this method while the
* loader is in the HAVE_NOTHING or HAVE_MASTER states causes an
* error to be emitted but otherwise has no effect.
* loader is in the HAVE_NOTHING causes an error to be emitted
* but otherwise has no effect.
* @param playlist (optional) {object} the parsed media playlist
* object to switch to
*/
loader.media = function(playlist) {
var mediaChange = false;
var startingState = loader.state, mediaChange;
// getter
if (!playlist) {
return loader.media_;
}
// setter
if (loader.state === 'HAVE_NOTHING' || loader.state === 'HAVE_MASTER') {
if (loader.state === 'HAVE_NOTHING') {
throw new Error('Cannot switch media playlist from ' + loader.state);
}
......@@ -213,7 +213,7 @@
playlist = loader.master.playlists[playlist];
}
mediaChange = playlist.uri !== loader.media_.uri;
mediaChange = !loader.media_ || playlist.uri !== loader.media_.uri;
// switch to fully loaded playlists immediately
if (loader.master.playlists[playlist.uri].endList) {
......@@ -258,7 +258,17 @@
withCredentials: withCredentials
}, function(error, request) {
haveMetadata(error, request, playlist.uri);
loader.trigger('mediachange');
if (error) {
return;
}
// fire loadedmetadata the first time a media playlist is loaded
if (startingState === 'HAVE_MASTER') {
loader.trigger('loadedmetadata');
} else {
loader.trigger('mediachange');
}
});
};
......@@ -320,19 +330,13 @@
loader.master.playlists[loader.master.playlists[i].uri] = loader.master.playlists[i];
}
request = xhr({
uri: resolveUrl(srcUrl, parser.manifest.playlists[0].uri),
withCredentials: withCredentials
}, function(error, request) {
// pass along the URL specified in the master playlist
haveMetadata(error,
request,
parser.manifest.playlists[0].uri);
if (!error) {
loader.trigger('loadedmetadata');
}
});
return loader.trigger('loadedplaylist');
loader.trigger('loadedplaylist');
if (!request) {
// no media playlist was specifically selected so start
// from the first listed one
loader.media(parser.manifest.playlists[0]);
}
return;
}
// loaded a media playlist
......@@ -468,8 +472,8 @@
}
// the playback position is outside the range of available
// segments so return the last one
return this.media_.segments.length - 1;
// segments so return the length
return this.media_.segments.length;
};
videojs.Hls.PlaylistLoader = PlaylistLoader;
......
......@@ -190,7 +190,8 @@ videojs.Hls.prototype.src = function(src) {
var updatedPlaylist = this.playlists.media();
if (!updatedPlaylist) {
// do nothing before an initial media playlist has been activated
// select the initial variant
this.playlists.media(this.selectPlaylist());
return;
}
......@@ -254,7 +255,7 @@ videojs.Hls.prototype.handleSourceOpen = function() {
// Returns the array of time range edge objects that were additively
// modified between two TimeRanges.
var bufferedAdditions = function(original, update) {
videojs.Hls.bufferedAdditions_ = function(original, update) {
var result = [], edges = [],
i, inOriginalRanges;
......@@ -271,6 +272,15 @@ var bufferedAdditions = function(original, update) {
var leftTime, rightTime;
leftTime = left.start !== undefined ? left.start : left.end;
rightTime = right.start !== undefined ? right.start : right.end;
// when two times are equal, ensure the original edge covers the
// update
if (leftTime === rightTime) {
if (left.original) {
return left.start !== undefined ? -1 : 1;
}
return right.start !== undefined ? -1 : 1;
}
return leftTime - rightTime;
});
......@@ -349,8 +359,8 @@ videojs.Hls.prototype.setupSourceBuffer_ = function() {
// annotate the segment with any start and end time information
// added by the media processing
segment = segmentInfo.playlist.segments[segmentInfo.mediaIndex];
timelineUpdates = bufferedAdditions(segmentInfo.buffered,
this.tech_.buffered());
timelineUpdates = videojs.Hls.bufferedAdditions_(segmentInfo.buffered,
this.tech_.buffered());
timelineUpdates.forEach(function(update) {
if (update.start !== undefined) {
segment.start = update.start;
......@@ -1112,15 +1122,16 @@ videojs.Hls.prototype.drainBuffer = function(event) {
this.sourceBuffer.timestampOffset = currentBuffered.end(0);
}
// the segment is asynchronously added to the current buffered data
if (currentBuffered.length) {
this.sourceBuffer.videoBuffer_.appendWindowStart = Math.min(this.tech_.currentTime(), currentBuffered.end(0));
} else if (this.sourceBuffer.videoBuffer_) {
this.sourceBuffer.videoBuffer_.appendWindowStart = 0;
// Chrome 45 stalls if appends overlap the playhead
this.sourceBuffer.appendWindowStart = Math.min(this.tech_.currentTime(), currentBuffered.end(0));
} else {
this.sourceBuffer.appendWindowStart = 0;
}
this.pendingSegment_ = segmentBuffer.shift();
this.pendingSegment_.buffered = this.tech_.buffered();
// the segment is asynchronously added to the current buffered data
this.sourceBuffer.appendBuffer(bytes);
};
......
......@@ -69,13 +69,16 @@
});
test('moves to HAVE_MASTER after loading a master playlist', function() {
var loader = new videojs.Hls.PlaylistLoader('master.m3u8');
var loader = new videojs.Hls.PlaylistLoader('master.m3u8'), state;
loader.on('loadedplaylist', function() {
state = loader.state;
});
requests.pop().respond(200, null,
'#EXTM3U\n' +
'#EXT-X-STREAM-INF:\n' +
'media.m3u8\n');
ok(loader.master, 'the master playlist is available');
strictEqual(loader.state, 'HAVE_MASTER', 'the state is correct');
strictEqual(state, 'HAVE_MASTER', 'the state at loadedplaylist correct');
});
test('jumps to HAVE_METADATA when initialized with a media playlist', function() {
......@@ -453,6 +456,20 @@
'updated the active media');
});
test('can switch playlists immediately after the master is downloaded', function() {
var loader = new videojs.Hls.PlaylistLoader('master.m3u8');
loader.on('loadedplaylist', function() {
loader.media('high.m3u8');
});
requests.pop().respond(200, null,
'#EXTM3U\n' +
'#EXT-X-STREAM-INF:BANDWIDTH=1\n' +
'low.m3u8\n' +
'#EXT-X-STREAM-INF:BANDWIDTH=2\n' +
'high.m3u8\n');
equal(requests[0].url, urlTo('high.m3u8'), 'switched variants immediately');
});
test('can switch media playlists based on URI', function() {
var loader = new videojs.Hls.PlaylistLoader('master.m3u8');
requests.pop().respond(200, null,
......@@ -624,9 +641,6 @@
'low.m3u8\n' +
'#EXT-X-STREAM-INF:BANDWIDTH=2\n' +
'high.m3u8\n');
throws(function() {
loader.media('high.m3u8');
}, 'throws an error from HAVE_MASTER');
});
test('throws an error if a switch to an unrecognized playlist is requested', function() {
......@@ -757,10 +771,8 @@
'#EXTINF:5,\n' +
'1.ts\n' +
'#EXT-X-ENDLIST\n');
equal(loader.getMediaIndexForTime_(4), 0, 'rounds down exact matches');
equal(loader.getMediaIndexForTime_(4), 1, 'rounds up exact matches');
equal(loader.getMediaIndexForTime_(3.7), 0, 'rounds down');
// FIXME: the test below should pass for HLSv3
//equal(loader.getMediaIndexForTime_(4.2), 0, 'rounds down');
equal(loader.getMediaIndexForTime_(4.5), 1, 'rounds up at 0.5');
});
......