hls.js 1.5.14-0.canary.10559 → 1.5.15
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 +2911 -4558
- package/dist/hls.js.d.ts +112 -186
- package/dist/hls.js.map +1 -1
- package/dist/hls.light.js +2291 -3311
- 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 +1813 -2835
- 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 +4707 -6356
- 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 +42 -42
- 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 +97 -250
- 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 +15 -21
- 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 +8 -17
- 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 +65 -131
- package/src/demux/video/avc-video-parser.ts +156 -219
- package/src/demux/video/base-video-parser.ts +9 -133
- 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 +1 -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/dist/hls.d.mts +0 -3179
- package/dist/hls.d.ts +0 -3179
- package/src/crypt/decrypter-aes-mode.ts +0 -4
- package/src/demux/video/hevc-video-parser.ts +0 -718
- 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
@@ -1,6 +1,6 @@
|
|
1
1
|
import { buildAbsoluteURL } from 'url-toolkit';
|
2
2
|
import { DateRange } from './date-range';
|
3
|
-
import { Fragment,
|
3
|
+
import { Fragment, Part } from './fragment';
|
4
4
|
import { LevelDetails } from './level-details';
|
5
5
|
import { LevelKey } from './level-key';
|
6
6
|
import { AttrList } from '../utils/attr-list';
|
@@ -10,6 +10,7 @@ import {
|
|
10
10
|
hasVariableReferences,
|
11
11
|
importVariableDefinition,
|
12
12
|
substituteVariables,
|
13
|
+
substituteVariablesInAttributes,
|
13
14
|
} from '../utils/variable-substitution';
|
14
15
|
import { isCodecType } from '../utils/codecs';
|
15
16
|
import type { CodecType } from '../utils/codecs';
|
@@ -119,7 +120,21 @@ export default class M3U8Parser {
|
|
119
120
|
while ((result = MASTER_PLAYLIST_REGEX.exec(string)) != null) {
|
120
121
|
if (result[1]) {
|
121
122
|
// '#EXT-X-STREAM-INF' is found, parse level tag in group 1
|
122
|
-
const attrs = new AttrList(result[1]
|
123
|
+
const attrs = new AttrList(result[1]) as LevelAttributes;
|
124
|
+
if (__USE_VARIABLE_SUBSTITUTION__) {
|
125
|
+
substituteVariablesInAttributes(parsed, attrs, [
|
126
|
+
'CODECS',
|
127
|
+
'SUPPLEMENTAL-CODECS',
|
128
|
+
'ALLOWED-CPC',
|
129
|
+
'PATHWAY-ID',
|
130
|
+
'STABLE-VARIANT-ID',
|
131
|
+
'AUDIO',
|
132
|
+
'VIDEO',
|
133
|
+
'SUBTITLES',
|
134
|
+
'CLOSED-CAPTIONS',
|
135
|
+
'NAME',
|
136
|
+
]);
|
137
|
+
}
|
123
138
|
const uri = __USE_VARIABLE_SUBSTITUTION__
|
124
139
|
? substituteVariables(parsed, result[2])
|
125
140
|
: result[2];
|
@@ -151,7 +166,15 @@ export default class M3U8Parser {
|
|
151
166
|
switch (tag) {
|
152
167
|
case 'SESSION-DATA': {
|
153
168
|
// #EXT-X-SESSION-DATA
|
154
|
-
const sessionAttrs = new AttrList(attributes
|
169
|
+
const sessionAttrs = new AttrList(attributes);
|
170
|
+
if (__USE_VARIABLE_SUBSTITUTION__) {
|
171
|
+
substituteVariablesInAttributes(parsed, sessionAttrs, [
|
172
|
+
'DATA-ID',
|
173
|
+
'LANGUAGE',
|
174
|
+
'VALUE',
|
175
|
+
'URI',
|
176
|
+
]);
|
177
|
+
}
|
155
178
|
const dataId = sessionAttrs['DATA-ID'];
|
156
179
|
if (dataId) {
|
157
180
|
if (parsed.sessionData === null) {
|
@@ -179,14 +202,26 @@ export default class M3U8Parser {
|
|
179
202
|
case 'DEFINE': {
|
180
203
|
// #EXT-X-DEFINE
|
181
204
|
if (__USE_VARIABLE_SUBSTITUTION__) {
|
182
|
-
const variableAttributes = new AttrList(attributes
|
205
|
+
const variableAttributes = new AttrList(attributes);
|
206
|
+
substituteVariablesInAttributes(parsed, variableAttributes, [
|
207
|
+
'NAME',
|
208
|
+
'VALUE',
|
209
|
+
'QUERYPARAM',
|
210
|
+
]);
|
183
211
|
addVariableDefinition(parsed, variableAttributes, baseurl);
|
184
212
|
}
|
185
213
|
break;
|
186
214
|
}
|
187
215
|
case 'CONTENT-STEERING': {
|
188
216
|
// #EXT-X-CONTENT-STEERING
|
189
|
-
const contentSteeringAttributes = new AttrList(attributes
|
217
|
+
const contentSteeringAttributes = new AttrList(attributes);
|
218
|
+
if (__USE_VARIABLE_SUBSTITUTION__) {
|
219
|
+
substituteVariablesInAttributes(
|
220
|
+
parsed,
|
221
|
+
contentSteeringAttributes,
|
222
|
+
['SERVER-URI', 'PATHWAY-ID'],
|
223
|
+
);
|
224
|
+
}
|
190
225
|
parsed.contentSteering = {
|
191
226
|
uri: M3U8Parser.resolve(
|
192
227
|
contentSteeringAttributes['SERVER-URI'],
|
@@ -243,13 +278,26 @@ export default class M3U8Parser {
|
|
243
278
|
let id = 0;
|
244
279
|
MASTER_PLAYLIST_MEDIA_REGEX.lastIndex = 0;
|
245
280
|
while ((result = MASTER_PLAYLIST_MEDIA_REGEX.exec(string)) !== null) {
|
246
|
-
const attrs = new AttrList(result[1]
|
281
|
+
const attrs = new AttrList(result[1]) as MediaAttributes;
|
247
282
|
const type = attrs.TYPE;
|
248
283
|
if (type) {
|
249
284
|
const groups: (typeof groupsByType)[keyof typeof groupsByType] =
|
250
285
|
groupsByType[type];
|
251
286
|
const medias: MediaPlaylist[] = results[type] || [];
|
252
287
|
results[type] = medias;
|
288
|
+
if (__USE_VARIABLE_SUBSTITUTION__) {
|
289
|
+
substituteVariablesInAttributes(parsed, attrs, [
|
290
|
+
'URI',
|
291
|
+
'GROUP-ID',
|
292
|
+
'LANGUAGE',
|
293
|
+
'ASSOC-LANGUAGE',
|
294
|
+
'STABLE-RENDITION-ID',
|
295
|
+
'NAME',
|
296
|
+
'INSTREAM-ID',
|
297
|
+
'CHARACTERISTICS',
|
298
|
+
'CHANNELS',
|
299
|
+
]);
|
300
|
+
}
|
253
301
|
const lang = attrs.LANGUAGE;
|
254
302
|
const assocLang = attrs['ASSOC-LANGUAGE'];
|
255
303
|
const channels = attrs.CHANNELS;
|
@@ -307,7 +355,6 @@ export default class M3U8Parser {
|
|
307
355
|
): LevelDetails {
|
308
356
|
const level = new LevelDetails(baseurl);
|
309
357
|
const fragments: M3U8ParserFragments = level.fragments;
|
310
|
-
const programDateTimes: MediaFragment[] = [];
|
311
358
|
// The most recent init segment seen (applies to all subsequent segments)
|
312
359
|
let currentInitSegment: Fragment | null = null;
|
313
360
|
let currentSN = 0;
|
@@ -373,11 +420,7 @@ export default class M3U8Parser {
|
|
373
420
|
frag.relurl = __USE_VARIABLE_SUBSTITUTION__
|
374
421
|
? substituteVariables(level, uri)
|
375
422
|
: uri;
|
376
|
-
assignProgramDateTime(
|
377
|
-
frag as MediaFragment,
|
378
|
-
prevFrag as MediaFragment,
|
379
|
-
programDateTimes,
|
380
|
-
);
|
423
|
+
assignProgramDateTime(frag, prevFrag);
|
381
424
|
prevFrag = frag;
|
382
425
|
totalduration += frag.duration;
|
383
426
|
currentSN++;
|
@@ -425,19 +468,19 @@ export default class M3U8Parser {
|
|
425
468
|
currentSN = level.startSN = parseInt(value1);
|
426
469
|
break;
|
427
470
|
case 'SKIP': {
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
471
|
+
const skipAttrs = new AttrList(value1);
|
472
|
+
if (__USE_VARIABLE_SUBSTITUTION__) {
|
473
|
+
substituteVariablesInAttributes(level, skipAttrs, [
|
474
|
+
'RECENTLY-REMOVED-DATERANGES',
|
475
|
+
]);
|
432
476
|
}
|
433
|
-
const skipAttrs = new AttrList(value1, level);
|
434
477
|
const skippedSegments =
|
435
478
|
skipAttrs.decimalInteger('SKIPPED-SEGMENTS');
|
436
479
|
if (Number.isFinite(skippedSegments)) {
|
437
|
-
level.skippedSegments
|
480
|
+
level.skippedSegments = skippedSegments;
|
438
481
|
// This will result in fragments[] containing undefined values, which we will fill in with `mergeDetails`
|
439
482
|
for (let i = skippedSegments; i--; ) {
|
440
|
-
fragments.
|
483
|
+
fragments.unshift(null);
|
441
484
|
}
|
442
485
|
currentSN += skippedSegments;
|
443
486
|
}
|
@@ -445,9 +488,8 @@ export default class M3U8Parser {
|
|
445
488
|
'RECENTLY-REMOVED-DATERANGES',
|
446
489
|
);
|
447
490
|
if (recentlyRemovedDateranges) {
|
448
|
-
level.recentlyRemovedDateranges =
|
449
|
-
|
450
|
-
).concat(recentlyRemovedDateranges.split('\t'));
|
491
|
+
level.recentlyRemovedDateranges =
|
492
|
+
recentlyRemovedDateranges.split('\t');
|
451
493
|
}
|
452
494
|
break;
|
453
495
|
}
|
@@ -480,13 +522,27 @@ export default class M3U8Parser {
|
|
480
522
|
frag.tagList.push([tag, value1]);
|
481
523
|
break;
|
482
524
|
case 'DATERANGE': {
|
483
|
-
const dateRangeAttr = new AttrList(value1
|
525
|
+
const dateRangeAttr = new AttrList(value1);
|
526
|
+
if (__USE_VARIABLE_SUBSTITUTION__) {
|
527
|
+
substituteVariablesInAttributes(level, dateRangeAttr, [
|
528
|
+
'ID',
|
529
|
+
'CLASS',
|
530
|
+
'START-DATE',
|
531
|
+
'END-DATE',
|
532
|
+
'SCTE35-CMD',
|
533
|
+
'SCTE35-OUT',
|
534
|
+
'SCTE35-IN',
|
535
|
+
]);
|
536
|
+
substituteVariablesInAttributes(
|
537
|
+
level,
|
538
|
+
dateRangeAttr,
|
539
|
+
dateRangeAttr.clientAttrs,
|
540
|
+
);
|
541
|
+
}
|
484
542
|
const dateRange = new DateRange(
|
485
543
|
dateRangeAttr,
|
486
544
|
level.dateRanges[dateRangeAttr.ID],
|
487
|
-
level.dateRangeTagCount,
|
488
545
|
);
|
489
|
-
level.dateRangeTagCount++;
|
490
546
|
if (dateRange.isValid || level.skippedSegments) {
|
491
547
|
level.dateRanges[dateRange.id] = dateRange;
|
492
548
|
} else {
|
@@ -498,7 +554,13 @@ export default class M3U8Parser {
|
|
498
554
|
}
|
499
555
|
case 'DEFINE': {
|
500
556
|
if (__USE_VARIABLE_SUBSTITUTION__) {
|
501
|
-
const variableAttributes = new AttrList(value1
|
557
|
+
const variableAttributes = new AttrList(value1);
|
558
|
+
substituteVariablesInAttributes(level, variableAttributes, [
|
559
|
+
'NAME',
|
560
|
+
'VALUE',
|
561
|
+
'IMPORT',
|
562
|
+
'QUERYPARAM',
|
563
|
+
]);
|
502
564
|
if ('IMPORT' in variableAttributes) {
|
503
565
|
importVariableDefinition(
|
504
566
|
level,
|
@@ -538,7 +600,13 @@ export default class M3U8Parser {
|
|
538
600
|
level.startTimeOffset = parseStartTimeOffset(value1);
|
539
601
|
break;
|
540
602
|
case 'MAP': {
|
541
|
-
const mapAttrs = new AttrList(value1
|
603
|
+
const mapAttrs = new AttrList(value1);
|
604
|
+
if (__USE_VARIABLE_SUBSTITUTION__) {
|
605
|
+
substituteVariablesInAttributes(level, mapAttrs, [
|
606
|
+
'BYTERANGE',
|
607
|
+
'URI',
|
608
|
+
]);
|
609
|
+
}
|
542
610
|
if (frag.duration) {
|
543
611
|
// Initial segment tag is after segment duration tag.
|
544
612
|
// #EXTINF: 6.0
|
@@ -567,7 +635,6 @@ export default class M3U8Parser {
|
|
567
635
|
currentInitSegment = frag;
|
568
636
|
createNextFrag = true;
|
569
637
|
}
|
570
|
-
currentInitSegment.cc = discontinuityCounter;
|
571
638
|
break;
|
572
639
|
}
|
573
640
|
case 'SERVER-CONTROL': {
|
@@ -600,10 +667,16 @@ export default class M3U8Parser {
|
|
600
667
|
const previousFragmentPart =
|
601
668
|
currentPart > 0 ? partList[partList.length - 1] : undefined;
|
602
669
|
const index = currentPart++;
|
603
|
-
const partAttrs = new AttrList(value1
|
670
|
+
const partAttrs = new AttrList(value1);
|
671
|
+
if (__USE_VARIABLE_SUBSTITUTION__) {
|
672
|
+
substituteVariablesInAttributes(level, partAttrs, [
|
673
|
+
'BYTERANGE',
|
674
|
+
'URI',
|
675
|
+
]);
|
676
|
+
}
|
604
677
|
const part = new Part(
|
605
678
|
partAttrs,
|
606
|
-
frag
|
679
|
+
frag,
|
607
680
|
baseurl,
|
608
681
|
index,
|
609
682
|
previousFragmentPart,
|
@@ -613,12 +686,20 @@ export default class M3U8Parser {
|
|
613
686
|
break;
|
614
687
|
}
|
615
688
|
case 'PRELOAD-HINT': {
|
616
|
-
const preloadHintAttrs = new AttrList(value1
|
689
|
+
const preloadHintAttrs = new AttrList(value1);
|
690
|
+
if (__USE_VARIABLE_SUBSTITUTION__) {
|
691
|
+
substituteVariablesInAttributes(level, preloadHintAttrs, ['URI']);
|
692
|
+
}
|
617
693
|
level.preloadHint = preloadHintAttrs;
|
618
694
|
break;
|
619
695
|
}
|
620
696
|
case 'RENDITION-REPORT': {
|
621
|
-
const renditionReportAttrs = new AttrList(value1
|
697
|
+
const renditionReportAttrs = new AttrList(value1);
|
698
|
+
if (__USE_VARIABLE_SUBSTITUTION__) {
|
699
|
+
substituteVariablesInAttributes(level, renditionReportAttrs, [
|
700
|
+
'URI',
|
701
|
+
]);
|
702
|
+
}
|
622
703
|
level.renditionReports = level.renditionReports || [];
|
623
704
|
level.renditionReports.push(renditionReportAttrs);
|
624
705
|
break;
|
@@ -633,16 +714,12 @@ export default class M3U8Parser {
|
|
633
714
|
fragments.pop();
|
634
715
|
totalduration -= prevFrag.duration;
|
635
716
|
if (level.partList) {
|
636
|
-
level.fragmentHint = prevFrag
|
717
|
+
level.fragmentHint = prevFrag;
|
637
718
|
}
|
638
719
|
} else if (level.partList) {
|
639
|
-
assignProgramDateTime(
|
640
|
-
frag as MediaFragment,
|
641
|
-
prevFrag as MediaFragment,
|
642
|
-
programDateTimes,
|
643
|
-
);
|
720
|
+
assignProgramDateTime(frag, prevFrag);
|
644
721
|
frag.cc = discontinuityCounter;
|
645
|
-
level.fragmentHint = frag
|
722
|
+
level.fragmentHint = frag;
|
646
723
|
if (levelkeys) {
|
647
724
|
setFragLevelKeys(frag, levelkeys, level);
|
648
725
|
}
|
@@ -661,21 +738,6 @@ export default class M3U8Parser {
|
|
661
738
|
if (firstFragment) {
|
662
739
|
level.startCC = firstFragment.cc;
|
663
740
|
}
|
664
|
-
/**
|
665
|
-
* Backfill any missing PDT values
|
666
|
-
* "If the first EXT-X-PROGRAM-DATE-TIME tag in a Playlist appears after
|
667
|
-
* one or more Media Segment URIs, the client SHOULD extrapolate
|
668
|
-
* backward from that tag (using EXTINF durations and/or media
|
669
|
-
* timestamps) to associate dates with those segments."
|
670
|
-
* We have already extrapolated forward, but all fragments up to the first instance of PDT do not have their PDTs
|
671
|
-
* computed.
|
672
|
-
*/
|
673
|
-
if (firstPdtIndex > 0) {
|
674
|
-
backfillProgramDateTimes(fragments, firstPdtIndex);
|
675
|
-
if (firstFragment) {
|
676
|
-
programDateTimes.unshift(firstFragment as MediaFragment);
|
677
|
-
}
|
678
|
-
}
|
679
741
|
} else {
|
680
742
|
level.endSN = 0;
|
681
743
|
level.startCC = 0;
|
@@ -684,83 +746,23 @@ export default class M3U8Parser {
|
|
684
746
|
totalduration += level.fragmentHint.duration;
|
685
747
|
}
|
686
748
|
level.totalduration = totalduration;
|
687
|
-
if (programDateTimes.length && level.dateRangeTagCount && firstFragment) {
|
688
|
-
mapDateRanges(programDateTimes, level);
|
689
|
-
}
|
690
|
-
|
691
749
|
level.endCC = discontinuityCounter;
|
692
750
|
|
693
|
-
|
694
|
-
|
695
|
-
|
696
|
-
|
697
|
-
|
698
|
-
|
699
|
-
|
700
|
-
|
701
|
-
|
702
|
-
|
703
|
-
|
704
|
-
const playlistEnd = details.live ? Infinity : details.totalduration;
|
705
|
-
const dateRangeIds = Object.keys(details.dateRanges);
|
706
|
-
for (let i = dateRangeIds.length; i--; ) {
|
707
|
-
const dateRange = details.dateRanges[dateRangeIds[i]];
|
708
|
-
const startDateTime = dateRange.startDate.getTime();
|
709
|
-
dateRange.tagAnchor = lastProgramDateTime;
|
710
|
-
for (let j = programDateTimeCount; j--; ) {
|
711
|
-
const fragIndex = findFragmentWithStartDate(
|
712
|
-
details,
|
713
|
-
startDateTime,
|
714
|
-
programDateTimes,
|
715
|
-
j,
|
716
|
-
playlistEnd,
|
717
|
-
);
|
718
|
-
if (fragIndex !== -1) {
|
719
|
-
dateRange.tagAnchor = details.fragments[fragIndex];
|
720
|
-
break;
|
721
|
-
}
|
751
|
+
/**
|
752
|
+
* Backfill any missing PDT values
|
753
|
+
* "If the first EXT-X-PROGRAM-DATE-TIME tag in a Playlist appears after
|
754
|
+
* one or more Media Segment URIs, the client SHOULD extrapolate
|
755
|
+
* backward from that tag (using EXTINF durations and/or media
|
756
|
+
* timestamps) to associate dates with those segments."
|
757
|
+
* We have already extrapolated forward, but all fragments up to the first instance of PDT do not have their PDTs
|
758
|
+
* computed.
|
759
|
+
*/
|
760
|
+
if (firstPdtIndex > 0) {
|
761
|
+
backfillProgramDateTimes(fragments, firstPdtIndex);
|
722
762
|
}
|
723
|
-
}
|
724
|
-
}
|
725
763
|
|
726
|
-
|
727
|
-
details: LevelDetails,
|
728
|
-
startDateTime: number,
|
729
|
-
programDateTimes: MediaFragment[],
|
730
|
-
index: number,
|
731
|
-
endTime: number,
|
732
|
-
): number {
|
733
|
-
const pdtFragment = programDateTimes[index];
|
734
|
-
if (pdtFragment) {
|
735
|
-
// find matching range between PDT tags
|
736
|
-
const durationBetweenPdt =
|
737
|
-
(programDateTimes[index + 1]?.start || endTime) - pdtFragment.start;
|
738
|
-
const pdtStart = pdtFragment.programDateTime as number;
|
739
|
-
if (
|
740
|
-
(startDateTime >= pdtStart || index === 0) &&
|
741
|
-
startDateTime <= pdtStart + durationBetweenPdt * 1000
|
742
|
-
) {
|
743
|
-
// map to fragment with date-time range
|
744
|
-
const startIndex = programDateTimes[index].sn - details.startSN;
|
745
|
-
const fragments = details.fragments;
|
746
|
-
if (fragments.length > programDateTimes.length) {
|
747
|
-
const endSegment =
|
748
|
-
programDateTimes[index + 1] || fragments[fragments.length - 1];
|
749
|
-
const endIndex = endSegment.sn - details.startSN;
|
750
|
-
for (let i = endIndex; i > startIndex; i--) {
|
751
|
-
const fragStartDateTime = fragments[i].programDateTime as number;
|
752
|
-
if (
|
753
|
-
startDateTime >= fragStartDateTime &&
|
754
|
-
startDateTime < fragStartDateTime + fragments[i].duration * 1000
|
755
|
-
) {
|
756
|
-
return i;
|
757
|
-
}
|
758
|
-
}
|
759
|
-
}
|
760
|
-
return startIndex;
|
761
|
-
}
|
764
|
+
return level;
|
762
765
|
}
|
763
|
-
return -1;
|
764
766
|
}
|
765
767
|
|
766
768
|
function parseKey(
|
@@ -769,7 +771,16 @@ function parseKey(
|
|
769
771
|
parsed: ParsedMultivariantPlaylist | LevelDetails,
|
770
772
|
): LevelKey {
|
771
773
|
// https://tools.ietf.org/html/rfc8216#section-4.3.2.4
|
772
|
-
const keyAttrs = new AttrList(keyTagAttributes
|
774
|
+
const keyAttrs = new AttrList(keyTagAttributes);
|
775
|
+
if (__USE_VARIABLE_SUBSTITUTION__) {
|
776
|
+
substituteVariablesInAttributes(parsed, keyAttrs, [
|
777
|
+
'KEYFORMAT',
|
778
|
+
'KEYFORMATVERSIONS',
|
779
|
+
'URI',
|
780
|
+
'IV',
|
781
|
+
'URI',
|
782
|
+
]);
|
783
|
+
}
|
773
784
|
const decryptmethod = keyAttrs.METHOD ?? '';
|
774
785
|
const decrypturi = keyAttrs.URI;
|
775
786
|
const decryptiv = keyAttrs.hexadecimalInteger('IV');
|
@@ -853,22 +864,17 @@ function backfillProgramDateTimes(
|
|
853
864
|
}
|
854
865
|
}
|
855
866
|
|
856
|
-
|
857
|
-
frag: MediaFragment,
|
858
|
-
prevFrag: MediaFragment | null,
|
859
|
-
programDateTimes: MediaFragment[],
|
860
|
-
) {
|
867
|
+
function assignProgramDateTime(frag, prevFrag) {
|
861
868
|
if (frag.rawProgramDateTime) {
|
862
869
|
frag.programDateTime = Date.parse(frag.rawProgramDateTime);
|
863
|
-
if (!Number.isFinite(frag.programDateTime)) {
|
864
|
-
frag.programDateTime = null;
|
865
|
-
frag.rawProgramDateTime = null;
|
866
|
-
return;
|
867
|
-
}
|
868
|
-
programDateTimes.push(frag);
|
869
870
|
} else if (prevFrag?.programDateTime) {
|
870
871
|
frag.programDateTime = prevFrag.endProgramDateTime;
|
871
872
|
}
|
873
|
+
|
874
|
+
if (!Number.isFinite(frag.programDateTime)) {
|
875
|
+
frag.programDateTime = null;
|
876
|
+
frag.rawProgramDateTime = null;
|
877
|
+
}
|
872
878
|
}
|
873
879
|
|
874
880
|
function setInitSegment(
|
@@ -8,6 +8,7 @@
|
|
8
8
|
|
9
9
|
import { Events } from '../events';
|
10
10
|
import { ErrorDetails, ErrorTypes } from '../errors';
|
11
|
+
import { logger } from '../utils/logger';
|
11
12
|
import M3U8Parser from './m3u8-parser';
|
12
13
|
import type { LevelParsed, VariableMap } from '../types/level';
|
13
14
|
import type {
|
@@ -220,10 +221,10 @@ class PlaylistLoader implements NetworkComponentAPI {
|
|
220
221
|
loaderContext.level === context.level
|
221
222
|
) {
|
222
223
|
// same URL can't overlap
|
223
|
-
|
224
|
+
logger.trace('[playlist-loader]: playlist request ongoing');
|
224
225
|
return;
|
225
226
|
}
|
226
|
-
|
227
|
+
logger.log(
|
227
228
|
`[playlist-loader]: aborting previous loader for type: ${context.type}`,
|
228
229
|
);
|
229
230
|
loader.abort();
|
@@ -407,7 +408,7 @@ class PlaylistLoader implements NetworkComponentAPI {
|
|
407
408
|
levels[0].audioCodec &&
|
408
409
|
!levels[0].attrs.AUDIO
|
409
410
|
) {
|
410
|
-
|
411
|
+
logger.log(
|
411
412
|
'[playlist-loader]: audio codec signaled in quality level, but no embedded audio track signaled, create one',
|
412
413
|
);
|
413
414
|
audioTracks.unshift({
|
@@ -452,6 +453,7 @@ class PlaylistLoader implements NetworkComponentAPI {
|
|
452
453
|
const { id, level, type } = context;
|
453
454
|
|
454
455
|
const url = getResponseUrl(response, context);
|
456
|
+
const levelUrlId = 0;
|
455
457
|
const levelId = Number.isFinite(level as number)
|
456
458
|
? (level as number)
|
457
459
|
: Number.isFinite(id as number)
|
@@ -463,7 +465,7 @@ class PlaylistLoader implements NetworkComponentAPI {
|
|
463
465
|
url,
|
464
466
|
levelId,
|
465
467
|
levelType,
|
466
|
-
|
468
|
+
levelUrlId,
|
467
469
|
this.variableList,
|
468
470
|
);
|
469
471
|
|
@@ -553,7 +555,7 @@ class PlaylistLoader implements NetworkComponentAPI {
|
|
553
555
|
message += ` id: ${context.id} group-id: "${context.groupId}"`;
|
554
556
|
}
|
555
557
|
const error = new Error(message);
|
556
|
-
|
558
|
+
logger.warn(`[playlist-loader]: ${message}`);
|
557
559
|
let details = ErrorDetails.UNKNOWN;
|
558
560
|
let fatal = false;
|
559
561
|
|