aac49993 by jrivera

Tests for endOfStream and segment.duration tracking behavior in updateEndHandler

1 parent cf295e56
......@@ -298,7 +298,7 @@ videojs.Hls.findSoleUncommonTimeRangesEnd_ = function(original, update) {
};
/**
* Updates segment with information about it's end-point in time and, optionally,
* Updates segment with information about its end-point in time and, optionally,
* the segment duration if we have enough information to determine a segment duration
* accurately.
* @param playlist {object} a media playlist object
......@@ -322,7 +322,7 @@ videojs.HlsHandler.prototype.updateSegmentMetadata_ = function(playlist, segment
// fix up segment durations based on segment end data
if (!previousSegment) {
// first segment is always has a start time of 0 making it's duration
// first segment is always has a start time of 0 making its duration
// equal to the segment end
segment.duration = segment.end;
} else if (previousSegment.end) {
......@@ -337,9 +337,9 @@ videojs.HlsHandler.prototype.updateSegmentMetadata_ = function(playlist, segment
* @param playlist {object} a media playlist object
* @param segmentIndex {number} the index of segment we last appended
* @param currentBuffered {object} the buffered region that currentTime resides in
* @return {boolean} whether or not endOfStream was called on the MediaSource
* @return {boolean} whether the calling function should call endOfStream on the MediaSource
*/
videojs.HlsHandler.prototype.maybeEndOfStream_ = function(playlist, segmentIndex, currentBuffered) {
videojs.HlsHandler.prototype.isEndOfStream_ = function(playlist, segmentIndex, currentBuffered) {
var
segments = playlist.segments,
appendedLastSegment,
......@@ -358,14 +358,9 @@ videojs.HlsHandler.prototype.maybeEndOfStream_ = function(playlist, segmentIndex
// if we've buffered to the end of the video, we need to call endOfStream
// so that MediaSources can trigger the `ended` event when it runs out of
// buffered data instead of waiting for me
if (playlist.endList &&
this.mediaSource.readyState === 'open' &&
(appendedLastSegment ||
bufferedToEnd)) {
this.mediaSource.endOfStream();
return true;
}
return false;
return playlist.endList &&
this.mediaSource.readyState === 'open' &&
(appendedLastSegment || bufferedToEnd);
};
var parseCodecs = function(codecs) {
......@@ -1310,7 +1305,8 @@ videojs.HlsHandler.prototype.updateEndHandler_ = function () {
currentMediaIndex,
currentBuffered,
seekable,
timelineUpdate;
timelineUpdate,
isEndOfStream;
// stop here if the update errored or was aborted
if (!segmentInfo) {
......@@ -1330,10 +1326,14 @@ videojs.HlsHandler.prototype.updateEndHandler_ = function () {
segments = playlist.segments;
currentMediaIndex = segmentInfo.mediaIndex + (segmentInfo.mediaSequence - playlist.mediaSequence);
currentBuffered = this.findBufferedRange_();
isEndOfStream = this.isEndOfStream_(playlist, currentMediaIndex, currentBuffered);
// if we switched renditions don't try to add segment timeline
// information to the playlist
if (segmentInfo.playlist.uri !== this.playlists.media().uri) {
if (isEndOfStream) {
return this.mediaSource.endOfStream();
}
return this.fillBuffer();
}
......@@ -1366,8 +1366,8 @@ videojs.HlsHandler.prototype.updateEndHandler_ = function () {
// If we decide to signal the end of stream, then we can return instead
// of trying to fetch more segments
if (this.maybeEndOfStream_(playlist, currentMediaIndex, currentBuffered)) {
return;
if (isEndOfStream) {
return this.mediaSource.endOfStream();
}
if (timelineUpdate !== null ||
......
......@@ -2144,6 +2144,79 @@ test('tracks segment end times as they are buffered', function() {
equal(player.tech_.hls.mediaSource.duration, 10 + 9.5, 'updated duration');
});
test('updates first segment duration as it is buffered', function() {
var bufferEnd = 0;
player.src({
src: 'media.m3u8',
type: 'application/x-mpegURL'
});
openMediaSource(player);
// as new segments are downloaded, the buffer end is updated
player.tech_.buffered = function() {
return videojs.createTimeRange(0, bufferEnd);
};
requests.shift().respond(200, null,
'#EXTM3U\n' +
'#EXTINF:10,\n' +
'0.ts\n' +
'#EXTINF:10,\n' +
'1.ts\n' +
'#EXT-X-ENDLIST\n');
// 0.ts is shorter than advertised
standardXHRResponse(requests.shift());
equal(player.tech_.hls.mediaSource.duration, 20, 'original duration is from the m3u8');
equal(player.tech_.hls.playlists.media().segments[0].duration, 10,
'segment duration initially based on playlist');
bufferEnd = 9.5;
player.tech_.hls.sourceBuffer.trigger('update');
player.tech_.hls.sourceBuffer.trigger('updateend');
equal(player.tech_.hls.playlists.media().segments[0].duration, 9.5,
'updated segment duration');
});
test('updates segment durations as they are buffered', function() {
var bufferEnd = 0;
player.src({
src: 'media.m3u8',
type: 'application/x-mpegURL'
});
openMediaSource(player);
// as new segments are downloaded, the buffer end is updated
player.tech_.buffered = function() {
return videojs.createTimeRange(0, bufferEnd);
};
requests.shift().respond(200, null,
'#EXTM3U\n' +
'#EXTINF:10,\n' +
'0.ts\n' +
'#EXTINF:10,\n' +
'1.ts\n' +
'#EXT-X-ENDLIST\n');
// 0.ts is shorter than advertised
standardXHRResponse(requests.shift());
equal(player.tech_.hls.mediaSource.duration, 20, 'original duration is from the m3u8');
equal(player.tech_.hls.playlists.media().segments[1].duration, 10,
'segment duration initially based on playlist');
bufferEnd = 9.5;
player.tech_.hls.sourceBuffer.trigger('update');
player.tech_.hls.sourceBuffer.trigger('updateend');
clock.tick(1);
standardXHRResponse(requests.shift());
bufferEnd = 19;
player.tech_.hls.sourceBuffer.trigger('update');
player.tech_.hls.sourceBuffer.trigger('updateend');
equal(player.tech_.hls.playlists.media().segments[1].duration, 9.5,
'updated segment duration');
});
QUnit.skip('seeking does not fail when targeted between segments', function() {
var currentTime, segmentUrl;
player.src({
......@@ -2428,7 +2501,7 @@ test('can be disposed before finishing initialization', function() {
}
});
test('calls ended() on the media source at the end of a playlist', function() {
test('calls endOfStream on the media source after appending the last segment', function() {
var endOfStreams = 0, buffered = [[]];
player.src({
src: 'http://example.com/media.m3u8',
......@@ -2441,11 +2514,15 @@ test('calls ended() on the media source at the end of a playlist', function() {
player.tech_.hls.mediaSource.endOfStream = function() {
endOfStreams++;
};
player.currentTime(20);
clock.tick(1);
// playlist response
requests.shift().respond(200, null,
'#EXTM3U\n' +
'#EXTINF:10,\n' +
'0.ts\n' +
'#EXTINF:10,\n' +
'1.ts\n' +
'#EXT-X-ENDLIST\n');
// segment response
requests[0].response = new ArrayBuffer(17);
......@@ -2454,7 +2531,50 @@ test('calls ended() on the media source at the end of a playlist', function() {
buffered =[[0, 10]];
player.tech_.hls.sourceBuffer.trigger('updateend');
strictEqual(endOfStreams, 1, 'ended media source');
strictEqual(endOfStreams, 1, 'called endOfStream on the media source');
});
test('calls endOfStream on the media source when the current buffer ends at duration', function() {
var endOfStreams = 0, buffered = [[]];
player.src({
src: 'http://example.com/media.m3u8',
type: 'application/vnd.apple.mpegurl'
});
openMediaSource(player);
player.tech_.buffered = function() {
return videojs.createTimeRanges(buffered);
};
player.tech_.hls.mediaSource.endOfStream = function() {
endOfStreams++;
};
player.currentTime(19);
clock.tick(1);
// playlist response
requests.shift().respond(200, null,
'#EXTM3U\n' +
'#EXTINF:10,\n' +
'0.ts\n' +
'#EXTINF:10,\n' +
'1.ts\n' +
'#EXT-X-ENDLIST\n');
// segment response
requests[0].response = new ArrayBuffer(17);
requests.shift().respond(200, null, '');
strictEqual(endOfStreams, 0, 'waits for the buffer update to finish');
buffered =[[10, 20]];
player.tech_.hls.sourceBuffer.trigger('updateend');
player.currentTime(5);
clock.tick(1);
// segment response
requests[0].response = new ArrayBuffer(17);
requests.shift().respond(200, null, '');
buffered =[[0, 20]];
player.tech_.hls.sourceBuffer.trigger('updateend');
strictEqual(endOfStreams, 2, 'called endOfStream on the media source twice');
});
test('calling play() at the end of a video replays', function() {
......