@amazon/vinyl-hls-parser 1.0.0
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/index.js +64 -0
- package/dist/hlsTestAssets/index.js.map +7 -0
- package/dist/hlsTestAssets/types/current/hls_mainPlaylist.d.ts +5 -0
- package/dist/hlsTestAssets/types/current/hls_mainPlaylist.d.ts.map +1 -0
- package/dist/hlsTestAssets/types/current/hls_mainPlaylist.js +21 -0
- package/dist/hlsTestAssets/types/current/hls_mediaPlaylist.d.ts +5 -0
- package/dist/hlsTestAssets/types/current/hls_mediaPlaylist.d.ts.map +1 -0
- package/dist/hlsTestAssets/types/current/hls_mediaPlaylist.js +29 -0
- package/dist/hlsTestAssets/types/current/hls_mediaPlaylistWithByteRanges.d.ts +5 -0
- package/dist/hlsTestAssets/types/current/hls_mediaPlaylistWithByteRanges.d.ts.map +1 -0
- package/dist/hlsTestAssets/types/current/hls_mediaPlaylistWithByteRanges.js +24 -0
- package/dist/hlsTestAssets/types/current/index.d.ts +5 -0
- package/dist/hlsTestAssets/types/current/index.d.ts.map +1 -0
- package/dist/hlsTestAssets/types/current/index.js +4 -0
- package/dist/hlsTestAssets/types/current/tsconfig.tsbuildinfo +1 -0
- package/dist/hlsTestAssets/types/ts3.7/hls_mainPlaylist.d.ts +5 -0
- package/dist/hlsTestAssets/types/ts3.7/hls_mediaPlaylist.d.ts +5 -0
- package/dist/hlsTestAssets/types/ts3.7/hls_mediaPlaylistWithByteRanges.d.ts +5 -0
- package/dist/hlsTestAssets/types/ts3.7/index.d.ts +5 -0
- package/dist/hlsTestAssets/types/ts4.1/hls_mainPlaylist.d.ts +5 -0
- package/dist/hlsTestAssets/types/ts4.1/hls_mediaPlaylist.d.ts +5 -0
- package/dist/hlsTestAssets/types/ts4.1/hls_mediaPlaylistWithByteRanges.d.ts +5 -0
- package/dist/hlsTestAssets/types/ts4.1/index.d.ts +5 -0
- package/dist/hlsTestAssets/types/ts4.7/hls_mainPlaylist.d.ts +5 -0
- package/dist/hlsTestAssets/types/ts4.7/hls_mediaPlaylist.d.ts +5 -0
- package/dist/hlsTestAssets/types/ts4.7/hls_mediaPlaylistWithByteRanges.d.ts +5 -0
- package/dist/hlsTestAssets/types/ts4.7/index.d.ts +5 -0
- package/dist/index.cjs +412 -0
- package/dist/index.cjs.map +7 -0
- package/dist/index.js +392 -0
- package/dist/index.js.map +7 -0
- package/dist/test/benchmark.css +71 -0
- package/dist/test/benchmark.es5.js +426 -0
- package/dist/test/benchmark.html +67 -0
- package/dist/test/benchmark.js +2677 -0
- package/dist/test/benchmark.js.map +7 -0
- package/dist/test/favicon.png +0 -0
- package/dist/test/index.html +61 -0
- package/dist/test/integ.es5.js +410 -0
- package/dist/test/integ.js +2127 -0
- package/dist/test/integ.js.map +7 -0
- package/dist/test/jasmine.css +304 -0
- package/dist/test/jasmineHtml.es5.js +670 -0
- package/dist/test/jasmineHtml.js +10851 -0
- package/dist/test/jasmineHtml.js.map +7 -0
- package/dist/test/styles.css +42 -0
- package/dist/types/current/index.d.ts +13 -0
- package/dist/types/current/index.d.ts.map +1 -0
- package/dist/types/current/index.js +12 -0
- package/dist/types/current/parse/parseAttributes.d.ts +7 -0
- package/dist/types/current/parse/parseAttributes.d.ts.map +1 -0
- package/dist/types/current/parse/parseAttributes.js +38 -0
- package/dist/types/current/parse/parseMainPlaylist.d.ts +10 -0
- package/dist/types/current/parse/parseMainPlaylist.d.ts.map +1 -0
- package/dist/types/current/parse/parseMainPlaylist.js +131 -0
- package/dist/types/current/parse/parseMediaPlaylist.d.ts +12 -0
- package/dist/types/current/parse/parseMediaPlaylist.d.ts.map +1 -0
- package/dist/types/current/parse/parseMediaPlaylist.js +191 -0
- package/dist/types/current/parse/parseUtil.d.ts +17 -0
- package/dist/types/current/parse/parseUtil.d.ts.map +1 -0
- package/dist/types/current/parse/parseUtil.js +51 -0
- package/dist/types/current/tsconfig.tsbuildinfo +1 -0
- package/dist/types/current/types/AlternativeRendition.d.ts +34 -0
- package/dist/types/current/types/AlternativeRendition.d.ts.map +1 -0
- package/dist/types/current/types/AlternativeRendition.js +6 -0
- package/dist/types/current/types/EncryptionKey.d.ts +28 -0
- package/dist/types/current/types/EncryptionKey.d.ts.map +1 -0
- package/dist/types/current/types/EncryptionKey.js +6 -0
- package/dist/types/current/types/HlsSegment.d.ts +46 -0
- package/dist/types/current/types/HlsSegment.d.ts.map +1 -0
- package/dist/types/current/types/HlsSegment.js +5 -0
- package/dist/types/current/types/MainPlaylist.d.ts +22 -0
- package/dist/types/current/types/MainPlaylist.d.ts.map +1 -0
- package/dist/types/current/types/MainPlaylist.js +5 -0
- package/dist/types/current/types/MediaPlaylist.d.ts +25 -0
- package/dist/types/current/types/MediaPlaylist.d.ts.map +1 -0
- package/dist/types/current/types/MediaPlaylist.js +5 -0
- package/dist/types/current/types/SessionData.d.ts +19 -0
- package/dist/types/current/types/SessionData.d.ts.map +1 -0
- package/dist/types/current/types/SessionData.js +5 -0
- package/dist/types/current/types/VariantStream.d.ts +31 -0
- package/dist/types/current/types/VariantStream.d.ts.map +1 -0
- package/dist/types/current/types/VariantStream.js +5 -0
- package/dist/types/ts3.7/index.d.ts +13 -0
- package/dist/types/ts3.7/parse/parseAttributes.d.ts +7 -0
- package/dist/types/ts3.7/parse/parseMainPlaylist.d.ts +10 -0
- package/dist/types/ts3.7/parse/parseMediaPlaylist.d.ts +12 -0
- package/dist/types/ts3.7/parse/parseUtil.d.ts +17 -0
- package/dist/types/ts3.7/types/AlternativeRendition.d.ts +34 -0
- package/dist/types/ts3.7/types/EncryptionKey.d.ts +28 -0
- package/dist/types/ts3.7/types/HlsSegment.d.ts +46 -0
- package/dist/types/ts3.7/types/MainPlaylist.d.ts +22 -0
- package/dist/types/ts3.7/types/MediaPlaylist.d.ts +25 -0
- package/dist/types/ts3.7/types/SessionData.d.ts +19 -0
- package/dist/types/ts3.7/types/VariantStream.d.ts +31 -0
- package/dist/types/ts4.1/index.d.ts +13 -0
- package/dist/types/ts4.1/parse/parseAttributes.d.ts +7 -0
- package/dist/types/ts4.1/parse/parseMainPlaylist.d.ts +10 -0
- package/dist/types/ts4.1/parse/parseMediaPlaylist.d.ts +12 -0
- package/dist/types/ts4.1/parse/parseUtil.d.ts +17 -0
- package/dist/types/ts4.1/types/AlternativeRendition.d.ts +34 -0
- package/dist/types/ts4.1/types/EncryptionKey.d.ts +28 -0
- package/dist/types/ts4.1/types/HlsSegment.d.ts +46 -0
- package/dist/types/ts4.1/types/MainPlaylist.d.ts +22 -0
- package/dist/types/ts4.1/types/MediaPlaylist.d.ts +25 -0
- package/dist/types/ts4.1/types/SessionData.d.ts +19 -0
- package/dist/types/ts4.1/types/VariantStream.d.ts +31 -0
- package/dist/types/ts4.7/index.d.ts +13 -0
- package/dist/types/ts4.7/parse/parseAttributes.d.ts +7 -0
- package/dist/types/ts4.7/parse/parseMainPlaylist.d.ts +10 -0
- package/dist/types/ts4.7/parse/parseMediaPlaylist.d.ts +12 -0
- package/dist/types/ts4.7/parse/parseUtil.d.ts +17 -0
- package/dist/types/ts4.7/types/AlternativeRendition.d.ts +34 -0
- package/dist/types/ts4.7/types/EncryptionKey.d.ts +28 -0
- package/dist/types/ts4.7/types/HlsSegment.d.ts +46 -0
- package/dist/types/ts4.7/types/MainPlaylist.d.ts +22 -0
- package/dist/types/ts4.7/types/MediaPlaylist.d.ts +25 -0
- package/dist/types/ts4.7/types/SessionData.d.ts +19 -0
- package/dist/types/ts4.7/types/VariantStream.d.ts +31 -0
- package/dist/unit/types/current/index.d.ts +7 -0
- package/dist/unit/types/current/index.d.ts.map +1 -0
- package/dist/unit/types/current/index.js +6 -0
- package/dist/unit/types/current/parse/parseAttributes.test.d.ts +2 -0
- package/dist/unit/types/current/parse/parseAttributes.test.d.ts.map +1 -0
- package/dist/unit/types/current/parse/parseAttributes.test.js +70 -0
- package/dist/unit/types/current/parse/parseMainPlaylist.test.d.ts +2 -0
- package/dist/unit/types/current/parse/parseMainPlaylist.test.d.ts.map +1 -0
- package/dist/unit/types/current/parse/parseMainPlaylist.test.js +194 -0
- package/dist/unit/types/current/parse/parseMediaPlaylist.test.d.ts +2 -0
- package/dist/unit/types/current/parse/parseMediaPlaylist.test.d.ts.map +1 -0
- package/dist/unit/types/current/parse/parseMediaPlaylist.test.js +401 -0
- package/dist/unit/types/current/parse/parseUtil.test.d.ts +2 -0
- package/dist/unit/types/current/parse/parseUtil.test.d.ts.map +1 -0
- package/dist/unit/types/current/parse/parseUtil.test.js +67 -0
- package/dist/unit/types/current/setup.d.ts +2 -0
- package/dist/unit/types/current/setup.d.ts.map +1 -0
- package/dist/unit/types/current/setup.js +19 -0
- package/dist/unit/types/current/tsconfig.tsbuildinfo +1 -0
- package/package.json +88 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __defProps = Object.defineProperties;
|
|
3
|
+
var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
|
|
4
|
+
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __propIsEnum = Object.prototype.propertyIsEnumerable;
|
|
7
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
8
|
+
var __spreadValues = (a, b) => {
|
|
9
|
+
for (var prop in b || (b = {}))
|
|
10
|
+
if (__hasOwnProp.call(b, prop))
|
|
11
|
+
__defNormalProp(a, prop, b[prop]);
|
|
12
|
+
if (__getOwnPropSymbols)
|
|
13
|
+
for (var prop of __getOwnPropSymbols(b)) {
|
|
14
|
+
if (__propIsEnum.call(b, prop))
|
|
15
|
+
__defNormalProp(a, prop, b[prop]);
|
|
16
|
+
}
|
|
17
|
+
return a;
|
|
18
|
+
};
|
|
19
|
+
var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
|
|
20
|
+
|
|
21
|
+
// src/types/EncryptionKey.ts
|
|
22
|
+
import { isOneOf } from "@amazon/vinyl-validation";
|
|
23
|
+
var encryptionMethodValidator = /* @__PURE__ */ isOneOf(
|
|
24
|
+
"NONE",
|
|
25
|
+
"AES-128",
|
|
26
|
+
"SAMPLE-AES",
|
|
27
|
+
"SAMPLE-AES-CTR"
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
// src/types/AlternativeRendition.ts
|
|
31
|
+
import { isOneOf as isOneOf2 } from "@amazon/vinyl-validation";
|
|
32
|
+
var renditionTypeValidator = /* @__PURE__ */ isOneOf2(
|
|
33
|
+
"AUDIO",
|
|
34
|
+
"VIDEO",
|
|
35
|
+
"SUBTITLES",
|
|
36
|
+
"CLOSED-CAPTIONS"
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
// src/parse/parseUtil.ts
|
|
40
|
+
import { char, isWhitespaceChar } from "@amazon/vinyl-util";
|
|
41
|
+
var LF = /* @__PURE__ */ char("\n");
|
|
42
|
+
var CR = /* @__PURE__ */ char("\r");
|
|
43
|
+
function readLine(reader) {
|
|
44
|
+
const line = reader.substringUntil((cc) => cc === LF || cc === CR);
|
|
45
|
+
if (reader.hasNext() && reader.peek === CR) reader.next();
|
|
46
|
+
if (reader.hasNext() && reader.peek === LF) reader.next();
|
|
47
|
+
return line;
|
|
48
|
+
}
|
|
49
|
+
function skipWhitespaceLine(reader) {
|
|
50
|
+
const start = reader.position;
|
|
51
|
+
reader.while((cc) => isWhitespaceChar(cc) && cc !== LF && cc !== CR);
|
|
52
|
+
if (!reader.hasNext() || reader.peek === LF || reader.peek === CR) {
|
|
53
|
+
if (reader.hasNext()) {
|
|
54
|
+
if (reader.peek === CR) reader.next();
|
|
55
|
+
if (reader.hasNext() && reader.peek === LF) reader.next();
|
|
56
|
+
}
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
reader.position = start;
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
function addIfPresent(obj, attrs, mappings) {
|
|
63
|
+
for (const [attrKey, objKey] of Object.entries(mappings)) {
|
|
64
|
+
if (attrs[attrKey]) {
|
|
65
|
+
;
|
|
66
|
+
obj[objKey] = attrs[attrKey];
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return obj;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// src/parse/parseMediaPlaylist.ts
|
|
73
|
+
import { StringReader, substitute } from "@amazon/vinyl-util";
|
|
74
|
+
|
|
75
|
+
// src/parse/parseAttributes.ts
|
|
76
|
+
import { char as char2 } from "@amazon/vinyl-util";
|
|
77
|
+
var EQUALS = /* @__PURE__ */ char2("=");
|
|
78
|
+
var COMMA = /* @__PURE__ */ char2(",");
|
|
79
|
+
var QUOTE = /* @__PURE__ */ char2('"');
|
|
80
|
+
function parseAttributes(reader) {
|
|
81
|
+
const attrs = {};
|
|
82
|
+
while (reader.hasNext() && reader.peek !== char2("\n") && reader.peek !== char2("\r")) {
|
|
83
|
+
const key = reader.substringUntilChar(EQUALS);
|
|
84
|
+
if (!key || !reader.hasNext()) break;
|
|
85
|
+
reader.next();
|
|
86
|
+
let value;
|
|
87
|
+
if (reader.peek === QUOTE) {
|
|
88
|
+
reader.next();
|
|
89
|
+
value = reader.substringUntilChar(QUOTE);
|
|
90
|
+
if (reader.hasNext()) reader.next();
|
|
91
|
+
} else {
|
|
92
|
+
value = reader.substringUntil(
|
|
93
|
+
(cc) => cc === COMMA || cc === char2("\n") || cc === char2("\r")
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
attrs[key] = value;
|
|
97
|
+
if (reader.hasNext() && reader.peek === COMMA) {
|
|
98
|
+
reader.next();
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return attrs;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// src/parse/parseMediaPlaylist.ts
|
|
105
|
+
var HLS_VARIABLE_PATTERN = /\{\$([^}]+)}/g;
|
|
106
|
+
var EXTM3U = "#EXTM3U";
|
|
107
|
+
var EXTINF = "#EXTINF:";
|
|
108
|
+
var EXT_X_TARGETDURATION = "#EXT-X-TARGETDURATION:";
|
|
109
|
+
var EXT_X_MEDIA_SEQUENCE = "#EXT-X-MEDIA-SEQUENCE:";
|
|
110
|
+
var EXT_X_ENDLIST = "#EXT-X-ENDLIST";
|
|
111
|
+
var EXT_X_PLAYLIST_TYPE = "#EXT-X-PLAYLIST-TYPE:";
|
|
112
|
+
var EXT_X_VERSION = "#EXT-X-VERSION:";
|
|
113
|
+
var EXT_X_KEY = "#EXT-X-KEY:";
|
|
114
|
+
var EXT_X_BYTERANGE = "#EXT-X-BYTERANGE:";
|
|
115
|
+
var EXT_X_DISCONTINUITY = "#EXT-X-DISCONTINUITY";
|
|
116
|
+
var EXT_X_PROGRAM_DATE_TIME = "#EXT-X-PROGRAM-DATE-TIME:";
|
|
117
|
+
var EXT_X_MAP = "#EXT-X-MAP:";
|
|
118
|
+
function parseMediaPlaylist(text, variables) {
|
|
119
|
+
const reader = new StringReader(text);
|
|
120
|
+
reader.white();
|
|
121
|
+
if (!reader.stringIf(EXTM3U)) {
|
|
122
|
+
reader.err("Expected #EXTM3U");
|
|
123
|
+
}
|
|
124
|
+
readLine(reader);
|
|
125
|
+
let version = 1;
|
|
126
|
+
let targetDuration = 0;
|
|
127
|
+
let mediaSequence = 0;
|
|
128
|
+
let playlistType = "LIVE";
|
|
129
|
+
let ended = false;
|
|
130
|
+
let currentKey;
|
|
131
|
+
let currentMap;
|
|
132
|
+
let pendingByteRange;
|
|
133
|
+
let pendingDiscontinuity = false;
|
|
134
|
+
let pendingDateTime;
|
|
135
|
+
const segments = [];
|
|
136
|
+
let sequenceCounter = 0;
|
|
137
|
+
while (reader.hasNext()) {
|
|
138
|
+
if (skipWhitespaceLine(reader)) continue;
|
|
139
|
+
const line = readLine(reader);
|
|
140
|
+
const trimmed = line.trim();
|
|
141
|
+
if (!trimmed) continue;
|
|
142
|
+
if (trimmed.startsWith(EXTINF)) {
|
|
143
|
+
const durationStr = trimmed.substring(EXTINF.length).split(",")[0];
|
|
144
|
+
const duration = Number(durationStr);
|
|
145
|
+
let uri = "";
|
|
146
|
+
while (reader.hasNext()) {
|
|
147
|
+
if (skipWhitespaceLine(reader)) continue;
|
|
148
|
+
const nextLine = readLine(reader).trim();
|
|
149
|
+
if (!nextLine.startsWith("#")) {
|
|
150
|
+
uri = variables ? substitute(nextLine, variables, HLS_VARIABLE_PATTERN) : nextLine;
|
|
151
|
+
break;
|
|
152
|
+
}
|
|
153
|
+
if (nextLine.startsWith(EXT_X_BYTERANGE)) {
|
|
154
|
+
const rangeStr = nextLine.substring(EXT_X_BYTERANGE.length);
|
|
155
|
+
pendingByteRange = parseByteRangeString(rangeStr);
|
|
156
|
+
} else if (nextLine === EXT_X_DISCONTINUITY) {
|
|
157
|
+
pendingDiscontinuity = true;
|
|
158
|
+
} else if (nextLine.startsWith(EXT_X_PROGRAM_DATE_TIME)) {
|
|
159
|
+
pendingDateTime = nextLine.substring(
|
|
160
|
+
EXT_X_PROGRAM_DATE_TIME.length
|
|
161
|
+
);
|
|
162
|
+
} else if (nextLine.startsWith(EXT_X_MAP)) {
|
|
163
|
+
const attrStr = nextLine.substring(EXT_X_MAP.length);
|
|
164
|
+
const attrReader = new StringReader(attrStr);
|
|
165
|
+
const attrs = parseAttributes(attrReader);
|
|
166
|
+
const mapUri = attrs["URI"];
|
|
167
|
+
if (mapUri) {
|
|
168
|
+
const byteRangeStr = attrs["BYTERANGE"];
|
|
169
|
+
currentMap = __spreadValues({
|
|
170
|
+
uri: mapUri
|
|
171
|
+
}, byteRangeStr && {
|
|
172
|
+
byteRange: parseByteRangeString(byteRangeStr)
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
segments.push(__spreadValues(__spreadProps(__spreadValues(__spreadValues(__spreadValues({
|
|
178
|
+
uri,
|
|
179
|
+
duration,
|
|
180
|
+
sequenceNumber: mediaSequence + sequenceCounter
|
|
181
|
+
}, currentKey && { key: currentKey }), currentMap && { map: currentMap }), pendingByteRange && { byteRange: pendingByteRange }), {
|
|
182
|
+
discontinuity: pendingDiscontinuity
|
|
183
|
+
}), pendingDateTime && { programDateTime: pendingDateTime }));
|
|
184
|
+
sequenceCounter++;
|
|
185
|
+
pendingByteRange = void 0;
|
|
186
|
+
pendingDiscontinuity = false;
|
|
187
|
+
pendingDateTime = void 0;
|
|
188
|
+
} else if (trimmed.startsWith(EXT_X_TARGETDURATION)) {
|
|
189
|
+
targetDuration = Number(
|
|
190
|
+
trimmed.substring(EXT_X_TARGETDURATION.length)
|
|
191
|
+
);
|
|
192
|
+
} else if (trimmed.startsWith(EXT_X_MEDIA_SEQUENCE)) {
|
|
193
|
+
mediaSequence = Number(
|
|
194
|
+
trimmed.substring(EXT_X_MEDIA_SEQUENCE.length)
|
|
195
|
+
);
|
|
196
|
+
} else if (trimmed === EXT_X_ENDLIST) {
|
|
197
|
+
ended = true;
|
|
198
|
+
} else if (trimmed.startsWith(EXT_X_PLAYLIST_TYPE)) {
|
|
199
|
+
const val = trimmed.substring(EXT_X_PLAYLIST_TYPE.length).trim();
|
|
200
|
+
if (val === "VOD" || val === "EVENT") {
|
|
201
|
+
playlistType = val;
|
|
202
|
+
}
|
|
203
|
+
} else if (trimmed.startsWith(EXT_X_VERSION)) {
|
|
204
|
+
version = Number(trimmed.substring(EXT_X_VERSION.length));
|
|
205
|
+
} else if (trimmed.startsWith(EXT_X_KEY)) {
|
|
206
|
+
const attrStr = trimmed.substring(EXT_X_KEY.length);
|
|
207
|
+
const attrReader = new StringReader(attrStr);
|
|
208
|
+
const attrs = parseAttributes(attrReader);
|
|
209
|
+
const method = attrs["METHOD"];
|
|
210
|
+
encryptionMethodValidator.assert(method);
|
|
211
|
+
currentKey = addIfPresent(
|
|
212
|
+
{
|
|
213
|
+
method
|
|
214
|
+
},
|
|
215
|
+
attrs,
|
|
216
|
+
{
|
|
217
|
+
URI: "uri",
|
|
218
|
+
IV: "iv",
|
|
219
|
+
KEYFORMAT: "keyFormat",
|
|
220
|
+
KEYFORMATVERSIONS: "keyFormatVersions"
|
|
221
|
+
}
|
|
222
|
+
);
|
|
223
|
+
} else if (trimmed.startsWith(EXT_X_MAP)) {
|
|
224
|
+
const attrStr = trimmed.substring(EXT_X_MAP.length);
|
|
225
|
+
const attrReader = new StringReader(attrStr);
|
|
226
|
+
const attrs = parseAttributes(attrReader);
|
|
227
|
+
const mapUri = attrs["URI"];
|
|
228
|
+
if (mapUri) {
|
|
229
|
+
const byteRangeStr = attrs["BYTERANGE"];
|
|
230
|
+
currentMap = __spreadValues({
|
|
231
|
+
uri: mapUri
|
|
232
|
+
}, byteRangeStr && {
|
|
233
|
+
byteRange: parseByteRangeString(byteRangeStr)
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
} else if (trimmed.startsWith(EXT_X_BYTERANGE)) {
|
|
237
|
+
const rangeStr = trimmed.substring(EXT_X_BYTERANGE.length);
|
|
238
|
+
pendingByteRange = parseByteRangeString(rangeStr);
|
|
239
|
+
} else if (trimmed === EXT_X_DISCONTINUITY) {
|
|
240
|
+
pendingDiscontinuity = true;
|
|
241
|
+
} else if (trimmed.startsWith(EXT_X_PROGRAM_DATE_TIME)) {
|
|
242
|
+
pendingDateTime = trimmed.substring(EXT_X_PROGRAM_DATE_TIME.length);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
return {
|
|
246
|
+
version,
|
|
247
|
+
targetDuration,
|
|
248
|
+
mediaSequence,
|
|
249
|
+
playlistType,
|
|
250
|
+
ended,
|
|
251
|
+
segments
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
function parseByteRangeString(str) {
|
|
255
|
+
const parts = str.split("@");
|
|
256
|
+
return {
|
|
257
|
+
length: Number(parts[0]),
|
|
258
|
+
offset: parts.length > 1 ? Number(parts[1]) : 0
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// src/parse/parseMainPlaylist.ts
|
|
263
|
+
import { StringReader as StringReader2, ValidationError } from "@amazon/vinyl-util";
|
|
264
|
+
var EXTM3U2 = "#EXTM3U";
|
|
265
|
+
var EXT_X_STREAM_INF = "#EXT-X-STREAM-INF:";
|
|
266
|
+
var EXT_X_MEDIA = "#EXT-X-MEDIA:";
|
|
267
|
+
var EXT_X_SESSION_DATA = "#EXT-X-SESSION-DATA:";
|
|
268
|
+
var EXT_X_DEFINE = "#EXT-X-DEFINE:";
|
|
269
|
+
function parseMainPlaylist(text) {
|
|
270
|
+
const reader = new StringReader2(text);
|
|
271
|
+
reader.white();
|
|
272
|
+
if (!reader.stringIf(EXTM3U2)) {
|
|
273
|
+
reader.err("Expected #EXTM3U");
|
|
274
|
+
}
|
|
275
|
+
readLine(reader);
|
|
276
|
+
const variants = [];
|
|
277
|
+
const alternativeRenditions = [];
|
|
278
|
+
const sessionData = [];
|
|
279
|
+
const defines = {};
|
|
280
|
+
while (reader.hasNext()) {
|
|
281
|
+
if (skipWhitespaceLine(reader)) continue;
|
|
282
|
+
const line = readLine(reader);
|
|
283
|
+
const trimmed = line.trim();
|
|
284
|
+
if (!trimmed) continue;
|
|
285
|
+
if (trimmed.startsWith(EXT_X_STREAM_INF)) {
|
|
286
|
+
const attrStr = trimmed.substring(EXT_X_STREAM_INF.length);
|
|
287
|
+
const attrReader = new StringReader2(attrStr);
|
|
288
|
+
const attrs = parseAttributes(attrReader);
|
|
289
|
+
let uri = "";
|
|
290
|
+
while (reader.hasNext()) {
|
|
291
|
+
if (skipWhitespaceLine(reader)) continue;
|
|
292
|
+
uri = readLine(reader).trim();
|
|
293
|
+
break;
|
|
294
|
+
}
|
|
295
|
+
variants.push(
|
|
296
|
+
addIfPresent(
|
|
297
|
+
__spreadValues(__spreadValues({
|
|
298
|
+
bandwidth: Number(attrs["BANDWIDTH"]),
|
|
299
|
+
uri
|
|
300
|
+
}, attrs["FRAME-RATE"] && {
|
|
301
|
+
frameRate: Number(attrs["FRAME-RATE"])
|
|
302
|
+
}), attrs.RESOLUTION && parseResolution(attrs.RESOLUTION)),
|
|
303
|
+
attrs,
|
|
304
|
+
{
|
|
305
|
+
CODECS: "codecs",
|
|
306
|
+
AUDIO: "audioGroup",
|
|
307
|
+
VIDEO: "videoGroup",
|
|
308
|
+
SUBTITLES: "subtitlesGroup",
|
|
309
|
+
"CLOSED-CAPTIONS": "closedCaptionsGroup"
|
|
310
|
+
}
|
|
311
|
+
)
|
|
312
|
+
);
|
|
313
|
+
} else if (trimmed.startsWith(EXT_X_MEDIA)) {
|
|
314
|
+
const attrStr = trimmed.substring(EXT_X_MEDIA.length);
|
|
315
|
+
const attrReader = new StringReader2(attrStr);
|
|
316
|
+
const attrs = parseAttributes(attrReader);
|
|
317
|
+
const type = attrs["TYPE"];
|
|
318
|
+
renditionTypeValidator.assert(type);
|
|
319
|
+
alternativeRenditions.push(
|
|
320
|
+
addIfPresent(
|
|
321
|
+
__spreadValues(__spreadValues({
|
|
322
|
+
type,
|
|
323
|
+
groupId: attrs["GROUP-ID"],
|
|
324
|
+
name: attrs["NAME"]
|
|
325
|
+
}, attrs["DEFAULT"] && {
|
|
326
|
+
default: attrs["DEFAULT"] === "YES"
|
|
327
|
+
}), attrs["AUTOSELECT"] && {
|
|
328
|
+
autoSelect: attrs["AUTOSELECT"] === "YES"
|
|
329
|
+
}),
|
|
330
|
+
attrs,
|
|
331
|
+
{
|
|
332
|
+
URI: "uri",
|
|
333
|
+
LANGUAGE: "language"
|
|
334
|
+
}
|
|
335
|
+
)
|
|
336
|
+
);
|
|
337
|
+
} else if (trimmed.startsWith(EXT_X_SESSION_DATA)) {
|
|
338
|
+
const attrStr = trimmed.substring(EXT_X_SESSION_DATA.length);
|
|
339
|
+
const attrReader = new StringReader2(attrStr);
|
|
340
|
+
const attrs = parseAttributes(attrReader);
|
|
341
|
+
sessionData.push(
|
|
342
|
+
addIfPresent(
|
|
343
|
+
{
|
|
344
|
+
dataId: attrs["DATA-ID"]
|
|
345
|
+
},
|
|
346
|
+
attrs,
|
|
347
|
+
{
|
|
348
|
+
VALUE: "value",
|
|
349
|
+
URI: "uri",
|
|
350
|
+
LANGUAGE: "language"
|
|
351
|
+
}
|
|
352
|
+
)
|
|
353
|
+
);
|
|
354
|
+
} else if (trimmed.startsWith(EXT_X_DEFINE)) {
|
|
355
|
+
const attrStr = trimmed.substring(EXT_X_DEFINE.length);
|
|
356
|
+
const attrReader = new StringReader2(attrStr);
|
|
357
|
+
const attrs = parseAttributes(attrReader);
|
|
358
|
+
if (attrs["NAME"] && attrs["VALUE"]) {
|
|
359
|
+
defines[attrs["NAME"]] = attrs["VALUE"];
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
return { variants, alternativeRenditions, sessionData, defines };
|
|
364
|
+
}
|
|
365
|
+
function parseResolution(resolution) {
|
|
366
|
+
const parts = resolution.split("x");
|
|
367
|
+
if (parts.length !== 2) {
|
|
368
|
+
throw new ValidationError(
|
|
369
|
+
"Invalid resolution format, expected WIDTHxHEIGHT"
|
|
370
|
+
);
|
|
371
|
+
}
|
|
372
|
+
const width = Number(parts[0]);
|
|
373
|
+
const height = Number(parts[1]);
|
|
374
|
+
if (!Number.isInteger(width) || !Number.isInteger(height) || width <= 0 || height <= 0) {
|
|
375
|
+
throw new ValidationError(
|
|
376
|
+
"Invalid resolution values, width and height must be positive integers"
|
|
377
|
+
);
|
|
378
|
+
}
|
|
379
|
+
return { width, height };
|
|
380
|
+
}
|
|
381
|
+
export {
|
|
382
|
+
HLS_VARIABLE_PATTERN,
|
|
383
|
+
addIfPresent,
|
|
384
|
+
encryptionMethodValidator,
|
|
385
|
+
parseAttributes,
|
|
386
|
+
parseMainPlaylist,
|
|
387
|
+
parseMediaPlaylist,
|
|
388
|
+
readLine,
|
|
389
|
+
renditionTypeValidator,
|
|
390
|
+
skipWhitespaceLine
|
|
391
|
+
};
|
|
392
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/types/EncryptionKey.ts", "../src/types/AlternativeRendition.ts", "../src/parse/parseUtil.ts", "../src/parse/parseMediaPlaylist.ts", "../src/parse/parseAttributes.ts", "../src/parse/parseMainPlaylist.ts"],
|
|
4
|
+
"sourcesContent": ["/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport type { ValueSchema } from '@amazon/vinyl-validation'\nimport { isOneOf } from '@amazon/vinyl-validation'\n\n/**\n * Encryption key metadata for an HLS media segment (EXT-X-KEY).\n *\n * Specifies how media segments are encrypted and provides the information needed to decrypt them.\n *\n * @see https://datatracker.ietf.org/doc/html/rfc8216#section-4.3.2.4\n */\nexport interface EncryptionKey {\n /** The encryption method applied to media segments (METHOD). */\n readonly method: EncryptionMethod\n\n /** The URI for obtaining the decryption key (URI). Required unless method is NONE. */\n readonly uri?: string\n\n /** The initialization vector for decryption as a hexadecimal string (IV). */\n readonly iv?: string\n\n /** The format of the key in the response (KEYFORMAT). Identifies the DRM system, e.g. FairPlay. */\n readonly keyFormat?: string\n\n /** A slash-separated list of integers indicating compatible key format versions (KEYFORMATVERSIONS). */\n readonly keyFormatVersions?: string\n}\n\n/**\n * The encryption method for an HLS segment (EXT-X-KEY METHOD attribute).\n *\n * @see https://datatracker.ietf.org/doc/html/rfc8216#section-4.3.2.4\n */\nexport type EncryptionMethod =\n | 'NONE'\n | 'AES-128'\n | 'SAMPLE-AES'\n | 'SAMPLE-AES-CTR'\n\nexport const encryptionMethodValidator: ValueSchema<EncryptionMethod> = /* @__PURE__ */ isOneOf(\n 'NONE',\n 'AES-128',\n 'SAMPLE-AES',\n 'SAMPLE-AES-CTR'\n)\n", "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport type { ValueSchema } from '@amazon/vinyl-validation'\nimport { isOneOf } from '@amazon/vinyl-validation'\n\n/**\n * An alternative rendition defined by EXT-X-MEDIA in an HLS Master Playlist.\n *\n * Alternative renditions allow a presentation to offer multiple versions of the same content,\n * such as audio in different languages or subtitles, that can be selected independently of the\n * variant stream.\n *\n * @see https://datatracker.ietf.org/doc/html/rfc8216#section-4.3.4.1\n */\nexport interface AlternativeRendition {\n /** The media type of this rendition (AUDIO, VIDEO, SUBTITLES, or CLOSED-CAPTIONS). */\n readonly type: RenditionType\n\n /** The group to which this rendition belongs (GROUP-ID). Variants reference groups to associate renditions. */\n readonly groupId: string\n\n /** A human-readable description of this rendition (NAME). */\n readonly name: string\n\n /** The URI of the Media Playlist for this rendition. Absent for CLOSED-CAPTIONS or when muxed into the variant. */\n readonly uri?: string\n\n /** The primary language of this rendition as a BCP 47 tag (LANGUAGE). */\n readonly language?: string\n\n /** Whether this rendition should be played when the user has not expressed a preference (DEFAULT). */\n readonly default?: boolean\n\n /** Whether the client may choose to play this rendition without explicit user selection (AUTOSELECT). */\n readonly autoSelect?: boolean\n}\n\n/**\n * The media type of an alternative rendition (EXT-X-MEDIA TYPE attribute).\n *\n * @see https://datatracker.ietf.org/doc/html/rfc8216#section-4.3.4.1\n */\nexport type RenditionType = 'AUDIO' | 'VIDEO' | 'SUBTITLES' | 'CLOSED-CAPTIONS'\n\nexport const renditionTypeValidator: ValueSchema<RenditionType> = /* @__PURE__ */ isOneOf(\n 'AUDIO',\n 'VIDEO',\n 'SUBTITLES',\n 'CLOSED-CAPTIONS'\n)\n", "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport { char, isWhitespaceChar, type StringReader } from '@amazon/vinyl-util'\n\nconst LF = /* @__PURE__ */ char('\\n')\nconst CR = /* @__PURE__ */ char('\\r')\n\n/**\n * Reads a line from the StringReader, advancing past the line ending.\n * Returns the line content without the line ending.\n */\nexport function readLine(reader: StringReader): string {\n const line = reader.substringUntil((cc) => cc === LF || cc === CR)\n if (reader.hasNext() && reader.peek === CR) reader.next()\n if (reader.hasNext() && reader.peek === LF) reader.next()\n return line\n}\n\n/**\n * Skips whitespace-only content at the current position up to and including\n * the next line ending. Returns true if a whitespace-only line was skipped.\n */\nexport function skipWhitespaceLine(reader: StringReader): boolean {\n const start = reader.position\n reader.while((cc) => isWhitespaceChar(cc) && cc !== LF && cc !== CR)\n if (!reader.hasNext() || reader.peek === LF || reader.peek === CR) {\n if (reader.hasNext()) {\n if (reader.peek === CR) reader.next()\n if (reader.hasNext() && reader.peek === LF) reader.next()\n }\n return true\n }\n reader.position = start\n return false\n}\n\n/**\n * Conditionally adds properties from an attribute map to an object.\n * Only adds a property if the corresponding attribute key exists and is truthy.\n */\nexport function addIfPresent<T extends Record<string, any>>(\n obj: T,\n attrs: Record<string, string>,\n mappings: Record<string, string>\n): T {\n for (const [attrKey, objKey] of Object.entries(mappings)) {\n if (attrs[attrKey]) {\n ;(obj as any)[objKey] = attrs[attrKey]\n }\n }\n return obj\n}\n", "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport { StringReader, substitute } from '@amazon/vinyl-util'\nimport type { EncryptionKey } from '@/types/EncryptionKey'\nimport { encryptionMethodValidator } from '@/types/EncryptionKey'\nimport type { HlsSegment, HlsMap } from '@/types/HlsSegment'\nimport type { MediaPlaylist } from '@/types/MediaPlaylist'\nimport { parseAttributes } from './parseAttributes'\nimport { readLine, skipWhitespaceLine, addIfPresent } from './parseUtil'\n\nexport const HLS_VARIABLE_PATTERN = /\\{\\$([^}]+)}/g\n\nconst EXTM3U = '#EXTM3U'\nconst EXTINF = '#EXTINF:'\nconst EXT_X_TARGETDURATION = '#EXT-X-TARGETDURATION:'\nconst EXT_X_MEDIA_SEQUENCE = '#EXT-X-MEDIA-SEQUENCE:'\nconst EXT_X_ENDLIST = '#EXT-X-ENDLIST'\nconst EXT_X_PLAYLIST_TYPE = '#EXT-X-PLAYLIST-TYPE:'\nconst EXT_X_VERSION = '#EXT-X-VERSION:'\nconst EXT_X_KEY = '#EXT-X-KEY:'\nconst EXT_X_BYTERANGE = '#EXT-X-BYTERANGE:'\nconst EXT_X_DISCONTINUITY = '#EXT-X-DISCONTINUITY'\nconst EXT_X_PROGRAM_DATE_TIME = '#EXT-X-PROGRAM-DATE-TIME:'\nconst EXT_X_MAP = '#EXT-X-MAP:'\n\n/**\n * Parses an HLS media playlist from M3U8 text.\n *\n * @param text The raw M3U8 manifest string.\n * @param variables Optional variable definitions from the master playlist's #EXT-X-DEFINE tags.\n * @returns A readonly MediaPlaylist structure.\n * @throws StringParseError if the manifest is malformed.\n */\nexport function parseMediaPlaylist(\n text: string,\n variables?: Readonly<Record<string, string>>\n): MediaPlaylist {\n const reader = new StringReader(text)\n\n reader.white()\n if (!reader.stringIf(EXTM3U)) {\n reader.err('Expected #EXTM3U')\n }\n readLine(reader)\n\n let version = 1\n let targetDuration = 0\n let mediaSequence = 0\n let playlistType: 'VOD' | 'EVENT' | 'LIVE' = 'LIVE'\n let ended = false\n\n let currentKey: EncryptionKey | undefined\n let currentMap: HlsMap | undefined\n let pendingByteRange: { length: number; offset: number } | undefined\n let pendingDiscontinuity = false\n let pendingDateTime: string | undefined\n\n const segments: HlsSegment[] = []\n let sequenceCounter = 0\n\n while (reader.hasNext()) {\n if (skipWhitespaceLine(reader)) continue\n\n const line = readLine(reader)\n const trimmed = line.trim()\n if (!trimmed) continue\n\n if (trimmed.startsWith(EXTINF)) {\n const durationStr = trimmed.substring(EXTINF.length).split(',')[0]\n const duration = Number(durationStr)\n\n // Find the URI, skipping any tags that come between EXTINF and URI\n let uri = ''\n while (reader.hasNext()) {\n if (skipWhitespaceLine(reader)) continue\n const nextLine = readLine(reader).trim()\n if (!nextLine.startsWith('#')) {\n uri = variables\n ? substitute(nextLine, variables, HLS_VARIABLE_PATTERN)\n : nextLine\n break\n }\n // Process tags that come between EXTINF and URI\n if (nextLine.startsWith(EXT_X_BYTERANGE)) {\n const rangeStr = nextLine.substring(EXT_X_BYTERANGE.length)\n pendingByteRange = parseByteRangeString(rangeStr)\n } else if (nextLine === EXT_X_DISCONTINUITY) {\n pendingDiscontinuity = true\n } else if (nextLine.startsWith(EXT_X_PROGRAM_DATE_TIME)) {\n pendingDateTime = nextLine.substring(\n EXT_X_PROGRAM_DATE_TIME.length\n )\n } else if (nextLine.startsWith(EXT_X_MAP)) {\n const attrStr = nextLine.substring(EXT_X_MAP.length)\n const attrReader = new StringReader(attrStr)\n const attrs = parseAttributes(attrReader)\n const mapUri = attrs['URI']\n if (mapUri) {\n const byteRangeStr = attrs['BYTERANGE']\n currentMap = {\n uri: mapUri,\n ...(byteRangeStr && {\n byteRange: parseByteRangeString(byteRangeStr),\n }),\n }\n }\n }\n }\n\n segments.push({\n uri,\n duration,\n sequenceNumber: mediaSequence + sequenceCounter,\n ...(currentKey && { key: currentKey }),\n ...(currentMap && { map: currentMap }),\n ...(pendingByteRange && { byteRange: pendingByteRange }),\n discontinuity: pendingDiscontinuity,\n ...(pendingDateTime && { programDateTime: pendingDateTime }),\n })\n\n sequenceCounter++\n pendingByteRange = undefined\n pendingDiscontinuity = false\n pendingDateTime = undefined\n } else if (trimmed.startsWith(EXT_X_TARGETDURATION)) {\n targetDuration = Number(\n trimmed.substring(EXT_X_TARGETDURATION.length)\n )\n } else if (trimmed.startsWith(EXT_X_MEDIA_SEQUENCE)) {\n mediaSequence = Number(\n trimmed.substring(EXT_X_MEDIA_SEQUENCE.length)\n )\n } else if (trimmed === EXT_X_ENDLIST) {\n ended = true\n } else if (trimmed.startsWith(EXT_X_PLAYLIST_TYPE)) {\n const val = trimmed.substring(EXT_X_PLAYLIST_TYPE.length).trim()\n if (val === 'VOD' || val === 'EVENT') {\n playlistType = val\n }\n } else if (trimmed.startsWith(EXT_X_VERSION)) {\n version = Number(trimmed.substring(EXT_X_VERSION.length))\n } else if (trimmed.startsWith(EXT_X_KEY)) {\n const attrStr = trimmed.substring(EXT_X_KEY.length)\n const attrReader = new StringReader(attrStr)\n const attrs = parseAttributes(attrReader)\n\n const method = attrs['METHOD']\n encryptionMethodValidator.assert(method)\n\n currentKey = addIfPresent(\n {\n method,\n },\n attrs,\n {\n URI: 'uri',\n IV: 'iv',\n KEYFORMAT: 'keyFormat',\n KEYFORMATVERSIONS: 'keyFormatVersions',\n }\n )\n } else if (trimmed.startsWith(EXT_X_MAP)) {\n const attrStr = trimmed.substring(EXT_X_MAP.length)\n const attrReader = new StringReader(attrStr)\n const attrs = parseAttributes(attrReader)\n\n const mapUri = attrs['URI']\n if (mapUri) {\n const byteRangeStr = attrs['BYTERANGE']\n currentMap = {\n uri: mapUri,\n ...(byteRangeStr && {\n byteRange: parseByteRangeString(byteRangeStr),\n }),\n }\n }\n } else if (trimmed.startsWith(EXT_X_BYTERANGE)) {\n const rangeStr = trimmed.substring(EXT_X_BYTERANGE.length)\n pendingByteRange = parseByteRangeString(rangeStr)\n } else if (trimmed === EXT_X_DISCONTINUITY) {\n pendingDiscontinuity = true\n } else if (trimmed.startsWith(EXT_X_PROGRAM_DATE_TIME)) {\n pendingDateTime = trimmed.substring(EXT_X_PROGRAM_DATE_TIME.length)\n }\n // Unrecognized tags and comments are silently skipped\n }\n\n return {\n version,\n targetDuration,\n mediaSequence,\n playlistType,\n ended,\n segments,\n }\n}\n\n/**\n * Parses a byte range string (e.g. \"1000@500\" or \"1000\") into length and offset.\n */\nfunction parseByteRangeString(str: string): {\n length: number\n offset: number\n} {\n const parts = str.split('@')\n return {\n length: Number(parts[0]),\n offset: parts.length > 1 ? Number(parts[1]) : 0,\n }\n}\n", "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport type { StringReader } from '@amazon/vinyl-util'\nimport { char } from '@amazon/vinyl-util'\n\nconst EQUALS = /* @__PURE__ */ char('=')\nconst COMMA = /* @__PURE__ */ char(',')\nconst QUOTE = /* @__PURE__ */ char('\"')\n\n/**\n * Parses an HLS attribute list (e.g. `KEY=VALUE,KEY=\"quoted,value\"`) using\n * StringReader cursor-based parsing. Handles quoted values with embedded commas.\n */\nexport function parseAttributes(reader: StringReader): Record<string, string> {\n const attrs: Record<string, string> = {}\n\n while (\n reader.hasNext() &&\n reader.peek !== char('\\n') &&\n reader.peek !== char('\\r')\n ) {\n const key = reader.substringUntilChar(EQUALS)\n if (!key || !reader.hasNext()) break\n reader.next() // skip '='\n\n let value: string\n if (reader.peek === QUOTE) {\n reader.next() // skip opening quote\n value = reader.substringUntilChar(QUOTE)\n if (reader.hasNext()) reader.next() // skip closing quote\n } else {\n value = reader.substringUntil(\n (cc) => cc === COMMA || cc === char('\\n') || cc === char('\\r')\n )\n }\n\n attrs[key] = value\n\n if (reader.hasNext() && reader.peek === COMMA) {\n reader.next() // skip ','\n }\n }\n\n return attrs\n}\n", "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport { StringReader, ValidationError } from '@amazon/vinyl-util'\nimport type { AlternativeRendition } from '@/types/AlternativeRendition'\nimport { renditionTypeValidator } from '@/types/AlternativeRendition'\nimport type { MainPlaylist } from '@/types/MainPlaylist'\nimport type { SessionData } from '@/types/SessionData'\nimport type { VariantStream } from '@/types/VariantStream'\nimport { parseAttributes } from './parseAttributes'\nimport { readLine, skipWhitespaceLine, addIfPresent } from './parseUtil'\n\nconst EXTM3U = '#EXTM3U'\nconst EXT_X_STREAM_INF = '#EXT-X-STREAM-INF:'\nconst EXT_X_MEDIA = '#EXT-X-MEDIA:'\nconst EXT_X_SESSION_DATA = '#EXT-X-SESSION-DATA:'\nconst EXT_X_DEFINE = '#EXT-X-DEFINE:'\n\n/**\n * Parses an HLS master playlist from M3U8 text.\n *\n * @param text The raw M3U8 manifest string.\n * @returns A readonly MainPlaylist structure.\n * @throws StringParseError if the manifest is malformed.\n */\nexport function parseMainPlaylist(text: string): MainPlaylist {\n const reader = new StringReader(text)\n\n reader.white()\n if (!reader.stringIf(EXTM3U)) {\n reader.err('Expected #EXTM3U')\n }\n // Advance past the rest of the #EXTM3U line\n readLine(reader)\n\n const variants: VariantStream[] = []\n const alternativeRenditions: AlternativeRendition[] = []\n const sessionData: SessionData[] = []\n const defines: Record<string, string> = {}\n\n while (reader.hasNext()) {\n if (skipWhitespaceLine(reader)) continue\n\n const line = readLine(reader)\n const trimmed = line.trim()\n if (!trimmed) continue\n\n if (trimmed.startsWith(EXT_X_STREAM_INF)) {\n const attrStr = trimmed.substring(EXT_X_STREAM_INF.length)\n const attrReader = new StringReader(attrStr)\n const attrs = parseAttributes(attrReader)\n\n // Next non-empty line is the URI\n let uri = ''\n while (reader.hasNext()) {\n if (skipWhitespaceLine(reader)) continue\n uri = readLine(reader).trim()\n break\n }\n\n variants.push(\n addIfPresent(\n {\n bandwidth: Number(attrs['BANDWIDTH']),\n uri,\n ...(attrs['FRAME-RATE'] && {\n frameRate: Number(attrs['FRAME-RATE']),\n }),\n ...(attrs.RESOLUTION &&\n parseResolution(attrs.RESOLUTION)),\n },\n attrs,\n {\n CODECS: 'codecs',\n AUDIO: 'audioGroup',\n VIDEO: 'videoGroup',\n SUBTITLES: 'subtitlesGroup',\n 'CLOSED-CAPTIONS': 'closedCaptionsGroup',\n }\n )\n )\n } else if (trimmed.startsWith(EXT_X_MEDIA)) {\n const attrStr = trimmed.substring(EXT_X_MEDIA.length)\n const attrReader = new StringReader(attrStr)\n const attrs = parseAttributes(attrReader)\n\n const type = attrs['TYPE']\n renditionTypeValidator.assert(type)\n\n alternativeRenditions.push(\n addIfPresent(\n {\n type,\n groupId: attrs['GROUP-ID'],\n name: attrs['NAME'],\n ...(attrs['DEFAULT'] && {\n default: attrs['DEFAULT'] === 'YES',\n }),\n ...(attrs['AUTOSELECT'] && {\n autoSelect: attrs['AUTOSELECT'] === 'YES',\n }),\n },\n attrs,\n {\n URI: 'uri',\n LANGUAGE: 'language',\n }\n )\n )\n } else if (trimmed.startsWith(EXT_X_SESSION_DATA)) {\n const attrStr = trimmed.substring(EXT_X_SESSION_DATA.length)\n const attrReader = new StringReader(attrStr)\n const attrs = parseAttributes(attrReader)\n\n sessionData.push(\n addIfPresent(\n {\n dataId: attrs['DATA-ID'],\n },\n attrs,\n {\n VALUE: 'value',\n URI: 'uri',\n LANGUAGE: 'language',\n }\n )\n )\n } else if (trimmed.startsWith(EXT_X_DEFINE)) {\n const attrStr = trimmed.substring(EXT_X_DEFINE.length)\n const attrReader = new StringReader(attrStr)\n const attrs = parseAttributes(attrReader)\n if (attrs['NAME'] && attrs['VALUE']) {\n defines[attrs['NAME']] = attrs['VALUE']\n }\n }\n // Unrecognized tags and comments are silently skipped\n }\n\n return { variants, alternativeRenditions, sessionData, defines }\n}\n\n/**\n * Parses a resolution string (e.g., \"1920x1080\") into width and height numbers.\n * Throws ValidationError for invalid formats per HLS spec.\n */\nfunction parseResolution(resolution: string): {\n width: number\n height: number\n} {\n const parts = resolution.split('x')\n if (parts.length !== 2) {\n throw new ValidationError(\n 'Invalid resolution format, expected WIDTHxHEIGHT'\n )\n }\n\n const width = Number(parts[0])\n const height = Number(parts[1])\n\n if (\n !Number.isInteger(width) ||\n !Number.isInteger(height) ||\n width <= 0 ||\n height <= 0\n ) {\n throw new ValidationError(\n 'Invalid resolution values, width and height must be positive integers'\n )\n }\n\n return { width, height }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["isOneOf", "char", "StringReader", "EXTM3U", "StringReader"]
|
|
7
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2024. Amazon.com, Inc. or its affiliates. All rights reserved.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
html,
|
|
6
|
+
body {
|
|
7
|
+
font-family: 'Roboto', sans-serif;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
h1,
|
|
11
|
+
h2,
|
|
12
|
+
h3,
|
|
13
|
+
h4,
|
|
14
|
+
h5 {
|
|
15
|
+
margin-top: 0;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
#benchmarkResults {
|
|
19
|
+
padding: 5px 5px 150px;
|
|
20
|
+
display: flex;
|
|
21
|
+
flex-direction: column;
|
|
22
|
+
align-content: center;
|
|
23
|
+
width: 540px;
|
|
24
|
+
margin: 0 auto;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.benchmarkComparison {
|
|
28
|
+
margin: 20px;
|
|
29
|
+
padding: 5px;
|
|
30
|
+
border: thin solid #b4b9c7;
|
|
31
|
+
border-radius: 8px;
|
|
32
|
+
display: flex;
|
|
33
|
+
flex-direction: column;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.benchmarkComparison > table {
|
|
37
|
+
width: 100%;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
thead,
|
|
41
|
+
tfoot {
|
|
42
|
+
background-color: #6f7893;
|
|
43
|
+
color: #fff;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
tbody {
|
|
47
|
+
background-color: #e8eaef;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
caption {
|
|
51
|
+
padding: 10px;
|
|
52
|
+
caption-side: bottom;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
table {
|
|
56
|
+
border-collapse: collapse;
|
|
57
|
+
border: thin solid rgb(220, 220, 227);
|
|
58
|
+
letter-spacing: 1px;
|
|
59
|
+
font-family: sans-serif;
|
|
60
|
+
font-size: 0.8rem;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
td,
|
|
64
|
+
th {
|
|
65
|
+
border: thin solid rgb(195, 195, 199);
|
|
66
|
+
padding: 5px 10px;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
td {
|
|
70
|
+
text-align: center;
|
|
71
|
+
}
|