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
65 additions
and
37 deletions
... | @@ -54,31 +54,39 @@ videojs.Hls.prototype.src = function(src) { | ... | @@ -54,31 +54,39 @@ 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 | this.src_ = src; | 58 | if (!src) { |
59 | 59 | return; | |
60 | mediaSource = new videojs.MediaSource(); | ||
61 | source = { | ||
62 | src: videojs.URL.createObjectURL(mediaSource), | ||
63 | type: "video/flv" | ||
64 | }; | ||
65 | this.mediaSource = mediaSource; | ||
66 | |||
67 | this.segmentBuffer_ = []; | ||
68 | this.segmentParser_ = new videojs.Hls.SegmentParser(); | ||
69 | |||
70 | // load the MediaSource into the player | ||
71 | this.mediaSource.addEventListener('sourceopen', videojs.bind(this, this.handleSourceOpen)); | ||
72 | |||
73 | this.player().ready(function() { | ||
74 | // do nothing if the tech has been disposed already | ||
75 | // this can occur if someone sets the src in player.ready(), for instance | ||
76 | if (!tech.el()) { | ||
77 | return; | ||
78 | } | ||
79 | tech.el().vjs_src(source.src); | ||
80 | }); | ||
81 | } | 60 | } |
61 | |||
62 | // if there is already a source loaded, clean it up | ||
63 | if (this.src_) { | ||
64 | this.resetSrc_(); | ||
65 | } | ||
66 | |||
67 | this.src_ = src; | ||
68 | |||
69 | mediaSource = new videojs.MediaSource(); | ||
70 | source = { | ||
71 | src: videojs.URL.createObjectURL(mediaSource), | ||
72 | type: "video/flv" | ||
73 | }; | ||
74 | this.mediaSource = mediaSource; | ||
75 | |||
76 | this.segmentBuffer_ = []; | ||
77 | this.segmentParser_ = new videojs.Hls.SegmentParser(); | ||
78 | |||
79 | // load the MediaSource into the player | ||
80 | this.mediaSource.addEventListener('sourceopen', videojs.bind(this, this.handleSourceOpen)); | ||
81 | |||
82 | this.player().ready(function() { | ||
83 | // do nothing if the tech has been disposed already | ||
84 | // this can occur if someone sets the src in player.ready(), for instance | ||
85 | if (!tech.el()) { | ||
86 | return; | ||
87 | } | ||
88 | tech.el().vjs_src(source.src); | ||
89 | }); | ||
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