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. This includes
  51. * gaps, meaning that if the playhead is in a gap, it is considered buffered.
  52. * If there is a small gap between the playhead and buffer start, consider it
  53. * as buffered.
  54. *
  55. * @param {TimeRanges} b
  56. * @param {number} time Playhead time
  57. * @param {number=} smallGapLimit Set in configuration
  58. * @return {boolean}
  59. */
  60. static isBuffered(b, time, smallGapLimit = 0) {
  61. if (!b || !b.length) {
  62. return false;
  63. }
  64. // Workaround Safari bug: https://bit.ly/2trx6O8
  65. if (b.length == 1 && b.end(0) - b.start(0) < 1e-6) {
  66. return false;
  67. }
  68. if (time > b.end(b.length - 1)) {
  69. return false;
  70. }
  71. // Push the time forward by the gap limit so that it is more likely to be in
  72. // the range.
  73. return (time + smallGapLimit >= b.start(0));
  74. }
  75. /**
  76. * Computes how far ahead of the given timestamp is buffered. To provide
  77. * smooth playback while jumping gaps, we don't include the gaps when
  78. * calculating this.
  79. * This only includes the amount of content that is buffered.
  80. *
  81. * @param {TimeRanges} b
  82. * @param {number} time
  83. * @return {number} The number of seconds buffered, in seconds, ahead of the
  84. * given time.
  85. */
  86. static bufferedAheadOf(b, time) {
  87. if (!b || !b.length) {
  88. return 0;
  89. }
  90. // Workaround Safari bug: https://bit.ly/2trx6O8
  91. if (b.length == 1 && b.end(0) - b.start(0) < 1e-6) {
  92. return 0;
  93. }
  94. // We calculate the buffered amount by ONLY accounting for the content
  95. // buffered (i.e. we ignore the times of the gaps). We also buffer through
  96. // all gaps.
  97. // Therefore, we start at the end and add up all buffers until |time|.
  98. let result = 0;
  99. for (const {start, end} of shaka.media.TimeRangesUtils.getBufferedInfo(b)) {
  100. if (end > time) {
  101. result += end - Math.max(start, time);
  102. }
  103. }
  104. return result;
  105. }
  106. /**
  107. * Determines if the given time is inside a gap between buffered ranges. If
  108. * it is, this returns the index of the buffer that is *ahead* of the gap.
  109. *
  110. * @param {TimeRanges} b
  111. * @param {number} time
  112. * @param {number} threshold
  113. * @return {?number} The index of the buffer after the gap, or null if not in
  114. * a gap.
  115. */
  116. static getGapIndex(b, time, threshold) {
  117. const TimeRangesUtils = shaka.media.TimeRangesUtils;
  118. if (!b || !b.length) {
  119. return null;
  120. }
  121. // Workaround Safari bug: https://bit.ly/2trx6O8
  122. if (b.length == 1 && b.end(0) - b.start(0) < 1e-6) {
  123. return null;
  124. }
  125. const idx = TimeRangesUtils.getBufferedInfo(b).findIndex((item, i, arr) => {
  126. return item.start > time &&
  127. (i == 0 || arr[i - 1].end - time <= threshold);
  128. });
  129. return idx >= 0 ? idx : null;
  130. }
  131. /**
  132. * @param {TimeRanges} b
  133. * @return {!Array.<shaka.extern.BufferedRange>}
  134. */
  135. static getBufferedInfo(b) {
  136. if (!b) {
  137. return [];
  138. }
  139. const ret = [];
  140. for (let i = 0; i < b.length; i++) {
  141. ret.push({start: b.start(i), end: b.end(i)});
  142. }
  143. return ret;
  144. }
  145. };