@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,337 @@
1
+ import { getRetryDelay, shouldRetry } from './error-helper';
2
+ import { LoadStats } from '../loader/load-stats';
3
+ import { logger } from '../utils/logger';
4
+ import type { HlsConfig } from '../config';
5
+ import type { RetryConfig } from '../config';
6
+ import type {
7
+ Loader,
8
+ LoaderCallbacks,
9
+ LoaderConfiguration,
10
+ LoaderContext,
11
+ LoaderResponse,
12
+ LoaderStats,
13
+ } from '../types/loader';
14
+
15
+ const AGE_HEADER_LINE_REGEX = /^age:\s*[\d.]+\s*$/im;
16
+
17
+ class XhrLoader implements Loader<LoaderContext> {
18
+ private xhrSetup:
19
+ | ((xhr: XMLHttpRequest, url: string) => Promise<void> | void)
20
+ | null;
21
+ private requestTimeout?: number;
22
+ private retryTimeout?: number;
23
+ private retryDelay: number;
24
+ private config: LoaderConfiguration | null = null;
25
+ private callbacks: LoaderCallbacks<LoaderContext> | null = null;
26
+ public context: LoaderContext | null = null;
27
+
28
+ private loader: XMLHttpRequest | null = null;
29
+ public stats: LoaderStats;
30
+
31
+ constructor(config: HlsConfig) {
32
+ this.xhrSetup = config ? config.xhrSetup || null : null;
33
+ this.stats = new LoadStats();
34
+ this.retryDelay = 0;
35
+ }
36
+
37
+ destroy() {
38
+ this.callbacks = null;
39
+ this.abortInternal();
40
+ this.loader = null;
41
+ this.config = null;
42
+ this.context = null;
43
+ this.xhrSetup = null;
44
+ }
45
+
46
+ abortInternal() {
47
+ const loader = this.loader;
48
+ self.clearTimeout(this.requestTimeout);
49
+ self.clearTimeout(this.retryTimeout);
50
+ if (loader) {
51
+ loader.onreadystatechange = null;
52
+ loader.onprogress = null;
53
+ if (loader.readyState !== 4) {
54
+ this.stats.aborted = true;
55
+ loader.abort();
56
+ }
57
+ }
58
+ }
59
+
60
+ abort() {
61
+ this.abortInternal();
62
+ if (this.callbacks?.onAbort) {
63
+ this.callbacks.onAbort(
64
+ this.stats,
65
+ this.context as LoaderContext,
66
+ this.loader,
67
+ );
68
+ }
69
+ }
70
+
71
+ load(
72
+ context: LoaderContext,
73
+ config: LoaderConfiguration,
74
+ callbacks: LoaderCallbacks<LoaderContext>,
75
+ ) {
76
+ if (this.stats.loading.start) {
77
+ throw new Error('Loader can only be used once.');
78
+ }
79
+ this.stats.loading.start = self.performance.now();
80
+ this.context = context;
81
+ this.config = config;
82
+ this.callbacks = callbacks;
83
+ this.loadInternal();
84
+ }
85
+
86
+ loadInternal() {
87
+ const { config, context } = this;
88
+ if (!config || !context) {
89
+ return;
90
+ }
91
+ const xhr = (this.loader = new self.XMLHttpRequest());
92
+
93
+ const stats = this.stats;
94
+ stats.loading.first = 0;
95
+ stats.loaded = 0;
96
+ stats.aborted = false;
97
+ const xhrSetup = this.xhrSetup;
98
+
99
+ if (xhrSetup) {
100
+ Promise.resolve()
101
+ .then(() => {
102
+ if (this.loader !== xhr || this.stats.aborted) return;
103
+ return xhrSetup(xhr, context.url);
104
+ })
105
+ .catch((error: Error) => {
106
+ if (this.loader !== xhr || this.stats.aborted) return;
107
+ xhr.open('GET', context.url, true);
108
+ return xhrSetup(xhr, context.url);
109
+ })
110
+ .then(() => {
111
+ if (this.loader !== xhr || this.stats.aborted) return;
112
+ this.openAndSendXhr(xhr, context, config);
113
+ })
114
+ .catch((error: Error) => {
115
+ // IE11 throws an exception on xhr.open if attempting to access an HTTP resource over HTTPS
116
+ this.callbacks?.onError(
117
+ { code: xhr.status, text: error.message },
118
+ context,
119
+ xhr,
120
+ stats,
121
+ );
122
+ return;
123
+ });
124
+ } else {
125
+ this.openAndSendXhr(xhr, context, config);
126
+ }
127
+ }
128
+
129
+ openAndSendXhr(
130
+ xhr: XMLHttpRequest,
131
+ context: LoaderContext,
132
+ config: LoaderConfiguration,
133
+ ) {
134
+ if (!xhr.readyState) {
135
+ xhr.open('GET', context.url, true);
136
+ }
137
+
138
+ const headers = context.headers;
139
+ const { maxTimeToFirstByteMs, maxLoadTimeMs } = config.loadPolicy;
140
+ if (headers) {
141
+ for (const header in headers) {
142
+ xhr.setRequestHeader(header, headers[header]);
143
+ }
144
+ }
145
+
146
+ if (context.rangeEnd) {
147
+ xhr.setRequestHeader(
148
+ 'Range',
149
+ 'bytes=' + context.rangeStart + '-' + (context.rangeEnd - 1),
150
+ );
151
+ }
152
+
153
+ xhr.onreadystatechange = this.readystatechange.bind(this);
154
+ xhr.onprogress = this.loadprogress.bind(this);
155
+ xhr.responseType = context.responseType as XMLHttpRequestResponseType;
156
+ // setup timeout before we perform request
157
+ self.clearTimeout(this.requestTimeout);
158
+ config.timeout =
159
+ maxTimeToFirstByteMs && Number.isFinite(maxTimeToFirstByteMs)
160
+ ? maxTimeToFirstByteMs
161
+ : maxLoadTimeMs;
162
+ this.requestTimeout = self.setTimeout(
163
+ this.loadtimeout.bind(this),
164
+ config.timeout,
165
+ );
166
+ xhr.send();
167
+ }
168
+
169
+ readystatechange() {
170
+ const { context, loader: xhr, stats } = this;
171
+ if (!context || !xhr) {
172
+ return;
173
+ }
174
+ const readyState = xhr.readyState;
175
+ const config = this.config as LoaderConfiguration;
176
+
177
+ // don't proceed if xhr has been aborted
178
+ if (stats.aborted) {
179
+ return;
180
+ }
181
+
182
+ // >= HEADERS_RECEIVED
183
+ if (readyState >= 2) {
184
+ if (stats.loading.first === 0) {
185
+ stats.loading.first = Math.max(
186
+ self.performance.now(),
187
+ stats.loading.start,
188
+ );
189
+ // readyState >= 2 AND readyState !==4 (readyState = HEADERS_RECEIVED || LOADING) rearm timeout as xhr not finished yet
190
+ if (config.timeout !== config.loadPolicy.maxLoadTimeMs) {
191
+ self.clearTimeout(this.requestTimeout);
192
+ config.timeout = config.loadPolicy.maxLoadTimeMs;
193
+ this.requestTimeout = self.setTimeout(
194
+ this.loadtimeout.bind(this),
195
+ config.loadPolicy.maxLoadTimeMs -
196
+ (stats.loading.first - stats.loading.start),
197
+ );
198
+ }
199
+ }
200
+
201
+ if (readyState === 4) {
202
+ self.clearTimeout(this.requestTimeout);
203
+ xhr.onreadystatechange = null;
204
+ xhr.onprogress = null;
205
+ const status = xhr.status;
206
+ // http status between 200 to 299 are all successful
207
+ const useResponseText =
208
+ xhr.responseType === 'text' ? xhr.responseText : null;
209
+ if (status >= 200 && status < 300) {
210
+ const data = useResponseText ?? xhr.response;
211
+ if (data != null) {
212
+ stats.loading.end = Math.max(
213
+ self.performance.now(),
214
+ stats.loading.first,
215
+ );
216
+ const len =
217
+ xhr.responseType === 'arraybuffer'
218
+ ? data.byteLength
219
+ : data.length;
220
+ stats.loaded = stats.total = len;
221
+ stats.bwEstimate =
222
+ (stats.total * 8000) / (stats.loading.end - stats.loading.first);
223
+ const onProgress = this.callbacks?.onProgress;
224
+ if (onProgress) {
225
+ onProgress(stats, context, data, xhr);
226
+ }
227
+ const response: LoaderResponse = {
228
+ url: xhr.responseURL,
229
+ data: data,
230
+ code: status,
231
+ };
232
+ this.callbacks?.onSuccess(response, stats, context, xhr);
233
+ return;
234
+ }
235
+ }
236
+
237
+ // Handle bad status or nullish response
238
+ const retryConfig = config.loadPolicy.errorRetry;
239
+ const retryCount = stats.retry;
240
+ // if max nb of retries reached or if http status between 400 and 499 (such error cannot be recovered, retrying is useless), return error
241
+ const response: LoaderResponse = {
242
+ url: context.url,
243
+ data: undefined,
244
+ code: status,
245
+ };
246
+ if (shouldRetry(retryConfig, retryCount, false, response)) {
247
+ this.retry(retryConfig);
248
+ } else {
249
+ logger.error(`${status} while loading ${context.url}`);
250
+ this.callbacks?.onError(
251
+ { code: status, text: xhr.statusText },
252
+ context,
253
+ xhr,
254
+ stats,
255
+ );
256
+ }
257
+ }
258
+ }
259
+ }
260
+
261
+ loadtimeout() {
262
+ if (!this.config) return;
263
+ const retryConfig = this.config.loadPolicy.timeoutRetry;
264
+ const retryCount = this.stats.retry;
265
+ if (shouldRetry(retryConfig, retryCount, true)) {
266
+ this.retry(retryConfig);
267
+ } else {
268
+ logger.warn(`timeout while loading ${this.context?.url}`);
269
+ const callbacks = this.callbacks;
270
+ if (callbacks) {
271
+ this.abortInternal();
272
+ callbacks.onTimeout(
273
+ this.stats,
274
+ this.context as LoaderContext,
275
+ this.loader,
276
+ );
277
+ }
278
+ }
279
+ }
280
+
281
+ retry(retryConfig: RetryConfig) {
282
+ const { context, stats } = this;
283
+ this.retryDelay = getRetryDelay(retryConfig, stats.retry);
284
+ stats.retry++;
285
+ logger.warn(
286
+ `${
287
+ status ? 'HTTP Status ' + status : 'Timeout'
288
+ } while loading ${context?.url}, retrying ${stats.retry}/${
289
+ retryConfig.maxNumRetry
290
+ } in ${this.retryDelay}ms`,
291
+ );
292
+ // abort and reset internal state
293
+ this.abortInternal();
294
+ this.loader = null;
295
+ // schedule retry
296
+ self.clearTimeout(this.retryTimeout);
297
+ this.retryTimeout = self.setTimeout(
298
+ this.loadInternal.bind(this),
299
+ this.retryDelay,
300
+ );
301
+ }
302
+
303
+ loadprogress(event: ProgressEvent) {
304
+ const stats = this.stats;
305
+
306
+ stats.loaded = event.loaded;
307
+ if (event.lengthComputable) {
308
+ stats.total = event.total;
309
+ }
310
+ }
311
+
312
+ getCacheAge(): number | null {
313
+ let result: number | null = null;
314
+ if (
315
+ this.loader &&
316
+ AGE_HEADER_LINE_REGEX.test(this.loader.getAllResponseHeaders())
317
+ ) {
318
+ const ageHeader = this.loader.getResponseHeader('age');
319
+ result = ageHeader ? parseFloat(ageHeader) : null;
320
+ }
321
+ return result;
322
+ }
323
+
324
+ getResponseHeader(name: string): string | null {
325
+ if (
326
+ this.loader &&
327
+ new RegExp(`^${name}:\\s*[\\d.]+\\s*$`, 'im').test(
328
+ this.loader.getAllResponseHeaders(),
329
+ )
330
+ ) {
331
+ return this.loader.getResponseHeader(name);
332
+ }
333
+ return null;
334
+ }
335
+ }
336
+
337
+ export default XhrLoader;
package/src/version.ts ADDED
@@ -0,0 +1 @@
1
+ export const version = __VERSION__;