Source: lib/media/play_rate_controller.js

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