Source: lib/media/playhead_observer.js

  1. /*! @license
  2. * Shaka Player
  3. * Copyright 2016 Google LLC
  4. * SPDX-License-Identifier: Apache-2.0
  5. */
  6. goog.provide('shaka.media.IPlayheadObserver');
  7. goog.provide('shaka.media.PlayheadObserverManager');
  8. goog.require('shaka.util.EventManager');
  9. goog.require('shaka.util.IReleasable');
  10. goog.require('shaka.util.Timer');
  11. /**
  12. * A playhead observer is a system that watches for meaningful changes in state
  13. * that are dependent on playhead information. The observer is responsible for
  14. * managing its own listeners.
  15. *
  16. * @extends {shaka.util.IReleasable}
  17. * @interface
  18. */
  19. shaka.media.IPlayheadObserver = class {
  20. /**
  21. * Check again (using an update playhead summary) if an event should be fired.
  22. * If an event should be fired, fire it.
  23. *
  24. * @param {number} positionInSeconds
  25. * @param {boolean} wasSeeking
  26. */
  27. poll(positionInSeconds, wasSeeking) {}
  28. };
  29. /**
  30. * The playhead observer manager is responsible for owning playhead observer
  31. * instances and polling them when needed. Destroying the manager will destroy
  32. * all observers managed by the manager.
  33. *
  34. * @implements {shaka.util.IReleasable}
  35. * @final
  36. */
  37. shaka.media.PlayheadObserverManager = class {
  38. /**
  39. * @param {!HTMLMediaElement} mediaElement
  40. */
  41. constructor(mediaElement) {
  42. /** @private {HTMLMediaElement} */
  43. this.mediaElement_ = mediaElement;
  44. /** @private {shaka.util.EventManager} */
  45. this.eventManager_ = new shaka.util.EventManager();
  46. /**
  47. * The set of all observers that this manager is responsible for updating.
  48. * We are using a set to ensure that we don't double update an observer if
  49. * it is accidentally added twice.
  50. *
  51. * @private {!Set<shaka.media.IPlayheadObserver>}
  52. */
  53. this.observers_ = new Set();
  54. /**
  55. * To fire events semi-accurately, poll the observers 4 times a second. This
  56. * should be frequent enough to trigger an event close enough to its actual
  57. * occurrence without the user noticing a delay.
  58. *
  59. * @private {shaka.util.Timer}
  60. */
  61. this.pollingLoop_ = new shaka.util.Timer(() => {
  62. this.pollAllObservers_(/* seeking= */ false);
  63. }).tickNow();
  64. if (!mediaElement.paused) {
  65. this.pollingLoop_.tickEvery(/* seconds= */ 0.25);
  66. }
  67. this.eventManager_.listen(mediaElement, 'playing', () => {
  68. this.pollingLoop_.tickNow().tickEvery(/* seconds= */ 0.25);
  69. });
  70. this.eventManager_.listen(mediaElement, 'pause', () => {
  71. this.pollingLoop_.stop();
  72. });
  73. }
  74. /** @override */
  75. release() {
  76. if (this.eventManager_) {
  77. this.eventManager_.release();
  78. this.eventManager_ = null;
  79. }
  80. // We need to stop the loop or else we may try to use a released resource.
  81. this.pollingLoop_.stop();
  82. for (const observer of this.observers_) {
  83. observer.release();
  84. }
  85. this.observers_.clear();
  86. }
  87. /**
  88. * Have the playhead observer manager manage a new observer. This will ensure
  89. * that observers are only tracked once within the manager. After this call,
  90. * the manager will be responsible for the life cycle of |observer|.
  91. *
  92. * @param {!shaka.media.IPlayheadObserver} observer
  93. */
  94. manage(observer) {
  95. this.observers_.add(observer);
  96. }
  97. /**
  98. * Notify all the observers that we just seeked.
  99. */
  100. notifyOfSeek() {
  101. this.pollAllObservers_(/* seeking= */ true);
  102. }
  103. /**
  104. * @param {boolean} seeking
  105. * @private
  106. */
  107. pollAllObservers_(seeking) {
  108. const currentTime = this.mediaElement_.currentTime;
  109. for (const observer of this.observers_) {
  110. observer.poll(currentTime, seeking);
  111. }
  112. }
  113. };