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