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.
Showing
2 changed files
with
193 additions
and
95 deletions
... | @@ -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 | ... | ... |
-
Please register or sign in to post a comment