a5ab53ec by David LaPalomento Committed by brandonocasey

Style fixes and restoring some command ordering

The refactor should not have changed the required ordering for statements so restore it. Adjust styles a tad.
1 parent 99580d5c
...@@ -91,7 +91,7 @@ Hls.findSoleUncommonTimeRangesEnd_ = function(original, update) { ...@@ -91,7 +91,7 @@ Hls.findSoleUncommonTimeRangesEnd_ = function(original, update) {
91 /** 91 /**
92 * Whether the browser has built-in HLS support. 92 * Whether the browser has built-in HLS support.
93 */ 93 */
94 Hls.supportsNativeHls = function() { 94 Hls.supportsNativeHls = (function() {
95 let video = document.createElement('video'); 95 let video = document.createElement('video');
96 let xMpegUrl; 96 let xMpegUrl;
97 let vndMpeg; 97 let vndMpeg;
...@@ -105,7 +105,7 @@ Hls.supportsNativeHls = function() { ...@@ -105,7 +105,7 @@ Hls.supportsNativeHls = function() {
105 vndMpeg = video.canPlayType('application/vnd.apple.mpegURL'); 105 vndMpeg = video.canPlayType('application/vnd.apple.mpegURL');
106 return (/probably|maybe/).test(xMpegUrl) || 106 return (/probably|maybe/).test(xMpegUrl) ||
107 (/probably|maybe/).test(vndMpeg); 107 (/probably|maybe/).test(vndMpeg);
108 }; 108 }());
109 109
110 // HLS is a source handler, not a tech. Make sure attempts to use it 110 // HLS is a source handler, not a tech. Make sure attempts to use it
111 // as one do not cause exceptions. 111 // as one do not cause exceptions.
...@@ -717,6 +717,7 @@ export default class HlsHandler extends Component { ...@@ -717,6 +717,7 @@ export default class HlsHandler extends Component {
717 let variant; 717 let variant;
718 let bandwidthBestVariant; 718 let bandwidthBestVariant;
719 let resolutionPlusOne; 719 let resolutionPlusOne;
720 let resolutionPlusOneAttribute;
720 let resolutionBestVariant; 721 let resolutionBestVariant;
721 let width; 722 let width;
722 let height; 723 let height;
...@@ -725,9 +726,9 @@ export default class HlsHandler extends Component { ...@@ -725,9 +726,9 @@ export default class HlsHandler extends Component {
725 726
726 // filter out any playlists that have been excluded due to 727 // filter out any playlists that have been excluded due to
727 // incompatible configurations or playback errors 728 // incompatible configurations or playback errors
728 sortedPlaylists = sortedPlaylists.filter((localvariant) => { 729 sortedPlaylists = sortedPlaylists.filter((localVariant) => {
729 if (typeof localvariant.excludeUntil !== 'undefined') { 730 if (typeof localVariant.excludeUntil !== 'undefined') {
730 return now >= localvariant.excludeUntil; 731 return now >= localVariant.excludeUntil;
731 } 732 }
732 return true; 733 return true;
733 }); 734 });
...@@ -785,21 +786,22 @@ export default class HlsHandler extends Component { ...@@ -785,21 +786,22 @@ export default class HlsHandler extends Component {
785 // since the playlists are sorted, the first variant that has 786 // since the playlists are sorted, the first variant that has
786 // dimensions less than or equal to the player size is the best 787 // dimensions less than or equal to the player size is the best
787 788
788 if (variant.attributes.RESOLUTION.width === width && 789 let variantResolution = variant.attributes.RESOLUTION;
789 variant.attributes.RESOLUTION.height === height) { 790
791 if (variantResolution.width === width &&
792 variantResolution.height === height) {
790 // if we have the exact resolution as the player use it 793 // if we have the exact resolution as the player use it
791 resolutionPlusOne = null; 794 resolutionPlusOne = null;
792 resolutionBestVariant = variant; 795 resolutionBestVariant = variant;
793 break; 796 break;
794 } else if (variant.attributes.RESOLUTION.width < width && 797 } else if (variantResolution.width < width &&
795 variant.attributes.RESOLUTION.height < height) { 798 variantResolution.height < height) {
796 // if both dimensions are less than the player use the 799 // if both dimensions are less than the player use the
797 // previous (next-largest) variant 800 // previous (next-largest) variant
798 break; 801 break;
799 } else if (!resolutionPlusOne || (variant.attributes.RESOLUTION.width < 802 } else if (!resolutionPlusOne ||
800 resolutionPlusOne.attributes.RESOLUTION.width && 803 (variantResolution.width < resolutionPlusOneAttribute.width &&
801 variant.attributes.RESOLUTION.height < 804 variantResolution.height < resolutionPlusOneAttribute.height)) {
802 resolutionPlusOne.attributes.RESOLUTION.height)) {
803 // If we still haven't found a good match keep a 805 // If we still haven't found a good match keep a
804 // reference to the previous variant for the next loop 806 // reference to the previous variant for the next loop
805 // iteration 807 // iteration
...@@ -809,6 +811,7 @@ export default class HlsHandler extends Component { ...@@ -809,6 +811,7 @@ export default class HlsHandler extends Component {
809 // the highest bandwidth variant that is just-larger-than 811 // the highest bandwidth variant that is just-larger-than
810 // the video player 812 // the video player
811 resolutionPlusOne = variant; 813 resolutionPlusOne = variant;
814 resolutionPlusOneAttribute = resolutionPlusOneAttribute.attributes.RESOLUTION;
812 } 815 }
813 } 816 }
814 817
...@@ -930,12 +933,13 @@ export default class HlsHandler extends Component { ...@@ -930,12 +933,13 @@ export default class HlsHandler extends Component {
930 933
931 // we have entered a state where we are fetching the same segment, 934 // we have entered a state where we are fetching the same segment,
932 // try to walk forward 935 // try to walk forward
936 /* eslint-disable max-len */
933 if (this.lastSegmentLoaded_ && 937 if (this.lastSegmentLoaded_ &&
934 this.playlistUriToUrl(this.lastSegmentLoaded_.uri) === 938 this.playlistUriToUrl(this.lastSegmentLoaded_.uri) === this.playlistUriToUrl(segment.uri) &&
935 this.playlistUriToUrl(segment.uri) &&
936 this.lastSegmentLoaded_.byterange === segment.byterange) { 939 this.lastSegmentLoaded_.byterange === segment.byterange) {
937 return this.fillBuffer(mediaIndex + 1); 940 return this.fillBuffer(mediaIndex + 1);
938 } 941 }
942 /* eslint-enable max-len */
939 943
940 // package up all the work to append the segment 944 // package up all the work to append the segment
941 segmentInfo = { 945 segmentInfo = {
...@@ -1236,9 +1240,9 @@ export default class HlsHandler extends Component { ...@@ -1236,9 +1240,9 @@ export default class HlsHandler extends Component {
1236 decrypter = new Hls.Decrypter(segmentInfo.encryptedBytes, 1240 decrypter = new Hls.Decrypter(segmentInfo.encryptedBytes,
1237 segment.key.bytes, 1241 segment.key.bytes,
1238 segIv, 1242 segIv,
1239 function(err, localBytes) { 1243 function(error, localBytes) {
1240 if (err) { 1244 if (error) {
1241 throw new Error(err); 1245 videojs.log.warn(error);
1242 } 1246 }
1243 segmentInfo.bytes = localBytes; 1247 segmentInfo.bytes = localBytes;
1244 }); 1248 });
...@@ -1415,10 +1419,10 @@ export default class HlsHandler extends Component { ...@@ -1415,10 +1419,10 @@ export default class HlsHandler extends Component {
1415 * @return a new TimeRanges object. 1419 * @return a new TimeRanges object.
1416 */ 1420 */
1417 HlsHandler.prototype.findBufferedRange_ = 1421 HlsHandler.prototype.findBufferedRange_ =
1418 filterBufferedRanges(function(start, end, time) { 1422 filterBufferedRanges(function(start, end, time) {
1419 return start - TIME_FUDGE_FACTOR <= time && 1423 return start - TIME_FUDGE_FACTOR <= time &&
1420 end + TIME_FUDGE_FACTOR >= time; 1424 end + TIME_FUDGE_FACTOR >= time;
1421 }); 1425 });
1422 /** 1426 /**
1423 * Returns the TimeRanges that begin at or later than the specified 1427 * Returns the TimeRanges that begin at or later than the specified
1424 * time. 1428 * time.
...@@ -1427,9 +1431,9 @@ filterBufferedRanges(function(start, end, time) { ...@@ -1427,9 +1431,9 @@ filterBufferedRanges(function(start, end, time) {
1427 * @return a new TimeRanges object. 1431 * @return a new TimeRanges object.
1428 */ 1432 */
1429 HlsHandler.prototype.findNextBufferedRange_ = 1433 HlsHandler.prototype.findNextBufferedRange_ =
1430 filterBufferedRanges(function(start, end, time) { 1434 filterBufferedRanges(function(start, end, time) {
1431 return start - TIME_FUDGE_FACTOR >= time; 1435 return start - TIME_FUDGE_FACTOR >= time;
1432 }); 1436 });
1433 1437
1434 /** 1438 /**
1435 * The Source Handler object, which informs video.js what additional 1439 * The Source Handler object, which informs video.js what additional
...@@ -1468,7 +1472,7 @@ HlsSourceHandler.canPlayType = function(type) { ...@@ -1468,7 +1472,7 @@ HlsSourceHandler.canPlayType = function(type) {
1468 let mpegurlRE = /^application\/(?:x-|vnd\.apple\.)mpegurl/i; 1472 let mpegurlRE = /^application\/(?:x-|vnd\.apple\.)mpegurl/i;
1469 1473
1470 // favor native HLS support if it's available 1474 // favor native HLS support if it's available
1471 if (Hls.supportsNativeHls()) { 1475 if (Hls.supportsNativeHls) {
1472 return false; 1476 return false;
1473 } 1477 }
1474 return mpegurlRE.test(type); 1478 return mpegurlRE.test(type);
......
1 /* eslint-disable max-len */
2
1 import document from 'global/document'; 3 import document from 'global/document';
2 import videojs from 'video.js'; 4 import videojs from 'video.js';
3 import sinon from 'sinon'; 5 import sinon from 'sinon';
...@@ -8,7 +10,6 @@ import testDataManifests from './test-manifests.js'; ...@@ -8,7 +10,6 @@ import testDataManifests from './test-manifests.js';
8 import Hls from '../src/videojs-contrib-hls'; 10 import Hls from '../src/videojs-contrib-hls';
9 /* eslint-enable no-unused-vars */ 11 /* eslint-enable no-unused-vars */
10 12
11 const bcSegment = new Uint8Array(1);
12 const Flash = videojs.getComponent('Flash'); 13 const Flash = videojs.getComponent('Flash');
13 let nextId = 0; 14 let nextId = 0;
14 15
...@@ -25,10 +26,9 @@ const mockTech = function(tech) { ...@@ -25,10 +26,9 @@ const mockTech = function(tech) {
25 } 26 }
26 27
27 tech.isMocked_ = true; 28 tech.isMocked_ = true;
28
29 tech.hls = tech.hls;
30 tech.src_ = null; 29 tech.src_ = null;
31 tech.time_ = null; 30 tech.time_ = null;
31
32 tech.paused_ = !tech.autoplay(); 32 tech.paused_ = !tech.autoplay();
33 tech.paused = function() { 33 tech.paused = function() {
34 return tech.paused_; 34 return tech.paused_;
...@@ -102,11 +102,12 @@ const createPlayer = function(options) { ...@@ -102,11 +102,12 @@ const createPlayer = function(options) {
102 }; 102 };
103 103
104 const openMediaSource = function(player, clock) { 104 const openMediaSource = function(player, clock) {
105 mockTech(player.tech_);
106
107 // ensure the Flash tech is ready 105 // ensure the Flash tech is ready
108 player.tech_.triggerReady(); 106 player.tech_.triggerReady();
109 clock.tick(1); 107 clock.tick(1);
108 // mock the tech *after* it has finished loading so that we don't
109 // mock a tech that will be unloaded on the next tick
110 mockTech(player.tech_);
110 111
111 // simulate the sourceopen event 112 // simulate the sourceopen event
112 player.tech_.hls.mediaSource.readyState = 'open'; 113 player.tech_.hls.mediaSource.readyState = 'open';
...@@ -162,14 +163,19 @@ const absoluteUrl = function(relativeUrl) { ...@@ -162,14 +163,19 @@ const absoluteUrl = function(relativeUrl) {
162 .join('/') 163 .join('/')
163 ); 164 );
164 }; 165 };
166
165 // a no-op MediaSource implementation to allow synchronous testing 167 // a no-op MediaSource implementation to allow synchronous testing
166 const MockMediaSource = videojs.extend(videojs.EventTarget, { 168 class MockMediaSource extends videojs.EventTarget {
167 constructor() {}, 169 static open() {}
168 duration: NaN, 170
169 seekable: videojs.createTimeRange(), 171 constructor() {
172 super();
173 this.duration = NaN;
174 this.seekable = videojs.createTimeRange();
175 }
170 addSeekableRange_(start, end) { 176 addSeekableRange_(start, end) {
171 this.seekable = videojs.createTimeRange(start, end); 177 this.seekable = videojs.createTimeRange(start, end);
172 }, 178 }
173 addSourceBuffer() { 179 addSourceBuffer() {
174 return new (videojs.extend(videojs.EventTarget, { 180 return new (videojs.extend(videojs.EventTarget, {
175 constructor() {}, 181 constructor() {},
...@@ -178,13 +184,12 @@ const MockMediaSource = videojs.extend(videojs.EventTarget, { ...@@ -178,13 +184,12 @@ const MockMediaSource = videojs.extend(videojs.EventTarget, {
178 appendBuffer() {}, 184 appendBuffer() {},
179 remove() {} 185 remove() {}
180 }))(); 186 }))();
181 }, 187 }
182 // endOfStream triggers an exception if flash isn't available 188 // endOfStream triggers an exception if flash isn't available
183 endOfStream(error) { 189 endOfStream(error) {
184 this.error_ = error; 190 this.error_ = error;
185 }, 191 }
186 open() {} 192 }
187 });
188 const URL = { 193 const URL = {
189 createObjectURL() { 194 createObjectURL() {
190 return 'blob:mock-vjs-object-url'; 195 return 'blob:mock-vjs-object-url';
...@@ -193,15 +198,14 @@ const URL = { ...@@ -193,15 +198,14 @@ const URL = {
193 198
194 QUnit.module('HLS -', { 199 QUnit.module('HLS -', {
195 beforeEach() { 200 beforeEach() {
196 // Mock the environment's timers because certain things - particularly
197 // player readiness - are asynchronous in video.js 5.
198 this.clock = sinon.useFakeTimers();
199
200 // setup a player
201 this.player = createPlayer();
202
203 this.old = {}; 201 this.old = {};
204 202
203 // Mock Media Sources
204 this.old.MediaSource = videojs.MediaSource;
205 videojs.MediaSource = MockMediaSource;
206 this.old.URL = videojs.URL;
207 videojs.URL = URL;
208
205 // mock out Flash features for phantomjs 209 // mock out Flash features for phantomjs
206 this.old.Flash = videojs.mergeOptions({}, Flash); 210 this.old.Flash = videojs.mergeOptions({}, Flash);
207 /* eslint-disable camelcase */ 211 /* eslint-disable camelcase */
...@@ -242,17 +246,6 @@ QUnit.module('HLS -', { ...@@ -242,17 +246,6 @@ QUnit.module('HLS -', {
242 return true; 246 return true;
243 }; 247 };
244 248
245 // fake XHRs
246 this.old.XHR = videojs.xhr.XMLHttpRequest;
247 this.sinonXHR = sinon.useFakeXMLHttpRequest();
248 this.requests = [];
249 this.sinonXHR.onCreate = (xhr) => {
250 // force the XHR2 timeout polyfill
251 xhr.timeout = null;
252 this.requests.push(xhr);
253 };
254 videojs.xhr.XMLHttpRequest = this.sinonXHR;
255
256 // Fake sourcebuffer 249 // Fake sourcebuffer
257 this.old.SourceBuffer = window.videojs.SourceBuffer; 250 this.old.SourceBuffer = window.videojs.SourceBuffer;
258 window.videojs.SourceBuffer = function() { 251 window.videojs.SourceBuffer = function() {
...@@ -265,28 +258,31 @@ QUnit.module('HLS -', { ...@@ -265,28 +258,31 @@ QUnit.module('HLS -', {
265 258
266 // force the HLS tech to run 259 // force the HLS tech to run
267 this.old.NativeHlsSupport = videojs.Hls.supportsNativeHls; 260 this.old.NativeHlsSupport = videojs.Hls.supportsNativeHls;
268 videojs.Hls.supportsNativeHls = function() { 261 videojs.Hls.supportsNativeHls = false;
269 return false;
270 };
271 262
272 this.old.Decrypt = videojs.Hls.Decrypter; 263 this.old.Decrypt = videojs.Hls.Decrypter;
273 videojs.Hls.Decrypter = function() {}; 264 videojs.Hls.Decrypter = function() {};
274 265
275 // Mock Media Sources 266 // fake XHRs
276 this.old.MediaSource = videojs.MediaSource; 267 this.old.XHR = videojs.xhr.XMLHttpRequest;
277 videojs.MediaSource = MockMediaSource; 268 this.sinonXHR = sinon.useFakeXMLHttpRequest();
278 this.old.URL = videojs.URL; 269 this.requests = [];
279 videojs.URL = URL; 270 this.sinonXHR.onCreate = (xhr) => {
271 // force the XHR2 timeout polyfill
272 xhr.timeout = null;
273 this.requests.push(xhr);
274 };
275 videojs.xhr.XMLHttpRequest = this.sinonXHR;
276
277 // Mock the environment's timers because certain things - particularly
278 // player readiness - are asynchronous in video.js 5.
279 this.clock = sinon.useFakeTimers();
280
281 // setup a player
282 this.player = createPlayer();
280 }, 283 },
281 284
282 afterEach() { 285 afterEach() {
283 // The clock _must_ be restored before disposing the player; otherwise,
284 // certain timeout listeners that happen inside video.js may throw errors.
285 this.clock.restore();
286 this.player.dispose();
287
288 this.sinonXHR.restore();
289 videojs.xhr.XMLHttpRequest = this.old.XHR;
290 videojs.MediaSource = this.old.MediaSource; 286 videojs.MediaSource = this.old.MediaSource;
291 videojs.URL = this.old.URL; 287 videojs.URL = this.old.URL;
292 288
...@@ -297,6 +293,16 @@ QUnit.module('HLS -', { ...@@ -297,6 +293,16 @@ QUnit.module('HLS -', {
297 videojs.Hls.supportsNativeHls = this.old.NativeHlsSupport; 293 videojs.Hls.supportsNativeHls = this.old.NativeHlsSupport;
298 videojs.Hls.Decrypter = this.old.Decrypt; 294 videojs.Hls.Decrypter = this.old.Decrypt;
299 videojs.SourceBuffer = this.old.SourceBuffer; 295 videojs.SourceBuffer = this.old.SourceBuffer;
296
297 // The clock _must_ be restored before disposing the player; otherwise,
298 // certain timeout listeners that happen inside video.js may throw errors.
299 this.clock.restore();
300 // XXX TODO WHY!?!?!?
301 this.player.dispose();
302
303 this.sinonXHR.restore();
304 videojs.xhr.XMLHttpRequest = this.old.XHR;
305
300 } 306 }
301 }); 307 });
302 308
...@@ -308,9 +314,7 @@ QUnit.test('starts playing if autoplay is specified', function() { ...@@ -308,9 +314,7 @@ QUnit.test('starts playing if autoplay is specified', function() {
308 src: 'manifest/playlist.m3u8', 314 src: 'manifest/playlist.m3u8',
309 type: 'application/vnd.apple.mpegurl' 315 type: 'application/vnd.apple.mpegurl'
310 }); 316 });
311 // REMOVEME workaround https://github.com/videojs/video.js/issues/2326 317
312 // this.player.tech_.triggerReady();
313 // this.clock.tick(1);
314 // make sure play() is called *after* the media source opens 318 // make sure play() is called *after* the media source opens
315 this.player.tech_.hls.play = function() { 319 this.player.tech_.hls.play = function() {
316 plays++; 320 plays++;
...@@ -417,8 +421,7 @@ QUnit.test('autoplay seeks to the live point after media source open', function( ...@@ -417,8 +421,7 @@ QUnit.test('autoplay seeks to the live point after media source open', function(
417 QUnit.notEqual(currentTime, 0, 'seeked on autoplay'); 421 QUnit.notEqual(currentTime, 0, 'seeked on autoplay');
418 }); 422 });
419 423
420 QUnit.test('duration is set when the source opens after the playlist is loaded', 424 QUnit.test('duration is set when the source opens after the playlist is loaded', function() {
421 function() {
422 this.player.src({ 425 this.player.src({
423 src: 'media.m3u8', 426 src: 'media.m3u8',
424 type: 'application/vnd.apple.mpegurl' 427 type: 'application/vnd.apple.mpegurl'
...@@ -527,11 +530,8 @@ QUnit.test('codecs are passed to the source buffer', function() { ...@@ -527,11 +530,8 @@ QUnit.test('codecs are passed to the source buffer', function() {
527 type: 'application/vnd.apple.mpegurl' 530 type: 'application/vnd.apple.mpegurl'
528 }); 531 });
529 openMediaSource(this.player, this.clock); 532 openMediaSource(this.player, this.clock);
530 let addSourceBuffer = this.player.tech_.hls.mediaSource.addSourceBuffer;
531
532 this.player.tech_.hls.mediaSource.addSourceBuffer = function(codec) { 533 this.player.tech_.hls.mediaSource.addSourceBuffer = function(codec) {
533 codecs.push(codec); 534 codecs.push(codec);
534 return addSourceBuffer.call(this, codec);
535 }; 535 };
536 536
537 this.requests.shift().respond(200, null, 537 this.requests.shift().respond(200, null,
...@@ -920,7 +920,7 @@ QUnit.test('buffer checks are noops when only the master is ready', function() { ...@@ -920,7 +920,7 @@ QUnit.test('buffer checks are noops when only the master is ready', function() {
920 standardXHRResponse(this.requests.shift()); 920 standardXHRResponse(this.requests.shift());
921 // media 921 // media
922 standardXHRResponse(this.requests.shift()); 922 standardXHRResponse(this.requests.shift());
923 // ignore any outstanding segmentthis.requests 923 // ignore any outstanding segment requests
924 this.requests.length = 0; 924 this.requests.length = 0;
925 925
926 // load in a new playlist which will cause playlists.media() to be 926 // load in a new playlist which will cause playlists.media() to be
...@@ -1083,7 +1083,7 @@ QUnit.test('downloads additional playlists if required', function() { ...@@ -1083,7 +1083,7 @@ QUnit.test('downloads additional playlists if required', function() {
1083 this.requests[2].respond(200, null, ''); 1083 this.requests[2].respond(200, null, '');
1084 standardXHRResponse(this.requests[3]); 1084 standardXHRResponse(this.requests[3]);
1085 1085
1086 QUnit.strictEqual(4, this.requests.length, 'this.requestswere made'); 1086 QUnit.strictEqual(4, this.requests.length, 'requests were made');
1087 QUnit.strictEqual(this.requests[3].url, 1087 QUnit.strictEqual(this.requests[3].url,
1088 absoluteUrl('manifest/' + playlist.uri), 1088 absoluteUrl('manifest/' + playlist.uri),
1089 'made playlist request'); 1089 'made playlist request');
...@@ -1200,7 +1200,7 @@ QUnit.test('selects the correct rendition by player dimensions', function() { ...@@ -1200,7 +1200,7 @@ QUnit.test('selects the correct rendition by player dimensions', function() {
1200 1200
1201 QUnit.deepEqual(playlist.attributes.RESOLUTION, 1201 QUnit.deepEqual(playlist.attributes.RESOLUTION,
1202 {width: 960, height: 540}, 1202 {width: 960, height: 540},
1203 'should return the correct resolution by playerdimensions'); 1203 'should return the correct resolution by player dimensions');
1204 QUnit.equal(playlist.attributes.BANDWIDTH, 1204 QUnit.equal(playlist.attributes.BANDWIDTH,
1205 1928000, 1205 1928000,
1206 'should have the expected bandwidth in case of multiple'); 1206 'should have the expected bandwidth in case of multiple');
...@@ -1227,7 +1227,7 @@ QUnit.test('selects the correct rendition by player dimensions', function() { ...@@ -1227,7 +1227,7 @@ QUnit.test('selects the correct rendition by player dimensions', function() {
1227 QUnit.deepEqual(playlist.attributes.RESOLUTION, 1227 QUnit.deepEqual(playlist.attributes.RESOLUTION,
1228 {width: 396, height: 224}, 1228 {width: 396, height: 224},
1229 'should return the correct resolution by ' + 1229 'should return the correct resolution by ' +
1230 'this.playerdimensions, if exact match'); 1230 'player dimensions, if exact match');
1231 QUnit.equal(playlist.attributes.BANDWIDTH, 1231 QUnit.equal(playlist.attributes.BANDWIDTH,
1232 440000, 1232 440000,
1233 'should have the expected bandwidth in case of multiple, if exact match'); 1233 'should have the expected bandwidth in case of multiple, if exact match');
...@@ -1245,7 +1245,7 @@ QUnit.test('selects the correct rendition by player dimensions', function() { ...@@ -1245,7 +1245,7 @@ QUnit.test('selects the correct rendition by player dimensions', function() {
1245 'should have the expected bandwidth in case of multiple, if exact match'); 1245 'should have the expected bandwidth in case of multiple, if exact match');
1246 }); 1246 });
1247 1247
1248 QUnit.test('selects the highest bitrate playlist when the this.playerdimensions are ' + 1248 QUnit.test('selects the highest bitrate playlist when the player dimensions are ' +
1249 'larger than any of the variants', function() { 1249 'larger than any of the variants', function() {
1250 let playlist; 1250 let playlist;
1251 1251
...@@ -1396,9 +1396,7 @@ QUnit.test('blacklists switching from video-only playlists to video+audio', func ...@@ -1396,9 +1396,7 @@ QUnit.test('blacklists switching from video-only playlists to video+audio', func
1396 'excluded incompatible playlist'); 1396 'excluded incompatible playlist');
1397 }); 1397 });
1398 1398
1399 QUnit.test('After an initial media playlist 404s, ' + 1399 QUnit.test('After an initial media playlist 404s, we fire loadedmetadata once we successfully load a playlist', function() {
1400 'we fire loadedmetadata once we successfully load a playlist',
1401 function() {
1402 let count = 0; 1400 let count = 0;
1403 1401
1404 this.player.src({ 1402 this.player.src({
...@@ -1487,8 +1485,7 @@ QUnit.test('does not blacklist compatible AAC codec strings', function() { ...@@ -1487,8 +1485,7 @@ QUnit.test('does not blacklist compatible AAC codec strings', function() {
1487 'did not blacklist'); 1485 'did not blacklist');
1488 }); 1486 });
1489 1487
1490 QUnit.test('blacklists switching between playlists with incompatible audio codecs', 1488 QUnit.test('blacklists switching between playlists with incompatible audio codecs', function() {
1491 function() {
1492 let alternatePlaylist; 1489 let alternatePlaylist;
1493 1490
1494 this.player.src({ 1491 this.player.src({
...@@ -1548,7 +1545,7 @@ QUnit.test('downloads the next segment if the buffer is getting low', function() ...@@ -1548,7 +1545,7 @@ QUnit.test('downloads the next segment if the buffer is getting low', function()
1548 standardXHRResponse(this.requests[0]); 1545 standardXHRResponse(this.requests[0]);
1549 standardXHRResponse(this.requests[1]); 1546 standardXHRResponse(this.requests[1]);
1550 1547
1551 QUnit.strictEqual(this.requests.length, 2, 'made two this.requests'); 1548 QUnit.strictEqual(this.requests.length, 2, 'made two requests');
1552 this.player.tech_.currentTime = function() { 1549 this.player.tech_.currentTime = function() {
1553 return 15; 1550 return 15;
1554 }; 1551 };
...@@ -1588,7 +1585,7 @@ QUnit.test('buffers based on the correct TimeRange if multiple ranges exist', fu ...@@ -1588,7 +1585,7 @@ QUnit.test('buffers based on the correct TimeRange if multiple ranges exist', fu
1588 standardXHRResponse(this.requests[0]); 1585 standardXHRResponse(this.requests[0]);
1589 standardXHRResponse(this.requests[1]); 1586 standardXHRResponse(this.requests[1]);
1590 1587
1591 QUnit.strictEqual(this.requests.length, 2, 'made two this.requests'); 1588 QUnit.strictEqual(this.requests.length, 2, 'made two requests');
1592 QUnit.strictEqual(this.requests[1].url, 1589 QUnit.strictEqual(this.requests[1].url,
1593 absoluteUrl('manifest/media-00002.ts'), 1590 absoluteUrl('manifest/media-00002.ts'),
1594 'made segment request'); 1591 'made segment request');
...@@ -1596,7 +1593,7 @@ QUnit.test('buffers based on the correct TimeRange if multiple ranges exist', fu ...@@ -1596,7 +1593,7 @@ QUnit.test('buffers based on the correct TimeRange if multiple ranges exist', fu
1596 currentTime = 22; 1593 currentTime = 22;
1597 this.player.tech_.hls.sourceBuffer.trigger('updateend'); 1594 this.player.tech_.hls.sourceBuffer.trigger('updateend');
1598 this.player.tech_.hls.checkBuffer_(); 1595 this.player.tech_.hls.checkBuffer_();
1599 QUnit.strictEqual(this.requests.length, 3, 'made three this.requests'); 1596 QUnit.strictEqual(this.requests.length, 3, 'made three requests');
1600 QUnit.strictEqual(this.requests[2].url, 1597 QUnit.strictEqual(this.requests[2].url,
1601 absoluteUrl('manifest/media-00003.ts'), 1598 absoluteUrl('manifest/media-00003.ts'),
1602 'made segment request'); 1599 'made segment request');
...@@ -1654,8 +1651,7 @@ QUnit.test('only appends one segment at a time', function() { ...@@ -1654,8 +1651,7 @@ QUnit.test('only appends one segment at a time', function() {
1654 QUnit.equal(appends, 1, 'appended once'); 1651 QUnit.equal(appends, 1, 'appended once');
1655 }); 1652 });
1656 1653
1657 QUnit.test('waits to download new segments until the media playlist is stable', 1654 QUnit.test('waits to download new segments until the media playlist is stable', function() {
1658 function() {
1659 this.player.src({ 1655 this.player.src({
1660 src: 'manifest/master.m3u8', 1656 src: 'manifest/master.m3u8',
1661 type: 'application/vnd.apple.mpegurl' 1657 type: 'application/vnd.apple.mpegurl'
...@@ -1770,6 +1766,7 @@ QUnit.test('segmentXhr is properly nulled out when dispose is called', function( ...@@ -1770,6 +1766,7 @@ QUnit.test('segmentXhr is properly nulled out when dispose is called', function(
1770 1766
1771 Flash.prototype.dispose = oldDispose; 1767 Flash.prototype.dispose = oldDispose;
1772 }); 1768 });
1769
1773 QUnit.test('does not modify the media index for in-buffer seeking', function() { 1770 QUnit.test('does not modify the media index for in-buffer seeking', function() {
1774 let mediaIndex; 1771 let mediaIndex;
1775 1772
...@@ -1872,7 +1869,7 @@ QUnit.test('seeking in an empty playlist is a non-erroring noop', function() { ...@@ -1872,7 +1869,7 @@ QUnit.test('seeking in an empty playlist is a non-erroring noop', function() {
1872 this.player.tech_.setCurrentTime(183); 1869 this.player.tech_.setCurrentTime(183);
1873 this.clock.tick(1); 1870 this.clock.tick(1);
1874 1871
1875 QUnit.equal(this.requests.length, requestsLength, 'made no additional this.requests'); 1872 QUnit.equal(this.requests.length, requestsLength, 'made no additional requests');
1876 }); 1873 });
1877 1874
1878 QUnit.test('sets seekable and duration for live playlists', function() { 1875 QUnit.test('sets seekable and duration for live playlists', function() {
...@@ -1900,10 +1897,6 @@ QUnit.test('sets seekable and duration for live playlists', function() { ...@@ -1900,10 +1897,6 @@ QUnit.test('sets seekable and duration for live playlists', function() {
1900 }); 1897 });
1901 1898
1902 QUnit.test('live playlist starts three target durations before live', function() { 1899 QUnit.test('live playlist starts three target durations before live', function() {
1903 /* eslint-disable no-unused-vars */
1904 let mediaPlaylist;
1905 /* eslint-enable no-unused-vars */
1906
1907 this.player.src({ 1900 this.player.src({
1908 src: 'live.m3u8', 1901 src: 'live.m3u8',
1909 type: 'application/vnd.apple.mpegurl' 1902 type: 'application/vnd.apple.mpegurl'
...@@ -1933,7 +1926,6 @@ QUnit.test('live playlist starts three target durations before live', function() ...@@ -1933,7 +1926,6 @@ QUnit.test('live playlist starts three target durations before live', function()
1933 }; 1926 };
1934 this.player.tech_.trigger('play'); 1927 this.player.tech_.trigger('play');
1935 this.clock.tick(1); 1928 this.clock.tick(1);
1936 mediaPlaylist = this.player.tech_.hls.playlists.media();
1937 QUnit.equal(this.player.currentTime(), 1929 QUnit.equal(this.player.currentTime(),
1938 this.player.tech_.hls.seekable().end(0), 1930 this.player.tech_.hls.seekable().end(0),
1939 'seeked to the seekable end'); 1931 'seeked to the seekable end');
...@@ -1961,14 +1953,14 @@ QUnit.test('live playlist starts with correct currentTime value', function() { ...@@ -1961,14 +1953,14 @@ QUnit.test('live playlist starts with correct currentTime value', function() {
1961 this.player.tech_.trigger('play'); 1953 this.player.tech_.trigger('play');
1962 this.clock.tick(1); 1954 this.clock.tick(1);
1963 1955
1956 let media = this.player.tech_.hls.playlists.media();
1957
1964 QUnit.strictEqual(this.player.currentTime(), 1958 QUnit.strictEqual(this.player.currentTime(),
1965 videojs.Hls.Playlist.seekable(this.player.tech_.hls.playlists.media()) 1959 videojs.Hls.Playlist.seekable(media).end(0),
1966 .end(0),
1967 'currentTime is updated at playback'); 1960 'currentTime is updated at playback');
1968 }); 1961 });
1969 1962
1970 QUnit.test('adjusts the seekable start based on the amount of expired live content', 1963 QUnit.test('adjusts the seekable start based on the amount of expired live content', function() {
1971 function() {
1972 this.player.src({ 1964 this.player.src({
1973 src: 'http://example.com/manifest/liveStart30sBefore.m3u8', 1965 src: 'http://example.com/manifest/liveStart30sBefore.m3u8',
1974 type: 'application/vnd.apple.mpegurl' 1966 type: 'application/vnd.apple.mpegurl'
...@@ -1988,9 +1980,7 @@ function() { ...@@ -1988,9 +1980,7 @@ function() {
1988 'offset the seekable start'); 1980 'offset the seekable start');
1989 }); 1981 });
1990 1982
1991 QUnit.test('estimates seekable ranges for live streams ' + 1983 QUnit.test('estimates seekable ranges for live streams that have been paused for a long time', function() {
1992 'that have been paused for a long time',
1993 function() {
1994 this.player.src({ 1984 this.player.src({
1995 src: 'http://example.com/manifest/liveStart30sBefore.m3u8', 1985 src: 'http://example.com/manifest/liveStart30sBefore.m3u8',
1996 type: 'application/vnd.apple.mpegurl' 1986 type: 'application/vnd.apple.mpegurl'
...@@ -2019,7 +2009,7 @@ QUnit.test('resets the time to a seekable position when resuming a live stream ' ...@@ -2019,7 +2009,7 @@ QUnit.test('resets the time to a seekable position when resuming a live stream '
2019 '#EXT-X-MEDIA-SEQUENCE:16\n' + 2009 '#EXT-X-MEDIA-SEQUENCE:16\n' +
2020 '#EXTINF:10,\n' + 2010 '#EXTINF:10,\n' +
2021 '16.ts\n'); 2011 '16.ts\n');
2022 // mock out the this.playerto simulate a live stream that has been 2012 // mock out the player to simulate a live stream that has been
2023 // playing for awhile 2013 // playing for awhile
2024 this.player.tech_.hls.seekable = function() { 2014 this.player.tech_.hls.seekable = function() {
2025 return videojs.createTimeRange(160, 170); 2015 return videojs.createTimeRange(160, 170);
...@@ -2041,50 +2031,7 @@ QUnit.test('resets the time to a seekable position when resuming a live stream ' ...@@ -2041,50 +2031,7 @@ QUnit.test('resets the time to a seekable position when resuming a live stream '
2041 this.player.tech_.trigger('seeked'); 2031 this.player.tech_.trigger('seeked');
2042 }); 2032 });
2043 2033
2044 QUnit.test('reloads out-of-date live playlists when switching variants', function() { 2034 QUnit.test('if withCredentials global option is used, withCredentials is set on the XHR object', function() {
2045 let oldManifest = testDataManifests['variant-update'];
2046
2047 this.player.src({
2048 src: 'http://example.com/master.m3u8',
2049 type: 'application/vnd.apple.mpegurl'
2050 });
2051 openMediaSource(this.player, this.clock);
2052
2053 this.player.tech_.hls.master = {
2054 playlists: [{
2055 mediaSequence: 15,
2056 segments: [1, 1, 1]
2057 }, {
2058 uri: 'http://example.com/variant-update.m3u8',
2059 mediaSequence: 0,
2060 segments: [1, 1]
2061 }]
2062 };
2063 // playing segment 15 on playlist zero
2064 this.player.tech_.hls.media = this.player.tech_.hls.master.playlists[0];
2065 this.player.mediaIndex = 1;
2066
2067 testDataManifests['variant-update'] = '#EXTM3U\n' +
2068 '#EXT-X-MEDIA-SEQUENCE:16\n' +
2069 '#EXTINF:10,\n' +
2070 '16.ts\n' +
2071 '#EXTINF:10,\n' +
2072 '17.ts\n';
2073
2074 // switch playlists
2075 this.player.tech_.hls.selectPlaylist = function() {
2076 return this.player.tech_.hls.master.playlists[1];
2077 };
2078 // timeupdate downloads segment 16 then switches playlists
2079 this.player.trigger('timeupdate');
2080
2081 QUnit.strictEqual(this.player.mediaIndex, 1, 'mediaIndex points at the next segment');
2082 testDataManifests['variant-update'] = oldManifest;
2083 });
2084
2085 QUnit.test('if withCredentials global option is used, ' +
2086 'withCredentials is set on the XHR object',
2087 function() {
2088 let hlsOptions = videojs.options.hls; 2035 let hlsOptions = videojs.options.hls;
2089 2036
2090 this.player.dispose(); 2037 this.player.dispose();
...@@ -2102,9 +2049,7 @@ function() { ...@@ -2102,9 +2049,7 @@ function() {
2102 videojs.options.hls = hlsOptions; 2049 videojs.options.hls = hlsOptions;
2103 }); 2050 });
2104 2051
2105 QUnit.test('if withCredentials src option is used, ' + 2052 QUnit.test('if withCredentials src option is used, withCredentials is set on the XHR object', function() {
2106 'withCredentials is set on the XHR object',
2107 function() {
2108 this.player.dispose(); 2053 this.player.dispose();
2109 this.player = createPlayer(); 2054 this.player = createPlayer();
2110 this.player.src({ 2055 this.player.src({
...@@ -2131,27 +2076,6 @@ QUnit.test('src level credentials supersede the global options', function() { ...@@ -2131,27 +2076,6 @@ QUnit.test('src level credentials supersede the global options', function() {
2131 2076
2132 }); 2077 });
2133 2078
2134 QUnit.test('does not break if the playlist has no segments', function() {
2135 this.player.src({
2136 src: 'manifest/master.m3u8',
2137 type: 'application/vnd.apple.mpegurl'
2138 });
2139 try {
2140 openMediaSource(this.player, this.clock);
2141 this.requests[0].respond(200, null,
2142 '#EXTM3U\n' +
2143 '#EXT-X-PLAYLIST-TYPE:VOD\n' +
2144 '#EXT-X-TARGETDURATION:10\n');
2145 } catch (e) {
2146 QUnit.ok(false, 'an error was thrown');
2147 throw e;
2148 }
2149 QUnit.ok(true, 'no error was thrown');
2150 QUnit.strictEqual(this.requests.length,
2151 1,
2152 'no this.requestsfor non-existent segments were queued');
2153 });
2154
2155 QUnit.test('aborts segment processing on seek', function() { 2079 QUnit.test('aborts segment processing on seek', function() {
2156 let currentTime = 0; 2080 let currentTime = 0;
2157 2081
...@@ -2382,10 +2306,12 @@ QUnit.skip('seeking does not fail when targeted between segments', function() { ...@@ -2382,10 +2306,12 @@ QUnit.skip('seeking does not fail when targeted between segments', function() {
2382 2306
2383 // seek to a time that is greater than the last tag in segment 0 but 2307 // seek to a time that is greater than the last tag in segment 0 but
2384 // less than the first in segment 1 2308 // less than the first in segment 1
2309
2385 /* eslint-disable no-warning-comments */ 2310 /* eslint-disable no-warning-comments */
2386 // FIXME: it's not possible to seek here without timestamp-based 2311 // FIXME: it's not possible to seek here without timestamp-based
2387 /* eslint-enable no-warning-comments */
2388 // segment durations 2312 // segment durations
2313 /* eslint-enable no-warning-comments */
2314
2389 this.player.tech_.setCurrentTime(9.4); 2315 this.player.tech_.setCurrentTime(9.4);
2390 this.clock.tick(1); 2316 this.clock.tick(1);
2391 QUnit.equal(this.requests[0].url, segmentUrl, 'requested the later segment'); 2317 QUnit.equal(this.requests[0].url, segmentUrl, 'requested the later segment');
...@@ -2506,8 +2432,7 @@ QUnit.test('the source handler supports HLS mime types', function() { ...@@ -2506,8 +2432,7 @@ QUnit.test('the source handler supports HLS mime types', function() {
2506 QUnit.ok(videojs.HlsSourceHandler(techName).canHandleSource({ 2432 QUnit.ok(videojs.HlsSourceHandler(techName).canHandleSource({
2507 type: 'aPplicatiOn/VnD.aPPle.MpEgUrL' 2433 type: 'aPplicatiOn/VnD.aPPle.MpEgUrL'
2508 }), 'supports vnd.apple.mpegurl'); 2434 }), 'supports vnd.apple.mpegurl');
2509 QUnit.ok(videojs.HlsSourceHandler(techName) 2435 QUnit.ok(videojs.HlsSourceHandler(techName).canPlayType('aPplicatiOn/VnD.aPPle.MpEgUrL'),
2510 .canPlayType('aPplicatiOn/VnD.aPPle.MpEgUrL'),
2511 'supports vnd.apple.mpegurl'); 2436 'supports vnd.apple.mpegurl');
2512 QUnit.ok(videojs.HlsSourceHandler(techName).canPlayType('aPplicatiOn/x-MPegUrl'), 2437 QUnit.ok(videojs.HlsSourceHandler(techName).canPlayType('aPplicatiOn/x-MPegUrl'),
2513 'supports x-mpegurl'); 2438 'supports x-mpegurl');
...@@ -2560,9 +2485,7 @@ QUnit.test('fires loadstart manually if Flash is used', function() { ...@@ -2560,9 +2485,7 @@ QUnit.test('fires loadstart manually if Flash is used', function() {
2560 QUnit.test('has no effect if native HLS is available', function() { 2485 QUnit.test('has no effect if native HLS is available', function() {
2561 let player; 2486 let player;
2562 2487
2563 videojs.Hls.supportsNativeHls = function() { 2488 videojs.Hls.supportsNativeHls = true;
2564 return true;
2565 };
2566 player = createPlayer(); 2489 player = createPlayer();
2567 player.src({ 2490 player.src({
2568 src: 'http://example.com/manifest/master.m3u8', 2491 src: 'http://example.com/manifest/master.m3u8',
...@@ -2840,8 +2763,7 @@ QUnit.test('a new key XHR is created when a the segment is requested', function( ...@@ -2840,8 +2763,7 @@ QUnit.test('a new key XHR is created when a the segment is requested', function(
2840 'a key XHR is created with the correct uri'); 2763 'a key XHR is created with the correct uri');
2841 }); 2764 });
2842 2765
2843 QUnit.test('seeking should abort an outstanding key request and create a new one', 2766 QUnit.test('seeking should abort an outstanding key request and create a new one', function() {
2844 function() {
2845 this.player.src({ 2767 this.player.src({
2846 src: 'https://example.com/encrypted.m3u8', 2768 src: 'https://example.com/encrypted.m3u8',
2847 type: 'application/vnd.apple.mpegurl' 2769 type: 'application/vnd.apple.mpegurl'
...@@ -2874,7 +2796,7 @@ function() { ...@@ -2874,7 +2796,7 @@ function() {
2874 'urls should match'); 2796 'urls should match');
2875 }); 2797 });
2876 2798
2877 QUnit.test('retries key this.requestsonce upon failure', function() { 2799 QUnit.test('retries key requests once upon failure', function() {
2878 this.player.src({ 2800 this.player.src({
2879 src: 'https://example.com/encrypted.m3u8', 2801 src: 'https://example.com/encrypted.m3u8',
2880 type: 'application/vnd.apple.mpegurl' 2802 type: 'application/vnd.apple.mpegurl'
...@@ -2901,7 +2823,7 @@ QUnit.test('retries key this.requestsonce upon failure', function() { ...@@ -2901,7 +2823,7 @@ QUnit.test('retries key this.requestsonce upon failure', function() {
2901 QUnit.equal(this.requests.length, 2, 'gives up after one retry'); 2823 QUnit.equal(this.requests.length, 2, 'gives up after one retry');
2902 }); 2824 });
2903 2825
2904 QUnit.test('blacklists playlist if key this.requestsfail more than once', function() { 2826 QUnit.test('blacklists playlist if key requests fail more than once', function() {
2905 let bytes = []; 2827 let bytes = [];
2906 let media; 2828 let media;
2907 2829
...@@ -2975,9 +2897,8 @@ QUnit.test('the key is supplied to the decrypter in the correct format', functio ...@@ -2975,9 +2897,8 @@ QUnit.test('the key is supplied to the decrypter in the correct format', functio
2975 'passed the specified segment key'); 2897 'passed the specified segment key');
2976 2898
2977 }); 2899 });
2978 QUnit.test('supplies the media sequence of current segment' + 2900
2979 ' as the IV by default, if no IV is specified', 2901 QUnit.test('supplies the media sequence of current segment as the IV by default, if no IV is specified', function() {
2980 function() {
2981 let ivs = []; 2902 let ivs = [];
2982 2903
2983 this.player.src({ 2904 this.player.src({
...@@ -3012,8 +2933,7 @@ function() { ...@@ -3012,8 +2933,7 @@ function() {
3012 'the IV for the segment is the media sequence'); 2933 'the IV for the segment is the media sequence');
3013 }); 2934 });
3014 2935
3015 QUnit.test('switching playlists with an outstanding key request does not stall playback', 2936 QUnit.test('switching playlists with an outstanding key request does not stall playback', function() {
3016 function() {
3017 let buffered = []; 2937 let buffered = [];
3018 let media = '#EXTM3U\n' + 2938 let media = '#EXTM3U\n' +
3019 '#EXT-X-MEDIA-SEQUENCE:5\n' + 2939 '#EXT-X-MEDIA-SEQUENCE:5\n' +
...@@ -3084,8 +3004,7 @@ QUnit.test('resolves relative key URLs against the playlist', function() { ...@@ -3084,8 +3004,7 @@ QUnit.test('resolves relative key URLs against the playlist', function() {
3084 'resolves the key URL'); 3004 'resolves the key URL');
3085 }); 3005 });
3086 3006
3087 QUnit.test('treats invalid keys as a key request failure and blacklists playlist', 3007 QUnit.test('treats invalid keys as a key request failure and blacklists playlist', function() {
3088 function() {
3089 let bytes = []; 3008 let bytes = [];
3090 let media; 3009 let media;
3091 3010
...@@ -3142,7 +3061,7 @@ QUnit.test('live stream should not call endOfStream', function() { ...@@ -3142,7 +3061,7 @@ QUnit.test('live stream should not call endOfStream', function() {
3142 '#EXT-X-MEDIA-SEQUENCE:0\n' + 3061 '#EXT-X-MEDIA-SEQUENCE:0\n' +
3143 '#EXTINF:1\n' + 3062 '#EXTINF:1\n' +
3144 '0.ts\n'); 3063 '0.ts\n');
3145 this.requests[1].response = bcSegment; 3064 this.requests[1].response = new Uint8Array(1);
3146 this.requests[1].respond(200, null, ''); 3065 this.requests[1].respond(200, null, '');
3147 QUnit.equal('open', 3066 QUnit.equal('open',
3148 this.player.tech_.hls.mediaSource.readyState, 3067 this.player.tech_.hls.mediaSource.readyState,
...@@ -3255,4 +3174,3 @@ QUnit.test('detects time range end-point changed by updates', function() { ...@@ -3255,4 +3174,3 @@ QUnit.test('detects time range end-point changed by updates', function() {
3255 null); 3174 null);
3256 QUnit.strictEqual(edge, null, 'treat null update buffer as an empty TimeRanges object'); 3175 QUnit.strictEqual(edge, null, 'treat null update buffer as an empty TimeRanges object');
3257 }); 3176 });
3258
......