hls.js 1.5.13 → 1.5.14-0.canary.10415
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 +4 -3
- package/dist/hls-demo.js +41 -38
- package/dist/hls-demo.js.map +1 -1
- package/dist/hls.js +4211 -2666
- package/dist/hls.js.d.ts +179 -110
- package/dist/hls.js.map +1 -1
- package/dist/hls.light.js +2841 -1921
- 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 +2569 -1639
- 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 +3572 -2017
- 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 +5 -2
- package/src/controller/abr-controller.ts +39 -25
- package/src/controller/audio-stream-controller.ts +156 -136
- package/src/controller/audio-track-controller.ts +1 -1
- package/src/controller/base-playlist-controller.ts +27 -10
- package/src/controller/base-stream-controller.ts +234 -89
- package/src/controller/buffer-controller.ts +250 -97
- package/src/controller/buffer-operation-queue.ts +16 -19
- package/src/controller/cap-level-controller.ts +3 -2
- package/src/controller/cmcd-controller.ts +51 -14
- package/src/controller/content-steering-controller.ts +29 -15
- package/src/controller/eme-controller.ts +10 -23
- package/src/controller/error-controller.ts +28 -22
- package/src/controller/fps-controller.ts +8 -3
- package/src/controller/fragment-finders.ts +44 -16
- package/src/controller/fragment-tracker.ts +58 -25
- package/src/controller/gap-controller.ts +43 -16
- package/src/controller/id3-track-controller.ts +45 -35
- package/src/controller/latency-controller.ts +18 -13
- package/src/controller/level-controller.ts +37 -19
- package/src/controller/stream-controller.ts +100 -83
- package/src/controller/subtitle-stream-controller.ts +35 -47
- package/src/controller/subtitle-track-controller.ts +5 -3
- package/src/controller/timeline-controller.ts +20 -22
- package/src/crypt/aes-crypto.ts +21 -2
- package/src/crypt/decrypter-aes-mode.ts +4 -0
- package/src/crypt/decrypter.ts +32 -16
- package/src/crypt/fast-aes-key.ts +28 -5
- package/src/demux/audio/aacdemuxer.ts +2 -2
- package/src/demux/audio/ac3-demuxer.ts +4 -3
- package/src/demux/audio/adts.ts +9 -4
- package/src/demux/audio/base-audio-demuxer.ts +16 -14
- package/src/demux/audio/mp3demuxer.ts +4 -3
- package/src/demux/audio/mpegaudio.ts +1 -1
- package/src/demux/mp4demuxer.ts +7 -7
- package/src/demux/sample-aes.ts +2 -0
- package/src/demux/transmuxer-interface.ts +8 -16
- package/src/demux/transmuxer-worker.ts +4 -4
- package/src/demux/transmuxer.ts +16 -3
- package/src/demux/tsdemuxer.ts +75 -38
- package/src/demux/video/avc-video-parser.ts +210 -121
- package/src/demux/video/base-video-parser.ts +135 -2
- package/src/demux/video/exp-golomb.ts +0 -208
- package/src/demux/video/hevc-video-parser.ts +749 -0
- package/src/events.ts +8 -1
- package/src/exports-named.ts +1 -1
- package/src/hls.ts +84 -47
- package/src/loader/date-range.ts +71 -5
- package/src/loader/fragment-loader.ts +23 -21
- package/src/loader/fragment.ts +8 -4
- package/src/loader/key-loader.ts +3 -1
- package/src/loader/level-details.ts +6 -6
- package/src/loader/level-key.ts +10 -9
- package/src/loader/m3u8-parser.ts +138 -144
- package/src/loader/playlist-loader.ts +5 -7
- package/src/remux/mp4-generator.ts +196 -1
- package/src/remux/mp4-remuxer.ts +32 -62
- package/src/remux/passthrough-remuxer.ts +1 -1
- package/src/task-loop.ts +5 -2
- package/src/types/component-api.ts +3 -1
- package/src/types/demuxer.ts +3 -0
- package/src/types/events.ts +19 -6
- package/src/types/fragment-tracker.ts +2 -2
- package/src/types/media-playlist.ts +9 -1
- package/src/types/remuxer.ts +1 -1
- package/src/utils/attr-list.ts +96 -9
- package/src/utils/buffer-helper.ts +12 -31
- package/src/utils/cea-608-parser.ts +1 -3
- package/src/utils/codecs.ts +34 -5
- package/src/utils/encryption-methods-util.ts +21 -0
- package/src/utils/fetch-loader.ts +1 -1
- package/src/utils/hash.ts +10 -0
- package/src/utils/hdr.ts +4 -7
- package/src/utils/imsc1-ttml-parser.ts +1 -1
- package/src/utils/keysystem-util.ts +1 -6
- package/src/utils/level-helper.ts +71 -44
- package/src/utils/logger.ts +58 -23
- package/src/utils/mp4-tools.ts +5 -3
- package/src/utils/rendition-helper.ts +100 -74
- package/src/utils/utf8-utils.ts +18 -0
- package/src/utils/variable-substitution.ts +0 -19
- package/src/utils/webvtt-parser.ts +2 -12
- package/src/demux/id3.ts +0 -411
- package/src/types/general.ts +0 -6
package/src/loader/fragment.ts
CHANGED
@@ -90,6 +90,10 @@ export class BaseSegment {
|
|
90
90
|
}
|
91
91
|
}
|
92
92
|
|
93
|
+
export interface MediaFragment extends Fragment {
|
94
|
+
sn: number;
|
95
|
+
}
|
96
|
+
|
93
97
|
/**
|
94
98
|
* Object representing parsed data from an HLS Segment. Found in {@link hls.js#LevelDetails.fragments}.
|
95
99
|
*/
|
@@ -123,9 +127,9 @@ export class Fragment extends BaseSegment {
|
|
123
127
|
// The ending Presentation Time Stamp (PTS) of the fragment. Set after transmux complete.
|
124
128
|
public endPTS?: number;
|
125
129
|
// The starting Decode Time Stamp (DTS) of the fragment. Set after transmux complete.
|
126
|
-
public startDTS
|
130
|
+
public startDTS?: number;
|
127
131
|
// The ending Decode Time Stamp (DTS) of the fragment. Set after transmux complete.
|
128
|
-
public endDTS
|
132
|
+
public endDTS?: number;
|
129
133
|
// The start time of the fragment, as listed in the manifest. Updated after transmux complete.
|
130
134
|
public start: number = 0;
|
131
135
|
// Set by `updateFragPTSDTS` in level-helper
|
@@ -274,13 +278,13 @@ export class Part extends BaseSegment {
|
|
274
278
|
public readonly gap: boolean = false;
|
275
279
|
public readonly independent: boolean = false;
|
276
280
|
public readonly relurl: string;
|
277
|
-
public readonly fragment:
|
281
|
+
public readonly fragment: MediaFragment;
|
278
282
|
public readonly index: number;
|
279
283
|
public stats: LoadStats = new LoadStats();
|
280
284
|
|
281
285
|
constructor(
|
282
286
|
partAttrs: AttrList,
|
283
|
-
frag:
|
287
|
+
frag: MediaFragment,
|
284
288
|
baseurl: string,
|
285
289
|
index: number,
|
286
290
|
previous?: Part,
|
package/src/loader/key-loader.ts
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
import { ErrorTypes, ErrorDetails } from '../errors';
|
2
|
-
import {
|
2
|
+
import type {
|
3
3
|
LoaderStats,
|
4
4
|
LoaderResponse,
|
5
5
|
LoaderConfiguration,
|
@@ -194,6 +194,8 @@ export default class KeyLoader implements ComponentAPI {
|
|
194
194
|
}
|
195
195
|
return this.loadKeyEME(keyInfo, frag);
|
196
196
|
case 'AES-128':
|
197
|
+
case 'AES-256':
|
198
|
+
case 'AES-256-CTR':
|
197
199
|
return this.loadKeyHTTP(keyInfo, frag);
|
198
200
|
default:
|
199
201
|
return Promise.reject(
|
@@ -1,7 +1,6 @@
|
|
1
|
-
import { Part } from './fragment';
|
2
|
-
import type { Fragment } from './fragment';
|
3
|
-
import type { AttrList } from '../utils/attr-list';
|
1
|
+
import type { Fragment, MediaFragment, Part } from './fragment';
|
4
2
|
import type { DateRange } from './date-range';
|
3
|
+
import type { AttrList } from '../utils/attr-list';
|
5
4
|
import type { VariableMap } from '../types/level';
|
6
5
|
|
7
6
|
const DEFAULT_TARGET_DURATION = 10;
|
@@ -15,10 +14,11 @@ export class LevelDetails {
|
|
15
14
|
public averagetargetduration?: number;
|
16
15
|
public endCC: number = 0;
|
17
16
|
public endSN: number = 0;
|
18
|
-
public fragments:
|
19
|
-
public fragmentHint?:
|
17
|
+
public fragments: MediaFragment[];
|
18
|
+
public fragmentHint?: MediaFragment;
|
20
19
|
public partList: Part[] | null = null;
|
21
20
|
public dateRanges: Record<string, DateRange>;
|
21
|
+
public dateRangeTagCount: number = 0;
|
22
22
|
public live: boolean = true;
|
23
23
|
public ageHeader: number = 0;
|
24
24
|
public advancedDateTime?: number;
|
@@ -148,7 +148,7 @@ export class LevelDetails {
|
|
148
148
|
|
149
149
|
get lastPartSn(): number {
|
150
150
|
if (this.partList?.length) {
|
151
|
-
return this.partList[this.partList.length - 1].fragment.sn
|
151
|
+
return this.partList[this.partList.length - 1].fragment.sn;
|
152
152
|
}
|
153
153
|
return this.endSN;
|
154
154
|
}
|
package/src/loader/level-key.ts
CHANGED
@@ -2,6 +2,7 @@ import {
|
|
2
2
|
changeEndianness,
|
3
3
|
convertDataUriToArrayBytes,
|
4
4
|
} from '../utils/keysystem-util';
|
5
|
+
import { isFullSegmentEncryption } from '../utils/encryption-methods-util';
|
5
6
|
import { KeySystemFormats } from '../utils/mediakeys-helper';
|
6
7
|
import { mp4pssh } from '../utils/mp4-tools';
|
7
8
|
import { logger } from '../utils/logger';
|
@@ -51,13 +52,14 @@ export class LevelKey implements DecryptData {
|
|
51
52
|
this.keyFormatVersions = formatversions;
|
52
53
|
this.iv = iv;
|
53
54
|
this.encrypted = method ? method !== 'NONE' : false;
|
54
|
-
this.isCommonEncryption =
|
55
|
+
this.isCommonEncryption =
|
56
|
+
this.encrypted && !isFullSegmentEncryption(method);
|
55
57
|
}
|
56
58
|
|
57
59
|
public isSupported(): boolean {
|
58
60
|
// If it's Segment encryption or No encryption, just select that key system
|
59
61
|
if (this.method) {
|
60
|
-
if (this.method
|
62
|
+
if (isFullSegmentEncryption(this.method) || this.method === 'NONE') {
|
61
63
|
return true;
|
62
64
|
}
|
63
65
|
if (this.keyFormat === 'identity') {
|
@@ -88,16 +90,15 @@ export class LevelKey implements DecryptData {
|
|
88
90
|
return null;
|
89
91
|
}
|
90
92
|
|
91
|
-
if (this.method
|
93
|
+
if (isFullSegmentEncryption(this.method) && this.uri && !this.iv) {
|
92
94
|
if (typeof sn !== 'number') {
|
93
95
|
// We are fetching decryption data for a initialization segment
|
94
|
-
// If the segment was encrypted with AES-128
|
96
|
+
// If the segment was encrypted with AES-128/256
|
95
97
|
// It must have an IV defined. We cannot substitute the Segment Number in.
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
}
|
98
|
+
logger.warn(
|
99
|
+
`missing IV for initialization segment with method="${this.method}" - compliance issue`,
|
100
|
+
);
|
101
|
+
|
101
102
|
// Explicitly set sn to resulting value from implicit conversions 'initSegment' values for IV generation.
|
102
103
|
sn = 0;
|
103
104
|
}
|
@@ -1,6 +1,6 @@
|
|
1
1
|
import { buildAbsoluteURL } from 'url-toolkit';
|
2
2
|
import { DateRange } from './date-range';
|
3
|
-
import { Fragment, Part } from './fragment';
|
3
|
+
import { Fragment, MediaFragment, 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,7 +10,6 @@ import {
|
|
10
10
|
hasVariableReferences,
|
11
11
|
importVariableDefinition,
|
12
12
|
substituteVariables,
|
13
|
-
substituteVariablesInAttributes,
|
14
13
|
} from '../utils/variable-substitution';
|
15
14
|
import { isCodecType } from '../utils/codecs';
|
16
15
|
import type { CodecType } from '../utils/codecs';
|
@@ -120,21 +119,7 @@ export default class M3U8Parser {
|
|
120
119
|
while ((result = MASTER_PLAYLIST_REGEX.exec(string)) != null) {
|
121
120
|
if (result[1]) {
|
122
121
|
// '#EXT-X-STREAM-INF' is found, parse level tag in group 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
|
-
}
|
122
|
+
const attrs = new AttrList(result[1], parsed) as LevelAttributes;
|
138
123
|
const uri = __USE_VARIABLE_SUBSTITUTION__
|
139
124
|
? substituteVariables(parsed, result[2])
|
140
125
|
: result[2];
|
@@ -166,15 +151,7 @@ export default class M3U8Parser {
|
|
166
151
|
switch (tag) {
|
167
152
|
case 'SESSION-DATA': {
|
168
153
|
// #EXT-X-SESSION-DATA
|
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
|
-
}
|
154
|
+
const sessionAttrs = new AttrList(attributes, parsed);
|
178
155
|
const dataId = sessionAttrs['DATA-ID'];
|
179
156
|
if (dataId) {
|
180
157
|
if (parsed.sessionData === null) {
|
@@ -202,26 +179,14 @@ export default class M3U8Parser {
|
|
202
179
|
case 'DEFINE': {
|
203
180
|
// #EXT-X-DEFINE
|
204
181
|
if (__USE_VARIABLE_SUBSTITUTION__) {
|
205
|
-
const variableAttributes = new AttrList(attributes);
|
206
|
-
substituteVariablesInAttributes(parsed, variableAttributes, [
|
207
|
-
'NAME',
|
208
|
-
'VALUE',
|
209
|
-
'QUERYPARAM',
|
210
|
-
]);
|
182
|
+
const variableAttributes = new AttrList(attributes, parsed);
|
211
183
|
addVariableDefinition(parsed, variableAttributes, baseurl);
|
212
184
|
}
|
213
185
|
break;
|
214
186
|
}
|
215
187
|
case 'CONTENT-STEERING': {
|
216
188
|
// #EXT-X-CONTENT-STEERING
|
217
|
-
const contentSteeringAttributes = new AttrList(attributes);
|
218
|
-
if (__USE_VARIABLE_SUBSTITUTION__) {
|
219
|
-
substituteVariablesInAttributes(
|
220
|
-
parsed,
|
221
|
-
contentSteeringAttributes,
|
222
|
-
['SERVER-URI', 'PATHWAY-ID'],
|
223
|
-
);
|
224
|
-
}
|
189
|
+
const contentSteeringAttributes = new AttrList(attributes, parsed);
|
225
190
|
parsed.contentSteering = {
|
226
191
|
uri: M3U8Parser.resolve(
|
227
192
|
contentSteeringAttributes['SERVER-URI'],
|
@@ -278,26 +243,13 @@ export default class M3U8Parser {
|
|
278
243
|
let id = 0;
|
279
244
|
MASTER_PLAYLIST_MEDIA_REGEX.lastIndex = 0;
|
280
245
|
while ((result = MASTER_PLAYLIST_MEDIA_REGEX.exec(string)) !== null) {
|
281
|
-
const attrs = new AttrList(result[1]) as MediaAttributes;
|
246
|
+
const attrs = new AttrList(result[1], parsed) as MediaAttributes;
|
282
247
|
const type = attrs.TYPE;
|
283
248
|
if (type) {
|
284
249
|
const groups: (typeof groupsByType)[keyof typeof groupsByType] =
|
285
250
|
groupsByType[type];
|
286
251
|
const medias: MediaPlaylist[] = results[type] || [];
|
287
252
|
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
|
-
}
|
301
253
|
const lang = attrs.LANGUAGE;
|
302
254
|
const assocLang = attrs['ASSOC-LANGUAGE'];
|
303
255
|
const channels = attrs.CHANNELS;
|
@@ -355,6 +307,7 @@ export default class M3U8Parser {
|
|
355
307
|
): LevelDetails {
|
356
308
|
const level = new LevelDetails(baseurl);
|
357
309
|
const fragments: M3U8ParserFragments = level.fragments;
|
310
|
+
const programDateTimes: MediaFragment[] = [];
|
358
311
|
// The most recent init segment seen (applies to all subsequent segments)
|
359
312
|
let currentInitSegment: Fragment | null = null;
|
360
313
|
let currentSN = 0;
|
@@ -420,7 +373,11 @@ export default class M3U8Parser {
|
|
420
373
|
frag.relurl = __USE_VARIABLE_SUBSTITUTION__
|
421
374
|
? substituteVariables(level, uri)
|
422
375
|
: uri;
|
423
|
-
assignProgramDateTime(
|
376
|
+
assignProgramDateTime(
|
377
|
+
frag as MediaFragment,
|
378
|
+
prevFrag as MediaFragment,
|
379
|
+
programDateTimes,
|
380
|
+
);
|
424
381
|
prevFrag = frag;
|
425
382
|
totalduration += frag.duration;
|
426
383
|
currentSN++;
|
@@ -468,19 +425,19 @@ export default class M3U8Parser {
|
|
468
425
|
currentSN = level.startSN = parseInt(value1);
|
469
426
|
break;
|
470
427
|
case 'SKIP': {
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
]);
|
428
|
+
if (level.skippedSegments) {
|
429
|
+
level.playlistParsingError = new Error(
|
430
|
+
`#EXT-X-SKIP MUST NOT appear more than once in a Playlist`,
|
431
|
+
);
|
476
432
|
}
|
433
|
+
const skipAttrs = new AttrList(value1, level);
|
477
434
|
const skippedSegments =
|
478
435
|
skipAttrs.decimalInteger('SKIPPED-SEGMENTS');
|
479
436
|
if (Number.isFinite(skippedSegments)) {
|
480
|
-
level.skippedSegments
|
437
|
+
level.skippedSegments += skippedSegments;
|
481
438
|
// This will result in fragments[] containing undefined values, which we will fill in with `mergeDetails`
|
482
439
|
for (let i = skippedSegments; i--; ) {
|
483
|
-
fragments.
|
440
|
+
fragments.push(null);
|
484
441
|
}
|
485
442
|
currentSN += skippedSegments;
|
486
443
|
}
|
@@ -488,8 +445,9 @@ export default class M3U8Parser {
|
|
488
445
|
'RECENTLY-REMOVED-DATERANGES',
|
489
446
|
);
|
490
447
|
if (recentlyRemovedDateranges) {
|
491
|
-
level.recentlyRemovedDateranges =
|
492
|
-
recentlyRemovedDateranges
|
448
|
+
level.recentlyRemovedDateranges = (
|
449
|
+
level.recentlyRemovedDateranges || []
|
450
|
+
).concat(recentlyRemovedDateranges.split('\t'));
|
493
451
|
}
|
494
452
|
break;
|
495
453
|
}
|
@@ -522,27 +480,13 @@ export default class M3U8Parser {
|
|
522
480
|
frag.tagList.push([tag, value1]);
|
523
481
|
break;
|
524
482
|
case 'DATERANGE': {
|
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
|
-
}
|
483
|
+
const dateRangeAttr = new AttrList(value1, level);
|
542
484
|
const dateRange = new DateRange(
|
543
485
|
dateRangeAttr,
|
544
486
|
level.dateRanges[dateRangeAttr.ID],
|
487
|
+
level.dateRangeTagCount,
|
545
488
|
);
|
489
|
+
level.dateRangeTagCount++;
|
546
490
|
if (dateRange.isValid || level.skippedSegments) {
|
547
491
|
level.dateRanges[dateRange.id] = dateRange;
|
548
492
|
} else {
|
@@ -554,13 +498,7 @@ export default class M3U8Parser {
|
|
554
498
|
}
|
555
499
|
case 'DEFINE': {
|
556
500
|
if (__USE_VARIABLE_SUBSTITUTION__) {
|
557
|
-
const variableAttributes = new AttrList(value1);
|
558
|
-
substituteVariablesInAttributes(level, variableAttributes, [
|
559
|
-
'NAME',
|
560
|
-
'VALUE',
|
561
|
-
'IMPORT',
|
562
|
-
'QUERYPARAM',
|
563
|
-
]);
|
501
|
+
const variableAttributes = new AttrList(value1, level);
|
564
502
|
if ('IMPORT' in variableAttributes) {
|
565
503
|
importVariableDefinition(
|
566
504
|
level,
|
@@ -600,13 +538,7 @@ export default class M3U8Parser {
|
|
600
538
|
level.startTimeOffset = parseStartTimeOffset(value1);
|
601
539
|
break;
|
602
540
|
case 'MAP': {
|
603
|
-
const mapAttrs = new AttrList(value1);
|
604
|
-
if (__USE_VARIABLE_SUBSTITUTION__) {
|
605
|
-
substituteVariablesInAttributes(level, mapAttrs, [
|
606
|
-
'BYTERANGE',
|
607
|
-
'URI',
|
608
|
-
]);
|
609
|
-
}
|
541
|
+
const mapAttrs = new AttrList(value1, level);
|
610
542
|
if (frag.duration) {
|
611
543
|
// Initial segment tag is after segment duration tag.
|
612
544
|
// #EXTINF: 6.0
|
@@ -635,6 +567,7 @@ export default class M3U8Parser {
|
|
635
567
|
currentInitSegment = frag;
|
636
568
|
createNextFrag = true;
|
637
569
|
}
|
570
|
+
currentInitSegment.cc = discontinuityCounter;
|
638
571
|
break;
|
639
572
|
}
|
640
573
|
case 'SERVER-CONTROL': {
|
@@ -667,16 +600,10 @@ export default class M3U8Parser {
|
|
667
600
|
const previousFragmentPart =
|
668
601
|
currentPart > 0 ? partList[partList.length - 1] : undefined;
|
669
602
|
const index = currentPart++;
|
670
|
-
const partAttrs = new AttrList(value1);
|
671
|
-
if (__USE_VARIABLE_SUBSTITUTION__) {
|
672
|
-
substituteVariablesInAttributes(level, partAttrs, [
|
673
|
-
'BYTERANGE',
|
674
|
-
'URI',
|
675
|
-
]);
|
676
|
-
}
|
603
|
+
const partAttrs = new AttrList(value1, level);
|
677
604
|
const part = new Part(
|
678
605
|
partAttrs,
|
679
|
-
frag,
|
606
|
+
frag as MediaFragment,
|
680
607
|
baseurl,
|
681
608
|
index,
|
682
609
|
previousFragmentPart,
|
@@ -686,20 +613,12 @@ export default class M3U8Parser {
|
|
686
613
|
break;
|
687
614
|
}
|
688
615
|
case 'PRELOAD-HINT': {
|
689
|
-
const preloadHintAttrs = new AttrList(value1);
|
690
|
-
if (__USE_VARIABLE_SUBSTITUTION__) {
|
691
|
-
substituteVariablesInAttributes(level, preloadHintAttrs, ['URI']);
|
692
|
-
}
|
616
|
+
const preloadHintAttrs = new AttrList(value1, level);
|
693
617
|
level.preloadHint = preloadHintAttrs;
|
694
618
|
break;
|
695
619
|
}
|
696
620
|
case 'RENDITION-REPORT': {
|
697
|
-
const renditionReportAttrs = new AttrList(value1);
|
698
|
-
if (__USE_VARIABLE_SUBSTITUTION__) {
|
699
|
-
substituteVariablesInAttributes(level, renditionReportAttrs, [
|
700
|
-
'URI',
|
701
|
-
]);
|
702
|
-
}
|
621
|
+
const renditionReportAttrs = new AttrList(value1, level);
|
703
622
|
level.renditionReports = level.renditionReports || [];
|
704
623
|
level.renditionReports.push(renditionReportAttrs);
|
705
624
|
break;
|
@@ -714,12 +633,16 @@ export default class M3U8Parser {
|
|
714
633
|
fragments.pop();
|
715
634
|
totalduration -= prevFrag.duration;
|
716
635
|
if (level.partList) {
|
717
|
-
level.fragmentHint = prevFrag;
|
636
|
+
level.fragmentHint = prevFrag as MediaFragment;
|
718
637
|
}
|
719
638
|
} else if (level.partList) {
|
720
|
-
assignProgramDateTime(
|
639
|
+
assignProgramDateTime(
|
640
|
+
frag as MediaFragment,
|
641
|
+
prevFrag as MediaFragment,
|
642
|
+
programDateTimes,
|
643
|
+
);
|
721
644
|
frag.cc = discontinuityCounter;
|
722
|
-
level.fragmentHint = frag;
|
645
|
+
level.fragmentHint = frag as MediaFragment;
|
723
646
|
if (levelkeys) {
|
724
647
|
setFragLevelKeys(frag, levelkeys, level);
|
725
648
|
}
|
@@ -738,6 +661,21 @@ export default class M3U8Parser {
|
|
738
661
|
if (firstFragment) {
|
739
662
|
level.startCC = firstFragment.cc;
|
740
663
|
}
|
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
|
+
}
|
741
679
|
} else {
|
742
680
|
level.endSN = 0;
|
743
681
|
level.startCC = 0;
|
@@ -746,23 +684,83 @@ export default class M3U8Parser {
|
|
746
684
|
totalduration += level.fragmentHint.duration;
|
747
685
|
}
|
748
686
|
level.totalduration = totalduration;
|
687
|
+
if (programDateTimes.length && level.dateRangeTagCount && firstFragment) {
|
688
|
+
mapDateRanges(programDateTimes, level);
|
689
|
+
}
|
690
|
+
|
749
691
|
level.endCC = discontinuityCounter;
|
750
692
|
|
751
|
-
|
752
|
-
|
753
|
-
|
754
|
-
|
755
|
-
|
756
|
-
|
757
|
-
|
758
|
-
|
759
|
-
|
760
|
-
|
761
|
-
|
693
|
+
return level;
|
694
|
+
}
|
695
|
+
}
|
696
|
+
|
697
|
+
export function mapDateRanges(
|
698
|
+
programDateTimes: MediaFragment[],
|
699
|
+
details: LevelDetails,
|
700
|
+
) {
|
701
|
+
// Make sure DateRanges are mapped to a ProgramDateTime tag that applies a date to a segment that overlaps with its start date
|
702
|
+
const programDateTimeCount = programDateTimes.length;
|
703
|
+
const lastProgramDateTime = programDateTimes[programDateTimeCount - 1];
|
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
|
+
}
|
762
722
|
}
|
723
|
+
}
|
724
|
+
}
|
763
725
|
|
764
|
-
|
726
|
+
function findFragmentWithStartDate(
|
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
|
+
}
|
765
762
|
}
|
763
|
+
return -1;
|
766
764
|
}
|
767
765
|
|
768
766
|
function parseKey(
|
@@ -771,16 +769,7 @@ function parseKey(
|
|
771
769
|
parsed: ParsedMultivariantPlaylist | LevelDetails,
|
772
770
|
): LevelKey {
|
773
771
|
// https://tools.ietf.org/html/rfc8216#section-4.3.2.4
|
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
|
-
}
|
772
|
+
const keyAttrs = new AttrList(keyTagAttributes, parsed);
|
784
773
|
const decryptmethod = keyAttrs.METHOD ?? '';
|
785
774
|
const decrypturi = keyAttrs.URI;
|
786
775
|
const decryptiv = keyAttrs.hexadecimalInteger('IV');
|
@@ -864,17 +853,22 @@ function backfillProgramDateTimes(
|
|
864
853
|
}
|
865
854
|
}
|
866
855
|
|
867
|
-
function assignProgramDateTime(
|
856
|
+
export function assignProgramDateTime(
|
857
|
+
frag: MediaFragment,
|
858
|
+
prevFrag: MediaFragment | null,
|
859
|
+
programDateTimes: MediaFragment[],
|
860
|
+
) {
|
868
861
|
if (frag.rawProgramDateTime) {
|
869
862
|
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);
|
870
869
|
} else if (prevFrag?.programDateTime) {
|
871
870
|
frag.programDateTime = prevFrag.endProgramDateTime;
|
872
871
|
}
|
873
|
-
|
874
|
-
if (!Number.isFinite(frag.programDateTime)) {
|
875
|
-
frag.programDateTime = null;
|
876
|
-
frag.rawProgramDateTime = null;
|
877
|
-
}
|
878
872
|
}
|
879
873
|
|
880
874
|
function setInitSegment(
|
@@ -8,7 +8,6 @@
|
|
8
8
|
|
9
9
|
import { Events } from '../events';
|
10
10
|
import { ErrorDetails, ErrorTypes } from '../errors';
|
11
|
-
import { logger } from '../utils/logger';
|
12
11
|
import M3U8Parser from './m3u8-parser';
|
13
12
|
import type { LevelParsed, VariableMap } from '../types/level';
|
14
13
|
import type {
|
@@ -221,10 +220,10 @@ class PlaylistLoader implements NetworkComponentAPI {
|
|
221
220
|
loaderContext.level === context.level
|
222
221
|
) {
|
223
222
|
// same URL can't overlap
|
224
|
-
logger.trace('[playlist-loader]: playlist request ongoing');
|
223
|
+
this.hls.logger.trace('[playlist-loader]: playlist request ongoing');
|
225
224
|
return;
|
226
225
|
}
|
227
|
-
logger.log(
|
226
|
+
this.hls.logger.log(
|
228
227
|
`[playlist-loader]: aborting previous loader for type: ${context.type}`,
|
229
228
|
);
|
230
229
|
loader.abort();
|
@@ -408,7 +407,7 @@ class PlaylistLoader implements NetworkComponentAPI {
|
|
408
407
|
levels[0].audioCodec &&
|
409
408
|
!levels[0].attrs.AUDIO
|
410
409
|
) {
|
411
|
-
logger.log(
|
410
|
+
this.hls.logger.log(
|
412
411
|
'[playlist-loader]: audio codec signaled in quality level, but no embedded audio track signaled, create one',
|
413
412
|
);
|
414
413
|
audioTracks.unshift({
|
@@ -453,7 +452,6 @@ class PlaylistLoader implements NetworkComponentAPI {
|
|
453
452
|
const { id, level, type } = context;
|
454
453
|
|
455
454
|
const url = getResponseUrl(response, context);
|
456
|
-
const levelUrlId = 0;
|
457
455
|
const levelId = Number.isFinite(level as number)
|
458
456
|
? (level as number)
|
459
457
|
: Number.isFinite(id as number)
|
@@ -465,7 +463,7 @@ class PlaylistLoader implements NetworkComponentAPI {
|
|
465
463
|
url,
|
466
464
|
levelId,
|
467
465
|
levelType,
|
468
|
-
|
466
|
+
0,
|
469
467
|
this.variableList,
|
470
468
|
);
|
471
469
|
|
@@ -555,7 +553,7 @@ class PlaylistLoader implements NetworkComponentAPI {
|
|
555
553
|
message += ` id: ${context.id} group-id: "${context.groupId}"`;
|
556
554
|
}
|
557
555
|
const error = new Error(message);
|
558
|
-
logger.warn(`[playlist-loader]: ${message}`);
|
556
|
+
this.hls.logger.warn(`[playlist-loader]: ${message}`);
|
559
557
|
let details = ErrorDetails.UNKNOWN;
|
560
558
|
let fatal = false;
|
561
559
|
|