1cf59034 by David LaPalomento

calculate duration from the longest time range with PTS information

Instead of calculating the duration by accumulating spans with and without PTS info from left to right, find the largest continuous span that has PTS information and then add up EXT-INF based durations before and after those spans. To make this proces simpler, split the interval we are calculating into segment sequences with continuous timestamps. Add segment indices for discontinuities to the m3u8 parser to support splitting intervals.
1 parent b068a6e6
Showing 47 changed files with 429 additions and 209 deletions
......@@ -375,7 +375,8 @@
// the manifest is empty until the parse stream begins delivering data
this.manifest = {
allowCache: true
allowCache: true,
discontinuityStarts: []
};
// update the manifest with the m3u8 entry from the parse stream
......@@ -513,6 +514,7 @@
},
'discontinuity': function() {
currentUri.discontinuity = true;
this.manifest.discontinuityStarts.push(uris.length);
},
'targetduration': function() {
if (!isFinite(entry.duration) || entry.duration < 0) {
......
......@@ -432,7 +432,7 @@
time -= Playlist.duration(this.media_,
this.media_.mediaSequence + i,
this.media_.mediaSequence + i + 1,
true);
false);
// HLS version 3 and lower round segment durations to the
// nearest decimal integer. When the correct media index is
......
......@@ -5,54 +5,126 @@
'use strict';
var DEFAULT_TARGET_DURATION = 10;
var accumulateDuration, duration, seekable, segmentsDuration;
var accumulateDuration, ascendingNumeric, duration, intervalDuration, rangeDuration, seekable;
accumulateDuration = function(playlist, startSequence, endSequence, strict) {
// Array.sort comparator to sort numbers in ascending order
ascendingNumeric = function(left, right) {
return left - right;
};
/**
* Returns the media duration for the segments between a start and
* exclusive end index. The start and end parameters are interpreted
* as indices into the currently available segments. This method
* does not calculate durations for segments that have expired.
* @param playlist {object} a media playlist object
* @param start {number} an inclusive lower boundary for the
* segments to examine.
* @param end {number} an exclusive upper boundary for the segments
* to examine.
* @param includeTrailingTime {boolean} if false, the interval between
* the final segment and the subsequent segment will not be included
* in the result
* @return {number} the duration between the start index and end
* index in seconds.
*/
accumulateDuration = function(playlist, start, end, includeTrailingTime) {
var
ranges = [],
rangeEnds = (playlist.discontinuityStarts || []).concat(end),
result = 0,
i;
// short circuit if start and end don't specify a non-empty range
// of segments
if (start >= end) {
return 0;
}
// create a range object for each discontinuity sequence
rangeEnds.sort(ascendingNumeric);
for (i = 0; i < rangeEnds.length; i++) {
if (rangeEnds[i] > start) {
ranges.push({ start: start, end: rangeEnds[i] });
i++;
break;
}
}
for (; i < rangeEnds.length; i++) {
// ignore times ranges later than end
if (rangeEnds[i] >= end) {
ranges.push({ start: rangeEnds[i - 1], end: end });
break;
}
ranges.push({ start: ranges[ranges.length - 1].end, end: rangeEnds[i] });
}
// add up the durations for each of the ranges
for (i = 0; i < ranges.length; i++) {
result += rangeDuration(playlist,
ranges[i],
i === ranges.length - 1 && includeTrailingTime);
}
return result;
};
/**
* Returns the duration of the specified range of segments. The
* range *must not* cross a discontinuity.
* @param playlist {object} a media playlist object
* @param range {object} an object that specifies a starting and
* ending index into the available segments.
* @param includeTrailingTime {boolean} if false, the interval between
* the final segment and the subsequent segment will not be included
* in the result
* @return {number} the duration of the range in seconds.
*/
rangeDuration = function(playlist, range, includeTrailingTime) {
var
result = 0,
targetDuration = playlist.targetDuration || DEFAULT_TARGET_DURATION,
segment, endSegment,
i, j;
segment,
left, right;
// accumulate the segment durations into the result
for (i = startSequence; i < endSequence; i++) {
segment = playlist.segments[i - playlist.mediaSequence];
// when PTS values aren't available, use information from the playlist
if (segment.minVideoPts === undefined) {
result += segment.duration ||
targetDuration;
continue;
// accumulate while searching for the earliest segment with
// available PTS information
for (left = range.start; left < range.end; left++) {
segment = playlist.segments[left];
if (segment.minVideoPts !== undefined) {
break;
}
result += segment.duration || targetDuration;
}
// find the last segment with PTS info and use that to calculate
// the interval duration
for (j = i; j < endSequence - 1; j++) {
endSegment = playlist.segments[j - playlist.mediaSequence + 1];
if (endSegment.maxVideoPts === undefined ||
endSegment.discontinuity) {
break;
}
// see if there's enough information to include the trailing time
if (includeTrailingTime) {
segment = playlist.segments[range.end];
if (segment && segment.minVideoPts !== undefined) {
result += 0.001 *
(Math.min(segment.minVideoPts, segment.minAudioPts) -
Math.min(playlist.segments[left].minVideoPts,
playlist.segments[left].minAudioPts));
return result;
}
endSegment = playlist.segments[j - playlist.mediaSequence];
}
result += (Math.max(endSegment.maxVideoPts, endSegment.maxAudioPts) -
Math.min(segment.minVideoPts, segment.minAudioPts)) * 0.001;
i = j;
// do the same thing while finding the latest segment
for (right = range.end - 1; right >= left; right--) {
segment = playlist.segments[right];
if (segment.maxVideoPts !== undefined) {
break;
}
result += segment.duration || targetDuration;
}
// attribute the gap between the latest PTS value in end segment
// and the earlier PTS in the next one to the result
segment = playlist.segments[endSequence - 1];
endSegment = playlist.segments[endSequence];
if (!strict &&
endSegment &&
!endSegment.discontinuity &&
endSegment.minVideoPts &&
segment &&
segment.maxVideoPts) {
result += (Math.min(endSegment.minVideoPts, endSegment.minAudioPts) -
Math.max(segment.maxVideoPts, segment.maxAudioPts)) * 0.001;
// add in the PTS interval in seconds between them
if (right >= left) {
result += 0.001 *
(Math.max(playlist.segments[right].maxVideoPts,
playlist.segments[right].maxAudioPts) -
Math.min(playlist.segments[left].minVideoPts,
playlist.segments[left].minAudioPts));
}
return result;
......@@ -68,14 +140,14 @@
* boundary for the playlist. Defaults to 0.
* @param endSequence {number} (optional) an exclusive upper boundary
* for the playlist. Defaults to playlist length.
* @param strict {boolean} (optional) if true, the interval between
* @param includeTrailingTime {boolean} if false, the interval between
* the final segment and the subsequent segment will not be included
* in the result
* @return {number} the duration between the start index and end
* index.
*/
segmentsDuration = function(playlist, startSequence, endSequence, strict) {
var targetDuration, expiredSegmentCount, result = 0;
intervalDuration = function(playlist, startSequence, endSequence, includeTrailingTime) {
var result = 0, targetDuration, expiredSegmentCount;
startSequence = startSequence || 0;
endSequence = endSequence !== undefined ? endSequence : (playlist.segments || []).length;
......@@ -87,9 +159,9 @@
// accumulate the segment durations into the result
result += accumulateDuration(playlist,
startSequence + expiredSegmentCount,
endSequence,
strict);
startSequence + expiredSegmentCount - playlist.mediaSequence,
endSequence - playlist.mediaSequence,
includeTrailingTime);
return result;
};
......@@ -104,17 +176,21 @@
* boundary for the playlist. Defaults to 0.
* @param endSequence {number} (optional) an exclusive upper boundary
* for the playlist. Defaults to playlist length.
* @param strict {boolean} (optional) if true, the interval between
* @param includeTrailingTime {boolean} (optional) if false, the interval between
* the final segment and the subsequent segment will not be included
* in the result
* @return {number} the duration between the start index and end
* index.
*/
duration = function(playlist, startSequence, endSequence, strict) {
duration = function(playlist, startSequence, endSequence, includeTrailingTime) {
if (!playlist) {
return 0;
}
if (includeTrailingTime === undefined) {
includeTrailingTime = true;
}
// if a slice of the total duration is not requested, use
// playlist-level duration indicators when they're present
if (startSequence === undefined && endSequence === undefined) {
......@@ -130,10 +206,10 @@
}
// calculate the total duration based on the segment durations
return segmentsDuration(playlist,
return intervalDuration(playlist,
startSequence,
endSequence,
strict);
includeTrailingTime);
};
/**
......@@ -155,8 +231,8 @@
return videojs.createTimeRange(0, duration(playlist));
}
start = segmentsDuration(playlist, 0, playlist.mediaSequence);
end = start + segmentsDuration(playlist,
start = intervalDuration(playlist, 0, playlist.mediaSequence);
end = start + intervalDuration(playlist,
playlist.mediaSequence,
playlist.mediaSequence + playlist.segments.length);
targetDuration = playlist.targetDuration || DEFAULT_TARGET_DURATION;
......
......@@ -276,7 +276,11 @@ videojs.Hls.prototype.setupMetadataCueTranslation_ = function() {
}
}
tech.addCuesForMetadata_(textTrack, metadata);
// store this event for processing once the muxing has finished
tech.segmentBuffer_[0].pendingMetadata.push({
textTrack: textTrack,
metadata: metadata
});
});
// when seeking, clear out all cues ahead of the earliest position
......@@ -300,22 +304,27 @@ videojs.Hls.prototype.setupMetadataCueTranslation_ = function() {
});
};
videojs.Hls.prototype.addCuesForMetadata_ = function(textTrack, metadata) {
var i, cue, frame, minPts, segmentInfo, segmentOffset, time;
segmentInfo = this.segmentBuffer_[0];
videojs.Hls.prototype.addCuesForMetadata_ = function(segmentInfo) {
var i, cue, frame, metadata, minPts, segment, segmentOffset, textTrack, time;
segmentOffset = videojs.Hls.Playlist.duration(segmentInfo.playlist,
segmentInfo.playlist.mediaSequence,
segmentInfo.playlist.mediaSequence + segmentInfo.mediaIndex);
minPts = Math.min(this.segmentParser_.stats.minVideoPts(),
this.segmentParser_.stats.minAudioPts());
// create cue points for all the ID3 frames in this metadata event
for (i = 0; i < metadata.frames.length; i++) {
frame = metadata.frames[i];
time = segmentOffset + ((metadata.pts - minPts) * 0.001);
cue = new window.VTTCue(time, time, frame.value || frame.url || '');
cue.frame = frame;
textTrack.addCue(cue);
segment = segmentInfo.playlist.segments[segmentInfo.mediaIndex];
minPts = Math.min(segment.minVideoPts, segment.minAudioPts);
while (segmentInfo.pendingMetadata.length) {
metadata = segmentInfo.pendingMetadata[0].metadata;
textTrack = segmentInfo.pendingMetadata[0].textTrack;
// create cue points for all the ID3 frames in this metadata event
for (i = 0; i < metadata.frames.length; i++) {
frame = metadata.frames[i];
time = segmentOffset + ((metadata.pts - minPts) * 0.001);
cue = new window.VTTCue(time, time, frame.value || frame.url || '');
cue.frame = frame;
textTrack.addCue(cue);
}
segmentInfo.pendingMetadata.shift();
}
};
......@@ -787,7 +796,10 @@ videojs.Hls.prototype.loadSegment = function(segmentUri, offset) {
// when a key is defined for this segment, the encrypted bytes
encryptedBytes: null,
// optionally, the decrypter that is unencrypting the segment
decrypter: null
decrypter: null,
// metadata events discovered during muxing that need to be
// translated into cue points
pendingMetadata: []
};
if (segmentInfo.playlist.segments[segmentInfo.mediaIndex].key) {
segmentInfo.encryptedBytes = new Uint8Array(this.response);
......@@ -904,48 +916,9 @@ videojs.Hls.prototype.drainBuffer = function(event) {
tags.push(this.segmentParser_.getNextTag());
}
this.addCuesForMetadata_(segmentInfo);
this.updateDuration(this.playlists.media());
/*
Live In-Progress
0 s c m
. . . |~~~~~~~|--%-----^--|~~~~~%~~~~~|-----| . . .
p q AAJ
Live In-Progress 2
0 s c m
. . . |~~~~~~~~~~%~~|--^--|~~~~~%~~~~~|-----| . . .
q AAJ
0 400 450
. . . |-------X-----| . . .
Live Before Buffering
c
. . . |~~%~~~~~| . . .
??
p = earliest known pts
s = earliest playback position
q = earliest pts after the last discontinuity
c = current time
m = the latest buffered playback position
~ = only EXTINF available
- = PTS available
% = discontinuity
. = expired or unavailable
A = buffered in actionscript
J = buffered in javascript
Calculate current pts from current time
- subtract current time from buffered end to find out the interval between the latest buffered playback position and current time
- determine the current segment by subtracting segment durations from the latest buffered playback position
- determine current pts based on max segment pts
Determine the target segment by calculating the duration of intermediate segments
Add the difference between current time and the target time to find the target pts
Skip samples until the next sample is greater than or equal to the target pts
*/
// if we're refilling the buffer after a seek, scan through the muxed
// FLV tags until we find the one that is closest to the desired
// playback time
......
......@@ -22,5 +22,6 @@
],
"targetDuration": 10,
"endList": true,
"discontinuitySequence": 0
}
"discontinuitySequence": 0,
"discontinuityStarts": []
}
\ No newline at end of file
......
......@@ -142,5 +142,6 @@
],
"targetDuration": 10,
"endList": true,
"discontinuitySequence": 0
}
"discontinuitySequence": 0,
"discontinuityStarts": []
}
\ No newline at end of file
......
......@@ -14,5 +14,6 @@
],
"targetDuration": 10,
"endList": true,
"discontinuitySequence": 0
}
"discontinuitySequence": 0,
"discontinuityStarts": []
}
\ No newline at end of file
......
......@@ -41,5 +41,6 @@
},
"uri": "http://c.brightcove.com/services/mobile/streaming/index/rendition.m3u8?assetId=1824687660001&videoId=1824650741001"
}
]
}
],
"discontinuityStarts": []
}
\ No newline at end of file
......
......@@ -138,5 +138,6 @@
],
"targetDuration": 10,
"endList": true,
"discontinuitySequence": 0
}
"discontinuitySequence": 0,
"discontinuityStarts": []
}
\ No newline at end of file
......
......@@ -14,5 +14,6 @@
],
"targetDuration": 10,
"endList": true,
"discontinuitySequence": 0
}
"discontinuitySequence": 0,
"discontinuityStarts": []
}
\ No newline at end of file
......
......@@ -22,5 +22,6 @@
}
],
"targetDuration": 19,
"endList": true
"endList": true,
"discontinuityStarts": [2]
}
......
......@@ -44,5 +44,6 @@
}
],
"targetDuration": 19,
"endList": true
"endList": true,
"discontinuityStarts": [2, 4, 7]
}
......
......@@ -22,5 +22,6 @@
],
"targetDuration": 10,
"endList": true,
"discontinuitySequence": 0
}
"discontinuitySequence": 0,
"discontinuityStarts": []
}
\ No newline at end of file
......
......@@ -14,5 +14,6 @@
],
"targetDuration": 10,
"endList": true,
"discontinuitySequence": 0
}
"discontinuitySequence": 0,
"discontinuityStarts": []
}
\ No newline at end of file
......
......@@ -22,5 +22,6 @@
],
"targetDuration": 8,
"endList": true,
"discontinuitySequence": 0
}
"discontinuitySequence": 0,
"discontinuityStarts": []
}
\ No newline at end of file
......
......@@ -29,5 +29,6 @@
],
"targetDuration": 10,
"endList": true,
"discontinuitySequence": 0
}
"discontinuitySequence": 0,
"discontinuityStarts": []
}
\ No newline at end of file
......
......@@ -41,5 +41,6 @@
},
"uri": "http://c.brightcove.com/services/mobile/streaming/index/rendition.m3u8?assetId=1824687660001&videoId=1824650741001"
}
]
}
],
"discontinuityStarts": []
}
\ No newline at end of file
......
......@@ -2,6 +2,7 @@
"allowCache": true,
"mediaSequence": 7794,
"discontinuitySequence": 0,
"discontinuityStarts": [],
"segments": [
{
"duration": 2.833,
......
......@@ -30,5 +30,6 @@
],
"targetDuration": 10,
"endList": true,
"discontinuitySequence": 0
}
"discontinuitySequence": 0,
"discontinuityStarts": []
}
\ No newline at end of file
......
......@@ -9,5 +9,6 @@
],
"targetDuration": 8,
"endList": true,
"discontinuitySequence": 0
}
"discontinuitySequence": 0,
"discontinuityStarts": []
}
\ No newline at end of file
......
......@@ -142,5 +142,6 @@
],
"targetDuration": 10,
"endList": true,
"discontinuitySequence": 0
}
"discontinuitySequence": 0,
"discontinuityStarts": []
}
\ No newline at end of file
......
......@@ -14,5 +14,6 @@
],
"targetDuration": 10,
"endList": true,
"discontinuitySequence": 0
}
"discontinuitySequence": 0,
"discontinuityStarts": []
}
\ No newline at end of file
......
......@@ -22,5 +22,6 @@
],
"targetDuration": 8,
"endList": true,
"discontinuitySequence": 0
}
"discontinuitySequence": 0,
"discontinuityStarts": []
}
\ No newline at end of file
......
......@@ -29,5 +29,6 @@
],
"targetDuration": 10,
"endList": true,
"discontinuitySequence": 0
}
"discontinuitySequence": 0,
"discontinuityStarts": []
}
\ No newline at end of file
......
......@@ -141,5 +141,6 @@
}
],
"endList": true,
"discontinuitySequence": 0
}
"discontinuitySequence": 0,
"discontinuityStarts": []
}
\ No newline at end of file
......
......@@ -17,5 +17,6 @@
}
],
"targetDuration": 8,
"discontinuitySequence": 0
}
"discontinuitySequence": 0,
"discontinuityStarts": []
}
\ No newline at end of file
......
......@@ -40,5 +40,6 @@
}
],
"targetDuration": 10,
"discontinuitySequence": 0
}
"discontinuitySequence": 0,
"discontinuityStarts": []
}
\ No newline at end of file
......
......@@ -8,5 +8,6 @@
}
],
"endList": true,
"discontinuitySequence": 0
}
"discontinuitySequence": 0,
"discontinuityStarts": []
}
\ No newline at end of file
......
......@@ -25,5 +25,6 @@
],
"targetDuration": 10,
"endList": true,
"discontinuitySequence": 0
}
"discontinuitySequence": 0,
"discontinuityStarts": []
}
\ No newline at end of file
......
......@@ -9,5 +9,6 @@
],
"targetDuration": 10,
"endList": true,
"discontinuitySequence": 0
}
"discontinuitySequence": 0,
"discontinuityStarts": []
}
\ No newline at end of file
......
......@@ -41,5 +41,6 @@
},
"uri": "media3.m3u8"
}
]
}
],
"discontinuityStarts": []
}
\ No newline at end of file
......
......@@ -22,5 +22,6 @@
],
"targetDuration": 10,
"endList": true,
"discontinuitySequence": 0
}
"discontinuitySequence": 0,
"discontinuityStarts": []
}
\ No newline at end of file
......
......@@ -22,5 +22,6 @@
],
"targetDuration": 8,
"endList": true,
"discontinuitySequence": 0
}
"discontinuitySequence": 0,
"discontinuityStarts": []
}
\ No newline at end of file
......
......@@ -12,5 +12,6 @@
}
],
"targetDuration": 10,
"discontinuitySequence": 0
}
"discontinuitySequence": 0,
"discontinuityStarts": []
}
\ No newline at end of file
......
......@@ -18,5 +18,6 @@
],
"targetDuration": 10,
"endList": true,
"discontinuitySequence": 0
}
"discontinuitySequence": 0,
"discontinuityStarts": []
}
\ No newline at end of file
......
......@@ -22,5 +22,6 @@
],
"targetDuration": 8,
"endList": true,
"discontinuitySequence": 0
}
"discontinuitySequence": 0,
"discontinuityStarts": []
}
\ No newline at end of file
......
......@@ -22,5 +22,6 @@
],
"targetDuration": 8,
"endList": true,
"discontinuitySequence": 0
}
"discontinuitySequence": 0,
"discontinuityStarts": []
}
\ No newline at end of file
......
......@@ -19,5 +19,6 @@
"duration": 10
}
],
"discontinuitySequence": 0
}
"discontinuitySequence": 0,
"discontinuityStarts": []
}
\ No newline at end of file
......
......@@ -22,5 +22,6 @@
],
"targetDuration": 8,
"endList": true,
"discontinuitySequence": 0
}
"discontinuitySequence": 0,
"discontinuityStarts": []
}
\ No newline at end of file
......
......@@ -142,5 +142,6 @@
],
"targetDuration": 10,
"endList": true,
"discontinuitySequence": 0
}
"discontinuitySequence": 0,
"discontinuityStarts": []
}
\ No newline at end of file
......
......@@ -10,5 +10,6 @@
],
"targetDuration": 8,
"endList": true,
"discontinuitySequence": 0
}
"discontinuitySequence": 0,
"discontinuityStarts": []
}
\ No newline at end of file
......
......@@ -10,5 +10,6 @@
{
"uri": "media1.m3u8"
}
]
}
],
"discontinuityStarts": []
}
\ No newline at end of file
......
......@@ -22,5 +22,6 @@
],
"targetDuration": 8,
"endList": true,
"discontinuitySequence": 0
}
"discontinuitySequence": 0,
"discontinuityStarts": []
}
\ No newline at end of file
......
......@@ -10,5 +10,6 @@
],
"targetDuration": 10,
"endList": true,
"discontinuitySequence": 0
}
"discontinuitySequence": 0,
"discontinuityStarts": []
}
\ No newline at end of file
......
......@@ -22,5 +22,6 @@
],
"targetDuration": 10,
"endList": true,
"discontinuitySequence": 0
}
"discontinuitySequence": 0,
"discontinuityStarts": []
}
\ No newline at end of file
......
......@@ -70,17 +70,17 @@
});
test('works when partial PTS information is available', function() {
var firstInterval, secondInterval, duration = Playlist.duration({
var duration = Playlist.duration({
mediaSequence: 0,
endList: true,
segments: [{
minVideoPts: 1,
minAudioPts: 2,
maxVideoPts: 1 * 10 * 1000 + 1,
maxVideoPts: 10 * 1000 + 1,
// intentionally less duration than video
// the max stream duration should be used
maxAudioPts: 1 * 10 * 1000 + 1,
maxAudioPts: 10 * 1000 + 1,
uri: '0.ts'
}, {
duration: 9,
......@@ -90,29 +90,96 @@
uri: '2.ts'
}, {
duration: 10,
minVideoPts: 2 * 10 * 1000 + 7,
minAudioPts: 2 * 10 * 1000 + 10,
maxVideoPts: 3 * 10 * 1000 + 1,
maxAudioPts: 3 * 10 * 1000 + 2,
minVideoPts: 30 * 1000 + 7,
minAudioPts: 30 * 1000 + 10,
maxVideoPts: 40 * 1000 + 1,
maxAudioPts: 40 * 1000 + 2,
uri: '3.ts'
}, {
duration: 10,
maxVideoPts: 4 * 10 * 1000 + 1,
maxAudioPts: 4 * 10 * 1000 + 2,
maxVideoPts: 50 * 1000 + 1,
maxAudioPts: 50 * 1000 + 2,
uri: '4.ts'
}]
}, 0, 5);
firstInterval = (1 * 10 * 1000 + 1) - 1;
firstInterval *= 0.001;
secondInterval = (4 * 10 * 1000 + 2) - (2 * 10 * 1000 + 7);
secondInterval *= 0.001;
equal(duration,
firstInterval + 9 + 10 + secondInterval,
((50 * 1000 + 2) - 1) * 0.001,
'calculated with mixed intervals');
});
test('ignores segments before the start', function() {
var duration = Playlist.duration({
mediaSequence: 0,
segments: [{
duration: 10,
uri: '0.ts'
}, {
duration: 10,
uri: '1.ts'
}, {
duration: 10,
uri: '2.ts'
}]
}, 1, 3);
equal(duration, 10 + 10, 'ignored the first segment');
});
test('ignores discontinuity sequences earlier than the start', function() {
var duration = Playlist.duration({
mediaSequence: 0,
discontinuityStarts: [1, 3],
segments: [{
minVideoPts: 0,
minAudioPts: 0,
maxVideoPts: 10 * 1000,
maxAudioPts: 10 * 1000,
uri: '0.ts'
}, {
discontinuity: true,
duration: 9,
uri: '1.ts'
}, {
duration: 10,
uri: '2.ts'
}, {
discontinuity: true,
duration: 10,
uri: '3.ts'
}]
}, 2, 4);
equal(duration, 10 + 10, 'excluded the earlier segments');
});
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,
uri: '0.ts'
}, {
discontinuity: true,
duration: 9,
uri: '1.ts'
}, {
duration: 10,
uri: '2.ts'
}, {
discontinuity: true,
duration: 10,
uri: '3.ts'
}]
}, 0, 2);
equal(duration, 19, 'excluded the later segments');
});
test('handles trailing segments without PTS information', function() {
var duration = Playlist.duration({
mediaSequence: 0,
......@@ -130,15 +197,15 @@
duration: 10,
uri: '2.ts'
}, {
minVideoPts: 30 * 1000,
minAudioPts: 30 * 1000,
maxVideoPts: 40 * 1000,
maxAudioPts: 40 * 1000,
minVideoPts: 29.5 * 1000,
minAudioPts: 29.5 * 1000,
maxVideoPts: 39.5 * 1000,
maxAudioPts: 39.5 * 1000,
uri: '3.ts'
}]
}, 0, 3);
equal(duration, 10 + 9 + 10, 'calculated duration');
equal(duration, 29.5, 'calculated duration');
});
test('uses PTS intervals when the start and end segment have them', function() {
......@@ -171,7 +238,45 @@
equal(duration, 30.1, 'used the PTS-based interval');
});
test('counts the time between segments as part of the later segment duration', function() {
test('uses the largest continuous available PTS ranges', function() {
var playlist = {
mediaSequence: 0,
segments: [{
minVideoPts: 0,
minAudioPts: 0,
maxVideoPts: 10 * 1000,
maxAudioPts: 10 * 1000,
uri: '0.ts'
}, {
duration: 10,
uri: '1.ts'
}, {
// starts 0.5s earlier than the previous segment indicates
minVideoPts: 19.5 * 1000,
minAudioPts: 19.5 * 1000,
maxVideoPts: 29.5 * 1000,
maxAudioPts: 29.5 * 1000,
uri: '2.ts'
}, {
duration: 10,
uri: '3.ts'
}, {
// ... but by the last segment, there is actual 0.5s more
// content than duration indicates
minVideoPts: 40.5 * 1000,
minAudioPts: 40.5 * 1000,
maxVideoPts: 50.5 * 1000,
maxAudioPts: 50.5 * 1000,
uri: '4.ts'
}]
};
equal(Playlist.duration(playlist, 0, 5),
50.5,
'calculated across the larger PTS interval');
});
test('counts the time between segments as part of the earlier segment\'s duration', function() {
var duration = Playlist.duration({
mediaSequence: 0,
endList: true,
......@@ -198,6 +303,7 @@
var duration = Playlist.duration({
mediaSequence: 0,
endList: true,
discontinuityStarts: [1],
segments: [{
minVideoPts: 0,
minAudioPts: 0,
......@@ -221,6 +327,7 @@
test('does not count ending segment gaps across a discontinuity', function() {
var duration = Playlist.duration({
mediaSequence: 0,
discontinuityStarts: [1],
endList: true,
segments: [{
minVideoPts: 0,
......@@ -242,7 +349,7 @@
equal(duration, (1 * 10 * 1000) * 0.001, 'did not include the segment gap');
});
test('strict duration does not count ending segment gaps', function() {
test('trailing duration on the final segment can be excluded', function() {
var duration = Playlist.duration({
mediaSequence: 0,
endList: true,
......@@ -260,11 +367,31 @@
duration: 10,
uri: '1.ts'
}]
}, 0, 1, true);
}, 0, 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,
discontinuityStarts: [1],
segments: [{
duration: 10,
uri: '0.ts'
}, {
discontinuity: true,
duration: 10,
uri: '1.ts'
}]
};
equal(Playlist.duration(playlist, 0, 0), 0, 'zero-length duration is zero');
equal(Playlist.duration(playlist, 0, 0, false), 0, 'zero-length duration is zero');
equal(Playlist.duration(playlist, 0, -1), 0, 'negative length duration is zero');
equal(Playlist.duration(playlist, 2, 1, false), 0, 'negative length duration is zero');
});
module('Playlist Seekable');
test('calculates seekable time ranges from the available segments', function() {
......
......@@ -2710,12 +2710,13 @@ test('treats invalid keys as a key request failure', function() {
equal(bytes[0], 'flv', 'appended the flv header');
tags.length = 0;
tags.push({ pts: 1, bytes: new Uint8Array([1]) });
tags.push({ pts: 2833, bytes: new Uint8Array([1]) },
{ pts: 4833, bytes: new Uint8Array([2]) });
// second segment request
standardXHRResponse(requests.shift());
equal(bytes.length, 2, 'appended bytes');
deepEqual(new Uint8Array([1]), bytes[1], 'skipped to the second segment');
deepEqual(bytes[1], new Uint8Array([1, 2]), 'skipped to the second segment');
});
test('live stream should not call endOfStream', function(){
......