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.
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 | "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 | ... | ... |
... | @@ -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 | ... | ... |
... | @@ -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 | ... | ... |
... | @@ -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 | ... | ... |
... | @@ -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 | ... | ... |
... | @@ -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(){ | ... | ... |
-
Please register or sign in to post a comment