Source: lib/offline/stream_bandwidth_estimator.js

  1. /*! @license
  2. * Shaka Player
  3. * Copyright 2016 Google LLC
  4. * SPDX-License-Identifier: Apache-2.0
  5. */
  6. goog.provide('shaka.offline.StreamBandwidthEstimator');
  7. goog.require('shaka.log');
  8. goog.requireType('shaka.media.SegmentReference');
  9. /**
  10. * A utility class to help estimate the size of streams based on stream and
  11. * variant bandwidths. This class's main purpose is to isolate the logic in
  12. * creating non-zero bandwidth estimates for all streams so that each stream
  13. * will have some influence over the progress of the download.
  14. */
  15. shaka.offline.StreamBandwidthEstimator = class {
  16. /** */
  17. constructor() {
  18. /** @private {!Map<number, number>} */
  19. this.estimateByStreamId_ = new Map();
  20. }
  21. /**
  22. * Add a new variant to the estimator. This will update the estimates for all
  23. * streams in the variant.
  24. *
  25. * @param {shaka.extern.Variant} variant
  26. */
  27. addVariant(variant) {
  28. // Three cases:
  29. // 1 - Only Audio
  30. // 2 - Only Video
  31. // 3 - Audio and Video
  32. const audio = variant.audio;
  33. const video = variant.video;
  34. // Case 1
  35. if (audio && !video) {
  36. const audioBitRate = audio.bandwidth || variant.bandwidth;
  37. this.setBitrate_(audio.id, audioBitRate);
  38. }
  39. // Case 2
  40. if (!audio && video) {
  41. const videoBitRate = video.bandwidth || variant.bandwidth;
  42. this.setBitrate_(video.id, videoBitRate);
  43. }
  44. // Case 3
  45. if (audio && video) {
  46. // Get the audio's bandwidth. If it is missing, default to our default
  47. // audio bandwidth.
  48. const audioBitRate =
  49. audio.bandwidth ||
  50. shaka.offline.StreamBandwidthEstimator.DEFAULT_AUDIO_BITRATE_;
  51. // Get the video's bandwidth. If it is missing, use the variant bandwidth
  52. // less the audio. If we get a negative bit rate, fall back to our
  53. // default video bandwidth.
  54. let videoBitRate = video.bandwidth || (variant.bandwidth - audioBitRate);
  55. if (videoBitRate <= 0) {
  56. shaka.log.warning(
  57. 'Audio bit rate consumes variants bandwidth. Setting video ' +
  58. 'bandwidth to match variant\'s bandwidth.');
  59. videoBitRate = variant.bandwidth;
  60. }
  61. this.setBitrate_(audio.id, audioBitRate);
  62. this.setBitrate_(video.id, videoBitRate);
  63. }
  64. }
  65. /**
  66. * @param {number} stream
  67. * @param {number} bitRate
  68. * @private
  69. */
  70. setBitrate_(stream, bitRate) {
  71. this.estimateByStreamId_.set(stream, bitRate);
  72. }
  73. /**
  74. * Create an estimate for the text stream.
  75. *
  76. * @param {shaka.extern.Stream} text
  77. */
  78. addText(text) {
  79. this.estimateByStreamId_.set(text.id,
  80. shaka.offline.StreamBandwidthEstimator.DEFAULT_TEXT_BITRATE_);
  81. }
  82. /**
  83. * Create an estimate for the image stream.
  84. *
  85. * @param {shaka.extern.Stream} image
  86. */
  87. addImage(image) {
  88. this.estimateByStreamId_.set(image.id, image.bandwidth ||
  89. shaka.offline.StreamBandwidthEstimator.DEFAULT_IMAGE_BITRATE_);
  90. }
  91. /**
  92. * Get the estimate for a segment that is part of a stream that has already
  93. * added to the estimator.
  94. *
  95. * @param {number} id
  96. * @param {!shaka.media.SegmentReference} segment
  97. * @return {number}
  98. */
  99. getSegmentEstimate(id, segment) {
  100. const duration = segment.endTime - segment.startTime;
  101. return this.getEstimate_(id) * duration;
  102. }
  103. /**
  104. * Get the estimate for an init segment for a stream that has already
  105. * added to the estimator.
  106. *
  107. * @param {number} id
  108. * @return {number}
  109. */
  110. getInitSegmentEstimate(id) {
  111. // Assume that the init segment is worth approximately half a second of
  112. // content.
  113. const duration = 0.5;
  114. return this.getEstimate_(id) * duration;
  115. }
  116. /**
  117. * @param {number} id
  118. * @return {number}
  119. * @private
  120. */
  121. getEstimate_(id) {
  122. let bitRate = this.estimateByStreamId_.get(id);
  123. if (bitRate == null) {
  124. bitRate = 0;
  125. shaka.log.error(
  126. 'Asking for bitrate of stream not given to the estimator');
  127. }
  128. if (bitRate == 0) {
  129. shaka.log.warning(
  130. 'Using bitrate of 0, this stream won\'t affect progress');
  131. }
  132. return bitRate;
  133. }
  134. };
  135. /**
  136. * Since audio bandwidth does not vary much, we are going to use a constant
  137. * approximation for audio bit rate allowing use to more accurately guess at
  138. * the video bitrate.
  139. *
  140. * YouTube's suggested bitrate for stereo audio is 384 kbps so we are going to
  141. * assume that: https://support.google.com/youtube/answer/1722171?hl=en
  142. *
  143. * @const {number}
  144. * @private
  145. */
  146. shaka.offline.StreamBandwidthEstimator.DEFAULT_AUDIO_BITRATE_ = 393216;
  147. /**
  148. * Since we don't normally get the bitrate for text, we still want to create
  149. * some approximation so that it can influence progress. This will use the
  150. * bitrate from "Tears of Steal" to give some kind of data-driven result.
  151. *
  152. * The file size for English subtitles is 4.7 KB. The video is 12:14 long,
  153. * which means that the text's bit rate is around 52 bps.
  154. *
  155. * @const {number}
  156. * @private
  157. */
  158. shaka.offline.StreamBandwidthEstimator.DEFAULT_TEXT_BITRATE_ = 52;
  159. /**
  160. * Since we don't normally get the bitrate for image, we still want to create
  161. * some approximation so that it can influence progress.
  162. *
  163. * The size of the thumbnail usually is 2KB.
  164. *
  165. * @const {number}
  166. * @private
  167. */
  168. shaka.offline.StreamBandwidthEstimator.DEFAULT_IMAGE_BITRATE_ = 2048;