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

feat: deprecate smoothQualityChange #1074

Merged
merged 10 commits into from
Jun 9, 2021
14 changes: 5 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -402,20 +402,16 @@ If true, this will take the device pixel ratio into account when doing rendition
This setting is `false` by default.

##### smoothQualityChange
* NOTE: DEPRECATED
* Type: `boolean`
* can be used as a source option
* can be used as an initialization option

When the `smoothQualityChange` property is set to `true`, a manual quality
change triggered via the [representations API](#vhsrepresentations) will use
smooth quality switching rather than the default fast (buffer-ejecting)
quality switching. Using smooth quality switching will mean no loading spinner
will appear during quality switches, but will cause quality switches to only
be visible after a few seconds.
smoothQualityChange is deprecated and will be removed in the next major version of VHS.

Note that this _only_ affects quality changes triggered via the representations
API; automatic quality switches based on available bandwidth will always be
smooth switches.
Instead of its prior behavior, smoothQualityChange will now call fastQualityChange, which
clears the buffer, chooses a new rendition, and starts loading content from the current
playhead position.

##### allowSeeksWithinUnsafeLiveWindow
* Type: `boolean`
Expand Down
2 changes: 1 addition & 1 deletion scripts/index-demo-page.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
rep.playlist.disabled = rep.id !== id;
});

