Source: lib/media/region_timeline.js

  1. /*! @license
  2. * Shaka Player
  3. * Copyright 2016 Google LLC
  4. * SPDX-License-Identifier: Apache-2.0
  5. */
  6. goog.provide('shaka.media.RegionTimeline');
  7. goog.require('shaka.util.FakeEvent');
  8. goog.require('shaka.util.FakeEventTarget');
  9. goog.require('shaka.util.IReleasable');
  10. goog.require('shaka.util.Timer');
  11. /**
  12. * The region timeline is a set of unique timeline region info entries. When
  13. * a new entry is added, the 'regionadd' event will be fired. When an entry is
  14. * deleted, the 'regionremove' event will be fired.
  15. *
  16. * @implements {shaka.util.IReleasable}
  17. * @template T
  18. * @final
  19. */
  20. shaka.media.RegionTimeline = class extends shaka.util.FakeEventTarget {
  21. /**
  22. * @param {!function():{start: number, end: number}} getSeekRange
  23. */
  24. constructor(getSeekRange) {
  25. super();
  26. /** @private {!Map<string, T>} */
  27. this.regions_ = new Map();
  28. /** @private {!function():{start: number, end: number}} */
  29. this.getSeekRange_ = getSeekRange;
  30. /**
  31. * Make sure all of the regions we're tracking are within the
  32. * seek range or further in the future. We don't want to store
  33. * regions that fall before the start of the seek range.
  34. *
  35. * @private {shaka.util.Timer}
  36. */
  37. this.filterTimer_ = null;
  38. }
  39. /** @override */
  40. release() {
  41. this.regions_.clear();
  42. this.releaseTimer_();
  43. super.release();
  44. }
  45. /**
  46. * @param {T} region
  47. */
  48. addRegion(region) {
  49. const key = this.generateKey_(region);
  50. // Make sure we don't add duplicate regions. We keep track of this here
  51. // instead of making the parser track it.
  52. if (!this.regions_.has(key)) {
  53. this.regions_.set(key, region);
  54. const event = new shaka.util.FakeEvent('regionadd', new Map([
  55. ['region', region],
  56. ]));
  57. this.dispatchEvent(event);
  58. this.setupTimer_();
  59. }
  60. }
  61. /**
  62. * @private
  63. */
  64. filterBySeekRange_() {
  65. const seekRange = this.getSeekRange_();
  66. for (const [key, region] of this.regions_) {
  67. // Only consider the seek range start here.
  68. // Future regions might become relevant eventually,
  69. // but regions that are in the past and can't ever be
  70. // seeked to will never come up again, and there's no
  71. // reason to store or process them.
  72. if (region.endTime < seekRange.start) {
  73. this.regions_.delete(key);
  74. const event = new shaka.util.FakeEvent('regionremove', new Map([
  75. ['region', region],
  76. ]));
  77. this.dispatchEvent(event);
  78. }
  79. }
  80. if (!this.regions_.size) {
  81. this.releaseTimer_();
  82. }
  83. }
  84. /** @private */
  85. setupTimer_() {
  86. if (this.filterTimer_) {
  87. return;
  88. }
  89. this.filterTimer_ = new shaka.util.Timer(() => {
  90. this.filterBySeekRange_();
  91. }).tickEvery(
  92. /* seconds= */ shaka.media.RegionTimeline.REGION_FILTER_INTERVAL);
  93. }
  94. /** @private */
  95. releaseTimer_() {
  96. if (this.filterTimer_) {
  97. this.filterTimer_.stop();
  98. this.filterTimer_ = null;
  99. }
  100. }
  101. /**
  102. * Get an iterable for all the regions in the timeline. This will allow
  103. * others to see what regions are in the timeline while not being able to
  104. * change the collection.
  105. *
  106. * @return {!Iterable<T>}
  107. */
  108. regions() {
  109. return this.regions_.values();
  110. }
  111. /**
  112. * @param {T} region
  113. * @return {string}
  114. * @private
  115. */
  116. generateKey_(region) {
  117. return `${region.schemeIdUri}_${region.id}_` +
  118. `${region.startTime.toFixed(1)}_${region.endTime.toFixed(1)}`;
  119. }
  120. };
  121. /** @const {number} */
  122. shaka.media.RegionTimeline.REGION_FILTER_INTERVAL = 2; // in seconds