From 59928f914b6f0a6852fb5b44f979543ee3d1096e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?A=CC=81lvaro=20Velad=20Galva=CC=81n?= Date: Fri, 20 Dec 2024 13:03:08 +0100 Subject: [PATCH] chore: Move PreferenceBasedCriteria and ExampleBasedCriteria to their own files Related to https://github.com/shaka-project/shaka-player/issues/7768 --- build/types/core | 2 + lib/media/adaptation_set_criteria.js | 385 ------------------------- lib/media/example_based_criteria.js | 51 ++++ lib/media/preference_based_criteria.js | 353 +++++++++++++++++++++++ 4 files changed, 406 insertions(+), 385 deletions(-) create mode 100644 lib/media/example_based_criteria.js create mode 100644 lib/media/preference_based_criteria.js diff --git a/build/types/core b/build/types/core index 27bdbe1ecf..cd8fd78e34 100644 --- a/build/types/core +++ b/build/types/core @@ -25,6 +25,7 @@ +../../lib/media/closed_caption_parser.js +../../lib/media/content_workarounds.js +../../lib/media/drm_engine.js ++../../lib/media/example_based_criteria.js +../../lib/media/gap_jumping_controller.js +../../lib/media/manifest_filterer.js +../../lib/media/manifest_parser.js @@ -34,6 +35,7 @@ +../../lib/media/play_rate_controller.js +../../lib/media/playhead.js +../../lib/media/playhead_observer.js ++../../lib/media/preference_based_criteria.js +../../lib/media/preload_manager.js +../../lib/media/presentation_timeline.js +../../lib/media/quality_observer.js diff --git a/lib/media/adaptation_set_criteria.js b/lib/media/adaptation_set_criteria.js index d15f0dc2d4..d99c61d15c 100644 --- a/lib/media/adaptation_set_criteria.js +++ b/lib/media/adaptation_set_criteria.js @@ -5,14 +5,8 @@ */ goog.provide('shaka.media.AdaptationSetCriteria'); -goog.provide('shaka.media.ExampleBasedCriteria'); -goog.provide('shaka.media.PreferenceBasedCriteria'); -goog.require('shaka.config.CodecSwitchingStrategy'); -goog.require('shaka.log'); goog.require('shaka.media.AdaptationSet'); -goog.require('shaka.media.Capabilities'); -goog.require('shaka.util.LanguageUtils'); /** @@ -32,382 +26,3 @@ shaka.media.AdaptationSetCriteria = class { */ create(variants) {} }; - - -/** - * @implements {shaka.media.AdaptationSetCriteria} - * @final - */ -shaka.media.ExampleBasedCriteria = class { - /** - * @param {shaka.extern.Variant} example - * @param {shaka.config.CodecSwitchingStrategy} codecSwitchingStrategy - */ - constructor(example, codecSwitchingStrategy) { - // We can't know if role and label are really important, so we don't use - // role and label for this. - const role = ''; - const audioLabel = ''; - const videoLabel = ''; - const hdrLevel = example.video && example.video.hdr ? - example.video.hdr : ''; - const spatialAudio = example.audio && example.audio.spatialAudio ? - example.audio.spatialAudio : false; - const videoLayout = example.video && example.video.videoLayout ? - example.video.videoLayout : ''; - const channelCount = example.audio && example.audio.channelsCount ? - example.audio.channelsCount : 0; - const audioCodec = example.audio && example.audio.codecs ? - example.audio.codecs : ''; - - /** @private {!shaka.media.AdaptationSetCriteria} */ - this.preferenceBasedCriteria_ = new shaka.media.PreferenceBasedCriteria( - example.language, role, channelCount, hdrLevel, spatialAudio, - videoLayout, audioLabel, videoLabel, - codecSwitchingStrategy, audioCodec); - } - - /** @override */ - create(variants) { - return this.preferenceBasedCriteria_.create(variants); - } -}; - - -/** - * @implements {shaka.media.AdaptationSetCriteria} - * @final - */ -shaka.media.PreferenceBasedCriteria = class { - /** - * @param {string} language - * @param {string} role - * @param {number} channelCount - * @param {string} hdrLevel - * @param {boolean} spatialAudio - * @param {string} videoLayout - * @param {string} audioLabel - * @param {string} videoLabel - * @param {shaka.config.CodecSwitchingStrategy} codecSwitchingStrategy - * @param {string} audioCodec - */ - constructor(language, role, channelCount, hdrLevel, spatialAudio, - videoLayout, audioLabel, videoLabel, codecSwitchingStrategy, audioCodec) { - /** @private {string} */ - this.language_ = language; - /** @private {string} */ - this.role_ = role; - /** @private {number} */ - this.channelCount_ = channelCount; - /** @private {string} */ - this.hdrLevel_ = hdrLevel; - /** @private {boolean} */ - this.spatialAudio_ = spatialAudio; - /** @private {string} */ - this.videoLayout_ = videoLayout; - /** @private {string} */ - this.audioLabel_ = audioLabel; - /** @private {string} */ - this.videoLabel_ = videoLabel; - /** @private {shaka.config.CodecSwitchingStrategy} */ - this.codecSwitchingStrategy_ = codecSwitchingStrategy; - /** @private {string} */ - this.audioCodec_ = audioCodec; - } - - /** @override */ - create(variants) { - const Class = shaka.media.PreferenceBasedCriteria; - - let current = []; - - const byLanguage = Class.filterByLanguage_(variants, this.language_); - const byPrimary = variants.filter((variant) => variant.primary); - - if (byLanguage.length) { - current = byLanguage; - } else if (byPrimary.length) { - current = byPrimary; - } else { - current = variants; - } - - // Now refine the choice based on role preference. Even the empty string - // works here, and will match variants without any roles. - const byRole = Class.filterVariantsByRole_(current, this.role_); - if (byRole.length) { - current = byRole; - } else { - shaka.log.warning('No exact match for variant role could be found.'); - } - - if (this.videoLayout_) { - const byVideoLayout = Class.filterVariantsByVideoLayout_( - current, this.videoLayout_); - if (byVideoLayout.length) { - current = byVideoLayout; - } else { - shaka.log.warning( - 'No exact match for the video layout could be found.'); - } - } - - if (this.hdrLevel_) { - const byHdrLevel = Class.filterVariantsByHDRLevel_( - current, this.hdrLevel_); - if (byHdrLevel.length) { - current = byHdrLevel; - } else { - shaka.log.warning( - 'No exact match for the hdr level could be found.'); - } - } - - if (this.channelCount_) { - const byChannel = Class.filterVariantsByAudioChannelCount_( - current, this.channelCount_); - if (byChannel.length) { - current = byChannel; - } else { - shaka.log.warning( - 'No exact match for the channel count could be found.'); - } - } - - if (this.audioLabel_) { - const byLabel = Class.filterVariantsByAudioLabel_( - current, this.audioLabel_); - if (byLabel.length) { - current = byLabel; - } else { - shaka.log.warning('No exact match for audio label could be found.'); - } - } - - if (this.videoLabel_) { - const byLabel = Class.filterVariantsByVideoLabel_( - current, this.videoLabel_); - if (byLabel.length) { - current = byLabel; - } else { - shaka.log.warning('No exact match for video label could be found.'); - } - } - - const bySpatialAudio = Class.filterVariantsBySpatialAudio_( - current, this.spatialAudio_); - if (bySpatialAudio.length) { - current = bySpatialAudio; - } else { - shaka.log.warning('No exact match for spatial audio could be found.'); - } - - if (this.audioCodec_) { - const byAudioCodec = Class.filterVariantsByAudioCodec_( - current, this.audioCodec_); - if (byAudioCodec.length) { - current = byAudioCodec; - } else { - shaka.log.warning('No exact match for audio codec could be found.'); - } - } - - const supportsSmoothCodecTransitions = this.codecSwitchingStrategy_ == - shaka.config.CodecSwitchingStrategy.SMOOTH && - shaka.media.Capabilities.isChangeTypeSupported(); - - return new shaka.media.AdaptationSet(current[0], current, - !supportsSmoothCodecTransitions); - } - - /** - * @param {!Array.} variants - * @param {string} preferredLanguage - * @return {!Array.} - * @private - */ - static filterByLanguage_(variants, preferredLanguage) { - const LanguageUtils = shaka.util.LanguageUtils; - - /** @type {string} */ - const preferredLocale = LanguageUtils.normalize(preferredLanguage); - - /** @type {?string} */ - const closestLocale = LanguageUtils.findClosestLocale( - preferredLocale, - variants.map((variant) => LanguageUtils.getLocaleForVariant(variant))); - - // There were no locales close to what we preferred. - if (!closestLocale) { - return []; - } - - // Find the variants that use the closest variant. - return variants.filter((variant) => { - return closestLocale == LanguageUtils.getLocaleForVariant(variant); - }); - } - - /** - * Filter Variants by role. - * - * @param {!Array.} variants - * @param {string} preferredRole - * @return {!Array.} - * @private - */ - static filterVariantsByRole_(variants, preferredRole) { - return variants.filter((variant) => { - if (!variant.audio) { - return false; - } - - if (preferredRole) { - return variant.audio.roles.includes(preferredRole); - } else { - return variant.audio.roles.length == 0; - } - }); - } - - /** - * Filter Variants by audio label. - * - * @param {!Array.} variants - * @param {string} preferredLabel - * @return {!Array.} - * @private - */ - static filterVariantsByAudioLabel_(variants, preferredLabel) { - return variants.filter((variant) => { - if (!variant.audio || !variant.audio.label) { - return false; - } - - const label1 = variant.audio.label.toLowerCase(); - const label2 = preferredLabel.toLowerCase(); - return label1 == label2; - }); - } - - /** - * Filter Variants by video label. - * - * @param {!Array.} variants - * @param {string} preferredLabel - * @return {!Array.} - * @private - */ - static filterVariantsByVideoLabel_(variants, preferredLabel) { - return variants.filter((variant) => { - if (!variant.video || !variant.video.label) { - return false; - } - - const label1 = variant.video.label.toLowerCase(); - const label2 = preferredLabel.toLowerCase(); - return label1 == label2; - }); - } - - /** - * Filter Variants by channelCount. - * - * @param {!Array.} variants - * @param {number} channelCount - * @return {!Array.} - * @private - */ - static filterVariantsByAudioChannelCount_(variants, channelCount) { - return variants.filter((variant) => { - if (variant.audio && variant.audio.channelsCount && - variant.audio.channelsCount != channelCount) { - return false; - } - return true; - }); - } - - /** - * Filters variants according to the given hdr level config. - * - * @param {!Array.} variants - * @param {string} hdrLevel - * @private - */ - static filterVariantsByHDRLevel_(variants, hdrLevel) { - if (hdrLevel == 'AUTO') { - // Auto detect the ideal HDR level. - if (window.matchMedia('(color-gamut: p3)').matches) { - const someHLG = variants.some((variant) => { - if (variant.video && variant.video.hdr && - variant.video.hdr == 'HLG') { - return true; - } - return false; - }); - hdrLevel = someHLG ? 'HLG' : 'PQ'; - } else { - hdrLevel = 'SDR'; - } - } - return variants.filter((variant) => { - if (variant.video && variant.video.hdr && variant.video.hdr != hdrLevel) { - return false; - } - return true; - }); - } - - - /** - * Filters variants according to the given video layout config. - * - * @param {!Array.} variants - * @param {string} videoLayout - * @private - */ - static filterVariantsByVideoLayout_(variants, videoLayout) { - return variants.filter((variant) => { - if (variant.video && variant.video.videoLayout && - variant.video.videoLayout != videoLayout) { - return false; - } - return true; - }); - } - - - /** - * Filters variants according to the given spatial audio config. - * - * @param {!Array.} variants - * @param {boolean} spatialAudio - * @private - */ - static filterVariantsBySpatialAudio_(variants, spatialAudio) { - return variants.filter((variant) => { - if (variant.audio && variant.audio.spatialAudio != spatialAudio) { - return false; - } - return true; - }); - } - - - /** - * Filters variants according to the given audio codec. - * - * @param {!Array} variants - * @param {string} audioCodec - * @private - */ - static filterVariantsByAudioCodec_(variants, audioCodec) { - return variants.filter((variant) => { - if (variant.audio && variant.audio.codecs != audioCodec) { - return false; - } - return true; - }); - } -}; diff --git a/lib/media/example_based_criteria.js b/lib/media/example_based_criteria.js new file mode 100644 index 0000000000..b17313c2ed --- /dev/null +++ b/lib/media/example_based_criteria.js @@ -0,0 +1,51 @@ +/*! @license + * Shaka Player + * Copyright 2016 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +goog.provide('shaka.media.ExampleBasedCriteria'); + +goog.require('shaka.media.AdaptationSetCriteria'); +goog.require('shaka.media.PreferenceBasedCriteria'); +goog.requireType('shaka.config.CodecSwitchingStrategy'); + + +/** + * @implements {shaka.media.AdaptationSetCriteria} + * @final + */ +shaka.media.ExampleBasedCriteria = class { + /** + * @param {shaka.extern.Variant} example + * @param {shaka.config.CodecSwitchingStrategy} codecSwitchingStrategy + */ + constructor(example, codecSwitchingStrategy) { + // We can't know if role and label are really important, so we don't use + // role and label for this. + const role = ''; + const audioLabel = ''; + const videoLabel = ''; + const hdrLevel = example.video && example.video.hdr ? + example.video.hdr : ''; + const spatialAudio = example.audio && example.audio.spatialAudio ? + example.audio.spatialAudio : false; + const videoLayout = example.video && example.video.videoLayout ? + example.video.videoLayout : ''; + const channelCount = example.audio && example.audio.channelsCount ? + example.audio.channelsCount : 0; + const audioCodec = example.audio && example.audio.codecs ? + example.audio.codecs : ''; + + /** @private {!shaka.media.AdaptationSetCriteria} */ + this.preferenceBasedCriteria_ = new shaka.media.PreferenceBasedCriteria( + example.language, role, channelCount, hdrLevel, spatialAudio, + videoLayout, audioLabel, videoLabel, + codecSwitchingStrategy, audioCodec); + } + + /** @override */ + create(variants) { + return this.preferenceBasedCriteria_.create(variants); + } +}; diff --git a/lib/media/preference_based_criteria.js b/lib/media/preference_based_criteria.js new file mode 100644 index 0000000000..19b8d2c78c --- /dev/null +++ b/lib/media/preference_based_criteria.js @@ -0,0 +1,353 @@ +/*! @license + * Shaka Player + * Copyright 2016 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +goog.provide('shaka.media.PreferenceBasedCriteria'); + +goog.require('shaka.config.CodecSwitchingStrategy'); +goog.require('shaka.log'); +goog.require('shaka.media.AdaptationSet'); +goog.require('shaka.media.AdaptationSetCriteria'); +goog.require('shaka.media.Capabilities'); +goog.require('shaka.util.LanguageUtils'); + + +/** + * @implements {shaka.media.AdaptationSetCriteria} + * @final + */ +shaka.media.PreferenceBasedCriteria = class { + /** + * @param {string} language + * @param {string} role + * @param {number} channelCount + * @param {string} hdrLevel + * @param {boolean} spatialAudio + * @param {string} videoLayout + * @param {string} audioLabel + * @param {string} videoLabel + * @param {shaka.config.CodecSwitchingStrategy} codecSwitchingStrategy + * @param {string} audioCodec + */ + constructor(language, role, channelCount, hdrLevel, spatialAudio, + videoLayout, audioLabel, videoLabel, codecSwitchingStrategy, audioCodec) { + /** @private {string} */ + this.language_ = language; + /** @private {string} */ + this.role_ = role; + /** @private {number} */ + this.channelCount_ = channelCount; + /** @private {string} */ + this.hdrLevel_ = hdrLevel; + /** @private {boolean} */ + this.spatialAudio_ = spatialAudio; + /** @private {string} */ + this.videoLayout_ = videoLayout; + /** @private {string} */ + this.audioLabel_ = audioLabel; + /** @private {string} */ + this.videoLabel_ = videoLabel; + /** @private {shaka.config.CodecSwitchingStrategy} */ + this.codecSwitchingStrategy_ = codecSwitchingStrategy; + /** @private {string} */ + this.audioCodec_ = audioCodec; + } + + /** @override */ + create(variants) { + const Class = shaka.media.PreferenceBasedCriteria; + + let current = []; + + const byLanguage = Class.filterByLanguage_(variants, this.language_); + const byPrimary = variants.filter((variant) => variant.primary); + + if (byLanguage.length) { + current = byLanguage; + } else if (byPrimary.length) { + current = byPrimary; + } else { + current = variants; + } + + // Now refine the choice based on role preference. Even the empty string + // works here, and will match variants without any roles. + const byRole = Class.filterVariantsByRole_(current, this.role_); + if (byRole.length) { + current = byRole; + } else { + shaka.log.warning('No exact match for variant role could be found.'); + } + + if (this.videoLayout_) { + const byVideoLayout = Class.filterVariantsByVideoLayout_( + current, this.videoLayout_); + if (byVideoLayout.length) { + current = byVideoLayout; + } else { + shaka.log.warning( + 'No exact match for the video layout could be found.'); + } + } + + if (this.hdrLevel_) { + const byHdrLevel = Class.filterVariantsByHDRLevel_( + current, this.hdrLevel_); + if (byHdrLevel.length) { + current = byHdrLevel; + } else { + shaka.log.warning( + 'No exact match for the hdr level could be found.'); + } + } + + if (this.channelCount_) { + const byChannel = Class.filterVariantsByAudioChannelCount_( + current, this.channelCount_); + if (byChannel.length) { + current = byChannel; + } else { + shaka.log.warning( + 'No exact match for the channel count could be found.'); + } + } + + if (this.audioLabel_) { + const byLabel = Class.filterVariantsByAudioLabel_( + current, this.audioLabel_); + if (byLabel.length) { + current = byLabel; + } else { + shaka.log.warning('No exact match for audio label could be found.'); + } + } + + if (this.videoLabel_) { + const byLabel = Class.filterVariantsByVideoLabel_( + current, this.videoLabel_); + if (byLabel.length) { + current = byLabel; + } else { + shaka.log.warning('No exact match for video label could be found.'); + } + } + + const bySpatialAudio = Class.filterVariantsBySpatialAudio_( + current, this.spatialAudio_); + if (bySpatialAudio.length) { + current = bySpatialAudio; + } else { + shaka.log.warning('No exact match for spatial audio could be found.'); + } + + if (this.audioCodec_) { + const byAudioCodec = Class.filterVariantsByAudioCodec_( + current, this.audioCodec_); + if (byAudioCodec.length) { + current = byAudioCodec; + } else { + shaka.log.warning('No exact match for audio codec could be found.'); + } + } + + const supportsSmoothCodecTransitions = this.codecSwitchingStrategy_ == + shaka.config.CodecSwitchingStrategy.SMOOTH && + shaka.media.Capabilities.isChangeTypeSupported(); + + return new shaka.media.AdaptationSet(current[0], current, + !supportsSmoothCodecTransitions); + } + + /** + * @param {!Array.} variants + * @param {string} preferredLanguage + * @return {!Array.} + * @private + */ + static filterByLanguage_(variants, preferredLanguage) { + const LanguageUtils = shaka.util.LanguageUtils; + + /** @type {string} */ + const preferredLocale = LanguageUtils.normalize(preferredLanguage); + + /** @type {?string} */ + const closestLocale = LanguageUtils.findClosestLocale( + preferredLocale, + variants.map((variant) => LanguageUtils.getLocaleForVariant(variant))); + + // There were no locales close to what we preferred. + if (!closestLocale) { + return []; + } + + // Find the variants that use the closest variant. + return variants.filter((variant) => { + return closestLocale == LanguageUtils.getLocaleForVariant(variant); + }); + } + + /** + * Filter Variants by role. + * + * @param {!Array.} variants + * @param {string} preferredRole + * @return {!Array.} + * @private + */ + static filterVariantsByRole_(variants, preferredRole) { + return variants.filter((variant) => { + if (!variant.audio) { + return false; + } + + if (preferredRole) { + return variant.audio.roles.includes(preferredRole); + } else { + return variant.audio.roles.length == 0; + } + }); + } + + /** + * Filter Variants by audio label. + * + * @param {!Array.} variants + * @param {string} preferredLabel + * @return {!Array.} + * @private + */ + static filterVariantsByAudioLabel_(variants, preferredLabel) { + return variants.filter((variant) => { + if (!variant.audio || !variant.audio.label) { + return false; + } + + const label1 = variant.audio.label.toLowerCase(); + const label2 = preferredLabel.toLowerCase(); + return label1 == label2; + }); + } + + /** + * Filter Variants by video label. + * + * @param {!Array.} variants + * @param {string} preferredLabel + * @return {!Array.} + * @private + */ + static filterVariantsByVideoLabel_(variants, preferredLabel) { + return variants.filter((variant) => { + if (!variant.video || !variant.video.label) { + return false; + } + + const label1 = variant.video.label.toLowerCase(); + const label2 = preferredLabel.toLowerCase(); + return label1 == label2; + }); + } + + /** + * Filter Variants by channelCount. + * + * @param {!Array.} variants + * @param {number} channelCount + * @return {!Array.} + * @private + */ + static filterVariantsByAudioChannelCount_(variants, channelCount) { + return variants.filter((variant) => { + if (variant.audio && variant.audio.channelsCount && + variant.audio.channelsCount != channelCount) { + return false; + } + return true; + }); + } + + /** + * Filters variants according to the given hdr level config. + * + * @param {!Array.} variants + * @param {string} hdrLevel + * @private + */ + static filterVariantsByHDRLevel_(variants, hdrLevel) { + if (hdrLevel == 'AUTO') { + // Auto detect the ideal HDR level. + if (window.matchMedia('(color-gamut: p3)').matches) { + const someHLG = variants.some((variant) => { + if (variant.video && variant.video.hdr && + variant.video.hdr == 'HLG') { + return true; + } + return false; + }); + hdrLevel = someHLG ? 'HLG' : 'PQ'; + } else { + hdrLevel = 'SDR'; + } + } + return variants.filter((variant) => { + if (variant.video && variant.video.hdr && variant.video.hdr != hdrLevel) { + return false; + } + return true; + }); + } + + + /** + * Filters variants according to the given video layout config. + * + * @param {!Array.} variants + * @param {string} videoLayout + * @private + */ + static filterVariantsByVideoLayout_(variants, videoLayout) { + return variants.filter((variant) => { + if (variant.video && variant.video.videoLayout && + variant.video.videoLayout != videoLayout) { + return false; + } + return true; + }); + } + + + /** + * Filters variants according to the given spatial audio config. + * + * @param {!Array.} variants + * @param {boolean} spatialAudio + * @private + */ + static filterVariantsBySpatialAudio_(variants, spatialAudio) { + return variants.filter((variant) => { + if (variant.audio && variant.audio.spatialAudio != spatialAudio) { + return false; + } + return true; + }); + } + + + /** + * Filters variants according to the given audio codec. + * + * @param {!Array} variants + * @param {string} audioCodec + * @private + */ + static filterVariantsByAudioCodec_(variants, audioCodec) { + return variants.filter((variant) => { + if (variant.audio && variant.audio.codecs != audioCodec) { + return false; + } + return true; + }); + } +};