Source: lib/media/play_rate_controller.js

  1. /*! @license
  2. * Shaka Player
  3. * Copyright 2016 Google LLC
  4. * SPDX-License-Identifier: Apache-2.0
  5. */
  6. goog.provide('shaka.media.PlayRateController');
  7. goog.require('goog.asserts');
  8. goog.require('shaka.log');
  9. goog.require('shaka.util.IReleasable');
  10. goog.require('shaka.util.Timer');
  11. /**
  12. * The play rate controller controls the playback rate on the media element.
  13. * This provides some missing functionality (e.g. negative playback rate). If
  14. * the playback rate on the media element can change outside of the controller,
  15. * the playback controller will need to be updated to stay in-sync.
  16. *
  17. * TODO: Try not to manage buffering above the browser with playbackRate=0.
  18. *
  19. * @implements {shaka.util.IReleasable}
  20. * @final
  21. */
  22. shaka.media.PlayRateController = class {
  23. /**
  24. * @param {shaka.media.PlayRateController.Harness} harness
  25. */
  26. constructor(harness) {
  27. /** @private {?shaka.media.PlayRateController.Harness} */
  28. this.harness_ = harness;
  29. /** @private {boolean} */
  30. this.isBuffering_ = false;
  31. /** @private {number} */
  32. this.rate_ = this.harness_.getRate();
  33. /** @private {number} */
  34. this.pollRate_ = 0.25;
  35. /** @private {shaka.util.Timer} */
  36. this.timer_ = new shaka.util.Timer(() => {
  37. this.harness_.movePlayhead(this.rate_ * this.pollRate_);
  38. });
  39. }
  40. /** @override */
  41. release() {
  42. if (this.timer_) {
  43. this.timer_.stop();
  44. this.timer_ = null;
  45. }
  46. this.harness_ = null;
  47. }
  48. /**
  49. * Sets the buffering flag, which controls the effective playback rate.
  50. *
  51. * @param {boolean} isBuffering If true, forces playback rate to 0 internally.
  52. */
  53. setBuffering(isBuffering) {
  54. this.isBuffering_ = isBuffering;
  55. this.apply_();
  56. }
  57. /**
  58. * Set the playback rate. This rate will only be used as provided when the
  59. * player is not buffering. You should never set the rate to 0.
  60. *
  61. * @param {number} rate
  62. */
  63. set(rate) {
  64. goog.asserts.assert(rate != 0, 'Should never set rate of 0 explicitly!');
  65. this.rate_ = rate;
  66. this.apply_();
  67. }
  68. /**
  69. * Get the real rate of the playback. This means that if we are using trick
  70. * play, this will report the trick play rate. If playback is occurring as
  71. * normal, this will report 1.
  72. *
  73. * @return {number}
  74. */
  75. getRealRate() {
  76. return this.rate_;
  77. }
  78. /**
  79. * Get the default play rate of the playback.
  80. *
  81. * @return {number}
  82. */
  83. getDefaultRate() {
  84. return this.harness_.getDefaultRate();
  85. }
  86. /**
  87. * Reapply the effects of |this.rate_| and |this.active_| to the media
  88. * element. This will only update the rate via the harness if the desired rate
  89. * has changed.
  90. *
  91. * @private
  92. */
  93. apply_() {
  94. // Always stop the timer. We may not start it again.
  95. this.timer_.stop();
  96. /** @type {number} */
  97. const rate = this.calculateCurrentRate_();
  98. shaka.log.v1('Changing effective playback rate to', rate);
  99. if (rate >= 0) {
  100. try {
  101. this.applyRate_(rate);
  102. return;
  103. } catch (e) {
  104. // Fall through to the next clause.
  105. //
  106. // Fast forward is accomplished through setting video.playbackRate.
  107. // If the play rate value is not supported by the browser (too big),
  108. // the browsers will throw.
  109. // Use this as a cue to fall back to fast forward through repeated
  110. // seeking, which is what we do for rewind as well.
  111. }
  112. }
  113. // When moving backwards or forwards in large steps,
  114. // set the playback rate to 0 so that we can manually
  115. // seek backwards with out fighting the playhead.
  116. this.timer_.tickEvery(this.pollRate_);
  117. this.applyRate_(0);
  118. }
  119. /**
  120. * Calculate the rate that the controller wants the media element to have
  121. * based on the current state of the controller.
  122. *
  123. * @return {number}
  124. * @private
  125. */
  126. calculateCurrentRate_() {
  127. return this.isBuffering_ ? 0 : this.rate_;
  128. }
  129. /**
  130. * If the new rate is different than the media element's playback rate, this
  131. * will change the playback rate. If the rate does not need to change, it will
  132. * not be set. This will avoid unnecessary ratechange events.
  133. *
  134. * @param {number} newRate
  135. * @return {boolean}
  136. * @private
  137. */
  138. applyRate_(newRate) {
  139. const oldRate = this.harness_.getRate();
  140. if (oldRate != newRate) {
  141. this.harness_.setRate(newRate);
  142. }
  143. return oldRate != newRate;
  144. }
  145. };
  146. /**
  147. * @typedef {{
  148. * getRate: function():number,
  149. * getDefaultRate: function():number,
  150. * setRate: function(number),
  151. * movePlayhead: function(number)
  152. * }}
  153. *
  154. * @description
  155. * A layer of abstraction between the controller and what it is controlling.
  156. * In tests this will be implemented with spies. In production this will be
  157. * implemented using a media element.
  158. *
  159. * @property {function():number} getRate
  160. * Get the current playback rate being seen by the user.
  161. *
  162. * @property {function():number} getDefaultRate
  163. * Get the default playback rate that the user should see.
  164. *
  165. * @property {function(number)} setRate
  166. * Set the playback rate that the user should see.
  167. *
  168. * @property {function(number)} movePlayhead
  169. * Move the playhead N seconds. If N is positive, the playhead will move
  170. * forward abs(N) seconds. If N is negative, the playhead will move backwards
  171. * abs(N) seconds.
  172. */
  173. shaka.media.PlayRateController.Harness;