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