b3bf48b9 by David LaPalomento

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.
1 parent 667e96ef
...@@ -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 });
......