@amazon/vinyl-hls-parser 1.2.0 → 2.0.4
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/hlsTestAssets/types/current/tsconfig.tsbuildinfo +1 -1
- package/dist/index.cjs +132 -57
- package/dist/index.cjs.map +4 -4
- package/dist/index.js +131 -56
- package/dist/index.js.map +4 -4
- package/dist/test/benchmark.es5.js +52 -36
- package/dist/test/benchmark.js +428 -260
- package/dist/test/benchmark.js.map +4 -4
- package/dist/types/current/index.d.ts +11 -10
- package/dist/types/current/index.d.ts.map +1 -1
- package/dist/types/current/index.js +11 -10
- package/dist/types/current/parse/parseMainPlaylist.d.ts +8 -3
- package/dist/types/current/parse/parseMainPlaylist.d.ts.map +1 -1
- package/dist/types/current/parse/parseMainPlaylist.js +27 -3
- package/dist/types/current/parse/parseMediaPlaylist.d.ts +11 -4
- package/dist/types/current/parse/parseMediaPlaylist.d.ts.map +1 -1
- package/dist/types/current/parse/parseMediaPlaylist.js +111 -7
- package/dist/types/current/tsconfig.tsbuildinfo +1 -1
- package/dist/types/current/types/AlternativeRendition.d.ts +13 -0
- package/dist/types/current/types/AlternativeRendition.d.ts.map +1 -1
- package/dist/types/current/types/HlsDateRange.d.ts +51 -0
- package/dist/types/current/types/HlsDateRange.d.ts.map +1 -0
- package/dist/types/current/types/HlsDateRange.js +11 -0
- package/dist/types/current/types/{MainPlaylist.d.ts → HlsMainPlaylist.d.ts} +4 -4
- package/dist/types/current/types/HlsMainPlaylist.d.ts.map +1 -0
- package/dist/types/{ts4.1/types/MediaPlaylist.d.ts → current/types/HlsMediaPlaylist.d.ts} +11 -4
- package/dist/types/current/types/HlsMediaPlaylist.d.ts.map +1 -0
- package/dist/types/ts3.7/index.d.ts +11 -10
- package/dist/types/ts3.7/parse/parseMainPlaylist.d.ts +8 -3
- package/dist/types/ts3.7/parse/parseMediaPlaylist.d.ts +11 -4
- package/dist/types/ts3.7/types/AlternativeRendition.d.ts +13 -0
- package/dist/types/ts3.7/types/HlsDateRange.d.ts +51 -0
- package/dist/types/ts3.7/types/{MainPlaylist.d.ts → HlsMainPlaylist.d.ts} +4 -4
- package/dist/types/ts3.7/types/{MediaPlaylist.d.ts → HlsMediaPlaylist.d.ts} +11 -4
- package/dist/types/ts4.1/index.d.ts +11 -10
- package/dist/types/ts4.1/parse/parseMainPlaylist.d.ts +8 -3
- package/dist/types/ts4.1/parse/parseMediaPlaylist.d.ts +11 -4
- package/dist/types/ts4.1/types/AlternativeRendition.d.ts +13 -0
- package/dist/types/ts4.1/types/HlsDateRange.d.ts +51 -0
- package/dist/types/{ts4.7/types/MainPlaylist.d.ts → ts4.1/types/HlsMainPlaylist.d.ts} +4 -4
- package/dist/types/{ts4.7/types/MediaPlaylist.d.ts → ts4.1/types/HlsMediaPlaylist.d.ts} +11 -4
- package/dist/types/ts4.7/index.d.ts +11 -10
- package/dist/types/ts4.7/parse/parseMainPlaylist.d.ts +8 -3
- package/dist/types/ts4.7/parse/parseMediaPlaylist.d.ts +11 -4
- package/dist/types/ts4.7/types/AlternativeRendition.d.ts +13 -0
- package/dist/types/ts4.7/types/HlsDateRange.d.ts +51 -0
- package/dist/types/{ts4.1/types/MainPlaylist.d.ts → ts4.7/types/HlsMainPlaylist.d.ts} +4 -4
- package/dist/types/{current/types/MediaPlaylist.d.ts → ts4.7/types/HlsMediaPlaylist.d.ts} +11 -4
- package/package.json +14 -14
- package/dist/types/current/types/MainPlaylist.d.ts.map +0 -1
- package/dist/types/current/types/MediaPlaylist.d.ts.map +0 -1
- /package/dist/types/current/types/{MainPlaylist.js → HlsMainPlaylist.js} +0 -0
- /package/dist/types/current/types/{MediaPlaylist.js → HlsMediaPlaylist.js} +0 -0
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
/** Auto-generated from build scripts */
|
|
2
|
-
export * from './types/VariantStream';
|
|
3
|
-
export * from './types/SessionData';
|
|
4
|
-
export * from './types/MediaPlaylist';
|
|
5
|
-
export * from './types/MainPlaylist';
|
|
6
|
-
export * from './types/HlsSegment';
|
|
7
|
-
export * from './types/EncryptionKey';
|
|
8
|
-
export * from './types/AlternativeRendition';
|
|
9
|
-
export * from './parse/parseUtil';
|
|
10
|
-
export * from './parse/parseMediaPlaylist';
|
|
11
|
-
export * from './parse/parseMainPlaylist';
|
|
12
2
|
export * from './parse/parseAttributes';
|
|
3
|
+
export * from './parse/parseMainPlaylist';
|
|
4
|
+
export * from './parse/parseMediaPlaylist';
|
|
5
|
+
export * from './parse/parseUtil';
|
|
6
|
+
export * from './types/AlternativeRendition';
|
|
7
|
+
export * from './types/EncryptionKey';
|
|
8
|
+
export * from './types/HlsDateRange';
|
|
9
|
+
export * from './types/HlsMainPlaylist';
|
|
10
|
+
export * from './types/HlsMediaPlaylist';
|
|
11
|
+
export * from './types/HlsSegment';
|
|
12
|
+
export * from './types/SessionData';
|
|
13
|
+
export * from './types/VariantStream';
|
|
13
14
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.ts"],"names":[],"mappings":"AAAA,wCAAwC;AACxC,cAAc,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.ts"],"names":[],"mappings":"AAAA,wCAAwC;AACxC,cAAc,yBAAyB,CAAA;AACvC,cAAc,2BAA2B,CAAA;AACzC,cAAc,4BAA4B,CAAA;AAC1C,cAAc,mBAAmB,CAAA;AACjC,cAAc,8BAA8B,CAAA;AAC5C,cAAc,uBAAuB,CAAA;AACrC,cAAc,sBAAsB,CAAA;AACpC,cAAc,yBAAyB,CAAA;AACvC,cAAc,0BAA0B,CAAA;AACxC,cAAc,oBAAoB,CAAA;AAClC,cAAc,qBAAqB,CAAA;AACnC,cAAc,uBAAuB,CAAA"}
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
/** Auto-generated from build scripts */
|
|
2
|
-
export * from './types/VariantStream';
|
|
3
|
-
export * from './types/SessionData';
|
|
4
|
-
export * from './types/MediaPlaylist';
|
|
5
|
-
export * from './types/MainPlaylist';
|
|
6
|
-
export * from './types/HlsSegment';
|
|
7
|
-
export * from './types/EncryptionKey';
|
|
8
|
-
export * from './types/AlternativeRendition';
|
|
9
|
-
export * from './parse/parseUtil';
|
|
10
|
-
export * from './parse/parseMediaPlaylist';
|
|
11
|
-
export * from './parse/parseMainPlaylist';
|
|
12
2
|
export * from './parse/parseAttributes';
|
|
3
|
+
export * from './parse/parseMainPlaylist';
|
|
4
|
+
export * from './parse/parseMediaPlaylist';
|
|
5
|
+
export * from './parse/parseUtil';
|
|
6
|
+
export * from './types/AlternativeRendition';
|
|
7
|
+
export * from './types/EncryptionKey';
|
|
8
|
+
export * from './types/HlsDateRange';
|
|
9
|
+
export * from './types/HlsMainPlaylist';
|
|
10
|
+
export * from './types/HlsMediaPlaylist';
|
|
11
|
+
export * from './types/HlsSegment';
|
|
12
|
+
export * from './types/SessionData';
|
|
13
|
+
export * from './types/VariantStream';
|
|
@@ -1,10 +1,15 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { HlsMainPlaylist } from '../types/HlsMainPlaylist';
|
|
2
2
|
/**
|
|
3
3
|
* Parses an HLS master playlist from M3U8 text.
|
|
4
4
|
*
|
|
5
5
|
* @param text The raw M3U8 manifest string.
|
|
6
|
-
* @
|
|
6
|
+
* @param queryParams Optional query parameters from the playlist's own URL,
|
|
7
|
+
* used to resolve `#EXT-X-DEFINE:QUERYPARAM="name"` entries (RFC 8216
|
|
8
|
+
* §4.4.5.1). Resolved values are exposed via {@link HlsMainPlaylist.defines} so
|
|
9
|
+
* child media playlists can `IMPORT` them. Interstitial ad manifests (e.g.
|
|
10
|
+
* AWS MediaTailor) carry session tokens this way.
|
|
11
|
+
* @returns A readonly HlsMainPlaylist structure.
|
|
7
12
|
* @throws StringParseError if the manifest is malformed.
|
|
8
13
|
*/
|
|
9
|
-
export declare function parseMainPlaylist(text: string):
|
|
14
|
+
export declare function parseMainPlaylist(text: string, queryParams?: Readonly<Record<string, string>>): HlsMainPlaylist;
|
|
10
15
|
//# sourceMappingURL=parseMainPlaylist.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"parseMainPlaylist.d.ts","sourceRoot":"","sources":["../../../../src/parse/parseMainPlaylist.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"parseMainPlaylist.d.ts","sourceRoot":"","sources":["../../../../src/parse/parseMainPlaylist.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAA;AAa/D;;;;;;;;;;;GAWG;AACH,wBAAgB,iBAAiB,CAC7B,IAAI,EAAE,MAAM,EACZ,WAAW,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,GAC/C,eAAe,CAwIjB"}
|
|
@@ -16,10 +16,15 @@ const EXT_X_DEFINE = '#EXT-X-DEFINE:';
|
|
|
16
16
|
* Parses an HLS master playlist from M3U8 text.
|
|
17
17
|
*
|
|
18
18
|
* @param text The raw M3U8 manifest string.
|
|
19
|
-
* @
|
|
19
|
+
* @param queryParams Optional query parameters from the playlist's own URL,
|
|
20
|
+
* used to resolve `#EXT-X-DEFINE:QUERYPARAM="name"` entries (RFC 8216
|
|
21
|
+
* §4.4.5.1). Resolved values are exposed via {@link HlsMainPlaylist.defines} so
|
|
22
|
+
* child media playlists can `IMPORT` them. Interstitial ad manifests (e.g.
|
|
23
|
+
* AWS MediaTailor) carry session tokens this way.
|
|
24
|
+
* @returns A readonly HlsMainPlaylist structure.
|
|
20
25
|
* @throws StringParseError if the manifest is malformed.
|
|
21
26
|
*/
|
|
22
|
-
export function parseMainPlaylist(text) {
|
|
27
|
+
export function parseMainPlaylist(text, queryParams) {
|
|
23
28
|
const reader = new StringReader(text);
|
|
24
29
|
reader.white();
|
|
25
30
|
if (!reader.stringIf(EXTM3U)) {
|
|
@@ -85,6 +90,15 @@ export function parseMainPlaylist(text) {
|
|
|
85
90
|
...(attrs['AUTOSELECT'] && {
|
|
86
91
|
autoSelect: attrs['AUTOSELECT'] === 'YES',
|
|
87
92
|
}),
|
|
93
|
+
...(attrs['FORCED'] && {
|
|
94
|
+
forced: attrs['FORCED'] === 'YES',
|
|
95
|
+
}),
|
|
96
|
+
...(attrs['CHARACTERISTICS'] && {
|
|
97
|
+
characteristics: attrs['CHARACTERISTICS']
|
|
98
|
+
.split(',')
|
|
99
|
+
.map((c) => c.trim())
|
|
100
|
+
.filter((c) => c.length > 0),
|
|
101
|
+
}),
|
|
88
102
|
}, attrs, {
|
|
89
103
|
URI: 'uri',
|
|
90
104
|
LANGUAGE: 'language',
|
|
@@ -106,7 +120,17 @@ export function parseMainPlaylist(text) {
|
|
|
106
120
|
const attrStr = trimmed.substring(EXT_X_DEFINE.length);
|
|
107
121
|
const attrReader = new StringReader(attrStr);
|
|
108
122
|
const attrs = parseAttributes(attrReader);
|
|
109
|
-
|
|
123
|
+
const queryParamName = attrs['QUERYPARAM'];
|
|
124
|
+
if (queryParamName) {
|
|
125
|
+
// Per RFC 8216 §4.4.5.1: QUERYPARAM copies the value from the
|
|
126
|
+
// named query parameter of the playlist's own URL. Ignore
|
|
127
|
+
// silently if the parameter is absent.
|
|
128
|
+
const queryValue = queryParams?.[queryParamName];
|
|
129
|
+
if (queryValue !== undefined) {
|
|
130
|
+
defines[queryParamName] = queryValue;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
else if (attrs['NAME'] && attrs['VALUE']) {
|
|
110
134
|
defines[attrs['NAME']] = attrs['VALUE'];
|
|
111
135
|
}
|
|
112
136
|
}
|
|
@@ -1,12 +1,19 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { HlsMediaPlaylist } from '../types/HlsMediaPlaylist';
|
|
2
2
|
export declare const HLS_VARIABLE_PATTERN: RegExp;
|
|
3
3
|
/**
|
|
4
4
|
* Parses an HLS media playlist from M3U8 text.
|
|
5
5
|
*
|
|
6
6
|
* @param text The raw M3U8 manifest string.
|
|
7
|
-
* @param variables Optional variable definitions from the
|
|
8
|
-
*
|
|
7
|
+
* @param variables Optional variable definitions from the parent multivariant
|
|
8
|
+
* playlist's #EXT-X-DEFINE tags. Media-playlist `#EXT-X-DEFINE:IMPORT="name"`
|
|
9
|
+
* entries resolve against this map. Local `#EXT-X-DEFINE:NAME=,VALUE=`
|
|
10
|
+
* entries are also supported and take precedence.
|
|
11
|
+
* @param queryParams Optional query parameters from the playlist's own URL,
|
|
12
|
+
* used to resolve `#EXT-X-DEFINE:QUERYPARAM="name"` entries (RFC 8216
|
|
13
|
+
* §4.4.5.1). Interstitial ad manifests (e.g. AWS MediaTailor) commonly carry
|
|
14
|
+
* session tokens this way and reference them as `{$name}` in child URIs.
|
|
15
|
+
* @returns A readonly HlsMediaPlaylist structure.
|
|
9
16
|
* @throws StringParseError if the manifest is malformed.
|
|
10
17
|
*/
|
|
11
|
-
export declare function parseMediaPlaylist(text: string, variables?: Readonly<Record<string, string>>):
|
|
18
|
+
export declare function parseMediaPlaylist(text: string, variables?: Readonly<Record<string, string>>, queryParams?: Readonly<Record<string, string>>): HlsMediaPlaylist;
|
|
12
19
|
//# sourceMappingURL=parseMediaPlaylist.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"parseMediaPlaylist.d.ts","sourceRoot":"","sources":["../../../../src/parse/parseMediaPlaylist.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"parseMediaPlaylist.d.ts","sourceRoot":"","sources":["../../../../src/parse/parseMediaPlaylist.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAA;AAQjE,eAAO,MAAM,oBAAoB,QAAmB,CAAA;AAiBpD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,kBAAkB,CAC9B,IAAI,EAAE,MAAM,EACZ,SAAS,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,EAC5C,WAAW,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,GAC/C,gBAAgB,CAiMlB"}
|
|
@@ -6,7 +6,11 @@ import { StringReader, substitute } from '@amazon/vinyl-util';
|
|
|
6
6
|
import { encryptionMethodValidator } from '../types/EncryptionKey';
|
|
7
7
|
import { parseAttributes } from './parseAttributes';
|
|
8
8
|
import { readLine, skipWhitespaceLine, addIfPresent } from './parseUtil';
|
|
9
|
-
|
|
9
|
+
// The variable-name class excludes `{` (and `}`) so a match attempt can't
|
|
10
|
+
// scan across token boundaries. With `[^}]` a crafted value such as
|
|
11
|
+
// `{$|{$|{$|…` makes replace() do polynomial work (ReDoS); `[^{}]` keeps it
|
|
12
|
+
// linear, and HLS variable names never contain `{`/`}`.
|
|
13
|
+
export const HLS_VARIABLE_PATTERN = /\{\$([^{}]+)}/g;
|
|
10
14
|
const EXTM3U = '#EXTM3U';
|
|
11
15
|
const EXTINF = '#EXTINF:';
|
|
12
16
|
const EXT_X_TARGETDURATION = '#EXT-X-TARGETDURATION:';
|
|
@@ -19,15 +23,28 @@ const EXT_X_BYTERANGE = '#EXT-X-BYTERANGE:';
|
|
|
19
23
|
const EXT_X_DISCONTINUITY = '#EXT-X-DISCONTINUITY';
|
|
20
24
|
const EXT_X_PROGRAM_DATE_TIME = '#EXT-X-PROGRAM-DATE-TIME:';
|
|
21
25
|
const EXT_X_MAP = '#EXT-X-MAP:';
|
|
26
|
+
const EXT_X_DEFINE = '#EXT-X-DEFINE:';
|
|
27
|
+
const EXT_X_DATERANGE = '#EXT-X-DATERANGE:';
|
|
22
28
|
/**
|
|
23
29
|
* Parses an HLS media playlist from M3U8 text.
|
|
24
30
|
*
|
|
25
31
|
* @param text The raw M3U8 manifest string.
|
|
26
|
-
* @param variables Optional variable definitions from the
|
|
27
|
-
*
|
|
32
|
+
* @param variables Optional variable definitions from the parent multivariant
|
|
33
|
+
* playlist's #EXT-X-DEFINE tags. Media-playlist `#EXT-X-DEFINE:IMPORT="name"`
|
|
34
|
+
* entries resolve against this map. Local `#EXT-X-DEFINE:NAME=,VALUE=`
|
|
35
|
+
* entries are also supported and take precedence.
|
|
36
|
+
* @param queryParams Optional query parameters from the playlist's own URL,
|
|
37
|
+
* used to resolve `#EXT-X-DEFINE:QUERYPARAM="name"` entries (RFC 8216
|
|
38
|
+
* §4.4.5.1). Interstitial ad manifests (e.g. AWS MediaTailor) commonly carry
|
|
39
|
+
* session tokens this way and reference them as `{$name}` in child URIs.
|
|
40
|
+
* @returns A readonly HlsMediaPlaylist structure.
|
|
28
41
|
* @throws StringParseError if the manifest is malformed.
|
|
29
42
|
*/
|
|
30
|
-
export function parseMediaPlaylist(text, variables) {
|
|
43
|
+
export function parseMediaPlaylist(text, variables, queryParams) {
|
|
44
|
+
const defines = { ...(variables ?? {}) };
|
|
45
|
+
const substituteVars = (v) => Object.keys(defines).length === 0
|
|
46
|
+
? v
|
|
47
|
+
: substitute(v, defines, HLS_VARIABLE_PATTERN);
|
|
31
48
|
const reader = new StringReader(text);
|
|
32
49
|
reader.white();
|
|
33
50
|
if (!reader.stringIf(EXTM3U)) {
|
|
@@ -45,6 +62,7 @@ export function parseMediaPlaylist(text, variables) {
|
|
|
45
62
|
let pendingDiscontinuity = false;
|
|
46
63
|
let pendingDateTime;
|
|
47
64
|
const segments = [];
|
|
65
|
+
const dateRanges = [];
|
|
48
66
|
let sequenceCounter = 0;
|
|
49
67
|
while (reader.hasNext()) {
|
|
50
68
|
if (skipWhitespaceLine(reader))
|
|
@@ -63,9 +81,7 @@ export function parseMediaPlaylist(text, variables) {
|
|
|
63
81
|
continue;
|
|
64
82
|
const nextLine = readLine(reader).trim();
|
|
65
83
|
if (!nextLine.startsWith('#')) {
|
|
66
|
-
uri =
|
|
67
|
-
? substitute(nextLine, variables, HLS_VARIABLE_PATTERN)
|
|
68
|
-
: nextLine;
|
|
84
|
+
uri = substituteVars(nextLine);
|
|
69
85
|
break;
|
|
70
86
|
}
|
|
71
87
|
// Process tags that come between EXTINF and URI
|
|
@@ -168,6 +184,37 @@ export function parseMediaPlaylist(text, variables) {
|
|
|
168
184
|
else if (trimmed.startsWith(EXT_X_PROGRAM_DATE_TIME)) {
|
|
169
185
|
pendingDateTime = trimmed.substring(EXT_X_PROGRAM_DATE_TIME.length);
|
|
170
186
|
}
|
|
187
|
+
else if (trimmed.startsWith(EXT_X_DEFINE)) {
|
|
188
|
+
const attrStr = trimmed.substring(EXT_X_DEFINE.length);
|
|
189
|
+
const attrReader = new StringReader(attrStr);
|
|
190
|
+
const attrs = parseAttributes(attrReader);
|
|
191
|
+
const importName = attrs['IMPORT'];
|
|
192
|
+
const queryParamName = attrs['QUERYPARAM'];
|
|
193
|
+
if (importName) {
|
|
194
|
+
// Per RFC 8216 §4.4.2.3: IMPORT copies the value from the
|
|
195
|
+
// parent multivariant's DEFINE map. Ignore silently if unknown.
|
|
196
|
+
const imported = variables?.[importName];
|
|
197
|
+
if (imported !== undefined)
|
|
198
|
+
defines[importName] = imported;
|
|
199
|
+
}
|
|
200
|
+
else if (queryParamName) {
|
|
201
|
+
// Per RFC 8216 §4.4.5.1: QUERYPARAM copies the value from the
|
|
202
|
+
// named query parameter of the playlist's own URL. Ignore
|
|
203
|
+
// silently if the parameter is absent.
|
|
204
|
+
const queryValue = queryParams?.[queryParamName];
|
|
205
|
+
if (queryValue !== undefined) {
|
|
206
|
+
defines[queryParamName] = queryValue;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
else if (attrs['NAME'] && attrs['VALUE']) {
|
|
210
|
+
defines[attrs['NAME']] = attrs['VALUE'];
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
else if (trimmed.startsWith(EXT_X_DATERANGE)) {
|
|
214
|
+
const dateRange = parseHlsDateRange(trimmed.substring(EXT_X_DATERANGE.length), substituteVars);
|
|
215
|
+
if (dateRange)
|
|
216
|
+
dateRanges.push(dateRange);
|
|
217
|
+
}
|
|
171
218
|
// Unrecognized tags and comments are silently skipped
|
|
172
219
|
}
|
|
173
220
|
return {
|
|
@@ -177,6 +224,63 @@ export function parseMediaPlaylist(text, variables) {
|
|
|
177
224
|
playlistType,
|
|
178
225
|
ended,
|
|
179
226
|
segments,
|
|
227
|
+
dateRanges,
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Attribute names that map to typed {@link HlsDateRange} fields and are therefore
|
|
232
|
+
* excluded from the free-form {@link HlsDateRange.clientAttributes} bag.
|
|
233
|
+
*/
|
|
234
|
+
const DATERANGE_RESERVED_ATTRS = new Set([
|
|
235
|
+
'ID',
|
|
236
|
+
'CLASS',
|
|
237
|
+
'START-DATE',
|
|
238
|
+
'END-DATE',
|
|
239
|
+
'DURATION',
|
|
240
|
+
'PLANNED-DURATION',
|
|
241
|
+
'END-ON-NEXT',
|
|
242
|
+
]);
|
|
243
|
+
/**
|
|
244
|
+
* Parses the attribute list of an EXT-X-DATERANGE tag. Returns undefined when
|
|
245
|
+
* the tag is missing the required ID or START-DATE attributes (per RFC 8216
|
|
246
|
+
* §4.3.2.7 a Date Range without both is not usable), so malformed tags are
|
|
247
|
+
* skipped rather than throwing.
|
|
248
|
+
*
|
|
249
|
+
* The `X-` client-defined attributes are collected verbatim into
|
|
250
|
+
* {@link HlsDateRange.clientAttributes}. Interstitial attributes such as
|
|
251
|
+
* `X-ASSET-URI` frequently reference EXT-X-DEFINE variables, so string values
|
|
252
|
+
* are passed through the same substitution applied to segment URIs.
|
|
253
|
+
*/
|
|
254
|
+
function parseHlsDateRange(attrStr, substituteVars) {
|
|
255
|
+
const attrs = parseAttributes(new StringReader(attrStr));
|
|
256
|
+
const id = attrs['ID'];
|
|
257
|
+
// START-DATE may be absent (the Record index type hides this), so gate on
|
|
258
|
+
// key presence rather than a nullish fallback.
|
|
259
|
+
const startDate = 'START-DATE' in attrs ? attrs['START-DATE'] : '';
|
|
260
|
+
// END-ON-NEXT ranges anchor to the following range and may legitimately
|
|
261
|
+
// omit START-DATE; RFC requires ID and CLASS in that case.
|
|
262
|
+
const endOnNext = attrs['END-ON-NEXT'] === 'YES';
|
|
263
|
+
if (!id)
|
|
264
|
+
return undefined;
|
|
265
|
+
if (!startDate && !endOnNext)
|
|
266
|
+
return undefined;
|
|
267
|
+
const clientAttributes = {};
|
|
268
|
+
for (const [key, value] of Object.entries(attrs)) {
|
|
269
|
+
if (DATERANGE_RESERVED_ATTRS.has(key))
|
|
270
|
+
continue;
|
|
271
|
+
clientAttributes[key] = substituteVars(value);
|
|
272
|
+
}
|
|
273
|
+
return {
|
|
274
|
+
id,
|
|
275
|
+
startDate,
|
|
276
|
+
...(attrs['CLASS'] && { classId: attrs['CLASS'] }),
|
|
277
|
+
...(attrs['END-DATE'] && { endDate: attrs['END-DATE'] }),
|
|
278
|
+
...(attrs['DURATION'] && { duration: Number(attrs['DURATION']) }),
|
|
279
|
+
...(attrs['PLANNED-DURATION'] && {
|
|
280
|
+
plannedDuration: Number(attrs['PLANNED-DURATION']),
|
|
281
|
+
}),
|
|
282
|
+
...(endOnNext && { endOnNext: true }),
|
|
283
|
+
clientAttributes,
|
|
180
284
|
};
|
|
181
285
|
}
|
|
182
286
|
/**
|