window.mpc.smoothQualityChange_();
window.mpc.fastQualityChange_();
});
var hlsOptGroup = document.querySelector('[label="hls"]');
var dashOptGroup = document.querySelector('[label="dash"]');
Expand Down
10 changes: 2 additions & 8 deletions src/master-playlist-controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -848,16 +848,10 @@ export class MasterPlaylistController extends videojs.EventTarget {
* removing already buffered content
*
* @private
* @deprecated
*/
smoothQualityChange_(media = this.selectPlaylist()) {
if (media === this.masterPlaylistLoader_.media()) {
return;
}

this.switchMedia_(media, 'smooth-quality');

this.mainSegmentLoader_.resetLoader();
// don't need to reset audio as it is reset when media changes
this.fastQualityChange_(media);
brandonocasey marked this conversation as resolved.
Show resolved Hide resolved
}

/**
Expand Down
11 changes: 10 additions & 1 deletion src/videojs-http-streaming.js
Original file line number Diff line number Diff line change
Expand Up @@ -590,7 +590,12 @@ class VhsHandler extends Component {
document.msFullscreenElement;

if (fullscreenElement && fullscreenElement.contains(this.tech_.el())) {
this.masterPlaylistController_.smoothQualityChange_();
this.masterPlaylistController_.fastQualityChange_();
} else {
// When leaving fullscreen, since the in page pixel dimensions should be smaller
// than full screen, see if there should be a rendition switch down to preserve
// bandwidth.
this.masterPlaylistController_.checkABR_();
}
});

Expand Down Expand Up @@ -708,6 +713,10 @@ class VhsHandler extends Component {
this.tech_.setCurrentTime(time);
};

if (this.options_.smoothQualityChange) {
videojs.log.warn('smoothQualityChange is deprecated and will be removed in the next major version');
}

this.masterPlaylistController_ = new MasterPlaylistController(this.options_);

const playbackWatcherOptions = videojs.mergeOptions(
Expand Down
5 changes: 0 additions & 5 deletions test/configuration.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,6 @@ const options = [{
default: 4194304,
test: 5,
alt: 555
}, {
name: 'smoothQualityChange',
default: false,
test: true,
alt: false
}, {
name: 'useBandwidthFromLocalStorage',
default: false,
Expand Down
148 changes: 16 additions & 132 deletions test/master-playlist-controller.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -430,144 +430,26 @@ QUnit.test(
}
);

QUnit.test('resyncs SegmentLoader for a smooth quality change', function(assert) {
let resyncs = 0;
// Since smoothQualityChange is deprecated, calls to smoothQualityChange_ should call
// fastQualityChange_.
QUnit.test('smoothQualityChange_ calls fastQualityChange_', function(assert) {
let fastQualityChangeCalls = 0;

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

const segmentLoader = this.masterPlaylistController.mainSegmentLoader_;
const originalResync = segmentLoader.resyncLoader;

segmentLoader.resyncLoader = function() {
resyncs++;
originalResync.call(segmentLoader);
};

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

this.masterPlaylistController.smoothQualityChange_();

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

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

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

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

const segmentLoader = this.masterPlaylistController.mainSegmentLoader_;
const originalResync = segmentLoader.resyncLoader;

segmentLoader.resyncLoader = function() {
resyncs++;
originalResync.call(segmentLoader);
};
this.masterPlaylistController.fastQualityChange_ = () => fastQualityChangeCalls++;

this.masterPlaylistController.smoothQualityChange_();

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

QUnit.test('smooth quality change resyncs audio segment loader', function(assert) {
this.requests.length = 0;
this.player.dispose();
this.player = createPlayer();
this.player.src({
src: 'alternate-audio-multiple-groups.m3u8',
type: 'application/vnd.apple.mpegurl'
});

this.clock.tick(1);

const masterPlaylistController = this.player.tech_.vhs.masterPlaylistController_;

masterPlaylistController.selectPlaylist = () => {
return masterPlaylistController.master().playlists[0];
};

// master
this.standardXHRResponse(this.requests.shift());
// media
this.standardXHRResponse(this.requests.shift());

masterPlaylistController.mediaSource.trigger('sourceopen');

this.clock.tick(1);

this.player.audioTracks()[0].enabled = true;

let resyncs = 0;
let resets = 0;
const realReset = masterPlaylistController.audioSegmentLoader_.resetLoader;

masterPlaylistController.audioSegmentLoader_.resetLoader = function() {
resets++;
realReset.call(this);
};

const originalResync = masterPlaylistController.audioSegmentLoader_.resyncLoader;

masterPlaylistController.audioSegmentLoader_.resyncLoader = function() {
resyncs++;
originalResync.call(masterPlaylistController.audioSegmentLoader_);
};

masterPlaylistController.smoothQualityChange_();
assert.equal(resyncs, 0, 'does not resync the audio segment loader when media same');

// force different media
masterPlaylistController.selectPlaylist = () => {
return masterPlaylistController.master().playlists[1];
};
this.masterPlaylistController.smoothQualityChange_();

assert.equal(this.requests.length, 3, 'three requests');
assert.ok(
this.requests[0].url.endsWith('eng/prog_index.m3u8'),
'requests eng playlist'
);
assert.ok(this.requests[1].url.endsWith('lo/main.mp4'), 'correct segment url');
assert.equal(
this.requests[1].requestHeaders.Range,
'bytes=0-603',
'requests init segment byte range'
);
assert.ok(this.requests[2].url.endsWith('lo/main.mp4'), 'correct segment url');
assert.equal(
this.requests[2].requestHeaders.Range,
'bytes=604-118754',
'requests segment byte range'
);
assert.notOk(this.requests[0].aborted, 'did not abort alt audio playlist request');
assert.notOk(this.requests[1].aborted, 'did not abort init request');
assert.notOk(this.requests[2].aborted, 'did not abort segment request');
masterPlaylistController.smoothQualityChange_();
assert.equal(this.requests.length, 4, 'added a request for new media');
assert.notOk(this.requests[0].aborted, 'did not abort alt audio playlist request');
assert.ok(this.requests[1].aborted, 'aborted init segment request');
assert.ok(this.requests[2].aborted, 'aborted segment request');
assert.equal(resyncs, 0, 'does not resync the audio segment loader yet');
// new media request
this.standardXHRResponse(this.requests[3]);
assert.equal(resyncs, 1, 'resyncs the audio segment loader when media changes');
assert.equal(resets, 0, 'does not reset the audio segment loader when media changes');
assert.equal(fastQualityChangeCalls, 1, 'called fastQualityChange_');
});

QUnit.test('resets everything for a fast quality change', function(assert) {
Expand Down Expand Up @@ -995,11 +877,11 @@ QUnit.test('audio segment loader is reset on audio track change', function(asser

let resyncs = 0;
let resets = 0;
const realReset = masterPlaylistController.audioSegmentLoader_.resetLoader;
const realReset = masterPlaylistController.audioSegmentLoader_.resetEverything;

masterPlaylistController.audioSegmentLoader_.resetLoader = function() {
masterPlaylistController.audioSegmentLoader_.resetEverything = function(done) {
resets++;
realReset.call(this);
realReset.call(this, done);
};

const originalResync = masterPlaylistController.audioSegmentLoader_.resyncLoader;
Expand Down Expand Up @@ -1032,6 +914,7 @@ QUnit.test('audio segment loader is reset on audio track change', function(asser
assert.equal(resyncs, 0, 'does not resync the audio segment loader yet');

this.player.audioTracks()[1].enabled = true;
this.clock.tick(1);

assert.equal(this.requests.length, 4, 'added a request for new media');
assert.ok(this.requests[0].aborted, 'aborted old alt audio playlist request');
Expand Down Expand Up @@ -4801,18 +4684,19 @@ QUnit.test('can pass or select a playlist for smoothQualityChange_', function(as

mpc.smoothQualityChange_(mpc.master().playlists[1]);
assert.deepEqual(calls, {
resetEverything: 0,
// should reset everything since smoothQualityChange_ calls fastQualityChange_
resetEverything: 1,
media: 1,
selectPlaylist: 0,
resyncLoader: 1
resyncLoader: 0
}, 'calls expected function when passed a playlist');

mpc.smoothQualityChange_();
assert.deepEqual(calls, {
resetEverything: 0,
resetEverything: 2,
media: 2,
selectPlaylist: 1,
resyncLoader: 2
resyncLoader: 0
}, 'calls expected function when not passed a playlist');
});

Expand Down
3 changes: 3 additions & 0 deletions test/segment-loader.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4344,6 +4344,9 @@ QUnit.module('SegmentLoader', function(hooks) {
// smoothQualityChange will reset loader after changing renditions, so need to
// mimic that behavior here in order for content to be overlayed over already
// buffered content.
//
// Now that smoothQualityChange is removed, this behavior can be mimicked by
// calling resetLoader.
loader.resetLoader();
this.clock.tick(1);

Expand Down
13 changes: 10 additions & 3 deletions test/videojs-http-streaming.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -5389,7 +5389,7 @@ QUnit.test('stats are reset on dispose', function(assert) {

// mocking the fullscreenElement no longer works, find another way to mock
// fullscreen behavior(without user gesture)
QUnit.skip('detects fullscreen and triggers a smooth quality change', function(assert) {
QUnit.skip('detects fullscreen and triggers a fast quality change', function(assert) {
const vhs = VhsSourceHandler.handleSource({
src: 'manifest/master.m3u8',
type: 'application/vnd.apple.mpegurl'
Expand All @@ -5405,7 +5405,7 @@ QUnit.skip('detects fullscreen and triggers a smooth quality change', function(a
}
});

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

Expand All @@ -5415,12 +5415,19 @@ QUnit.skip('detects fullscreen and triggers a smooth quality change', function(a

assert.equal(qualityChanges, 1, 'made a fast quality change');

let checkABRCalls = 0;

vhs.masterPlaylistController_.checkABR_ = () => checkABRCalls++;

// don't do a fast quality change when returning from fullscreen;
// allow the video element to rescale the already buffered video
//
// do check the current rendition to see if it should be changed for the next
// segment loaded
document[fullscreenElementName] = null;
Events.trigger(document, 'fullscreenchange');

assert.equal(qualityChanges, 1, 'did not make another quality change');
assert.equal(checkABRCalls, 1, 'called to check the ABR');
vhs.dispose();
});

Expand Down