aacd01ed by David LaPalomento

Merge pull request #223 from videojs/hotfix/src-changes

Clean up the playlist loader when sources change
2 parents c849e312 88fba5f3
...@@ -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.
...@@ -377,12 +384,7 @@ videojs.Hls.prototype.cancelSegmentXhr = function() { ...@@ -377,12 +384,7 @@ videojs.Hls.prototype.cancelSegmentXhr = function() {
377 * Abort all outstanding work and cleanup. 384 * Abort all outstanding work and cleanup.
378 */ 385 */
379 videojs.Hls.prototype.dispose = function() { 386 videojs.Hls.prototype.dispose = function() {
380 var player = this.player(); 387 this.stopCheckingBuffer_();
381
382 // remove event handlers
383 player.off('timeupdate', this.fillBuffer);
384 player.off('timeupdate', this.drainBuffer);
385 player.off('waiting', this.drainBuffer);
386 388
387 if (this.playlists) { 389 if (this.playlists) {
388 this.playlists.dispose(); 390 this.playlists.dispose();
...@@ -468,6 +470,46 @@ videojs.Hls.prototype.selectPlaylist = function () { ...@@ -468,6 +470,46 @@ videojs.Hls.prototype.selectPlaylist = function () {
468 }; 470 };
469 471
470 /** 472 /**
473 * Periodically request new segments and append video data.
474 */
475 videojs.Hls.prototype.checkBuffer_ = function() {
476 // calling this method directly resets any outstanding buffer checks
477 if (this.checkBufferTimeout_) {
478 window.clearTimeout(this.checkBufferTimeout_);
479 this.checkBufferTimeout_ = null;
480 }
481
482 this.fillBuffer();
483 this.drainBuffer();
484
485 // wait awhile and try again
486 this.checkBufferTimeout_ = window.setTimeout(videojs.bind(this, this.checkBuffer_),
487 bufferCheckInterval);
488 };
489
490 /**
491 * Setup a periodic task to request new segments if necessary and
492 * append bytes into the SourceBuffer.
493 */
494 videojs.Hls.prototype.startCheckingBuffer_ = function() {
495 // if the player ever stalls, check if there is video data available
496 // to append immediately
497 this.player().on('waiting', videojs.bind(this, this.drainBuffer));
498
499 this.checkBuffer_();
500 };
501
502 /**
503 * Stop the periodic task requesting new segments and feeding the
504 * SourceBuffer.
505 */
506 videojs.Hls.prototype.stopCheckingBuffer_ = function() {
507 window.clearTimeout(this.checkBufferTimeout_);
508 this.checkBufferTimeout_ = null;
509 this.player().off('waiting', this.drainBuffer);
510 };
511
512 /**
471 * Determines whether there is enough video data currently in the buffer 513 * 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. 514 * 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 515 * @param offset (optional) {number} the offset into the downloaded segment
...@@ -481,6 +523,11 @@ videojs.Hls.prototype.fillBuffer = function(offset) { ...@@ -481,6 +523,11 @@ videojs.Hls.prototype.fillBuffer = function(offset) {
481 segment, 523 segment,
482 segmentUri; 524 segmentUri;
483 525
526 // if a video has not been specified, do nothing
527 if (!player.currentSrc() || !this.playlists) {
528 return;
529 }
530
484 // if there is a request already in flight, do nothing 531 // if there is a request already in flight, do nothing
485 if (this.segmentXhr_) { 532 if (this.segmentXhr_) {
486 return; 533 return;
...@@ -488,6 +535,7 @@ videojs.Hls.prototype.fillBuffer = function(offset) { ...@@ -488,6 +535,7 @@ videojs.Hls.prototype.fillBuffer = function(offset) {
488 535
489 // if no segments are available, do nothing 536 // if no segments are available, do nothing
490 if (this.playlists.state === "HAVE_NOTHING" || 537 if (this.playlists.state === "HAVE_NOTHING" ||
538 !this.playlists.media() ||
491 !this.playlists.media().segments) { 539 !this.playlists.media().segments) {
492 return; 540 return;
493 } 541 }
......
...@@ -26,6 +26,7 @@ var ...@@ -26,6 +26,7 @@ var
26 oldMediaSourceOpen, 26 oldMediaSourceOpen,
27 oldSegmentParser, 27 oldSegmentParser,
28 oldSetTimeout, 28 oldSetTimeout,
29 oldClearTimeout,
29 oldSourceBuffer, 30 oldSourceBuffer,
30 oldFlashSupported, 31 oldFlashSupported,
31 oldNativeHlsSupport, 32 oldNativeHlsSupport,
...@@ -114,6 +115,16 @@ var ...@@ -114,6 +115,16 @@ var
114 on: Function.prototype 115 on: Function.prototype
115 }; 116 };
116 }; 117 };
118 },
119
120 // return an absolute version of a page-relative URL
121 absoluteUrl = function(relativeUrl) {
122 return window.location.origin +
123 (window.location.pathname
124 .split('/')
125 .slice(0, -1)
126 .concat(relativeUrl)
127 .join('/'));
117 }; 128 };
118 129
119 module('HLS', { 130 module('HLS', {
...@@ -136,6 +147,7 @@ module('HLS', { ...@@ -136,6 +147,7 @@ module('HLS', {
136 // store functionality that some tests need to mock 147 // store functionality that some tests need to mock
137 oldSegmentParser = videojs.Hls.SegmentParser; 148 oldSegmentParser = videojs.Hls.SegmentParser;
138 oldSetTimeout = window.setTimeout; 149 oldSetTimeout = window.setTimeout;
150 oldClearTimeout = window.clearTimeout;
139 151
140 oldNativeHlsSupport = videojs.Hls.supportsNativeHls; 152 oldNativeHlsSupport = videojs.Hls.supportsNativeHls;
141 153
...@@ -156,7 +168,6 @@ module('HLS', { ...@@ -156,7 +168,6 @@ module('HLS', {
156 }, 168 },
157 169
158 teardown: function() { 170 teardown: function() {
159 player.dispose();
160 videojs.Flash.isSupported = oldFlashSupported; 171 videojs.Flash.isSupported = oldFlashSupported;
161 videojs.MediaSource.open = oldMediaSourceOpen; 172 videojs.MediaSource.open = oldMediaSourceOpen;
162 videojs.Hls.SegmentParser = oldSegmentParser; 173 videojs.Hls.SegmentParser = oldSegmentParser;
...@@ -164,6 +175,8 @@ module('HLS', { ...@@ -164,6 +175,8 @@ module('HLS', {
164 videojs.Hls.decrypt = oldDecrypt; 175 videojs.Hls.decrypt = oldDecrypt;
165 videojs.SourceBuffer = oldSourceBuffer; 176 videojs.SourceBuffer = oldSourceBuffer;
166 window.setTimeout = oldSetTimeout; 177 window.setTimeout = oldSetTimeout;
178 window.clearTimeout = oldClearTimeout;
179 player.dispose();
167 xhr.restore(); 180 xhr.restore();
168 } 181 }
169 }); 182 });
...@@ -207,6 +220,39 @@ test('creates a PlaylistLoader on init', function() { ...@@ -207,6 +220,39 @@ test('creates a PlaylistLoader on init', function() {
207 'the playlist is selected'); 220 'the playlist is selected');
208 }); 221 });
209 222
223 test('re-initializes the playlist loader when switching sources', function() {
224 // source is set
225 player.src({
226 src:'manifest/media.m3u8',
227 type: 'application/vnd.apple.mpegurl'
228 });
229 openMediaSource(player);
230 // loader gets media playlist
231 standardXHRResponse(requests.shift());
232 // request a segment
233 standardXHRResponse(requests.shift());
234 // change the source
235 player.src({
236 src:'manifest/master.m3u8',
237 type: 'application/vnd.apple.mpegurl'
238 });
239 ok(!player.hls.playlists.media(), 'no media playlist');
240 equal(player.hls.playlists.state,
241 'HAVE_NOTHING',
242 'reset the playlist loader state');
243 equal(requests.length, 1, 'requested the new src');
244
245 // buffer check
246 player.hls.checkBuffer_();
247 equal(requests.length, 1, 'did not request a stale segment');
248
249 // sourceopen
250 openMediaSource(player);
251
252 equal(requests.length, 1, 'made one request');
253 ok(requests[0].url.indexOf('master.m3u8') >= 0, 'requested only the new playlist');
254 });
255
210 test('sets the duration if one is available on the playlist', function() { 256 test('sets the duration if one is available on the playlist', function() {
211 var calls = 0; 257 var calls = 0;
212 player.duration = function(value) { 258 player.duration = function(value) {
...@@ -261,9 +307,7 @@ test('starts downloading a segment on loadedmetadata', function() { ...@@ -261,9 +307,7 @@ test('starts downloading a segment on loadedmetadata', function() {
261 standardXHRResponse(requests[0]); 307 standardXHRResponse(requests[0]);
262 standardXHRResponse(requests[1]); 308 standardXHRResponse(requests[1]);
263 strictEqual(requests[1].url, 309 strictEqual(requests[1].url,
264 window.location.origin + 310 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'); 311 'the first segment is requested');
268 }); 312 });
269 313
...@@ -359,14 +403,10 @@ test('downloads media playlists after loading the master', function() { ...@@ -359,14 +403,10 @@ test('downloads media playlists after loading the master', function() {
359 403
360 strictEqual(requests[0].url, 'manifest/master.m3u8', 'master playlist requested'); 404 strictEqual(requests[0].url, 'manifest/master.m3u8', 'master playlist requested');
361 strictEqual(requests[1].url, 405 strictEqual(requests[1].url,
362 window.location.origin + 406 absoluteUrl('manifest/media.m3u8'),
363 window.location.pathname.split('/').slice(0, -1).join('/') +
364 '/manifest/media.m3u8',
365 'media playlist requested'); 407 'media playlist requested');
366 strictEqual(requests[2].url, 408 strictEqual(requests[2].url,
367 window.location.origin + 409 absoluteUrl('manifest/media-00001.ts'),
368 window.location.pathname.split('/').slice(0, -1).join('/') +
369 '/manifest/media-00001.ts',
370 'first segment requested'); 410 'first segment requested');
371 }); 411 });
372 412
...@@ -390,19 +430,13 @@ test('upshift if initial bandwidth is high', function() { ...@@ -390,19 +430,13 @@ test('upshift if initial bandwidth is high', function() {
390 430
391 strictEqual(requests[0].url, 'manifest/master.m3u8', 'master playlist requested'); 431 strictEqual(requests[0].url, 'manifest/master.m3u8', 'master playlist requested');
392 strictEqual(requests[1].url, 432 strictEqual(requests[1].url,
393 window.location.origin + 433 absoluteUrl('manifest/media.m3u8'),
394 window.location.pathname.split('/').slice(0, -1).join('/') +
395 '/manifest/media.m3u8',
396 'media playlist requested'); 434 'media playlist requested');
397 strictEqual(requests[2].url, 435 strictEqual(requests[2].url,
398 window.location.origin + 436 absoluteUrl('manifest/media3.m3u8'),
399 window.location.pathname.split('/').slice(0, -1).join('/') +
400 '/manifest/media3.m3u8',
401 'media playlist requested'); 437 'media playlist requested');
402 strictEqual(requests[3].url, 438 strictEqual(requests[3].url,
403 window.location.origin + 439 absoluteUrl('manifest/media3-00001.ts'),
404 window.location.pathname.split('/').slice(0, -1).join('/') +
405 '/manifest/media3-00001.ts',
406 'first segment requested'); 440 'first segment requested');
407 }); 441 });
408 442
...@@ -424,27 +458,96 @@ test('dont downshift if bandwidth is low', function() { ...@@ -424,27 +458,96 @@ test('dont downshift if bandwidth is low', function() {
424 458
425 strictEqual(requests[0].url, 'manifest/master.m3u8', 'master playlist requested'); 459 strictEqual(requests[0].url, 'manifest/master.m3u8', 'master playlist requested');
426 strictEqual(requests[1].url, 460 strictEqual(requests[1].url,
427 window.location.origin + 461 absoluteUrl('manifest/media.m3u8'),
428 window.location.pathname.split('/').slice(0, -1).join('/') +
429 '/manifest/media.m3u8',
430 'media playlist requested'); 462 'media playlist requested');
431 strictEqual(requests[2].url, 463 strictEqual(requests[2].url,
432 window.location.origin + 464 absoluteUrl('manifest/media-00001.ts'),
433 window.location.pathname.split('/').slice(0, -1).join('/') +
434 '/manifest/media-00001.ts',
435 'first segment requested'); 465 'first segment requested');
436 }); 466 });
437 467
438 test('timeupdates do not check to fill the buffer until a media playlist is ready', function() { 468 test('starts checking the buffer on init', function() {
469 var player, i, calls, callbacks = [], fills = 0, drains = 0;
470 // capture timeouts
471 window.setTimeout = function(callback) {
472 callbacks.push(callback);
473 return callbacks.length - 1;
474 };
475 window.clearTimeout = function(index) {
476 callbacks[index] = Function.prototype;
477 };
478
479 player = createPlayer();
480 player.hls.fillBuffer = function() {
481 fills++;
482 };
483 player.hls.drainBuffer = function() {
484 drains++;
485 };
486 player.src({
487 src: 'manifest/master.m3u8',
488 type: 'application/vnd.apple.mpegurl'
489 });
490 ok(callbacks.length > 0, 'set timeouts');
491
492 // run the initial set of callbacks. this should cause
493 // fill/drainBuffer to be run
494 calls = callbacks.slice();
495 for (i = 0; i < calls.length; i++) {
496 calls[i]();
497 }
498 equal(fills, 1, 'called fillBuffer');
499 equal(drains, 1, 'called drainBuffer');
500
501 player.dispose();
502 // the remaining callbacks do not run any buffer checks
503 calls = callbacks.slice();
504 for (i = 0; i < calls.length; i++) {
505 calls[i]();
506 }
507 equal(fills, 1, 'did not call fillBuffer again');
508 equal(drains, 1, 'did not call drainBuffer again');
509 });
510
511 test('buffer checks are noops until a media playlist is ready', function() {
439 player.src({ 512 player.src({
440 src: 'manifest/media.m3u8', 513 src: 'manifest/media.m3u8',
441 type: 'application/vnd.apple.mpegurl' 514 type: 'application/vnd.apple.mpegurl'
442 }); 515 });
443 openMediaSource(player); 516 openMediaSource(player);
444 player.trigger('timeupdate'); 517 player.hls.checkBuffer_();
518
519 strictEqual(1, requests.length, 'one request was made');
520 strictEqual(requests[0].url, 'manifest/media.m3u8', 'media playlist requested');
521 });
522
523 test('buffer checks are noops when only the master is ready', function() {
524 player.src({
525 src: 'manifest/master.m3u8',
526 type: 'application/vnd.apple.mpegurl'
527 });
528 openMediaSource(player);
529 standardXHRResponse(requests.shift());
530 standardXHRResponse(requests.shift());
531 // ignore any outstanding segment requests
532 requests.length = 0;
533
534 // load in a new playlist which will cause playlists.media() to be
535 // undefined while it is being fetched
536 player.src({
537 src: 'manifest/master.m3u8',
538 type: 'application/vnd.apple.mpegurl'
539 });
540 openMediaSource(player);
541
542 // respond with the master playlist but don't send the media playlist yet
543 standardXHRResponse(requests.shift());
544 // trigger fillBuffer()
545 player.hls.checkBuffer_();
445 546
446 strictEqual(1, requests.length, 'one request was made'); 547 strictEqual(1, requests.length, 'one request was made');
447 strictEqual('manifest/media.m3u8', requests[0].url, 'media playlist requested'); 548 strictEqual(requests[0].url,
549 absoluteUrl('manifest/media.m3u8'),
550 'media playlist requested');
448 }); 551 });
449 552
450 test('calculates the bandwidth after downloading a segment', function() { 553 test('calculates the bandwidth after downloading a segment', function() {
...@@ -493,7 +596,7 @@ test('selects a playlist after segment downloads', function() { ...@@ -493,7 +596,7 @@ test('selects a playlist after segment downloads', function() {
493 player.buffered = function() { 596 player.buffered = function() {
494 return videojs.createTimeRange(0, 2); 597 return videojs.createTimeRange(0, 2);
495 }; 598 };
496 player.trigger('timeupdate'); 599 player.hls.checkBuffer_();
497 600
498 standardXHRResponse(requests[3]); 601 standardXHRResponse(requests[3]);
499 602
...@@ -587,10 +690,7 @@ test('downloads additional playlists if required', function() { ...@@ -587,10 +690,7 @@ test('downloads additional playlists if required', function() {
587 690
588 strictEqual(4, requests.length, 'requests were made'); 691 strictEqual(4, requests.length, 'requests were made');
589 strictEqual(requests[3].url, 692 strictEqual(requests[3].url,
590 window.location.origin + 693 absoluteUrl('manifest/' + playlist.uri),
591 window.location.pathname.split('/').slice(0, -1).join('/') +
592 '/manifest/' +
593 playlist.uri,
594 'made playlist request'); 694 'made playlist request');
595 strictEqual(playlist.uri, 695 strictEqual(playlist.uri,
596 player.hls.playlists.media().uri, 696 player.hls.playlists.media().uri,
...@@ -766,15 +866,13 @@ test('downloads the next segment if the buffer is getting low', function() { ...@@ -766,15 +866,13 @@ test('downloads the next segment if the buffer is getting low', function() {
766 player.buffered = function() { 866 player.buffered = function() {
767 return videojs.createTimeRange(0, 19.999); 867 return videojs.createTimeRange(0, 19.999);
768 }; 868 };
769 player.trigger('timeupdate'); 869 player.hls.checkBuffer_();
770 870
771 standardXHRResponse(requests[2]); 871 standardXHRResponse(requests[2]);
772 872
773 strictEqual(requests.length, 3, 'made a request'); 873 strictEqual(requests.length, 3, 'made a request');
774 strictEqual(requests[2].url, 874 strictEqual(requests[2].url,
775 window.location.origin + 875 absoluteUrl('manifest/media-00002.ts'),
776 window.location.pathname.split('/').slice(0, -1).join('/') +
777 '/manifest/media-00002.ts',
778 'made segment request'); 876 'made segment request');
779 }); 877 });
780 878
...@@ -1267,7 +1365,7 @@ test('waits until the buffer is empty before appending bytes at a discontinuity' ...@@ -1267,7 +1365,7 @@ test('waits until the buffer is empty before appending bytes at a discontinuity'
1267 // play to 6s to trigger the next segment request 1365 // play to 6s to trigger the next segment request
1268 currentTime = 6; 1366 currentTime = 6;
1269 bufferEnd = 10; 1367 bufferEnd = 10;
1270 player.trigger('timeupdate'); 1368 player.hls.checkBuffer_();
1271 strictEqual(aborts, 0, 'no aborts before the buffer empties'); 1369 strictEqual(aborts, 0, 'no aborts before the buffer empties');
1272 1370
1273 standardXHRResponse(requests.pop()); 1371 standardXHRResponse(requests.pop());
...@@ -1315,7 +1413,7 @@ test('clears the segment buffer on seek', function() { ...@@ -1315,7 +1413,7 @@ test('clears the segment buffer on seek', function() {
1315 // play to 6s to trigger the next segment request 1413 // play to 6s to trigger the next segment request
1316 currentTime = 6; 1414 currentTime = 6;
1317 bufferEnd = 10; 1415 bufferEnd = 10;
1318 player.trigger('timeupdate'); 1416 player.hls.checkBuffer_();
1319 1417
1320 standardXHRResponse(requests.pop()); 1418 standardXHRResponse(requests.pop());
1321 1419
...@@ -1365,7 +1463,7 @@ test('continues playing after seek to discontinuity', function() { ...@@ -1365,7 +1463,7 @@ test('continues playing after seek to discontinuity', function() {
1365 1463
1366 currentTime = 1; 1464 currentTime = 1;
1367 bufferEnd = 10; 1465 bufferEnd = 10;
1368 player.trigger('timeupdate'); 1466 player.hls.checkBuffer_();
1369 1467
1370 standardXHRResponse(requests.pop()); 1468 standardXHRResponse(requests.pop());
1371 1469
...@@ -1435,10 +1533,7 @@ test('remove event handlers on dispose', function() { ...@@ -1435,10 +1533,7 @@ test('remove event handlers on dispose', function() {
1435 oldOn.call(player, type, handler); 1533 oldOn.call(player, type, handler);
1436 }; 1534 };
1437 player.off = function(type, handler) { 1535 player.off = function(type, handler) {
1438 // ignore the top-level videojs removals that aren't relevant to HLS 1536 offhandlers++;
1439 if (type && type !== 'dispose') {
1440 offhandlers++;
1441 }
1442 oldOff.call(player, type, handler); 1537 oldOff.call(player, type, handler);
1443 }; 1538 };
1444 player.src({ 1539 player.src({
...@@ -1452,10 +1547,8 @@ test('remove event handlers on dispose', function() { ...@@ -1452,10 +1547,8 @@ test('remove event handlers on dispose', function() {
1452 1547
1453 player.dispose(); 1548 player.dispose();
1454 1549
1455 equal(offhandlers, onhandlers, 'the amount of on and off handlers is the same'); 1550 ok(offhandlers > onhandlers, 'more handlers were removed than were registered');
1456 1551 equal(offhandlers - onhandlers, 1, 'one handler was registered during init');
1457 player.off = oldOff;
1458 player.on = oldOn;
1459 }); 1552 });
1460 1553
1461 test('aborts the source buffer on disposal', function() { 1554 test('aborts the source buffer on disposal', function() {
...@@ -1538,7 +1631,7 @@ test('tracks the bytes downloaded', function() { ...@@ -1538,7 +1631,7 @@ test('tracks the bytes downloaded', function() {
1538 1631
1539 strictEqual(player.hls.bytesReceived, 17, 'tracked bytes received'); 1632 strictEqual(player.hls.bytesReceived, 17, 'tracked bytes received');
1540 1633
1541 player.trigger('timeupdate'); 1634 player.hls.checkBuffer_();
1542 1635
1543 // transmit some more 1636 // transmit some more
1544 requests[0].response = new ArrayBuffer(5); 1637 requests[0].response = new ArrayBuffer(5);
...@@ -1918,7 +2011,7 @@ test('skip segments if key requests fail more than once', function() { ...@@ -1918,7 +2011,7 @@ test('skip segments if key requests fail more than once', function() {
1918 2011
1919 player.hls.playlists.trigger('loadedplaylist'); 2012 player.hls.playlists.trigger('loadedplaylist');
1920 2013
1921 player.trigger('timeupdate'); 2014 player.hls.checkBuffer_();
1922 2015
1923 // respond to ts segment 2016 // respond to ts segment
1924 standardXHRResponse(requests.pop()); 2017 standardXHRResponse(requests.pop());
...@@ -1934,7 +2027,7 @@ test('skip segments if key requests fail more than once', function() { ...@@ -1934,7 +2027,7 @@ test('skip segments if key requests fail more than once', function() {
1934 2027
1935 equal(bytes.length, 1, 'bytes from the ts segments should not be added'); 2028 equal(bytes.length, 1, 'bytes from the ts segments should not be added');
1936 2029
1937 player.trigger('timeupdate'); 2030 player.hls.checkBuffer_();
1938 2031
1939 tags.length = 0; 2032 tags.length = 0;
1940 tags.push({pts: 0, bytes: 1}); 2033 tags.push({pts: 0, bytes: 1});
...@@ -2106,7 +2199,7 @@ test('treats invalid keys as a key request failure', function() { ...@@ -2106,7 +2199,7 @@ test('treats invalid keys as a key request failure', function() {
2106 requests.shift().respond(200, null, ''); 2199 requests.shift().respond(200, null, '');
2107 2200
2108 // the first segment should be dropped and playback moves on 2201 // the first segment should be dropped and playback moves on
2109 player.trigger('timeupdate'); 2202 player.hls.checkBuffer_();
2110 equal(bytes.length, 1, 'did not append bytes'); 2203 equal(bytes.length, 1, 'did not append bytes');
2111 equal(bytes[0], 'flv', 'appended the flv header'); 2204 equal(bytes[0], 'flv', 'appended the flv header');
2112 2205
......