Merge pull request #445 from dmlap/seekable-start
Use information from buffering to calculate seekable
Showing
5 changed files
with
151 additions
and
112 deletions
... | @@ -390,7 +390,10 @@ | ... | @@ -390,7 +390,10 @@ |
390 | return 0; | 390 | return 0; |
391 | } | 391 | } |
392 | 392 | ||
393 | // 1) Walk backward until we find the first segment with timeline | 393 | // find segments with known timing information that bound the |
394 | // target time | ||
395 | |||
396 | // Walk backward until we find the first segment with timeline | ||
394 | // information that is earlier than `time` | 397 | // information that is earlier than `time` |
395 | for (i = lastSegment; i >= 0; i--) { | 398 | for (i = lastSegment; i >= 0; i--) { |
396 | segment = this.media_.segments[i]; | 399 | segment = this.media_.segments[i]; |
... | @@ -415,7 +418,7 @@ | ... | @@ -415,7 +418,7 @@ |
415 | } | 418 | } |
416 | } | 419 | } |
417 | 420 | ||
418 | // 2) Walk forward until we find the first segment with timeline | 421 | // Walk forward until we find the first segment with timeline |
419 | // information that is greater than `time` | 422 | // information that is greater than `time` |
420 | for (i = 0; i < numSegments; i++) { | 423 | for (i = 0; i < numSegments; i++) { |
421 | segment = this.media_.segments[i]; | 424 | segment = this.media_.segments[i]; |
... | @@ -424,7 +427,8 @@ | ... | @@ -424,7 +427,8 @@ |
424 | knownEnd = segment.start; | 427 | knownEnd = segment.start; |
425 | if (endIndex < 0) { | 428 | if (endIndex < 0) { |
426 | // The first segment claims to start *after* the time we are | 429 | // The first segment claims to start *after* the time we are |
427 | // searching for so just return it | 430 | // searching for so the target segment must no longer be |
431 | // available | ||
428 | return -1; | 432 | return -1; |
429 | } | 433 | } |
430 | break; | 434 | break; |
... | @@ -436,6 +440,9 @@ | ... | @@ -436,6 +440,9 @@ |
436 | } | 440 | } |
437 | } | 441 | } |
438 | 442 | ||
443 | // use the bounds we just found and playlist information to | ||
444 | // estimate the segment that contains the time we are looking for | ||
445 | |||
439 | if (startIndex !== undefined) { | 446 | if (startIndex !== undefined) { |
440 | // We have a known-start point that is before our desired time so | 447 | // We have a known-start point that is before our desired time so |
441 | // walk from that point forwards | 448 | // walk from that point forwards | ... | ... |
... | @@ -4,23 +4,70 @@ | ... | @@ -4,23 +4,70 @@ |
4 | (function(window, videojs) { | 4 | (function(window, videojs) { |
5 | 'use strict'; | 5 | 'use strict'; |
6 | 6 | ||
7 | var DEFAULT_TARGET_DURATION = 10; | 7 | var duration, intervalDuration, backwardDuration, forwardDuration, seekable; |
8 | var duration, intervalDuration, optionalMin, optionalMax, seekable; | 8 | |
9 | 9 | backwardDuration = function(playlist, endSequence) { | |
10 | // Math.min that will return the alternative input if one of its | 10 | var result = 0, segment, i; |
11 | // parameters in undefined | 11 | |
12 | optionalMin = function(left, right) { | 12 | i = endSequence - playlist.mediaSequence; |
13 | left = isFinite(left) ? left : Infinity; | 13 | // if a start time is available for segment immediately following |
14 | right = isFinite(right) ? right : Infinity; | 14 | // the interval, use it |
15 | return Math.min(left, right); | 15 | segment = playlist.segments[i]; |
16 | // Walk backward until we find the latest segment with timeline | ||
17 | // information that is earlier than endSequence | ||
18 | if (segment) { | ||
19 | if (segment.start !== undefined) { | ||
20 | return { result: segment.start, precise: true }; | ||
21 | } | ||
22 | if (segment.end !== undefined) { | ||
23 | return { | ||
24 | result: segment.end - segment.duration, | ||
25 | precise: true | ||
26 | }; | ||
27 | } | ||
28 | } | ||
29 | while (i--) { | ||
30 | segment = playlist.segments[i]; | ||
31 | if (segment.end !== undefined) { | ||
32 | return { result: result + segment.end, precise: true }; | ||
33 | } | ||
34 | |||
35 | result += segment.duration; | ||
36 | |||
37 | if (segment.start !== undefined) { | ||
38 | return { result: result + segment.start, precise: true }; | ||
39 | } | ||
40 | } | ||
41 | return { result: result, precise: false }; | ||
16 | }; | 42 | }; |
17 | 43 | ||
18 | // Math.max that will return the alternative input if one of its | 44 | forwardDuration = function(playlist, endSequence) { |
19 | // parameters in undefined | 45 | var result = 0, segment, i; |
20 | optionalMax = function(left, right) { | 46 | |
21 | left = isFinite(left) ? left: -Infinity; | 47 | i = endSequence - playlist.mediaSequence; |
22 | right = isFinite(right) ? right: -Infinity; | 48 | // Walk forward until we find the earliest segment with timeline |
23 | return Math.max(left, right); | 49 | // information |
50 | for (; i < playlist.segments.length; i++) { | ||
51 | segment = playlist.segments[i]; | ||
52 | if (segment.start !== undefined) { | ||
53 | return { | ||
54 | result: segment.start - result, | ||
55 | precise: true | ||
56 | }; | ||
57 | } | ||
58 | |||
59 | result += segment.duration; | ||
60 | |||
61 | if (segment.end !== undefined) { | ||
62 | return { | ||
63 | result: segment.end - result, | ||
64 | precise: true | ||
65 | }; | ||
66 | } | ||
67 | |||
68 | } | ||
69 | // indicate we didn't find a useful duration estimate | ||
70 | return { result: -1, precise: false }; | ||
24 | }; | 71 | }; |
25 | 72 | ||
26 | /** | 73 | /** |
... | @@ -31,42 +78,40 @@ | ... | @@ -31,42 +78,40 @@ |
31 | * @param playlist {object} a media playlist object | 78 | * @param playlist {object} a media playlist object |
32 | * @param endSequence {number} (optional) an exclusive upper boundary | 79 | * @param endSequence {number} (optional) an exclusive upper boundary |
33 | * for the playlist. Defaults to playlist length. | 80 | * for the playlist. Defaults to playlist length. |
34 | * @return {number} the duration between the start index and end | 81 | * @return {number} the duration between the first available segment |
35 | * index. | 82 | * and end index. |
36 | */ | 83 | */ |
37 | intervalDuration = function(playlist, endSequence) { | 84 | intervalDuration = function(playlist, endSequence) { |
38 | var result = 0, segment, targetDuration, i; | 85 | var backward, forward; |
39 | 86 | ||
40 | if (endSequence === undefined) { | 87 | if (endSequence === undefined) { |
41 | endSequence = playlist.mediaSequence + (playlist.segments || []).length; | 88 | endSequence = playlist.mediaSequence + playlist.segments.length; |
42 | } | 89 | } |
43 | if (endSequence < 0) { | 90 | |
91 | if (endSequence < playlist.mediaSequence) { | ||
44 | return 0; | 92 | return 0; |
45 | } | 93 | } |
46 | targetDuration = playlist.targetDuration || DEFAULT_TARGET_DURATION; | ||
47 | 94 | ||
48 | i = endSequence - playlist.mediaSequence; | 95 | // do a backward walk to estimate the duration |
49 | // if a start time is available for segment immediately following | 96 | backward = backwardDuration(playlist, endSequence); |
50 | // the interval, use it | 97 | if (backward.precise) { |
51 | segment = playlist.segments[i]; | 98 | // if we were able to base our duration estimate on timing |
52 | // Walk backward until we find the latest segment with timeline | 99 | // information provided directly from the Media Source, return |
53 | // information that is earlier than endSequence | 100 | // it |
54 | if (segment && segment.start !== undefined) { | 101 | return backward.result; |
55 | return segment.start; | ||
56 | } | 102 | } |
57 | while (i--) { | ||
58 | segment = playlist.segments[i]; | ||
59 | if (segment.end !== undefined) { | ||
60 | return result + segment.end; | ||
61 | } | ||
62 | |||
63 | result += (segment.duration || targetDuration); | ||
64 | 103 | ||
65 | if (segment.start !== undefined) { | 104 | // walk forward to see if a precise duration estimate can be made |
66 | return result + segment.start; | 105 | // that way |
67 | } | 106 | forward = forwardDuration(playlist, endSequence); |
107 | if (forward.precise) { | ||
108 | // we found a segment that has been buffered and so it's | ||
109 | // position is known precisely | ||
110 | return forward.result; | ||
68 | } | 111 | } |
69 | return result; | 112 | |
113 | // return the less-precise, playlist-based duration estimate | ||
114 | return backward.result; | ||
70 | }; | 115 | }; |
71 | 116 | ||
72 | /** | 117 | /** | ... | ... |
... | @@ -52,6 +52,9 @@ videojs.HlsHandler = videojs.extend(Component, { | ... | @@ -52,6 +52,9 @@ videojs.HlsHandler = videojs.extend(Component, { |
52 | // being downloaded or processed | 52 | // being downloaded or processed |
53 | this.pendingSegment_ = null; | 53 | this.pendingSegment_ = null; |
54 | 54 | ||
55 | // start playlist selection at a reasonable bandwidth for | ||
56 | // broadband internet | ||
57 | this.bandwidth = options.bandwidth || 4194304; // 0.5 Mbps | ||
55 | this.bytesReceived = 0; | 58 | this.bytesReceived = 0; |
56 | 59 | ||
57 | // loadingState_ tracks how far along the buffering process we | 60 | // loadingState_ tracks how far along the buffering process we | ... | ... |
... | @@ -93,15 +93,50 @@ | ... | @@ -93,15 +93,50 @@ |
93 | equal(duration, 50.0002, 'calculated with mixed intervals'); | 93 | equal(duration, 50.0002, 'calculated with mixed intervals'); |
94 | }); | 94 | }); |
95 | 95 | ||
96 | test('uses timeline values for the expired duration of live playlists', function() { | ||
97 | var playlist = { | ||
98 | mediaSequence: 12, | ||
99 | segments: [{ | ||
100 | duration: 10, | ||
101 | end: 120.5, | ||
102 | uri: '0.ts' | ||
103 | }, { | ||
104 | duration: 9, | ||
105 | uri: '1.ts' | ||
106 | }] | ||
107 | }, duration; | ||
108 | |||
109 | duration = Playlist.duration(playlist, playlist.mediaSequence); | ||
110 | equal(duration, 110.5, 'used segment end time'); | ||
111 | duration = Playlist.duration(playlist, playlist.mediaSequence + 1); | ||
112 | equal(duration, 120.5, 'used segment end time'); | ||
113 | duration = Playlist.duration(playlist, playlist.mediaSequence + 2); | ||
114 | equal(duration, 120.5 + 9, 'used segment end time'); | ||
115 | }); | ||
116 | |||
117 | test('looks outside the queried interval for live playlist timeline values', function() { | ||
118 | var playlist = { | ||
119 | mediaSequence: 12, | ||
120 | segments: [{ | ||
121 | duration: 10, | ||
122 | uri: '0.ts' | ||
123 | }, { | ||
124 | duration: 9, | ||
125 | end: 120.5, | ||
126 | uri: '1.ts' | ||
127 | }] | ||
128 | }, duration; | ||
129 | |||
130 | duration = Playlist.duration(playlist, playlist.mediaSequence); | ||
131 | equal(duration, 120.5 - 9 - 10, 'used segment end time'); | ||
132 | }); | ||
133 | |||
96 | test('ignores discontinuity sequences later than the end', function() { | 134 | test('ignores discontinuity sequences later than the end', function() { |
97 | var duration = Playlist.duration({ | 135 | var duration = Playlist.duration({ |
98 | mediaSequence: 0, | 136 | mediaSequence: 0, |
99 | discontinuityStarts: [1, 3], | 137 | discontinuityStarts: [1, 3], |
100 | segments: [{ | 138 | segments: [{ |
101 | minVideoPts: 0, | 139 | duration: 10, |
102 | minAudioPts: 0, | ||
103 | maxVideoPts: 10 * 1000, | ||
104 | maxAudioPts: 10 * 1000, | ||
105 | uri: '0.ts' | 140 | uri: '0.ts' |
106 | }, { | 141 | }, { |
107 | discontinuity: true, | 142 | discontinuity: true, |
... | @@ -200,17 +235,10 @@ | ... | @@ -200,17 +235,10 @@ |
200 | endList: true, | 235 | endList: true, |
201 | discontinuityStarts: [1], | 236 | discontinuityStarts: [1], |
202 | segments: [{ | 237 | segments: [{ |
203 | minVideoPts: 0, | 238 | duration: 10, |
204 | minAudioPts: 0, | ||
205 | maxVideoPts: 1 * 10 * 1000, | ||
206 | maxAudioPts: 1 * 10 * 1000, | ||
207 | uri: '0.ts' | 239 | uri: '0.ts' |
208 | }, { | 240 | }, { |
209 | discontinuity: true, | 241 | discontinuity: true, |
210 | minVideoPts: 2 * 10 * 1000, | ||
211 | minAudioPts: 2 * 10 * 1000, | ||
212 | maxVideoPts: 3 * 10 * 1000, | ||
213 | maxAudioPts: 3 * 10 * 1000, | ||
214 | duration: 10, | 242 | duration: 10, |
215 | uri: '1.ts' | 243 | uri: '1.ts' |
216 | }] | 244 | }] |
... | @@ -219,54 +247,6 @@ | ... | @@ -219,54 +247,6 @@ |
219 | equal(duration, 10 + 10, 'handles discontinuities'); | 247 | equal(duration, 10 + 10, 'handles discontinuities'); |
220 | }); | 248 | }); |
221 | 249 | ||
222 | test('does not count ending segment gaps across a discontinuity', function() { | ||
223 | var duration = Playlist.duration({ | ||
224 | mediaSequence: 0, | ||
225 | discontinuityStarts: [1], | ||
226 | endList: true, | ||
227 | segments: [{ | ||
228 | minVideoPts: 0, | ||
229 | minAudioPts: 0, | ||
230 | maxVideoPts: 1 * 10 * 1000, | ||
231 | maxAudioPts: 1 * 10 * 1000, | ||
232 | uri: '0.ts' | ||
233 | }, { | ||
234 | discontinuity: true, | ||
235 | minVideoPts: 1 * 10 * 1000 + 100, | ||
236 | minAudioPts: 1 * 10 * 1000 + 100, | ||
237 | maxVideoPts: 2 * 10 * 1000 + 100, | ||
238 | maxAudioPts: 2 * 10 * 1000 + 100, | ||
239 | duration: 10, | ||
240 | uri: '1.ts' | ||
241 | }] | ||
242 | }, 1); | ||
243 | |||
244 | equal(duration, (1 * 10 * 1000) * 0.001, 'did not include the segment gap'); | ||
245 | }); | ||
246 | |||
247 | test('trailing duration on the final segment can be excluded', function() { | ||
248 | var duration = Playlist.duration({ | ||
249 | mediaSequence: 0, | ||
250 | endList: true, | ||
251 | segments: [{ | ||
252 | minVideoPts: 0, | ||
253 | minAudioPts: 0, | ||
254 | maxVideoPts: 1 * 10 * 1000, | ||
255 | maxAudioPts: 1 * 10 * 1000, | ||
256 | uri: '0.ts' | ||
257 | }, { | ||
258 | minVideoPts: 1 * 10 * 1000 + 100, | ||
259 | minAudioPts: 1 * 10 * 1000 + 100, | ||
260 | maxVideoPts: 2 * 10 * 1000 + 100, | ||
261 | maxAudioPts: 2 * 10 * 1000 + 100, | ||
262 | duration: 10, | ||
263 | uri: '1.ts' | ||
264 | }] | ||
265 | }, 1, false); | ||
266 | |||
267 | equal(duration, (1 * 10 * 1000) * 0.001, 'did not include the segment gap'); | ||
268 | }); | ||
269 | |||
270 | test('a non-positive length interval has zero duration', function() { | 250 | test('a non-positive length interval has zero duration', function() { |
271 | var playlist = { | 251 | var playlist = { |
272 | mediaSequence: 0, | 252 | mediaSequence: 0, |
... | @@ -341,16 +321,19 @@ | ... | @@ -341,16 +321,19 @@ |
341 | 321 | ||
342 | test('only considers available segments', function() { | 322 | test('only considers available segments', function() { |
343 | var seekable = Playlist.seekable({ | 323 | var seekable = Playlist.seekable({ |
344 | targetDuration: 10, | ||
345 | mediaSequence: 7, | 324 | mediaSequence: 7, |
346 | segments: [{ | 325 | segments: [{ |
347 | uri: '8.ts' | 326 | uri: '8.ts', |
327 | duration: 10 | ||
348 | }, { | 328 | }, { |
349 | uri: '9.ts' | 329 | uri: '9.ts', |
330 | duration: 10 | ||
350 | }, { | 331 | }, { |
351 | uri: '10.ts' | 332 | uri: '10.ts', |
333 | duration: 10 | ||
352 | }, { | 334 | }, { |
353 | uri: '11.ts' | 335 | uri: '11.ts', |
336 | duration: 10 | ||
354 | }] | 337 | }] |
355 | }); | 338 | }); |
356 | equal(seekable.length, 1, 'there are seekable ranges'); | 339 | equal(seekable.length, 1, 'there are seekable ranges'); | ... | ... |
... | @@ -738,8 +738,8 @@ test('buffer checks are noops when only the master is ready', function() { | ... | @@ -738,8 +738,8 @@ test('buffer checks are noops when only the master is ready', function() { |
738 | type: 'application/vnd.apple.mpegurl' | 738 | type: 'application/vnd.apple.mpegurl' |
739 | }); | 739 | }); |
740 | openMediaSource(player); | 740 | openMediaSource(player); |
741 | standardXHRResponse(requests.shift()); | 741 | standardXHRResponse(requests.shift()); // master |
742 | standardXHRResponse(requests.shift()); | 742 | standardXHRResponse(requests.shift()); // media |
743 | // ignore any outstanding segment requests | 743 | // ignore any outstanding segment requests |
744 | requests.length = 0; | 744 | requests.length = 0; |
745 | 745 | ||
... | @@ -752,7 +752,8 @@ test('buffer checks are noops when only the master is ready', function() { | ... | @@ -752,7 +752,8 @@ test('buffer checks are noops when only the master is ready', function() { |
752 | openMediaSource(player); | 752 | openMediaSource(player); |
753 | 753 | ||
754 | // respond with the master playlist but don't send the media playlist yet | 754 | // respond with the master playlist but don't send the media playlist yet |
755 | standardXHRResponse(requests.shift()); | 755 | player.tech_.hls.bandwidth = 1; // force media1 to be requested |
756 | standardXHRResponse(requests.shift()); // master | ||
756 | // trigger fillBuffer() | 757 | // trigger fillBuffer() |
757 | player.tech_.hls.checkBuffer_(); | 758 | player.tech_.hls.checkBuffer_(); |
758 | 759 | ||
... | @@ -1362,8 +1363,8 @@ test('waits to download new segments until the media playlist is stable', functi | ... | @@ -1362,8 +1363,8 @@ test('waits to download new segments until the media playlist is stable', functi |
1362 | type: 'application/vnd.apple.mpegurl' | 1363 | type: 'application/vnd.apple.mpegurl' |
1363 | }); | 1364 | }); |
1364 | openMediaSource(player); | 1365 | openMediaSource(player); |
1365 | standardXHRResponse(requests.shift()); // master | ||
1366 | player.tech_.hls.bandwidth = 1; // make sure we stay on the lowest variant | 1366 | player.tech_.hls.bandwidth = 1; // make sure we stay on the lowest variant |
1367 | standardXHRResponse(requests.shift()); // master | ||
1367 | standardXHRResponse(requests.shift()); // media1 | 1368 | standardXHRResponse(requests.shift()); // media1 |
1368 | 1369 | ||
1369 | // force a playlist switch | 1370 | // force a playlist switch | ... | ... |
-
Please register or sign in to post a comment