Skip to content

Commit

Permalink
feat: Temporarily disable the active variant from NETWORK HTTP_ERROR (s…
Browse files Browse the repository at this point in the history
…haka-project#4189)

When a `NETWORK HTTP_ERROR` is thrown, we'll temporarily disable the variant for `shaka.extern.Restrictions.maxDisabledTime` seconds (default will be 30 seconds).

Closes shaka-project#4121
Closes shaka-project#1542
  • Loading branch information
albertdaurell committed May 5, 2022
1 parent 3cfd3d3 commit b57279d
Show file tree
Hide file tree
Showing 17 changed files with 369 additions and 3 deletions.
1 change: 1 addition & 0 deletions demo/common/message_ids.js
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
Expand Up @@ -392,7 +392,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
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
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
7 changes: 7 additions & 0 deletions externs/shaka/manifest.js
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,12 @@ 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() / 1000) + config.streaming.maxDisabledTime" and once this
* maxDisabledTime 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
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
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
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
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 @@ -5203,6 +5211,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 @@ -5490,6 +5513,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
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 @@ -5503,6 +5579,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
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
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
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
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

0 comments on commit b57279d

Please sign in to comment.