Source: lib/media/adaptation_set_criteria.js

  1. /**
  2. * @license
  3. * Copyright 2016 Google Inc.
  4. *
  5. * Licensed under the Apache License, Version 2.0 (the "License");
  6. * you may not use this file except in compliance with the License.
  7. * You may obtain a copy of the License at
  8. *
  9. * http://www.apache.org/licenses/LICENSE-2.0
  10. *
  11. * Unless required by applicable law or agreed to in writing, software
  12. * distributed under the License is distributed on an "AS IS" BASIS,
  13. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. * See the License for the specific language governing permissions and
  15. * limitations under the License.
  16. */
  17. goog.provide('shaka.media.AdaptationSetCriteria');
  18. goog.provide('shaka.media.ExampleBasedCriteria');
  19. goog.provide('shaka.media.PreferenceBasedCriteria');
  20. goog.require('shaka.media.AdaptationSet');
  21. /**
  22. * An adaptation set criteria is a unit of logic that can take a set of
  23. * variants and return a subset of variants that should (and can) be
  24. * adapted between.
  25. *
  26. * @interface
  27. */
  28. shaka.media.AdaptationSetCriteria = class {
  29. /**
  30. * Take a set of variants, and return a subset of variants that can be
  31. * adapted between.
  32. *
  33. * @param {!Array.<shaka.extern.Variant>} variants
  34. * @return {!shaka.media.AdaptationSet}
  35. */
  36. create(variants) {}
  37. };
  38. /**
  39. * @implements {shaka.media.AdaptationSetCriteria}
  40. * @final
  41. */
  42. shaka.media.ExampleBasedCriteria = class {
  43. /**
  44. * @param {shaka.extern.Variant} example
  45. */
  46. constructor(example) {
  47. /** @private {shaka.extern.Variant} */
  48. this.example_ = example;
  49. // We can't know what role is really the most important, so we don't use
  50. // role for this.
  51. const role = '';
  52. const channelCount = example.audio && example.audio.channelsCount ?
  53. example.audio.channelsCount :
  54. 0;
  55. /** @private {!shaka.media.AdaptationSetCriteria} */
  56. this.fallback_ = new shaka.media.PreferenceBasedCriteria(
  57. example.language, role, channelCount);
  58. }
  59. /** @override */
  60. create(variants) {
  61. // We can't assume that the example is |variants| because it could actually
  62. // be from another period.
  63. const shortList = variants.filter((variant) => {
  64. return shaka.media.AdaptationSet.areAdaptable(this.example_, variant);
  65. });
  66. if (shortList.length) {
  67. // Use the first item in the short list as the root. It should not matter
  68. // which element we use as all items in the short list should already be
  69. // compatible.
  70. return new shaka.media.AdaptationSet(shortList[0], shortList);
  71. } else {
  72. return this.fallback_.create(variants);
  73. }
  74. }
  75. };
  76. /**
  77. * @implements {shaka.media.AdaptationSetCriteria}
  78. * @final
  79. */
  80. shaka.media.PreferenceBasedCriteria = class {
  81. /**
  82. * @param {string} language
  83. * @param {string} role
  84. * @param {number} channelCount
  85. */
  86. constructor(language, role, channelCount) {
  87. /** @private {string} */
  88. this.language_ = language;
  89. /** @private {string} */
  90. this.role_ = role;
  91. /** @private {number} */
  92. this.channelCount_ = channelCount;
  93. }
  94. /** @override */
  95. create(variants) {
  96. const Class = shaka.media.PreferenceBasedCriteria;
  97. const StreamUtils = shaka.util.StreamUtils;
  98. let current = [];
  99. const byLanguage = Class.filterByLanguage_(variants, this.language_);
  100. const byPrimary = variants.filter((variant) => variant.primary);
  101. if (byLanguage.length) {
  102. current = byLanguage;
  103. } else if (byPrimary.length) {
  104. current = byPrimary;
  105. } else {
  106. current = variants;
  107. }
  108. // Now refine the choice based on role preference.
  109. if (this.role_) {
  110. const byRole = Class.filterVariantsByRole_(current, this.role_);
  111. if (byRole.length) {
  112. current = byRole;
  113. } else {
  114. shaka.log.warning('No exact match for variant role could be found.');
  115. }
  116. }
  117. if (this.channelCount_) {
  118. const byChannel = StreamUtils.filterVariantsByAudioChannelCount(
  119. current, this.channelCount_);
  120. if (byChannel.length) {
  121. current = byChannel;
  122. } else {
  123. shaka.log.warning(
  124. 'No exact match for the channel count could be found.');
  125. }
  126. }
  127. // Make sure we only return a valid adaptation set.
  128. const set = new shaka.media.AdaptationSet(current[0]);
  129. for (const variant of current) {
  130. if (set.canInclude(variant)) {
  131. set.add(variant);
  132. }
  133. }
  134. return set;
  135. }
  136. /**
  137. * @param {!Array.<shaka.extern.Variant>} variants
  138. * @param {string} preferredLanguage
  139. * @return {!Array.<shaka.extern.Variant>}
  140. * @private
  141. */
  142. static filterByLanguage_(variants, preferredLanguage) {
  143. const LanguageUtils = shaka.util.LanguageUtils;
  144. const findClosestLocale = LanguageUtils.findClosestLocale;
  145. const getLocaleForVariant = LanguageUtils.getLocaleForVariant;
  146. const normalize = LanguageUtils.normalize;
  147. /** @type {string} */
  148. const preferredLocale = normalize(preferredLanguage);
  149. /** @type {?string} */
  150. const closestLocale = findClosestLocale(
  151. preferredLocale,
  152. variants.map((variant) => getLocaleForVariant(variant)));
  153. // There were no locales close to what we preferred.
  154. if (!closestLocale) {
  155. return [];
  156. }
  157. // Find the variants that use the closest variant.
  158. return variants.filter((variant) => {
  159. return closestLocale == getLocaleForVariant(variant);
  160. });
  161. }
  162. /**
  163. * Filter Variants by role.
  164. *
  165. * @param {!Array.<shaka.extern.Variant>} variants
  166. * @param {string} preferredRole
  167. * @return {!Array.<shaka.extern.Variant>}
  168. * @private
  169. */
  170. static filterVariantsByRole_(variants, preferredRole) {
  171. return variants.filter((variant) => {
  172. const audio = variant.audio;
  173. const video = variant.video;
  174. return (audio && audio.roles.indexOf(preferredRole) >= 0) ||
  175. (video && video.roles.indexOf(preferredRole) >= 0);
  176. });
  177. }
  178. };