Source: lib/abr/simple_abr_manager.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.abr.SimpleAbrManager');
  18. goog.require('goog.asserts');
  19. goog.require('shaka.abr.EwmaBandwidthEstimator');
  20. goog.require('shaka.log');
  21. goog.require('shaka.util.StreamUtils');
  22. /**
  23. * <p>
  24. * This defines the default ABR manager for the Player. An instance of this
  25. * class is used when no ABR manager is given.
  26. * </p>
  27. * <p>
  28. * The behavior of this class is to take throughput samples using
  29. * segmentDownloaded to estimate the current network bandwidth. Then it will
  30. * use that to choose the streams that best fit the current bandwidth. It will
  31. * always pick the highest bandwidth variant it thinks can be played.
  32. * </p>
  33. * <p>
  34. * After initial choices are made, this class will call switchCallback() when
  35. * there is a better choice. switchCallback() will not be called more than once
  36. * per ({@link shaka.abr.SimpleAbrManager.SWITCH_INTERVAL_MS}).
  37. * </p>
  38. *
  39. * @constructor
  40. * @struct
  41. * @implements {shakaExtern.AbrManager}
  42. * @export
  43. */
  44. shaka.abr.SimpleAbrManager = function() {
  45. /** @private {?shakaExtern.AbrManager.SwitchCallback} */
  46. this.switch_ = null;
  47. /** @private {boolean} */
  48. this.enabled_ = false;
  49. /** @private {shaka.abr.EwmaBandwidthEstimator} */
  50. this.bandwidthEstimator_ = new shaka.abr.EwmaBandwidthEstimator();
  51. // TODO: Consider using NetworkInformation's change event to throw out an old
  52. // estimate based on changing network types, such as wifi => 3g.
  53. /**
  54. * A filtered list of Variants to choose from.
  55. * @private {!Array.<!shakaExtern.Variant>}
  56. */
  57. this.variants_ = [];
  58. /** @private {boolean} */
  59. this.startupComplete_ = false;
  60. /**
  61. * The last wall-clock time, in milliseconds, when streams were chosen.
  62. *
  63. * @private {?number}
  64. */
  65. this.lastTimeChosenMs_ = null;
  66. /** @private {?shakaExtern.AbrConfiguration} */
  67. this.config_ = null;
  68. };
  69. /**
  70. * @override
  71. * @export
  72. */
  73. shaka.abr.SimpleAbrManager.prototype.stop = function() {
  74. this.switch_ = null;
  75. this.enabled_ = false;
  76. this.variants_ = [];
  77. this.lastTimeChosenMs_ = null;
  78. // Don't reset |startupComplete_|: if we've left the startup interval, we can
  79. // start using bandwidth estimates right away after init() is called.
  80. };
  81. /**
  82. * @override
  83. * @export
  84. */
  85. shaka.abr.SimpleAbrManager.prototype.init = function(switchCallback) {
  86. this.switch_ = switchCallback;
  87. };
  88. /**
  89. * @override
  90. * @export
  91. */
  92. shaka.abr.SimpleAbrManager.prototype.chooseVariant = function() {
  93. const SimpleAbrManager = shaka.abr.SimpleAbrManager;
  94. // Get sorted Variants.
  95. let sortedVariants = SimpleAbrManager.filterAndSortVariants_(
  96. this.config_.restrictions, this.variants_);
  97. let currentBandwidth = this.bandwidthEstimator_.getBandwidthEstimate(
  98. this.config_.defaultBandwidthEstimate);
  99. if (this.variants_.length && !sortedVariants.length) {
  100. // If we couldn't meet the ABR restrictions, we should still play something.
  101. // These restrictions are not "hard" restrictions in the way that top-level
  102. // or DRM-based restrictions are. Sort the variants without restrictions
  103. // and keep just the first (lowest-bandwidth) one.
  104. shaka.log.warning('No variants met the ABR restrictions. ' +
  105. 'Choosing a variant by lowest bandwidth.');
  106. sortedVariants = SimpleAbrManager.filterAndSortVariants_(
  107. /* restrictions */ null, this.variants_);
  108. sortedVariants = [sortedVariants[0]];
  109. }
  110. // Start by assuming that we will use the first Stream.
  111. let chosen = sortedVariants[0] || null;
  112. for (let i = 0; i < sortedVariants.length; ++i) {
  113. let variant = sortedVariants[i];
  114. let nextVariant = sortedVariants[i + 1] || {bandwidth: Infinity};
  115. let minBandwidth = variant.bandwidth /
  116. this.config_.bandwidthDowngradeTarget;
  117. let maxBandwidth = nextVariant.bandwidth /
  118. this.config_.bandwidthUpgradeTarget;
  119. shaka.log.v2('Bandwidth ranges:',
  120. (variant.bandwidth / 1e6).toFixed(3),
  121. (minBandwidth / 1e6).toFixed(3),
  122. (maxBandwidth / 1e6).toFixed(3));
  123. if (currentBandwidth >= minBandwidth && currentBandwidth <= maxBandwidth) {
  124. chosen = variant;
  125. }
  126. }
  127. this.lastTimeChosenMs_ = Date.now();
  128. return chosen;
  129. };
  130. /**
  131. * @override
  132. * @export
  133. */
  134. shaka.abr.SimpleAbrManager.prototype.enable = function() {
  135. this.enabled_ = true;
  136. };
  137. /**
  138. * @override
  139. * @export
  140. */
  141. shaka.abr.SimpleAbrManager.prototype.disable = function() {
  142. this.enabled_ = false;
  143. };
  144. /**
  145. * @override
  146. * @export
  147. */
  148. shaka.abr.SimpleAbrManager.prototype.segmentDownloaded = function(
  149. deltaTimeMs, numBytes) {
  150. shaka.log.v2('Segment downloaded:',
  151. 'deltaTimeMs=' + deltaTimeMs,
  152. 'numBytes=' + numBytes,
  153. 'lastTimeChosenMs=' + this.lastTimeChosenMs_,
  154. 'enabled=' + this.enabled_);
  155. goog.asserts.assert(deltaTimeMs >= 0, 'expected a non-negative duration');
  156. this.bandwidthEstimator_.sample(deltaTimeMs, numBytes);
  157. if ((this.lastTimeChosenMs_ != null) && this.enabled_) {
  158. this.suggestStreams_();
  159. }
  160. };
  161. /**
  162. * @override
  163. * @export
  164. */
  165. shaka.abr.SimpleAbrManager.prototype.getBandwidthEstimate = function() {
  166. return this.bandwidthEstimator_.getBandwidthEstimate(
  167. this.config_.defaultBandwidthEstimate);
  168. };
  169. /**
  170. * @override
  171. * @export
  172. */
  173. shaka.abr.SimpleAbrManager.prototype.setVariants = function(variants) {
  174. this.variants_ = variants;
  175. };
  176. /**
  177. * @override
  178. * @export
  179. */
  180. shaka.abr.SimpleAbrManager.prototype.configure = function(config) {
  181. this.config_ = config;
  182. };
  183. /**
  184. * Calls switch_() with the variant chosen by chooseVariant().
  185. *
  186. * @private
  187. */
  188. shaka.abr.SimpleAbrManager.prototype.suggestStreams_ = function() {
  189. shaka.log.v2('Suggesting Streams...');
  190. goog.asserts.assert(this.lastTimeChosenMs_ != null,
  191. 'lastTimeChosenMs_ should not be null');
  192. if (!this.startupComplete_) {
  193. // Check if we've got enough data yet.
  194. if (!this.bandwidthEstimator_.hasGoodEstimate()) {
  195. shaka.log.v2('Still waiting for a good estimate...');
  196. return;
  197. }
  198. this.startupComplete_ = true;
  199. } else {
  200. // Check if we've left the switch interval.
  201. let now = Date.now();
  202. let delta = now - this.lastTimeChosenMs_;
  203. if (delta < this.config_.switchInterval * 1000) {
  204. shaka.log.v2('Still within switch interval...');
  205. return;
  206. }
  207. }
  208. let chosenVariant = this.chooseVariant();
  209. let bandwidthEstimate = this.bandwidthEstimator_.getBandwidthEstimate(
  210. this.config_.defaultBandwidthEstimate);
  211. let currentBandwidthKbps = Math.round(bandwidthEstimate / 1000.0);
  212. shaka.log.debug(
  213. 'Calling switch_(), bandwidth=' + currentBandwidthKbps + ' kbps');
  214. // If any of these chosen streams are already chosen, Player will filter them
  215. // out before passing the choices on to StreamingEngine.
  216. this.switch_(chosenVariant);
  217. };
  218. /**
  219. * @param {?shakaExtern.Restrictions} restrictions
  220. * @param {!Array.<shakaExtern.Variant>} variants
  221. * @return {!Array.<shakaExtern.Variant>} variants filtered according to
  222. * |restrictions| and sorted in ascending order of bandwidth.
  223. * @private
  224. */
  225. shaka.abr.SimpleAbrManager.filterAndSortVariants_ = function(
  226. restrictions, variants) {
  227. if (restrictions) {
  228. variants = variants.filter((variant) => {
  229. // This was already checked in another scope, but the compiler doesn't
  230. // seem to understand that.
  231. goog.asserts.assert(restrictions, 'Restrictions should exist!');
  232. return shaka.util.StreamUtils.meetsRestrictions(
  233. variant, restrictions,
  234. /* maxHwRes */ {width: Infinity, height: Infinity});
  235. });
  236. }
  237. return variants.sort((v1, v2) => {
  238. return v1.bandwidth - v2.bandwidth;
  239. });
  240. };