bce88a18 by jrivera Committed by David LaPalomento

buffer at the current time range end instead of incrementing a variable. closes #423

1 parent c7d5b626
......@@ -2,7 +2,7 @@ CHANGELOG
=========
## HEAD (Unreleased)
_(none)_
* buffer at the current time range end instead of incrementing a variable. ([view](https://github.com/videojs/videojs-contrib-hls/pull/423))
--------------------
......
......@@ -44,7 +44,7 @@
"karma-sauce-launcher": "~0.1.8",
"qunitjs": "^1.18.0",
"sinon": "1.10.2",
"video.js": "^5.0.0-rc.96"
"video.js": "^5.1.0"
},
"dependencies": {
"pkcs7": "^0.2.2",
......
......@@ -367,7 +367,17 @@
* closest playback position that is currently available.
*/
PlaylistLoader.prototype.getMediaIndexForTime_ = function(time) {
var i, j, segment, targetDuration;
var
i,
segment,
originalTime = time,
targetDuration = this.media_.targetDuration || 10,
numSegments = this.media_.segments.length,
lastSegment = numSegments - 1,
startIndex,
endIndex,
knownStart,
knownEnd;
if (!this.media_) {
return 0;
......@@ -379,57 +389,105 @@
return 0;
}
// 1) Walk backward until we find the latest segment with timeline
// 1) Walk backward until we find the first segment with timeline
// information that is earlier than `time`
targetDuration = this.media_.targetDuration || 10;
i = this.media_.segments.length;
while (i--) {
for (i = lastSegment; i >= 0; i--) {
segment = this.media_.segments[i];
if (segment.end !== undefined && segment.end <= time) {
time -= segment.end;
startIndex = i + 1;
knownStart = segment.end;
if (startIndex >= numSegments) {
// The last segment claims to end *before* the time we are
// searching for so just return it
return numSegments;
}
break;
}
if (segment.start !== undefined && segment.start < time) {
if (segment.start !== undefined && segment.start <= time) {
if (segment.end !== undefined && segment.end > time) {
// we've found the target segment exactly
return i;
}
startIndex = i;
knownStart = segment.start;
break;
}
}
time -= segment.start;
time -= segment.duration || targetDuration;
if (time < 0) {
// the segment with start information is also our best guess
// for the momment
return i;
// 2) Walk forward until we find the first segment with timeline
// information that is greater than `time`
for (i = 0; i < numSegments; i++) {
segment = this.media_.segments[i];
if (segment.start !== undefined && segment.start > time) {
endIndex = i - 1;
knownEnd = segment.start;
if (endIndex < 0) {
// The first segment claims to start *after* the time we are
// searching for so just return it
return -1;
}
break;
}
if (segment.end !== undefined && segment.end > time) {
endIndex = i;
knownEnd = segment.end;
break;
}
}
i++;
// 2) Walk forward, testing each segment to see if `time` falls within it
for (j = i; j < this.media_.segments.length; j++) {
segment = this.media_.segments[j];
time -= segment.duration || targetDuration;
if (startIndex !== undefined) {
// We have a known-start point that is before our desired time so
// walk from that point forwards
time = time - knownStart;
for (i = startIndex; i < (endIndex || numSegments); i++) {
segment = this.media_.segments[i];
time -= segment.duration || targetDuration;
if (time < 0) {
return i;
}
}
if (time < 0) {
return j;
if (i === endIndex) {
// We haven't found a segment but we did hit a known end point
// so fallback to "Algorithm Jon" - try to interpolate the segment
// index based on the known span of the timeline we are dealing with
// and the number of segments inside that span
return startIndex + Math.floor(
((originalTime - knownStart) / (knownEnd - knownStart)) *
(endIndex - startIndex));
}
// 2a) If we discover a segment that has timeline information
// before finding the result segment, the playlist information
// must have been inaccurate. Start a binary search for the
// segment which contains `time`. If the guess turns out to be
// incorrect, we'll have more info to work with next time.
if (segment.start !== undefined || segment.end !== undefined) {
return Math.floor((j - i) * 0.5);
// We _still_ haven't found a segment so load the last one
return lastSegment;
} else if (endIndex !== undefined) {
// We _only_ have a known-end point that is after our desired time so
// walk from that point backwards
time = knownEnd - time;
for (i = endIndex; i >= 0; i--) {
segment = this.media_.segments[i];
time -= segment.duration || targetDuration;
if (time < 0) {
return i;
}
}
// We haven't found a segment so load the first one
return 0;
} else {
// We known nothing so use "Algorithm A" - walk from the front
// of the playlist naively subtracking durations until we find
// a segment that contains time and return it
for (i = 0; i < numSegments; i++) {
segment = this.media_.segments[i];
time -= segment.duration || targetDuration;
if (time < 0) {
return i;
}
}
// We are out of possible candidates so load the last one...
// The last one is the least likely to overlap a buffer and therefore
// the one most likely to tell us something about the timeline
return lastSegment;
}
// the playback position is outside the range of available
// segments so return the length
return this.media_.segments.length;
};
videojs.Hls.PlaylistLoader = PlaylistLoader;
......
......@@ -653,8 +653,8 @@
equal(loader.getMediaIndexForTime_(3), 0, 'time three is index zero');
equal(loader.getMediaIndexForTime_(10), 2, 'time 10 is index 2');
equal(loader.getMediaIndexForTime_(22),
3,
'time greater than the length is index 3');
2,
'time greater than the length is index 2');
});
test('returns the lower index when calculating for a segment boundary', function() {
......@@ -683,9 +683,9 @@
'1002.ts\n');
loader.media().segments[0].start = 150;
equal(loader.getMediaIndexForTime_(0), 0, 'the lowest returned value is zero');
equal(loader.getMediaIndexForTime_(45), 0, 'expired content returns zero');
equal(loader.getMediaIndexForTime_(75), 0, 'expired content returns zero');
equal(loader.getMediaIndexForTime_(0), -1, 'the lowest returned value is negative one');
equal(loader.getMediaIndexForTime_(45), -1, 'expired content returns negative one');
equal(loader.getMediaIndexForTime_(75), -1, 'expired content returns negative one');
equal(loader.getMediaIndexForTime_(50 + 100), 0, 'calculates the earliest available position');
equal(loader.getMediaIndexForTime_(50 + 100 + 2), 0, 'calculates within the first segment');
equal(loader.getMediaIndexForTime_(50 + 100 + 2), 0, 'calculates within the first segment');
......
......@@ -218,6 +218,7 @@ module('HLS', {
var el = document.createElement('div');
el.id = 'vjs_mock_flash_' + nextId++;
el.className = 'vjs-tech vjs-mock-flash';
el.duration = Infinity;
el.vjs_load = function() {};
el.vjs_getProperty = function(attr) {
if (attr === 'buffered') {
......@@ -1131,7 +1132,7 @@ test('buffers based on the correct TimeRange if multiple ranges exist', function
return videojs.createTimeRange(buffered);
};
currentTime = 8;
buffered = [[0, 10], [20, 40]];
buffered = [[0, 10], [20, 30]];
standardXHRResponse(requests[0]);
standardXHRResponse(requests[1]);
......@@ -1144,14 +1145,9 @@ test('buffers based on the correct TimeRange if multiple ranges exist', function
currentTime = 22;
player.tech_.hls.sourceBuffer.trigger('updateend');
player.tech_.hls.checkBuffer_();
strictEqual(requests.length, 2, 'made no additional requests');
buffered = [[0, 10], [20, 30]];
player.tech_.hls.checkBuffer_();
standardXHRResponse(requests[2]);
strictEqual(requests.length, 3, 'made three requests');
strictEqual(requests[2].url,
absoluteUrl('manifest/media-00004.ts'),
absoluteUrl('manifest/media-00003.ts'),
'made segment request');
});
......@@ -1381,7 +1377,7 @@ test('seeking in an empty playlist is a non-erroring noop', function() {
equal(requests.length, requestsLength, 'made no additional requests');
});
test('duration is Infinity for live playlists', function() {
test('tech\'s duration reports Infinity for live playlists', function() {
player.src({
src: 'http://example.com/manifest/missingEndlist.m3u8',
type: 'application/vnd.apple.mpegurl'
......@@ -1390,9 +1386,13 @@ test('duration is Infinity for live playlists', function() {
standardXHRResponse(requests[0]);
strictEqual(player.tech_.hls.mediaSource.duration,
strictEqual(player.tech_.duration(),
Infinity,
'duration on the tech is infinity');
notEqual(player.tech_.hls.mediaSource.duration,
Infinity,
'duration is infinity');
'duration on the mediaSource is not infinity');
});
test('live playlist starts three target durations before live', function() {
......@@ -1644,7 +1644,7 @@ test('calls mediaSource\'s timestampOffset on discontinuity', function() {
});
test('sets timestampOffset when seeking with discontinuities', function() {
var removes = [], timeRange = videojs.createTimeRange(0, 10);
var timeRange = videojs.createTimeRange(0, 10);
player.src({
src: 'discontinuity.m3u8',
......@@ -1670,18 +1670,12 @@ test('sets timestampOffset when seeking with discontinuities', function() {
'3.ts\n' +
'#EXT-X-ENDLIST\n');
player.tech_.hls.sourceBuffer.timestampOffset = 0;
player.tech_.hls.sourceBuffer.remove = function(start, end) {
timeRange = videojs.createTimeRange();
removes.push([start, end]);
};
player.currentTime(21);
clock.tick(1);
equal(requests.shift().aborted, true, 'aborted first request');
standardXHRResponse(requests.pop()); // 3.ts
clock.tick(1000);
equal(player.tech_.hls.sourceBuffer.timestampOffset, 20, 'timestampOffset starts at zero');
equal(removes.length, 1, 'remove was called');
});
test('can seek before the source buffer opens', function() {
......