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