diff --git a/src/master-playlist-controller.js b/src/master-playlist-controller.js index 1200e3c61..99c933692 100644 --- a/src/master-playlist-controller.js +++ b/src/master-playlist-controller.js @@ -530,14 +530,20 @@ export class MasterPlaylistController extends videojs.EventTarget { this.masterPlaylistLoader_.media(media); - // delete all buffered data to allow an immediate quality switch, then seek - // in place to give the browser a kick to remove any cached frames from the - // previous rendition + // Delete all buffered data to allow an immediate quality switch, then seek to give + // the browser a kick to remove any cached frames from the previous rendtion (.04 seconds + // ahead is roughly the minimum that will accomplish this across a variety of content + // in IE and Edge, but seeking in place is sufficient on all other browsers) + // Edge/IE bug: https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/14600375/ + // Chrome bug: https://bugs.chromium.org/p/chromium/issues/detail?id=651904 this.mainSegmentLoader_.resetEverything(() => { - // Since this is not a typical seek, we avoid the seekTo method which can cause - // segments from the previously enabled rendition to load before the new playlist - // has finished loading - this.tech_.setCurrentTime(this.tech_.currentTime()); + // Since this is not a typical seek, we avoid the seekTo method which can cause segments + // from the previously enabled rendition to load before the new playlist has finished loading + if (videojs.browser.IE_VERSION || videojs.browser.IS_EDGE) { + this.tech_.setCurrentTime(this.tech_.currentTime() + 0.04); + } else { + this.tech_.setCurrentTime(this.tech_.currentTime()); + } }); // don't need to reset audio as it is reset when media changes diff --git a/test/master-playlist-controller.test.js b/test/master-playlist-controller.test.js index 1f82d4ee7..9e75eddc8 100644 --- a/test/master-playlist-controller.test.js +++ b/test/master-playlist-controller.test.js @@ -375,6 +375,121 @@ QUnit.test('resets everything for a fast quality change', function(assert) { assert.deepEqual(removeFuncArgs, {start: 0, end: 60}, 'remove() called with correct arguments if media is changed'); }); +QUnit.test('seeks in place for fast quality switch on non-IE/Edge browsers', function(assert) { + let seeks = 0; + + this.masterPlaylistController.mediaSource.trigger('sourceopen'); + // master + this.standardXHRResponse(this.requests.shift()); + // media + this.standardXHRResponse(this.requests.shift()); + // segment + this.standardXHRResponse(this.requests.shift()); + // trigger updateend to indicate the end of the append operation + this.masterPlaylistController.mediaSource.sourceBuffers[0].trigger('updateend'); + + // media is changed + this.masterPlaylistController.selectPlaylist = () => { + return this.masterPlaylistController.master().playlists[0]; + }; + + this.player.tech_.on('seeking', function() { + seeks++; + }); + + const timeBeforeSwitch = this.player.currentTime(); + + this.masterPlaylistController.fastQualityChange_(); + // trigger updateend to indicate the end of the remove operation + this.masterPlaylistController.mediaSource.sourceBuffers[0].trigger('updateend'); + this.clock.tick(1); + + assert.equal(this.player.currentTime(), timeBeforeSwitch, 'current time remains the same on fast quality switch'); + assert.equal(seeks, 1, 'seek event occurs on fast quality switch'); +}); + +QUnit.test('seeks forward 0.04 sec for fast quality switch on Edge', function(assert) { + let oldIEVersion = videojs.browser.IE_VERSION; + let oldIsEdge = videojs.browser.IS_EDGE; + let seeks = 0; + + this.masterPlaylistController.mediaSource.trigger('sourceopen'); + // master + this.standardXHRResponse(this.requests.shift()); + // media + this.standardXHRResponse(this.requests.shift()); + // segment + this.standardXHRResponse(this.requests.shift()); + // trigger updateend to indicate the end of the append operation + this.masterPlaylistController.mediaSource.sourceBuffers[0].trigger('updateend'); + + // media is changed + this.masterPlaylistController.selectPlaylist = () => { + return this.masterPlaylistController.master().playlists[0]; + }; + + this.player.tech_.on('seeking', function() { + seeks++; + }); + + const timeBeforeSwitch = this.player.currentTime(); + + videojs.browser.IE_VERSION = null; + videojs.browser.IS_EDGE = true; + + this.masterPlaylistController.fastQualityChange_(); + // trigger updateend to indicate the end of the remove operation + this.masterPlaylistController.mediaSource.sourceBuffers[0].trigger('updateend'); + this.clock.tick(1); + + assert.equal(this.player.currentTime(), timeBeforeSwitch + 0.04, 'seeks forward on fast quality switch'); + assert.equal(seeks, 1, 'seek event occurs on fast quality switch'); + + videojs.browser.IE_VERSION = oldIEVersion; + videojs.browser.IS_EDGE = oldIsEdge; +}); + +QUnit.test('seeks forward 0.04 sec for fast quality switch on IE', function(assert) { + let oldIEVersion = videojs.browser.IE_VERSION; + let oldIsEdge = videojs.browser.IS_EDGE; + let seeks = 0; + + this.masterPlaylistController.mediaSource.trigger('sourceopen'); + // master + this.standardXHRResponse(this.requests.shift()); + // media + this.standardXHRResponse(this.requests.shift()); + // segment + this.standardXHRResponse(this.requests.shift()); + // trigger updateend to indicate the end of the append operation + this.masterPlaylistController.mediaSource.sourceBuffers[0].trigger('updateend'); + + // media is changed + this.masterPlaylistController.selectPlaylist = () => { + return this.masterPlaylistController.master().playlists[0]; + }; + + this.player.tech_.on('seeking', function() { + seeks++; + }); + + const timeBeforeSwitch = this.player.currentTime(); + + videojs.browser.IE_VERSION = 11; + videojs.browser.IS_EDGE = false; + + this.masterPlaylistController.fastQualityChange_(); + // trigger updateend to indicate the end of the remove operation + this.masterPlaylistController.mediaSource.sourceBuffers[0].trigger('updateend'); + this.clock.tick(1); + + assert.equal(this.player.currentTime(), timeBeforeSwitch + 0.04, 'seeks forward on fast quality switch'); + assert.equal(seeks, 1, 'seek event occurs on fast quality switch'); + + videojs.browser.IE_VERSION = oldIEVersion; + videojs.browser.IS_EDGE = oldIsEdge; +}); + QUnit.test('audio segment loader is reset on audio track change', function(assert) { this.requests.length = 0; this.player = createPlayer();