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

Remove buffered data on fast quality switches #113

Merged
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
38 changes: 34 additions & 4 deletions src/master-playlist-controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,7 @@ export class MasterPlaylistController extends videojs.EventTarget {
// update the SegmentLoader instead of doing it twice here and
// on `loadedplaylist`
this.mainSegmentLoader_.playlist(media, this.requestOptions_);

this.mainSegmentLoader_.load();

this.tech_.trigger({
Expand Down Expand Up @@ -496,13 +497,12 @@ export class MasterPlaylistController extends videojs.EventTarget {

/**
* Re-tune playback quality level for the current player
* conditions. This method may perform destructive actions, like
* removing already buffered content, to readjust the currently
* active playlist quickly.
* conditions without performing destructive actions, like
* removing already buffered content
*
* @private
*/
fastQualityChange_() {
smoothQualityChange_() {
let media = this.selectPlaylist();

if (media !== this.masterPlaylistLoader_.media()) {
Expand All @@ -513,6 +513,36 @@ export class MasterPlaylistController extends videojs.EventTarget {
}
}

/**
* Re-tune playback quality level for the current player
* conditions. This method will perform destructive actions like removing
* already buffered content in order to readjust the currently active
* playlist quickly. This is good for manual quality changes
*
* @private
*/
fastQualityChange_() {
const media = this.selectPlaylist();

if (media === this.masterPlaylistLoader_.media()) {
return;
}

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
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());
});

// don't need to reset audio as it is reset when media changes
}

/**
* Begin playback.
*/
Expand Down
12 changes: 8 additions & 4 deletions src/segment-loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -547,11 +547,13 @@ export default class SegmentLoader extends videojs.EventTarget {

/**
* Delete all the buffered data and reset the SegmentLoader
* @param {Function} [done] an optional callback to be executed when the remove
* operation is complete
*/
resetEverything() {
resetEverything(done) {
this.ended_ = false;
this.resetLoader();
this.remove(0, this.duration_());
this.remove(0, this.duration_(), done);
// clears fmp4 captions
this.captionParser_.clearAllCaptions();
this.trigger('reseteverything');
Expand Down Expand Up @@ -582,10 +584,12 @@ export default class SegmentLoader extends videojs.EventTarget {
* Remove any data in the source buffer between start and end times
* @param {Number} start - the start time of the region to remove from the buffer
* @param {Number} end - the end time of the region to remove from the buffer
* @param {Function} [done] - an optional callback to be executed when the remove
* operation is complete
*/
remove(start, end) {
remove(start, end, done) {
if (this.sourceUpdater_) {
this.sourceUpdater_.remove(start, end);
this.sourceUpdater_.remove(start, end, done);
}
removeCuesFromTrack(start, end, this.segmentMetadataTrack_);

Expand Down
6 changes: 4 additions & 2 deletions src/source-updater.js
Original file line number Diff line number Diff line change
Expand Up @@ -131,14 +131,16 @@ export default class SourceUpdater {
*
* @param {Number} start where to start the removal
* @param {Number} end where to end the removal
* @param {Function} [done=noop] optional callback to be executed when the remove
* operation is complete
* @see http://www.w3.org/TR/media-source/#widl-SourceBuffer-remove-void-double-start-unrestricted-double-end
*/
remove(start, end) {
remove(start, end, done = noop) {
if (this.processedAppend_) {
this.queueCallback_(() => {
this.logger_(`remove [${start} => ${end}]`);
this.sourceBuffer_.remove(start, end);
}, noop);
}, done);
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/videojs-http-streaming.js
Original file line number Diff line number Diff line change
Expand Up @@ -320,7 +320,7 @@ class HlsHandler extends Component {
document.msFullscreenElement;

if (fullscreenElement && fullscreenElement.contains(this.tech_.el())) {
this.masterPlaylistController_.fastQualityChange_();
this.masterPlaylistController_.smoothQualityChange_();
}
});
this.on(this.tech_, 'error', function() {
Expand Down
65 changes: 58 additions & 7 deletions test/master-playlist-controller.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@ QUnit.test('selects lowest bitrate rendition when enableLowInitialPlaylist is se
assert.equal(numCallsToSelectPlaylist, 0, 'selectPlaylist');
});

QUnit.test('resyncs SegmentLoader for a fast quality change', function(assert) {
QUnit.test('resyncs SegmentLoader for a smooth quality change', function(assert) {
let resyncs = 0;

this.masterPlaylistController.mediaSource.trigger('sourceopen');
Expand All @@ -240,15 +240,15 @@ QUnit.test('resyncs SegmentLoader for a fast quality change', function(assert) {
return this.masterPlaylistController.master().playlists[0];
};

this.masterPlaylistController.fastQualityChange_();
this.masterPlaylistController.smoothQualityChange_();

assert.equal(resyncs, 1, 'resynced the segmentLoader');

// verify stats
assert.equal(this.player.tech_.hls.stats.bandwidth, 4194304, 'default bandwidth');
});

QUnit.test('does not resync the segmentLoader when no fast quality change occurs',
QUnit.test('does not resync the segmentLoader when no smooth quality change occurs',
function(assert) {
let resyncs = 0;

Expand All @@ -264,14 +264,14 @@ QUnit.test('does not resync the segmentLoader when no fast quality change occurs
resyncs++;
};

this.masterPlaylistController.fastQualityChange_();
this.masterPlaylistController.smoothQualityChange_();

assert.equal(resyncs, 0, 'did not resync the segmentLoader');
// verify stats
assert.equal(this.player.tech_.hls.stats.bandwidth, 4194304, 'default bandwidth');
});

QUnit.test('fast quality change resyncs audio segment loader', function(assert) {
QUnit.test('smooth quality change resyncs audio segment loader', function(assert) {
this.requests.length = 0;
this.player = createPlayer();
this.player.src({
Expand Down Expand Up @@ -306,7 +306,7 @@ QUnit.test('fast quality change resyncs audio segment loader', function(assert)
};

masterPlaylistController.audioSegmentLoader_.resyncLoader = () => resyncs++;
masterPlaylistController.fastQualityChange_();
masterPlaylistController.smoothQualityChange_();
assert.equal(resyncs, 0, 'does not resync the audio segment loader when media same');

// force different media
Expand All @@ -315,7 +315,7 @@ QUnit.test('fast quality change resyncs audio segment loader', function(assert)
};

assert.equal(this.requests.length, 1, 'one request');
masterPlaylistController.fastQualityChange_();
masterPlaylistController.smoothQualityChange_();
assert.equal(this.requests.length, 2, 'added a request for new media');
assert.equal(resyncs, 0, 'does not resync the audio segment loader yet');
// new media request
Expand All @@ -324,6 +324,57 @@ QUnit.test('fast quality change resyncs audio segment loader', function(assert)
assert.equal(resets, 0, 'does not reset the audio segment loader when media changes');
});

QUnit.test('resets everything for a fast quality change', function(assert) {
let resyncs = 0;
let resets = 0;
let removeFuncArgs = {};

this.masterPlaylistController.mediaSource.trigger('sourceopen');
// master
this.standardXHRResponse(this.requests.shift());
// media
this.standardXHRResponse(this.requests.shift());

let segmentLoader = this.masterPlaylistController.mainSegmentLoader_;

segmentLoader.resyncLoader = () => resyncs++;

segmentLoader.remove = function(start, end) {
removeFuncArgs = {
start,
end
};
};

segmentLoader.duration_ = () => 60;

segmentLoader.on('reseteverything', function() {
resets++;
});

// media is unchanged
this.masterPlaylistController.fastQualityChange_();

assert.equal(resyncs, 0, 'does not resync segment loader if media is unchanged');

assert.equal(resets, 0, 'reseteverything event not triggered if media is unchanged');

assert.deepEqual(removeFuncArgs, {}, 'remove() not called if media is unchanged');

// media is changed
this.masterPlaylistController.selectPlaylist = () => {
return this.masterPlaylistController.master().playlists[0];
};

this.masterPlaylistController.fastQualityChange_();

assert.equal(resyncs, 1, 'resynced segment loader if media is changed');

assert.equal(resets, 1, 'reseteverything event triggered if media is changed');

assert.deepEqual(removeFuncArgs, {start: 0, end: 60}, 'remove() called with correct arguments if media is changed');
});

QUnit.test('audio segment loader is reset on audio track change', function(assert) {
this.requests.length = 0;
this.player = createPlayer();
Expand Down
4 changes: 2 additions & 2 deletions test/videojs-http-streaming.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3040,7 +3040,7 @@ QUnit.test('stats are reset on dispose', function(assert) {
assert.equal(hls.stats.mediaBytesTransferred, 0, 'stat is reset');
});

QUnit.test('detects fullscreen and triggers a quality change', function(assert) {
QUnit.test('detects fullscreen and triggers a smooth quality change', function(assert) {
let qualityChanges = 0;
let hls = HlsSourceHandler.handleSource({
src: 'manifest/master.m3u8',
Expand All @@ -3056,7 +3056,7 @@ QUnit.test('detects fullscreen and triggers a quality change', function(assert)
}
});

hls.masterPlaylistController_.fastQualityChange_ = function() {
hls.masterPlaylistController_.smoothQualityChange_ = function() {
qualityChanges++;
};

Expand Down