Home Reference Source

src/utils/buffer-helper.ts

  1. /**
  2. * @module BufferHelper
  3. *
  4. * Providing methods dealing with buffer length retrieval for example.
  5. *
  6. * In general, a helper around HTML5 MediaElement TimeRanges gathered from `buffered` property.
  7. *
  8. * Also @see https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/buffered
  9. */
  10.  
  11. type BufferTimeRange = {
  12. start: number
  13. end: number
  14. };
  15.  
  16. type Bufferable = {
  17. buffered: TimeRanges
  18. };
  19.  
  20. export class BufferHelper {
  21. /**
  22. * Return true if `media`'s buffered include `position`
  23. * @param {Bufferable} media
  24. * @param {number} position
  25. * @returns {boolean}
  26. */
  27. static isBuffered (media: Bufferable, position: number): boolean {
  28. try {
  29. if (media) {
  30. let buffered = media.buffered;
  31. for (let i = 0; i < buffered.length; i++) {
  32. if (position >= buffered.start(i) && position <= buffered.end(i)) {
  33. return true;
  34. }
  35. }
  36. }
  37. } catch (error) {
  38. // this is to catch
  39. // InvalidStateError: Failed to read the 'buffered' property from 'SourceBuffer':
  40. // This SourceBuffer has been removed from the parent media source
  41. }
  42. return false;
  43. }
  44.  
  45. static bufferInfo (
  46. media: Bufferable,
  47. pos: number,
  48. maxHoleDuration: number
  49. ): {
  50. len: number,
  51. start: number,
  52. end: number,
  53. nextStart?: number,
  54. } {
  55. try {
  56. if (media) {
  57. let vbuffered = media.buffered;
  58. let buffered: BufferTimeRange[] = [];
  59. let i: number;
  60. for (i = 0; i < vbuffered.length; i++) {
  61. buffered.push({ start: vbuffered.start(i), end: vbuffered.end(i) });
  62. }
  63.  
  64. return this.bufferedInfo(buffered, pos, maxHoleDuration);
  65. }
  66. } catch (error) {
  67. // this is to catch
  68. // InvalidStateError: Failed to read the 'buffered' property from 'SourceBuffer':
  69. // This SourceBuffer has been removed from the parent media source
  70. }
  71. return { len: 0, start: pos, end: pos, nextStart: undefined };
  72. }
  73.  
  74. static bufferedInfo (
  75. buffered: BufferTimeRange[],
  76. pos: number,
  77. maxHoleDuration: number
  78. ): {
  79. len: number,
  80. start: number,
  81. end: number,
  82. nextStart?: number,
  83. } {
  84. // sort on buffer.start/smaller end (IE does not always return sorted buffered range)
  85. buffered.sort(function (a, b) {
  86. let diff = a.start - b.start;
  87. if (diff) {
  88. return diff;
  89. } else {
  90. return b.end - a.end;
  91. }
  92. });
  93.  
  94. let buffered2: BufferTimeRange[] = [];
  95. if (maxHoleDuration) {
  96. // there might be some small holes between buffer time range
  97. // consider that holes smaller than maxHoleDuration are irrelevant and build another
  98. // buffer time range representations that discards those holes
  99. for (let i = 0; i < buffered.length; i++) {
  100. let buf2len = buffered2.length;
  101. if (buf2len) {
  102. let buf2end = buffered2[buf2len - 1].end;
  103. // if small hole (value between 0 or maxHoleDuration ) or overlapping (negative)
  104. if ((buffered[i].start - buf2end) < maxHoleDuration) {
  105. // merge overlapping time ranges
  106. // update lastRange.end only if smaller than item.end
  107. // e.g. [ 1, 15] with [ 2,8] => [ 1,15] (no need to modify lastRange.end)
  108. // whereas [ 1, 8] with [ 2,15] => [ 1,15] ( lastRange should switch from [1,8] to [1,15])
  109. if (buffered[i].end > buf2end) {
  110. buffered2[buf2len - 1].end = buffered[i].end;
  111. }
  112. } else {
  113. // big hole
  114. buffered2.push(buffered[i]);
  115. }
  116. } else {
  117. // first value
  118. buffered2.push(buffered[i]);
  119. }
  120. }
  121. } else {
  122. buffered2 = buffered;
  123. }
  124.  
  125. let bufferLen = 0;
  126.  
  127. // bufferStartNext can possibly be undefined based on the conditional logic below
  128. let bufferStartNext: number | undefined;
  129.  
  130. // bufferStart and bufferEnd are buffer boundaries around current video position
  131. let bufferStart: number = pos;
  132. let bufferEnd: number = pos;
  133. for (let i = 0; i < buffered2.length; i++) {
  134. let start = buffered2[i].start,
  135. end = buffered2[i].end;
  136. // logger.log('buf start/end:' + buffered.start(i) + '/' + buffered.end(i));
  137. if ((pos + maxHoleDuration) >= start && pos < end) {
  138. // play position is inside this buffer TimeRange, retrieve end of buffer position and buffer length
  139. bufferStart = start;
  140. bufferEnd = end;
  141. bufferLen = bufferEnd - pos;
  142. } else if ((pos + maxHoleDuration) < start) {
  143. bufferStartNext = start;
  144. break;
  145. }
  146. }
  147. return { len: bufferLen, start: bufferStart, end: bufferEnd, nextStart: bufferStartNext };
  148. }
  149. }