2633a46d by David LaPalomento

Ensure overlapping buffered edges are not interpreted as updates

If the old and new buffered ranges have a shared start or end point, that edge should not be interpreted as a new buffered boundary. Fix up a number of the tests. Some tests are still failing.
1 parent 5ee4363a
...@@ -187,20 +187,20 @@ ...@@ -187,20 +187,20 @@
187 * active media playlist. When called with a single argument, 187 * active media playlist. When called with a single argument,
188 * triggers the playlist loader to asynchronously switch to the 188 * triggers the playlist loader to asynchronously switch to the
189 * specified media playlist. Calling this method while the 189 * specified media playlist. Calling this method while the
190 * loader is in the HAVE_NOTHING or HAVE_MASTER states causes an 190 * loader is in the HAVE_NOTHING causes an error to be emitted
191 * error to be emitted but otherwise has no effect. 191 * but otherwise has no effect.
192 * @param playlist (optional) {object} the parsed media playlist 192 * @param playlist (optional) {object} the parsed media playlist
193 * object to switch to 193 * object to switch to
194 */ 194 */
195 loader.media = function(playlist) { 195 loader.media = function(playlist) {
196 var mediaChange = false; 196 var startingState = loader.state, mediaChange;
197 // getter 197 // getter
198 if (!playlist) { 198 if (!playlist) {
199 return loader.media_; 199 return loader.media_;
200 } 200 }
201 201
202 // setter 202 // setter
203 if (loader.state === 'HAVE_NOTHING' || loader.state === 'HAVE_MASTER') { 203 if (loader.state === 'HAVE_NOTHING') {
204 throw new Error('Cannot switch media playlist from ' + loader.state); 204 throw new Error('Cannot switch media playlist from ' + loader.state);
205 } 205 }
206 206
...@@ -213,7 +213,7 @@ ...@@ -213,7 +213,7 @@
213 playlist = loader.master.playlists[playlist]; 213 playlist = loader.master.playlists[playlist];
214 } 214 }
215 215
216 mediaChange = playlist.uri !== loader.media_.uri; 216 mediaChange = !loader.media_ || playlist.uri !== loader.media_.uri;
217 217
218 // switch to fully loaded playlists immediately 218 // switch to fully loaded playlists immediately
219 if (loader.master.playlists[playlist.uri].endList) { 219 if (loader.master.playlists[playlist.uri].endList) {
...@@ -258,7 +258,17 @@ ...@@ -258,7 +258,17 @@
258 withCredentials: withCredentials 258 withCredentials: withCredentials
259 }, function(error, request) { 259 }, function(error, request) {
260 haveMetadata(error, request, playlist.uri); 260 haveMetadata(error, request, playlist.uri);
261
262 if (error) {
263 return;
264 }
265
266 // fire loadedmetadata the first time a media playlist is loaded
267 if (startingState === 'HAVE_MASTER') {
268 loader.trigger('loadedmetadata');
269 } else {
261 loader.trigger('mediachange'); 270 loader.trigger('mediachange');
271 }
262 }); 272 });
263 }; 273 };
264 274
...@@ -320,19 +330,13 @@ ...@@ -320,19 +330,13 @@
320 loader.master.playlists[loader.master.playlists[i].uri] = loader.master.playlists[i]; 330 loader.master.playlists[loader.master.playlists[i].uri] = loader.master.playlists[i];
321 } 331 }
322 332
323 request = xhr({ 333 loader.trigger('loadedplaylist');
324 uri: resolveUrl(srcUrl, parser.manifest.playlists[0].uri), 334 if (!request) {
325 withCredentials: withCredentials 335 // no media playlist was specifically selected so start
326 }, function(error, request) { 336 // from the first listed one
327 // pass along the URL specified in the master playlist 337 loader.media(parser.manifest.playlists[0]);
328 haveMetadata(error,
329 request,
330 parser.manifest.playlists[0].uri);
331 if (!error) {
332 loader.trigger('loadedmetadata');
333 } 338 }
334 }); 339 return;
335 return loader.trigger('loadedplaylist');
336 } 340 }
337 341
338 // loaded a media playlist 342 // loaded a media playlist
...@@ -468,8 +472,8 @@ ...@@ -468,8 +472,8 @@
468 } 472 }
469 473
470 // the playback position is outside the range of available 474 // the playback position is outside the range of available
471 // segments so return the last one 475 // segments so return the length
472 return this.media_.segments.length - 1; 476 return this.media_.segments.length;
473 }; 477 };
474 478
475 videojs.Hls.PlaylistLoader = PlaylistLoader; 479 videojs.Hls.PlaylistLoader = PlaylistLoader;
......
...@@ -190,7 +190,8 @@ videojs.Hls.prototype.src = function(src) { ...@@ -190,7 +190,8 @@ videojs.Hls.prototype.src = function(src) {
190 var updatedPlaylist = this.playlists.media(); 190 var updatedPlaylist = this.playlists.media();
191 191
192 if (!updatedPlaylist) { 192 if (!updatedPlaylist) {
193 // do nothing before an initial media playlist has been activated 193 // select the initial variant
194 this.playlists.media(this.selectPlaylist());
194 return; 195 return;
195 } 196 }
196 197
...@@ -254,7 +255,7 @@ videojs.Hls.prototype.handleSourceOpen = function() { ...@@ -254,7 +255,7 @@ videojs.Hls.prototype.handleSourceOpen = function() {
254 255
255 // Returns the array of time range edge objects that were additively 256 // Returns the array of time range edge objects that were additively
256 // modified between two TimeRanges. 257 // modified between two TimeRanges.
257 var bufferedAdditions = function(original, update) { 258 videojs.Hls.bufferedAdditions_ = function(original, update) {
258 var result = [], edges = [], 259 var result = [], edges = [],
259 i, inOriginalRanges; 260 i, inOriginalRanges;
260 261
...@@ -271,6 +272,15 @@ var bufferedAdditions = function(original, update) { ...@@ -271,6 +272,15 @@ var bufferedAdditions = function(original, update) {
271 var leftTime, rightTime; 272 var leftTime, rightTime;
272 leftTime = left.start !== undefined ? left.start : left.end; 273 leftTime = left.start !== undefined ? left.start : left.end;
273 rightTime = right.start !== undefined ? right.start : right.end; 274 rightTime = right.start !== undefined ? right.start : right.end;
275
276 // when two times are equal, ensure the original edge covers the
277 // update
278 if (leftTime === rightTime) {
279 if (left.original) {
280 return left.start !== undefined ? -1 : 1;
281 }
282 return right.start !== undefined ? -1 : 1;
283 }
274 return leftTime - rightTime; 284 return leftTime - rightTime;
275 }); 285 });
276 286
...@@ -349,7 +359,7 @@ videojs.Hls.prototype.setupSourceBuffer_ = function() { ...@@ -349,7 +359,7 @@ videojs.Hls.prototype.setupSourceBuffer_ = function() {
349 // annotate the segment with any start and end time information 359 // annotate the segment with any start and end time information
350 // added by the media processing 360 // added by the media processing
351 segment = segmentInfo.playlist.segments[segmentInfo.mediaIndex]; 361 segment = segmentInfo.playlist.segments[segmentInfo.mediaIndex];
352 timelineUpdates = bufferedAdditions(segmentInfo.buffered, 362 timelineUpdates = videojs.Hls.bufferedAdditions_(segmentInfo.buffered,
353 this.tech_.buffered()); 363 this.tech_.buffered());
354 timelineUpdates.forEach(function(update) { 364 timelineUpdates.forEach(function(update) {
355 if (update.start !== undefined) { 365 if (update.start !== undefined) {
...@@ -1112,15 +1122,16 @@ videojs.Hls.prototype.drainBuffer = function(event) { ...@@ -1112,15 +1122,16 @@ videojs.Hls.prototype.drainBuffer = function(event) {
1112 this.sourceBuffer.timestampOffset = currentBuffered.end(0); 1122 this.sourceBuffer.timestampOffset = currentBuffered.end(0);
1113 } 1123 }
1114 1124
1115 // the segment is asynchronously added to the current buffered data
1116 if (currentBuffered.length) { 1125 if (currentBuffered.length) {
1117 this.sourceBuffer.videoBuffer_.appendWindowStart = Math.min(this.tech_.currentTime(), currentBuffered.end(0)); 1126 // Chrome 45 stalls if appends overlap the playhead
1118 } else if (this.sourceBuffer.videoBuffer_) { 1127 this.sourceBuffer.appendWindowStart = Math.min(this.tech_.currentTime(), currentBuffered.end(0));
1119 this.sourceBuffer.videoBuffer_.appendWindowStart = 0; 1128 } else {
1129 this.sourceBuffer.appendWindowStart = 0;
1120 } 1130 }
1121 this.pendingSegment_ = segmentBuffer.shift(); 1131 this.pendingSegment_ = segmentBuffer.shift();
1122 this.pendingSegment_.buffered = this.tech_.buffered(); 1132 this.pendingSegment_.buffered = this.tech_.buffered();
1123 1133
1134 // the segment is asynchronously added to the current buffered data
1124 this.sourceBuffer.appendBuffer(bytes); 1135 this.sourceBuffer.appendBuffer(bytes);
1125 }; 1136 };
1126 1137
......
...@@ -69,13 +69,16 @@ ...@@ -69,13 +69,16 @@
69 }); 69 });
70 70
71 test('moves to HAVE_MASTER after loading a master playlist', function() { 71 test('moves to HAVE_MASTER after loading a master playlist', function() {
72 var loader = new videojs.Hls.PlaylistLoader('master.m3u8'); 72 var loader = new videojs.Hls.PlaylistLoader('master.m3u8'), state;
73 loader.on('loadedplaylist', function() {
74 state = loader.state;
75 });
73 requests.pop().respond(200, null, 76 requests.pop().respond(200, null,
74 '#EXTM3U\n' + 77 '#EXTM3U\n' +
75 '#EXT-X-STREAM-INF:\n' + 78 '#EXT-X-STREAM-INF:\n' +
76 'media.m3u8\n'); 79 'media.m3u8\n');
77 ok(loader.master, 'the master playlist is available'); 80 ok(loader.master, 'the master playlist is available');
78 strictEqual(loader.state, 'HAVE_MASTER', 'the state is correct'); 81 strictEqual(state, 'HAVE_MASTER', 'the state at loadedplaylist correct');
79 }); 82 });
80 83
81 test('jumps to HAVE_METADATA when initialized with a media playlist', function() { 84 test('jumps to HAVE_METADATA when initialized with a media playlist', function() {
...@@ -453,6 +456,20 @@ ...@@ -453,6 +456,20 @@
453 'updated the active media'); 456 'updated the active media');
454 }); 457 });
455 458
459 test('can switch playlists immediately after the master is downloaded', function() {
460 var loader = new videojs.Hls.PlaylistLoader('master.m3u8');
461 loader.on('loadedplaylist', function() {
462 loader.media('high.m3u8');
463 });
464 requests.pop().respond(200, null,
465 '#EXTM3U\n' +
466 '#EXT-X-STREAM-INF:BANDWIDTH=1\n' +
467 'low.m3u8\n' +
468 '#EXT-X-STREAM-INF:BANDWIDTH=2\n' +
469 'high.m3u8\n');
470 equal(requests[0].url, urlTo('high.m3u8'), 'switched variants immediately');
471 });
472
456 test('can switch media playlists based on URI', function() { 473 test('can switch media playlists based on URI', function() {
457 var loader = new videojs.Hls.PlaylistLoader('master.m3u8'); 474 var loader = new videojs.Hls.PlaylistLoader('master.m3u8');
458 requests.pop().respond(200, null, 475 requests.pop().respond(200, null,
...@@ -624,9 +641,6 @@ ...@@ -624,9 +641,6 @@
624 'low.m3u8\n' + 641 'low.m3u8\n' +
625 '#EXT-X-STREAM-INF:BANDWIDTH=2\n' + 642 '#EXT-X-STREAM-INF:BANDWIDTH=2\n' +
626 'high.m3u8\n'); 643 'high.m3u8\n');
627 throws(function() {
628 loader.media('high.m3u8');
629 }, 'throws an error from HAVE_MASTER');
630 }); 644 });
631 645
632 test('throws an error if a switch to an unrecognized playlist is requested', function() { 646 test('throws an error if a switch to an unrecognized playlist is requested', function() {
...@@ -757,10 +771,8 @@ ...@@ -757,10 +771,8 @@
757 '#EXTINF:5,\n' + 771 '#EXTINF:5,\n' +
758 '1.ts\n' + 772 '1.ts\n' +
759 '#EXT-X-ENDLIST\n'); 773 '#EXT-X-ENDLIST\n');
760 equal(loader.getMediaIndexForTime_(4), 0, 'rounds down exact matches'); 774 equal(loader.getMediaIndexForTime_(4), 1, 'rounds up exact matches');
761 equal(loader.getMediaIndexForTime_(3.7), 0, 'rounds down'); 775 equal(loader.getMediaIndexForTime_(3.7), 0, 'rounds down');
762 // FIXME: the test below should pass for HLSv3
763 //equal(loader.getMediaIndexForTime_(4.2), 0, 'rounds down');
764 equal(loader.getMediaIndexForTime_(4.5), 1, 'rounds up at 0.5'); 776 equal(loader.getMediaIndexForTime_(4.5), 1, 'rounds up at 0.5');
765 }); 777 });
766 778
......
...@@ -686,73 +686,59 @@ test('downloads media playlists after loading the master', function() { ...@@ -686,73 +686,59 @@ test('downloads media playlists after loading the master', function() {
686 }); 686 });
687 openMediaSource(player); 687 openMediaSource(player);
688 688
689 // set bandwidth to an appropriate number so we don't switch 689 player.tech_.hls.bandwidth = 20e10;
690 player.tech_.hls.bandwidth = 200000;
691 standardXHRResponse(requests[0]); 690 standardXHRResponse(requests[0]);
692 standardXHRResponse(requests[1]); 691 standardXHRResponse(requests[1]);
693 standardXHRResponse(requests[2]); 692 standardXHRResponse(requests[2]);
694 693
695 strictEqual(requests[0].url, 'manifest/master.m3u8', 'master playlist requested'); 694 strictEqual(requests[0].url, 'manifest/master.m3u8', 'master playlist requested');
696 strictEqual(requests[1].url, 695 strictEqual(requests[1].url,
697 absoluteUrl('manifest/media.m3u8'), 696 absoluteUrl('manifest/media3.m3u8'),
698 'media playlist requested'); 697 'media playlist requested');
699 strictEqual(requests[2].url, 698 strictEqual(requests[2].url,
700 absoluteUrl('manifest/media-00001.ts'), 699 absoluteUrl('manifest/media3-00001.ts'),
701 'first segment requested'); 700 'first segment requested');
702 }); 701 });
703 702
704 test('upshift if initial bandwidth is high', function() { 703 test('upshifts if the initial bandwidth hint is high', function() {
705 player.src({ 704 player.src({
706 src: 'manifest/master.m3u8', 705 src: 'manifest/master.m3u8',
707 type: 'application/vnd.apple.mpegurl' 706 type: 'application/vnd.apple.mpegurl'
708 }); 707 });
709 openMediaSource(player); 708 openMediaSource(player);
710 709
710 player.tech_.hls.bandwidth = 10e20;
711 standardXHRResponse(requests[0]); 711 standardXHRResponse(requests[0]);
712
713 player.tech_.hls.playlists.setBandwidth = function() {
714 player.tech_.hls.playlists.bandwidth = 1000000000;
715 };
716
717 standardXHRResponse(requests[1]); 712 standardXHRResponse(requests[1]);
718 standardXHRResponse(requests[2]); 713 standardXHRResponse(requests[2]);
719 714
720 standardXHRResponse(requests[3]);
721
722 strictEqual(requests[0].url, 'manifest/master.m3u8', 'master playlist requested'); 715 strictEqual(requests[0].url, 'manifest/master.m3u8', 'master playlist requested');
723 strictEqual(requests[1].url, 716 strictEqual(requests[1].url,
724 absoluteUrl('manifest/media.m3u8'),
725 'media playlist requested');
726 strictEqual(requests[2].url,
727 absoluteUrl('manifest/media3.m3u8'), 717 absoluteUrl('manifest/media3.m3u8'),
728 'media playlist requested'); 718 'media playlist requested');
729 strictEqual(requests[3].url, 719 strictEqual(requests[2].url,
730 absoluteUrl('manifest/media3-00001.ts'), 720 absoluteUrl('manifest/media3-00001.ts'),
731 'first segment requested'); 721 'first segment requested');
732 }); 722 });
733 723
734 test('dont downshift if bandwidth is low', function() { 724 test('downshifts if the initial bandwidth hint is low', function() {
735 player.src({ 725 player.src({
736 src: 'manifest/master.m3u8', 726 src: 'manifest/master.m3u8',
737 type: 'application/vnd.apple.mpegurl' 727 type: 'application/vnd.apple.mpegurl'
738 }); 728 });
739 openMediaSource(player); 729 openMediaSource(player);
740 730
731 player.tech_.hls.bandwidth = 100;
741 standardXHRResponse(requests[0]); 732 standardXHRResponse(requests[0]);
742
743 player.tech_.hls.playlists.setBandwidth = function() {
744 player.tech_.hls.playlists.bandwidth = 100;
745 };
746
747 standardXHRResponse(requests[1]); 733 standardXHRResponse(requests[1]);
748 standardXHRResponse(requests[2]); 734 standardXHRResponse(requests[2]);
749 735
750 strictEqual(requests[0].url, 'manifest/master.m3u8', 'master playlist requested'); 736 strictEqual(requests[0].url, 'manifest/master.m3u8', 'master playlist requested');
751 strictEqual(requests[1].url, 737 strictEqual(requests[1].url,
752 absoluteUrl('manifest/media.m3u8'), 738 absoluteUrl('manifest/media1.m3u8'),
753 'media playlist requested'); 739 'media playlist requested');
754 strictEqual(requests[2].url, 740 strictEqual(requests[2].url,
755 absoluteUrl('manifest/media-00001.ts'), 741 absoluteUrl('manifest/media1-00001.ts'),
756 'first segment requested'); 742 'first segment requested');
757 }); 743 });
758 744
...@@ -822,7 +808,7 @@ test('buffer checks are noops when only the master is ready', function() { ...@@ -822,7 +808,7 @@ test('buffer checks are noops when only the master is ready', function() {
822 808
823 strictEqual(1, requests.length, 'one request was made'); 809 strictEqual(1, requests.length, 'one request was made');
824 strictEqual(requests[0].url, 810 strictEqual(requests[0].url,
825 absoluteUrl('manifest/media.m3u8'), 811 absoluteUrl('manifest/media1.m3u8'),
826 'media playlist requested'); 812 'media playlist requested');
827 }); 813 });
828 814
...@@ -876,11 +862,9 @@ test('selects a playlist after segment downloads', function() { ...@@ -876,11 +862,9 @@ test('selects a playlist after segment downloads', function() {
876 return player.tech_.hls.playlists.master.playlists[0]; 862 return player.tech_.hls.playlists.master.playlists[0];
877 }; 863 };
878 864
879 standardXHRResponse(requests[0]); 865 standardXHRResponse(requests[0]); // master
880 866 standardXHRResponse(requests[1]); // media
881 player.tech_.hls.bandwidth = 3000000; 867 standardXHRResponse(requests[2]); // segment
882 standardXHRResponse(requests[1]);
883 standardXHRResponse(requests[2]);
884 868
885 strictEqual(calls, 2, 'selects after the initial segment'); 869 strictEqual(calls, 2, 'selects after the initial segment');
886 player.currentTime = function() { 870 player.currentTime = function() {
...@@ -889,6 +873,7 @@ test('selects a playlist after segment downloads', function() { ...@@ -889,6 +873,7 @@ test('selects a playlist after segment downloads', function() {
889 player.buffered = function() { 873 player.buffered = function() {
890 return videojs.createTimeRange(0, 2); 874 return videojs.createTimeRange(0, 2);
891 }; 875 };
876 player.tech_.hls.sourceBuffer.trigger('updateend');
892 player.tech_.hls.checkBuffer_(); 877 player.tech_.hls.checkBuffer_();
893 878
894 standardXHRResponse(requests[3]); 879 standardXHRResponse(requests[3]);
...@@ -896,9 +881,7 @@ test('selects a playlist after segment downloads', function() { ...@@ -896,9 +881,7 @@ test('selects a playlist after segment downloads', function() {
896 strictEqual(calls, 3, 'selects after additional segments'); 881 strictEqual(calls, 3, 'selects after additional segments');
897 }); 882 });
898 883
899 test('moves to the next segment if there is a network error', function() { 884 test('reports an error if a segment is unreachable', function() {
900 var mediaIndex;
901
902 player.src({ 885 player.src({
903 src: 'manifest/master.m3u8', 886 src: 'manifest/master.m3u8',
904 type: 'application/vnd.apple.mpegurl' 887 type: 'application/vnd.apple.mpegurl'
...@@ -909,62 +892,8 @@ test('moves to the next segment if there is a network error', function() { ...@@ -909,62 +892,8 @@ test('moves to the next segment if there is a network error', function() {
909 standardXHRResponse(requests[0]); 892 standardXHRResponse(requests[0]);
910 standardXHRResponse(requests[1]); 893 standardXHRResponse(requests[1]);
911 894
912 mediaIndex = player.tech_.hls.mediaIndex;
913 player.trigger('timeupdate');
914
915 requests[2].respond(400); 895 requests[2].respond(400);
916 strictEqual(mediaIndex + 1, player.tech_.hls.mediaIndex, 'media index is incremented'); 896 strictEqual(player.tech_.hls.mediaSource.error_, 'network', 'network error is triggered');
917 });
918
919 test('updates playlist timeline offsets if it detects a desynchronization', function() {
920 var buffered = [], currentTime = 0;
921
922 player.src({
923 src: 'manifest/master.m3u8',
924 type: 'application/vnd.apple.mpegurl'
925 });
926 openMediaSource(player);
927 standardXHRResponse(requests.shift()); // master
928 requests.shift().respond(200, null,
929 '#EXTM3U\n' +
930 '#EXT-X-MEDIA-SEQUENCE:2\n' +
931 '#EXTINF:10,\n' +
932 '2.ts\n' +
933 '#EXTINF:10,\n' +
934 '3.ts\n'); // media
935 player.tech_.buffered = function() { return videojs.createTimeRange(buffered); };
936 player.tech_.currentTime = function() { return currentTime; };
937 player.tech_.paused = function() { return false; };
938 player.tech_.trigger('play');
939 clock.tick(1);
940 standardXHRResponse(requests.shift()); // segment 0
941 equal(player.tech_.hls.mediaIndex, 1, 'incremented mediaIndex');
942
943 player.tech_.hls.sourceBuffer.trigger('updateend');
944 buffered.push([0, 10]);
945
946 // force a playlist switch
947 player.tech_.hls.playlists.media('media1.m3u8');
948 requests = requests.filter(function(request) {
949 return !request.aborted;
950 });
951 requests.shift().respond(200, null,
952 '#EXTM3U\n' +
953 '#EXT-X-MEDIA-SEQUENCE:9999\n' +
954 '#EXTINF:10,\n' +
955 '3.ts\n' +
956 '#EXTINF:10,\n' +
957 '4.ts\n' +
958 '#EXTINF:10,\n' +
959 '5.ts\n'); // media1
960 player.tech_.hls.checkBuffer_();
961 standardXHRResponse(requests.shift());
962
963 buffered.push([20, 30]);
964 currentTime = 8;
965
966 player.tech_.hls.sourceBuffer.trigger('updateend');
967 equal(player.tech_.hls.mediaIndex, 0, 'prepared to request the missing segment');
968 }); 897 });
969 898
970 test('updates the duration after switching playlists', function() { 899 test('updates the duration after switching playlists', function() {
...@@ -974,19 +903,22 @@ test('updates the duration after switching playlists', function() { ...@@ -974,19 +903,22 @@ test('updates the duration after switching playlists', function() {
974 type: 'application/vnd.apple.mpegurl' 903 type: 'application/vnd.apple.mpegurl'
975 }); 904 });
976 openMediaSource(player); 905 openMediaSource(player);
906
907 player.tech_.hls.bandwidth = 1e20;
908 standardXHRResponse(requests[0]); // master
909 standardXHRResponse(requests[1]); // media3
910
977 player.tech_.hls.selectPlaylist = function() { 911 player.tech_.hls.selectPlaylist = function() {
978 selectedPlaylist = true; 912 selectedPlaylist = true;
979 913
980 // this duraiton should be overwritten by the playlist change 914 // this duration should be overwritten by the playlist change
981 player.tech_.hls.mediaSource.duration = -Infinity; 915 player.tech_.hls.mediaSource.duration = -Infinity;
982 916
983 return player.tech_.hls.playlists.master.playlists[1]; 917 return player.tech_.hls.playlists.master.playlists[1];
984 }; 918 };
985 919
986 standardXHRResponse(requests[0]); 920 standardXHRResponse(requests[2]); // segment 0
987 standardXHRResponse(requests[1]); 921 standardXHRResponse(requests[3]); // media1
988 standardXHRResponse(requests[2]);
989 standardXHRResponse(requests[3]);
990 ok(selectedPlaylist, 'selected playlist'); 922 ok(selectedPlaylist, 'selected playlist');
991 ok(player.tech_.hls.mediaSource.duration !== -Infinity, 'updates the duration'); 923 ok(player.tech_.hls.mediaSource.duration !== -Infinity, 'updates the duration');
992 }); 924 });
...@@ -1058,21 +990,6 @@ test('selects a playlist below the current bandwidth', function() { ...@@ -1058,21 +990,6 @@ test('selects a playlist below the current bandwidth', function() {
1058 'the low bitrate stream is selected'); 990 'the low bitrate stream is selected');
1059 }); 991 });
1060 992
1061 test('scales the bandwidth estimate for the first segment', function() {
1062 player.src({
1063 src: 'manifest/master.m3u8',
1064 type: 'application/vnd.apple.mpegurl'
1065 });
1066 openMediaSource(player);
1067
1068 requests[0].bandwidth = 500;
1069 requests.shift().respond(200, null,
1070 '#EXTM3U\n' +
1071 '#EXT-X-PLAYLIST-TYPE:VOD\n' +
1072 '#EXT-X-TARGETDURATION:10\n');
1073 equal(player.tech_.hls.bandwidth, 500 * 5, 'scaled the bandwidth estimate by 5');
1074 });
1075
1076 test('allows initial bandwidth to be provided', function() { 993 test('allows initial bandwidth to be provided', function() {
1077 player.src({ 994 player.src({
1078 src: 'manifest/master.m3u8', 995 src: 'manifest/master.m3u8',
...@@ -1242,6 +1159,7 @@ test('downloads the next segment if the buffer is getting low', function() { ...@@ -1242,6 +1159,7 @@ test('downloads the next segment if the buffer is getting low', function() {
1242 player.tech_.buffered = function() { 1159 player.tech_.buffered = function() {
1243 return videojs.createTimeRange(0, 19.999); 1160 return videojs.createTimeRange(0, 19.999);
1244 }; 1161 };
1162 player.tech_.hls.sourceBuffer.trigger('updateend');
1245 player.tech_.hls.checkBuffer_(); 1163 player.tech_.hls.checkBuffer_();
1246 1164
1247 standardXHRResponse(requests[2]); 1165 standardXHRResponse(requests[2]);
...@@ -1253,8 +1171,12 @@ test('downloads the next segment if the buffer is getting low', function() { ...@@ -1253,8 +1171,12 @@ test('downloads the next segment if the buffer is getting low', function() {
1253 }); 1171 });
1254 1172
1255 test('buffers based on the correct TimeRange if multiple ranges exist', function() { 1173 test('buffers based on the correct TimeRange if multiple ranges exist', function() {
1174 var currentTime, buffered;
1256 player.tech_.currentTime = function() { 1175 player.tech_.currentTime = function() {
1257 return 8; 1176 return currentTime;
1177 };
1178 player.tech_.buffered = function() {
1179 return videojs.createTimeRange(buffered);
1258 }; 1180 };
1259 1181
1260 player.src({ 1182 player.src({
...@@ -1262,37 +1184,28 @@ test('buffers based on the correct TimeRange if multiple ranges exist', function ...@@ -1262,37 +1184,28 @@ test('buffers based on the correct TimeRange if multiple ranges exist', function
1262 type: 'application/vnd.apple.mpegurl' 1184 type: 'application/vnd.apple.mpegurl'
1263 }); 1185 });
1264 openMediaSource(player); 1186 openMediaSource(player);
1265 player.tech_.buffered = function() { 1187 currentTime = 8;
1266 return videojs.createTimeRange([[0, 10], [50, 160]]); 1188 buffered = [[0, 10], [20, 40]];
1267 };
1268 1189
1269 standardXHRResponse(requests[0]); 1190 standardXHRResponse(requests[0]);
1270 standardXHRResponse(requests[1]); 1191 standardXHRResponse(requests[1]);
1271 1192
1272 strictEqual(requests.length, 2, 'made two requests'); 1193 strictEqual(requests.length, 2, 'made two requests');
1273 strictEqual(requests[1].url, 1194 strictEqual(requests[1].url,
1274 absoluteUrl('manifest/media-00001.ts'), 1195 absoluteUrl('manifest/media-00002.ts'),
1275 'made segment request'); 1196 'made segment request');
1276 1197
1277 player.tech_.currentTime = function() { 1198 currentTime = 22;
1278 return 55; 1199 player.tech_.hls.sourceBuffer.trigger('updateend');
1279 };
1280
1281 player.tech_.hls.checkBuffer_(); 1200 player.tech_.hls.checkBuffer_();
1282
1283 strictEqual(requests.length, 2, 'made no additional requests'); 1201 strictEqual(requests.length, 2, 'made no additional requests');
1284 1202
1285 player.tech_.currentTime = function() { 1203 buffered = [[0, 10], [20, 30]];
1286 return 134;
1287 };
1288
1289 player.tech_.hls.checkBuffer_(); 1204 player.tech_.hls.checkBuffer_();
1290 standardXHRResponse(requests[2]); 1205 standardXHRResponse(requests[2]);
1291
1292 strictEqual(requests.length, 3, 'made three requests'); 1206 strictEqual(requests.length, 3, 'made three requests');
1293
1294 strictEqual(requests[2].url, 1207 strictEqual(requests[2].url,
1295 absoluteUrl('manifest/media-00002.ts'), 1208 absoluteUrl('manifest/media-00004.ts'),
1296 'made segment request'); 1209 'made segment request');
1297 }); 1210 });
1298 1211
...@@ -1334,15 +1247,8 @@ test('only appends one segment at a time', function() { ...@@ -1334,15 +1247,8 @@ test('only appends one segment at a time', function() {
1334 standardXHRResponse(requests.pop()); // media.m3u8 1247 standardXHRResponse(requests.pop()); // media.m3u8
1335 standardXHRResponse(requests.pop()); // segment 0 1248 standardXHRResponse(requests.pop()); // segment 0
1336 1249
1337 player.tech_.hls.sourceBuffer.updating = true;
1338 player.tech_.hls.sourceBuffer.appendBuffer = function() {
1339 appends++;
1340 };
1341
1342 player.tech_.hls.checkBuffer_(); 1250 player.tech_.hls.checkBuffer_();
1343 standardXHRResponse(requests.pop()); // segment 1 1251 equal(requests.length, 0, 'did not request while updating');
1344 player.tech_.hls.checkBuffer_(); // should be a no-op
1345 equal(appends, 0, 'did not append while updating');
1346 }); 1252 });
1347 1253
1348 QUnit.skip('records the min and max PTS values for a segment', function() { 1254 QUnit.skip('records the min and max PTS values for a segment', function() {
...@@ -1424,7 +1330,6 @@ QUnit.skip('records PTS values for audio-only segments', function() { ...@@ -1424,7 +1330,6 @@ QUnit.skip('records PTS values for audio-only segments', function() {
1424 }); 1330 });
1425 1331
1426 test('waits to download new segments until the media playlist is stable', function() { 1332 test('waits to download new segments until the media playlist is stable', function() {
1427 var media;
1428 player.src({ 1333 player.src({
1429 src: 'manifest/master.m3u8', 1334 src: 'manifest/master.m3u8',
1430 type: 'application/vnd.apple.mpegurl' 1335 type: 'application/vnd.apple.mpegurl'
...@@ -1432,22 +1337,19 @@ test('waits to download new segments until the media playlist is stable', functi ...@@ -1432,22 +1337,19 @@ test('waits to download new segments until the media playlist is stable', functi
1432 openMediaSource(player); 1337 openMediaSource(player);
1433 standardXHRResponse(requests.shift()); // master 1338 standardXHRResponse(requests.shift()); // master
1434 player.tech_.hls.bandwidth = 1; // make sure we stay on the lowest variant 1339 player.tech_.hls.bandwidth = 1; // make sure we stay on the lowest variant
1435 standardXHRResponse(requests.shift()); // media 1340 standardXHRResponse(requests.shift()); // media1
1436 1341
1437 // mock a playlist switch 1342 // force a playlist switch
1438 media = player.tech_.hls.playlists.media(); 1343 player.tech_.hls.playlists.media('media3.m3u8');
1439 player.tech_.hls.playlists.media = function() {
1440 return media;
1441 };
1442 player.tech_.hls.playlists.state = 'SWITCHING_MEDIA';
1443 1344
1444 standardXHRResponse(requests.shift()); // segment 0 1345 standardXHRResponse(requests.shift()); // segment 0
1346 player.tech_.hls.sourceBuffer.trigger('updateend');
1445 1347
1446 equal(requests.length, 0, 'no requests outstanding'); 1348 equal(requests.length, 1, 'only the playlist request outstanding');
1447 player.tech_.hls.checkBuffer_(); 1349 player.tech_.hls.checkBuffer_();
1448 equal(requests.length, 0, 'delays segment fetching'); 1350 equal(requests.length, 1, 'delays segment fetching');
1449 1351
1450 player.tech_.hls.playlists.state = 'LOADED_METADATA'; 1352 standardXHRResponse(requests.shift()); // media3
1451 player.tech_.hls.checkBuffer_(); 1353 player.tech_.hls.checkBuffer_();
1452 equal(requests.length, 1, 'resumes segment fetching'); 1354 equal(requests.length, 1, 'resumes segment fetching');
1453 }); 1355 });
...@@ -1841,52 +1743,24 @@ QUnit.skip('translates ID3 PTS values across discontinuities', function() { ...@@ -1841,52 +1743,24 @@ QUnit.skip('translates ID3 PTS values across discontinuities', function() {
1841 equal(track.cues[1].endTime, 11, 'second cue ended at the correct time'); 1743 equal(track.cues[1].endTime, 11, 'second cue ended at the correct time');
1842 }); 1744 });
1843 1745
1844 test('adjusts the segment offsets for out-of-buffer seeking', function() {
1845 player.src({
1846 src: 'manifest/media.m3u8',
1847 type: 'application/vnd.apple.mpegurl'
1848 });
1849 openMediaSource(player);
1850 standardXHRResponse(requests.shift()); // media
1851 player.tech_.hls.sourceBuffer.buffered = function() {
1852 return videojs.createTimeRange(0, 20);
1853 };
1854 equal(player.tech_.hls.mediaIndex, 0, 'starts at zero');
1855
1856 player.tech_.setCurrentTime(35);
1857 clock.tick(1);
1858 // drop the aborted segment
1859 requests.shift();
1860 equal(player.tech_.hls.mediaIndex, 3, 'moved the mediaIndex');
1861 standardXHRResponse(requests.shift());
1862 });
1863
1864 test('seeks between buffered time ranges', function() { 1746 test('seeks between buffered time ranges', function() {
1865 player.src({ 1747 player.src({
1866 src: 'manifest/media.m3u8', 1748 src: 'media.m3u8',
1867 type: 'application/vnd.apple.mpegurl' 1749 type: 'application/vnd.apple.mpegurl'
1868 }); 1750 });
1869 openMediaSource(player); 1751 openMediaSource(player);
1870 standardXHRResponse(requests.shift()); // media 1752 standardXHRResponse(requests.shift()); // media
1871 player.tech_.buffered = function() { 1753 player.tech_.buffered = function() {
1872 return { 1754 return videojs.createTimeRange([[0, 10], [20, 30]]);
1873 length: 2,
1874 ranges_: [[0, 10], [20, 30]],
1875 start: function(i) {
1876 return this.ranges_[i][0];
1877 },
1878 end: function(i) {
1879 return this.ranges_[i][1];
1880 }
1881 };
1882 }; 1755 };
1883 1756
1884 player.tech_.setCurrentTime(15); 1757 player.tech_.setCurrentTime(15);
1885 clock.tick(1); 1758 clock.tick(1);
1886 // drop the aborted segment 1759 // drop the aborted segment
1887 requests.shift(); 1760 requests.shift();
1888 equal(player.tech_.hls.mediaIndex, 1, 'updated the mediaIndex'); 1761 equal(requests[0].url,
1889 standardXHRResponse(requests.shift()); 1762 absoluteUrl('media-00002.ts'),
1763 'requested the correct segment');
1890 }); 1764 });
1891 1765
1892 test('does not modify the media index for in-buffer seeking', function() { 1766 test('does not modify the media index for in-buffer seeking', function() {
...@@ -1979,40 +1853,6 @@ test('duration is Infinity for live playlists', function() { ...@@ -1979,40 +1853,6 @@ test('duration is Infinity for live playlists', function() {
1979 'duration is infinity'); 1853 'duration is infinity');
1980 }); 1854 });
1981 1855
1982 test('updates the media index when a playlist reloads', function() {
1983 player.src({
1984 src: 'http://example.com/live-updating.m3u8',
1985 type: 'application/vnd.apple.mpegurl'
1986 });
1987 openMediaSource(player);
1988 player.tech_.trigger('play');
1989
1990 requests[0].respond(200, null,
1991 '#EXTM3U\n' +
1992 '#EXTINF:10,\n' +
1993 '0.ts\n' +
1994 '#EXTINF:10,\n' +
1995 '1.ts\n' +
1996 '#EXTINF:10,\n' +
1997 '2.ts\n');
1998 standardXHRResponse(requests[1]);
1999 // play the stream until 2.ts is playing
2000 player.tech_.hls.mediaIndex = 3;
2001 // trigger a playlist refresh
2002 player.tech_.hls.playlists.trigger('mediaupdatetimeout');
2003 requests[2].respond(200, null,
2004 '#EXTM3U\n' +
2005 '#EXT-X-MEDIA-SEQUENCE:1\n' +
2006 '#EXTINF:10,\n' +
2007 '1.ts\n' +
2008 '#EXTINF:10,\n' +
2009 '2.ts\n' +
2010 '#EXTINF:10,\n' +
2011 '3.ts\n');
2012
2013 strictEqual(player.tech_.hls.mediaIndex, 2, 'mediaIndex is updated after the reload');
2014 });
2015
2016 test('live playlist starts three target durations before live', function() { 1856 test('live playlist starts three target durations before live', function() {
2017 var mediaPlaylist; 1857 var mediaPlaylist;
2018 player.src({ 1858 player.src({
...@@ -2040,30 +1880,11 @@ test('live playlist starts three target durations before live', function() { ...@@ -2040,30 +1880,11 @@ test('live playlist starts three target durations before live', function() {
2040 player.tech_.trigger('play'); 1880 player.tech_.trigger('play');
2041 clock.tick(1); 1881 clock.tick(1);
2042 mediaPlaylist = player.tech_.hls.playlists.media(); 1882 mediaPlaylist = player.tech_.hls.playlists.media();
2043 equal(player.tech_.hls.mediaIndex, 1, 'mediaIndex is updated at play');
2044 equal(player.currentTime(), player.tech_.hls.seekable().end(0), 'seeked to the seekable end'); 1883 equal(player.currentTime(), player.tech_.hls.seekable().end(0), 'seeked to the seekable end');
2045 1884
2046 equal(requests.length, 1, 'begins buffering'); 1885 equal(requests.length, 1, 'begins buffering');
2047 }); 1886 });
2048 1887
2049 test('does not reset live currentTime if mediaIndex is one beyond the last available segment', function() {
2050 var playlist = {
2051 mediaSequence: 20,
2052 targetDuration: 9,
2053 segments: [{
2054 duration: 3
2055 }, {
2056 duration: 3
2057 }, {
2058 duration: 3
2059 }]
2060 };
2061
2062 equal(playlist.segments.length,
2063 videojs.Hls.translateMediaIndex(playlist.segments.length, playlist, playlist),
2064 'did not change mediaIndex');
2065 });
2066
2067 test('live playlist starts with correct currentTime value', function() { 1888 test('live playlist starts with correct currentTime value', function() {
2068 player.src({ 1889 player.src({
2069 src: 'http://example.com/manifest/liveStart30sBefore.m3u8', 1890 src: 'http://example.com/manifest/liveStart30sBefore.m3u8',
...@@ -2117,39 +1938,6 @@ test('resets the time to a seekable position when resuming a live stream ' + ...@@ -2117,39 +1938,6 @@ test('resets the time to a seekable position when resuming a live stream ' +
2117 player.tech_.trigger('seeked'); 1938 player.tech_.trigger('seeked');
2118 }); 1939 });
2119 1940
2120 test('mediaIndex is zero before the first segment loads', function() {
2121 window.manifests['first-seg-load'] =
2122 '#EXTM3U\n' +
2123 '#EXTINF:10,\n' +
2124 '0.ts\n';
2125 player.src({
2126 src: 'http://example.com/first-seg-load.m3u8',
2127 type: 'application/vnd.apple.mpegurl'
2128 });
2129 openMediaSource(player);
2130
2131 strictEqual(player.tech_.hls.mediaIndex, 0, 'mediaIndex is zero');
2132 });
2133
2134 test('mediaIndex returns correctly at playlist boundaries', function() {
2135 player.src({
2136 src: 'http://example.com/master.m3u8',
2137 type: 'application/vnd.apple.mpegurl'
2138 });
2139
2140 openMediaSource(player);
2141 standardXHRResponse(requests.shift()); // master
2142 standardXHRResponse(requests.shift()); // media
2143
2144 strictEqual(player.tech_.hls.mediaIndex, 0, 'mediaIndex is zero at first segment');
2145
2146 // seek to end
2147 player.tech_.setCurrentTime(40);
2148 clock.tick(1);
2149
2150 strictEqual(player.tech_.hls.mediaIndex, 3, 'mediaIndex is 3 at last segment');
2151 });
2152
2153 test('reloads out-of-date live playlists when switching variants', function() { 1941 test('reloads out-of-date live playlists when switching variants', function() {
2154 player.src({ 1942 player.src({
2155 src: 'http://example.com/master.m3u8', 1943 src: 'http://example.com/master.m3u8',
...@@ -3209,4 +2997,42 @@ test('does not download segments if preload option set to none', function() { ...@@ -3209,4 +2997,42 @@ test('does not download segments if preload option set to none', function() {
3209 equal(requests.length, 0, 'did not download any segments'); 2997 equal(requests.length, 0, 'did not download any segments');
3210 }); 2998 });
3211 2999
3000 module('Buffer Inspection');
3001
3002 test('detects time range edges added by updates', function() {
3003 var edges;
3004
3005 edges = videojs.Hls.bufferedAdditions_(videojs.createTimeRange([[0, 10]]),
3006 videojs.createTimeRange([[0, 11]]));
3007 deepEqual(edges, [{ end: 11 }], 'detected a forward addition');
3008
3009 edges = videojs.Hls.bufferedAdditions_(videojs.createTimeRange([[5, 10]]),
3010 videojs.createTimeRange([[0, 10]]));
3011 deepEqual(edges, [{ start: 0 }], 'detected a backward addition');
3012
3013 edges = videojs.Hls.bufferedAdditions_(videojs.createTimeRange([[5, 10]]),
3014 videojs.createTimeRange([[0, 11]]));
3015 deepEqual(edges, [
3016 { start: 0 }, { end: 11 }
3017 ], 'detected forward and backward additions');
3018
3019 edges = videojs.Hls.bufferedAdditions_(videojs.createTimeRange([[0, 10]]),
3020 videojs.createTimeRange([[0, 10]]));
3021 deepEqual(edges, [], 'detected no addition');
3022
3023 edges = videojs.Hls.bufferedAdditions_(videojs.createTimeRange([]),
3024 videojs.createTimeRange([[0, 10]]));
3025 deepEqual(edges, [
3026 { start: 0 },
3027 { end: 10 }
3028 ], 'detected an initial addition');
3029
3030 edges = videojs.Hls.bufferedAdditions_(videojs.createTimeRange([[0, 10]]),
3031 videojs.createTimeRange([[0, 10], [20, 30]]));
3032 deepEqual(edges, [
3033 { start: 20 },
3034 { end: 30}
3035 ], 'detected a non-contiguous addition');
3036 });
3037
3212 })(window, window.videojs); 3038 })(window, window.videojs);
......