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 @@ ...@@ -375,7 +375,8 @@
375 375
376 // the manifest is empty until the parse stream begins delivering data 376 // the manifest is empty until the parse stream begins delivering data
377 this.manifest = { 377 this.manifest = {
378 allowCache: true 378 allowCache: true,
379 discontinuityStarts: []
379 }; 380 };
380 381
381 // update the manifest with the m3u8 entry from the parse stream 382 // update the manifest with the m3u8 entry from the parse stream
...@@ -513,6 +514,7 @@ ...@@ -513,6 +514,7 @@
513 }, 514 },
514 'discontinuity': function() { 515 'discontinuity': function() {
515 currentUri.discontinuity = true; 516 currentUri.discontinuity = true;
517 this.manifest.discontinuityStarts.push(uris.length);
516 }, 518 },
517 'targetduration': function() { 519 'targetduration': function() {
518 if (!isFinite(entry.duration) || entry.duration < 0) { 520 if (!isFinite(entry.duration) || entry.duration < 0) {
......
...@@ -432,7 +432,7 @@ ...@@ -432,7 +432,7 @@
432 time -= Playlist.duration(this.media_, 432 time -= Playlist.duration(this.media_,
433 this.media_.mediaSequence + i, 433 this.media_.mediaSequence + i,
434 this.media_.mediaSequence + i + 1, 434 this.media_.mediaSequence + i + 1,
435 true); 435 false);
436 436
437 // HLS version 3 and lower round segment durations to the 437 // HLS version 3 and lower round segment durations to the
438 // nearest decimal integer. When the correct media index is 438 // nearest decimal integer. When the correct media index is
......
...@@ -5,54 +5,126 @@ ...@@ -5,54 +5,126 @@
5 'use strict'; 5 'use strict';
6 6
7 var DEFAULT_TARGET_DURATION = 10; 7 var DEFAULT_TARGET_DURATION = 10;
8 var accumulateDuration, duration, seekable, segmentsDuration; 8 var accumulateDuration, ascendingNumeric, duration, intervalDuration, rangeDuration, seekable;
9 9
10 accumulateDuration = function(playlist, startSequence, endSequence, strict) { 10 // Array.sort comparator to sort numbers in ascending order
11 ascendingNumeric = function(left, right) {
12 return left - right;
13 };
14
15 /**
16 * Returns the media duration for the segments between a start and
17 * exclusive end index. The start and end parameters are interpreted
18 * as indices into the currently available segments. This method
19 * does not calculate durations for segments that have expired.
20 * @param playlist {object} a media playlist object
21 * @param start {number} an inclusive lower boundary for the
22 * segments to examine.
23 * @param end {number} an exclusive upper boundary for the segments
24 * to examine.
25 * @param includeTrailingTime {boolean} if false, the interval between
26 * the final segment and the subsequent segment will not be included
27 * in the result
28 * @return {number} the duration between the start index and end
29 * index in seconds.
30 */
31 accumulateDuration = function(playlist, start, end, includeTrailingTime) {
32 var
33 ranges = [],
34 rangeEnds = (playlist.discontinuityStarts || []).concat(end),
35 result = 0,
36 i;
37
38 // short circuit if start and end don't specify a non-empty range
39 // of segments
40 if (start >= end) {
41 return 0;
42 }
43
44 // create a range object for each discontinuity sequence
45 rangeEnds.sort(ascendingNumeric);
46 for (i = 0; i < rangeEnds.length; i++) {
47 if (rangeEnds[i] > start) {
48 ranges.push({ start: start, end: rangeEnds[i] });
49 i++;
50 break;
51 }
52 }
53 for (; i < rangeEnds.length; i++) {
54 // ignore times ranges later than end
55 if (rangeEnds[i] >= end) {
56 ranges.push({ start: rangeEnds[i - 1], end: end });
57 break;
58 }
59 ranges.push({ start: ranges[ranges.length - 1].end, end: rangeEnds[i] });
60 }
61
62 // add up the durations for each of the ranges
63 for (i = 0; i < ranges.length; i++) {
64 result += rangeDuration(playlist,
65 ranges[i],
66 i === ranges.length - 1 && includeTrailingTime);
67 }
68
69 return result;
70 };
71
72 /**
73 * Returns the duration of the specified range of segments. The
74 * range *must not* cross a discontinuity.
75 * @param playlist {object} a media playlist object
76 * @param range {object} an object that specifies a starting and
77 * ending index into the available segments.
78 * @param includeTrailingTime {boolean} if false, the interval between
79 * the final segment and the subsequent segment will not be included
80 * in the result
81 * @return {number} the duration of the range in seconds.
82 */
83 rangeDuration = function(playlist, range, includeTrailingTime) {
11 var 84 var
12 result = 0, 85 result = 0,
13 targetDuration = playlist.targetDuration || DEFAULT_TARGET_DURATION, 86 targetDuration = playlist.targetDuration || DEFAULT_TARGET_DURATION,
14 segment, endSegment, 87 segment,
15 i, j; 88 left, right;
16 89
17 // accumulate the segment durations into the result 90 // accumulate while searching for the earliest segment with
18 for (i = startSequence; i < endSequence; i++) { 91 // available PTS information
19 segment = playlist.segments[i - playlist.mediaSequence]; 92 for (left = range.start; left < range.end; left++) {
20 93 segment = playlist.segments[left];
21 // when PTS values aren't available, use information from the playlist 94 if (segment.minVideoPts !== undefined) {
22 if (segment.minVideoPts === undefined) { 95 break;
23 result += segment.duration ||
24 targetDuration;
25 continue;
26 } 96 }
97 result += segment.duration || targetDuration;
98 }
27 99
28 // find the last segment with PTS info and use that to calculate 100 // see if there's enough information to include the trailing time
29 // the interval duration 101 if (includeTrailingTime) {
30 for (j = i; j < endSequence - 1; j++) { 102 segment = playlist.segments[range.end];
31 endSegment = playlist.segments[j - playlist.mediaSequence + 1]; 103 if (segment && segment.minVideoPts !== undefined) {
32 if (endSegment.maxVideoPts === undefined || 104 result += 0.001 *
33 endSegment.discontinuity) { 105 (Math.min(segment.minVideoPts, segment.minAudioPts) -
34 break; 106 Math.min(playlist.segments[left].minVideoPts,
35 } 107 playlist.segments[left].minAudioPts));
108 return result;
36 } 109 }
37 endSegment = playlist.segments[j - playlist.mediaSequence]; 110 }
38 111
39 result += (Math.max(endSegment.maxVideoPts, endSegment.maxAudioPts) - 112 // do the same thing while finding the latest segment
40 Math.min(segment.minVideoPts, segment.minAudioPts)) * 0.001; 113 for (right = range.end - 1; right >= left; right--) {
41 i = j; 114 segment = playlist.segments[right];
115 if (segment.maxVideoPts !== undefined) {
116 break;
117 }
118 result += segment.duration || targetDuration;
42 } 119 }
43 120
44 // attribute the gap between the latest PTS value in end segment 121 // add in the PTS interval in seconds between them
45 // and the earlier PTS in the next one to the result 122 if (right >= left) {
46 segment = playlist.segments[endSequence - 1]; 123 result += 0.001 *
47 endSegment = playlist.segments[endSequence]; 124 (Math.max(playlist.segments[right].maxVideoPts,
48 if (!strict && 125 playlist.segments[right].maxAudioPts) -
49 endSegment && 126 Math.min(playlist.segments[left].minVideoPts,
50 !endSegment.discontinuity && 127 playlist.segments[left].minAudioPts));
51 endSegment.minVideoPts &&
52 segment &&
53 segment.maxVideoPts) {
54 result += (Math.min(endSegment.minVideoPts, endSegment.minAudioPts) -
55 Math.max(segment.maxVideoPts, segment.maxAudioPts)) * 0.001;
56 } 128 }
57 129
58 return result; 130 return result;
...@@ -68,14 +140,14 @@ ...@@ -68,14 +140,14 @@
68 * boundary for the playlist. Defaults to 0. 140 * boundary for the playlist. Defaults to 0.
69 * @param endSequence {number} (optional) an exclusive upper boundary 141 * @param endSequence {number} (optional) an exclusive upper boundary
70 * for the playlist. Defaults to playlist length. 142 * for the playlist. Defaults to playlist length.
71 * @param strict {boolean} (optional) if true, the interval between 143 * @param includeTrailingTime {boolean} if false, the interval between
72 * the final segment and the subsequent segment will not be included 144 * the final segment and the subsequent segment will not be included
73 * in the result 145 * in the result
74 * @return {number} the duration between the start index and end 146 * @return {number} the duration between the start index and end
75 * index. 147 * index.
76 */ 148 */
77 segmentsDuration = function(playlist, startSequence, endSequence, strict) { 149 intervalDuration = function(playlist, startSequence, endSequence, includeTrailingTime) {
78 var targetDuration, expiredSegmentCount, result = 0; 150 var result = 0, targetDuration, expiredSegmentCount;
79 151
80 startSequence = startSequence || 0; 152 startSequence = startSequence || 0;
81 endSequence = endSequence !== undefined ? endSequence : (playlist.segments || []).length; 153 endSequence = endSequence !== undefined ? endSequence : (playlist.segments || []).length;
...@@ -87,9 +159,9 @@ ...@@ -87,9 +159,9 @@
87 159
88 // accumulate the segment durations into the result 160 // accumulate the segment durations into the result
89 result += accumulateDuration(playlist, 161 result += accumulateDuration(playlist,
90 startSequence + expiredSegmentCount, 162 startSequence + expiredSegmentCount - playlist.mediaSequence,
91 endSequence, 163 endSequence - playlist.mediaSequence,
92 strict); 164 includeTrailingTime);
93 165
94 return result; 166 return result;
95 }; 167 };
...@@ -104,17 +176,21 @@ ...@@ -104,17 +176,21 @@
104 * boundary for the playlist. Defaults to 0. 176 * boundary for the playlist. Defaults to 0.
105 * @param endSequence {number} (optional) an exclusive upper boundary 177 * @param endSequence {number} (optional) an exclusive upper boundary
106 * for the playlist. Defaults to playlist length. 178 * for the playlist. Defaults to playlist length.
107 * @param strict {boolean} (optional) if true, the interval between 179 * @param includeTrailingTime {boolean} (optional) if false, the interval between
108 * the final segment and the subsequent segment will not be included 180 * the final segment and the subsequent segment will not be included
109 * in the result 181 * in the result
110 * @return {number} the duration between the start index and end 182 * @return {number} the duration between the start index and end
111 * index. 183 * index.
112 */ 184 */
113 duration = function(playlist, startSequence, endSequence, strict) { 185 duration = function(playlist, startSequence, endSequence, includeTrailingTime) {
114 if (!playlist) { 186 if (!playlist) {
115 return 0; 187 return 0;
116 } 188 }
117 189
190 if (includeTrailingTime === undefined) {
191 includeTrailingTime = true;
192 }
193
118 // if a slice of the total duration is not requested, use 194 // if a slice of the total duration is not requested, use
119 // playlist-level duration indicators when they're present 195 // playlist-level duration indicators when they're present
120 if (startSequence === undefined && endSequence === undefined) { 196 if (startSequence === undefined && endSequence === undefined) {
...@@ -130,10 +206,10 @@ ...@@ -130,10 +206,10 @@
130 } 206 }
131 207
132 // calculate the total duration based on the segment durations 208 // calculate the total duration based on the segment durations
133 return segmentsDuration(playlist, 209 return intervalDuration(playlist,
134 startSequence, 210 startSequence,
135 endSequence, 211 endSequence,
136 strict); 212 includeTrailingTime);
137 }; 213 };
138 214
139 /** 215 /**
...@@ -155,8 +231,8 @@ ...@@ -155,8 +231,8 @@
155 return videojs.createTimeRange(0, duration(playlist)); 231 return videojs.createTimeRange(0, duration(playlist));
156 } 232 }
157 233
158 start = segmentsDuration(playlist, 0, playlist.mediaSequence); 234 start = intervalDuration(playlist, 0, playlist.mediaSequence);
159 end = start + segmentsDuration(playlist, 235 end = start + intervalDuration(playlist,
160 playlist.mediaSequence, 236 playlist.mediaSequence,
161 playlist.mediaSequence + playlist.segments.length); 237 playlist.mediaSequence + playlist.segments.length);
162 targetDuration = playlist.targetDuration || DEFAULT_TARGET_DURATION; 238 targetDuration = playlist.targetDuration || DEFAULT_TARGET_DURATION;
......
...@@ -276,7 +276,11 @@ videojs.Hls.prototype.setupMetadataCueTranslation_ = function() { ...@@ -276,7 +276,11 @@ videojs.Hls.prototype.setupMetadataCueTranslation_ = function() {
276 } 276 }
277 } 277 }
278 278
279 tech.addCuesForMetadata_(textTrack, metadata); 279 // store this event for processing once the muxing has finished
280 tech.segmentBuffer_[0].pendingMetadata.push({
281 textTrack: textTrack,
282 metadata: metadata
283 });
280 }); 284 });
281 285
282 // when seeking, clear out all cues ahead of the earliest position 286 // when seeking, clear out all cues ahead of the earliest position
...@@ -300,22 +304,27 @@ videojs.Hls.prototype.setupMetadataCueTranslation_ = function() { ...@@ -300,22 +304,27 @@ videojs.Hls.prototype.setupMetadataCueTranslation_ = function() {
300 }); 304 });
301 }; 305 };
302 306
303 videojs.Hls.prototype.addCuesForMetadata_ = function(textTrack, metadata) { 307 videojs.Hls.prototype.addCuesForMetadata_ = function(segmentInfo) {
304 var i, cue, frame, minPts, segmentInfo, segmentOffset, time; 308 var i, cue, frame, metadata, minPts, segment, segmentOffset, textTrack, time;
305 segmentInfo = this.segmentBuffer_[0];
306 segmentOffset = videojs.Hls.Playlist.duration(segmentInfo.playlist, 309 segmentOffset = videojs.Hls.Playlist.duration(segmentInfo.playlist,
307 segmentInfo.playlist.mediaSequence, 310 segmentInfo.playlist.mediaSequence,
308 segmentInfo.playlist.mediaSequence + segmentInfo.mediaIndex); 311 segmentInfo.playlist.mediaSequence + segmentInfo.mediaIndex);
309 minPts = Math.min(this.segmentParser_.stats.minVideoPts(), 312 segment = segmentInfo.playlist.segments[segmentInfo.mediaIndex];
310 this.segmentParser_.stats.minAudioPts()); 313 minPts = Math.min(segment.minVideoPts, segment.minAudioPts);
311 314
312 // create cue points for all the ID3 frames in this metadata event 315 while (segmentInfo.pendingMetadata.length) {
313 for (i = 0; i < metadata.frames.length; i++) { 316 metadata = segmentInfo.pendingMetadata[0].metadata;
314 frame = metadata.frames[i]; 317 textTrack = segmentInfo.pendingMetadata[0].textTrack;
315 time = segmentOffset + ((metadata.pts - minPts) * 0.001); 318
316 cue = new window.VTTCue(time, time, frame.value || frame.url || ''); 319 // create cue points for all the ID3 frames in this metadata event
317 cue.frame = frame; 320 for (i = 0; i < metadata.frames.length; i++) {
318 textTrack.addCue(cue); 321 frame = metadata.frames[i];
322 time = segmentOffset + ((metadata.pts - minPts) * 0.001);
323 cue = new window.VTTCue(time, time, frame.value || frame.url || '');
324 cue.frame = frame;
325 textTrack.addCue(cue);
326 }
327 segmentInfo.pendingMetadata.shift();
319 } 328 }
320 }; 329 };
321 330
...@@ -787,7 +796,10 @@ videojs.Hls.prototype.loadSegment = function(segmentUri, offset) { ...@@ -787,7 +796,10 @@ videojs.Hls.prototype.loadSegment = function(segmentUri, offset) {
787 // when a key is defined for this segment, the encrypted bytes 796 // when a key is defined for this segment, the encrypted bytes
788 encryptedBytes: null, 797 encryptedBytes: null,
789 // optionally, the decrypter that is unencrypting the segment 798 // optionally, the decrypter that is unencrypting the segment
790 decrypter: null 799 decrypter: null,
800 // metadata events discovered during muxing that need to be
801 // translated into cue points
802 pendingMetadata: []
791 }; 803 };
792 if (segmentInfo.playlist.segments[segmentInfo.mediaIndex].key) { 804 if (segmentInfo.playlist.segments[segmentInfo.mediaIndex].key) {
793 segmentInfo.encryptedBytes = new Uint8Array(this.response); 805 segmentInfo.encryptedBytes = new Uint8Array(this.response);
...@@ -904,48 +916,9 @@ videojs.Hls.prototype.drainBuffer = function(event) { ...@@ -904,48 +916,9 @@ videojs.Hls.prototype.drainBuffer = function(event) {
904 tags.push(this.segmentParser_.getNextTag()); 916 tags.push(this.segmentParser_.getNextTag());
905 } 917 }
906 918
919 this.addCuesForMetadata_(segmentInfo);
907 this.updateDuration(this.playlists.media()); 920 this.updateDuration(this.playlists.media());
908 921
909 /*
910 Live In-Progress
911 0 s c m
912 . . . |~~~~~~~|--%-----^--|~~~~~%~~~~~|-----| . . .
913 p q AAJ
914
915 Live In-Progress 2
916 0 s c m
917 . . . |~~~~~~~~~~%~~|--^--|~~~~~%~~~~~|-----| . . .
918 q AAJ
919
920 0 400 450
921 . . . |-------X-----| . . .
922
923 Live Before Buffering
924 c
925 . . . |~~%~~~~~| . . .
926 ??
927
928 p = earliest known pts
929 s = earliest playback position
930 q = earliest pts after the last discontinuity
931 c = current time
932 m = the latest buffered playback position
933 ~ = only EXTINF available
934 - = PTS available
935 % = discontinuity
936 . = expired or unavailable
937 A = buffered in actionscript
938 J = buffered in javascript
939
940 Calculate current pts from current time
941 - subtract current time from buffered end to find out the interval between the latest buffered playback position and current time
942 - determine the current segment by subtracting segment durations from the latest buffered playback position
943 - determine current pts based on max segment pts
944 Determine the target segment by calculating the duration of intermediate segments
945 Add the difference between current time and the target time to find the target pts
946 Skip samples until the next sample is greater than or equal to the target pts
947 */
948
949 // if we're refilling the buffer after a seek, scan through the muxed 922 // if we're refilling the buffer after a seek, scan through the muxed
950 // FLV tags until we find the one that is closest to the desired 923 // FLV tags until we find the one that is closest to the desired
951 // playback time 924 // playback time
......
...@@ -22,5 +22,6 @@ ...@@ -22,5 +22,6 @@
22 ], 22 ],
23 "targetDuration": 10, 23 "targetDuration": 10,
24 "endList": true, 24 "endList": true,
25 "discontinuitySequence": 0 25 "discontinuitySequence": 0,
26 } 26 "discontinuityStarts": []
27 }
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -142,5 +142,6 @@ ...@@ -142,5 +142,6 @@
142 ], 142 ],
143 "targetDuration": 10, 143 "targetDuration": 10,
144 "endList": true, 144 "endList": true,
145 "discontinuitySequence": 0 145 "discontinuitySequence": 0,
146 } 146 "discontinuityStarts": []
147 }
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -14,5 +14,6 @@ ...@@ -14,5 +14,6 @@
14 ], 14 ],
15 "targetDuration": 10, 15 "targetDuration": 10,
16 "endList": true, 16 "endList": true,
17 "discontinuitySequence": 0 17 "discontinuitySequence": 0,
18 } 18 "discontinuityStarts": []
19 }
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -41,5 +41,6 @@ ...@@ -41,5 +41,6 @@
41 }, 41 },
42 "uri": "http://c.brightcove.com/services/mobile/streaming/index/rendition.m3u8?assetId=1824687660001&videoId=1824650741001" 42 "uri": "http://c.brightcove.com/services/mobile/streaming/index/rendition.m3u8?assetId=1824687660001&videoId=1824650741001"
43 } 43 }
44 ] 44 ],
45 } 45 "discontinuityStarts": []
46 }
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -138,5 +138,6 @@ ...@@ -138,5 +138,6 @@
138 ], 138 ],
139 "targetDuration": 10, 139 "targetDuration": 10,
140 "endList": true, 140 "endList": true,
141 "discontinuitySequence": 0 141 "discontinuitySequence": 0,
142 } 142 "discontinuityStarts": []
143 }
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -14,5 +14,6 @@ ...@@ -14,5 +14,6 @@
14 ], 14 ],
15 "targetDuration": 10, 15 "targetDuration": 10,
16 "endList": true, 16 "endList": true,
17 "discontinuitySequence": 0 17 "discontinuitySequence": 0,
18 } 18 "discontinuityStarts": []
19 }
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -22,5 +22,6 @@ ...@@ -22,5 +22,6 @@
22 } 22 }
23 ], 23 ],
24 "targetDuration": 19, 24 "targetDuration": 19,
25 "endList": true 25 "endList": true,
26 "discontinuityStarts": [2]
26 } 27 }
......
...@@ -44,5 +44,6 @@ ...@@ -44,5 +44,6 @@
44 } 44 }
45 ], 45 ],
46 "targetDuration": 19, 46 "targetDuration": 19,
47 "endList": true 47 "endList": true,
48 "discontinuityStarts": [2, 4, 7]
48 } 49 }
......
...@@ -22,5 +22,6 @@ ...@@ -22,5 +22,6 @@
22 ], 22 ],
23 "targetDuration": 10, 23 "targetDuration": 10,
24 "endList": true, 24 "endList": true,
25 "discontinuitySequence": 0 25 "discontinuitySequence": 0,
26 } 26 "discontinuityStarts": []
27 }
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -14,5 +14,6 @@ ...@@ -14,5 +14,6 @@
14 ], 14 ],
15 "targetDuration": 10, 15 "targetDuration": 10,
16 "endList": true, 16 "endList": true,
17 "discontinuitySequence": 0 17 "discontinuitySequence": 0,
18 } 18 "discontinuityStarts": []
19 }
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -22,5 +22,6 @@ ...@@ -22,5 +22,6 @@
22 ], 22 ],
23 "targetDuration": 8, 23 "targetDuration": 8,
24 "endList": true, 24 "endList": true,
25 "discontinuitySequence": 0 25 "discontinuitySequence": 0,
26 } 26 "discontinuityStarts": []
27 }
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -29,5 +29,6 @@ ...@@ -29,5 +29,6 @@
29 ], 29 ],
30 "targetDuration": 10, 30 "targetDuration": 10,
31 "endList": true, 31 "endList": true,
32 "discontinuitySequence": 0 32 "discontinuitySequence": 0,
33 } 33 "discontinuityStarts": []
34 }
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -41,5 +41,6 @@ ...@@ -41,5 +41,6 @@
41 }, 41 },
42 "uri": "http://c.brightcove.com/services/mobile/streaming/index/rendition.m3u8?assetId=1824687660001&videoId=1824650741001" 42 "uri": "http://c.brightcove.com/services/mobile/streaming/index/rendition.m3u8?assetId=1824687660001&videoId=1824650741001"
43 } 43 }
44 ] 44 ],
45 } 45 "discontinuityStarts": []
46 }
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
2 "allowCache": true, 2 "allowCache": true,
3 "mediaSequence": 7794, 3 "mediaSequence": 7794,
4 "discontinuitySequence": 0, 4 "discontinuitySequence": 0,
5 "discontinuityStarts": [],
5 "segments": [ 6 "segments": [
6 { 7 {
7 "duration": 2.833, 8 "duration": 2.833,
......
...@@ -30,5 +30,6 @@ ...@@ -30,5 +30,6 @@
30 ], 30 ],
31 "targetDuration": 10, 31 "targetDuration": 10,
32 "endList": true, 32 "endList": true,
33 "discontinuitySequence": 0 33 "discontinuitySequence": 0,
34 } 34 "discontinuityStarts": []
35 }
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -9,5 +9,6 @@ ...@@ -9,5 +9,6 @@
9 ], 9 ],
10 "targetDuration": 8, 10 "targetDuration": 8,
11 "endList": true, 11 "endList": true,
12 "discontinuitySequence": 0 12 "discontinuitySequence": 0,
13 } 13 "discontinuityStarts": []
14 }
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -142,5 +142,6 @@ ...@@ -142,5 +142,6 @@
142 ], 142 ],
143 "targetDuration": 10, 143 "targetDuration": 10,
144 "endList": true, 144 "endList": true,
145 "discontinuitySequence": 0 145 "discontinuitySequence": 0,
146 } 146 "discontinuityStarts": []
147 }
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -14,5 +14,6 @@ ...@@ -14,5 +14,6 @@
14 ], 14 ],
15 "targetDuration": 10, 15 "targetDuration": 10,
16 "endList": true, 16 "endList": true,
17 "discontinuitySequence": 0 17 "discontinuitySequence": 0,
18 } 18 "discontinuityStarts": []
19 }
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -22,5 +22,6 @@ ...@@ -22,5 +22,6 @@
22 ], 22 ],
23 "targetDuration": 8, 23 "targetDuration": 8,
24 "endList": true, 24 "endList": true,
25 "discontinuitySequence": 0 25 "discontinuitySequence": 0,
26 } 26 "discontinuityStarts": []
27 }
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -29,5 +29,6 @@ ...@@ -29,5 +29,6 @@
29 ], 29 ],
30 "targetDuration": 10, 30 "targetDuration": 10,
31 "endList": true, 31 "endList": true,
32 "discontinuitySequence": 0 32 "discontinuitySequence": 0,
33 } 33 "discontinuityStarts": []
34 }
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -141,5 +141,6 @@ ...@@ -141,5 +141,6 @@
141 } 141 }
142 ], 142 ],
143 "endList": true, 143 "endList": true,
144 "discontinuitySequence": 0 144 "discontinuitySequence": 0,
145 } 145 "discontinuityStarts": []
146 }
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -17,5 +17,6 @@ ...@@ -17,5 +17,6 @@
17 } 17 }
18 ], 18 ],
19 "targetDuration": 8, 19 "targetDuration": 8,
20 "discontinuitySequence": 0 20 "discontinuitySequence": 0,
21 } 21 "discontinuityStarts": []
22 }
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -40,5 +40,6 @@ ...@@ -40,5 +40,6 @@
40 } 40 }
41 ], 41 ],
42 "targetDuration": 10, 42 "targetDuration": 10,
43 "discontinuitySequence": 0 43 "discontinuitySequence": 0,
44 } 44 "discontinuityStarts": []
45 }
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -8,5 +8,6 @@ ...@@ -8,5 +8,6 @@
8 } 8 }
9 ], 9 ],
10 "endList": true, 10 "endList": true,
11 "discontinuitySequence": 0 11 "discontinuitySequence": 0,
12 } 12 "discontinuityStarts": []
13 }
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -25,5 +25,6 @@ ...@@ -25,5 +25,6 @@
25 ], 25 ],
26 "targetDuration": 10, 26 "targetDuration": 10,
27 "endList": true, 27 "endList": true,
28 "discontinuitySequence": 0 28 "discontinuitySequence": 0,
29 } 29 "discontinuityStarts": []
30 }
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -9,5 +9,6 @@ ...@@ -9,5 +9,6 @@
9 ], 9 ],
10 "targetDuration": 10, 10 "targetDuration": 10,
11 "endList": true, 11 "endList": true,
12 "discontinuitySequence": 0 12 "discontinuitySequence": 0,
13 } 13 "discontinuityStarts": []
14 }
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -41,5 +41,6 @@ ...@@ -41,5 +41,6 @@
41 }, 41 },
42 "uri": "media3.m3u8" 42 "uri": "media3.m3u8"
43 } 43 }
44 ] 44 ],
45 } 45 "discontinuityStarts": []
46 }
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -22,5 +22,6 @@ ...@@ -22,5 +22,6 @@
22 ], 22 ],
23 "targetDuration": 10, 23 "targetDuration": 10,
24 "endList": true, 24 "endList": true,
25 "discontinuitySequence": 0 25 "discontinuitySequence": 0,
26 } 26 "discontinuityStarts": []
27 }
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -22,5 +22,6 @@ ...@@ -22,5 +22,6 @@
22 ], 22 ],
23 "targetDuration": 8, 23 "targetDuration": 8,
24 "endList": true, 24 "endList": true,
25 "discontinuitySequence": 0 25 "discontinuitySequence": 0,
26 } 26 "discontinuityStarts": []
27 }
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -12,5 +12,6 @@ ...@@ -12,5 +12,6 @@
12 } 12 }
13 ], 13 ],
14 "targetDuration": 10, 14 "targetDuration": 10,
15 "discontinuitySequence": 0 15 "discontinuitySequence": 0,
16 } 16 "discontinuityStarts": []
17 }
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -18,5 +18,6 @@ ...@@ -18,5 +18,6 @@
18 ], 18 ],
19 "targetDuration": 10, 19 "targetDuration": 10,
20 "endList": true, 20 "endList": true,
21 "discontinuitySequence": 0 21 "discontinuitySequence": 0,
22 } 22 "discontinuityStarts": []
23 }
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -22,5 +22,6 @@ ...@@ -22,5 +22,6 @@
22 ], 22 ],
23 "targetDuration": 8, 23 "targetDuration": 8,
24 "endList": true, 24 "endList": true,
25 "discontinuitySequence": 0 25 "discontinuitySequence": 0,
26 } 26 "discontinuityStarts": []
27 }
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -22,5 +22,6 @@ ...@@ -22,5 +22,6 @@
22 ], 22 ],
23 "targetDuration": 8, 23 "targetDuration": 8,
24 "endList": true, 24 "endList": true,
25 "discontinuitySequence": 0 25 "discontinuitySequence": 0,
26 } 26 "discontinuityStarts": []
27 }
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -19,5 +19,6 @@ ...@@ -19,5 +19,6 @@
19 "duration": 10 19 "duration": 10
20 } 20 }
21 ], 21 ],
22 "discontinuitySequence": 0 22 "discontinuitySequence": 0,
23 } 23 "discontinuityStarts": []
24 }
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -22,5 +22,6 @@ ...@@ -22,5 +22,6 @@
22 ], 22 ],
23 "targetDuration": 8, 23 "targetDuration": 8,
24 "endList": true, 24 "endList": true,
25 "discontinuitySequence": 0 25 "discontinuitySequence": 0,
26 } 26 "discontinuityStarts": []
27 }
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -142,5 +142,6 @@ ...@@ -142,5 +142,6 @@
142 ], 142 ],
143 "targetDuration": 10, 143 "targetDuration": 10,
144 "endList": true, 144 "endList": true,
145 "discontinuitySequence": 0 145 "discontinuitySequence": 0,
146 } 146 "discontinuityStarts": []
147 }
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -10,5 +10,6 @@ ...@@ -10,5 +10,6 @@
10 ], 10 ],
11 "targetDuration": 8, 11 "targetDuration": 8,
12 "endList": true, 12 "endList": true,
13 "discontinuitySequence": 0 13 "discontinuitySequence": 0,
14 } 14 "discontinuityStarts": []
15 }
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -10,5 +10,6 @@ ...@@ -10,5 +10,6 @@
10 { 10 {
11 "uri": "media1.m3u8" 11 "uri": "media1.m3u8"
12 } 12 }
13 ] 13 ],
14 } 14 "discontinuityStarts": []
15 }
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -22,5 +22,6 @@ ...@@ -22,5 +22,6 @@
22 ], 22 ],
23 "targetDuration": 8, 23 "targetDuration": 8,
24 "endList": true, 24 "endList": true,
25 "discontinuitySequence": 0 25 "discontinuitySequence": 0,
26 } 26 "discontinuityStarts": []
27 }
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -10,5 +10,6 @@ ...@@ -10,5 +10,6 @@
10 ], 10 ],
11 "targetDuration": 10, 11 "targetDuration": 10,
12 "endList": true, 12 "endList": true,
13 "discontinuitySequence": 0 13 "discontinuitySequence": 0,
14 } 14 "discontinuityStarts": []
15 }
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -22,5 +22,6 @@ ...@@ -22,5 +22,6 @@
22 ], 22 ],
23 "targetDuration": 10, 23 "targetDuration": 10,
24 "endList": true, 24 "endList": true,
25 "discontinuitySequence": 0 25 "discontinuitySequence": 0,
26 } 26 "discontinuityStarts": []
27 }
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -70,17 +70,17 @@ ...@@ -70,17 +70,17 @@
70 }); 70 });
71 71
72 test('works when partial PTS information is available', function() { 72 test('works when partial PTS information is available', function() {
73 var firstInterval, secondInterval, duration = Playlist.duration({ 73 var duration = Playlist.duration({
74 mediaSequence: 0, 74 mediaSequence: 0,
75 endList: true, 75 endList: true,
76 segments: [{ 76 segments: [{
77 minVideoPts: 1, 77 minVideoPts: 1,
78 minAudioPts: 2, 78 minAudioPts: 2,
79 maxVideoPts: 1 * 10 * 1000 + 1, 79 maxVideoPts: 10 * 1000 + 1,
80 80
81 // intentionally less duration than video 81 // intentionally less duration than video
82 // the max stream duration should be used 82 // the max stream duration should be used
83 maxAudioPts: 1 * 10 * 1000 + 1, 83 maxAudioPts: 10 * 1000 + 1,
84 uri: '0.ts' 84 uri: '0.ts'
85 }, { 85 }, {
86 duration: 9, 86 duration: 9,
...@@ -90,29 +90,96 @@ ...@@ -90,29 +90,96 @@
90 uri: '2.ts' 90 uri: '2.ts'
91 }, { 91 }, {
92 duration: 10, 92 duration: 10,
93 minVideoPts: 2 * 10 * 1000 + 7, 93 minVideoPts: 30 * 1000 + 7,
94 minAudioPts: 2 * 10 * 1000 + 10, 94 minAudioPts: 30 * 1000 + 10,
95 maxVideoPts: 3 * 10 * 1000 + 1, 95 maxVideoPts: 40 * 1000 + 1,
96 maxAudioPts: 3 * 10 * 1000 + 2, 96 maxAudioPts: 40 * 1000 + 2,
97 uri: '3.ts' 97 uri: '3.ts'
98 }, { 98 }, {
99 duration: 10, 99 duration: 10,
100 maxVideoPts: 4 * 10 * 1000 + 1, 100 maxVideoPts: 50 * 1000 + 1,
101 maxAudioPts: 4 * 10 * 1000 + 2, 101 maxAudioPts: 50 * 1000 + 2,
102 uri: '4.ts' 102 uri: '4.ts'
103 }] 103 }]
104 }, 0, 5); 104 }, 0, 5);
105 105
106 firstInterval = (1 * 10 * 1000 + 1) - 1;
107 firstInterval *= 0.001;
108 secondInterval = (4 * 10 * 1000 + 2) - (2 * 10 * 1000 + 7);
109 secondInterval *= 0.001;
110
111 equal(duration, 106 equal(duration,
112 firstInterval + 9 + 10 + secondInterval, 107 ((50 * 1000 + 2) - 1) * 0.001,
113 'calculated with mixed intervals'); 108 'calculated with mixed intervals');
114 }); 109 });
115 110
111 test('ignores segments before the start', function() {
112 var duration = Playlist.duration({
113 mediaSequence: 0,
114 segments: [{
115 duration: 10,
116 uri: '0.ts'
117 }, {
118 duration: 10,
119 uri: '1.ts'
120 }, {
121 duration: 10,
122 uri: '2.ts'
123 }]
124 }, 1, 3);
125
126 equal(duration, 10 + 10, 'ignored the first segment');
127 });
128
129 test('ignores discontinuity sequences earlier than the start', function() {
130 var duration = Playlist.duration({
131 mediaSequence: 0,
132 discontinuityStarts: [1, 3],
133 segments: [{
134 minVideoPts: 0,
135 minAudioPts: 0,
136 maxVideoPts: 10 * 1000,
137 maxAudioPts: 10 * 1000,
138 uri: '0.ts'
139 }, {
140 discontinuity: true,
141 duration: 9,
142 uri: '1.ts'
143 }, {
144 duration: 10,
145 uri: '2.ts'
146 }, {
147 discontinuity: true,
148 duration: 10,
149 uri: '3.ts'
150 }]
151 }, 2, 4);
152
153 equal(duration, 10 + 10, 'excluded the earlier segments');
154 });
155
156 test('ignores discontinuity sequences later than the end', function() {
157 var duration = Playlist.duration({
158 mediaSequence: 0,
159 discontinuityStarts: [1, 3],
160 segments: [{
161 minVideoPts: 0,
162 minAudioPts: 0,
163 maxVideoPts: 10 * 1000,
164 maxAudioPts: 10 * 1000,
165 uri: '0.ts'
166 }, {
167 discontinuity: true,
168 duration: 9,
169 uri: '1.ts'
170 }, {
171 duration: 10,
172 uri: '2.ts'
173 }, {
174 discontinuity: true,
175 duration: 10,
176 uri: '3.ts'
177 }]
178 }, 0, 2);
179
180 equal(duration, 19, 'excluded the later segments');
181 });
182
116 test('handles trailing segments without PTS information', function() { 183 test('handles trailing segments without PTS information', function() {
117 var duration = Playlist.duration({ 184 var duration = Playlist.duration({
118 mediaSequence: 0, 185 mediaSequence: 0,
...@@ -130,15 +197,15 @@ ...@@ -130,15 +197,15 @@
130 duration: 10, 197 duration: 10,
131 uri: '2.ts' 198 uri: '2.ts'
132 }, { 199 }, {
133 minVideoPts: 30 * 1000, 200 minVideoPts: 29.5 * 1000,
134 minAudioPts: 30 * 1000, 201 minAudioPts: 29.5 * 1000,
135 maxVideoPts: 40 * 1000, 202 maxVideoPts: 39.5 * 1000,
136 maxAudioPts: 40 * 1000, 203 maxAudioPts: 39.5 * 1000,
137 uri: '3.ts' 204 uri: '3.ts'
138 }] 205 }]
139 }, 0, 3); 206 }, 0, 3);
140 207
141 equal(duration, 10 + 9 + 10, 'calculated duration'); 208 equal(duration, 29.5, 'calculated duration');
142 }); 209 });
143 210
144 test('uses PTS intervals when the start and end segment have them', function() { 211 test('uses PTS intervals when the start and end segment have them', function() {
...@@ -171,7 +238,45 @@ ...@@ -171,7 +238,45 @@
171 equal(duration, 30.1, 'used the PTS-based interval'); 238 equal(duration, 30.1, 'used the PTS-based interval');
172 }); 239 });
173 240
174 test('counts the time between segments as part of the later segment duration', function() { 241 test('uses the largest continuous available PTS ranges', function() {
242 var playlist = {
243 mediaSequence: 0,
244 segments: [{
245 minVideoPts: 0,
246 minAudioPts: 0,
247 maxVideoPts: 10 * 1000,
248 maxAudioPts: 10 * 1000,
249 uri: '0.ts'
250 }, {
251 duration: 10,
252 uri: '1.ts'
253 }, {
254 // starts 0.5s earlier than the previous segment indicates
255 minVideoPts: 19.5 * 1000,
256 minAudioPts: 19.5 * 1000,
257 maxVideoPts: 29.5 * 1000,
258 maxAudioPts: 29.5 * 1000,
259 uri: '2.ts'
260 }, {
261 duration: 10,
262 uri: '3.ts'
263 }, {
264 // ... but by the last segment, there is actual 0.5s more
265 // content than duration indicates
266 minVideoPts: 40.5 * 1000,
267 minAudioPts: 40.5 * 1000,
268 maxVideoPts: 50.5 * 1000,
269 maxAudioPts: 50.5 * 1000,
270 uri: '4.ts'
271 }]
272 };
273
274 equal(Playlist.duration(playlist, 0, 5),
275 50.5,
276 'calculated across the larger PTS interval');
277 });
278
279 test('counts the time between segments as part of the earlier segment\'s duration', function() {
175 var duration = Playlist.duration({ 280 var duration = Playlist.duration({
176 mediaSequence: 0, 281 mediaSequence: 0,
177 endList: true, 282 endList: true,
...@@ -198,6 +303,7 @@ ...@@ -198,6 +303,7 @@
198 var duration = Playlist.duration({ 303 var duration = Playlist.duration({
199 mediaSequence: 0, 304 mediaSequence: 0,
200 endList: true, 305 endList: true,
306 discontinuityStarts: [1],
201 segments: [{ 307 segments: [{
202 minVideoPts: 0, 308 minVideoPts: 0,
203 minAudioPts: 0, 309 minAudioPts: 0,
...@@ -221,6 +327,7 @@ ...@@ -221,6 +327,7 @@
221 test('does not count ending segment gaps across a discontinuity', function() { 327 test('does not count ending segment gaps across a discontinuity', function() {
222 var duration = Playlist.duration({ 328 var duration = Playlist.duration({
223 mediaSequence: 0, 329 mediaSequence: 0,
330 discontinuityStarts: [1],
224 endList: true, 331 endList: true,
225 segments: [{ 332 segments: [{
226 minVideoPts: 0, 333 minVideoPts: 0,
...@@ -242,7 +349,7 @@ ...@@ -242,7 +349,7 @@
242 equal(duration, (1 * 10 * 1000) * 0.001, 'did not include the segment gap'); 349 equal(duration, (1 * 10 * 1000) * 0.001, 'did not include the segment gap');
243 }); 350 });
244 351
245 test('strict duration does not count ending segment gaps', function() { 352 test('trailing duration on the final segment can be excluded', function() {
246 var duration = Playlist.duration({ 353 var duration = Playlist.duration({
247 mediaSequence: 0, 354 mediaSequence: 0,
248 endList: true, 355 endList: true,
...@@ -260,11 +367,31 @@ ...@@ -260,11 +367,31 @@
260 duration: 10, 367 duration: 10,
261 uri: '1.ts' 368 uri: '1.ts'
262 }] 369 }]
263 }, 0, 1, true); 370 }, 0, 1, false);
264 371
265 equal(duration, (1 * 10 * 1000) * 0.001, 'did not include the segment gap'); 372 equal(duration, (1 * 10 * 1000) * 0.001, 'did not include the segment gap');
266 }); 373 });
267 374
375 test('a non-positive length interval has zero duration', function() {
376 var playlist = {
377 mediaSequence: 0,
378 discontinuityStarts: [1],
379 segments: [{
380 duration: 10,
381 uri: '0.ts'
382 }, {
383 discontinuity: true,
384 duration: 10,
385 uri: '1.ts'
386 }]
387 };
388
389 equal(Playlist.duration(playlist, 0, 0), 0, 'zero-length duration is zero');
390 equal(Playlist.duration(playlist, 0, 0, false), 0, 'zero-length duration is zero');
391 equal(Playlist.duration(playlist, 0, -1), 0, 'negative length duration is zero');
392 equal(Playlist.duration(playlist, 2, 1, false), 0, 'negative length duration is zero');
393 });
394
268 module('Playlist Seekable'); 395 module('Playlist Seekable');
269 396
270 test('calculates seekable time ranges from the available segments', function() { 397 test('calculates seekable time ranges from the available segments', function() {
......
...@@ -2710,12 +2710,13 @@ test('treats invalid keys as a key request failure', function() { ...@@ -2710,12 +2710,13 @@ test('treats invalid keys as a key request failure', function() {
2710 equal(bytes[0], 'flv', 'appended the flv header'); 2710 equal(bytes[0], 'flv', 'appended the flv header');
2711 2711
2712 tags.length = 0; 2712 tags.length = 0;
2713 tags.push({ pts: 1, bytes: new Uint8Array([1]) }); 2713 tags.push({ pts: 2833, bytes: new Uint8Array([1]) },
2714 { pts: 4833, bytes: new Uint8Array([2]) });
2714 // second segment request 2715 // second segment request
2715 standardXHRResponse(requests.shift()); 2716 standardXHRResponse(requests.shift());
2716 2717
2717 equal(bytes.length, 2, 'appended bytes'); 2718 equal(bytes.length, 2, 'appended bytes');
2718 deepEqual(new Uint8Array([1]), bytes[1], 'skipped to the second segment'); 2719 deepEqual(bytes[1], new Uint8Array([1, 2]), 'skipped to the second segment');
2719 }); 2720 });
2720 2721
2721 test('live stream should not call endOfStream', function(){ 2722 test('live stream should not call endOfStream', function(){
......