@zenvor/hls.js 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (159) hide show
  1. package/LICENSE +28 -0
  2. package/README.md +472 -0
  3. package/dist/hls-demo.js +26995 -0
  4. package/dist/hls-demo.js.map +1 -0
  5. package/dist/hls.d.mts +4204 -0
  6. package/dist/hls.d.ts +4204 -0
  7. package/dist/hls.js +40050 -0
  8. package/dist/hls.js.d.ts +4204 -0
  9. package/dist/hls.js.map +1 -0
  10. package/dist/hls.light.js +27145 -0
  11. package/dist/hls.light.js.map +1 -0
  12. package/dist/hls.light.min.js +2 -0
  13. package/dist/hls.light.min.js.map +1 -0
  14. package/dist/hls.light.mjs +26392 -0
  15. package/dist/hls.light.mjs.map +1 -0
  16. package/dist/hls.min.js +2 -0
  17. package/dist/hls.min.js.map +1 -0
  18. package/dist/hls.mjs +38956 -0
  19. package/dist/hls.mjs.map +1 -0
  20. package/dist/hls.worker.js +2 -0
  21. package/dist/hls.worker.js.map +1 -0
  22. package/package.json +143 -0
  23. package/src/config.ts +794 -0
  24. package/src/controller/abr-controller.ts +1019 -0
  25. package/src/controller/algo-data-controller.ts +794 -0
  26. package/src/controller/audio-stream-controller.ts +1099 -0
  27. package/src/controller/audio-track-controller.ts +454 -0
  28. package/src/controller/base-playlist-controller.ts +438 -0
  29. package/src/controller/base-stream-controller.ts +2526 -0
  30. package/src/controller/buffer-controller.ts +2015 -0
  31. package/src/controller/buffer-operation-queue.ts +159 -0
  32. package/src/controller/cap-level-controller.ts +367 -0
  33. package/src/controller/cmcd-controller.ts +422 -0
  34. package/src/controller/content-steering-controller.ts +622 -0
  35. package/src/controller/eme-controller.ts +1617 -0
  36. package/src/controller/error-controller.ts +627 -0
  37. package/src/controller/fps-controller.ts +146 -0
  38. package/src/controller/fragment-finders.ts +256 -0
  39. package/src/controller/fragment-tracker.ts +567 -0
  40. package/src/controller/gap-controller.ts +719 -0
  41. package/src/controller/id3-track-controller.ts +488 -0
  42. package/src/controller/interstitial-player.ts +302 -0
  43. package/src/controller/interstitials-controller.ts +2895 -0
  44. package/src/controller/interstitials-schedule.ts +698 -0
  45. package/src/controller/latency-controller.ts +294 -0
  46. package/src/controller/level-controller.ts +776 -0
  47. package/src/controller/stream-controller.ts +1597 -0
  48. package/src/controller/subtitle-stream-controller.ts +508 -0
  49. package/src/controller/subtitle-track-controller.ts +617 -0
  50. package/src/controller/timeline-controller.ts +677 -0
  51. package/src/crypt/aes-crypto.ts +36 -0
  52. package/src/crypt/aes-decryptor.ts +339 -0
  53. package/src/crypt/decrypter-aes-mode.ts +4 -0
  54. package/src/crypt/decrypter.ts +225 -0
  55. package/src/crypt/fast-aes-key.ts +39 -0
  56. package/src/define-plugin.d.ts +17 -0
  57. package/src/demux/audio/aacdemuxer.ts +126 -0
  58. package/src/demux/audio/ac3-demuxer.ts +170 -0
  59. package/src/demux/audio/adts.ts +249 -0
  60. package/src/demux/audio/base-audio-demuxer.ts +205 -0
  61. package/src/demux/audio/dolby.ts +21 -0
  62. package/src/demux/audio/mp3demuxer.ts +85 -0
  63. package/src/demux/audio/mpegaudio.ts +177 -0
  64. package/src/demux/chunk-cache.ts +42 -0
  65. package/src/demux/dummy-demuxed-track.ts +13 -0
  66. package/src/demux/inject-worker.ts +75 -0
  67. package/src/demux/mp4demuxer.ts +234 -0
  68. package/src/demux/sample-aes.ts +198 -0
  69. package/src/demux/transmuxer-interface.ts +449 -0
  70. package/src/demux/transmuxer-worker.ts +221 -0
  71. package/src/demux/transmuxer.ts +560 -0
  72. package/src/demux/tsdemuxer.ts +1256 -0
  73. package/src/demux/video/avc-video-parser.ts +401 -0
  74. package/src/demux/video/base-video-parser.ts +198 -0
  75. package/src/demux/video/exp-golomb.ts +153 -0
  76. package/src/demux/video/hevc-video-parser.ts +736 -0
  77. package/src/empty-es.js +5 -0
  78. package/src/empty.js +3 -0
  79. package/src/errors.ts +107 -0
  80. package/src/events.ts +548 -0
  81. package/src/exports-default.ts +3 -0
  82. package/src/exports-named.ts +81 -0
  83. package/src/hls.ts +1613 -0
  84. package/src/is-supported.ts +54 -0
  85. package/src/loader/date-range.ts +207 -0
  86. package/src/loader/fragment-loader.ts +403 -0
  87. package/src/loader/fragment.ts +487 -0
  88. package/src/loader/interstitial-asset-list.ts +162 -0
  89. package/src/loader/interstitial-event.ts +337 -0
  90. package/src/loader/key-loader.ts +439 -0
  91. package/src/loader/level-details.ts +203 -0
  92. package/src/loader/level-key.ts +259 -0
  93. package/src/loader/load-stats.ts +17 -0
  94. package/src/loader/m3u8-parser.ts +1072 -0
  95. package/src/loader/playlist-loader.ts +839 -0
  96. package/src/polyfills/number.ts +15 -0
  97. package/src/remux/aac-helper.ts +81 -0
  98. package/src/remux/mp4-generator.ts +1380 -0
  99. package/src/remux/mp4-remuxer.ts +1261 -0
  100. package/src/remux/passthrough-remuxer.ts +434 -0
  101. package/src/task-loop.ts +130 -0
  102. package/src/types/algo.ts +44 -0
  103. package/src/types/buffer.ts +105 -0
  104. package/src/types/component-api.ts +20 -0
  105. package/src/types/demuxer.ts +208 -0
  106. package/src/types/events.ts +574 -0
  107. package/src/types/fragment-tracker.ts +23 -0
  108. package/src/types/level.ts +268 -0
  109. package/src/types/loader.ts +198 -0
  110. package/src/types/media-playlist.ts +92 -0
  111. package/src/types/network-details.ts +3 -0
  112. package/src/types/remuxer.ts +104 -0
  113. package/src/types/track.ts +12 -0
  114. package/src/types/transmuxer.ts +46 -0
  115. package/src/types/tuples.ts +6 -0
  116. package/src/types/vtt.ts +11 -0
  117. package/src/utils/arrays.ts +22 -0
  118. package/src/utils/attr-list.ts +192 -0
  119. package/src/utils/binary-search.ts +46 -0
  120. package/src/utils/buffer-helper.ts +173 -0
  121. package/src/utils/cea-608-parser.ts +1413 -0
  122. package/src/utils/chunker.ts +41 -0
  123. package/src/utils/codecs.ts +314 -0
  124. package/src/utils/cues.ts +96 -0
  125. package/src/utils/discontinuities.ts +174 -0
  126. package/src/utils/encryption-methods-util.ts +21 -0
  127. package/src/utils/error-helper.ts +95 -0
  128. package/src/utils/event-listener-helper.ts +16 -0
  129. package/src/utils/ewma-bandwidth-estimator.ts +97 -0
  130. package/src/utils/ewma.ts +43 -0
  131. package/src/utils/fetch-loader.ts +331 -0
  132. package/src/utils/global.ts +2 -0
  133. package/src/utils/hash.ts +10 -0
  134. package/src/utils/hdr.ts +67 -0
  135. package/src/utils/hex.ts +32 -0
  136. package/src/utils/imsc1-ttml-parser.ts +261 -0
  137. package/src/utils/keysystem-util.ts +45 -0
  138. package/src/utils/level-helper.ts +629 -0
  139. package/src/utils/logger.ts +120 -0
  140. package/src/utils/media-option-attributes.ts +49 -0
  141. package/src/utils/mediacapabilities-helper.ts +301 -0
  142. package/src/utils/mediakeys-helper.ts +210 -0
  143. package/src/utils/mediasource-helper.ts +37 -0
  144. package/src/utils/mp4-tools.ts +1473 -0
  145. package/src/utils/number.ts +3 -0
  146. package/src/utils/numeric-encoding-utils.ts +26 -0
  147. package/src/utils/output-filter.ts +46 -0
  148. package/src/utils/rendition-helper.ts +505 -0
  149. package/src/utils/safe-json-stringify.ts +22 -0
  150. package/src/utils/texttrack-utils.ts +164 -0
  151. package/src/utils/time-ranges.ts +17 -0
  152. package/src/utils/timescale-conversion.ts +46 -0
  153. package/src/utils/utf8-utils.ts +18 -0
  154. package/src/utils/variable-substitution.ts +105 -0
  155. package/src/utils/vttcue.ts +384 -0
  156. package/src/utils/vttparser.ts +497 -0
  157. package/src/utils/webvtt-parser.ts +166 -0
  158. package/src/utils/xhr-loader.ts +337 -0
  159. package/src/version.ts +1 -0
