buffer at the current time range end instead of incrementing a variable. closes #423
Showing
6 changed files
with
108 additions
and
56 deletions
... | @@ -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; | ... | ... |
This diff is collapsed.
Click to expand it.
... | @@ -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() { | ... | ... |
-
Please register or sign in to post a comment