buffer at the current time range end instead of incrementing a variable. closes #423
Showing
6 changed files
with
208 additions
and
108 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 | } | ||
416 | |||
417 | // 2) Walk forward until we find the first segment with timeline | ||
418 | // information that is greater than `time` | ||
419 | for (i = 0; i < numSegments; i++) { | ||
420 | segment = this.media_.segments[i]; | ||
421 | if (segment.start !== undefined && segment.start > time) { | ||
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; | ||
428 | } | ||
429 | break; | ||
430 | } | ||
431 | if (segment.end !== undefined && segment.end > time) { | ||
432 | endIndex = i; | ||
433 | knownEnd = segment.end; | ||
434 | break; | ||
435 | } | ||
436 | } | ||
398 | 437 | ||
399 | time -= segment.start; | 438 | if (startIndex !== undefined) { |
439 | // We have a known-start point that is before our desired time so | ||
440 | // walk from that point forwards | ||
441 | time = time - knownStart; | ||
442 | for (i = startIndex; i < (endIndex || numSegments); i++) { | ||
443 | segment = this.media_.segments[i]; | ||
400 | time -= segment.duration || targetDuration; | 444 | time -= segment.duration || targetDuration; |
401 | if (time < 0) { | 445 | if (time < 0) { |
402 | // the segment with start information is also our best guess | ||
403 | // for the momment | ||
404 | return i; | 446 | return i; |
405 | } | 447 | } |
406 | break; | ||
407 | } | 448 | } |
449 | |||
450 | if (i === endIndex) { | ||
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)); | ||
408 | } | 458 | } |
409 | i++; | ||
410 | 459 | ||
411 | // 2) Walk forward, testing each segment to see if `time` falls within it | 460 | // We _still_ haven't found a segment so load the last one |
412 | for (j = i; j < this.media_.segments.length; j++) { | 461 | return lastSegment; |
413 | segment = this.media_.segments[j]; | 462 | } else if (endIndex !== undefined) { |
463 | // We _only_ have a known-end point that is after our desired time so | ||
464 | // walk from that point backwards | ||
465 | time = knownEnd - time; | ||
466 | for (i = endIndex; i >= 0; i--) { | ||
467 | segment = this.media_.segments[i]; | ||
414 | time -= segment.duration || targetDuration; | 468 | time -= segment.duration || targetDuration; |
415 | |||
416 | if (time < 0) { | 469 | if (time < 0) { |
417 | return j; | 470 | return i; |
418 | } | 471 | } |
419 | 472 | } | |
420 | // 2a) If we discover a segment that has timeline information | 473 | // We haven't found a segment so load the first one |
421 | // before finding the result segment, the playlist information | 474 | return 0; |
422 | // must have been inaccurate. Start a binary search for the | 475 | } else { |
423 | // segment which contains `time`. If the guess turns out to be | 476 | // We known nothing so use "Algorithm A" - walk from the front |
424 | // incorrect, we'll have more info to work with next time. | 477 | // of the playlist naively subtracking durations until we find |
425 | if (segment.start !== undefined || segment.end !== undefined) { | 478 | // a segment that contains time and return it |
426 | return Math.floor((j - i) * 0.5); | 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; | ||
427 | } | 484 | } |
428 | } | 485 | } |
429 | 486 | // We are out of possible candidates so load the last one... | |
430 | // the playback position is outside the range of available | 487 | // The last one is the least likely to overlap a buffer and therefore |
431 | // segments so return the length | 488 | // the one most likely to tell us something about the timeline |
432 | return this.media_.segments.length; | 489 | return lastSegment; |
490 | } | ||
433 | }; | 491 | }; |
434 | 492 | ||
435 | videojs.Hls.PlaylistLoader = PlaylistLoader; | 493 | videojs.Hls.PlaylistLoader = PlaylistLoader; | ... | ... |
... | @@ -304,13 +304,26 @@ videojs.Hls.prototype.setupSourceBuffer_ = function() { | ... | @@ -304,13 +304,26 @@ videojs.Hls.prototype.setupSourceBuffer_ = function() { |
304 | // transition the sourcebuffer to the ended state if we've hit the end of | 304 | // transition the sourcebuffer to the ended state if we've hit the end of |
305 | // the playlist | 305 | // the playlist |
306 | this.sourceBuffer.addEventListener('updateend', function() { | 306 | this.sourceBuffer.addEventListener('updateend', function() { |
307 | var segmentInfo = this.pendingSegment_, segment, currentBuffered, timelineUpdates; | 307 | var |
308 | segmentInfo = this.pendingSegment_, | ||
309 | segment, | ||
310 | playlist, | ||
311 | currentMediaIndex, | ||
312 | currentBuffered, | ||
313 | timelineUpdates; | ||
314 | |||
315 | // stop here if the update errored or was aborted | ||
316 | if (!segmentInfo) { | ||
317 | return; | ||
318 | } | ||
308 | 319 | ||
309 | this.pendingSegment_ = null; | 320 | this.pendingSegment_ = null; |
310 | 321 | ||
311 | // if we've buffered to the end of the video, let the MediaSource know | 322 | // if we've buffered to the end of the video, let the MediaSource know |
312 | currentBuffered = this.findCurrentBuffered_(); | 323 | currentBuffered = this.findCurrentBuffered_(); |
313 | if (currentBuffered.length && this.duration() === currentBuffered.end(0)) { | 324 | if (currentBuffered.length && |
325 | this.duration() === currentBuffered.end(0) && | ||
326 | this.mediaSource.readyState === 'open') { | ||
314 | this.mediaSource.endOfStream(); | 327 | this.mediaSource.endOfStream(); |
315 | } | 328 | } |
316 | 329 | ||
... | @@ -319,26 +332,46 @@ videojs.Hls.prototype.setupSourceBuffer_ = function() { | ... | @@ -319,26 +332,46 @@ videojs.Hls.prototype.setupSourceBuffer_ = function() { |
319 | return; | 332 | return; |
320 | } | 333 | } |
321 | 334 | ||
335 | // if we switched renditions don't try to add segment timeline | ||
336 | // information to the playlist | ||
337 | if (segmentInfo.playlist.uri !== this.playlists.media().uri) { | ||
338 | return this.fillBuffer(); | ||
339 | } | ||
340 | |||
341 | playlist = this.playlists.media(); | ||
342 | currentMediaIndex = segmentInfo.mediaIndex + (segmentInfo.mediaSequence - playlist.mediaSequence); | ||
343 | |||
322 | // annotate the segment with any start and end time information | 344 | // annotate the segment with any start and end time information |
323 | // added by the media processing | 345 | // added by the media processing |
324 | segment = segmentInfo.playlist.segments[segmentInfo.mediaIndex]; | 346 | segment = playlist.segments[currentMediaIndex]; |
347 | |||
325 | timelineUpdates = videojs.Hls.bufferedAdditions_(segmentInfo.buffered, | 348 | timelineUpdates = videojs.Hls.bufferedAdditions_(segmentInfo.buffered, |
326 | this.tech_.buffered()); | 349 | this.tech_.buffered()); |
327 | timelineUpdates.forEach(function(update) { | 350 | |
351 | timelineUpdates.forEach(function (update) { | ||
352 | if (segment) { | ||
328 | if (update.start !== undefined) { | 353 | if (update.start !== undefined) { |
329 | segment.start = update.start; | 354 | segment.start = update.start; |
330 | } | 355 | } |
331 | if (update.end !== undefined) { | 356 | if (update.end !== undefined) { |
332 | segment.end = update.end; | 357 | segment.end = update.end; |
333 | } | 358 | } |
359 | } | ||
334 | }); | 360 | }); |
335 | 361 | ||
336 | if (timelineUpdates.length) { | 362 | if (timelineUpdates.length) { |
337 | this.updateDuration(segmentInfo.playlist); | 363 | this.updateDuration(playlist); |
364 | // check if it's time to download the next segment | ||
365 | this.fillBuffer(); | ||
366 | return; | ||
338 | } | 367 | } |
339 | 368 | ||
340 | // check if it's time to download the next segment | 369 | // the last segment append must have been entirely in the |
341 | this.checkBuffer_(); | 370 | // already buffered time ranges. just buffer forward until we |
371 | // find a segment that adds to the buffered time ranges and | ||
372 | // improves subsequent media index calculations. | ||
373 | this.fillBuffer(currentMediaIndex + 1); | ||
374 | return; | ||
342 | }.bind(this)); | 375 | }.bind(this)); |
343 | }; | 376 | }; |
344 | 377 | ||
... | @@ -426,11 +459,8 @@ videojs.Hls.prototype.setCurrentTime = function(currentTime) { | ... | @@ -426,11 +459,8 @@ videojs.Hls.prototype.setCurrentTime = function(currentTime) { |
426 | this.cancelKeyXhr(); | 459 | this.cancelKeyXhr(); |
427 | } | 460 | } |
428 | 461 | ||
429 | // clear out the segment being processed | ||
430 | this.pendingSegment_ = null; | ||
431 | |||
432 | // begin filling the buffer at the new position | 462 | // begin filling the buffer at the new position |
433 | this.fillBuffer(currentTime); | 463 | this.fillBuffer(this.playlists.getMediaIndexForTime_(currentTime)); |
434 | }; | 464 | }; |
435 | 465 | ||
436 | videojs.Hls.prototype.duration = function() { | 466 | videojs.Hls.prototype.duration = function() { |
... | @@ -465,15 +495,25 @@ videojs.Hls.prototype.updateDuration = function(playlist) { | ... | @@ -465,15 +495,25 @@ videojs.Hls.prototype.updateDuration = function(playlist) { |
465 | this.mediaSource.duration = newDuration; | 495 | this.mediaSource.duration = newDuration; |
466 | this.tech_.trigger('durationchange'); | 496 | this.tech_.trigger('durationchange'); |
467 | this.mediaSource.removeEventListener('sourceopen', setDuration); | 497 | this.mediaSource.removeEventListener('sourceopen', setDuration); |
468 | }.bind(this); | 498 | }.bind(this), |
499 | seekable = this.seekable(); | ||
500 | |||
501 | // TODO: Move to videojs-contrib-media-sources | ||
502 | if (seekable.length && newDuration === Infinity) { | ||
503 | if (isNaN(oldDuration)) { | ||
504 | oldDuration = 0; | ||
505 | } | ||
506 | newDuration = Math.max(oldDuration, | ||
507 | seekable.end(0) + playlist.targetDuration * 3); | ||
508 | } | ||
469 | 509 | ||
470 | // if the duration has changed, invalidate the cached value | 510 | // if the duration has changed, invalidate the cached value |
471 | if (oldDuration !== newDuration) { | 511 | if (oldDuration !== newDuration) { |
472 | if (this.mediaSource.readyState === 'open') { | 512 | if (this.mediaSource.readyState !== 'open') { |
513 | this.mediaSource.addEventListener('sourceopen', setDuration); | ||
514 | } else if (!this.sourceBuffer || !this.sourceBuffer.updating) { | ||
473 | this.mediaSource.duration = newDuration; | 515 | this.mediaSource.duration = newDuration; |
474 | this.tech_.trigger('durationchange'); | 516 | this.tech_.trigger('durationchange'); |
475 | } else { | ||
476 | this.mediaSource.addEventListener('sourceopen', setDuration); | ||
477 | } | 517 | } |
478 | } | 518 | } |
479 | }; | 519 | }; |
... | @@ -507,6 +547,8 @@ videojs.Hls.prototype.cancelSegmentXhr = function() { | ... | @@ -507,6 +547,8 @@ videojs.Hls.prototype.cancelSegmentXhr = function() { |
507 | this.segmentXhr_.abort(); | 547 | this.segmentXhr_.abort(); |
508 | this.segmentXhr_ = null; | 548 | this.segmentXhr_ = null; |
509 | } | 549 | } |
550 | // clear out the segment being processed | ||
551 | this.pendingSegment_ = null; | ||
510 | }; | 552 | }; |
511 | 553 | ||
512 | /** | 554 | /** |
... | @@ -667,11 +709,17 @@ videojs.Hls.prototype.stopCheckingBuffer_ = function() { | ... | @@ -667,11 +709,17 @@ videojs.Hls.prototype.stopCheckingBuffer_ = function() { |
667 | */ | 709 | */ |
668 | videojs.Hls.prototype.findCurrentBuffered_ = function() { | 710 | videojs.Hls.prototype.findCurrentBuffered_ = function() { |
669 | var | 711 | var |
670 | tech = this.tech_, | ||
671 | currentTime = tech.currentTime(), | ||
672 | buffered = this.tech_.buffered(), | ||
673 | ranges, | 712 | ranges, |
674 | i; | 713 | i, |
714 | tech = this.tech_, | ||
715 | // !!The order of the next two lines is important!! | ||
716 | // `currentTime` must be equal-to or greater-than the start of the | ||
717 | // buffered range. Flash executes out-of-process so, every value can | ||
718 | // change behind the scenes from line-to-line. By reading `currentTime` | ||
719 | // after `buffered`, we ensure that it is always a current or later | ||
720 | // value during playback. | ||
721 | buffered = tech.buffered(), | ||
722 | currentTime = tech.currentTime(); | ||
675 | 723 | ||
676 | if (buffered && buffered.length) { | 724 | if (buffered && buffered.length) { |
677 | // Search for a range containing the play-head | 725 | // Search for a range containing the play-head |
... | @@ -697,13 +745,13 @@ videojs.Hls.prototype.findCurrentBuffered_ = function() { | ... | @@ -697,13 +745,13 @@ videojs.Hls.prototype.findCurrentBuffered_ = function() { |
697 | * @param seekToTime (optional) {number} the offset into the downloaded segment | 745 | * @param seekToTime (optional) {number} the offset into the downloaded segment |
698 | * to seek to, in seconds | 746 | * to seek to, in seconds |
699 | */ | 747 | */ |
700 | videojs.Hls.prototype.fillBuffer = function(seekToTime) { | 748 | videojs.Hls.prototype.fillBuffer = function(mediaIndex) { |
701 | var | 749 | var |
702 | tech = this.tech_, | 750 | tech = this.tech_, |
703 | currentTime = tech.currentTime(), | 751 | currentTime = tech.currentTime(), |
704 | currentBuffered = this.findCurrentBuffered_(), | 752 | currentBuffered = this.findCurrentBuffered_(), |
753 | currentBufferedEnd = 0, | ||
705 | bufferedTime = 0, | 754 | bufferedTime = 0, |
706 | mediaIndex = 0, | ||
707 | segment, | 755 | segment, |
708 | segmentInfo; | 756 | segmentInfo; |
709 | 757 | ||
... | @@ -739,39 +787,46 @@ videojs.Hls.prototype.fillBuffer = function(seekToTime) { | ... | @@ -739,39 +787,46 @@ videojs.Hls.prototype.fillBuffer = function(seekToTime) { |
739 | return; | 787 | return; |
740 | } | 788 | } |
741 | 789 | ||
742 | // find the next segment to download | 790 | if (mediaIndex === undefined) { |
743 | if (typeof seekToTime === 'number') { | 791 | if (currentBuffered && currentBuffered.length) { |
744 | mediaIndex = this.playlists.getMediaIndexForTime_(seekToTime); | 792 | currentBufferedEnd = currentBuffered.end(0); |
745 | } else if (currentBuffered && currentBuffered.length) { | 793 | mediaIndex = this.playlists.getMediaIndexForTime_(currentBufferedEnd); |
746 | mediaIndex = this.playlists.getMediaIndexForTime_(currentBuffered.end(0)); | 794 | bufferedTime = Math.max(0, currentBufferedEnd - currentTime); |
747 | bufferedTime = Math.max(0, currentBuffered.end(0) - currentTime); | 795 | |
796 | // if there is plenty of content in the buffer and we're not | ||
797 | // seeking, relax for awhile | ||
798 | if (bufferedTime >= videojs.Hls.GOAL_BUFFER_LENGTH) { | ||
799 | return; | ||
800 | } | ||
748 | } else { | 801 | } else { |
749 | mediaIndex = this.playlists.getMediaIndexForTime_(this.tech_.currentTime()); | 802 | mediaIndex = this.playlists.getMediaIndexForTime_(this.tech_.currentTime()); |
750 | } | 803 | } |
804 | } | ||
751 | segment = this.playlists.media().segments[mediaIndex]; | 805 | segment = this.playlists.media().segments[mediaIndex]; |
752 | 806 | ||
753 | // if the video has finished downloading, stop trying to buffer | 807 | // if the video has finished downloading |
754 | if (!segment) { | 808 | if (!segment) { |
755 | return; | 809 | return; |
756 | } | 810 | } |
757 | 811 | ||
758 | // if there is plenty of content in the buffer and we're not | 812 | // we have entered a state where we are fetching the same segment, |
759 | // seeking, relax for awhile | 813 | // try to walk forward |
760 | if (typeof seekToTime !== 'number' && | 814 | if (this.lastSegmentLoaded_ && |
761 | bufferedTime >= videojs.Hls.GOAL_BUFFER_LENGTH) { | 815 | this.lastSegmentLoaded_ === this.playlistUriToUrl(segment.uri)) { |
762 | return; | 816 | return this.fillBuffer(mediaIndex + 1); |
763 | } | 817 | } |
764 | 818 | ||
765 | // package up all the work to append the segment | 819 | // package up all the work to append the segment |
766 | segmentInfo = { | 820 | segmentInfo = { |
767 | // resolve the segment URL relative to the playlist | 821 | // resolve the segment URL relative to the playlist |
768 | uri: this.playlistUriToUrl(segment.uri), | 822 | uri: this.playlistUriToUrl(segment.uri), |
769 | // the segment's mediaIndex at the time it was received | 823 | // the segment's mediaIndex & mediaSequence at the time it was requested |
770 | mediaIndex: mediaIndex, | 824 | mediaIndex: mediaIndex, |
825 | mediaSequence: this.playlists.media().mediaSequence, | ||
771 | // the segment's playlist | 826 | // the segment's playlist |
772 | playlist: this.playlists.media(), | 827 | playlist: this.playlists.media(), |
773 | // optionally, a time offset to seek to within the segment | 828 | // The state of the buffer when this segment was requested |
774 | offset: seekToTime, | 829 | currentBufferedEnd: currentBufferedEnd, |
775 | // unencrypted bytes of the segment | 830 | // unencrypted bytes of the segment |
776 | bytes: null, | 831 | bytes: null, |
777 | // when a key is defined for this segment, the encrypted bytes | 832 | // when a key is defined for this segment, the encrypted bytes |
... | @@ -856,6 +911,7 @@ videojs.Hls.prototype.loadSegment = function(segmentInfo) { | ... | @@ -856,6 +911,7 @@ videojs.Hls.prototype.loadSegment = function(segmentInfo) { |
856 | return; | 911 | return; |
857 | } | 912 | } |
858 | 913 | ||
914 | self.lastSegmentLoaded_ = segmentInfo.uri; | ||
859 | self.setBandwidth(request); | 915 | self.setBandwidth(request); |
860 | 916 | ||
861 | if (segment.key) { | 917 | if (segment.key) { |
... | @@ -944,41 +1000,33 @@ videojs.Hls.prototype.drainBuffer = function(event) { | ... | @@ -944,41 +1000,33 @@ videojs.Hls.prototype.drainBuffer = function(event) { |
944 | 1000 | ||
945 | event = event || {}; | 1001 | event = event || {}; |
946 | 1002 | ||
1003 | if (segmentInfo.mediaIndex > 0) { | ||
1004 | segmentTimestampOffset = videojs.Hls.Playlist.duration(segmentInfo.playlist, | ||
1005 | playlist.mediaSequence + segmentInfo.mediaIndex); | ||
1006 | } | ||
1007 | |||
947 | // If we have seeked into a non-buffered time-range, remove all buffered | 1008 | // If we have seeked into a non-buffered time-range, remove all buffered |
948 | // time-ranges because they could have been incorrectly placed originally | 1009 | // time-ranges because they could have been incorrectly placed originally |
949 | if (this.tech_.seeking() && outsideBufferedRanges) { | 1010 | if (this.tech_.seeking() && outsideBufferedRanges) { |
950 | if (hasBufferedContent) { | ||
951 | // In Chrome, it seems that too many independent buffered time-ranges can | ||
952 | // cause playback to fail to resume when seeking so just kill all of them | ||
953 | this.sourceBuffer.remove(0, Infinity); | ||
954 | return; | ||
955 | } | ||
956 | |||
957 | // If there are discontinuities in the playlist, we can't be sure of anything | 1011 | // If there are discontinuities in the playlist, we can't be sure of anything |
958 | // related to time so we reset the timestamp offset and start appending data | 1012 | // related to time so we reset the timestamp offset and start appending data |
959 | // anew on every seek | 1013 | // anew on every seek |
960 | if (segmentInfo.playlist.discontinuityStarts.length) { | 1014 | if (segmentInfo.playlist.discontinuityStarts.length) { |
961 | if (segmentInfo.mediaIndex > 0) { | ||
962 | segmentTimestampOffset = videojs.Hls.Playlist.duration(segmentInfo.playlist, segmentInfo.mediaIndex); | ||
963 | } | ||
964 | |||
965 | // Now that the forward buffer is clear, we have to set timestamp offset to | 1015 | // Now that the forward buffer is clear, we have to set timestamp offset to |
966 | // the start of the buffered region | 1016 | // the start of the buffered region |
967 | this.sourceBuffer.timestampOffset = segmentTimestampOffset; | 1017 | this.sourceBuffer.timestampOffset = segmentTimestampOffset; |
968 | } | 1018 | } |
969 | } else if (segment.discontinuity) { | 1019 | } else if (segment.discontinuity && currentBuffered.length) { |
970 | // If we aren't seeking and are crossing a discontinuity, we should set | 1020 | // If we aren't seeking and are crossing a discontinuity, we should set |
971 | // timestampOffset for new segments to be appended the end of the current | 1021 | // timestampOffset for new segments to be appended the end of the current |
972 | // buffered time-range | 1022 | // buffered time-range |
973 | this.sourceBuffer.timestampOffset = currentBuffered.end(0); | 1023 | this.sourceBuffer.timestampOffset = currentBuffered.end(0); |
1024 | } else if (!hasBufferedContent && this.tech_.currentTime() > 0.05) { | ||
1025 | // If we are trying to play at a position that is not zero but we aren't | ||
1026 | // currently seeking according to the video element | ||
1027 | this.sourceBuffer.timestampOffset = segmentTimestampOffset; | ||
974 | } | 1028 | } |
975 | 1029 | ||
976 | if (currentBuffered.length) { | ||
977 | // Chrome 45 stalls if appends overlap the playhead | ||
978 | this.sourceBuffer.appendWindowStart = Math.min(this.tech_.currentTime(), currentBuffered.end(0)); | ||
979 | } else { | ||
980 | this.sourceBuffer.appendWindowStart = 0; | ||
981 | } | ||
982 | this.pendingSegment_.buffered = this.tech_.buffered(); | 1030 | this.pendingSegment_.buffered = this.tech_.buffered(); |
983 | 1031 | ||
984 | // the segment is asynchronously added to the current buffered data | 1032 | // the segment is asynchronously added to the current buffered data | ... | ... |
... | @@ -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