bce76238 by David LaPalomento

Merge pull request #445 from dmlap/seekable-start

	Use information from buffering to calculate seekable
2 parents c46fd273 4b4efa03
......@@ -390,7 +390,10 @@
return 0;
}
// 1) Walk backward until we find the first segment with timeline
// find segments with known timing information that bound the
// target time
// Walk backward until we find the first segment with timeline
// information that is earlier than `time`
for (i = lastSegment; i >= 0; i--) {
segment = this.media_.segments[i];
......@@ -415,7 +418,7 @@
}
}
// 2) Walk forward until we find the first segment with timeline
// 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];
......@@ -424,7 +427,8 @@
knownEnd = segment.start;
if (endIndex < 0) {
// The first segment claims to start *after* the time we are
// searching for so just return it
// searching for so the target segment must no longer be
// available
return -1;
}
break;
......@@ -436,6 +440,9 @@
}
}
// use the bounds we just found and playlist information to
// estimate the segment that contains the time we are looking for
if (startIndex !== undefined) {
// We have a known-start point that is before our desired time so
// walk from that point forwards
......
......@@ -4,23 +4,70 @@
(function(window, videojs) {
'use strict';
var DEFAULT_TARGET_DURATION = 10;
var duration, intervalDuration, optionalMin, optionalMax, seekable;
// Math.min that will return the alternative input if one of its
// parameters in undefined
optionalMin = function(left, right) {
left = isFinite(left) ? left : Infinity;
right = isFinite(right) ? right : Infinity;
return Math.min(left, right);
var duration, intervalDuration, backwardDuration, forwardDuration, seekable;
backwardDuration = function(playlist, endSequence) {
var result = 0, segment, i;
i = endSequence - playlist.mediaSequence;
// if a start time is available for segment immediately following
// the interval, use it
segment = playlist.segments[i];
// Walk backward until we find the latest segment with timeline
// information that is earlier than endSequence
if (segment) {
if (segment.start !== undefined) {
return { result: segment.start, precise: true };
}
if (segment.end !== undefined) {
return {
result: segment.end - segment.duration,
precise: true
};
}
}
while (i--) {
segment = playlist.segments[i];
if (segment.end !== undefined) {
return { result: result + segment.end, precise: true };
}
result += segment.duration;
// Math.max that will return the alternative input if one of its
// parameters in undefined
optionalMax = function(left, right) {
left = isFinite(left) ? left: -Infinity;
right = isFinite(right) ? right: -Infinity;
return Math.max(left, right);
if (segment.start !== undefined) {
return { result: result + segment.start, precise: true };
}
}
return { result: result, precise: false };
};
forwardDuration = function(playlist, endSequence) {
var result = 0, segment, i;
i = endSequence - playlist.mediaSequence;
// Walk forward until we find the earliest segment with timeline
// information
for (; i < playlist.segments.length; i++) {
segment = playlist.segments[i];
if (segment.start !== undefined) {
return {
result: segment.start - result,
precise: true
};
}
result += segment.duration;
if (segment.end !== undefined) {
return {
result: segment.end - result,
precise: true
};
}
}
// indicate we didn't find a useful duration estimate
return { result: -1, precise: false };
};
/**
......@@ -31,42 +78,40 @@
* @param playlist {object} a media playlist object
* @param endSequence {number} (optional) an exclusive upper boundary
* for the playlist. Defaults to playlist length.
* @return {number} the duration between the start index and end
* index.
* @return {number} the duration between the first available segment
* and end index.
*/
intervalDuration = function(playlist, endSequence) {
var result = 0, segment, targetDuration, i;
var backward, forward;
if (endSequence === undefined) {
endSequence = playlist.mediaSequence + (playlist.segments || []).length;
endSequence = playlist.mediaSequence + playlist.segments.length;
}
if (endSequence < 0) {
if (endSequence < playlist.mediaSequence) {
return 0;
}
targetDuration = playlist.targetDuration || DEFAULT_TARGET_DURATION;
i = endSequence - playlist.mediaSequence;
// if a start time is available for segment immediately following
// the interval, use it
segment = playlist.segments[i];
// Walk backward until we find the latest segment with timeline
// information that is earlier than endSequence
if (segment && segment.start !== undefined) {
return segment.start;
// do a backward walk to estimate the duration
backward = backwardDuration(playlist, endSequence);
if (backward.precise) {
// if we were able to base our duration estimate on timing
// information provided directly from the Media Source, return
// it
return backward.result;
}
while (i--) {
segment = playlist.segments[i];
if (segment.end !== undefined) {
return result + segment.end;
}
result += (segment.duration || targetDuration);
if (segment.start !== undefined) {
return result + segment.start;
}
// walk forward to see if a precise duration estimate can be made
// that way
forward = forwardDuration(playlist, endSequence);
if (forward.precise) {
// we found a segment that has been buffered and so it's
// position is known precisely
return forward.result;
}
return result;
// return the less-precise, playlist-based duration estimate
return backward.result;
};
/**
......
......@@ -52,6 +52,9 @@ videojs.HlsHandler = videojs.extend(Component, {
// being downloaded or processed
this.pendingSegment_ = null;
// start playlist selection at a reasonable bandwidth for
// broadband internet
this.bandwidth = options.bandwidth || 4194304; // 0.5 Mbps
this.bytesReceived = 0;
// loadingState_ tracks how far along the buffering process we
......
......@@ -93,15 +93,50 @@
equal(duration, 50.0002, 'calculated with mixed intervals');
});
test('uses timeline values for the expired duration of live playlists', function() {
var playlist = {
mediaSequence: 12,
segments: [{
duration: 10,
end: 120.5,
uri: '0.ts'
}, {
duration: 9,
uri: '1.ts'
}]
}, duration;
duration = Playlist.duration(playlist, playlist.mediaSequence);
equal(duration, 110.5, 'used segment end time');
duration = Playlist.duration(playlist, playlist.mediaSequence + 1);
equal(duration, 120.5, 'used segment end time');
duration = Playlist.duration(playlist, playlist.mediaSequence + 2);
equal(duration, 120.5 + 9, 'used segment end time');
});
test('looks outside the queried interval for live playlist timeline values', function() {
var playlist = {
mediaSequence: 12,
segments: [{
duration: 10,
uri: '0.ts'
}, {
duration: 9,
end: 120.5,
uri: '1.ts'
}]
}, duration;
duration = Playlist.duration(playlist, playlist.mediaSequence);
equal(duration, 120.5 - 9 - 10, 'used segment end time');
});
test('ignores discontinuity sequences later than the end', function() {
var duration = Playlist.duration({
mediaSequence: 0,
discontinuityStarts: [1, 3],
segments: [{
minVideoPts: 0,
minAudioPts: 0,
maxVideoPts: 10 * 1000,
maxAudioPts: 10 * 1000,
duration: 10,
uri: '0.ts'
}, {
discontinuity: true,
......@@ -200,17 +235,10 @@
endList: true,
discontinuityStarts: [1],
segments: [{
minVideoPts: 0,
minAudioPts: 0,
maxVideoPts: 1 * 10 * 1000,
maxAudioPts: 1 * 10 * 1000,
duration: 10,
uri: '0.ts'
}, {
discontinuity: true,
minVideoPts: 2 * 10 * 1000,
minAudioPts: 2 * 10 * 1000,
maxVideoPts: 3 * 10 * 1000,
maxAudioPts: 3 * 10 * 1000,
duration: 10,
uri: '1.ts'
}]
......@@ -219,54 +247,6 @@
equal(duration, 10 + 10, 'handles discontinuities');
});
test('does not count ending segment gaps across a discontinuity', function() {
var duration = Playlist.duration({
mediaSequence: 0,
discontinuityStarts: [1],
endList: true,
segments: [{
minVideoPts: 0,
minAudioPts: 0,
maxVideoPts: 1 * 10 * 1000,
maxAudioPts: 1 * 10 * 1000,
uri: '0.ts'
}, {
discontinuity: true,
minVideoPts: 1 * 10 * 1000 + 100,
minAudioPts: 1 * 10 * 1000 + 100,
maxVideoPts: 2 * 10 * 1000 + 100,
maxAudioPts: 2 * 10 * 1000 + 100,
duration: 10,
uri: '1.ts'
}]
}, 1);
equal(duration, (1 * 10 * 1000) * 0.001, 'did not include the segment gap');
});
test('trailing duration on the final segment can be excluded', function() {
var duration = Playlist.duration({
mediaSequence: 0,
endList: true,
segments: [{
minVideoPts: 0,
minAudioPts: 0,
maxVideoPts: 1 * 10 * 1000,
maxAudioPts: 1 * 10 * 1000,
uri: '0.ts'
}, {
minVideoPts: 1 * 10 * 1000 + 100,
minAudioPts: 1 * 10 * 1000 + 100,
maxVideoPts: 2 * 10 * 1000 + 100,
maxAudioPts: 2 * 10 * 1000 + 100,
duration: 10,
uri: '1.ts'
}]
}, 1, false);
equal(duration, (1 * 10 * 1000) * 0.001, 'did not include the segment gap');
});
test('a non-positive length interval has zero duration', function() {
var playlist = {
mediaSequence: 0,
......@@ -341,16 +321,19 @@
test('only considers available segments', function() {
var seekable = Playlist.seekable({
targetDuration: 10,
mediaSequence: 7,
segments: [{
uri: '8.ts'
uri: '8.ts',
duration: 10
}, {
uri: '9.ts'
uri: '9.ts',
duration: 10
}, {
uri: '10.ts'
uri: '10.ts',
duration: 10
}, {
uri: '11.ts'
uri: '11.ts',
duration: 10
}]
});
equal(seekable.length, 1, 'there are seekable ranges');
......
......@@ -738,8 +738,8 @@ test('buffer checks are noops when only the master is ready', function() {
type: 'application/vnd.apple.mpegurl'
});
openMediaSource(player);
standardXHRResponse(requests.shift());
standardXHRResponse(requests.shift());
standardXHRResponse(requests.shift()); // master
standardXHRResponse(requests.shift()); // media
// ignore any outstanding segment requests
requests.length = 0;
......@@ -752,7 +752,8 @@ test('buffer checks are noops when only the master is ready', function() {
openMediaSource(player);
// respond with the master playlist but don't send the media playlist yet
standardXHRResponse(requests.shift());
player.tech_.hls.bandwidth = 1; // force media1 to be requested
standardXHRResponse(requests.shift()); // master
// trigger fillBuffer()
player.tech_.hls.checkBuffer_();
......@@ -1362,8 +1363,8 @@ test('waits to download new segments until the media playlist is stable', functi
type: 'application/vnd.apple.mpegurl'
});
openMediaSource(player);
standardXHRResponse(requests.shift()); // master
player.tech_.hls.bandwidth = 1; // make sure we stay on the lowest variant
standardXHRResponse(requests.shift()); // master
standardXHRResponse(requests.shift()); // media1
// force a playlist switch
......