@@ -0,0 +1,146 @@
1
+ import { Events } from '../events';
2
+ import type StreamController from './stream-controller';
3
+ import type Hls from '../hls';
4
+ import type { ComponentAPI } from '../types/component-api';
5
+ import type { MediaAttachingData } from '../types/events';
6
+
7
+ class FPSController implements ComponentAPI {
8
+ private hls: Hls;
9
+ private isVideoPlaybackQualityAvailable: boolean = false;
10
+ private timer?: number;
11
+ private media: HTMLVideoElement | null = null;
12
+ private lastTime: any;
13
+ private lastDroppedFrames: number = 0;
14
+ private lastDecodedFrames: number = 0;
15
+ // stream controller must be provided as a dependency!
16
+ private streamController!: StreamController;
17
+
18
+ constructor(hls: Hls) {
19
+ this.hls = hls;
20
+
21
+ this.registerListeners();
22
+ }
23
+
24
+ public setStreamController(streamController: StreamController) {
25
+ this.streamController = streamController;
26
+ }
27
+
28
+ protected registerListeners() {
29
+ this.hls.on(Events.MEDIA_ATTACHING, this.onMediaAttaching, this);
30
+ this.hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
31
+ }
32
+
33
+ protected unregisterListeners() {
34
+ this.hls.off(Events.MEDIA_ATTACHING, this.onMediaAttaching, this);
35
+ this.hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
36
+ }
37
+
38
+ destroy() {
39
+ if (this.timer) {
40
+ clearInterval(this.timer);
41
+ }
42
+
43
+ this.unregisterListeners();
44
+ this.isVideoPlaybackQualityAvailable = false;
45
+ this.media = null;
46
+ }
47
+
48
+ protected onMediaAttaching(
49
+ event: Events.MEDIA_ATTACHING,
50
+ data: MediaAttachingData,
51
+ ) {
52
+ const config = this.hls.config;
53
+ if (config.capLevelOnFPSDrop) {
54
+ const media =
55
+ data.media instanceof self.HTMLVideoElement ? data.media : null;
56
+ this.media = media;
57
+ if (media && typeof media.getVideoPlaybackQuality === 'function') {
58
+ this.isVideoPlaybackQualityAvailable = true;
59
+ }
60
+
61
+ self.clearInterval(this.timer);
62
+ this.timer = self.setInterval(
63
+ this.checkFPSInterval.bind(this),
64
+ config.fpsDroppedMonitoringPeriod,
65
+ );
66
+ }
67
+ }
68
+
69
+ private onMediaDetaching() {
70
+ this.media = null;
71
+ }
72
+
73
+ checkFPS(
74
+ video: HTMLVideoElement,
75
+ decodedFrames: number,
76
+ droppedFrames: number,
77
+ ) {
78
+ const currentTime = performance.now();
79
+ if (decodedFrames) {
80
+ if (this.lastTime) {
81
+ const currentPeriod = currentTime - this.lastTime;
82
+ const currentDropped = droppedFrames - this.lastDroppedFrames;
83
+ const currentDecoded = decodedFrames - this.lastDecodedFrames;
84
+ const droppedFPS = (1000 * currentDropped) / currentPeriod;
85
+ const hls = this.hls;
86
+ hls.trigger(Events.FPS_DROP, {
87
+ currentDropped: currentDropped,
88
+ currentDecoded: currentDecoded,
89
+ totalDroppedFrames: droppedFrames,
90
+ });
91
+ if (droppedFPS > 0) {
92
+ // hls.logger.log('checkFPS : droppedFPS/decodedFPS:' + droppedFPS/(1000 * currentDecoded / currentPeriod));
93
+ if (
94
+ currentDropped >
95
+ hls.config.fpsDroppedMonitoringThreshold * currentDecoded
96
+ ) {
97
+ let currentLevel = hls.currentLevel;
98
+ hls.logger.warn(
99
+ 'drop FPS ratio greater than max allowed value for currentLevel: ' +
100
+ currentLevel,
101
+ );
102
+ if (
103
+ currentLevel > 0 &&
104
+ (hls.autoLevelCapping === -1 ||
105
+ hls.autoLevelCapping >= currentLevel)
106
+ ) {
107
+ currentLevel = currentLevel - 1;
108
+ hls.trigger(Events.FPS_DROP_LEVEL_CAPPING, {
109
+ level: currentLevel,
110
+ droppedLevel: hls.currentLevel,
111
+ });
112
+ hls.autoLevelCapping = currentLevel;
113
+ this.streamController.nextLevelSwitch();
114
+ }
115
+ }
116
+ }
117
+ }
118
+ this.lastTime = currentTime;
119
+ this.lastDroppedFrames = droppedFrames;
120
+ this.lastDecodedFrames = decodedFrames;
121
+ }
122
+ }
123
+
124
+ checkFPSInterval() {
125
+ const video = this.media;
126
+ if (video) {
127
+ if (this.isVideoPlaybackQualityAvailable) {
128
+ const videoPlaybackQuality = video.getVideoPlaybackQuality();
129
+ this.checkFPS(
130
+ video,
131
+ videoPlaybackQuality.totalVideoFrames,
132
+ videoPlaybackQuality.droppedVideoFrames,
133
+ );
134
+ } else {
135
+ // HTMLVideoElement doesn't include the webkit types
136
+ this.checkFPS(
137
+ video,
138
+ (video as any).webkitDecodedFrameCount as number,
139
+ (video as any).webkitDroppedFrameCount as number,
140
+ );
141
+ }
142
+ }
143
+ }
144
+ }
145
+
146
+ export default FPSController;
@@ -0,0 +1,256 @@
1
+ import BinarySearch from '../utils/binary-search';
2
+ import type { Fragment, MediaFragment } from '../loader/fragment';
3
+ import type { LevelDetails } from '../loader/level-details';
4
+
5
+ /**
6
+ * Returns first fragment whose endPdt value exceeds the given PDT, or null.
7
+ * @param fragments - The array of candidate fragments
8
+ * @param PDTValue - The PDT value which must be exceeded
9
+ * @param maxFragLookUpTolerance - The amount of time that a fragment's start/end can be within in order to be considered contiguous
10
+ */
11
+ export function findFragmentByPDT(
12
+ fragments: MediaFragment[],
13
+ PDTValue: number | null,
14
+ maxFragLookUpTolerance: number,
15
+ ): MediaFragment | null {
16
+ if (
17
+ PDTValue === null ||
18
+ !Array.isArray(fragments) ||
19
+ !fragments.length ||
20
+ !Number.isFinite(PDTValue)
21
+ ) {
22
+ return null;
23
+ }
24
+
25
+ // if less than start
26
+ const startPDT = fragments[0].programDateTime;
27
+ if (PDTValue < (startPDT || 0)) {
28
+ return null;
29
+ }
30
+
31
+ const endPDT = fragments[fragments.length - 1].endProgramDateTime;
32
+ if (PDTValue >= (endPDT || 0)) {
33
+ return null;
34
+ }
35
+
36
+ for (let seg = 0; seg < fragments.length; ++seg) {
37
+ const frag = fragments[seg];
38
+ if (pdtWithinToleranceTest(PDTValue, maxFragLookUpTolerance, frag)) {
39
+ return frag;
40
+ }
41
+ }
42
+
43
+ return null;
44
+ }
45
+
46
+ /**
47
+ * Finds a fragment based on the SN of the previous fragment; or based on the needs of the current buffer.
48
+ * This method compensates for small buffer gaps by applying a tolerance to the start of any candidate fragment, thus
49
+ * breaking any traps which would cause the same fragment to be continuously selected within a small range.
50
+ * @param fragPrevious - The last frag successfully appended
51
+ * @param fragments - The array of candidate fragments
52
+ * @param bufferEnd - The end of the contiguous buffered range the playhead is currently within
53
+ * @param maxFragLookUpTolerance - The amount of time that a fragment's start/end can be within in order to be considered contiguous
54
+ * @returns a matching fragment or null
55
+ */
56
+ export function findFragmentByPTS(
57
+ fragPrevious: MediaFragment | null,
58
+ fragments: MediaFragment[],
59
+ bufferEnd: number = 0,
60
+ maxFragLookUpTolerance: number = 0,
61
+ nextFragLookupTolerance: number = 0.005,
62
+ ): MediaFragment | null {
63
+ let fragNext: MediaFragment | null = null;
64
+ if (fragPrevious) {
65
+ fragNext = fragments[1 + fragPrevious.sn - fragments[0].sn] || null;
66
+ // check for buffer-end rounding error
67
+ const bufferEdgeError = (fragPrevious.endDTS as number) - bufferEnd;
68
+ if (bufferEdgeError > 0 && bufferEdgeError < 0.0000015) {
69
+ bufferEnd += 0.0000015;
70
+ }
71
+ if (
72
+ fragNext &&
73
+ fragPrevious.level !== fragNext.level &&
74
+ fragNext.end <= fragPrevious.end
75
+ ) {
76
+ fragNext = fragments[2 + fragPrevious.sn - fragments[0].sn] || null;
77
+ }
78
+ } else if (bufferEnd === 0 && fragments[0].start === 0) {
79
+ fragNext = fragments[0];
80
+ }
81
+ // Prefer the next fragment if it's within tolerance
82
+ if (
83
+ fragNext &&
84
+ (((!fragPrevious || fragPrevious.level === fragNext.level) &&
85
+ fragmentWithinToleranceTest(
86
+ bufferEnd,
87
+ maxFragLookUpTolerance,
88
+ fragNext,
89
+ ) === 0) ||
90
+ fragmentWithinFastStartSwitch(
91
+ fragNext,
92
+ fragPrevious,
93
+ Math.min(nextFragLookupTolerance, maxFragLookUpTolerance),
94
+ ))
95
+ ) {
96
+ return fragNext;
97
+ }
98
+ // We might be seeking past the tolerance so find the best match
99
+ const foundFragment = BinarySearch.search(
100
+ fragments,
101
+ fragmentWithinToleranceTest.bind(null, bufferEnd, maxFragLookUpTolerance),
102
+ );
103
+ if (foundFragment && (foundFragment !== fragPrevious || !fragNext)) {
104
+ return foundFragment;
105
+ }
106
+ // If no match was found return the next fragment after fragPrevious, or null
107
+ return fragNext;
108
+ }
109
+
110
+ function fragmentWithinFastStartSwitch(
111
+ fragNext: Fragment,
112
+ fragPrevious: Fragment | null,
113
+ nextFragLookupTolerance: number,
114
+ ): boolean {
115
+ if (
116
+ fragPrevious?.start === 0 &&
117
+ fragPrevious.level < fragNext.level &&
118
+ (fragPrevious.endPTS || 0) > 0
119
+ ) {
120
+ const firstDuration = fragPrevious.tagList.reduce((duration, tag) => {
121
+ if (tag[0] === 'INF') {
122
+ duration += parseFloat(tag[1]);
123
+ }
124
+ return duration;
125
+ }, nextFragLookupTolerance);
126
+ return fragNext.start <= firstDuration;
127
+ }
128
+ return false;
129
+ }
130
+
131
+ /**
132
+ * The test function used by the findFragmentBySn's BinarySearch to look for the best match to the current buffer conditions.
133
+ * @param candidate - The fragment to test
134
+ * @param bufferEnd - The end of the current buffered range the playhead is currently within
135
+ * @param maxFragLookUpTolerance - The amount of time that a fragment's start can be within in order to be considered contiguous
136
+ * @returns 0 if it matches, 1 if too low, -1 if too high
137
+ */
138
+ export function fragmentWithinToleranceTest(
139
+ bufferEnd = 0,
140
+ maxFragLookUpTolerance = 0,
141
+ candidate: MediaFragment,
142
+ ) {
143
+ // eagerly accept an accurate match (no tolerance)
144
+ if (
145
+ candidate.start <= bufferEnd &&
146
+ candidate.start + candidate.duration > bufferEnd
147
+ ) {
148
+ return 0;
149
+ }
150
+ // offset should be within fragment boundary - config.maxFragLookUpTolerance
151
+ // this is to cope with situations like
152
+ // bufferEnd = 9.991
153
+ // frag[Ø] : [0,10]
154
+ // frag[1] : [10,20]
155
+ // bufferEnd is within frag[0] range ... although what we are expecting is to return frag[1] here
156
+ // frag start frag start+duration
157
+ // |-----------------------------|
158
+ // <---> <--->
159
+ // ...--------><-----------------------------><---------....
160
+ // previous frag matching fragment next frag
161
+ // return -1 return 0 return 1
162
+ // logger.log(`level/sn/start/end/bufEnd:${level}/${candidate.sn}/${candidate.start}/${(candidate.start+candidate.duration)}/${bufferEnd}`);
163
+ // Set the lookup tolerance to be small enough to detect the current segment - ensures we don't skip over very small segments
164
+ const candidateLookupTolerance = Math.min(
165
+ maxFragLookUpTolerance,
166
+ candidate.duration + (candidate.deltaPTS ? candidate.deltaPTS : 0),
167
+ );
168
+ if (
169
+ candidate.start + candidate.duration - candidateLookupTolerance <=
170
+ bufferEnd
171
+ ) {
172
+ return 1;
173
+ } else if (
174
+ candidate.start - candidateLookupTolerance > bufferEnd &&
175
+ candidate.start
176
+ ) {
177
+ // if maxFragLookUpTolerance will have negative value then don't return -1 for first element
178
+ return -1;
179
+ }
180
+
181
+ return 0;
182
+ }
183
+
184
+ /**
185
+ * The test function used by the findFragmentByPdt's BinarySearch to look for the best match to the current buffer conditions.
186
+ * This function tests the candidate's program date time values, as represented in Unix time
187
+ * @param candidate - The fragment to test
188
+ * @param pdtBufferEnd - The Unix time representing the end of the current buffered range
189
+ * @param maxFragLookUpTolerance - The amount of time that a fragment's start can be within in order to be considered contiguous
190
+ * @returns true if contiguous, false otherwise
191
+ */
192
+ export function pdtWithinToleranceTest(
193
+ pdtBufferEnd: number,
194
+ maxFragLookUpTolerance: number,
195
+ candidate: MediaFragment,
196
+ ): boolean {
197
+ const candidateLookupTolerance =
198
+ Math.min(
199
+ maxFragLookUpTolerance,
200
+ candidate.duration + (candidate.deltaPTS ? candidate.deltaPTS : 0),
201
+ ) * 1000;
202
+
203
+ // endProgramDateTime can be null, default to zero
204
+ const endProgramDateTime = candidate.endProgramDateTime || 0;
205
+ return endProgramDateTime - candidateLookupTolerance > pdtBufferEnd;
206
+ }
207
+
208
+ export function findFragWithCC(
209
+ fragments: MediaFragment[],
210
+ cc: number,
211
+ ): MediaFragment | null {
212
+ return BinarySearch.search(fragments, (candidate) => {
213
+ if (candidate.cc < cc) {
214
+ return 1;
215
+ } else if (candidate.cc > cc) {
216
+ return -1;
217
+ } else {
218
+ return 0;
219
+ }
220
+ });
221
+ }
222
+
223
+ export function findNearestWithCC(
224
+ details: LevelDetails | undefined,
225
+ cc: number,
226
+ pos: number,
227
+ ): MediaFragment | null {
228
+ if (details) {
229
+ if (details.startCC <= cc && details.endCC >= cc) {
230
+ let fragments = details.fragments;
231
+ const { fragmentHint } = details;
232
+ if (fragmentHint) {
233
+ fragments = fragments.concat(fragmentHint);
234
+ }
235
+ let closest: MediaFragment | undefined;
236
+ BinarySearch.search(fragments, (candidate) => {
237
+ if (candidate.cc < cc) {
238
+ return 1;
239
+ }
240
+ if (candidate.cc > cc) {
241
+ return -1;
242
+ }
243
+ closest = candidate;
244
+ if (candidate.end <= pos) {
245
+ return 1;
246
+ }
247
+ if (candidate.start > pos) {
248
+ return -1;
249
+ }
250
+ return 0;
251
+ });
252
+ return closest || null;
253
+ }
254
+ }
255
+ return null;
256
+ }