UNPKG

shaka-player

Version:
231 lines (200 loc) 6.37 kB
/*! @license * Shaka Player * Copyright 2016 Google LLC * SPDX-License-Identifier: Apache-2.0 */ goog.provide('shaka.media.AdaptationSetCriteria'); goog.provide('shaka.media.ExampleBasedCriteria'); goog.provide('shaka.media.PreferenceBasedCriteria'); goog.require('shaka.log'); goog.require('shaka.media.AdaptationSet'); goog.require('shaka.util.LanguageUtils'); goog.require('shaka.util.StreamUtils'); /** * An adaptation set criteria is a unit of logic that can take a set of * variants and return a subset of variants that should (and can) be * adapted between. * * @interface */ shaka.media.AdaptationSetCriteria = class { /** * Take a set of variants, and return a subset of variants that can be * adapted between. * * @param {!Array.<shaka.extern.Variant>} variants * @return {!shaka.media.AdaptationSet} */ create(variants) {} }; /** * @implements {shaka.media.AdaptationSetCriteria} * @final */ shaka.media.ExampleBasedCriteria = class { /** * @param {shaka.extern.Variant} example */ constructor(example) { /** @private {shaka.extern.Variant} */ this.example_ = example; // We can't know if role and label are really important, so we don't use // role and label for this. const role = ''; const label = ''; const channelCount = example.audio && example.audio.channelsCount ? example.audio.channelsCount : 0; /** @private {!shaka.media.AdaptationSetCriteria} */ this.fallback_ = new shaka.media.PreferenceBasedCriteria( example.language, role, channelCount, label); } /** @override */ create(variants) { // We can't assume that the example is in |variants| because it could // actually be from another period. const shortList = variants.filter((variant) => { return shaka.media.AdaptationSet.areAdaptable(this.example_, variant); }); if (shortList.length) { // Use the first item in the short list as the root. It should not matter // which element we use as all items in the short list should already be // compatible. return new shaka.media.AdaptationSet(shortList[0], shortList); } else { return this.fallback_.create(variants); } } }; /** * @implements {shaka.media.AdaptationSetCriteria} * @final */ shaka.media.PreferenceBasedCriteria = class { /** * @param {string} language * @param {string} role * @param {number} channelCount * @param {string=} label */ constructor(language, role, channelCount, label = '') { /** @private {string} */ this.language_ = language; /** @private {string} */ this.role_ = role; /** @private {number} */ this.channelCount_ = channelCount; /** @private {string} */ this.label_ = label; } /** @override */ create(variants) { const Class = shaka.media.PreferenceBasedCriteria; const StreamUtils = shaka.util.StreamUtils; 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.channelCount_) { const byChannel = StreamUtils.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.label_) { const byLabel = Class.filterVariantsByLabel_(current, this.label_); if (byLabel.length) { current = byLabel; } else { shaka.log.warning('No exact match for variant label could be found.'); } } // Make sure we only return a valid adaptation set. const set = new shaka.media.AdaptationSet(current[0]); for (const variant of current) { if (set.canInclude(variant)) { set.add(variant); } } return set; } /** * @param {!Array.<shaka.extern.Variant>} variants * @param {string} preferredLanguage * @return {!Array.<shaka.extern.Variant>} * @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.<shaka.extern.Variant>} variants * @param {string} preferredRole * @return {!Array.<shaka.extern.Variant>} * @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 label. * * @param {!Array.<shaka.extern.Variant>} variants * @param {string} preferredLabel * @return {!Array.<shaka.extern.Variant>} * @private */ static filterVariantsByLabel_(variants, preferredLabel) { return variants.filter((variant) => { if (!variant.audio) { return false; } const label1 = variant.audio.label.toLowerCase(); const label2 = preferredLabel.toLowerCase(); return label1 == label2; }); } };