bce76238 by David LaPalomento

Merge pull request #445 from dmlap/seekable-start

	Use information from buffering to calculate seekable
2 parents c46fd273 4b4efa03
...@@ -390,7 +390,10 @@ ...@@ -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
16 }; 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;
17 36
18 // Math.max that will return the alternative input if one of its 37 if (segment.start !== undefined) {
19 // parameters in undefined 38 return { result: result + segment.start, precise: true };
20 optionalMax = function(left, right) { 39 }
21 left = isFinite(left) ? left: -Infinity; 40 }
22 right = isFinite(right) ? right: -Infinity; 41 return { result: result, precise: false };
23 return Math.max(left, right); 42 };
43
44 forwardDuration = function(playlist, endSequence) {
45 var result = 0, segment, i;
46
47 i = endSequence - playlist.mediaSequence;
48 // Walk forward until we find the earliest segment with timeline
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
......