@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,449 @@
1
+ import { EventEmitter } from 'eventemitter3';
2
+ import {
3
+ hasUMDWorker,
4
+ injectWorker,
5
+ loadWorker,
6
+ removeWorkerFromStore as removeWorkerClient,
7
+ } from './inject-worker';
8
+ import Transmuxer, {
9
+ isPromise,
10
+ TransmuxConfig,
11
+ TransmuxState,
12
+ } from '../demux/transmuxer';
13
+ import { ErrorDetails, ErrorTypes } from '../errors';
14
+ import { Events } from '../events';
15
+ import { PlaylistLevelType } from '../types/loader';
16
+ import { getM2TSSupportedAudioTypes } from '../utils/codecs';
17
+ import { stringify } from '../utils/safe-json-stringify';
18
+ import type { WorkerContext } from './inject-worker';
19
+ import type { HlsEventEmitter, HlsListeners } from '../events';
20
+ import type Hls from '../hls';
21
+ import type { MediaFragment, Part } from '../loader/fragment';
22
+ import type { ErrorData, FragDecryptedData } from '../types/events';
23
+ import type { ChunkMetadata, TransmuxerResult } from '../types/transmuxer';
24
+ import type { TimestampOffset } from '../utils/timescale-conversion';
25
+
26
+ let transmuxerInstanceCount: number = 0;
27
+
28
+ export default class TransmuxerInterface {
29
+ public error: Error | null = null;
30
+ private hls: Hls;
31
+ private id: PlaylistLevelType;
32
+ private instanceNo: number = transmuxerInstanceCount++;
33
+ private observer: HlsEventEmitter;
34
+ private frag: MediaFragment | null = null;
35
+ private part: Part | null = null;
36
+ private useWorker: boolean;
37
+ private workerContext: WorkerContext | null = null;
38
+ private transmuxer: Transmuxer | null = null;
39
+ private onTransmuxComplete: (transmuxResult: TransmuxerResult) => void;
40
+ private onFlush: (chunkMeta: ChunkMetadata) => void;
41
+
42
+ constructor(
43
+ hls: Hls,
44
+ id: PlaylistLevelType,
45
+ onTransmuxComplete: (transmuxResult: TransmuxerResult) => void,
46
+ onFlush: (chunkMeta: ChunkMetadata) => void,
47
+ ) {
48
+ const config = hls.config;
49
+ this.hls = hls;
50
+ this.id = id;
51
+ this.useWorker = !!config.enableWorker;
52
+ this.onTransmuxComplete = onTransmuxComplete;
53
+ this.onFlush = onFlush;
54
+
55
+ const forwardMessage = (
56
+ ev: Events.ERROR | Events.FRAG_DECRYPTED,
57
+ data: ErrorData | FragDecryptedData,
58
+ ) => {
59
+ data = data || {};
60
+ data.frag = this.frag || undefined;
61
+ if (ev === Events.ERROR) {
62
+ data = data as ErrorData;
63
+ data.parent = this.id;
64
+ data.part = this.part;
65
+ this.error = data.error;
66
+ }
67
+ this.hls.trigger(ev, data);
68
+ };
69
+
70
+ // forward events to main thread
71
+ this.observer = new EventEmitter() as HlsEventEmitter;
72
+ this.observer.on(Events.FRAG_DECRYPTED, forwardMessage);
73
+ this.observer.on(Events.ERROR, forwardMessage);
74
+
75
+ const m2tsTypeSupported = getM2TSSupportedAudioTypes(
76
+ config.preferManagedMediaSource,
77
+ );
78
+
79
+ if (this.useWorker && typeof Worker !== 'undefined') {
80
+ const logger = this.hls.logger;
81
+ const canCreateWorker = config.workerPath || hasUMDWorker();
82
+ if (canCreateWorker) {
83
+ try {
84
+ if (config.workerPath) {
85
+ logger.log(`loading Web Worker ${config.workerPath} for "${id}"`);
86
+ this.workerContext = loadWorker(config.workerPath);
87
+ } else {
88
+ logger.log(`injecting Web Worker for "${id}"`);
89
+ this.workerContext = injectWorker();
90
+ }
91
+ const { worker } = this.workerContext;
92
+ worker.addEventListener('message', this.onWorkerMessage);
93
+ worker.addEventListener('error', this.onWorkerError);
94
+ worker.postMessage({
95
+ instanceNo: this.instanceNo,
96
+ cmd: 'init',
97
+ typeSupported: m2tsTypeSupported,
98
+ id,
99
+ config: stringify(config),
100
+ });
101
+ } catch (err) {
102
+ logger.warn(
103
+ `Error setting up "${id}" Web Worker, fallback to inline`,
104
+ err,
105
+ );
106
+ this.terminateWorker();
107
+ this.error = null;
108
+ this.transmuxer = new Transmuxer(
109
+ this.observer,
110
+ m2tsTypeSupported,
111
+ config,
112
+ '',
113
+ id,
114
+ hls.logger,
115
+ );
116
+ }
117
+ return;
118
+ }
119
+ }
120
+
121
+ this.transmuxer = new Transmuxer(
122
+ this.observer,
123
+ m2tsTypeSupported,
124
+ config,
125
+ '',
126
+ id,
127
+ hls.logger,
128
+ );
129
+ }
130
+
131
+ reset() {
132
+ this.frag = null;
133
+ this.part = null;
134
+ if (this.workerContext) {
135
+ const instanceNo = this.instanceNo;
136
+ this.instanceNo = transmuxerInstanceCount++;
137
+ const config = this.hls.config;
138
+ const m2tsTypeSupported = getM2TSSupportedAudioTypes(
139
+ config.preferManagedMediaSource,
140
+ );
141
+ this.workerContext.worker.postMessage({
142
+ instanceNo: this.instanceNo,
143
+ cmd: 'reset',
144
+ resetNo: instanceNo,
145
+ typeSupported: m2tsTypeSupported,
146
+ id: this.id,
147
+ config: stringify(config),
148
+ });
149
+ }
150
+ }
151
+
152
+ private terminateWorker() {
153
+ if (this.workerContext) {
154
+ const { worker } = this.workerContext;
155
+ this.workerContext = null;
156
+ worker.removeEventListener('message', this.onWorkerMessage);
157
+ worker.removeEventListener('error', this.onWorkerError);
158
+ removeWorkerClient(this.hls.config.workerPath);
159
+ }
160
+ }
161
+
162
+ destroy() {
163
+ if (this.workerContext) {
164
+ this.terminateWorker();
165
+ // @ts-ignore
166
+ this.onWorkerMessage = this.onWorkerError = null;
167
+ } else {
168
+ const transmuxer = this.transmuxer;
169
+ if (transmuxer) {
170
+ transmuxer.destroy();
171
+ this.transmuxer = null;
172
+ }
173
+ }
174
+ const observer = this.observer;
175
+ if (observer) {
176
+ observer.removeAllListeners();
177
+ }
178
+ this.frag = null;
179
+ this.part = null;
180
+ // @ts-ignore
181
+ this.observer = null;
182
+ // @ts-ignore
183
+ this.hls = null;
184
+ }
185
+
186
+ push(
187
+ data: ArrayBuffer,
188
+ initSegmentData: Uint8Array | undefined,
189
+ audioCodec: string | undefined,
190
+ videoCodec: string | undefined,
191
+ frag: MediaFragment,
192
+ part: Part | null,
193
+ duration: number,
194
+ accurateTimeOffset: boolean,
195
+ chunkMeta: ChunkMetadata,
196
+ defaultInitPTS?: TimestampOffset,
197
+ ) {
198
+ chunkMeta.transmuxing.start = self.performance.now();
199
+ const { instanceNo, transmuxer } = this;
200
+ const timeOffset = part ? part.start : frag.start;
201
+ // TODO: push "clear-lead" decrypt data for unencrypted fragments in streams with encrypted ones
202
+ const decryptdata = frag.decryptdata;
203
+ const lastFrag = this.frag;
204
+
205
+ const discontinuity = !(lastFrag && frag.cc === lastFrag.cc);
206
+ const trackSwitch = !(lastFrag && chunkMeta.level === lastFrag.level);
207
+ const snDiff = lastFrag ? chunkMeta.sn - lastFrag.sn : -1;
208
+ const partDiff = this.part ? chunkMeta.part - this.part.index : -1;
209
+ const progressive =
210
+ snDiff === 0 &&
211
+ chunkMeta.id > 1 &&
212
+ chunkMeta.id === lastFrag?.stats.chunkCount;
213
+ const contiguous =
214
+ !trackSwitch &&
215
+ (snDiff === 1 ||
216
+ (snDiff === 0 && (partDiff === 1 || (progressive && partDiff <= 0))));
217
+ const now = self.performance.now();
218
+
219
+ if (trackSwitch || snDiff || frag.stats.parsing.start === 0) {
220
+ frag.stats.parsing.start = now;
221
+ }
222
+ if (part && (partDiff || !contiguous)) {
223
+ part.stats.parsing.start = now;
224
+ }
225
+ const initSegmentChange = !(
226
+ lastFrag && frag.initSegment?.url === lastFrag.initSegment?.url
227
+ );
228
+ const state = new TransmuxState(
229
+ discontinuity,
230
+ contiguous,
231
+ accurateTimeOffset,
232
+ trackSwitch,
233
+ timeOffset,
234
+ initSegmentChange,
235
+ );
236
+ if (!contiguous || discontinuity || initSegmentChange) {
237
+ this.hls.logger
238
+ .log(`[transmuxer-interface]: Starting new transmux session for ${frag.type} sn: ${chunkMeta.sn}${
239
+ chunkMeta.part > -1 ? ' part: ' + chunkMeta.part : ''
240
+ } ${this.id === PlaylistLevelType.MAIN ? 'level' : 'track'}: ${chunkMeta.level} id: ${chunkMeta.id}
241
+ discontinuity: ${discontinuity}
242
+ trackSwitch: ${trackSwitch}
243
+ contiguous: ${contiguous}
244
+ accurateTimeOffset: ${accurateTimeOffset}
245
+ timeOffset: ${timeOffset}
246
+ initSegmentChange: ${initSegmentChange}`);
247
+ const config = new TransmuxConfig(
248
+ audioCodec,
249
+ videoCodec,
250
+ initSegmentData,
251
+ duration,
252
+ defaultInitPTS,
253
+ );
254
+ this.configureTransmuxer(config);
255
+ }
256
+
257
+ this.frag = frag;
258
+ this.part = part;
259
+
260
+ // Frags with sn of 'initSegment' are not transmuxed
261
+ if (this.workerContext) {
262
+ // post fragment payload as transferable objects for ArrayBuffer (no copy)
263
+ this.workerContext.worker.postMessage(
264
+ {
265
+ instanceNo,
266
+ cmd: 'demux',
267
+ data,
268
+ decryptdata,
269
+ chunkMeta,
270
+ state,
271
+ },
272
+ data instanceof ArrayBuffer ? [data] : [],
273
+ );
274
+ } else if (transmuxer) {
275
+ const transmuxResult = transmuxer.push(
276
+ data,
277
+ decryptdata,
278
+ chunkMeta,
279
+ state,
280
+ );
281
+ if (isPromise(transmuxResult)) {
282
+ transmuxResult
283
+ .then((data) => {
284
+ this.handleTransmuxComplete(data);
285
+ })
286
+ .catch((error) => {
287
+ this.transmuxerError(
288
+ error,
289
+ chunkMeta,
290
+ 'transmuxer-interface push error',
291
+ );
292
+ });
293
+ } else {
294
+ this.handleTransmuxComplete(transmuxResult as TransmuxerResult);
295
+ }
296
+ }
297
+ }
298
+
299
+ flush(chunkMeta: ChunkMetadata) {
300
+ chunkMeta.transmuxing.start = self.performance.now();
301
+ const { instanceNo, transmuxer } = this;
302
+ if (this.workerContext) {
303
+ 1;
304
+ this.workerContext.worker.postMessage({
305
+ instanceNo,
306
+ cmd: 'flush',
307
+ chunkMeta,
308
+ });
309
+ } else if (transmuxer) {
310
+ const transmuxResult = transmuxer.flush(chunkMeta);
311
+ if (isPromise(transmuxResult)) {
312
+ transmuxResult
313
+ .then((data) => {
314
+ this.handleFlushResult(data, chunkMeta);
315
+ })
316
+ .catch((error) => {
317
+ this.transmuxerError(
318
+ error,
319
+ chunkMeta,
320
+ 'transmuxer-interface flush error',
321
+ );
322
+ });
323
+ } else {
324
+ this.handleFlushResult(transmuxResult, chunkMeta);
325
+ }
326
+ }
327
+ }
328
+
329
+ private transmuxerError(
330
+ error: Error,
331
+ chunkMeta: ChunkMetadata,
332
+ reason: string,
333
+ ) {
334
+ if (!this.hls) {
335
+ return;
336
+ }
337
+ this.error = error;
338
+ this.hls.trigger(Events.ERROR, {
339
+ type: ErrorTypes.MEDIA_ERROR,
340
+ details: ErrorDetails.FRAG_PARSING_ERROR,
341
+ chunkMeta,
342
+ frag: this.frag || undefined,
343
+ part: this.part || undefined,
344
+ fatal: false,
345
+ error,
346
+ err: error,
347
+ reason,
348
+ });
349
+ }
350
+
351
+ private handleFlushResult(
352
+ results: Array<TransmuxerResult>,
353
+ chunkMeta: ChunkMetadata,
354
+ ) {
355
+ results.forEach((result) => {
356
+ this.handleTransmuxComplete(result);
357
+ });
358
+ this.onFlush(chunkMeta);
359
+ }
360
+
361
+ private onWorkerMessage = (
362
+ event: MessageEvent<{
363
+ event: string;
364
+ data?: any;
365
+ instanceNo?: number;
366
+ } | null>,
367
+ ) => {
368
+ const data = event.data;
369
+ const hls = this.hls;
370
+ if (!hls || !data?.event || data.instanceNo !== this.instanceNo) {
371
+ return;
372
+ }
373
+ switch (data.event) {
374
+ case 'init': {
375
+ const objectURL = this.workerContext?.objectURL;
376
+ if (objectURL) {
377
+ // revoke the Object URL that was used to create transmuxer worker, so as not to leak it
378
+ self.URL.revokeObjectURL(objectURL);
379
+ }
380
+ break;
381
+ }
382
+
383
+ case 'transmuxComplete': {
384
+ this.handleTransmuxComplete(data.data);
385
+ break;
386
+ }
387
+
388
+ case 'flush': {
389
+ this.onFlush(data.data);
390
+ break;
391
+ }
392
+
393
+ // pass logs from the worker thread to the main logger
394
+ case 'workerLog': {
395
+ if (hls.logger[data.data.logType]) {
396
+ hls.logger[data.data.logType](data.data.message);
397
+ }
398
+ break;
399
+ }
400
+
401
+ default: {
402
+ data.data = data.data || {};
403
+ data.data.frag = this.frag;
404
+ data.data.part = this.part;
405
+ data.data.id = this.id;
406
+ hls.trigger(data.event as keyof HlsListeners, data.data);
407
+ break;
408
+ }
409
+ }
410
+ };
411
+
412
+ private onWorkerError = (event) => {
413
+ if (!this.hls) {
414
+ return;
415
+ }
416
+ const error = new Error(
417
+ `${event.message} (${event.filename}:${event.lineno})`,
418
+ );
419
+ this.hls.config.enableWorker = false;
420
+ this.hls.logger.warn(
421
+ `Error in "${this.id}" Web Worker, fallback to inline`,
422
+ );
423
+ this.hls.trigger(Events.ERROR, {
424
+ type: ErrorTypes.OTHER_ERROR,
425
+ details: ErrorDetails.INTERNAL_EXCEPTION,
426
+ fatal: false,
427
+ event: 'demuxerWorker',
428
+ error,
429
+ });
430
+ };
431
+
432
+ private configureTransmuxer(config: TransmuxConfig) {
433
+ const { instanceNo, transmuxer } = this;
434
+ if (this.workerContext) {
435
+ this.workerContext.worker.postMessage({
436
+ instanceNo,
437
+ cmd: 'configure',
438
+ config,
439
+ });
440
+ } else if (transmuxer) {
441
+ transmuxer.configure(config);
442
+ }
443
+ }
444
+
445
+ private handleTransmuxComplete(result: TransmuxerResult) {
446
+ result.chunkMeta.transmuxing.end = self.performance.now();
447
+ this.onTransmuxComplete(result);
448
+ }
449
+ }
@@ -0,0 +1,221 @@
1
+ import { EventEmitter } from 'eventemitter3';
2
+ import Transmuxer, { isPromise } from '../demux/transmuxer';
3
+ import { ErrorDetails, ErrorTypes } from '../errors';
4
+ import { Events } from '../events';
5
+ import { enableLogs, type ILogger } from '../utils/logger';
6
+ import type { RemuxedTrack, RemuxerResult } from '../types/remuxer';
7
+ import type { ChunkMetadata, TransmuxerResult } from '../types/transmuxer';
8
+
9
+ const transmuxers: (Transmuxer | undefined)[] = [];
10
+
11
+ if (typeof __IN_WORKER__ !== 'undefined' && __IN_WORKER__) {
12
+ startWorker();
13
+ }
14
+
15
+ function startWorker() {
16
+ self.addEventListener('message', (ev) => {
17
+ const data = ev.data;
18
+ const instanceNo = data.instanceNo;
19
+ if (instanceNo === undefined) {
20
+ return;
21
+ }
22
+ const transmuxer = transmuxers[instanceNo];
23
+ if (data.cmd === 'reset') {
24
+ delete transmuxers[data.resetNo];
25
+ if (transmuxer) {
26
+ transmuxer.destroy();
27
+ }
28
+ data.cmd = 'init';
29
+ }
30
+ if (data.cmd === 'init') {
31
+ const config = JSON.parse(data.config);
32
+ const observer = new EventEmitter();
33
+ observer.on(Events.FRAG_DECRYPTED, forwardMessage);
34
+ observer.on(Events.ERROR, forwardMessage);
35
+ const logger = enableLogs(config.debug, data.id);
36
+ forwardWorkerLogs(logger, instanceNo);
37
+ transmuxers[instanceNo] = new Transmuxer(
38
+ observer,
39
+ data.typeSupported,
40
+ config,
41
+ '',
42
+ data.id,
43
+ logger,
44
+ );
45
+ forwardMessage('init', null, instanceNo);
46
+ return;
47
+ }
48
+ if (!transmuxer) {
49
+ return;
50
+ }
51
+ switch (data.cmd) {
52
+ case 'configure': {
53
+ transmuxer.configure(data.config);
54
+ break;
55
+ }
56
+ case 'demux': {
57
+ const transmuxResult: TransmuxerResult | Promise<TransmuxerResult> =
58
+ transmuxer.push(
59
+ data.data,
60
+ data.decryptdata,
61
+ data.chunkMeta,
62
+ data.state,
63
+ );
64
+ if (isPromise(transmuxResult)) {
65
+ transmuxResult
66
+ .then((data) => {
67
+ emitTransmuxComplete(self, data, instanceNo);
68
+ })
69
+ .catch((error) => {
70
+ forwardMessage(
71
+ Events.ERROR,
72
+ {
73
+ instanceNo,
74
+ type: ErrorTypes.MEDIA_ERROR,
75
+ details: ErrorDetails.FRAG_PARSING_ERROR,
76
+ chunkMeta: data.chunkMeta,
77
+ fatal: false,
78
+ error,
79
+ err: error,
80
+ reason: `transmuxer-worker push error`,
81
+ },
82
+ instanceNo,
83
+ );
84
+ });
85
+ } else {
86
+ emitTransmuxComplete(self, transmuxResult, instanceNo);
87
+ }
88
+ break;
89
+ }
90
+ case 'flush': {
91
+ const chunkMeta = data.chunkMeta as ChunkMetadata;
92
+ const transmuxResult = transmuxer.flush(chunkMeta);
93
+ if (isPromise(transmuxResult)) {
94
+ transmuxResult
95
+ .then((results: Array<TransmuxerResult>) => {
96
+ handleFlushResult(
97
+ self,
98
+ results as Array<TransmuxerResult>,
99
+ chunkMeta,
100
+ instanceNo,
101
+ );
102
+ })
103
+ .catch((error) => {
104
+ forwardMessage(
105
+ Events.ERROR,
106
+ {
107
+ type: ErrorTypes.MEDIA_ERROR,
108
+ details: ErrorDetails.FRAG_PARSING_ERROR,
109
+ chunkMeta: data.chunkMeta,
110
+ fatal: false,
111
+ error,
112
+ err: error,
113
+ reason: `transmuxer-worker flush error`,
114
+ },
115
+ instanceNo,
116
+ );
117
+ });
118
+ } else {
119
+ handleFlushResult(
120
+ self,
121
+ transmuxResult as Array<TransmuxerResult>,
122
+ chunkMeta,
123
+ instanceNo,
124
+ );
125
+ }
126
+ break;
127
+ }
128
+ default:
129
+ break;
130
+ }
131
+ });
132
+ }
133
+
134
+ function emitTransmuxComplete(
135
+ self: any,
136
+ transmuxResult: TransmuxerResult,
137
+ instanceNo: number,
138
+ ): boolean {
139
+ if (isEmptyResult(transmuxResult.remuxResult)) {
140
+ return false;
141
+ }
142
+ const transferable: Array<ArrayBuffer> = [];
143
+ const { audio, video } = transmuxResult.remuxResult;
144
+ if (audio) {
145
+ addToTransferable(transferable, audio);
146
+ }
147
+ if (video) {
148
+ addToTransferable(transferable, video);
149
+ }
150
+ self.postMessage(
151
+ { event: 'transmuxComplete', data: transmuxResult, instanceNo },
152
+ transferable,
153
+ );
154
+ return true;
155
+ }
156
+
157
+ // Converts data to a transferable object https://developers.google.com/web/updates/2011/12/Transferable-Objects-Lightning-Fast)
158
+ // in order to minimize message passing overhead
159
+ function addToTransferable(
160
+ transferable: Array<ArrayBuffer>,
161
+ track: RemuxedTrack,
162
+ ) {
163
+ if (track.data1) {
164
+ transferable.push(track.data1.buffer);
165
+ }
166
+ if (track.data2) {
167
+ transferable.push(track.data2.buffer);
168
+ }
169
+ }
170
+
171
+ function handleFlushResult(
172
+ self: any,
173
+ results: Array<TransmuxerResult>,
174
+ chunkMeta: ChunkMetadata,
175
+ instanceNo: number,
176
+ ) {
177
+ const parsed = results.reduce(
178
+ (parsed, result) =>
179
+ emitTransmuxComplete(self, result, instanceNo) || parsed,
180
+ false,
181
+ );
182
+ if (!parsed) {
183
+ // Emit at least one "transmuxComplete" message even if media is not found to update stream-controller state to PARSING
184
+ self.postMessage({
185
+ event: 'transmuxComplete',
186
+ data: results[0],
187
+ instanceNo,
188
+ });
189
+ }
190
+ self.postMessage({ event: 'flush', data: chunkMeta, instanceNo });
191
+ }
192
+
193
+ function forwardMessage(event, data, instanceNo) {
194
+ self.postMessage({ event, data, instanceNo });
195
+ }
196
+
197
+ function forwardWorkerLogs(logger: ILogger, instanceNo: number) {
198
+ for (const logFn in logger) {
199
+ logger[logFn] = function () {
200
+ const message = Array.prototype.join.call(arguments, ' ');
201
+ forwardMessage(
202
+ 'workerLog',
203
+ {
204
+ logType: logFn,
205
+ message,
206
+ },
207
+ instanceNo,
208
+ );
209
+ };
210
+ }
211
+ }
212
+
213
+ function isEmptyResult(remuxResult: RemuxerResult) {
214
+ return (
215
+ !remuxResult.audio &&
216
+ !remuxResult.video &&
217
+ !remuxResult.text &&
218
+ !remuxResult.id3 &&
219
+ !remuxResult.initSegment
220
+ );
221
+ }