hls.js 1.5.14-0.canary.10517 → 1.5.14
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/README.md +3 -4
- package/dist/hls-demo.js +38 -41
- package/dist/hls-demo.js.map +1 -1
- package/dist/hls.js +2903 -4542
- package/dist/hls.js.d.ts +112 -186
- package/dist/hls.js.map +1 -1
- package/dist/hls.light.js +2284 -3295
- 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 +1804 -2817
- 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 +4652 -6293
- 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 +38 -38
- package/src/config.ts +2 -5
- package/src/controller/abr-controller.ts +25 -39
- package/src/controller/audio-stream-controller.ts +136 -156
- package/src/controller/audio-track-controller.ts +1 -1
- package/src/controller/base-playlist-controller.ts +10 -27
- package/src/controller/base-stream-controller.ts +107 -263
- package/src/controller/buffer-controller.ts +98 -252
- package/src/controller/buffer-operation-queue.ts +19 -16
- package/src/controller/cap-level-controller.ts +2 -3
- package/src/controller/cmcd-controller.ts +14 -51
- package/src/controller/content-steering-controller.ts +15 -29
- package/src/controller/eme-controller.ts +23 -10
- package/src/controller/error-controller.ts +22 -28
- package/src/controller/fps-controller.ts +3 -8
- package/src/controller/fragment-finders.ts +16 -44
- package/src/controller/fragment-tracker.ts +25 -58
- package/src/controller/gap-controller.ts +16 -43
- package/src/controller/id3-track-controller.ts +35 -45
- package/src/controller/latency-controller.ts +13 -18
- package/src/controller/level-controller.ts +19 -37
- package/src/controller/stream-controller.ts +83 -100
- package/src/controller/subtitle-stream-controller.ts +47 -35
- package/src/controller/subtitle-track-controller.ts +3 -5
- package/src/controller/timeline-controller.ts +22 -20
- package/src/crypt/aes-crypto.ts +2 -21
- package/src/crypt/decrypter.ts +16 -32
- package/src/crypt/fast-aes-key.ts +5 -28
- package/src/demux/audio/aacdemuxer.ts +5 -5
- package/src/demux/audio/ac3-demuxer.ts +4 -5
- package/src/demux/audio/adts.ts +4 -9
- package/src/demux/audio/base-audio-demuxer.ts +14 -16
- package/src/demux/audio/mp3demuxer.ts +3 -4
- package/src/demux/audio/mpegaudio.ts +1 -1
- package/src/demux/id3.ts +411 -0
- package/src/demux/inject-worker.ts +4 -38
- package/src/demux/mp4demuxer.ts +7 -7
- package/src/demux/sample-aes.ts +0 -2
- package/src/demux/transmuxer-interface.ts +83 -106
- package/src/demux/transmuxer-worker.ts +77 -111
- package/src/demux/transmuxer.ts +22 -46
- package/src/demux/tsdemuxer.ts +62 -122
- package/src/demux/video/avc-video-parser.ts +121 -210
- package/src/demux/video/base-video-parser.ts +2 -135
- package/src/demux/video/exp-golomb.ts +208 -0
- package/src/errors.ts +0 -2
- package/src/events.ts +1 -8
- package/src/exports-named.ts +1 -1
- package/src/hls.ts +48 -97
- package/src/loader/date-range.ts +5 -71
- package/src/loader/fragment-loader.ts +21 -23
- package/src/loader/fragment.ts +4 -8
- package/src/loader/key-loader.ts +1 -3
- package/src/loader/level-details.ts +6 -6
- package/src/loader/level-key.ts +9 -10
- package/src/loader/m3u8-parser.ts +144 -138
- package/src/loader/playlist-loader.ts +7 -5
- package/src/remux/mp4-generator.ts +1 -196
- package/src/remux/mp4-remuxer.ts +84 -55
- package/src/remux/passthrough-remuxer.ts +8 -23
- package/src/task-loop.ts +2 -5
- package/src/types/component-api.ts +1 -3
- package/src/types/demuxer.ts +0 -3
- package/src/types/events.ts +6 -19
- package/src/types/fragment-tracker.ts +2 -2
- package/src/types/general.ts +6 -0
- package/src/types/media-playlist.ts +1 -9
- package/src/types/remuxer.ts +1 -1
- package/src/utils/attr-list.ts +9 -96
- package/src/utils/buffer-helper.ts +31 -12
- package/src/utils/cea-608-parser.ts +3 -1
- package/src/utils/codecs.ts +5 -34
- package/src/utils/discontinuities.ts +47 -21
- package/src/utils/fetch-loader.ts +1 -1
- package/src/utils/hdr.ts +7 -4
- package/src/utils/imsc1-ttml-parser.ts +1 -1
- package/src/utils/keysystem-util.ts +6 -1
- package/src/utils/level-helper.ts +44 -71
- package/src/utils/logger.ts +23 -58
- package/src/utils/mp4-tools.ts +3 -5
- package/src/utils/rendition-helper.ts +74 -100
- package/src/utils/variable-substitution.ts +19 -0
- package/src/utils/webvtt-parser.ts +12 -2
- package/src/crypt/decrypter-aes-mode.ts +0 -4
- package/src/demux/video/hevc-video-parser.ts +0 -749
- package/src/utils/encryption-methods-util.ts +0 -21
- package/src/utils/hash.ts +0 -10
- package/src/utils/utf8-utils.ts +0 -18
- package/src/version.ts +0 -1
@@ -3,17 +3,16 @@
|
|
3
3
|
*/
|
4
4
|
|
5
5
|
import { logger } from './logger';
|
6
|
-
import {
|
7
|
-
import {
|
8
|
-
import type { Fragment, MediaFragment, Part } from '../loader/fragment';
|
9
|
-
import type { LevelDetails } from '../loader/level-details';
|
6
|
+
import { Fragment, Part } from '../loader/fragment';
|
7
|
+
import { LevelDetails } from '../loader/level-details';
|
10
8
|
import type { Level } from '../types/level';
|
9
|
+
import { DateRange } from '../loader/date-range';
|
11
10
|
|
12
11
|
type FragmentIntersection = (oldFrag: Fragment, newFrag: Fragment) => void;
|
13
12
|
type PartIntersection = (oldPart: Part, newPart: Part) => void;
|
14
13
|
|
15
14
|
export function updatePTS(
|
16
|
-
fragments:
|
15
|
+
fragments: Fragment[],
|
17
16
|
fromIdx: number,
|
18
17
|
toIdx: number,
|
19
18
|
): void {
|
@@ -22,7 +21,7 @@ export function updatePTS(
|
|
22
21
|
updateFromToPTS(fragFrom, fragTo);
|
23
22
|
}
|
24
23
|
|
25
|
-
function updateFromToPTS(fragFrom:
|
24
|
+
function updateFromToPTS(fragFrom: Fragment, fragTo: Fragment) {
|
26
25
|
const fragToPTS = fragTo.startPTS as number;
|
27
26
|
// if we know startPTS[toIdx]
|
28
27
|
if (Number.isFinite(fragToPTS)) {
|
@@ -56,7 +55,7 @@ function updateFromToPTS(fragFrom: MediaFragment, fragTo: MediaFragment) {
|
|
56
55
|
|
57
56
|
export function updateFragPTSDTS(
|
58
57
|
details: LevelDetails | undefined,
|
59
|
-
frag:
|
58
|
+
frag: Fragment,
|
60
59
|
startPTS: number,
|
61
60
|
endPTS: number,
|
62
61
|
startDTS: number,
|
@@ -83,11 +82,11 @@ export function updateFragPTSDTS(
|
|
83
82
|
|
84
83
|
maxStartPTS = Math.max(startPTS, fragStartPts);
|
85
84
|
startPTS = Math.min(startPTS, fragStartPts);
|
86
|
-
startDTS = Math.min(startDTS, frag.startDTS
|
85
|
+
startDTS = Math.min(startDTS, frag.startDTS);
|
87
86
|
|
88
87
|
minEndPTS = Math.min(endPTS, fragEndPts);
|
89
88
|
endPTS = Math.max(endPTS, fragEndPts);
|
90
|
-
endDTS = Math.max(endDTS, frag.endDTS
|
89
|
+
endDTS = Math.max(endDTS, frag.endDTS);
|
91
90
|
}
|
92
91
|
|
93
92
|
const drift = startPTS - frag.start;
|
@@ -102,7 +101,7 @@ export function updateFragPTSDTS(
|
|
102
101
|
frag.minEndPTS = minEndPTS;
|
103
102
|
frag.endDTS = endDTS;
|
104
103
|
|
105
|
-
const sn = frag.sn;
|
104
|
+
const sn = frag.sn as number; // 'initSegment'
|
106
105
|
// exit if sn out of range
|
107
106
|
if (!details || sn < details.startSN || sn > details.endSN) {
|
108
107
|
return 0;
|
@@ -197,10 +196,10 @@ export function mergeDetails(
|
|
197
196
|
},
|
198
197
|
);
|
199
198
|
|
200
|
-
const fragmentsToCheck = newDetails.fragmentHint
|
201
|
-
? newDetails.fragments.concat(newDetails.fragmentHint)
|
202
|
-
: newDetails.fragments;
|
203
199
|
if (currentInitSegment) {
|
200
|
+
const fragmentsToCheck = newDetails.fragmentHint
|
201
|
+
? newDetails.fragments.concat(newDetails.fragmentHint)
|
202
|
+
: newDetails.fragments;
|
204
203
|
fragmentsToCheck.forEach((frag) => {
|
205
204
|
if (
|
206
205
|
frag &&
|
@@ -221,30 +220,14 @@ export function mergeDetails(
|
|
221
220
|
for (let i = newDetails.skippedSegments; i--; ) {
|
222
221
|
newDetails.fragments.shift();
|
223
222
|
}
|
224
|
-
newDetails.startSN = newDetails.fragments[0].sn;
|
223
|
+
newDetails.startSN = newDetails.fragments[0].sn as number;
|
225
224
|
newDetails.startCC = newDetails.fragments[0].cc;
|
226
|
-
} else {
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
);
|
232
|
-
}
|
233
|
-
const programDateTimes = oldDetails.fragments.filter(
|
234
|
-
(frag) => frag.rawProgramDateTime,
|
225
|
+
} else if (newDetails.canSkipDateRanges) {
|
226
|
+
newDetails.dateRanges = mergeDateRanges(
|
227
|
+
oldDetails.dateRanges,
|
228
|
+
newDetails.dateRanges,
|
229
|
+
newDetails.recentlyRemovedDateranges,
|
235
230
|
);
|
236
|
-
if (oldDetails.hasProgramDateTime && !newDetails.hasProgramDateTime) {
|
237
|
-
for (let i = 1; i < fragmentsToCheck.length; i++) {
|
238
|
-
if (fragmentsToCheck[i].programDateTime === null) {
|
239
|
-
assignProgramDateTime(
|
240
|
-
fragmentsToCheck[i],
|
241
|
-
fragmentsToCheck[i - 1],
|
242
|
-
programDateTimes,
|
243
|
-
);
|
244
|
-
}
|
245
|
-
}
|
246
|
-
}
|
247
|
-
mapDateRanges(programDateTimes, newDetails);
|
248
231
|
}
|
249
232
|
}
|
250
233
|
|
@@ -310,38 +293,27 @@ export function mergeDetails(
|
|
310
293
|
|
311
294
|
function mergeDateRanges(
|
312
295
|
oldDateRanges: Record<string, DateRange>,
|
313
|
-
|
296
|
+
deltaDateRanges: Record<string, DateRange>,
|
297
|
+
recentlyRemovedDateranges: string[] | undefined,
|
314
298
|
): Record<string, DateRange> {
|
315
|
-
const { dateRanges: deltaDateRanges, recentlyRemovedDateranges } = newDetails;
|
316
299
|
const dateRanges = Object.assign({}, oldDateRanges);
|
317
300
|
if (recentlyRemovedDateranges) {
|
318
301
|
recentlyRemovedDateranges.forEach((id) => {
|
319
302
|
delete dateRanges[id];
|
320
303
|
});
|
321
304
|
}
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
305
|
+
Object.keys(deltaDateRanges).forEach((id) => {
|
306
|
+
const dateRange = new DateRange(deltaDateRanges[id].attr, dateRanges[id]);
|
307
|
+
if (dateRange.isValid) {
|
308
|
+
dateRanges[id] = dateRange;
|
309
|
+
} else {
|
310
|
+
logger.warn(
|
311
|
+
`Ignoring invalid Playlist Delta Update DATERANGE tag: "${JSON.stringify(
|
312
|
+
deltaDateRanges[id].attr,
|
313
|
+
)}"`,
|
330
314
|
);
|
331
|
-
|
332
|
-
|
333
|
-
if (!mergedDateRange) {
|
334
|
-
dateRange.tagOrder += mergeCount;
|
335
|
-
}
|
336
|
-
} else {
|
337
|
-
logger.warn(
|
338
|
-
`Ignoring invalid Playlist Delta Update DATERANGE tag: "${JSON.stringify(
|
339
|
-
deltaDateRanges[id].attr,
|
340
|
-
)}"`,
|
341
|
-
);
|
342
|
-
}
|
343
|
-
});
|
344
|
-
}
|
315
|
+
}
|
316
|
+
});
|
345
317
|
return dateRanges;
|
346
318
|
}
|
347
319
|
|
@@ -394,7 +366,7 @@ export function mapFragmentIntersection(
|
|
394
366
|
for (let i = start; i <= end; i++) {
|
395
367
|
const oldFrag = oldFrags[delta + i];
|
396
368
|
let newFrag = newFrags[i];
|
397
|
-
if (skippedSegments && !newFrag &&
|
369
|
+
if (skippedSegments && !newFrag && i < skippedSegments) {
|
398
370
|
// Fill in skipped segments in delta playlist
|
399
371
|
newFrag = newDetails.fragments[i] = oldFrag;
|
400
372
|
}
|
@@ -461,37 +433,38 @@ export function computeReloadInterval(
|
|
461
433
|
}
|
462
434
|
|
463
435
|
export function getFragmentWithSN(
|
464
|
-
|
436
|
+
level: Level,
|
465
437
|
sn: number,
|
466
438
|
fragCurrent: Fragment | null,
|
467
|
-
):
|
468
|
-
if (!details) {
|
439
|
+
): Fragment | null {
|
440
|
+
if (!level?.details) {
|
469
441
|
return null;
|
470
442
|
}
|
471
|
-
|
472
|
-
|
443
|
+
const levelDetails = level.details;
|
444
|
+
let fragment: Fragment | undefined =
|
445
|
+
levelDetails.fragments[sn - levelDetails.startSN];
|
473
446
|
if (fragment) {
|
474
447
|
return fragment;
|
475
448
|
}
|
476
|
-
fragment =
|
449
|
+
fragment = levelDetails.fragmentHint;
|
477
450
|
if (fragment && fragment.sn === sn) {
|
478
451
|
return fragment;
|
479
452
|
}
|
480
|
-
if (sn <
|
481
|
-
return fragCurrent
|
453
|
+
if (sn < levelDetails.startSN && fragCurrent && fragCurrent.sn === sn) {
|
454
|
+
return fragCurrent;
|
482
455
|
}
|
483
456
|
return null;
|
484
457
|
}
|
485
458
|
|
486
459
|
export function getPartWith(
|
487
|
-
|
460
|
+
level: Level,
|
488
461
|
sn: number,
|
489
462
|
partIndex: number,
|
490
463
|
): Part | null {
|
491
|
-
if (!details) {
|
464
|
+
if (!level?.details) {
|
492
465
|
return null;
|
493
466
|
}
|
494
|
-
return findPart(details
|
467
|
+
return findPart(level.details?.partList, sn, partIndex);
|
495
468
|
}
|
496
469
|
|
497
470
|
export function findPart(
|
package/src/utils/logger.ts
CHANGED
@@ -11,25 +11,6 @@ export interface ILogger {
|
|
11
11
|
error: ILogFunction;
|
12
12
|
}
|
13
13
|
|
14
|
-
export class Logger implements ILogger {
|
15
|
-
trace: ILogFunction;
|
16
|
-
debug: ILogFunction;
|
17
|
-
log: ILogFunction;
|
18
|
-
warn: ILogFunction;
|
19
|
-
info: ILogFunction;
|
20
|
-
error: ILogFunction;
|
21
|
-
|
22
|
-
constructor(label: string, logger: ILogger) {
|
23
|
-
const lb = `[${label}]:`;
|
24
|
-
this.trace = noop;
|
25
|
-
this.debug = logger.debug.bind(null, lb);
|
26
|
-
this.log = logger.log.bind(null, lb);
|
27
|
-
this.warn = logger.warn.bind(null, lb);
|
28
|
-
this.info = logger.info.bind(null, lb);
|
29
|
-
this.error = logger.error.bind(null, lb);
|
30
|
-
}
|
31
|
-
}
|
32
|
-
|
33
14
|
const noop: ILogFunction = function () {};
|
34
15
|
|
35
16
|
const fakeLogger: ILogger = {
|
@@ -41,9 +22,7 @@ const fakeLogger: ILogger = {
|
|
41
22
|
error: noop,
|
42
23
|
};
|
43
24
|
|
44
|
-
|
45
|
-
return Object.assign({}, fakeLogger);
|
46
|
-
}
|
25
|
+
let exportedLogger: ILogger = fakeLogger;
|
47
26
|
|
48
27
|
// let lastCallTime;
|
49
28
|
// function formatMsgWithTimeInfo(type, msg) {
|
@@ -54,37 +33,33 @@ function createLogger() {
|
|
54
33
|
// return msg;
|
55
34
|
// }
|
56
35
|
|
57
|
-
function consolePrintFn(type: string
|
36
|
+
function consolePrintFn(type: string): ILogFunction {
|
58
37
|
const func: ILogFunction = self.console[type];
|
59
|
-
|
60
|
-
|
61
|
-
|
38
|
+
if (func) {
|
39
|
+
return func.bind(self.console, `[${type}] >`);
|
40
|
+
}
|
41
|
+
return noop;
|
62
42
|
}
|
63
43
|
|
64
|
-
function
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
)
|
69
|
-
|
70
|
-
|
71
|
-
|
44
|
+
function exportLoggerFunctions(
|
45
|
+
debugConfig: boolean | ILogger,
|
46
|
+
...functions: string[]
|
47
|
+
): void {
|
48
|
+
functions.forEach(function (type) {
|
49
|
+
exportedLogger[type] = debugConfig[type]
|
50
|
+
? debugConfig[type].bind(debugConfig)
|
51
|
+
: consolePrintFn(type);
|
52
|
+
});
|
72
53
|
}
|
73
54
|
|
74
|
-
|
75
|
-
|
76
|
-
export function enableLogs(
|
77
|
-
debugConfig: boolean | ILogger,
|
78
|
-
context: string,
|
79
|
-
id?: string | undefined,
|
80
|
-
): ILogger {
|
55
|
+
export function enableLogs(debugConfig: boolean | ILogger, id: string): void {
|
81
56
|
// check that console is available
|
82
|
-
const newLogger = createLogger();
|
83
57
|
if (
|
84
58
|
(typeof console === 'object' && debugConfig === true) ||
|
85
59
|
typeof debugConfig === 'object'
|
86
60
|
) {
|
87
|
-
|
61
|
+
exportLoggerFunctions(
|
62
|
+
debugConfig,
|
88
63
|
// Remove out from list here to hard-disable a log-level
|
89
64
|
// 'trace',
|
90
65
|
'debug',
|
@@ -92,29 +67,19 @@ export function enableLogs(
|
|
92
67
|
'info',
|
93
68
|
'warn',
|
94
69
|
'error',
|
95
|
-
|
96
|
-
keys.forEach((key) => {
|
97
|
-
newLogger[key] = getLoggerFn(key, debugConfig, id);
|
98
|
-
});
|
70
|
+
);
|
99
71
|
// Some browsers don't allow to use bind on console object anyway
|
100
72
|
// fallback to default if needed
|
101
73
|
try {
|
102
|
-
|
103
|
-
`Debug logs enabled for "${
|
74
|
+
exportedLogger.log(
|
75
|
+
`Debug logs enabled for "${id}" in hls.js version ${__VERSION__}`,
|
104
76
|
);
|
105
77
|
} catch (e) {
|
106
|
-
|
107
|
-
return createLogger();
|
78
|
+
exportedLogger = fakeLogger;
|
108
79
|
}
|
109
|
-
// global exported logger uses the same functions as new logger without `id`
|
110
|
-
keys.forEach((key) => {
|
111
|
-
exportedLogger[key] = getLoggerFn(key, debugConfig);
|
112
|
-
});
|
113
80
|
} else {
|
114
|
-
|
115
|
-
Object.assign(exportedLogger, newLogger);
|
81
|
+
exportedLogger = fakeLogger;
|
116
82
|
}
|
117
|
-
return newLogger;
|
118
83
|
}
|
119
84
|
|
120
85
|
export const logger: ILogger = exportedLogger;
|
package/src/utils/mp4-tools.ts
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
import { ElementaryStreamTypes } from '../loader/fragment';
|
2
2
|
import { sliceUint8 } from './typed-array';
|
3
|
-
import { utf8ArrayToStr } from '
|
3
|
+
import { utf8ArrayToStr } from '../demux/id3';
|
4
4
|
import { logger } from '../utils/logger';
|
5
5
|
import Hex from './hex';
|
6
6
|
import type { PassthroughTrack, UserdataSample } from '../types/demuxer';
|
@@ -327,7 +327,7 @@ function parseStsd(stsd: Uint8Array): { codec: string; encrypted: boolean } {
|
|
327
327
|
case 'mp4a': {
|
328
328
|
const codecBox = findBox(sampleEntries, [fourCC])[0];
|
329
329
|
const esdsBox = findBox(codecBox.subarray(28), ['esds'])[0];
|
330
|
-
if (esdsBox && esdsBox.length >
|
330
|
+
if (esdsBox && esdsBox.length > 12) {
|
331
331
|
let i = 4;
|
332
332
|
// ES Descriptor tag
|
333
333
|
if (esdsBox[i++] !== 0x03) {
|
@@ -478,9 +478,7 @@ function parseStsd(stsd: Uint8Array): { codec: string; encrypted: boolean } {
|
|
478
478
|
|
479
479
|
function skipBERInteger(bytes: Uint8Array, i: number): number {
|
480
480
|
const limit = i + 5;
|
481
|
-
while (bytes[i++] & 0x80 && i < limit) {
|
482
|
-
/* do nothing */
|
483
|
-
}
|
481
|
+
while (bytes[i++] & 0x80 && i < limit) {}
|
484
482
|
return i;
|
485
483
|
}
|
486
484
|
|
@@ -13,7 +13,6 @@ export type CodecSetTier = {
|
|
13
13
|
minBitrate: number;
|
14
14
|
minHeight: number;
|
15
15
|
minFramerate: number;
|
16
|
-
minIndex: number;
|
17
16
|
maxScore: number;
|
18
17
|
videoRanges: Record<string, number>;
|
19
18
|
channels: Record<string, number>;
|
@@ -33,7 +32,6 @@ type StartParameters = {
|
|
33
32
|
preferHDR: boolean;
|
34
33
|
minFramerate: number;
|
35
34
|
minBitrate: number;
|
36
|
-
minIndex: number;
|
37
35
|
};
|
38
36
|
|
39
37
|
export function getStartCodecTier(
|
@@ -46,15 +44,13 @@ export function getStartCodecTier(
|
|
46
44
|
const codecSets = Object.keys(codecTiers);
|
47
45
|
const channelsPreference = audioPreference?.channels;
|
48
46
|
const audioCodecPreference = audioPreference?.audioCodec;
|
49
|
-
const videoCodecPreference = videoPreference?.videoCodec;
|
50
47
|
const preferStereo = channelsPreference && parseInt(channelsPreference) === 2;
|
51
48
|
// Use first level set to determine stereo, and minimum resolution and framerate
|
52
|
-
let hasStereo =
|
49
|
+
let hasStereo = true;
|
53
50
|
let hasCurrentVideoRange = false;
|
54
51
|
let minHeight = Infinity;
|
55
52
|
let minFramerate = Infinity;
|
56
53
|
let minBitrate = Infinity;
|
57
|
-
let minIndex = Infinity;
|
58
54
|
let selectedScore = 0;
|
59
55
|
let videoRanges: Array<VideoRange> = [];
|
60
56
|
|
@@ -65,7 +61,7 @@ export function getStartCodecTier(
|
|
65
61
|
|
66
62
|
for (let i = codecSets.length; i--; ) {
|
67
63
|
const tier = codecTiers[codecSets[i]];
|
68
|
-
hasStereo
|
64
|
+
hasStereo = tier.channels[2] > 0;
|
69
65
|
minHeight = Math.min(minHeight, tier.minHeight);
|
70
66
|
minFramerate = Math.min(minFramerate, tier.minFramerate);
|
71
67
|
minBitrate = Math.min(minBitrate, tier.minBitrate);
|
@@ -74,6 +70,7 @@ export function getStartCodecTier(
|
|
74
70
|
);
|
75
71
|
if (matchingVideoRanges.length > 0) {
|
76
72
|
hasCurrentVideoRange = true;
|
73
|
+
videoRanges = matchingVideoRanges;
|
77
74
|
}
|
78
75
|
}
|
79
76
|
minHeight = Number.isFinite(minHeight) ? minHeight : 0;
|
@@ -85,8 +82,8 @@ export function getStartCodecTier(
|
|
85
82
|
// If there are no variants with matching preference, set currentVideoRange to undefined
|
86
83
|
if (!hasCurrentVideoRange) {
|
87
84
|
currentVideoRange = undefined;
|
85
|
+
videoRanges = [];
|
88
86
|
}
|
89
|
-
const hasMultipleSets = codecSets.length > 1;
|
90
87
|
const codecSet = codecSets.reduce(
|
91
88
|
(selected: string | undefined, candidate: string) => {
|
92
89
|
// Remove candiates which do not meet bitrate, default audio, stereo or channels preference, 1080p or lower, 30fps or lower, or SDR/HDR selection if present
|
@@ -94,99 +91,80 @@ export function getStartCodecTier(
|
|
94
91
|
if (candidate === selected) {
|
95
92
|
return selected;
|
96
93
|
}
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
`audio codec preference "${audioCodecPreference}" not found`,
|
124
|
-
);
|
125
|
-
return selected;
|
126
|
-
}
|
127
|
-
if (channelsPreference && !preferStereo) {
|
128
|
-
if (!candidateTier.channels[channelsPreference]) {
|
129
|
-
logStartCodecCandidateIgnored(
|
130
|
-
candidate,
|
131
|
-
`no renditions with ${channelsPreference} channel sound found (channels options: ${Object.keys(
|
132
|
-
candidateTier.channels,
|
133
|
-
)})`,
|
134
|
-
);
|
135
|
-
return selected;
|
136
|
-
}
|
137
|
-
} else if (
|
138
|
-
(!audioCodecPreference || preferStereo) &&
|
139
|
-
hasStereo &&
|
140
|
-
candidateTier.channels['2'] === 0
|
141
|
-
) {
|
142
|
-
logStartCodecCandidateIgnored(
|
143
|
-
candidate,
|
144
|
-
`no renditions with stereo sound found`,
|
145
|
-
);
|
146
|
-
return selected;
|
147
|
-
}
|
148
|
-
if (candidateTier.minHeight > maxHeight) {
|
149
|
-
logStartCodecCandidateIgnored(
|
150
|
-
candidate,
|
151
|
-
`min resolution of ${candidateTier.minHeight} > maximum of ${maxHeight}`,
|
152
|
-
);
|
153
|
-
return selected;
|
154
|
-
}
|
155
|
-
if (candidateTier.minFramerate > maxFramerate) {
|
156
|
-
logStartCodecCandidateIgnored(
|
157
|
-
candidate,
|
158
|
-
`min framerate of ${candidateTier.minFramerate} > maximum of ${maxFramerate}`,
|
159
|
-
);
|
160
|
-
return selected;
|
161
|
-
}
|
162
|
-
if (
|
163
|
-
!videoRanges.some((range) => candidateTier.videoRanges[range] > 0)
|
164
|
-
) {
|
165
|
-
logStartCodecCandidateIgnored(
|
166
|
-
candidate,
|
167
|
-
`no variants with VIDEO-RANGE of ${JSON.stringify(
|
168
|
-
videoRanges,
|
169
|
-
)} found`,
|
170
|
-
);
|
171
|
-
return selected;
|
172
|
-
}
|
173
|
-
if (
|
174
|
-
videoCodecPreference &&
|
175
|
-
candidate.indexOf(videoCodecPreference.substring(0, 4)) % 5 !== 0
|
176
|
-
) {
|
177
|
-
logStartCodecCandidateIgnored(
|
178
|
-
candidate,
|
179
|
-
`video codec preference "${videoCodecPreference}" not found`,
|
180
|
-
);
|
181
|
-
return selected;
|
182
|
-
}
|
183
|
-
if (candidateTier.maxScore < selectedScore) {
|
94
|
+
if (candidateTier.minBitrate > currentBw) {
|
95
|
+
logStartCodecCandidateIgnored(
|
96
|
+
candidate,
|
97
|
+
`min bitrate of ${candidateTier.minBitrate} > current estimate of ${currentBw}`,
|
98
|
+
);
|
99
|
+
return selected;
|
100
|
+
}
|
101
|
+
if (!candidateTier.hasDefaultAudio) {
|
102
|
+
logStartCodecCandidateIgnored(
|
103
|
+
candidate,
|
104
|
+
`no renditions with default or auto-select sound found`,
|
105
|
+
);
|
106
|
+
return selected;
|
107
|
+
}
|
108
|
+
if (
|
109
|
+
audioCodecPreference &&
|
110
|
+
candidate.indexOf(audioCodecPreference.substring(0, 4)) % 5 !== 0
|
111
|
+
) {
|
112
|
+
logStartCodecCandidateIgnored(
|
113
|
+
candidate,
|
114
|
+
`audio codec preference "${audioCodecPreference}" not found`,
|
115
|
+
);
|
116
|
+
return selected;
|
117
|
+
}
|
118
|
+
if (channelsPreference && !preferStereo) {
|
119
|
+
if (!candidateTier.channels[channelsPreference]) {
|
184
120
|
logStartCodecCandidateIgnored(
|
185
121
|
candidate,
|
186
|
-
`
|
122
|
+
`no renditions with ${channelsPreference} channel sound found (channels options: ${Object.keys(
|
123
|
+
candidateTier.channels,
|
124
|
+
)})`,
|
187
125
|
);
|
188
126
|
return selected;
|
189
127
|
}
|
128
|
+
} else if (
|
129
|
+
(!audioCodecPreference || preferStereo) &&
|
130
|
+
hasStereo &&
|
131
|
+
candidateTier.channels['2'] === 0
|
132
|
+
) {
|
133
|
+
logStartCodecCandidateIgnored(
|
134
|
+
candidate,
|
135
|
+
`no renditions with stereo sound found`,
|
136
|
+
);
|
137
|
+
return selected;
|
138
|
+
}
|
139
|
+
if (candidateTier.minHeight > maxHeight) {
|
140
|
+
logStartCodecCandidateIgnored(
|
141
|
+
candidate,
|
142
|
+
`min resolution of ${candidateTier.minHeight} > maximum of ${maxHeight}`,
|
143
|
+
);
|
144
|
+
return selected;
|
145
|
+
}
|
146
|
+
if (candidateTier.minFramerate > maxFramerate) {
|
147
|
+
logStartCodecCandidateIgnored(
|
148
|
+
candidate,
|
149
|
+
`min framerate of ${candidateTier.minFramerate} > maximum of ${maxFramerate}`,
|
150
|
+
);
|
151
|
+
return selected;
|
152
|
+
}
|
153
|
+
if (!videoRanges.some((range) => candidateTier.videoRanges[range] > 0)) {
|
154
|
+
logStartCodecCandidateIgnored(
|
155
|
+
candidate,
|
156
|
+
`no variants with VIDEO-RANGE of ${JSON.stringify(
|
157
|
+
videoRanges,
|
158
|
+
)} found`,
|
159
|
+
);
|
160
|
+
return selected;
|
161
|
+
}
|
162
|
+
if (candidateTier.maxScore < selectedScore) {
|
163
|
+
logStartCodecCandidateIgnored(
|
164
|
+
candidate,
|
165
|
+
`max score of ${candidateTier.maxScore} < selected max of ${selectedScore}`,
|
166
|
+
);
|
167
|
+
return selected;
|
190
168
|
}
|
191
169
|
// Remove candiates with less preferred codecs or more errors
|
192
170
|
if (
|
@@ -197,7 +175,6 @@ export function getStartCodecTier(
|
|
197
175
|
) {
|
198
176
|
return selected;
|
199
177
|
}
|
200
|
-
minIndex = candidateTier.minIndex;
|
201
178
|
selectedScore = candidateTier.maxScore;
|
202
179
|
return candidate;
|
203
180
|
},
|
@@ -209,7 +186,6 @@ export function getStartCodecTier(
|
|
209
186
|
preferHDR,
|
210
187
|
minFramerate,
|
211
188
|
minBitrate,
|
212
|
-
minIndex,
|
213
189
|
};
|
214
190
|
}
|
215
191
|
|
@@ -267,7 +243,7 @@ export function getCodecTiers(
|
|
267
243
|
): Record<string, CodecSetTier> {
|
268
244
|
return levels
|
269
245
|
.slice(minAutoLevel, maxAutoLevel + 1)
|
270
|
-
.reduce((tiers: Record<string, CodecSetTier>, level
|
246
|
+
.reduce((tiers: Record<string, CodecSetTier>, level) => {
|
271
247
|
if (!level.codecSet) {
|
272
248
|
return tiers;
|
273
249
|
}
|
@@ -278,7 +254,6 @@ export function getCodecTiers(
|
|
278
254
|
minBitrate: Infinity,
|
279
255
|
minHeight: Infinity,
|
280
256
|
minFramerate: Infinity,
|
281
|
-
minIndex: index,
|
282
257
|
maxScore: 0,
|
283
258
|
videoRanges: { SDR: 0 },
|
284
259
|
channels: { '2': 0 },
|
@@ -290,7 +265,6 @@ export function getCodecTiers(
|
|
290
265
|
const lesserWidthOrHeight = Math.min(level.height, level.width);
|
291
266
|
tier.minHeight = Math.min(tier.minHeight, lesserWidthOrHeight);
|
292
267
|
tier.minFramerate = Math.min(tier.minFramerate, level.frameRate);
|
293
|
-
tier.minIndex = Math.min(tier.minIndex, index);
|
294
268
|
tier.maxScore = Math.max(tier.maxScore, level.score);
|
295
269
|
tier.fragmentError += level.fragmentError;
|
296
270
|
tier.videoRanges[level.videoRange] =
|
@@ -9,6 +9,25 @@ export function hasVariableReferences(str: string): boolean {
|
|
9
9
|
return VARIABLE_REPLACEMENT_REGEX.test(str);
|
10
10
|
}
|
11
11
|
|
12
|
+
export function substituteVariablesInAttributes(
|
13
|
+
parsed: Pick<
|
14
|
+
ParsedMultivariantPlaylist | LevelDetails,
|
15
|
+
'variableList' | 'hasVariableRefs' | 'playlistParsingError'
|
16
|
+
>,
|
17
|
+
attr: AttrList,
|
18
|
+
attributeNames: string[],
|
19
|
+
) {
|
20
|
+
if (parsed.variableList !== null || parsed.hasVariableRefs) {
|
21
|
+
for (let i = attributeNames.length; i--; ) {
|
22
|
+
const name = attributeNames[i];
|
23
|
+
const value = attr[name];
|
24
|
+
if (value) {
|
25
|
+
attr[name] = substituteVariables(parsed, value);
|
26
|
+
}
|
27
|
+
}
|
28
|
+
}
|
29
|
+
}
|
30
|
+
|
12
31
|
export function substituteVariables(
|
13
32
|
parsed: Pick<
|
14
33
|
ParsedMultivariantPlaylist | LevelDetails,
|