Source: lib/media/time_ranges_utils.js

  1. /*! @license
  2. * Shaka Player
  3. * Copyright 2016 Google LLC
  4. * SPDX-License-Identifier: Apache-2.0
  5. */
  6. goog.provide('shaka.media.TimeRangesUtils');
  7. /**
  8. * @summary A set of utility functions for dealing with TimeRanges objects.
  9. */
  10. shaka.media.TimeRangesUtils = class {
  11. /**
  12. * Returns whether the buffer is small enough to be ignored.
  13. *
  14. * @param {TimeRanges} b
  15. * @return {boolean}
  16. * @private
  17. */
  18. static isBufferNegligible_(b) {
  19. // Workaround Safari bug: https://bit.ly/2trx6O8
  20. // Firefox may leave <1e-4s of data in buffer after clearing all content
  21. return b.length == 1 && b.end(0) - b.start(0) < 1e-4;
  22. }
  23. /**
  24. * Gets the first timestamp in the buffer.
  25. *
  26. * @param {TimeRanges} b
  27. * @return {?number} The first buffered timestamp, in seconds, if |buffered|
  28. * is non-empty; otherwise, return null.
  29. */
  30. static bufferStart(b) {
  31. if (!b) {
  32. return null;
  33. }
  34. if (shaka.media.TimeRangesUtils.isBufferNegligible_(b)) {
  35. return null;
  36. }
  37. // Workaround Edge bug: https://bit.ly/2JYLPeB
  38. if (b.length == 1 && b.start(0) < 0) {
  39. return 0;
  40. }
  41. return b.length ? b.start(0) : null;
  42. }
  43. /**
  44. * Gets the last timestamp in the buffer.
  45. *
  46. * @param {TimeRanges} b
  47. * @return {?number} The last buffered timestamp, in seconds, if |buffered|
  48. * is non-empty; otherwise, return null.
  49. */
  50. static bufferEnd(b) {
  51. if (!b) {
  52. return null;
  53. }
  54. if (shaka.media.TimeRangesUtils.isBufferNegligible_(b)) {
  55. return null;
  56. }
  57. return b.length ? b.end(b.length - 1) : null;
  58. }
  59. /**
  60. * Determines if the given time is inside a buffered range.
  61. *
  62. * @param {TimeRanges} b
  63. * @param {number} time Playhead time
  64. * @return {boolean}
  65. */
  66. static isBuffered(b, time) {
  67. if (!b || !b.length) {
  68. return false;
  69. }
  70. if (shaka.media.TimeRangesUtils.isBufferNegligible_(b)) {
  71. return false;
  72. }
  73. if (time > b.end(b.length - 1)) {
  74. return false;
  75. }
  76. return time >= b.start(0);
  77. }
  78. /**
  79. * Computes how far ahead of the given timestamp is buffered. To provide
  80. * smooth playback while jumping gaps, we don't include the gaps when
  81. * calculating this.
  82. * This only includes the amount of content that is buffered.
  83. *
  84. * @param {TimeRanges} b
  85. * @param {number} time
  86. * @return {number} The number of seconds buffered, in seconds, ahead of the
  87. * given time.
  88. */
  89. static bufferedAheadOf(b, time) {
  90. if (!b || !b.length) {
  91. return 0;
  92. }
  93. if (shaka.media.TimeRangesUtils.isBufferNegligible_(b)) {
  94. return 0;
  95. }
  96. // We calculate the buffered amount by ONLY accounting for the content
  97. // buffered (i.e. we ignore the times of the gaps). We also buffer through
  98. // all gaps.
  99. // Therefore, we start at the end and add up all buffers until |time|.
  100. let result = 0;
  101. for (const {start, end} of shaka.media.TimeRangesUtils.getBufferedInfo(b)) {
  102. if (end > time) {
  103. result += end - Math.max(start, time);
  104. }
  105. }
  106. return result;
  107. }
  108. /**
  109. * Determines if the given time is inside a gap between buffered ranges. If
  110. * it is, this returns the index of the buffer that is *ahead* of the gap.
  111. *
  112. * @param {TimeRanges} b
  113. * @param {number} time
  114. * @param {number} threshold
  115. * @return {?number} The index of the buffer after the gap, or null if not in
  116. * a gap.
  117. */
  118. static getGapIndex(b, time, threshold) {
  119. const TimeRangesUtils = shaka.media.TimeRangesUtils;
  120. if (!b || !b.length) {
  121. return null;
  122. }
  123. if (shaka.media.TimeRangesUtils.isBufferNegligible_(b)) {
  124. return null;
  125. }
  126. const idx = TimeRangesUtils.getBufferedInfo(b).findIndex((item, i, arr) => {
  127. return item.start > time &&
  128. (i == 0 || arr[i - 1].end - time <= threshold);
  129. });
  130. return idx >= 0 ? idx : null;
  131. }
  132. /**
  133. * @param {TimeRanges} b
  134. * @return {!Array<shaka.extern.BufferedRange>}
  135. */
  136. static getBufferedInfo(b) {
  137. if (!b) {
  138. return [];
  139. }
  140. const ret = [];
  141. for (let i = 0; i < b.length; i++) {
  142. ret.push({start: b.start(i), end: b.end(i)});
  143. }
  144. return ret;
  145. }
  146. /**
  147. * This operation can be potentially EXPENSIVE and should only be done in
  148. * debug builds for debugging purposes.
  149. *
  150. * @param {TimeRanges} oldRanges
  151. * @param {TimeRanges} newRanges
  152. * @return {?shaka.extern.BufferedRange} The last added range,
  153. * chronologically by presentation time.
  154. */
  155. static computeAddedRange(oldRanges, newRanges) {
  156. const TimeRangesUtils = shaka.media.TimeRangesUtils;
  157. if (!oldRanges || !oldRanges.length) {
  158. return null;
  159. }
  160. if (!newRanges || !newRanges.length) {
  161. return TimeRangesUtils.getBufferedInfo(newRanges).pop();
  162. }
  163. const newRangesReversed =
  164. TimeRangesUtils.getBufferedInfo(newRanges).reverse();
  165. const oldRangesReversed =
  166. TimeRangesUtils.getBufferedInfo(oldRanges).reverse();
  167. for (const newRange of newRangesReversed) {
  168. let foundOverlap = false;
  169. for (const oldRange of oldRangesReversed) {
  170. if (oldRange.end >= newRange.start && oldRange.end <= newRange.end) {
  171. foundOverlap = true;
  172. // If the new range goes beyond the corresponding old one, the
  173. // difference is newly-added.
  174. if (newRange.end > oldRange.end) {
  175. return {start: oldRange.end, end: newRange.end};
  176. }
  177. }
  178. }
  179. if (!foundOverlap) {
  180. return newRange;
  181. }
  182. }
  183. return null;
  184. }
  185. };