Merge pull request #394 from videojs/more-discontinuity
Fixed discontinuity seeking with MSE
Showing
2 changed files
with
92 additions
and
20 deletions
... | @@ -465,7 +465,9 @@ videojs.Hls.prototype.play = function() { | ... | @@ -465,7 +465,9 @@ videojs.Hls.prototype.play = function() { |
465 | }; | 465 | }; |
466 | 466 | ||
467 | videojs.Hls.prototype.setCurrentTime = function(currentTime) { | 467 | videojs.Hls.prototype.setCurrentTime = function(currentTime) { |
468 | var buffered, i; | 468 | var |
469 | buffered = this.findCurrentBuffered_(); | ||
470 | |||
469 | if (!(this.playlists && this.playlists.media())) { | 471 | if (!(this.playlists && this.playlists.media())) { |
470 | // return immediately if the metadata is not ready yet | 472 | // return immediately if the metadata is not ready yet |
471 | return 0; | 473 | return 0; |
... | @@ -479,13 +481,9 @@ videojs.Hls.prototype.setCurrentTime = function(currentTime) { | ... | @@ -479,13 +481,9 @@ videojs.Hls.prototype.setCurrentTime = function(currentTime) { |
479 | 481 | ||
480 | // if the seek location is already buffered, continue buffering as | 482 | // if the seek location is already buffered, continue buffering as |
481 | // usual | 483 | // usual |
482 | buffered = this.tech_.buffered(); | 484 | if (buffered && buffered.length) { |
483 | for (i = 0; i < buffered.length; i++) { | ||
484 | if (this.tech_.buffered().start(i) <= currentTime && | ||
485 | this.tech_.buffered().end(i) >= currentTime) { | ||
486 | return currentTime; | 485 | return currentTime; |
487 | } | 486 | } |
488 | } | ||
489 | 487 | ||
490 | // determine the requested segment | 488 | // determine the requested segment |
491 | this.mediaIndex = this.playlists.getMediaIndexForTime_(currentTime); | 489 | this.mediaIndex = this.playlists.getMediaIndexForTime_(currentTime); |
... | @@ -518,11 +516,11 @@ videojs.Hls.prototype.seekable = function() { | ... | @@ -518,11 +516,11 @@ videojs.Hls.prototype.seekable = function() { |
518 | var currentSeekable, startOffset, media; | 516 | var currentSeekable, startOffset, media; |
519 | 517 | ||
520 | if (!this.playlists) { | 518 | if (!this.playlists) { |
521 | return videojs.createTimeRange(); | 519 | return videojs.createTimeRanges(); |
522 | } | 520 | } |
523 | media = this.playlists.media(); | 521 | media = this.playlists.media(); |
524 | if (!media) { | 522 | if (!media) { |
525 | return videojs.createTimeRange(); | 523 | return videojs.createTimeRanges(); |
526 | } | 524 | } |
527 | 525 | ||
528 | // report the seekable range relative to the earliest possible | 526 | // report the seekable range relative to the earliest possible |
... | @@ -534,7 +532,7 @@ videojs.Hls.prototype.seekable = function() { | ... | @@ -534,7 +532,7 @@ videojs.Hls.prototype.seekable = function() { |
534 | } | 532 | } |
535 | 533 | ||
536 | startOffset = this.playlists.expiredPostDiscontinuity_ - this.playlists.expiredPreDiscontinuity_; | 534 | startOffset = this.playlists.expiredPostDiscontinuity_ - this.playlists.expiredPreDiscontinuity_; |
537 | return videojs.createTimeRange(startOffset, | 535 | return videojs.createTimeRanges(startOffset, |
538 | startOffset + (currentSeekable.end(0) - currentSeekable.start(0))); | 536 | startOffset + (currentSeekable.end(0) - currentSeekable.start(0))); |
539 | }; | 537 | }; |
540 | 538 | ||
... | @@ -753,6 +751,7 @@ videojs.Hls.prototype.findCurrentBuffered_ = function() { | ... | @@ -753,6 +751,7 @@ videojs.Hls.prototype.findCurrentBuffered_ = function() { |
753 | tech = this.tech_, | 751 | tech = this.tech_, |
754 | currentTime = tech.currentTime(), | 752 | currentTime = tech.currentTime(), |
755 | buffered = this.tech_.buffered(), | 753 | buffered = this.tech_.buffered(), |
754 | ranges, | ||
756 | i; | 755 | i; |
757 | 756 | ||
758 | if (buffered && buffered.length) { | 757 | if (buffered && buffered.length) { |
... | @@ -760,22 +759,26 @@ videojs.Hls.prototype.findCurrentBuffered_ = function() { | ... | @@ -760,22 +759,26 @@ videojs.Hls.prototype.findCurrentBuffered_ = function() { |
760 | for (i = 0;i < buffered.length; i++) { | 759 | for (i = 0;i < buffered.length; i++) { |
761 | if (buffered.start(i) <= currentTime && | 760 | if (buffered.start(i) <= currentTime && |
762 | buffered.end(i) >= currentTime) { | 761 | buffered.end(i) >= currentTime) { |
763 | return videojs.createTimeRange(buffered.start(i), buffered.end(i)); | 762 | ranges = videojs.createTimeRanges(buffered.start(i), buffered.end(i)); |
763 | ranges.indexOf = i; | ||
764 | return ranges; | ||
764 | } | 765 | } |
765 | } | 766 | } |
766 | } | 767 | } |
767 | 768 | ||
768 | // Return an empty range if no ranges exist | 769 | // Return an empty range if no ranges exist |
769 | return videojs.createTimeRange(); | 770 | ranges = videojs.createTimeRanges(); |
771 | ranges.indexOf = -1; | ||
772 | return ranges; | ||
770 | }; | 773 | }; |
771 | 774 | ||
772 | /** | 775 | /** |
773 | * Determines whether there is enough video data currently in the buffer | 776 | * Determines whether there is enough video data currently in the buffer |
774 | * and downloads a new segment if the buffered time is less than the goal. | 777 | * and downloads a new segment if the buffered time is less than the goal. |
775 | * @param offset (optional) {number} the offset into the downloaded segment | 778 | * @param seekToTime (optional) {number} the offset into the downloaded segment |
776 | * to seek to, in milliseconds | 779 | * to seek to, in milliseconds |
777 | */ | 780 | */ |
778 | videojs.Hls.prototype.fillBuffer = function(offset) { | 781 | videojs.Hls.prototype.fillBuffer = function(seekToTime) { |
779 | var | 782 | var |
780 | tech = this.tech_, | 783 | tech = this.tech_, |
781 | currentTime = tech.currentTime(), | 784 | currentTime = tech.currentTime(), |
... | @@ -825,14 +828,14 @@ videojs.Hls.prototype.fillBuffer = function(offset) { | ... | @@ -825,14 +828,14 @@ videojs.Hls.prototype.fillBuffer = function(offset) { |
825 | 828 | ||
826 | // if there is plenty of content in the buffer and we're not | 829 | // if there is plenty of content in the buffer and we're not |
827 | // seeking, relax for awhile | 830 | // seeking, relax for awhile |
828 | if (typeof offset !== 'number' && bufferedTime >= videojs.Hls.GOAL_BUFFER_LENGTH) { | 831 | if (typeof seekToTime !== 'number' && bufferedTime >= videojs.Hls.GOAL_BUFFER_LENGTH) { |
829 | return; | 832 | return; |
830 | } | 833 | } |
831 | 834 | ||
832 | // resolve the segment URL relative to the playlist | 835 | // resolve the segment URL relative to the playlist |
833 | segmentUri = this.playlistUriToUrl(segment.uri); | 836 | segmentUri = this.playlistUriToUrl(segment.uri); |
834 | 837 | ||
835 | this.loadSegment(segmentUri, offset); | 838 | this.loadSegment(segmentUri, seekToTime); |
836 | }; | 839 | }; |
837 | 840 | ||
838 | videojs.Hls.prototype.playlistUriToUrl = function(segmentRelativeUrl) { | 841 | videojs.Hls.prototype.playlistUriToUrl = function(segmentRelativeUrl) { |
... | @@ -863,7 +866,7 @@ videojs.Hls.prototype.setBandwidth = function(xhr) { | ... | @@ -863,7 +866,7 @@ videojs.Hls.prototype.setBandwidth = function(xhr) { |
863 | this.tech_.trigger('bandwidthupdate'); | 866 | this.tech_.trigger('bandwidthupdate'); |
864 | }; | 867 | }; |
865 | 868 | ||
866 | videojs.Hls.prototype.loadSegment = function(segmentUri, offset) { | 869 | videojs.Hls.prototype.loadSegment = function(segmentUri, seekToTime) { |
867 | var self = this; | 870 | var self = this; |
868 | 871 | ||
869 | // request the next segment | 872 | // request the next segment |
... | @@ -910,7 +913,7 @@ videojs.Hls.prototype.loadSegment = function(segmentUri, offset) { | ... | @@ -910,7 +913,7 @@ videojs.Hls.prototype.loadSegment = function(segmentUri, offset) { |
910 | // the segment's playlist | 913 | // the segment's playlist |
911 | playlist: self.playlists.media(), | 914 | playlist: self.playlists.media(), |
912 | // optionally, a time offset to seek to within the segment | 915 | // optionally, a time offset to seek to within the segment |
913 | offset: offset, | 916 | offset: seekToTime, |
914 | // unencrypted bytes of the segment | 917 | // unencrypted bytes of the segment |
915 | bytes: null, | 918 | bytes: null, |
916 | // when a key is defined for this segment, the encrypted bytes | 919 | // when a key is defined for this segment, the encrypted bytes |
... | @@ -948,6 +951,10 @@ videojs.Hls.prototype.drainBuffer = function(event) { | ... | @@ -948,6 +951,10 @@ videojs.Hls.prototype.drainBuffer = function(event) { |
948 | segment, | 951 | segment, |
949 | decrypter, | 952 | decrypter, |
950 | segIv, | 953 | segIv, |
954 | segmentTimestampOffset = 0, | ||
955 | hasBufferedContent = (this.tech_.buffered().length !== 0), | ||
956 | currentBuffered = this.findCurrentBuffered_(), | ||
957 | outsideBufferedRanges = !(currentBuffered && currentBuffered.length), | ||
951 | // ptsTime, | 958 | // ptsTime, |
952 | segmentBuffer = this.segmentBuffer_; | 959 | segmentBuffer = this.segmentBuffer_; |
953 | 960 | ||
... | @@ -1032,8 +1039,33 @@ videojs.Hls.prototype.drainBuffer = function(event) { | ... | @@ -1032,8 +1039,33 @@ videojs.Hls.prototype.drainBuffer = function(event) { |
1032 | // this.tech_.el().vjs_discontinuity(); | 1039 | // this.tech_.el().vjs_discontinuity(); |
1033 | // } | 1040 | // } |
1034 | 1041 | ||
1035 | if (segment.discontinuity) { | 1042 | // If we have seeked into a non-buffered time-range, remove all buffered |
1036 | this.sourceBuffer.timestampOffset = this.findCurrentBuffered_().end(0); | 1043 | // time-ranges because they could have been incorrectly placed originally |
1044 | if (this.tech_.seeking() && outsideBufferedRanges) { | ||
1045 | if (hasBufferedContent) { | ||
1046 | // In Chrome, it seems that too many independent buffered time-ranges can | ||
1047 | // cause playback to fail to resume when seeking so just kill all of them | ||
1048 | this.sourceBuffer.remove(0, Infinity); | ||
1049 | return; | ||
1050 | } | ||
1051 | |||
1052 | // If there are discontinuities in the playlist, we can't be sure of anything | ||
1053 | // related to time so we reset the timestamp offset and start appending data | ||
1054 | // anew on every seek | ||
1055 | if (segmentInfo.playlist.discontinuityStarts.length) { | ||
1056 | if (segmentInfo.mediaIndex > 0) { | ||
1057 | segmentTimestampOffset = videojs.Hls.Playlist.duration(segmentInfo.playlist, 0, segmentInfo.mediaIndex); | ||
1058 | } | ||
1059 | |||
1060 | // Now that the forward buffer is clear, we have to set timestamp offset to | ||
1061 | // the start of the buffered region | ||
1062 | this.sourceBuffer.timestampOffset = segmentTimestampOffset; | ||
1063 | } | ||
1064 | } else if (segment.discontinuity) { | ||
1065 | // If we aren't seeking and are crossing a discontinuity, we should set | ||
1066 | // timestampOffset for new segments to be appended the end of the current | ||
1067 | // buffered time-range | ||
1068 | this.sourceBuffer.timestampOffset = currentBuffered.end(0); | ||
1037 | } | 1069 | } |
1038 | 1070 | ||
1039 | this.sourceBuffer.appendBuffer(bytes); | 1071 | this.sourceBuffer.appendBuffer(bytes); | ... | ... |
... | @@ -2283,8 +2283,8 @@ test('calls mediaSource\'s timestampOffset on discontinuity', function() { | ... | @@ -2283,8 +2283,8 @@ test('calls mediaSource\'s timestampOffset on discontinuity', function() { |
2283 | '#EXTINF:10,0\n' + | 2283 | '#EXTINF:10,0\n' + |
2284 | '2.ts\n' + | 2284 | '2.ts\n' + |
2285 | '#EXT-X-ENDLIST\n'); | 2285 | '#EXT-X-ENDLIST\n'); |
2286 | standardXHRResponse(requests.pop()); // 1.ts | ||
2287 | player.tech_.hls.sourceBuffer.timestampOffset = 0; | 2286 | player.tech_.hls.sourceBuffer.timestampOffset = 0; |
2287 | standardXHRResponse(requests.pop()); // 1.ts | ||
2288 | 2288 | ||
2289 | equal(player.tech_.hls.sourceBuffer.timestampOffset, 0, 'timestampOffset starts at zero'); | 2289 | equal(player.tech_.hls.sourceBuffer.timestampOffset, 0, 'timestampOffset starts at zero'); |
2290 | 2290 | ||
... | @@ -2295,6 +2295,46 @@ test('calls mediaSource\'s timestampOffset on discontinuity', function() { | ... | @@ -2295,6 +2295,46 @@ test('calls mediaSource\'s timestampOffset on discontinuity', function() { |
2295 | equal(player.tech_.hls.sourceBuffer.timestampOffset, 10, 'timestampOffset set after discontinuity'); | 2295 | equal(player.tech_.hls.sourceBuffer.timestampOffset, 10, 'timestampOffset set after discontinuity'); |
2296 | }); | 2296 | }); |
2297 | 2297 | ||
2298 | test('sets timestampOffset when seeking with discontinuities', function() { | ||
2299 | var removes = [], timeRange = videojs.createTimeRange(0, 10); | ||
2300 | |||
2301 | player.src({ | ||
2302 | src: 'discontinuity.m3u8', | ||
2303 | type: 'application/vnd.apple.mpegurl' | ||
2304 | }); | ||
2305 | openMediaSource(player); | ||
2306 | player.play(); | ||
2307 | player.tech_.buffered = function() { | ||
2308 | return timeRange; | ||
2309 | }; | ||
2310 | player.tech_.seeking = function (){ | ||
2311 | return true; | ||
2312 | }; | ||
2313 | |||
2314 | requests.pop().respond(200, null, | ||
2315 | '#EXTM3U\n' + | ||
2316 | '#EXTINF:10,0\n' + | ||
2317 | '1.ts\n' + | ||
2318 | '#EXTINF:10,0\n' + | ||
2319 | '2.ts\n' + | ||
2320 | '#EXT-X-DISCONTINUITY\n' + | ||
2321 | '#EXTINF:10,0\n' + | ||
2322 | '3.ts\n' + | ||
2323 | '#EXT-X-ENDLIST\n'); | ||
2324 | player.tech_.hls.sourceBuffer.timestampOffset = 0; | ||
2325 | player.tech_.hls.sourceBuffer.remove = function(start, end) { | ||
2326 | timeRange = videojs.createTimeRange(); | ||
2327 | removes.push([start, end]); | ||
2328 | }; | ||
2329 | |||
2330 | player.currentTime(21); | ||
2331 | clock.tick(1); | ||
2332 | equal(requests.shift().aborted, true, 'aborted first request'); | ||
2333 | standardXHRResponse(requests.pop()); // 3.ts | ||
2334 | clock.tick(1000); | ||
2335 | equal(player.tech_.hls.sourceBuffer.timestampOffset, 20, 'timestampOffset starts at zero'); | ||
2336 | equal(removes.length, 1, 'remove was called'); | ||
2337 | }); | ||
2298 | 2338 | ||
2299 | test('can seek before the source buffer opens', function() { | 2339 | test('can seek before the source buffer opens', function() { |
2300 | player.src({ | 2340 | player.src({ | ... | ... |
-
Please register or sign in to post a comment