bce88a18 by jrivera Committed by David LaPalomento

buffer at the current time range end instead of incrementing a variable. closes #423

1 parent c7d5b626
...@@ -2,7 +2,7 @@ CHANGELOG ...@@ -2,7 +2,7 @@ CHANGELOG
2 ========= 2 =========
3 3
4 ## HEAD (Unreleased) 4 ## HEAD (Unreleased)
5 _(none)_ 5 * buffer at the current time range end instead of incrementing a variable. ([view](https://github.com/videojs/videojs-contrib-hls/pull/423))
6 6
7 -------------------- 7 --------------------
8 8
......
...@@ -44,7 +44,7 @@ ...@@ -44,7 +44,7 @@
44 "karma-sauce-launcher": "~0.1.8", 44 "karma-sauce-launcher": "~0.1.8",
45 "qunitjs": "^1.18.0", 45 "qunitjs": "^1.18.0",
46 "sinon": "1.10.2", 46 "sinon": "1.10.2",
47 "video.js": "^5.0.0-rc.96" 47 "video.js": "^5.1.0"
48 }, 48 },
49 "dependencies": { 49 "dependencies": {
50 "pkcs7": "^0.2.2", 50 "pkcs7": "^0.2.2",
......
...@@ -367,7 +367,17 @@ ...@@ -367,7 +367,17 @@
367 * closest playback position that is currently available. 367 * closest playback position that is currently available.
368 */ 368 */
369 PlaylistLoader.prototype.getMediaIndexForTime_ = function(time) { 369 PlaylistLoader.prototype.getMediaIndexForTime_ = function(time) {
370 var i, j, segment, targetDuration; 370 var
371 i,
372 segment,
373 originalTime = time,
374 targetDuration = this.media_.targetDuration || 10,
375 numSegments = this.media_.segments.length,
376 lastSegment = numSegments - 1,
377 startIndex,
378 endIndex,
379 knownStart,
380 knownEnd;
371 381
372 if (!this.media_) { 382 if (!this.media_) {
373 return 0; 383 return 0;
...@@ -379,57 +389,105 @@ ...@@ -379,57 +389,105 @@
379 return 0; 389 return 0;
380 } 390 }
381 391
382 // 1) Walk backward until we find the latest segment with timeline 392 // 1) Walk backward until we find the first segment with timeline
383 // information that is earlier than `time` 393 // information that is earlier than `time`
384 targetDuration = this.media_.targetDuration || 10; 394 for (i = lastSegment; i >= 0; i--) {
385 i = this.media_.segments.length;
386 while (i--) {
387 segment = this.media_.segments[i]; 395 segment = this.media_.segments[i];
388 if (segment.end !== undefined && segment.end <= time) { 396 if (segment.end !== undefined && segment.end <= time) {
389 time -= segment.end; 397 startIndex = i + 1;
398 knownStart = segment.end;
399 if (startIndex >= numSegments) {
400 // The last segment claims to end *before* the time we are
401 // searching for so just return it
402 return numSegments;
403 }
390 break; 404 break;
391 } 405 }
392 if (segment.start !== undefined && segment.start < time) { 406 if (segment.start !== undefined && segment.start <= time) {
393
394 if (segment.end !== undefined && segment.end > time) { 407 if (segment.end !== undefined && segment.end > time) {
395 // we've found the target segment exactly 408 // we've found the target segment exactly
396 return i; 409 return i;
397 } 410 }
411 startIndex = i;
412 knownStart = segment.start;
413 break;
414 }
415 }
398 416
399 time -= segment.start; 417 // 2) Walk forward until we find the first segment with timeline
400 time -= segment.duration || targetDuration; 418 // information that is greater than `time`
401 if (time < 0) { 419 for (i = 0; i < numSegments; i++) {
402 // the segment with start information is also our best guess 420 segment = this.media_.segments[i];
403 // for the momment 421 if (segment.start !== undefined && segment.start > time) {
404 return i; 422 endIndex = i - 1;
423 knownEnd = segment.start;
424 if (endIndex < 0) {
425 // The first segment claims to start *after* the time we are
426 // searching for so just return it
427 return -1;
405 } 428 }
406 break; 429 break;
407 } 430 }
431 if (segment.end !== undefined && segment.end > time) {
432 endIndex = i;
433 knownEnd = segment.end;
434 break;
435 }
408 } 436 }
409 i++;
410 437
411 // 2) Walk forward, testing each segment to see if `time` falls within it 438 if (startIndex !== undefined) {
412 for (j = i; j < this.media_.segments.length; j++) { 439 // We have a known-start point that is before our desired time so
413 segment = this.media_.segments[j]; 440 // walk from that point forwards
414 time -= segment.duration || targetDuration; 441 time = time - knownStart;
442 for (i = startIndex; i < (endIndex || numSegments); i++) {
443 segment = this.media_.segments[i];
444 time -= segment.duration || targetDuration;
445 if (time < 0) {
446 return i;
447 }
448 }
415 449
416 if (time < 0) { 450 if (i === endIndex) {
417 return j; 451 // We haven't found a segment but we did hit a known end point
452 // so fallback to "Algorithm Jon" - try to interpolate the segment
453 // index based on the known span of the timeline we are dealing with
454 // and the number of segments inside that span
455 return startIndex + Math.floor(
456 ((originalTime - knownStart) / (knownEnd - knownStart)) *
457 (endIndex - startIndex));
418 } 458 }
419 459
420 // 2a) If we discover a segment that has timeline information 460 // We _still_ haven't found a segment so load the last one
421 // before finding the result segment, the playlist information 461 return lastSegment;
422 // must have been inaccurate. Start a binary search for the 462 } else if (endIndex !== undefined) {
423 // segment which contains `time`. If the guess turns out to be 463 // We _only_ have a known-end point that is after our desired time so
424 // incorrect, we'll have more info to work with next time. 464 // walk from that point backwards
425 if (segment.start !== undefined || segment.end !== undefined) { 465 time = knownEnd - time;
426 return Math.floor((j - i) * 0.5); 466 for (i = endIndex; i >= 0; i--) {
467 segment = this.media_.segments[i];
468 time -= segment.duration || targetDuration;
469 if (time < 0) {
470 return i;
471 }
427 } 472 }
473 // We haven't found a segment so load the first one
474 return 0;
475 } else {
476 // We known nothing so use "Algorithm A" - walk from the front
477 // of the playlist naively subtracking durations until we find
478 // a segment that contains time and return it
479 for (i = 0; i < numSegments; i++) {
480 segment = this.media_.segments[i];
481 time -= segment.duration || targetDuration;
482 if (time < 0) {
483 return i;
484 }
485 }
486 // We are out of possible candidates so load the last one...
487 // The last one is the least likely to overlap a buffer and therefore
488 // the one most likely to tell us something about the timeline
489 return lastSegment;
428 } 490 }
429
430 // the playback position is outside the range of available
431 // segments so return the length
432 return this.media_.segments.length;
433 }; 491 };
434 492
435 videojs.Hls.PlaylistLoader = PlaylistLoader; 493 videojs.Hls.PlaylistLoader = PlaylistLoader;
......
...@@ -653,8 +653,8 @@ ...@@ -653,8 +653,8 @@
653 equal(loader.getMediaIndexForTime_(3), 0, 'time three is index zero'); 653 equal(loader.getMediaIndexForTime_(3), 0, 'time three is index zero');
654 equal(loader.getMediaIndexForTime_(10), 2, 'time 10 is index 2'); 654 equal(loader.getMediaIndexForTime_(10), 2, 'time 10 is index 2');
655 equal(loader.getMediaIndexForTime_(22), 655 equal(loader.getMediaIndexForTime_(22),
656 3, 656 2,
657 'time greater than the length is index 3'); 657 'time greater than the length is index 2');
658 }); 658 });
659 659
660 test('returns the lower index when calculating for a segment boundary', function() { 660 test('returns the lower index when calculating for a segment boundary', function() {
...@@ -683,9 +683,9 @@ ...@@ -683,9 +683,9 @@
683 '1002.ts\n'); 683 '1002.ts\n');
684 loader.media().segments[0].start = 150; 684 loader.media().segments[0].start = 150;
685 685
686 equal(loader.getMediaIndexForTime_(0), 0, 'the lowest returned value is zero'); 686 equal(loader.getMediaIndexForTime_(0), -1, 'the lowest returned value is negative one');
687 equal(loader.getMediaIndexForTime_(45), 0, 'expired content returns zero'); 687 equal(loader.getMediaIndexForTime_(45), -1, 'expired content returns negative one');
688 equal(loader.getMediaIndexForTime_(75), 0, 'expired content returns zero'); 688 equal(loader.getMediaIndexForTime_(75), -1, 'expired content returns negative one');
689 equal(loader.getMediaIndexForTime_(50 + 100), 0, 'calculates the earliest available position'); 689 equal(loader.getMediaIndexForTime_(50 + 100), 0, 'calculates the earliest available position');
690 equal(loader.getMediaIndexForTime_(50 + 100 + 2), 0, 'calculates within the first segment'); 690 equal(loader.getMediaIndexForTime_(50 + 100 + 2), 0, 'calculates within the first segment');
691 equal(loader.getMediaIndexForTime_(50 + 100 + 2), 0, 'calculates within the first segment'); 691 equal(loader.getMediaIndexForTime_(50 + 100 + 2), 0, 'calculates within the first segment');
......
...@@ -218,6 +218,7 @@ module('HLS', { ...@@ -218,6 +218,7 @@ module('HLS', {
218 var el = document.createElement('div'); 218 var el = document.createElement('div');
219 el.id = 'vjs_mock_flash_' + nextId++; 219 el.id = 'vjs_mock_flash_' + nextId++;
220 el.className = 'vjs-tech vjs-mock-flash'; 220 el.className = 'vjs-tech vjs-mock-flash';
221 el.duration = Infinity;
221 el.vjs_load = function() {}; 222 el.vjs_load = function() {};
222 el.vjs_getProperty = function(attr) { 223 el.vjs_getProperty = function(attr) {
223 if (attr === 'buffered') { 224 if (attr === 'buffered') {
...@@ -1131,7 +1132,7 @@ test('buffers based on the correct TimeRange if multiple ranges exist', function ...@@ -1131,7 +1132,7 @@ test('buffers based on the correct TimeRange if multiple ranges exist', function
1131 return videojs.createTimeRange(buffered); 1132 return videojs.createTimeRange(buffered);
1132 }; 1133 };
1133 currentTime = 8; 1134 currentTime = 8;
1134 buffered = [[0, 10], [20, 40]]; 1135 buffered = [[0, 10], [20, 30]];
1135 1136
1136 standardXHRResponse(requests[0]); 1137 standardXHRResponse(requests[0]);
1137 standardXHRResponse(requests[1]); 1138 standardXHRResponse(requests[1]);
...@@ -1144,14 +1145,9 @@ test('buffers based on the correct TimeRange if multiple ranges exist', function ...@@ -1144,14 +1145,9 @@ test('buffers based on the correct TimeRange if multiple ranges exist', function
1144 currentTime = 22; 1145 currentTime = 22;
1145 player.tech_.hls.sourceBuffer.trigger('updateend'); 1146 player.tech_.hls.sourceBuffer.trigger('updateend');
1146 player.tech_.hls.checkBuffer_(); 1147 player.tech_.hls.checkBuffer_();
1147 strictEqual(requests.length, 2, 'made no additional requests');
1148
1149 buffered = [[0, 10], [20, 30]];
1150 player.tech_.hls.checkBuffer_();
1151 standardXHRResponse(requests[2]);
1152 strictEqual(requests.length, 3, 'made three requests'); 1148 strictEqual(requests.length, 3, 'made three requests');
1153 strictEqual(requests[2].url, 1149 strictEqual(requests[2].url,
1154 absoluteUrl('manifest/media-00004.ts'), 1150 absoluteUrl('manifest/media-00003.ts'),
1155 'made segment request'); 1151 'made segment request');
1156 }); 1152 });
1157 1153
...@@ -1381,7 +1377,7 @@ test('seeking in an empty playlist is a non-erroring noop', function() { ...@@ -1381,7 +1377,7 @@ test('seeking in an empty playlist is a non-erroring noop', function() {
1381 equal(requests.length, requestsLength, 'made no additional requests'); 1377 equal(requests.length, requestsLength, 'made no additional requests');
1382 }); 1378 });
1383 1379
1384 test('duration is Infinity for live playlists', function() { 1380 test('tech\'s duration reports Infinity for live playlists', function() {
1385 player.src({ 1381 player.src({
1386 src: 'http://example.com/manifest/missingEndlist.m3u8', 1382 src: 'http://example.com/manifest/missingEndlist.m3u8',
1387 type: 'application/vnd.apple.mpegurl' 1383 type: 'application/vnd.apple.mpegurl'
...@@ -1390,9 +1386,13 @@ test('duration is Infinity for live playlists', function() { ...@@ -1390,9 +1386,13 @@ test('duration is Infinity for live playlists', function() {
1390 1386
1391 standardXHRResponse(requests[0]); 1387 standardXHRResponse(requests[0]);
1392 1388
1393 strictEqual(player.tech_.hls.mediaSource.duration, 1389 strictEqual(player.tech_.duration(),
1390 Infinity,
1391 'duration on the tech is infinity');
1392
1393 notEqual(player.tech_.hls.mediaSource.duration,
1394 Infinity, 1394 Infinity,
1395 'duration is infinity'); 1395 'duration on the mediaSource is not infinity');
1396 }); 1396 });
1397 1397
1398 test('live playlist starts three target durations before live', function() { 1398 test('live playlist starts three target durations before live', function() {
...@@ -1644,7 +1644,7 @@ test('calls mediaSource\'s timestampOffset on discontinuity', function() { ...@@ -1644,7 +1644,7 @@ test('calls mediaSource\'s timestampOffset on discontinuity', function() {
1644 }); 1644 });
1645 1645
1646 test('sets timestampOffset when seeking with discontinuities', function() { 1646 test('sets timestampOffset when seeking with discontinuities', function() {
1647 var removes = [], timeRange = videojs.createTimeRange(0, 10); 1647 var timeRange = videojs.createTimeRange(0, 10);
1648 1648
1649 player.src({ 1649 player.src({
1650 src: 'discontinuity.m3u8', 1650 src: 'discontinuity.m3u8',
...@@ -1670,18 +1670,12 @@ test('sets timestampOffset when seeking with discontinuities', function() { ...@@ -1670,18 +1670,12 @@ test('sets timestampOffset when seeking with discontinuities', function() {
1670 '3.ts\n' + 1670 '3.ts\n' +
1671 '#EXT-X-ENDLIST\n'); 1671 '#EXT-X-ENDLIST\n');
1672 player.tech_.hls.sourceBuffer.timestampOffset = 0; 1672 player.tech_.hls.sourceBuffer.timestampOffset = 0;
1673 player.tech_.hls.sourceBuffer.remove = function(start, end) {
1674 timeRange = videojs.createTimeRange();
1675 removes.push([start, end]);
1676 };
1677
1678 player.currentTime(21); 1673 player.currentTime(21);
1679 clock.tick(1); 1674 clock.tick(1);
1680 equal(requests.shift().aborted, true, 'aborted first request'); 1675 equal(requests.shift().aborted, true, 'aborted first request');
1681 standardXHRResponse(requests.pop()); // 3.ts 1676 standardXHRResponse(requests.pop()); // 3.ts
1682 clock.tick(1000); 1677 clock.tick(1000);
1683 equal(player.tech_.hls.sourceBuffer.timestampOffset, 20, 'timestampOffset starts at zero'); 1678 equal(player.tech_.hls.sourceBuffer.timestampOffset, 20, 'timestampOffset starts at zero');
1684 equal(removes.length, 1, 'remove was called');
1685 }); 1679 });
1686 1680
1687 test('can seek before the source buffer opens', function() { 1681 test('can seek before the source buffer opens', function() {
......