Home Reference Source

src/utils/texttrack-utils.ts

  1. import { logger } from './logger';
  2.  
  3. export function sendAddTrackEvent(track: TextTrack, videoEl: HTMLMediaElement) {
  4. let event: Event;
  5. try {
  6. event = new Event('addtrack');
  7. } catch (err) {
  8. // for IE11
  9. event = document.createEvent('Event');
  10. event.initEvent('addtrack', false, false);
  11. }
  12. (event as any).track = track;
  13. videoEl.dispatchEvent(event);
  14. }
  15.  
  16. export function addCueToTrack(track: TextTrack, cue: VTTCue) {
  17. // Sometimes there are cue overlaps on segmented vtts so the same
  18. // cue can appear more than once in different vtt files.
  19. // This avoid showing duplicated cues with same timecode and text.
  20. const mode = track.mode;
  21. if (mode === 'disabled') {
  22. track.mode = 'hidden';
  23. }
  24. if (track.cues && !track.cues.getCueById(cue.id)) {
  25. try {
  26. track.addCue(cue);
  27. if (!track.cues.getCueById(cue.id)) {
  28. throw new Error(`addCue is failed for: ${cue}`);
  29. }
  30. } catch (err) {
  31. logger.debug(`[texttrack-utils]: ${err}`);
  32. const textTrackCue = new (self.TextTrackCue as any)(
  33. cue.startTime,
  34. cue.endTime,
  35. cue.text
  36. );
  37. textTrackCue.id = cue.id;
  38. track.addCue(textTrackCue);
  39. }
  40. }
  41. if (mode === 'disabled') {
  42. track.mode = mode;
  43. }
  44. }
  45.  
  46. export function clearCurrentCues(track: TextTrack) {
  47. // When track.mode is disabled, track.cues will be null.
  48. // To guarantee the removal of cues, we need to temporarily
  49. // change the mode to hidden
  50. const mode = track.mode;
  51. if (mode === 'disabled') {
  52. track.mode = 'hidden';
  53. }
  54. if (track.cues) {
  55. for (let i = track.cues.length; i--; ) {
  56. track.removeCue(track.cues[i]);
  57. }
  58. }
  59. if (mode === 'disabled') {
  60. track.mode = mode;
  61. }
  62. }
  63.  
  64. export function removeCuesInRange(
  65. track: TextTrack,
  66. start: number,
  67. end: number,
  68. predicate?: (cue: TextTrackCue) => boolean
  69. ) {
  70. const mode = track.mode;
  71. if (mode === 'disabled') {
  72. track.mode = 'hidden';
  73. }
  74.  
  75. if (track.cues && track.cues.length > 0) {
  76. const cues = getCuesInRange(track.cues, start, end);
  77. for (let i = 0; i < cues.length; i++) {
  78. if (!predicate || predicate(cues[i])) {
  79. track.removeCue(cues[i]);
  80. }
  81. }
  82. }
  83. if (mode === 'disabled') {
  84. track.mode = mode;
  85. }
  86. }
  87.  
  88. // Find first cue starting after given time.
  89. // Modified version of binary search O(log(n)).
  90. function getFirstCueIndexAfterTime(
  91. cues: TextTrackCueList | TextTrackCue[],
  92. time: number
  93. ): number {
  94. // If first cue starts after time, start there
  95. if (time < cues[0].startTime) {
  96. return 0;
  97. }
  98. // If the last cue ends before time there is no overlap
  99. const len = cues.length - 1;
  100. if (time > cues[len].endTime) {
  101. return -1;
  102. }
  103.  
  104. let left = 0;
  105. let right = len;
  106.  
  107. while (left <= right) {
  108. const mid = Math.floor((right + left) / 2);
  109.  
  110. if (time < cues[mid].startTime) {
  111. right = mid - 1;
  112. } else if (time > cues[mid].startTime && left < len) {
  113. left = mid + 1;
  114. } else {
  115. // If it's not lower or higher, it must be equal.
  116. return mid;
  117. }
  118. }
  119. // At this point, left and right have swapped.
  120. // No direct match was found, left or right element must be the closest. Check which one has the smallest diff.
  121. return cues[left].startTime - time < time - cues[right].startTime
  122. ? left
  123. : right;
  124. }
  125.  
  126. export function getCuesInRange(
  127. cues: TextTrackCueList | TextTrackCue[],
  128. start: number,
  129. end: number
  130. ): TextTrackCue[] {
  131. const cuesFound: TextTrackCue[] = [];
  132. const firstCueInRange = getFirstCueIndexAfterTime(cues, start);
  133. if (firstCueInRange > -1) {
  134. for (let i = firstCueInRange, len = cues.length; i < len; i++) {
  135. const cue = cues[i];
  136. if (cue.startTime >= start && cue.endTime <= end) {
  137. cuesFound.push(cue);
  138. } else if (cue.startTime > end) {
  139. return cuesFound;
  140. }
  141. }
  142. }
  143. return cuesFound;
  144. }