hls.js 1.5.10-0.canary.10320 → 1.5.10-0.canary.10326
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/dist/hls.js +61 -47
- package/dist/hls.js.map +1 -1
- package/dist/hls.light.js +63 -48
- package/dist/hls.light.js.map +1 -1
- package/dist/hls.light.min.js +1 -1
- package/dist/hls.light.min.js.map +1 -1
- package/dist/hls.light.mjs +60 -46
- package/dist/hls.light.mjs.map +1 -1
- package/dist/hls.min.js +1 -1
- package/dist/hls.min.js.map +1 -1
- package/dist/hls.mjs +58 -45
- package/dist/hls.mjs.map +1 -1
- package/dist/hls.worker.js +1 -1
- package/dist/hls.worker.js.map +1 -1
- package/package.json +1 -1
- package/src/controller/base-stream-controller.ts +4 -3
- package/src/controller/fragment-finders.ts +34 -3
- package/src/controller/stream-controller.ts +7 -1
- package/src/demux/transmuxer-interface.ts +26 -16
- package/src/demux/transmuxer-worker.ts +1 -1
- package/src/demux/tsdemuxer.ts +31 -21
- package/src/utils/xhr-loader.ts +5 -5
package/package.json
CHANGED
@@ -438,7 +438,7 @@ export default class BaseStreamController
|
|
438
438
|
if (this.state === State.STOPPED || this.state === State.ERROR) {
|
439
439
|
return;
|
440
440
|
}
|
441
|
-
this.warn(reason);
|
441
|
+
this.warn(`Frag error: ${reason?.message || reason}`);
|
442
442
|
this.resetFragmentLoading(frag);
|
443
443
|
});
|
444
444
|
}
|
@@ -1398,7 +1398,7 @@ export default class BaseStreamController
|
|
1398
1398
|
let { fragPrevious } = this;
|
1399
1399
|
let { fragments, endSN } = levelDetails;
|
1400
1400
|
const { fragmentHint } = levelDetails;
|
1401
|
-
const
|
1401
|
+
const { maxFragLookUpTolerance } = config;
|
1402
1402
|
const partList = levelDetails.partList;
|
1403
1403
|
|
1404
1404
|
const loadingParts = !!(
|
@@ -1414,7 +1414,8 @@ export default class BaseStreamController
|
|
1414
1414
|
|
1415
1415
|
let frag;
|
1416
1416
|
if (bufferEnd < end) {
|
1417
|
-
const lookupTolerance =
|
1417
|
+
const lookupTolerance =
|
1418
|
+
bufferEnd > end - maxFragLookUpTolerance ? 0 : maxFragLookUpTolerance;
|
1418
1419
|
// Remove the tolerance if it would put the bufferEnd past the actual end of stream
|
1419
1420
|
// Uses buffer and sequence number to calculate switch segment (required if using EXT-X-DISCONTINUITY-SEQUENCE)
|
1420
1421
|
frag = findFragmentByPTS(
|
@@ -58,6 +58,7 @@ export function findFragmentByPTS(
|
|
58
58
|
fragments: Array<Fragment>,
|
59
59
|
bufferEnd: number = 0,
|
60
60
|
maxFragLookUpTolerance: number = 0,
|
61
|
+
nextFragLookupTolerance: number = 0.005,
|
61
62
|
): Fragment | null {
|
62
63
|
let fragNext: Fragment | null = null;
|
63
64
|
if (fragPrevious) {
|
@@ -76,9 +77,17 @@ export function findFragmentByPTS(
|
|
76
77
|
// Prefer the next fragment if it's within tolerance
|
77
78
|
if (
|
78
79
|
fragNext &&
|
79
|
-
(!fragPrevious || fragPrevious.level === fragNext.level) &&
|
80
|
-
|
81
|
-
|
80
|
+
(((!fragPrevious || fragPrevious.level === fragNext.level) &&
|
81
|
+
fragmentWithinToleranceTest(
|
82
|
+
bufferEnd,
|
83
|
+
maxFragLookUpTolerance,
|
84
|
+
fragNext,
|
85
|
+
) === 0) ||
|
86
|
+
fragmentWithinFastStartSwitch(
|
87
|
+
fragNext,
|
88
|
+
fragPrevious,
|
89
|
+
Math.min(nextFragLookupTolerance, maxFragLookUpTolerance),
|
90
|
+
))
|
82
91
|
) {
|
83
92
|
return fragNext;
|
84
93
|
}
|
@@ -94,6 +103,28 @@ export function findFragmentByPTS(
|
|
94
103
|
return fragNext;
|
95
104
|
}
|
96
105
|
|
106
|
+
function fragmentWithinFastStartSwitch(
|
107
|
+
fragNext: Fragment,
|
108
|
+
fragPrevious: Fragment | null,
|
109
|
+
nextFragLookupTolerance: number,
|
110
|
+
): boolean {
|
111
|
+
if (
|
112
|
+
fragPrevious &&
|
113
|
+
fragPrevious.start === 0 &&
|
114
|
+
fragPrevious.level < fragNext.level &&
|
115
|
+
(fragPrevious.endPTS || 0) > 0
|
116
|
+
) {
|
117
|
+
const firstDuration = fragPrevious.tagList.reduce((duration, tag) => {
|
118
|
+
if (tag[0] === 'INF') {
|
119
|
+
duration += parseFloat(tag[1]);
|
120
|
+
}
|
121
|
+
return duration;
|
122
|
+
}, nextFragLookupTolerance);
|
123
|
+
return fragNext.start <= firstDuration;
|
124
|
+
}
|
125
|
+
return false;
|
126
|
+
}
|
127
|
+
|
97
128
|
/**
|
98
129
|
* The test function used by the findFragmentBySn's BinarySearch to look for the best match to the current buffer conditions.
|
99
130
|
* @param candidate - The fragment to test
|
@@ -1268,7 +1268,13 @@ export default class StreamController
|
|
1268
1268
|
// In the case that AAC and HE-AAC audio codecs are signalled in manifest,
|
1269
1269
|
// force HE-AAC, as it seems that most browsers prefers it.
|
1270
1270
|
// don't force HE-AAC if mono stream, or in Firefox
|
1271
|
-
|
1271
|
+
const audioMetadata = audio.metadata;
|
1272
|
+
if (
|
1273
|
+
audioMetadata &&
|
1274
|
+
'channelCount' in audioMetadata &&
|
1275
|
+
(audioMetadata.channelCount || 1) !== 1 &&
|
1276
|
+
ua.indexOf('firefox') === -1
|
1277
|
+
) {
|
1272
1278
|
audioCodec = 'mp4a.40.5';
|
1273
1279
|
}
|
1274
1280
|
}
|
@@ -17,7 +17,7 @@ import { Fragment, Part } from '../loader/fragment';
|
|
17
17
|
import { getM2TSSupportedAudioTypes } from '../utils/codecs';
|
18
18
|
import type { ChunkMetadata, TransmuxerResult } from '../types/transmuxer';
|
19
19
|
import type Hls from '../hls';
|
20
|
-
import type { HlsEventEmitter } from '../events';
|
20
|
+
import type { HlsEventEmitter, HlsListeners } from '../events';
|
21
21
|
import type { PlaylistLevelType } from '../types/loader';
|
22
22
|
import type { RationalTimestamp } from '../utils/timescale-conversion';
|
23
23
|
|
@@ -30,7 +30,9 @@ export default class TransmuxerInterface {
|
|
30
30
|
private part: Part | null = null;
|
31
31
|
private useWorker: boolean;
|
32
32
|
private workerContext: WorkerContext | null = null;
|
33
|
-
private onwmsg?:
|
33
|
+
private onwmsg?: (
|
34
|
+
event: MessageEvent<{ event: string; data?: any } | null>,
|
35
|
+
) => void;
|
34
36
|
private transmuxer: Transmuxer | null = null;
|
35
37
|
private onTransmuxComplete: (transmuxResult: TransmuxerResult) => void;
|
36
38
|
private onFlush: (chunkMeta: ChunkMetadata) => void;
|
@@ -67,9 +69,6 @@ export default class TransmuxerInterface {
|
|
67
69
|
config.preferManagedMediaSource,
|
68
70
|
);
|
69
71
|
|
70
|
-
// navigator.vendor is not always available in Web Worker
|
71
|
-
// refer to https://developer.mozilla.org/en-US/docs/Web/API/WorkerGlobalScope/navigator
|
72
|
-
const vendor = navigator.vendor;
|
73
72
|
if (this.useWorker && typeof Worker !== 'undefined') {
|
74
73
|
const canCreateWorker = config.workerPath || hasUMDWorker();
|
75
74
|
if (canCreateWorker) {
|
@@ -81,9 +80,9 @@ export default class TransmuxerInterface {
|
|
81
80
|
logger.log(`injecting Web Worker for "${id}"`);
|
82
81
|
this.workerContext = injectWorker();
|
83
82
|
}
|
84
|
-
this.onwmsg = (
|
83
|
+
this.onwmsg = (event) => this.onWorkerMessage(event);
|
85
84
|
const { worker } = this.workerContext;
|
86
|
-
worker.addEventListener('message', this.onwmsg
|
85
|
+
worker.addEventListener('message', this.onwmsg);
|
87
86
|
worker.onerror = (event) => {
|
88
87
|
const error = new Error(
|
89
88
|
`${event.message} (${event.filename}:${event.lineno})`,
|
@@ -101,7 +100,7 @@ export default class TransmuxerInterface {
|
|
101
100
|
worker.postMessage({
|
102
101
|
cmd: 'init',
|
103
102
|
typeSupported: m2tsTypeSupported,
|
104
|
-
vendor:
|
103
|
+
vendor: '',
|
105
104
|
id: id,
|
106
105
|
config: JSON.stringify(config),
|
107
106
|
});
|
@@ -116,7 +115,7 @@ export default class TransmuxerInterface {
|
|
116
115
|
this.observer,
|
117
116
|
m2tsTypeSupported,
|
118
117
|
config,
|
119
|
-
|
118
|
+
'',
|
120
119
|
id,
|
121
120
|
);
|
122
121
|
}
|
@@ -128,12 +127,12 @@ export default class TransmuxerInterface {
|
|
128
127
|
this.observer,
|
129
128
|
m2tsTypeSupported,
|
130
129
|
config,
|
131
|
-
|
130
|
+
'',
|
132
131
|
id,
|
133
132
|
);
|
134
133
|
}
|
135
134
|
|
136
|
-
resetWorker()
|
135
|
+
resetWorker() {
|
137
136
|
if (this.workerContext) {
|
138
137
|
const { worker, objectURL } = this.workerContext;
|
139
138
|
if (objectURL) {
|
@@ -147,7 +146,7 @@ export default class TransmuxerInterface {
|
|
147
146
|
}
|
148
147
|
}
|
149
148
|
|
150
|
-
destroy()
|
149
|
+
destroy() {
|
151
150
|
if (this.workerContext) {
|
152
151
|
this.resetWorker();
|
153
152
|
this.onwmsg = undefined;
|
@@ -180,7 +179,7 @@ export default class TransmuxerInterface {
|
|
180
179
|
accurateTimeOffset: boolean,
|
181
180
|
chunkMeta: ChunkMetadata,
|
182
181
|
defaultInitPTS?: RationalTimestamp,
|
183
|
-
)
|
182
|
+
) {
|
184
183
|
chunkMeta.transmuxing.start = self.performance.now();
|
185
184
|
const { transmuxer } = this;
|
186
185
|
const timeOffset = part ? part.start : frag.start;
|
@@ -347,9 +346,20 @@ export default class TransmuxerInterface {
|
|
347
346
|
this.onFlush(chunkMeta);
|
348
347
|
}
|
349
348
|
|
350
|
-
private onWorkerMessage(
|
351
|
-
|
349
|
+
private onWorkerMessage(
|
350
|
+
event: MessageEvent<{ event: string; data?: any } | null>,
|
351
|
+
) {
|
352
|
+
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
|
+
}
|
352
359
|
const hls = this.hls;
|
360
|
+
if (!this.hls) {
|
361
|
+
return;
|
362
|
+
}
|
353
363
|
switch (data.event) {
|
354
364
|
case 'init': {
|
355
365
|
const objectURL = this.workerContext?.objectURL;
|
@@ -381,7 +391,7 @@ export default class TransmuxerInterface {
|
|
381
391
|
data.data = data.data || {};
|
382
392
|
data.data.frag = this.frag;
|
383
393
|
data.data.id = this.id;
|
384
|
-
hls.trigger(data.event, data.data);
|
394
|
+
hls.trigger(data.event as keyof HlsListeners, data.data);
|
385
395
|
break;
|
386
396
|
}
|
387
397
|
}
|
package/src/demux/tsdemuxer.ts
CHANGED
@@ -374,6 +374,7 @@ class TSDemuxer implements Demuxer {
|
|
374
374
|
offset,
|
375
375
|
this.typeSupported,
|
376
376
|
isSampleAes,
|
377
|
+
this.observer,
|
377
378
|
);
|
378
379
|
|
379
380
|
// only update track id if track PID found while parsing PMT
|
@@ -422,16 +423,12 @@ class TSDemuxer implements Demuxer {
|
|
422
423
|
}
|
423
424
|
|
424
425
|
if (tsPacketErrors > 0) {
|
425
|
-
|
426
|
-
|
426
|
+
emitParsingError(
|
427
|
+
this.observer,
|
428
|
+
new Error(
|
429
|
+
`Found ${tsPacketErrors} TS packet/s that do not start with 0x47`,
|
430
|
+
),
|
427
431
|
);
|
428
|
-
this.observer.emit(Events.ERROR, Events.ERROR, {
|
429
|
-
type: ErrorTypes.MEDIA_ERROR,
|
430
|
-
details: ErrorDetails.FRAG_PARSING_ERROR,
|
431
|
-
fatal: false,
|
432
|
-
error,
|
433
|
-
reason: error.message,
|
434
|
-
});
|
435
432
|
}
|
436
433
|
|
437
434
|
videoTrack.pesData = videoData;
|
@@ -628,16 +625,7 @@ class TSDemuxer implements Demuxer {
|
|
628
625
|
} else {
|
629
626
|
reason = 'No ADTS header found in AAC PES';
|
630
627
|
}
|
631
|
-
|
632
|
-
logger.warn(`parsing error: ${reason}`);
|
633
|
-
this.observer.emit(Events.ERROR, Events.ERROR, {
|
634
|
-
type: ErrorTypes.MEDIA_ERROR,
|
635
|
-
details: ErrorDetails.FRAG_PARSING_ERROR,
|
636
|
-
fatal: false,
|
637
|
-
levelRetry: recoverable,
|
638
|
-
error,
|
639
|
-
reason,
|
640
|
-
});
|
628
|
+
emitParsingError(this.observer, new Error(reason), recoverable);
|
641
629
|
if (!recoverable) {
|
642
630
|
return;
|
643
631
|
}
|
@@ -768,6 +756,7 @@ function parsePMT(
|
|
768
756
|
offset: number,
|
769
757
|
typeSupported: TypeSupported,
|
770
758
|
isSampleAes: boolean,
|
759
|
+
observer: HlsEventEmitter,
|
771
760
|
) {
|
772
761
|
const result = {
|
773
762
|
audioPid: -1,
|
@@ -897,7 +886,8 @@ function parsePMT(
|
|
897
886
|
case 0xc2: // SAMPLE-AES EC3
|
898
887
|
/* falls through */
|
899
888
|
case 0x87:
|
900
|
-
|
889
|
+
emitParsingError(observer, new Error('Unsupported EC-3 in M2TS found'));
|
890
|
+
return result;
|
901
891
|
|
902
892
|
case 0x24: // ITU-T Rec. H.265 and ISO/IEC 23008-2 (HEVC)
|
903
893
|
if (__USE_M2TS_ADVANCED_CODECS__) {
|
@@ -907,7 +897,11 @@ function parsePMT(
|
|
907
897
|
logger.log('HEVC in M2TS found');
|
908
898
|
}
|
909
899
|
} else {
|
910
|
-
|
900
|
+
emitParsingError(
|
901
|
+
observer,
|
902
|
+
new Error('Unsupported HEVC in M2TS found'),
|
903
|
+
);
|
904
|
+
return result;
|
911
905
|
}
|
912
906
|
break;
|
913
907
|
|
@@ -922,6 +916,22 @@ function parsePMT(
|
|
922
916
|
return result;
|
923
917
|
}
|
924
918
|
|
919
|
+
function emitParsingError(
|
920
|
+
observer: HlsEventEmitter,
|
921
|
+
error: Error,
|
922
|
+
levelRetry?: boolean,
|
923
|
+
) {
|
924
|
+
logger.warn(`parsing error: ${error.message}`);
|
925
|
+
observer.emit(Events.ERROR, Events.ERROR, {
|
926
|
+
type: ErrorTypes.MEDIA_ERROR,
|
927
|
+
details: ErrorDetails.FRAG_PARSING_ERROR,
|
928
|
+
fatal: false,
|
929
|
+
levelRetry,
|
930
|
+
error,
|
931
|
+
reason: error.message,
|
932
|
+
});
|
933
|
+
}
|
934
|
+
|
925
935
|
function logEncryptedSamplesFoundInUnencryptedStream(type: string) {
|
926
936
|
logger.log(`${type} with AES-128-CBC encryption found in unencrypted stream`);
|
927
937
|
}
|
package/src/utils/xhr-loader.ts
CHANGED
@@ -40,8 +40,6 @@ class XhrLoader implements Loader<LoaderContext> {
|
|
40
40
|
this.config = null;
|
41
41
|
this.context = null;
|
42
42
|
this.xhrSetup = null;
|
43
|
-
// @ts-ignore
|
44
|
-
this.stats = null;
|
45
43
|
}
|
46
44
|
|
47
45
|
abortInternal() {
|
@@ -100,15 +98,16 @@ class XhrLoader implements Loader<LoaderContext> {
|
|
100
98
|
if (xhrSetup) {
|
101
99
|
Promise.resolve()
|
102
100
|
.then(() => {
|
103
|
-
if (this.stats.aborted) return;
|
101
|
+
if (this.loader !== xhr || this.stats.aborted) return;
|
104
102
|
return xhrSetup(xhr, context.url);
|
105
103
|
})
|
106
104
|
.catch((error: Error) => {
|
105
|
+
if (this.loader !== xhr || this.stats.aborted) return;
|
107
106
|
xhr.open('GET', context.url, true);
|
108
107
|
return xhrSetup(xhr, context.url);
|
109
108
|
})
|
110
109
|
.then(() => {
|
111
|
-
if (this.stats.aborted) return;
|
110
|
+
if (this.loader !== xhr || this.stats.aborted) return;
|
112
111
|
this.openAndSendXhr(xhr, context, config);
|
113
112
|
})
|
114
113
|
.catch((error: Error) => {
|
@@ -263,7 +262,8 @@ class XhrLoader implements Loader<LoaderContext> {
|
|
263
262
|
}
|
264
263
|
|
265
264
|
loadtimeout() {
|
266
|
-
|
265
|
+
if (!this.config) return;
|
266
|
+
const retryConfig = this.config.loadPolicy.timeoutRetry;
|
267
267
|
const retryCount = this.stats.retry;
|
268
268
|
if (shouldRetry(retryConfig, retryCount, true)) {
|
269
269
|
this.retry(retryConfig);
|