Cleanup the current source when loading a new one
If src() is called when a src is already loaded, make sure to abort any outstanding work and reset the state of the SourceBuffer. Switching sources after initial load occasionally caused weird audio and video artifacts because the underlying decoder was working from the old state.
Showing
2 changed files
with
43 additions
and
15 deletions
... | @@ -54,7 +54,16 @@ videojs.Hls.prototype.src = function(src) { | ... | @@ -54,7 +54,16 @@ videojs.Hls.prototype.src = function(src) { |
54 | mediaSource, | 54 | mediaSource, |
55 | source; | 55 | source; |
56 | 56 | ||
57 | if (src) { | 57 | // do nothing if the src is falsey |
58 | if (!src) { | ||
59 | return; | ||
60 | } | ||
61 | |||
62 | // if there is already a source loaded, clean it up | ||
63 | if (this.src_) { | ||
64 | this.resetSrc_(); | ||
65 | } | ||
66 | |||
58 | this.src_ = src; | 67 | this.src_ = src; |
59 | 68 | ||
60 | mediaSource = new videojs.MediaSource(); | 69 | mediaSource = new videojs.MediaSource(); |
... | @@ -78,7 +87,6 @@ videojs.Hls.prototype.src = function(src) { | ... | @@ -78,7 +87,6 @@ videojs.Hls.prototype.src = function(src) { |
78 | } | 87 | } |
79 | tech.el().vjs_src(source.src); | 88 | tech.el().vjs_src(source.src); |
80 | }); | 89 | }); |
81 | } | ||
82 | }; | 90 | }; |
83 | 91 | ||
84 | videojs.Hls.prototype.handleSourceOpen = function() { | 92 | videojs.Hls.prototype.handleSourceOpen = function() { |
... | @@ -231,16 +239,11 @@ videojs.Hls.prototype.updateDuration = function(playlist) { | ... | @@ -231,16 +239,11 @@ videojs.Hls.prototype.updateDuration = function(playlist) { |
231 | }; | 239 | }; |
232 | 240 | ||
233 | /** | 241 | /** |
234 | * Abort all outstanding work and cleanup. | 242 | * Clear all buffers and reset any state relevant to the current |
243 | * source. After this function is called, the tech should be in a | ||
244 | * state suitable for switching to a different video. | ||
235 | */ | 245 | */ |
236 | videojs.Hls.prototype.dispose = function() { | 246 | videojs.Hls.prototype.resetSrc_ = function() { |
237 | var player = this.player(); | ||
238 | |||
239 | // remove event handlers | ||
240 | player.off('timeupdate', this.fillBuffer); | ||
241 | player.off('timeupdate', this.drainBuffer); | ||
242 | player.off('waiting', this.drainBuffer); | ||
243 | |||
244 | if (this.segmentXhr_) { | 247 | if (this.segmentXhr_) { |
245 | this.segmentXhr_.onreadystatechange = null; | 248 | this.segmentXhr_.onreadystatechange = null; |
246 | this.segmentXhr_.abort(); | 249 | this.segmentXhr_.abort(); |
... | @@ -251,12 +254,28 @@ videojs.Hls.prototype.dispose = function() { | ... | @@ -251,12 +254,28 @@ videojs.Hls.prototype.dispose = function() { |
251 | keyXhr.abort(); | 254 | keyXhr.abort(); |
252 | keyXhr = null; | 255 | keyXhr = null; |
253 | } | 256 | } |
254 | if (this.playlists) { | ||
255 | this.playlists.dispose(); | ||
256 | } | ||
257 | if (this.sourceBuffer) { | 257 | if (this.sourceBuffer) { |
258 | this.sourceBuffer.abort(); | 258 | this.sourceBuffer.abort(); |
259 | } | 259 | } |
260 | }; | ||
261 | |||
262 | /** | ||
263 | * Abort all outstanding work and cleanup. | ||
264 | */ | ||
265 | videojs.Hls.prototype.dispose = function() { | ||
266 | var player = this.player(); | ||
267 | |||
268 | // remove event handlers | ||
269 | player.off('timeupdate', this.fillBuffer); | ||
270 | player.off('timeupdate', this.drainBuffer); | ||
271 | player.off('waiting', this.drainBuffer); | ||
272 | |||
273 | if (this.playlists) { | ||
274 | this.playlists.dispose(); | ||
275 | } | ||
276 | |||
277 | this.resetSrc_(); | ||
278 | |||
260 | videojs.Flash.prototype.dispose.call(this); | 279 | videojs.Flash.prototype.dispose.call(this); |
261 | }; | 280 | }; |
262 | 281 | ... | ... |
... | @@ -293,7 +293,9 @@ test('recognizes domain-relative URLs', function() { | ... | @@ -293,7 +293,9 @@ test('recognizes domain-relative URLs', function() { |
293 | }); | 293 | }); |
294 | 294 | ||
295 | test('re-initializes the tech for each source', function() { | 295 | test('re-initializes the tech for each source', function() { |
296 | var firstPlaylists, secondPlaylists, firstMSE, secondMSE; | 296 | var firstPlaylists, secondPlaylists, firstMSE, secondMSE, aborts; |
297 | |||
298 | aborts = 0; | ||
297 | 299 | ||
298 | player.src({ | 300 | player.src({ |
299 | src: 'manifest/master.m3u8', | 301 | src: 'manifest/master.m3u8', |
... | @@ -302,6 +304,11 @@ test('re-initializes the tech for each source', function() { | ... | @@ -302,6 +304,11 @@ test('re-initializes the tech for each source', function() { |
302 | openMediaSource(player); | 304 | openMediaSource(player); |
303 | firstPlaylists = player.hls.playlists; | 305 | firstPlaylists = player.hls.playlists; |
304 | firstMSE = player.hls.mediaSource; | 306 | firstMSE = player.hls.mediaSource; |
307 | player.hls.sourceBuffer.abort = function() { | ||
308 | aborts++; | ||
309 | }; | ||
310 | standardXHRResponse(requests.shift()); | ||
311 | standardXHRResponse(requests.shift()); | ||
305 | 312 | ||
306 | player.src({ | 313 | player.src({ |
307 | src: 'manifest/master.m3u8', | 314 | src: 'manifest/master.m3u8', |
... | @@ -311,6 +318,8 @@ test('re-initializes the tech for each source', function() { | ... | @@ -311,6 +318,8 @@ test('re-initializes the tech for each source', function() { |
311 | secondPlaylists = player.hls.playlists; | 318 | secondPlaylists = player.hls.playlists; |
312 | secondMSE = player.hls.mediaSource; | 319 | secondMSE = player.hls.mediaSource; |
313 | 320 | ||
321 | equal(1, aborts, 'aborted the old source buffer'); | ||
322 | ok(requests[0].aborted, 'aborted the old segment request'); | ||
314 | notStrictEqual(firstPlaylists, secondPlaylists, 'the playlist object is not reused'); | 323 | notStrictEqual(firstPlaylists, secondPlaylists, 'the playlist object is not reused'); |
315 | notStrictEqual(firstMSE, secondMSE, 'the media source object is not reused'); | 324 | notStrictEqual(firstMSE, secondMSE, 'the media source object is not reused'); |
316 | }); | 325 | }); | ... | ... |
-
Please register or sign in to post a comment