3e33b1f5 by David LaPalomento

Use information from buffering to calculate seekable

Look forward and backward through the playlist to find an anchor to base seekable calculations on. This is helpful for live playlists in particular, where the only way to determine the correct starting position for the seekable range is to look at where future segments have ended up in the buffer. Without this change, the start of seekable was always zero, even if the live sliding window had moved forward.
1 parent 56919c5f
...@@ -389,7 +389,10 @@ ...@@ -389,7 +389,10 @@
389 return 0; 389 return 0;
390 } 390 }
391 391
392 // 1) Walk backward until we find the first segment with timeline 392 // find segments with known timing information that bound the
393 // target time
394
395 // Walk backward until we find the first segment with timeline
393 // information that is earlier than `time` 396 // information that is earlier than `time`
394 for (i = lastSegment; i >= 0; i--) { 397 for (i = lastSegment; i >= 0; i--) {
395 segment = this.media_.segments[i]; 398 segment = this.media_.segments[i];
...@@ -414,7 +417,7 @@ ...@@ -414,7 +417,7 @@
414 } 417 }
415 } 418 }
416 419
417 // 2) Walk forward until we find the first segment with timeline 420 // Walk forward until we find the first segment with timeline
418 // information that is greater than `time` 421 // information that is greater than `time`
419 for (i = 0; i < numSegments; i++) { 422 for (i = 0; i < numSegments; i++) {
420 segment = this.media_.segments[i]; 423 segment = this.media_.segments[i];
...@@ -423,7 +426,8 @@ ...@@ -423,7 +426,8 @@
423 knownEnd = segment.start; 426 knownEnd = segment.start;
424 if (endIndex < 0) { 427 if (endIndex < 0) {
425 // The first segment claims to start *after* the time we are 428 // The first segment claims to start *after* the time we are
426 // searching for so just return it 429 // searching for so the target segment must no longer be
430 // available
427 return -1; 431 return -1;
428 } 432 }
429 break; 433 break;
...@@ -435,6 +439,9 @@ ...@@ -435,6 +439,9 @@
435 } 439 }
436 } 440 }
437 441
442 // use the bounds we just found and playlist information to
443 // estimate the segment that contains the time we are looking for
444
438 if (startIndex !== undefined) { 445 if (startIndex !== undefined) {
439 // We have a known-start point that is before our desired time so 446 // We have a known-start point that is before our desired time so
440 // walk from that point forwards 447 // 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 /**
......
...@@ -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');
......