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: Temporarily disable the active variant from NETWORK HTTP_ERROR #4189

1 change: 1 addition & 0 deletions demo/common/message_ids.js
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,7 @@ shakaDemo.MessageIds = {
MANIFEST_SECTION_HEADER: 'DEMO_MANIFEST_SECTION_HEADER',
MAX_ATTEMPTS: 'DEMO_MAX_ATTEMPTS',
MAX_BANDWIDTH: 'DEMO_MAX_BANDWIDTH',
MAX_DISABLED_TIME: 'DEMO_MAX_DISABLED_TIME',
MAX_FRAMERATE: 'DEMO_MAX_FRAMERATE',
MAX_HEIGHT: 'DEMO_MAX_HEIGHT',
MAX_PIXELS: 'DEMO_MAX_PIXELS',
Expand Down
4 changes: 3 additions & 1 deletion demo/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -390,7 +390,9 @@ shakaDemo.Config = class {
.addBoolInput_(MessageIds.DISPATCH_ALL_EMSG_BOXES,
'streaming.dispatchAllEmsgBoxes')
.addBoolInput_(MessageIds.OBSERVE_QUALITY_CHANGES,
'streaming.observeQualityChanges');
'streaming.observeQualityChanges')
.addNumberInput_(MessageIds.MAX_DISABLED_TIME,
'streaming.maxDisabledTime');

if (!shakaDemoMain.getNativeControlsEnabled()) {
this.addBoolInput_(MessageIds.ALWAYS_STREAM_TEXT,
Expand Down
1 change: 1 addition & 0 deletions demo/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@
"DEMO_MANIFEST_URL_ERROR": "Must have a manifest URL, or IMA DAI id fields",
"DEMO_MAX_ATTEMPTS": "Max Attempts",
"DEMO_MAX_BANDWIDTH": "Max Bandwidth",
"DEMO_MAX_DISABLED_TIME": "Max Variant Disabled Time",
"DEMO_MAX_FRAMERATE": "Max Framerate",
"DEMO_MAX_HEIGHT": "Max Height",
"DEMO_MAX_PIXELS": "Max Pixels",
Expand Down
4 changes: 4 additions & 0 deletions demo/locales/source.json
Original file line number Diff line number Diff line change
Expand Up @@ -535,6 +535,10 @@
"description": "The name of a configuration value.",
"message": "Max Bandwidth"
},
"DEMO_MAX_DISABLED_TIME": {
"description": "The maximum amount of seconds a variant can be disabled when an error is thrown.",
"message": "Max Variant Disabled Time"
},
"DEMO_MAX_FRAMERATE": {
"description": "The name of a configuration value.",
"message": "Max Framerate"
Expand Down
6 changes: 6 additions & 0 deletions externs/shaka/manifest.js
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@ shaka.extern.DrmInfo;
* @typedef {{
* id: number,
* language: string,
* disabledUntilTime: number,
* primary: boolean,
* audio: ?shaka.extern.Stream,
* video: ?shaka.extern.Stream,
Expand All @@ -198,6 +199,11 @@ shaka.extern.DrmInfo;
* The Variant's language, specified as a language code. <br>
* See {@link https://tools.ietf.org/html/rfc5646} <br>
* See {@link http://www.iso.org/iso/home/standards/language_codes.htm}
* @property {number} disabledUntilTime
* <i>Defaults to 0.</i> <br>
* 0 means the variant is enabled. The Player will set this value to
* "Date.now + config.streaming.maxDisabledTime" and once this maxDisabledTime
albertdaurell marked this conversation as resolved.
Show resolved Hide resolved
* has passed Player will set the value to 0 in order to reenable the variant.
* @property {boolean} primary
* <i>Defaults to false.</i> <br>
* True indicates that the player should use this Variant over others if user
Expand Down
7 changes: 6 additions & 1 deletion externs/shaka/player.js
Original file line number Diff line number Diff line change
Expand Up @@ -852,7 +852,8 @@ shaka.extern.ManifestConfiguration;
* preferNativeHls: boolean,
* updateIntervalSeconds: number,
* dispatchAllEmsgBoxes: boolean,
* observeQualityChanges: boolean
* observeQualityChanges: boolean,
* maxDisabledTime: number
* }}
*
* @description
Expand Down Expand Up @@ -957,6 +958,10 @@ shaka.extern.ManifestConfiguration;
* @property {boolean} observeQualityChanges
* If true, monitor media quality changes and emit
* <code.shaka.Player.MediaQualityChangedEvent</code>.
* @property {number} maxDisabledTime
* The maximum time a variant can be disabled when NETWORK HTTP_ERROR
* is reached, in seconds.
* If all variants are disabled this way, NETWORK HTTP_ERROR will be thrown.
* @exportDoc
*/
shaka.extern.StreamingConfiguration;
Expand Down
1 change: 1 addition & 0 deletions lib/hls/hls_parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -460,6 +460,7 @@ shaka.hls.HlsParser = class {
variants.push({
id: 0,
language: 'und',
disabledUntilTime: 0,
primary: true,
audio: type == 'audio' ? streamInfo.stream : null,
video: type == 'video' ? streamInfo.stream : null,
Expand Down
1 change: 1 addition & 0 deletions lib/offline/manifest_converter.js
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,7 @@ shaka.offline.ManifestConverter = class {
return {
id: id,
language: '',
disabledUntilTime: 0,
primary: false,
audio: null,
video: null,
Expand Down
82 changes: 82 additions & 0 deletions lib/player.js
Original file line number Diff line number Diff line change
Expand Up @@ -679,6 +679,10 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
shaka.Player.createEmptyPayload_(),
walkerImplementation);

/** @private {shaka.util.Timer} */
this.checkVariantsTimer_ =
new shaka.util.Timer(() => this.checkVariants_());

// Even though |attach| will start in later interpreter cycles, it should be
// the LAST thing we do in the constructor because conceptually it relies on
// player having been initialized.
Expand Down Expand Up @@ -1393,6 +1397,9 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
this.eventManager_.unlisten(has.mediaElement, 'ratechange');
}

// Stop the variant checker timer
this.checkVariantsTimer_.stop();

// Some observers use some playback components, shutting down the observers
// first ensures that they don't try to use the playback components
// mid-destroy.
Expand Down Expand Up @@ -2117,6 +2124,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
const variant = {
id: 0,
language: 'und',
disabledUntilTime: 0,
primary: false,
audio: null,
video: {
Expand Down Expand Up @@ -5198,6 +5206,21 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
}
}

/**
* Re-apply restrictions to the variants, to re-enable variants that were
* temporarily disabled due to network errors.
* If any variants are enabled this way, a new variant might be chosen for
* playback.
* @private
*/
checkVariants_() {
const tracksChanged = shaka.util.StreamUtils.applyRestrictions(
this.manifest_.variants, this.config_.restrictions, this.maxHwRes_);
if (tracksChanged) {
this.chooseVariant_();
}
}

/**
* Choose a text stream from all possible text streams while taking into
* account user preference.
Expand Down Expand Up @@ -5485,6 +5508,59 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
shaka.Player.EventName.AbrStatusChanged, data));
}

/**
* Tries to recover from NETWORK HTTP_ERROR, temporary disabling the current
* problematic variant.
* @param {!shaka.util.Error} error
* @return {boolean}
* @private
*/
tryToRecoverFromError_(error) {
if (error.code !== shaka.util.Error.Code.HTTP_ERROR ||
error.category !== shaka.util.Error.Category.NETWORK) {
return false;
}

const maxDisabledTime = this.config_.streaming.maxDisabledTime;
if (maxDisabledTime == 0) {
return false;
}

shaka.log.debug('Recoverable NETWORK HTTP_ERROR, trying to recover...');

// Obtain the active variant and disable it from manifest variants
const activeVariantTrack = this.getVariantTracks().find((t) => t.active);
goog.asserts.assert(activeVariantTrack, 'Active variant should be found');
const manifest = this.manifest_;
for (const variant of manifest.variants) {
if (variant.id === activeVariantTrack.id) {
variant.disabledUntilTime = (Date.now() / 1000) + maxDisabledTime;
}
}

// Apply restrictions in order to disable variants
albertdaurell marked this conversation as resolved.
Show resolved Hide resolved
shaka.util.StreamUtils.applyRestrictions(
manifest.variants, this.config_.restrictions, this.maxHwRes_);

// Select for a new variant
const chosenVariant = this.chooseVariant_();
if (!chosenVariant) {
shaka.log.warning('Not enough variants to recover from error');
return false;
}

// Get the safeMargin to ensure a seamless playback
const {video} = this.getBufferedInfo();
const safeMargin =
video.reduce((size, {start, end}) => size + end - start, 0);

this.switchVariant_(chosenVariant, /* fromAdaptation= */ false,
/* clearBuffers= */ true, /* safeMargin= */ safeMargin);

this.checkVariantsTimer_.tickAfter(maxDisabledTime);
return true;
}

/**
* @param {!shaka.util.Error} error
* @private
Expand All @@ -5498,6 +5574,12 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
return;
}


if (this.tryToRecoverFromError_(error)) {
error.handled = true;
return;
}

const eventName = shaka.Player.EventName.Error;
const event = this.makeEvent_(eventName, (new Map()).set('detail', error));
this.dispatchEvent(event);
Expand Down
1 change: 1 addition & 0 deletions lib/util/player_configuration.js
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@ shaka.util.PlayerConfiguration = class {
updateIntervalSeconds: 1,
dispatchAllEmsgBoxes: false,
observeQualityChanges: false,
maxDisabledTime: 30,
};

// Some browsers will stop earlier than others before a gap (e.g., Edge
Expand Down
7 changes: 7 additions & 0 deletions lib/util/stream_utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,13 @@ shaka.util.StreamUtils = class {

const video = variant.video;

if (variant.disabledUntilTime != 0) {
if (variant.disabledUntilTime > Date.now() / 1000) {
return false;
}
variant.disabledUntilTime = 0;
}

// |video.width| and |video.height| can be undefined, which breaks
// the math, so make sure they are there first.
if (video && video.width && video.height) {
Expand Down
1 change: 1 addition & 0 deletions test/media/adaptation_set_unit.js
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@ describe('AdaptationSet', () => {
audio: audio,
bandwidth: 1024,
id: id,
disabledUntilTime: 0,
language: '',
primary: false,
video: video,
Expand Down
1 change: 1 addition & 0 deletions test/media/streaming_engine_unit.js
Original file line number Diff line number Diff line change
Expand Up @@ -396,6 +396,7 @@ describe('StreamingEngine', () => {
video: /** @type {shaka.extern.Stream} */ (alternateVideoStream),
id: 0,
language: 'und',
disabledUntilTime: 0,
primary: false,
bandwidth: 0,
allowedByApplication: true,
Expand Down
Loading