hls.js 1.5.14-0.canary.10431 → 1.5.14-0.canary.10432

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.
package/package.json CHANGED
@@ -130,5 +130,5 @@
130
130
  "url-toolkit": "2.2.5",
131
131
  "wrangler": "3.63.2"
132
132
  },
133
- "version": "1.5.14-0.canary.10431"
133
+ "version": "1.5.14-0.canary.10432"
134
134
  }
@@ -365,6 +365,10 @@ export default class BaseStreamController
365
365
 
366
366
  protected onHandlerDestroying() {
367
367
  this.stopLoad();
368
+ if (this.transmuxer) {
369
+ this.transmuxer.destroy();
370
+ this.transmuxer = null;
371
+ }
368
372
  super.onHandlerDestroying();
369
373
  // @ts-ignore
370
374
  this.hls = this.onMediaSeeking = this.onMediaEnded = null;
@@ -1931,10 +1935,7 @@ export default class BaseStreamController
1931
1935
  }
1932
1936
 
1933
1937
  protected resetTransmuxer() {
1934
- if (this.transmuxer) {
1935
- this.transmuxer.destroy();
1936
- this.transmuxer = null;
1937
- }
1938
+ this.transmuxer?.reset();
1938
1939
  }
1939
1940
 
1940
1941
  protected recoverWorkerError(data: ErrorData) {
@@ -4,16 +4,16 @@
4
4
  import BaseAudioDemuxer from './base-audio-demuxer';
5
5
  import * as ADTS from './adts';
6
6
  import * as MpegAudio from './mpegaudio';
7
- import { logger } from '../../utils/logger';
8
7
  import { getId3Data } from '@svta/common-media-library/id3/getId3Data';
9
8
  import type { HlsEventEmitter } from '../../events';
10
9
  import type { HlsConfig } from '../../config';
10
+ import type { ILogger } from '../../utils/logger';
11
11
 
12
12
  class AACDemuxer extends BaseAudioDemuxer {
13
13
  private readonly observer: HlsEventEmitter;
14
14
  private readonly config: HlsConfig;
15
15
 
16
- constructor(observer, config) {
16
+ constructor(observer: HlsEventEmitter, config) {
17
17
  super();
18
18
  this.observer = observer;
19
19
  this.config = config;
@@ -42,7 +42,7 @@ class AACDemuxer extends BaseAudioDemuxer {
42
42
  }
43
43
 
44
44
  // Source for probe info - https://wiki.multimedia.cx/index.php?title=ADTS
45
- static probe(data: Uint8Array | undefined): boolean {
45
+ static probe(data: Uint8Array | undefined, logger: ILogger): boolean {
46
46
  if (!data) {
47
47
  return false;
48
48
  }
@@ -8,7 +8,7 @@ import type { AudioFrame, DemuxedAudioTrack } from '../../types/demuxer';
8
8
  export class AC3Demuxer extends BaseAudioDemuxer {
9
9
  private readonly observer: HlsEventEmitter;
10
10
 
11
- constructor(observer) {
11
+ constructor(observer: HlsEventEmitter) {
12
12
  super();
13
13
  this.observer = observer;
14
14
  }
@@ -1,6 +1,9 @@
1
1
  // ensure the worker ends up in the bundle
2
2
  // If the worker should not be included this gets aliased to empty.js
3
3
  import './transmuxer-worker';
4
+ import { version } from '../version';
5
+
6
+ const workerStore: Record<string, WorkerContext> = {};
4
7
 
5
8
  export function hasUMDWorker(): boolean {
6
9
  return typeof __HLS_WORKER_BUNDLE__ === 'function';
@@ -10,9 +13,15 @@ export type WorkerContext = {
10
13
  worker: Worker;
11
14
  objectURL?: string;
12
15
  scriptURL?: string;
16
+ clientCount: number;
13
17
  };
14
18
 
15
19
  export function injectWorker(): WorkerContext {
20
+ const workerContext = workerStore[version];
21
+ if (workerContext) {
22
+ workerContext.clientCount++;
23
+ return workerContext;
24
+ }
16
25
  const blob = new self.Blob(
17
26
  [
18
27
  `var exports={};var module={exports:exports};function define(f){f()};define.amd=true;(${__HLS_WORKER_BUNDLE__.toString()})(true);`,
@@ -23,19 +32,44 @@ export function injectWorker(): WorkerContext {
23
32
  );
24
33
  const objectURL = self.URL.createObjectURL(blob);
25
34
  const worker = new self.Worker(objectURL);
26
-
27
- return {
35
+ const result = {
28
36
  worker,
29
37
  objectURL,
38
+ clientCount: 1,
30
39
  };
40
+ workerStore[version] = result;
41
+ return result;
31
42
  }
32
43
 
33
44
  export function loadWorker(path: string): WorkerContext {
45
+ const workerContext = workerStore[path];
46
+ if (workerContext) {
47
+ workerContext.clientCount++;
48
+ return workerContext;
49
+ }
34
50
  const scriptURL = new self.URL(path, self.location.href).href;
35
51
  const worker = new self.Worker(scriptURL);
36
-
37
- return {
52
+ const result = {
38
53
  worker,
39
54
  scriptURL,
55
+ clientCount: 1,
40
56
  };
57
+ workerStore[path] = result;
58
+ return result;
59
+ }
60
+
61
+ export function removeWorkerFromStore(path?: string | null) {
62
+ const workerContext = workerStore[path || version];
63
+ if (workerContext) {
64
+ const clientCount = workerContext.clientCount--;
65
+ if (clientCount === 1) {
66
+ const { worker, objectURL } = workerContext;
67
+ delete workerStore[path || version];
68
+ if (objectURL) {
69
+ // revoke the Object URL that was used to create transmuxer worker, so as not to leak it
70
+ self.URL.revokeObjectURL(objectURL);
71
+ }
72
+ worker.terminate();
73
+ }
74
+ }
41
75
  }
@@ -3,6 +3,7 @@ import {
3
3
  hasUMDWorker,
4
4
  injectWorker,
5
5
  loadWorker,
6
+ removeWorkerFromStore as removeWorkerClient,
6
7
  } from './inject-worker';
7
8
  import { Events } from '../events';
8
9
  import Transmuxer, {
@@ -10,29 +11,30 @@ import Transmuxer, {
10
11
  TransmuxState,
11
12
  isPromise,
12
13
  } from '../demux/transmuxer';
13
- import { logger } from '../utils/logger';
14
14
  import { ErrorTypes, ErrorDetails } from '../errors';
15
15
  import { EventEmitter } from 'eventemitter3';
16
16
  import { MediaFragment, Part } from '../loader/fragment';
17
17
  import { getM2TSSupportedAudioTypes } from '../utils/codecs';
18
+
18
19
  import type { ChunkMetadata, TransmuxerResult } from '../types/transmuxer';
19
20
  import type Hls from '../hls';
20
21
  import type { HlsEventEmitter, HlsListeners } from '../events';
22
+ import type { ErrorData, FragDecryptedData } from '../types/events';
21
23
  import type { PlaylistLevelType } from '../types/loader';
22
24
  import type { RationalTimestamp } from '../utils/timescale-conversion';
23
25
 
26
+ let transmuxerInstanceCount: number = 0;
27
+
24
28
  export default class TransmuxerInterface {
25
29
  public error: Error | null = null;
26
30
  private hls: Hls;
27
31
  private id: PlaylistLevelType;
32
+ private instanceNo: number = transmuxerInstanceCount++;
28
33
  private observer: HlsEventEmitter;
29
34
  private frag: MediaFragment | null = null;
30
35
  private part: Part | null = null;
31
36
  private useWorker: boolean;
32
37
  private workerContext: WorkerContext | null = null;
33
- private onwmsg?: (
34
- event: MessageEvent<{ event: string; data?: any } | null>,
35
- ) => void;
36
38
  private transmuxer: Transmuxer | null = null;
37
39
  private onTransmuxComplete: (transmuxResult: TransmuxerResult) => void;
38
40
  private onFlush: (chunkMeta: ChunkMetadata) => void;
@@ -50,11 +52,16 @@ export default class TransmuxerInterface {
50
52
  this.onTransmuxComplete = onTransmuxComplete;
51
53
  this.onFlush = onFlush;
52
54
 
53
- const forwardMessage = (ev, data) => {
55
+ const forwardMessage = (
56
+ ev: Events.ERROR | Events.FRAG_DECRYPTED,
57
+ data: ErrorData | FragDecryptedData,
58
+ ) => {
54
59
  data = data || {};
55
- data.frag = this.frag;
56
- data.id = this.id;
60
+ data.frag = this.frag || undefined;
57
61
  if (ev === Events.ERROR) {
62
+ data = data as ErrorData;
63
+ data.parent = this.id;
64
+ data.part = this.part;
58
65
  this.error = data.error;
59
66
  }
60
67
  this.hls.trigger(ev, data);
@@ -70,6 +77,7 @@ export default class TransmuxerInterface {
70
77
  );
71
78
 
72
79
  if (this.useWorker && typeof Worker !== 'undefined') {
80
+ const logger = this.hls.logger;
73
81
  const canCreateWorker = config.workerPath || hasUMDWorker();
74
82
  if (canCreateWorker) {
75
83
  try {
@@ -80,28 +88,14 @@ export default class TransmuxerInterface {
80
88
  logger.log(`injecting Web Worker for "${id}"`);
81
89
  this.workerContext = injectWorker();
82
90
  }
83
- this.onwmsg = (event) => this.onWorkerMessage(event);
84
91
  const { worker } = this.workerContext;
85
- worker.addEventListener('message', this.onwmsg);
86
- worker.onerror = (event) => {
87
- const error = new Error(
88
- `${event.message} (${event.filename}:${event.lineno})`,
89
- );
90
- config.enableWorker = false;
91
- logger.warn(`Error in "${id}" Web Worker, fallback to inline`);
92
- this.hls.trigger(Events.ERROR, {
93
- type: ErrorTypes.OTHER_ERROR,
94
- details: ErrorDetails.INTERNAL_EXCEPTION,
95
- fatal: false,
96
- event: 'demuxerWorker',
97
- error,
98
- });
99
- };
92
+ worker.addEventListener('message', this.onWorkerMessage);
93
+ worker.addEventListener('error', this.onWorkerError);
100
94
  worker.postMessage({
95
+ instanceNo: this.instanceNo,
101
96
  cmd: 'init',
102
97
  typeSupported: m2tsTypeSupported,
103
- vendor: '',
104
- id: id,
98
+ id,
105
99
  config: JSON.stringify(config),
106
100
  });
107
101
  } catch (err) {
@@ -109,7 +103,7 @@ export default class TransmuxerInterface {
109
103
  `Error setting up "${id}" Web Worker, fallback to inline`,
110
104
  err,
111
105
  );
112
- this.resetWorker();
106
+ this.terminateWorker();
113
107
  this.error = null;
114
108
  this.transmuxer = new Transmuxer(
115
109
  this.observer,
@@ -117,6 +111,7 @@ export default class TransmuxerInterface {
117
111
  config,
118
112
  '',
119
113
  id,
114
+ hls.logger,
120
115
  );
121
116
  }
122
117
  return;
@@ -129,27 +124,46 @@ export default class TransmuxerInterface {
129
124
  config,
130
125
  '',
131
126
  id,
127
+ hls.logger,
132
128
  );
133
129
  }
134
130
 
135
- resetWorker() {
131
+ reset() {
132
+ this.frag = null;
133
+ this.part = null;
136
134
  if (this.workerContext) {
137
- const { worker, objectURL } = this.workerContext;
138
- if (objectURL) {
139
- // revoke the Object URL that was used to create transmuxer worker, so as not to leak it
140
- self.URL.revokeObjectURL(objectURL);
141
- }
142
- worker.removeEventListener('message', this.onwmsg as any);
143
- worker.onerror = null;
144
- worker.terminate();
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: JSON.stringify(config),
148
+ });
149
+ }
150
+ }
151
+
152
+ private terminateWorker() {
153
+ if (this.workerContext) {
154
+ const { worker } = this.workerContext;
145
155
  this.workerContext = null;
156
+ worker.removeEventListener('message', this.onWorkerMessage);
157
+ worker.removeEventListener('error', this.onWorkerError);
158
+ removeWorkerClient(this.hls.config.workerPath);
146
159
  }
147
160
  }
148
161
 
149
162
  destroy() {
150
163
  if (this.workerContext) {
151
- this.resetWorker();
152
- this.onwmsg = undefined;
164
+ this.terminateWorker();
165
+ // @ts-ignore
166
+ this.onWorkerMessage = this.onWorkerError = null;
153
167
  } else {
154
168
  const transmuxer = this.transmuxer;
155
169
  if (transmuxer) {
@@ -162,6 +176,7 @@ export default class TransmuxerInterface {
162
176
  observer.removeAllListeners();
163
177
  }
164
178
  this.frag = null;
179
+ this.part = null;
165
180
  // @ts-ignore
166
181
  this.observer = null;
167
182
  // @ts-ignore
@@ -181,7 +196,7 @@ export default class TransmuxerInterface {
181
196
  defaultInitPTS?: RationalTimestamp,
182
197
  ) {
183
198
  chunkMeta.transmuxing.start = self.performance.now();
184
- const { transmuxer } = this;
199
+ const { instanceNo, transmuxer } = this;
185
200
  const timeOffset = part ? part.start : frag.start;
186
201
  // TODO: push "clear-lead" decrypt data for unencrypted fragments in streams with encrypted ones
187
202
  const decryptdata = frag.decryptdata;
@@ -219,7 +234,8 @@ export default class TransmuxerInterface {
219
234
  initSegmentChange,
220
235
  );
221
236
  if (!contiguous || discontinuity || initSegmentChange) {
222
- logger.log(`[transmuxer-interface, ${frag.type}]: Starting new transmux session for sn: ${chunkMeta.sn} p: ${chunkMeta.part} level: ${chunkMeta.level} id: ${chunkMeta.id}
237
+ this.hls.logger
238
+ .log(`[transmuxer-interface, ${frag.type}]: Starting new transmux session for sn: ${chunkMeta.sn} p: ${chunkMeta.part} level: ${chunkMeta.level} id: ${chunkMeta.id}
223
239
  discontinuity: ${discontinuity}
224
240
  trackSwitch: ${trackSwitch}
225
241
  contiguous: ${contiguous}
@@ -244,6 +260,7 @@ export default class TransmuxerInterface {
244
260
  // post fragment payload as transferable objects for ArrayBuffer (no copy)
245
261
  this.workerContext.worker.postMessage(
246
262
  {
263
+ instanceNo,
247
264
  cmd: 'demux',
248
265
  data,
249
266
  decryptdata,
@@ -260,7 +277,6 @@ export default class TransmuxerInterface {
260
277
  state,
261
278
  );
262
279
  if (isPromise(transmuxResult)) {
263
- transmuxer.async = true;
264
280
  transmuxResult
265
281
  .then((data) => {
266
282
  this.handleTransmuxComplete(data);
@@ -273,7 +289,6 @@ export default class TransmuxerInterface {
273
289
  );
274
290
  });
275
291
  } else {
276
- transmuxer.async = false;
277
292
  this.handleTransmuxComplete(transmuxResult as TransmuxerResult);
278
293
  }
279
294
  }
@@ -281,20 +296,17 @@ export default class TransmuxerInterface {
281
296
 
282
297
  flush(chunkMeta: ChunkMetadata) {
283
298
  chunkMeta.transmuxing.start = self.performance.now();
284
- const { transmuxer } = this;
299
+ const { instanceNo, transmuxer } = this;
285
300
  if (this.workerContext) {
286
301
  1;
287
302
  this.workerContext.worker.postMessage({
303
+ instanceNo,
288
304
  cmd: 'flush',
289
305
  chunkMeta,
290
306
  });
291
307
  } else if (transmuxer) {
292
- let transmuxResult = transmuxer.flush(chunkMeta);
293
- const asyncFlush = isPromise(transmuxResult);
294
- if (asyncFlush || transmuxer.async) {
295
- if (!isPromise(transmuxResult)) {
296
- transmuxResult = Promise.resolve(transmuxResult);
297
- }
308
+ const transmuxResult = transmuxer.flush(chunkMeta);
309
+ if (isPromise(transmuxResult)) {
298
310
  transmuxResult
299
311
  .then((data) => {
300
312
  this.handleFlushResult(data, chunkMeta);
@@ -307,10 +319,7 @@ export default class TransmuxerInterface {
307
319
  );
308
320
  });
309
321
  } else {
310
- this.handleFlushResult(
311
- transmuxResult as Array<TransmuxerResult>,
312
- chunkMeta,
313
- );
322
+ this.handleFlushResult(transmuxResult, chunkMeta);
314
323
  }
315
324
  }
316
325
  }
@@ -329,6 +338,7 @@ export default class TransmuxerInterface {
329
338
  details: ErrorDetails.FRAG_PARSING_ERROR,
330
339
  chunkMeta,
331
340
  frag: this.frag || undefined,
341
+ part: this.part || undefined,
332
342
  fatal: false,
333
343
  error,
334
344
  err: error,
@@ -346,18 +356,16 @@ export default class TransmuxerInterface {
346
356
  this.onFlush(chunkMeta);
347
357
  }
348
358
 
349
- private onWorkerMessage(
350
- event: MessageEvent<{ event: string; data?: any } | null>,
351
- ) {
359
+ private onWorkerMessage = (
360
+ event: MessageEvent<{
361
+ event: string;
362
+ data?: any;
363
+ instanceNo?: number;
364
+ } | null>,
365
+ ) => {
352
366
  const data = event.data;
353
- if (!data?.event) {
354
- logger.warn(
355
- `worker message received with no ${data ? 'event name' : 'data'}`,
356
- );
357
- return;
358
- }
359
367
  const hls = this.hls;
360
- if (!this.hls) {
368
+ if (!hls || !data?.event || data.instanceNo !== this.instanceNo) {
361
369
  return;
362
370
  }
363
371
  switch (data.event) {
@@ -381,26 +389,49 @@ export default class TransmuxerInterface {
381
389
  }
382
390
 
383
391
  // pass logs from the worker thread to the main logger
384
- case 'workerLog':
385
- if (logger[data.data.logType]) {
386
- logger[data.data.logType](data.data.message);
392
+ case 'workerLog': {
393
+ if (hls.logger[data.data.logType]) {
394
+ hls.logger[data.data.logType](data.data.message);
387
395
  }
388
396
  break;
397
+ }
389
398
 
390
399
  default: {
391
400
  data.data = data.data || {};
392
401
  data.data.frag = this.frag;
402
+ data.data.part = this.part;
393
403
  data.data.id = this.id;
394
404
  hls.trigger(data.event as keyof HlsListeners, data.data);
395
405
  break;
396
406
  }
397
407
  }
398
- }
408
+ };
409
+
410
+ private onWorkerError = (event) => {
411
+ if (!this.hls) {
412
+ return;
413
+ }
414
+ const error = new Error(
415
+ `${event.message} (${event.filename}:${event.lineno})`,
416
+ );
417
+ this.hls.config.enableWorker = false;
418
+ this.hls.logger.warn(
419
+ `Error in "${this.id}" Web Worker, fallback to inline`,
420
+ );
421
+ this.hls.trigger(Events.ERROR, {
422
+ type: ErrorTypes.OTHER_ERROR,
423
+ details: ErrorDetails.INTERNAL_EXCEPTION,
424
+ fatal: false,
425
+ event: 'demuxerWorker',
426
+ error,
427
+ });
428
+ };
399
429
 
400
430
  private configureTransmuxer(config: TransmuxConfig) {
401
- const { transmuxer } = this;
431
+ const { instanceNo, transmuxer } = this;
402
432
  if (this.workerContext) {
403
433
  this.workerContext.worker.postMessage({
434
+ instanceNo,
404
435
  cmd: 'configure',
405
436
  config,
406
437
  });