Merge pull request #223 from videojs/hotfix/src-changes
Clean up the playlist loader when sources change
Showing
2 changed files
with
239 additions
and
98 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. |
... | @@ -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 | ||
1439 | if (type && type !== 'dispose') { | ||
1440 | offhandlers++; | 1536 | 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 | ... | ... |
-
Please register or sign in to post a comment