f72a8799 by David LaPalomento

Merge pull request #394 from videojs/more-discontinuity

Fixed discontinuity seeking with MSE
2 parents 598e5e89 00252b91
...@@ -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({
......