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.ManifestParserUtils');
  23. goog.require('shaka.util.StreamUtils');
  24. /**
  25. * <p>
  26. * This defines the default ABR manager for the Player. An instance of this
  27. * class is used when no ABR manager is given.
  28. * </p>
  29. * <p>
  30. * The behavior of this class is to take throughput samples using
  31. * segmentDownloaded to estimate the current network bandwidth. Then it will
  32. * use that to choose the streams that best fit the current bandwidth. It will
  33. * always pick the highest bandwidth variant it thinks can be played.
  34. * </p>
  35. * <p>
  36. * After the initial choice (in chooseStreams), this class will call
  37. * switchCallback() when there is a better choice. switchCallback() will not
  38. * be called more than once per
  39. * ({@link shaka.abr.SimpleAbrManager.SWITCH_INTERVAL_MS}).
  40. * </p>
  41. * <p>
  42. * This does not adapt for text streams, it will always select the first one.
  43. * </p>
  44. *
  45. * @constructor
  46. * @struct
  47. * @implements {shakaExtern.AbrManager}
  48. * @export
  49. */
  50. shaka.abr.SimpleAbrManager = function() {
  51. /** @private {?shakaExtern.AbrManager.SwitchCallback} */
  52. this.switch_ = null;
  53. /** @private {boolean} */
  54. this.enabled_ = false;
  55. /** @private {shaka.abr.EwmaBandwidthEstimator} */
  56. this.bandwidthEstimator_ = new shaka.abr.EwmaBandwidthEstimator();
  57. /**
  58. * A filtered list of Variants to choose from.
  59. * @private {!Array.<!shakaExtern.Variant>}
  60. */
  61. this.variants_ = [];
  62. /**
  63. * A filtered list of text streams to choose from.
  64. * @private {!Array.<!shakaExtern.Stream>}
  65. */
  66. this.textStreams_ = [];
  67. /** @private {boolean} */
  68. this.startupComplete_ = false;
  69. /**
  70. * The last wall-clock time, in milliseconds, when Streams were chosen via
  71. * chooseStreams() or switch_().
  72. *
  73. * @private {?number}
  74. */
  75. this.lastTimeChosenMs_ = null;
  76. /** @private {shakaExtern.Restrictions} */
  77. this.restrictions_ = {
  78. minWidth: 0,
  79. maxWidth: Infinity,
  80. minHeight: 0,
  81. maxHeight: Infinity,
  82. minPixels: 0,
  83. maxPixels: Infinity,
  84. minBandwidth: 0,
  85. maxBandwidth: Infinity
  86. };
  87. };
  88. /**
  89. * The minimum amount of time that must pass between switches, in milliseconds.
  90. * This keeps us from changing too often and annoying the user.
  91. *
  92. * @const {number}
  93. */
  94. shaka.abr.SimpleAbrManager.SWITCH_INTERVAL_MS = 8000;
  95. /**
  96. * The fraction of the estimated bandwidth which we should try to use when
  97. * upgrading.
  98. *
  99. * @private
  100. * @const {number}
  101. */
  102. shaka.abr.SimpleAbrManager.BANDWIDTH_UPGRADE_TARGET_ = 0.85;
  103. /**
  104. * The largest fraction of the estimated bandwidth we should use. We should
  105. * downgrade to avoid this.
  106. *
  107. * @private
  108. * @const {number}
  109. */
  110. shaka.abr.SimpleAbrManager.BANDWIDTH_DOWNGRADE_TARGET_ = 0.95;
  111. /**
  112. * @override
  113. * @export
  114. */
  115. shaka.abr.SimpleAbrManager.prototype.stop = function() {
  116. this.switch_ = null;
  117. this.enabled_ = false;
  118. this.variants_ = [];
  119. this.textStreams_ = [];
  120. this.lastTimeChosenMs_ = null;
  121. // Don't reset |startupComplete_|: if we've left the startup interval then we
  122. // can start using bandwidth estimates right away if init() is called again.
  123. };
  124. /**
  125. * @override
  126. * @export
  127. */
  128. shaka.abr.SimpleAbrManager.prototype.init = function(switchCallback) {
  129. this.switch_ = switchCallback;
  130. };
  131. /**
  132. * @override
  133. * @export
  134. */
  135. shaka.abr.SimpleAbrManager.prototype.chooseStreams = function(
  136. mediaTypesToUpdate) {
  137. var ContentType = shaka.util.ManifestParserUtils.ContentType;
  138. // Choose streams for the specific types requested.
  139. var chosen = {};
  140. if (mediaTypesToUpdate.indexOf(ContentType.AUDIO) > -1 ||
  141. mediaTypesToUpdate.indexOf(ContentType.VIDEO) > -1) {
  142. // Choose a new Variant
  143. var variant = this.chooseVariant_(this.variants_);
  144. if (variant && variant.video)
  145. chosen[ContentType.VIDEO] = variant.video;
  146. if (variant && variant.audio)
  147. chosen[ContentType.AUDIO] = variant.audio;
  148. }
  149. if (mediaTypesToUpdate.indexOf(ContentType.TEXT) > -1) {
  150. // We don't adapt text, so just choose stream 0.
  151. chosen[ContentType.TEXT] = this.textStreams_[0];
  152. }
  153. this.lastTimeChosenMs_ = Date.now();
  154. return chosen;
  155. };
  156. /**
  157. * @override
  158. * @export
  159. */
  160. shaka.abr.SimpleAbrManager.prototype.enable = function() {
  161. this.enabled_ = true;
  162. };
  163. /**
  164. * @override
  165. * @export
  166. */
  167. shaka.abr.SimpleAbrManager.prototype.disable = function() {
  168. this.enabled_ = false;
  169. };
  170. /**
  171. * @override
  172. * @export
  173. */
  174. shaka.abr.SimpleAbrManager.prototype.segmentDownloaded = function(
  175. deltaTimeMs, numBytes) {
  176. shaka.log.v2('Segment downloaded:',
  177. 'deltaTimeMs=' + deltaTimeMs,
  178. 'numBytes=' + numBytes);
  179. goog.asserts.assert(deltaTimeMs >= 0, 'expected a non-negative duration');
  180. this.bandwidthEstimator_.sample(deltaTimeMs, numBytes);
  181. if ((this.lastTimeChosenMs_ != null) && this.enabled_)
  182. this.suggestStreams_();
  183. };
  184. /**
  185. * @override
  186. * @export
  187. */
  188. shaka.abr.SimpleAbrManager.prototype.getBandwidthEstimate = function() {
  189. return this.bandwidthEstimator_.getBandwidthEstimate();
  190. };
  191. /**
  192. * @override
  193. * @export
  194. */
  195. shaka.abr.SimpleAbrManager.prototype.setDefaultEstimate = function(estimate) {
  196. this.bandwidthEstimator_.setDefaultEstimate(estimate);
  197. };
  198. /**
  199. * @override
  200. * @export
  201. */
  202. shaka.abr.SimpleAbrManager.prototype.setRestrictions = function(restrictions) {
  203. this.restrictions_ = restrictions;
  204. };
  205. /**
  206. * @override
  207. * @export
  208. */
  209. shaka.abr.SimpleAbrManager.prototype.setVariants = function(variants) {
  210. this.variants_ = variants;
  211. };
  212. /**
  213. * @override
  214. * @export
  215. */
  216. shaka.abr.SimpleAbrManager.prototype.setTextStreams = function(streams) {
  217. this.textStreams_ = streams;
  218. };
  219. /**
  220. * Calls switch_() with which Streams to switch to.
  221. *
  222. * @private
  223. */
  224. shaka.abr.SimpleAbrManager.prototype.suggestStreams_ = function() {
  225. shaka.log.v2('Suggesting Streams...');
  226. goog.asserts.assert(this.lastTimeChosenMs_ != null,
  227. 'lastTimeChosenMs_ should not be null');
  228. var ContentType = shaka.util.ManifestParserUtils.ContentType;
  229. if (!this.startupComplete_) {
  230. // Check if we've got enough data yet.
  231. if (!this.bandwidthEstimator_.hasGoodEstimate()) {
  232. shaka.log.v2('Still waiting for a good estimate...');
  233. return;
  234. }
  235. this.startupComplete_ = true;
  236. } else {
  237. // Check if we've left the switch interval.
  238. var now = Date.now();
  239. var delta = now - this.lastTimeChosenMs_;
  240. if (delta < shaka.abr.SimpleAbrManager.SWITCH_INTERVAL_MS) {
  241. shaka.log.v2('Still within switch interval...');
  242. return;
  243. }
  244. }
  245. var chosen = this.chooseStreams([ContentType.AUDIO, ContentType.VIDEO]);
  246. var currentBandwidthKbps =
  247. Math.round(this.bandwidthEstimator_.getBandwidthEstimate() / 1000.0);
  248. shaka.log.debug(
  249. 'Calling switch_(), bandwidth=' + currentBandwidthKbps + ' kbps');
  250. // If any of these chosen streams are already chosen, Player will filter them
  251. // out before passing the choices on to StreamingEngine.
  252. this.switch_(chosen);
  253. };
  254. /**
  255. * Chooses a Variant with an optimal bandwidth.
  256. *
  257. * @param {!Array.<shakaExtern.Variant>} variants
  258. * @return {shakaExtern.Variant}
  259. * @private
  260. */
  261. shaka.abr.SimpleAbrManager.prototype.chooseVariant_ = function(variants) {
  262. // Alias.
  263. var SimpleAbrManager = shaka.abr.SimpleAbrManager;
  264. // Get sorted Streams.
  265. var sortedVariants = SimpleAbrManager.filterAndSortVariants_(
  266. this.restrictions_, variants);
  267. var currentBandwidth = this.bandwidthEstimator_.getBandwidthEstimate();
  268. if (variants.length && !sortedVariants.length) {
  269. throw new shaka.util.Error(
  270. shaka.util.Error.Severity.CRITICAL,
  271. shaka.util.Error.Category.MANIFEST,
  272. shaka.util.Error.Code.RESTRICTIONS_CANNOT_BE_MET);
  273. }
  274. // Start by assuming that we will use the first Stream.
  275. var chosen = sortedVariants[0];
  276. for (var i = 0; i < sortedVariants.length; ++i) {
  277. var variant = sortedVariants[i];
  278. var nextVariant = sortedVariants[i + 1] || {bandwidth: Infinity};
  279. var minBandwidth = variant.bandwidth /
  280. SimpleAbrManager.BANDWIDTH_DOWNGRADE_TARGET_;
  281. var maxBandwidth = nextVariant.bandwidth /
  282. SimpleAbrManager.BANDWIDTH_UPGRADE_TARGET_;
  283. shaka.log.v2('Bandwidth ranges:',
  284. (variant.bandwidth / 1e6).toFixed(3),
  285. (minBandwidth / 1e6).toFixed(3),
  286. (maxBandwidth / 1e6).toFixed(3));
  287. if (currentBandwidth >= minBandwidth && currentBandwidth <= maxBandwidth)
  288. chosen = variant;
  289. }
  290. return chosen;
  291. };
  292. /**
  293. * @param {shakaExtern.Restrictions} restrictions
  294. * @param {!Array.<shakaExtern.Variant>} variants
  295. * @return {!Array.<shakaExtern.Variant>} variants filtered according to
  296. * |restrictions| and sorted in ascending order of bandwidth.
  297. * @private
  298. */
  299. shaka.abr.SimpleAbrManager.filterAndSortVariants_ = function(
  300. restrictions, variants) {
  301. return variants
  302. .filter(function(variant) {
  303. return shaka.util.StreamUtils.meetsRestrictions(
  304. variant, restrictions,
  305. /* maxHwRes */ {width: Infinity, height: Infinity});
  306. })
  307. .sort(function(v1, v2) {
  308. return v1.bandwidth - v2.bandwidth;
  309. });
  310. };