Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: stutter after fast quality change in IE/Edge #213

Merged
merged 9 commits into from
Sep 21, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 13 additions & 7 deletions src/master-playlist-controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
115 changes: 115 additions & 0 deletions test/master-playlist-controller.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
gesinger marked this conversation as resolved.
Show resolved Hide resolved
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();
Expand Down