80a72cdf by David LaPalomento

Clean up the playlist loader when sources change

Make sure the playlist loader is immediately disposed when the source is changed instead of waiting for sourceopen. Previously, it was possible that the source would be changed and fillBuffer() would run before the new set of playlists had been loaded. In that case, HLS would download a segment from the old playlist and block drainBuffer() from making progress because playlist.segments in the segmentBuffer element would be undefined. Switch from using timeupdate to trigger buffering to using straight window.timeout which fixes #160 and fixes #133.
1 parent c849e312
...@@ -10,6 +10,9 @@ var ...@@ -10,6 +10,9 @@ var
10 // a fudge factor to apply to advertised playlist bitrates to account for 10 // a fudge factor to apply to advertised playlist bitrates to account for
11 // temporary flucations in client bandwidth 11 // temporary flucations in client bandwidth
12 bandwidthVariance = 1.1, 12 bandwidthVariance = 1.1,
13
14 // the amount of time to wait between checking the state of the buffer
15 bufferCheckInterval = 500,
13 keyXhr, 16 keyXhr,
14 keyFailed, 17 keyFailed,
15 resolveUrl; 18 resolveUrl;
...@@ -36,6 +39,11 @@ videojs.Hls = videojs.Flash.extend({ ...@@ -36,6 +39,11 @@ videojs.Hls = videojs.Flash.extend({
36 this.currentTime = videojs.Hls.prototype.currentTime; 39 this.currentTime = videojs.Hls.prototype.currentTime;
37 this.setCurrentTime = videojs.Hls.prototype.setCurrentTime; 40 this.setCurrentTime = videojs.Hls.prototype.setCurrentTime;
38 41
42 // periodically check if new data needs to be downloaded or
43 // buffered data should be appended to the source buffer
44 this.segmentBuffer_ = [];
45 this.startCheckingBuffer_();
46
39 videojs.Hls.prototype.src.call(this, options.source && options.source.src); 47 videojs.Hls.prototype.src.call(this, options.source && options.source.src);
40 } 48 }
41 }); 49 });
...@@ -49,7 +57,10 @@ videojs.Hls.GOAL_BUFFER_LENGTH = 30; ...@@ -49,7 +57,10 @@ videojs.Hls.GOAL_BUFFER_LENGTH = 30;
49 videojs.Hls.prototype.src = function(src) { 57 videojs.Hls.prototype.src = function(src) {
50 var 58 var
51 tech = this, 59 tech = this,
60 player = this.player(),
61 settings = player.options().hls || {},
52 mediaSource, 62 mediaSource,
63 oldMediaPlaylist,
53 source; 64 source;
54 65
55 // do nothing if the src is falsey 66 // do nothing if the src is falsey
...@@ -115,46 +126,13 @@ videojs.Hls.prototype.src = function(src) { ...@@ -115,46 +126,13 @@ videojs.Hls.prototype.src = function(src) {
115 // load the MediaSource into the player 126 // load the MediaSource into the player
116 this.mediaSource.addEventListener('sourceopen', videojs.bind(this, this.handleSourceOpen)); 127 this.mediaSource.addEventListener('sourceopen', videojs.bind(this, this.handleSourceOpen));
117 128
118 this.player().ready(function() { 129 // cleanup the old playlist loader, if necessary
119 // do nothing if the tech has been disposed already
120 // this can occur if someone sets the src in player.ready(), for instance
121 if (!tech.el()) {
122 return;
123 }
124 tech.el().vjs_src(source.src);
125 });
126 };
127
128 videojs.Hls.setMediaIndexForLive = function(selectedPlaylist) {
129 var tailIterator = selectedPlaylist.segments.length,
130 tailDuration = 0,
131 targetTail = (selectedPlaylist.targetDuration || 10) * 3;
132
133 while (tailDuration < targetTail && tailIterator > 0) {
134 tailDuration += selectedPlaylist.segments[tailIterator - 1].duration;
135 tailIterator--;
136 }
137
138 return tailIterator;
139 };
140
141 videojs.Hls.prototype.handleSourceOpen = function() {
142 // construct the video data buffer and set the appropriate MIME type
143 var
144 player = this.player(),
145 settings = player.options().hls || {},
146 sourceBuffer = this.mediaSource.addSourceBuffer('video/flv; codecs="vp6,aac"'),
147 oldMediaPlaylist;
148
149 this.sourceBuffer = sourceBuffer;
150 sourceBuffer.appendBuffer(this.segmentParser_.getFlvHeader());
151
152 this.mediaIndex = 0;
153
154 if (this.playlists) { 130 if (this.playlists) {
155 this.playlists.dispose(); 131 this.playlists.dispose();
156 } 132 }
157 133
134 this.mediaIndex = 0;
135
158 this.playlists = new videojs.Hls.PlaylistLoader(this.src_, settings.withCredentials); 136 this.playlists = new videojs.Hls.PlaylistLoader(this.src_, settings.withCredentials);
159 137
160 this.playlists.on('loadedmetadata', videojs.bind(this, function() { 138 this.playlists.on('loadedmetadata', videojs.bind(this, function() {
...@@ -164,11 +142,7 @@ videojs.Hls.prototype.handleSourceOpen = function() { ...@@ -164,11 +142,7 @@ videojs.Hls.prototype.handleSourceOpen = function() {
164 setupEvents = function() { 142 setupEvents = function() {
165 this.fillBuffer(); 143 this.fillBuffer();
166 144
167 // periodically check if new data needs to be downloaded or 145
168 // buffered data should be appended to the source buffer
169 player.on('timeupdate', videojs.bind(this, this.fillBuffer));
170 player.on('timeupdate', videojs.bind(this, this.drainBuffer));
171 player.on('waiting', videojs.bind(this, this.drainBuffer));
172 146
173 player.trigger('loadedmetadata'); 147 player.trigger('loadedmetadata');
174 }; 148 };
...@@ -255,6 +229,39 @@ videojs.Hls.prototype.handleSourceOpen = function() { ...@@ -255,6 +229,39 @@ videojs.Hls.prototype.handleSourceOpen = function() {
255 player.trigger('mediachange'); 229 player.trigger('mediachange');
256 })); 230 }));
257 231
232 this.player().ready(function() {
233 // do nothing if the tech has been disposed already
234 // this can occur if someone sets the src in player.ready(), for instance
235 if (!tech.el()) {
236 return;
237 }
238 tech.el().vjs_src(source.src);
239 });
240 };
241
242 videojs.Hls.setMediaIndexForLive = function(selectedPlaylist) {
243 var tailIterator = selectedPlaylist.segments.length,
244 tailDuration = 0,
245 targetTail = (selectedPlaylist.targetDuration || 10) * 3;
246
247 while (tailDuration < targetTail && tailIterator > 0) {
248 tailDuration += selectedPlaylist.segments[tailIterator - 1].duration;
249 tailIterator--;
250 }
251
252 return tailIterator;
253 };
254
255 videojs.Hls.prototype.handleSourceOpen = function() {
256 // construct the video data buffer and set the appropriate MIME type
257 var
258 player = this.player(),
259 sourceBuffer = this.mediaSource.addSourceBuffer('video/flv; codecs="vp6,aac"');
260
261 this.sourceBuffer = sourceBuffer;
262 sourceBuffer.appendBuffer(this.segmentParser_.getFlvHeader());
263
264
258 // if autoplay is enabled, begin playback. This is duplicative of 265 // if autoplay is enabled, begin playback. This is duplicative of
259 // code in video.js but is required because play() must be invoked 266 // code in video.js but is required because play() must be invoked
260 // *after* the media source has opened. 267 // *after* the media source has opened.
...@@ -379,10 +386,7 @@ videojs.Hls.prototype.cancelSegmentXhr = function() { ...@@ -379,10 +386,7 @@ videojs.Hls.prototype.cancelSegmentXhr = function() {
379 videojs.Hls.prototype.dispose = function() { 386 videojs.Hls.prototype.dispose = function() {
380 var player = this.player(); 387 var player = this.player();
381 388
382 // remove event handlers 389 this.stopCheckingBuffer_();
383 player.off('timeupdate', this.fillBuffer);
384 player.off('timeupdate', this.drainBuffer);
385 player.off('waiting', this.drainBuffer);
386 390
387 if (this.playlists) { 391 if (this.playlists) {
388 this.playlists.dispose(); 392 this.playlists.dispose();
...@@ -468,6 +472,46 @@ videojs.Hls.prototype.selectPlaylist = function () { ...@@ -468,6 +472,46 @@ videojs.Hls.prototype.selectPlaylist = function () {
468 }; 472 };
469 473
470 /** 474 /**
475 * Periodically request new segments and append video data.
476 */
477 videojs.Hls.prototype.checkBuffer_ = function() {
478 // calling this method directly resets any outstanding buffer checks
479 if (this.checkBufferTimeout_) {
480 window.clearTimeout(this.checkBufferTimeout_);
481 this.checkBufferTimeout_ = null;
482 }
483
484 this.fillBuffer();
485 this.drainBuffer();
486
487 // wait awhile and try again
488 this.checkBufferTimeout_ = window.setTimeout(videojs.bind(this, this.checkBuffer_),
489 bufferCheckInterval);
490 };
491
492 /**
493 * Setup a periodic task to request new segments if necessary and
494 * append bytes into the SourceBuffer.
495 */
496 videojs.Hls.prototype.startCheckingBuffer_ = function() {
497 // if the player ever stalls, check if there is video data available
498 // to append immediately
499 this.player().on('waiting', videojs.bind(this, this.drainBuffer));
500
501 this.checkBuffer_();
502 };
503
504 /**
505 * Stop the periodic task requesting new segments and feeding the
506 * SourceBuffer.
507 */
508 videojs.Hls.prototype.stopCheckingBuffer_ = function() {
509 window.clearTimeout(this.checkBufferTimeout_);
510 this.checkBufferTimeout_ = null;
511 this.player().off('waiting', this.drainBuffer);
512 };
513
514 /**
471 * Determines whether there is enough video data currently in the buffer 515 * Determines whether there is enough video data currently in the buffer
472 * and downloads a new segment if the buffered time is less than the goal. 516 * and downloads a new segment if the buffered time is less than the goal.
473 * @param offset (optional) {number} the offset into the downloaded segment 517 * @param offset (optional) {number} the offset into the downloaded segment
...@@ -481,6 +525,11 @@ videojs.Hls.prototype.fillBuffer = function(offset) { ...@@ -481,6 +525,11 @@ videojs.Hls.prototype.fillBuffer = function(offset) {
481 segment, 525 segment,
482 segmentUri; 526 segmentUri;
483 527
528 // if a video has not been specified, do nothing
529 if (!player.currentSrc() || !this.playlists) {
530 return;
531 }
532
484 // if there is a request already in flight, do nothing 533 // if there is a request already in flight, do nothing
485 if (this.segmentXhr_) { 534 if (this.segmentXhr_) {
486 return; 535 return;
...@@ -488,6 +537,7 @@ videojs.Hls.prototype.fillBuffer = function(offset) { ...@@ -488,6 +537,7 @@ videojs.Hls.prototype.fillBuffer = function(offset) {
488 537
489 // if no segments are available, do nothing 538 // if no segments are available, do nothing
490 if (this.playlists.state === "HAVE_NOTHING" || 539 if (this.playlists.state === "HAVE_NOTHING" ||
540 !this.playlists.media() ||
491 !this.playlists.media().segments) { 541 !this.playlists.media().segments) {
492 return; 542 return;
493 } 543 }
......
...@@ -114,6 +114,16 @@ var ...@@ -114,6 +114,16 @@ var
114 on: Function.prototype 114 on: Function.prototype
115 }; 115 };
116 }; 116 };
117 },
118
119 // return an absolute version of a page-relative URL
120 absoluteUrl = function(relativeUrl) {
121 return window.location.origin +
122 (window.location.pathname
123 .split('/')
124 .slice(0, -1)
125 .concat(relativeUrl)
126 .join('/'));
117 }; 127 };
118 128
119 module('HLS', { 129 module('HLS', {
...@@ -207,6 +217,40 @@ test('creates a PlaylistLoader on init', function() { ...@@ -207,6 +217,40 @@ test('creates a PlaylistLoader on init', function() {
207 'the playlist is selected'); 217 'the playlist is selected');
208 }); 218 });
209 219
220 test('re-initializes the playlist loader when switching sources', function() {
221 // source is set
222 player.src({
223 src:'manifest/media.m3u8',
224 type: 'application/vnd.apple.mpegurl'
225 });
226 openMediaSource(player);
227 // loader gets media playlist
228 standardXHRResponse(requests.shift());
229 // request a segment
230 standardXHRResponse(requests.shift());
231 // change the source
232 player.src({
233 src:'manifest/master.m3u8',
234 type: 'application/vnd.apple.mpegurl'
235 });
236 ok(!player.hls.playlists.media(), 'no media playlist');
237 equal(player.hls.playlists.state,
238 'HAVE_NOTHING',
239 'reset the playlist loader state');
240 equal(requests.length, 1, 'requested the new src');
241
242 // buffer check
243 player.hls.checkBuffer_();
244 equal(requests.length, 1, 'did not request a stale segment');
245
246 // sourceopen
247 openMediaSource(player);
248
249 equal(requests.length, 1, 'made one request');
250 ok(requests[0].url.indexOf('master.m3u8') >= 0, 'requested only the new playlist');
251 });
252
253
210 test('sets the duration if one is available on the playlist', function() { 254 test('sets the duration if one is available on the playlist', function() {
211 var calls = 0; 255 var calls = 0;
212 player.duration = function(value) { 256 player.duration = function(value) {
...@@ -261,9 +305,7 @@ test('starts downloading a segment on loadedmetadata', function() { ...@@ -261,9 +305,7 @@ test('starts downloading a segment on loadedmetadata', function() {
261 standardXHRResponse(requests[0]); 305 standardXHRResponse(requests[0]);
262 standardXHRResponse(requests[1]); 306 standardXHRResponse(requests[1]);
263 strictEqual(requests[1].url, 307 strictEqual(requests[1].url,
264 window.location.origin + 308 absoluteUrl('manifest/media-00001.ts'),
265 window.location.pathname.split('/').slice(0, -1).join('/') +
266 '/manifest/media-00001.ts',
267 'the first segment is requested'); 309 'the first segment is requested');
268 }); 310 });
269 311
...@@ -359,14 +401,10 @@ test('downloads media playlists after loading the master', function() { ...@@ -359,14 +401,10 @@ test('downloads media playlists after loading the master', function() {
359 401
360 strictEqual(requests[0].url, 'manifest/master.m3u8', 'master playlist requested'); 402 strictEqual(requests[0].url, 'manifest/master.m3u8', 'master playlist requested');
361 strictEqual(requests[1].url, 403 strictEqual(requests[1].url,
362 window.location.origin + 404 absoluteUrl('manifest/media.m3u8'),
363 window.location.pathname.split('/').slice(0, -1).join('/') +
364 '/manifest/media.m3u8',
365 'media playlist requested'); 405 'media playlist requested');
366 strictEqual(requests[2].url, 406 strictEqual(requests[2].url,
367 window.location.origin + 407 absoluteUrl('manifest/media-00001.ts'),
368 window.location.pathname.split('/').slice(0, -1).join('/') +
369 '/manifest/media-00001.ts',
370 'first segment requested'); 408 'first segment requested');
371 }); 409 });
372 410
...@@ -390,19 +428,13 @@ test('upshift if initial bandwidth is high', function() { ...@@ -390,19 +428,13 @@ test('upshift if initial bandwidth is high', function() {
390 428
391 strictEqual(requests[0].url, 'manifest/master.m3u8', 'master playlist requested'); 429 strictEqual(requests[0].url, 'manifest/master.m3u8', 'master playlist requested');
392 strictEqual(requests[1].url, 430 strictEqual(requests[1].url,
393 window.location.origin + 431 absoluteUrl('manifest/media.m3u8'),
394 window.location.pathname.split('/').slice(0, -1).join('/') +
395 '/manifest/media.m3u8',
396 'media playlist requested'); 432 'media playlist requested');
397 strictEqual(requests[2].url, 433 strictEqual(requests[2].url,
398 window.location.origin + 434 absoluteUrl('manifest/media3.m3u8'),
399 window.location.pathname.split('/').slice(0, -1).join('/') +
400 '/manifest/media3.m3u8',
401 'media playlist requested'); 435 'media playlist requested');
402 strictEqual(requests[3].url, 436 strictEqual(requests[3].url,
403 window.location.origin + 437 absoluteUrl('manifest/media3-00001.ts'),
404 window.location.pathname.split('/').slice(0, -1).join('/') +
405 '/manifest/media3-00001.ts',
406 'first segment requested'); 438 'first segment requested');
407 }); 439 });
408 440
...@@ -424,27 +456,53 @@ test('dont downshift if bandwidth is low', function() { ...@@ -424,27 +456,53 @@ test('dont downshift if bandwidth is low', function() {
424 456
425 strictEqual(requests[0].url, 'manifest/master.m3u8', 'master playlist requested'); 457 strictEqual(requests[0].url, 'manifest/master.m3u8', 'master playlist requested');
426 strictEqual(requests[1].url, 458 strictEqual(requests[1].url,
427 window.location.origin + 459 absoluteUrl('manifest/media.m3u8'),
428 window.location.pathname.split('/').slice(0, -1).join('/') +
429 '/manifest/media.m3u8',
430 'media playlist requested'); 460 'media playlist requested');
431 strictEqual(requests[2].url, 461 strictEqual(requests[2].url,
432 window.location.origin + 462 absoluteUrl('manifest/media-00001.ts'),
433 window.location.pathname.split('/').slice(0, -1).join('/') +
434 '/manifest/media-00001.ts',
435 'first segment requested'); 463 'first segment requested');
436 }); 464 });
437 465
438 test('timeupdates do not check to fill the buffer until a media playlist is ready', function() { 466 test('buffer checks are noops until a media playlist is ready', function() {
439 player.src({ 467 player.src({
440 src: 'manifest/media.m3u8', 468 src: 'manifest/media.m3u8',
441 type: 'application/vnd.apple.mpegurl' 469 type: 'application/vnd.apple.mpegurl'
442 }); 470 });
443 openMediaSource(player); 471 openMediaSource(player);
444 player.trigger('timeupdate'); 472 player.hls.checkBuffer_();
473
474 strictEqual(1, requests.length, 'one request was made');
475 strictEqual(requests[0].url, 'manifest/media.m3u8', 'media playlist requested');
476 });
477
478 test('buffer checks are noops when only the master is ready', function() {
479 player.src({
480 src: 'manifest/master.m3u8',
481 type: 'application/vnd.apple.mpegurl'
482 });
483 openMediaSource(player);
484 standardXHRResponse(requests.shift());
485 standardXHRResponse(requests.shift());
486 // ignore any outstanding segment requests
487 requests.length = 0;
488
489 // load in a new playlist which will cause playlists.media() to be
490 // undefined while it is being fetched
491 player.src({
492 src: 'manifest/master.m3u8',
493 type: 'application/vnd.apple.mpegurl'
494 });
495 openMediaSource(player);
496
497 // respond with the master playlist but don't send the media playlist yet
498 standardXHRResponse(requests.shift());
499 // trigger fillBuffer()
500 player.hls.checkBuffer_();
445 501
446 strictEqual(1, requests.length, 'one request was made'); 502 strictEqual(1, requests.length, 'one request was made');
447 strictEqual('manifest/media.m3u8', requests[0].url, 'media playlist requested'); 503 strictEqual(requests[0].url,
504 absoluteUrl('manifest/media.m3u8'),
505 'media playlist requested');
448 }); 506 });
449 507
450 test('calculates the bandwidth after downloading a segment', function() { 508 test('calculates the bandwidth after downloading a segment', function() {
...@@ -493,7 +551,7 @@ test('selects a playlist after segment downloads', function() { ...@@ -493,7 +551,7 @@ test('selects a playlist after segment downloads', function() {
493 player.buffered = function() { 551 player.buffered = function() {
494 return videojs.createTimeRange(0, 2); 552 return videojs.createTimeRange(0, 2);
495 }; 553 };
496 player.trigger('timeupdate'); 554 player.hls.checkBuffer_();
497 555
498 standardXHRResponse(requests[3]); 556 standardXHRResponse(requests[3]);
499 557
...@@ -587,10 +645,7 @@ test('downloads additional playlists if required', function() { ...@@ -587,10 +645,7 @@ test('downloads additional playlists if required', function() {
587 645
588 strictEqual(4, requests.length, 'requests were made'); 646 strictEqual(4, requests.length, 'requests were made');
589 strictEqual(requests[3].url, 647 strictEqual(requests[3].url,
590 window.location.origin + 648 absoluteUrl('manifest/' + playlist.uri),
591 window.location.pathname.split('/').slice(0, -1).join('/') +
592 '/manifest/' +
593 playlist.uri,
594 'made playlist request'); 649 'made playlist request');
595 strictEqual(playlist.uri, 650 strictEqual(playlist.uri,
596 player.hls.playlists.media().uri, 651 player.hls.playlists.media().uri,
...@@ -766,15 +821,13 @@ test('downloads the next segment if the buffer is getting low', function() { ...@@ -766,15 +821,13 @@ test('downloads the next segment if the buffer is getting low', function() {
766 player.buffered = function() { 821 player.buffered = function() {
767 return videojs.createTimeRange(0, 19.999); 822 return videojs.createTimeRange(0, 19.999);
768 }; 823 };
769 player.trigger('timeupdate'); 824 player.hls.checkBuffer_();
770 825
771 standardXHRResponse(requests[2]); 826 standardXHRResponse(requests[2]);
772 827
773 strictEqual(requests.length, 3, 'made a request'); 828 strictEqual(requests.length, 3, 'made a request');
774 strictEqual(requests[2].url, 829 strictEqual(requests[2].url,
775 window.location.origin + 830 absoluteUrl('manifest/media-00002.ts'),
776 window.location.pathname.split('/').slice(0, -1).join('/') +
777 '/manifest/media-00002.ts',
778 'made segment request'); 831 'made segment request');
779 }); 832 });
780 833
...@@ -1267,7 +1320,7 @@ test('waits until the buffer is empty before appending bytes at a discontinuity' ...@@ -1267,7 +1320,7 @@ test('waits until the buffer is empty before appending bytes at a discontinuity'
1267 // play to 6s to trigger the next segment request 1320 // play to 6s to trigger the next segment request
1268 currentTime = 6; 1321 currentTime = 6;
1269 bufferEnd = 10; 1322 bufferEnd = 10;
1270 player.trigger('timeupdate'); 1323 player.hls.checkBuffer_();
1271 strictEqual(aborts, 0, 'no aborts before the buffer empties'); 1324 strictEqual(aborts, 0, 'no aborts before the buffer empties');
1272 1325
1273 standardXHRResponse(requests.pop()); 1326 standardXHRResponse(requests.pop());
...@@ -1315,7 +1368,7 @@ test('clears the segment buffer on seek', function() { ...@@ -1315,7 +1368,7 @@ test('clears the segment buffer on seek', function() {
1315 // play to 6s to trigger the next segment request 1368 // play to 6s to trigger the next segment request
1316 currentTime = 6; 1369 currentTime = 6;
1317 bufferEnd = 10; 1370 bufferEnd = 10;
1318 player.trigger('timeupdate'); 1371 player.hls.checkBuffer_();
1319 1372
1320 standardXHRResponse(requests.pop()); 1373 standardXHRResponse(requests.pop());
1321 1374
...@@ -1365,7 +1418,7 @@ test('continues playing after seek to discontinuity', function() { ...@@ -1365,7 +1418,7 @@ test('continues playing after seek to discontinuity', function() {
1365 1418
1366 currentTime = 1; 1419 currentTime = 1;
1367 bufferEnd = 10; 1420 bufferEnd = 10;
1368 player.trigger('timeupdate'); 1421 player.hls.checkBuffer_();
1369 1422
1370 standardXHRResponse(requests.pop()); 1423 standardXHRResponse(requests.pop());
1371 1424
...@@ -1435,10 +1488,7 @@ test('remove event handlers on dispose', function() { ...@@ -1435,10 +1488,7 @@ test('remove event handlers on dispose', function() {
1435 oldOn.call(player, type, handler); 1488 oldOn.call(player, type, handler);
1436 }; 1489 };
1437 player.off = function(type, handler) { 1490 player.off = function(type, handler) {
1438 // ignore the top-level videojs removals that aren't relevant to HLS
1439 if (type && type !== 'dispose') {
1440 offhandlers++; 1491 offhandlers++;
1441 }
1442 oldOff.call(player, type, handler); 1492 oldOff.call(player, type, handler);
1443 }; 1493 };
1444 player.src({ 1494 player.src({
...@@ -1452,10 +1502,8 @@ test('remove event handlers on dispose', function() { ...@@ -1452,10 +1502,8 @@ test('remove event handlers on dispose', function() {
1452 1502
1453 player.dispose(); 1503 player.dispose();
1454 1504
1455 equal(offhandlers, onhandlers, 'the amount of on and off handlers is the same'); 1505 ok(offhandlers > onhandlers, 'more handlers were removed than were registered');
1456 1506 equal(offhandlers - onhandlers, 1, 'one handler was registered during init');
1457 player.off = oldOff;
1458 player.on = oldOn;
1459 }); 1507 });
1460 1508
1461 test('aborts the source buffer on disposal', function() { 1509 test('aborts the source buffer on disposal', function() {
...@@ -1538,7 +1586,7 @@ test('tracks the bytes downloaded', function() { ...@@ -1538,7 +1586,7 @@ test('tracks the bytes downloaded', function() {
1538 1586
1539 strictEqual(player.hls.bytesReceived, 17, 'tracked bytes received'); 1587 strictEqual(player.hls.bytesReceived, 17, 'tracked bytes received');
1540 1588
1541 player.trigger('timeupdate'); 1589 player.hls.checkBuffer_();
1542 1590
1543 // transmit some more 1591 // transmit some more
1544 requests[0].response = new ArrayBuffer(5); 1592 requests[0].response = new ArrayBuffer(5);
...@@ -1918,7 +1966,7 @@ test('skip segments if key requests fail more than once', function() { ...@@ -1918,7 +1966,7 @@ test('skip segments if key requests fail more than once', function() {
1918 1966
1919 player.hls.playlists.trigger('loadedplaylist'); 1967 player.hls.playlists.trigger('loadedplaylist');
1920 1968
1921 player.trigger('timeupdate'); 1969 player.hls.checkBuffer_();
1922 1970
1923 // respond to ts segment 1971 // respond to ts segment
1924 standardXHRResponse(requests.pop()); 1972 standardXHRResponse(requests.pop());
...@@ -1934,7 +1982,7 @@ test('skip segments if key requests fail more than once', function() { ...@@ -1934,7 +1982,7 @@ test('skip segments if key requests fail more than once', function() {
1934 1982
1935 equal(bytes.length, 1, 'bytes from the ts segments should not be added'); 1983 equal(bytes.length, 1, 'bytes from the ts segments should not be added');
1936 1984
1937 player.trigger('timeupdate'); 1985 player.hls.checkBuffer_();
1938 1986
1939 tags.length = 0; 1987 tags.length = 0;
1940 tags.push({pts: 0, bytes: 1}); 1988 tags.push({pts: 0, bytes: 1});
...@@ -2106,7 +2154,7 @@ test('treats invalid keys as a key request failure', function() { ...@@ -2106,7 +2154,7 @@ test('treats invalid keys as a key request failure', function() {
2106 requests.shift().respond(200, null, ''); 2154 requests.shift().respond(200, null, '');
2107 2155
2108 // the first segment should be dropped and playback moves on 2156 // the first segment should be dropped and playback moves on
2109 player.trigger('timeupdate'); 2157 player.hls.checkBuffer_();
2110 equal(bytes.length, 1, 'did not append bytes'); 2158 equal(bytes.length, 1, 'did not append bytes');
2111 equal(bytes[0], 'flv', 'appended the flv header'); 2159 equal(bytes[0], 'flv', 'appended the flv header');
2112 2160
......