@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.
Files changed (139) hide show
  1. package/dist/hlsTestAssets/index.js +64 -0
  2. package/dist/hlsTestAssets/index.js.map +7 -0
  3. package/dist/hlsTestAssets/types/current/hls_mainPlaylist.d.ts +5 -0
  4. package/dist/hlsTestAssets/types/current/hls_mainPlaylist.d.ts.map +1 -0
  5. package/dist/hlsTestAssets/types/current/hls_mainPlaylist.js +21 -0
  6. package/dist/hlsTestAssets/types/current/hls_mediaPlaylist.d.ts +5 -0
  7. package/dist/hlsTestAssets/types/current/hls_mediaPlaylist.d.ts.map +1 -0
  8. package/dist/hlsTestAssets/types/current/hls_mediaPlaylist.js +29 -0
  9. package/dist/hlsTestAssets/types/current/hls_mediaPlaylistWithByteRanges.d.ts +5 -0
  10. package/dist/hlsTestAssets/types/current/hls_mediaPlaylistWithByteRanges.d.ts.map +1 -0
  11. package/dist/hlsTestAssets/types/current/hls_mediaPlaylistWithByteRanges.js +24 -0
  12. package/dist/hlsTestAssets/types/current/index.d.ts +5 -0
  13. package/dist/hlsTestAssets/types/current/index.d.ts.map +1 -0
  14. package/dist/hlsTestAssets/types/current/index.js +4 -0
  15. package/dist/hlsTestAssets/types/current/tsconfig.tsbuildinfo +1 -0
  16. package/dist/hlsTestAssets/types/ts3.7/hls_mainPlaylist.d.ts +5 -0
  17. package/dist/hlsTestAssets/types/ts3.7/hls_mediaPlaylist.d.ts +5 -0
  18. package/dist/hlsTestAssets/types/ts3.7/hls_mediaPlaylistWithByteRanges.d.ts +5 -0
  19. package/dist/hlsTestAssets/types/ts3.7/index.d.ts +5 -0
  20. package/dist/hlsTestAssets/types/ts4.1/hls_mainPlaylist.d.ts +5 -0
  21. package/dist/hlsTestAssets/types/ts4.1/hls_mediaPlaylist.d.ts +5 -0
  22. package/dist/hlsTestAssets/types/ts4.1/hls_mediaPlaylistWithByteRanges.d.ts +5 -0
  23. package/dist/hlsTestAssets/types/ts4.1/index.d.ts +5 -0
  24. package/dist/hlsTestAssets/types/ts4.7/hls_mainPlaylist.d.ts +5 -0
  25. package/dist/hlsTestAssets/types/ts4.7/hls_mediaPlaylist.d.ts +5 -0
  26. package/dist/hlsTestAssets/types/ts4.7/hls_mediaPlaylistWithByteRanges.d.ts +5 -0
  27. package/dist/hlsTestAssets/types/ts4.7/index.d.ts +5 -0
  28. package/dist/index.cjs +412 -0
  29. package/dist/index.cjs.map +7 -0
  30. package/dist/index.js +392 -0
  31. package/dist/index.js.map +7 -0
  32. package/dist/test/benchmark.css +71 -0
  33. package/dist/test/benchmark.es5.js +426 -0
  34. package/dist/test/benchmark.html +67 -0
  35. package/dist/test/benchmark.js +2677 -0
  36. package/dist/test/benchmark.js.map +7 -0
  37. package/dist/test/favicon.png +0 -0
  38. package/dist/test/index.html +61 -0
  39. package/dist/test/integ.es5.js +410 -0
  40. package/dist/test/integ.js +2127 -0
  41. package/dist/test/integ.js.map +7 -0
  42. package/dist/test/jasmine.css +304 -0
  43. package/dist/test/jasmineHtml.es5.js +670 -0
  44. package/dist/test/jasmineHtml.js +10851 -0
  45. package/dist/test/jasmineHtml.js.map +7 -0
  46. package/dist/test/styles.css +42 -0
  47. package/dist/types/current/index.d.ts +13 -0
  48. package/dist/types/current/index.d.ts.map +1 -0
  49. package/dist/types/current/index.js +12 -0
  50. package/dist/types/current/parse/parseAttributes.d.ts +7 -0
  51. package/dist/types/current/parse/parseAttributes.d.ts.map +1 -0
  52. package/dist/types/current/parse/parseAttributes.js +38 -0
  53. package/dist/types/current/parse/parseMainPlaylist.d.ts +10 -0
  54. package/dist/types/current/parse/parseMainPlaylist.d.ts.map +1 -0
  55. package/dist/types/current/parse/parseMainPlaylist.js +131 -0
  56. package/dist/types/current/parse/parseMediaPlaylist.d.ts +12 -0
  57. package/dist/types/current/parse/parseMediaPlaylist.d.ts.map +1 -0
  58. package/dist/types/current/parse/parseMediaPlaylist.js +191 -0
  59. package/dist/types/current/parse/parseUtil.d.ts +17 -0
  60. package/dist/types/current/parse/parseUtil.d.ts.map +1 -0
  61. package/dist/types/current/parse/parseUtil.js +51 -0
  62. package/dist/types/current/tsconfig.tsbuildinfo +1 -0
  63. package/dist/types/current/types/AlternativeRendition.d.ts +34 -0
  64. package/dist/types/current/types/AlternativeRendition.d.ts.map +1 -0
  65. package/dist/types/current/types/AlternativeRendition.js +6 -0
  66. package/dist/types/current/types/EncryptionKey.d.ts +28 -0
  67. package/dist/types/current/types/EncryptionKey.d.ts.map +1 -0
  68. package/dist/types/current/types/EncryptionKey.js +6 -0
  69. package/dist/types/current/types/HlsSegment.d.ts +46 -0
  70. package/dist/types/current/types/HlsSegment.d.ts.map +1 -0
  71. package/dist/types/current/types/HlsSegment.js +5 -0
  72. package/dist/types/current/types/MainPlaylist.d.ts +22 -0
  73. package/dist/types/current/types/MainPlaylist.d.ts.map +1 -0
  74. package/dist/types/current/types/MainPlaylist.js +5 -0
  75. package/dist/types/current/types/MediaPlaylist.d.ts +25 -0
  76. package/dist/types/current/types/MediaPlaylist.d.ts.map +1 -0
  77. package/dist/types/current/types/MediaPlaylist.js +5 -0
  78. package/dist/types/current/types/SessionData.d.ts +19 -0
  79. package/dist/types/current/types/SessionData.d.ts.map +1 -0
  80. package/dist/types/current/types/SessionData.js +5 -0
  81. package/dist/types/current/types/VariantStream.d.ts +31 -0
  82. package/dist/types/current/types/VariantStream.d.ts.map +1 -0
  83. package/dist/types/current/types/VariantStream.js +5 -0
  84. package/dist/types/ts3.7/index.d.ts +13 -0
  85. package/dist/types/ts3.7/parse/parseAttributes.d.ts +7 -0
  86. package/dist/types/ts3.7/parse/parseMainPlaylist.d.ts +10 -0
  87. package/dist/types/ts3.7/parse/parseMediaPlaylist.d.ts +12 -0
  88. package/dist/types/ts3.7/parse/parseUtil.d.ts +17 -0
  89. package/dist/types/ts3.7/types/AlternativeRendition.d.ts +34 -0
  90. package/dist/types/ts3.7/types/EncryptionKey.d.ts +28 -0
  91. package/dist/types/ts3.7/types/HlsSegment.d.ts +46 -0
  92. package/dist/types/ts3.7/types/MainPlaylist.d.ts +22 -0
  93. package/dist/types/ts3.7/types/MediaPlaylist.d.ts +25 -0
  94. package/dist/types/ts3.7/types/SessionData.d.ts +19 -0
  95. package/dist/types/ts3.7/types/VariantStream.d.ts +31 -0
  96. package/dist/types/ts4.1/index.d.ts +13 -0
  97. package/dist/types/ts4.1/parse/parseAttributes.d.ts +7 -0
  98. package/dist/types/ts4.1/parse/parseMainPlaylist.d.ts +10 -0
  99. package/dist/types/ts4.1/parse/parseMediaPlaylist.d.ts +12 -0
  100. package/dist/types/ts4.1/parse/parseUtil.d.ts +17 -0
  101. package/dist/types/ts4.1/types/AlternativeRendition.d.ts +34 -0
  102. package/dist/types/ts4.1/types/EncryptionKey.d.ts +28 -0
  103. package/dist/types/ts4.1/types/HlsSegment.d.ts +46 -0
  104. package/dist/types/ts4.1/types/MainPlaylist.d.ts +22 -0
  105. package/dist/types/ts4.1/types/MediaPlaylist.d.ts +25 -0
  106. package/dist/types/ts4.1/types/SessionData.d.ts +19 -0
  107. package/dist/types/ts4.1/types/VariantStream.d.ts +31 -0
  108. package/dist/types/ts4.7/index.d.ts +13 -0
  109. package/dist/types/ts4.7/parse/parseAttributes.d.ts +7 -0
  110. package/dist/types/ts4.7/parse/parseMainPlaylist.d.ts +10 -0
  111. package/dist/types/ts4.7/parse/parseMediaPlaylist.d.ts +12 -0
  112. package/dist/types/ts4.7/parse/parseUtil.d.ts +17 -0
  113. package/dist/types/ts4.7/types/AlternativeRendition.d.ts +34 -0
  114. package/dist/types/ts4.7/types/EncryptionKey.d.ts +28 -0
  115. package/dist/types/ts4.7/types/HlsSegment.d.ts +46 -0
  116. package/dist/types/ts4.7/types/MainPlaylist.d.ts +22 -0
  117. package/dist/types/ts4.7/types/MediaPlaylist.d.ts +25 -0
  118. package/dist/types/ts4.7/types/SessionData.d.ts +19 -0
  119. package/dist/types/ts4.7/types/VariantStream.d.ts +31 -0
  120. package/dist/unit/types/current/index.d.ts +7 -0
  121. package/dist/unit/types/current/index.d.ts.map +1 -0
  122. package/dist/unit/types/current/index.js +6 -0
  123. package/dist/unit/types/current/parse/parseAttributes.test.d.ts +2 -0
  124. package/dist/unit/types/current/parse/parseAttributes.test.d.ts.map +1 -0
  125. package/dist/unit/types/current/parse/parseAttributes.test.js +70 -0
  126. package/dist/unit/types/current/parse/parseMainPlaylist.test.d.ts +2 -0
  127. package/dist/unit/types/current/parse/parseMainPlaylist.test.d.ts.map +1 -0
  128. package/dist/unit/types/current/parse/parseMainPlaylist.test.js +194 -0
  129. package/dist/unit/types/current/parse/parseMediaPlaylist.test.d.ts +2 -0
  130. package/dist/unit/types/current/parse/parseMediaPlaylist.test.d.ts.map +1 -0
  131. package/dist/unit/types/current/parse/parseMediaPlaylist.test.js +401 -0
  132. package/dist/unit/types/current/parse/parseUtil.test.d.ts +2 -0
  133. package/dist/unit/types/current/parse/parseUtil.test.d.ts.map +1 -0
  134. package/dist/unit/types/current/parse/parseUtil.test.js +67 -0
  135. package/dist/unit/types/current/setup.d.ts +2 -0
  136. package/dist/unit/types/current/setup.d.ts.map +1 -0
  137. package/dist/unit/types/current/setup.js +19 -0
  138. package/dist/unit/types/current/tsconfig.tsbuildinfo +1 -0
  139. package/package.json +88 -0
@@ -0,0 +1,42 @@
1
+ /*
2
+ * Copyright (c) 2024. Amazon.com, Inc. or its affiliates. All rights reserved.
3
+ */
4
+
5
+ html,
6
+ body {
7
+ height: 100%;
8
+ font-family: sans-serif;
9
+ margin: 0;
10
+ padding: 0;
11
+ }
12
+
13
+ body {
14
+ background-color: #dcdee5;
15
+ display: flex;
16
+ flex-direction: column;
17
+ }
18
+
19
+ .callout {
20
+ background-color: #bfcbdb;
21
+ box-sizing: border-box;
22
+ padding: 10px;
23
+ font-family: Helvetica, Arial, 'sans-serif';
24
+ cursor: pointer;
25
+ transition: background-color 0.3s;
26
+ }
27
+
28
+ .callout:hover {
29
+ background-color: #7d9fcc;
30
+ }
31
+
32
+ .instructions {
33
+ display: inline-block;
34
+ font-style: italic;
35
+ padding: 5px;
36
+ font-family: inherit;
37
+ }
38
+
39
+ .jasmine_html-reporter {
40
+ margin: 0 !important;
41
+ padding: 0 !important;
42
+ }
@@ -0,0 +1,13 @@
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
+ export * from './parse/parseAttributes';
13
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.ts"],"names":[],"mappings":"AAAA,wCAAwC;AACxC,cAAc,uBAAuB,CAAA;AACrC,cAAc,qBAAqB,CAAA;AACnC,cAAc,uBAAuB,CAAA;AACrC,cAAc,sBAAsB,CAAA;AACpC,cAAc,oBAAoB,CAAA;AAClC,cAAc,uBAAuB,CAAA;AACrC,cAAc,8BAA8B,CAAA;AAC5C,cAAc,mBAAmB,CAAA;AACjC,cAAc,4BAA4B,CAAA;AAC1C,cAAc,2BAA2B,CAAA;AACzC,cAAc,yBAAyB,CAAA"}
@@ -0,0 +1,12 @@
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
+ export * from './parse/parseAttributes';
@@ -0,0 +1,7 @@
1
+ import type { StringReader } from '@amazon/vinyl-util';
2
+ /**
3
+ * Parses an HLS attribute list (e.g. `KEY=VALUE,KEY="quoted,value"`) using
4
+ * StringReader cursor-based parsing. Handles quoted values with embedded commas.
5
+ */
6
+ export declare function parseAttributes(reader: StringReader): Record<string, string>;
7
+ //# sourceMappingURL=parseAttributes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parseAttributes.d.ts","sourceRoot":"","sources":["../../../../src/parse/parseAttributes.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AAOtD;;;GAGG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,YAAY,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CA+B5E"}
@@ -0,0 +1,38 @@
1
+ /*
2
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+ import { char } from '@amazon/vinyl-util';
6
+ const EQUALS = char('=');
7
+ const COMMA = char(',');
8
+ const QUOTE = char('"');
9
+ /**
10
+ * Parses an HLS attribute list (e.g. `KEY=VALUE,KEY="quoted,value"`) using
11
+ * StringReader cursor-based parsing. Handles quoted values with embedded commas.
12
+ */
13
+ export function parseAttributes(reader) {
14
+ const attrs = {};
15
+ while (reader.hasNext() &&
16
+ reader.peek !== char('\n') &&
17
+ reader.peek !== char('\r')) {
18
+ const key = reader.substringUntilChar(EQUALS);
19
+ if (!key || !reader.hasNext())
20
+ break;
21
+ reader.next(); // skip '='
22
+ let value;
23
+ if (reader.peek === QUOTE) {
24
+ reader.next(); // skip opening quote
25
+ value = reader.substringUntilChar(QUOTE);
26
+ if (reader.hasNext())
27
+ reader.next(); // skip closing quote
28
+ }
29
+ else {
30
+ value = reader.substringUntil((cc) => cc === COMMA || cc === char('\n') || cc === char('\r'));
31
+ }
32
+ attrs[key] = value;
33
+ if (reader.hasNext() && reader.peek === COMMA) {
34
+ reader.next(); // skip ','
35
+ }
36
+ }
37
+ return attrs;
38
+ }
@@ -0,0 +1,10 @@
1
+ import type { MainPlaylist } from '../types/MainPlaylist';
2
+ /**
3
+ * Parses an HLS master playlist from M3U8 text.
4
+ *
5
+ * @param text The raw M3U8 manifest string.
6
+ * @returns A readonly MainPlaylist structure.
7
+ * @throws StringParseError if the manifest is malformed.
8
+ */
9
+ export declare function parseMainPlaylist(text: string): MainPlaylist;
10
+ //# sourceMappingURL=parseMainPlaylist.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parseMainPlaylist.d.ts","sourceRoot":"","sources":["../../../../src/parse/parseMainPlaylist.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAA;AAYxD;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,CAkH5D"}
@@ -0,0 +1,131 @@
1
+ /*
2
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+ import { StringReader, ValidationError } from '@amazon/vinyl-util';
6
+ import { renditionTypeValidator } from '../types/AlternativeRendition';
7
+ import { parseAttributes } from './parseAttributes';
8
+ import { readLine, skipWhitespaceLine, addIfPresent } from './parseUtil';
9
+ const EXTM3U = '#EXTM3U';
10
+ const EXT_X_STREAM_INF = '#EXT-X-STREAM-INF:';
11
+ const EXT_X_MEDIA = '#EXT-X-MEDIA:';
12
+ const EXT_X_SESSION_DATA = '#EXT-X-SESSION-DATA:';
13
+ const EXT_X_DEFINE = '#EXT-X-DEFINE:';
14
+ /**
15
+ * Parses an HLS master playlist from M3U8 text.
16
+ *
17
+ * @param text The raw M3U8 manifest string.
18
+ * @returns A readonly MainPlaylist structure.
19
+ * @throws StringParseError if the manifest is malformed.
20
+ */
21
+ export function parseMainPlaylist(text) {
22
+ const reader = new StringReader(text);
23
+ reader.white();
24
+ if (!reader.stringIf(EXTM3U)) {
25
+ reader.err('Expected #EXTM3U');
26
+ }
27
+ // Advance past the rest of the #EXTM3U line
28
+ readLine(reader);
29
+ const variants = [];
30
+ const alternativeRenditions = [];
31
+ const sessionData = [];
32
+ const defines = {};
33
+ while (reader.hasNext()) {
34
+ if (skipWhitespaceLine(reader))
35
+ continue;
36
+ const line = readLine(reader);
37
+ const trimmed = line.trim();
38
+ if (!trimmed)
39
+ continue;
40
+ if (trimmed.startsWith(EXT_X_STREAM_INF)) {
41
+ const attrStr = trimmed.substring(EXT_X_STREAM_INF.length);
42
+ const attrReader = new StringReader(attrStr);
43
+ const attrs = parseAttributes(attrReader);
44
+ // Next non-empty line is the URI
45
+ let uri = '';
46
+ while (reader.hasNext()) {
47
+ if (skipWhitespaceLine(reader))
48
+ continue;
49
+ uri = readLine(reader).trim();
50
+ break;
51
+ }
52
+ variants.push(addIfPresent({
53
+ bandwidth: Number(attrs['BANDWIDTH']),
54
+ uri,
55
+ ...(attrs['FRAME-RATE'] && {
56
+ frameRate: Number(attrs['FRAME-RATE']),
57
+ }),
58
+ ...(attrs.RESOLUTION &&
59
+ parseResolution(attrs.RESOLUTION)),
60
+ }, attrs, {
61
+ CODECS: 'codecs',
62
+ AUDIO: 'audioGroup',
63
+ VIDEO: 'videoGroup',
64
+ SUBTITLES: 'subtitlesGroup',
65
+ 'CLOSED-CAPTIONS': 'closedCaptionsGroup',
66
+ }));
67
+ }
68
+ else if (trimmed.startsWith(EXT_X_MEDIA)) {
69
+ const attrStr = trimmed.substring(EXT_X_MEDIA.length);
70
+ const attrReader = new StringReader(attrStr);
71
+ const attrs = parseAttributes(attrReader);
72
+ const type = attrs['TYPE'];
73
+ renditionTypeValidator.assert(type);
74
+ alternativeRenditions.push(addIfPresent({
75
+ type,
76
+ groupId: attrs['GROUP-ID'],
77
+ name: attrs['NAME'],
78
+ ...(attrs['DEFAULT'] && {
79
+ default: attrs['DEFAULT'] === 'YES',
80
+ }),
81
+ ...(attrs['AUTOSELECT'] && {
82
+ autoSelect: attrs['AUTOSELECT'] === 'YES',
83
+ }),
84
+ }, attrs, {
85
+ URI: 'uri',
86
+ LANGUAGE: 'language',
87
+ }));
88
+ }
89
+ else if (trimmed.startsWith(EXT_X_SESSION_DATA)) {
90
+ const attrStr = trimmed.substring(EXT_X_SESSION_DATA.length);
91
+ const attrReader = new StringReader(attrStr);
92
+ const attrs = parseAttributes(attrReader);
93
+ sessionData.push(addIfPresent({
94
+ dataId: attrs['DATA-ID'],
95
+ }, attrs, {
96
+ VALUE: 'value',
97
+ URI: 'uri',
98
+ LANGUAGE: 'language',
99
+ }));
100
+ }
101
+ else if (trimmed.startsWith(EXT_X_DEFINE)) {
102
+ const attrStr = trimmed.substring(EXT_X_DEFINE.length);
103
+ const attrReader = new StringReader(attrStr);
104
+ const attrs = parseAttributes(attrReader);
105
+ if (attrs['NAME'] && attrs['VALUE']) {
106
+ defines[attrs['NAME']] = attrs['VALUE'];
107
+ }
108
+ }
109
+ // Unrecognized tags and comments are silently skipped
110
+ }
111
+ return { variants, alternativeRenditions, sessionData, defines };
112
+ }
113
+ /**
114
+ * Parses a resolution string (e.g., "1920x1080") into width and height numbers.
115
+ * Throws ValidationError for invalid formats per HLS spec.
116
+ */
117
+ function parseResolution(resolution) {
118
+ const parts = resolution.split('x');
119
+ if (parts.length !== 2) {
120
+ throw new ValidationError('Invalid resolution format, expected WIDTHxHEIGHT');
121
+ }
122
+ const width = Number(parts[0]);
123
+ const height = Number(parts[1]);
124
+ if (!Number.isInteger(width) ||
125
+ !Number.isInteger(height) ||
126
+ width <= 0 ||
127
+ height <= 0) {
128
+ throw new ValidationError('Invalid resolution values, width and height must be positive integers');
129
+ }
130
+ return { width, height };
131
+ }
@@ -0,0 +1,12 @@
1
+ import type { MediaPlaylist } from '../types/MediaPlaylist';
2
+ export declare const HLS_VARIABLE_PATTERN: RegExp;
3
+ /**
4
+ * Parses an HLS media playlist from M3U8 text.
5
+ *
6
+ * @param text The raw M3U8 manifest string.
7
+ * @param variables Optional variable definitions from the master playlist's #EXT-X-DEFINE tags.
8
+ * @returns A readonly MediaPlaylist structure.
9
+ * @throws StringParseError if the manifest is malformed.
10
+ */
11
+ export declare function parseMediaPlaylist(text: string, variables?: Readonly<Record<string, string>>): MediaPlaylist;
12
+ //# sourceMappingURL=parseMediaPlaylist.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parseMediaPlaylist.d.ts","sourceRoot":"","sources":["../../../../src/parse/parseMediaPlaylist.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAA;AAI1D,eAAO,MAAM,oBAAoB,QAAkB,CAAA;AAenD;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAC9B,IAAI,EAAE,MAAM,EACZ,SAAS,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,GAC7C,aAAa,CA+Jf"}
@@ -0,0 +1,191 @@
1
+ /*
2
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+ import { StringReader, substitute } from '@amazon/vinyl-util';
6
+ import { encryptionMethodValidator } from '../types/EncryptionKey';
7
+ import { parseAttributes } from './parseAttributes';
8
+ import { readLine, skipWhitespaceLine, addIfPresent } from './parseUtil';
9
+ export const HLS_VARIABLE_PATTERN = /\{\$([^}]+)}/g;
10
+ const EXTM3U = '#EXTM3U';
11
+ const EXTINF = '#EXTINF:';
12
+ const EXT_X_TARGETDURATION = '#EXT-X-TARGETDURATION:';
13
+ const EXT_X_MEDIA_SEQUENCE = '#EXT-X-MEDIA-SEQUENCE:';
14
+ const EXT_X_ENDLIST = '#EXT-X-ENDLIST';
15
+ const EXT_X_PLAYLIST_TYPE = '#EXT-X-PLAYLIST-TYPE:';
16
+ const EXT_X_VERSION = '#EXT-X-VERSION:';
17
+ const EXT_X_KEY = '#EXT-X-KEY:';
18
+ const EXT_X_BYTERANGE = '#EXT-X-BYTERANGE:';
19
+ const EXT_X_DISCONTINUITY = '#EXT-X-DISCONTINUITY';
20
+ const EXT_X_PROGRAM_DATE_TIME = '#EXT-X-PROGRAM-DATE-TIME:';
21
+ const EXT_X_MAP = '#EXT-X-MAP:';
22
+ /**
23
+ * Parses an HLS media playlist from M3U8 text.
24
+ *
25
+ * @param text The raw M3U8 manifest string.
26
+ * @param variables Optional variable definitions from the master playlist's #EXT-X-DEFINE tags.
27
+ * @returns A readonly MediaPlaylist structure.
28
+ * @throws StringParseError if the manifest is malformed.
29
+ */
30
+ export function parseMediaPlaylist(text, variables) {
31
+ const reader = new StringReader(text);
32
+ reader.white();
33
+ if (!reader.stringIf(EXTM3U)) {
34
+ reader.err('Expected #EXTM3U');
35
+ }
36
+ readLine(reader);
37
+ let version = 1;
38
+ let targetDuration = 0;
39
+ let mediaSequence = 0;
40
+ let playlistType = 'LIVE';
41
+ let ended = false;
42
+ let currentKey;
43
+ let currentMap;
44
+ let pendingByteRange;
45
+ let pendingDiscontinuity = false;
46
+ let pendingDateTime;
47
+ const segments = [];
48
+ let sequenceCounter = 0;
49
+ while (reader.hasNext()) {
50
+ if (skipWhitespaceLine(reader))
51
+ continue;
52
+ const line = readLine(reader);
53
+ const trimmed = line.trim();
54
+ if (!trimmed)
55
+ continue;
56
+ if (trimmed.startsWith(EXTINF)) {
57
+ const durationStr = trimmed.substring(EXTINF.length).split(',')[0];
58
+ const duration = Number(durationStr);
59
+ // Find the URI, skipping any tags that come between EXTINF and URI
60
+ let uri = '';
61
+ while (reader.hasNext()) {
62
+ if (skipWhitespaceLine(reader))
63
+ continue;
64
+ const nextLine = readLine(reader).trim();
65
+ if (!nextLine.startsWith('#')) {
66
+ uri = variables
67
+ ? substitute(nextLine, variables, HLS_VARIABLE_PATTERN)
68
+ : nextLine;
69
+ break;
70
+ }
71
+ // Process tags that come between EXTINF and URI
72
+ if (nextLine.startsWith(EXT_X_BYTERANGE)) {
73
+ const rangeStr = nextLine.substring(EXT_X_BYTERANGE.length);
74
+ pendingByteRange = parseByteRangeString(rangeStr);
75
+ }
76
+ else if (nextLine === EXT_X_DISCONTINUITY) {
77
+ pendingDiscontinuity = true;
78
+ }
79
+ else if (nextLine.startsWith(EXT_X_PROGRAM_DATE_TIME)) {
80
+ pendingDateTime = nextLine.substring(EXT_X_PROGRAM_DATE_TIME.length);
81
+ }
82
+ else if (nextLine.startsWith(EXT_X_MAP)) {
83
+ const attrStr = nextLine.substring(EXT_X_MAP.length);
84
+ const attrReader = new StringReader(attrStr);
85
+ const attrs = parseAttributes(attrReader);
86
+ const mapUri = attrs['URI'];
87
+ if (mapUri) {
88
+ const byteRangeStr = attrs['BYTERANGE'];
89
+ currentMap = {
90
+ uri: mapUri,
91
+ ...(byteRangeStr && {
92
+ byteRange: parseByteRangeString(byteRangeStr),
93
+ }),
94
+ };
95
+ }
96
+ }
97
+ }
98
+ segments.push({
99
+ uri,
100
+ duration,
101
+ sequenceNumber: mediaSequence + sequenceCounter,
102
+ ...(currentKey && { key: currentKey }),
103
+ ...(currentMap && { map: currentMap }),
104
+ ...(pendingByteRange && { byteRange: pendingByteRange }),
105
+ discontinuity: pendingDiscontinuity,
106
+ ...(pendingDateTime && { programDateTime: pendingDateTime }),
107
+ });
108
+ sequenceCounter++;
109
+ pendingByteRange = undefined;
110
+ pendingDiscontinuity = false;
111
+ pendingDateTime = undefined;
112
+ }
113
+ else if (trimmed.startsWith(EXT_X_TARGETDURATION)) {
114
+ targetDuration = Number(trimmed.substring(EXT_X_TARGETDURATION.length));
115
+ }
116
+ else if (trimmed.startsWith(EXT_X_MEDIA_SEQUENCE)) {
117
+ mediaSequence = Number(trimmed.substring(EXT_X_MEDIA_SEQUENCE.length));
118
+ }
119
+ else if (trimmed === EXT_X_ENDLIST) {
120
+ ended = true;
121
+ }
122
+ else if (trimmed.startsWith(EXT_X_PLAYLIST_TYPE)) {
123
+ const val = trimmed.substring(EXT_X_PLAYLIST_TYPE.length).trim();
124
+ if (val === 'VOD' || val === 'EVENT') {
125
+ playlistType = val;
126
+ }
127
+ }
128
+ else if (trimmed.startsWith(EXT_X_VERSION)) {
129
+ version = Number(trimmed.substring(EXT_X_VERSION.length));
130
+ }
131
+ else if (trimmed.startsWith(EXT_X_KEY)) {
132
+ const attrStr = trimmed.substring(EXT_X_KEY.length);
133
+ const attrReader = new StringReader(attrStr);
134
+ const attrs = parseAttributes(attrReader);
135
+ const method = attrs['METHOD'];
136
+ encryptionMethodValidator.assert(method);
137
+ currentKey = addIfPresent({
138
+ method,
139
+ }, attrs, {
140
+ URI: 'uri',
141
+ IV: 'iv',
142
+ KEYFORMAT: 'keyFormat',
143
+ KEYFORMATVERSIONS: 'keyFormatVersions',
144
+ });
145
+ }
146
+ else if (trimmed.startsWith(EXT_X_MAP)) {
147
+ const attrStr = trimmed.substring(EXT_X_MAP.length);
148
+ const attrReader = new StringReader(attrStr);
149
+ const attrs = parseAttributes(attrReader);
150
+ const mapUri = attrs['URI'];
151
+ if (mapUri) {
152
+ const byteRangeStr = attrs['BYTERANGE'];
153
+ currentMap = {
154
+ uri: mapUri,
155
+ ...(byteRangeStr && {
156
+ byteRange: parseByteRangeString(byteRangeStr),
157
+ }),
158
+ };
159
+ }
160
+ }
161
+ else if (trimmed.startsWith(EXT_X_BYTERANGE)) {
162
+ const rangeStr = trimmed.substring(EXT_X_BYTERANGE.length);
163
+ pendingByteRange = parseByteRangeString(rangeStr);
164
+ }
165
+ else if (trimmed === EXT_X_DISCONTINUITY) {
166
+ pendingDiscontinuity = true;
167
+ }
168
+ else if (trimmed.startsWith(EXT_X_PROGRAM_DATE_TIME)) {
169
+ pendingDateTime = trimmed.substring(EXT_X_PROGRAM_DATE_TIME.length);
170
+ }
171
+ // Unrecognized tags and comments are silently skipped
172
+ }
173
+ return {
174
+ version,
175
+ targetDuration,
176
+ mediaSequence,
177
+ playlistType,
178
+ ended,
179
+ segments,
180
+ };
181
+ }
182
+ /**
183
+ * Parses a byte range string (e.g. "1000@500" or "1000") into length and offset.
184
+ */
185
+ function parseByteRangeString(str) {
186
+ const parts = str.split('@');
187
+ return {
188
+ length: Number(parts[0]),
189
+ offset: parts.length > 1 ? Number(parts[1]) : 0,
190
+ };
191
+ }
@@ -0,0 +1,17 @@
1
+ import { type StringReader } from '@amazon/vinyl-util';
2
+ /**
3
+ * Reads a line from the StringReader, advancing past the line ending.
4
+ * Returns the line content without the line ending.
5
+ */
6
+ export declare function readLine(reader: StringReader): string;
7
+ /**
8
+ * Skips whitespace-only content at the current position up to and including
9
+ * the next line ending. Returns true if a whitespace-only line was skipped.
10
+ */
11
+ export declare function skipWhitespaceLine(reader: StringReader): boolean;
12
+ /**
13
+ * Conditionally adds properties from an attribute map to an object.
14
+ * Only adds a property if the corresponding attribute key exists and is truthy.
15
+ */
16
+ export declare function addIfPresent<T extends Record<string, any>>(obj: T, attrs: Record<string, string>, mappings: Record<string, string>): T;
17
+ //# sourceMappingURL=parseUtil.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parseUtil.d.ts","sourceRoot":"","sources":["../../../../src/parse/parseUtil.ts"],"names":[],"mappings":"AAKA,OAAO,EAA0B,KAAK,YAAY,EAAE,MAAM,oBAAoB,CAAA;AAK9E;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,MAAM,EAAE,YAAY,GAAG,MAAM,CAKrD;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAYhE;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EACtD,GAAG,EAAE,CAAC,EACN,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC7B,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GACjC,CAAC,CAOH"}
@@ -0,0 +1,51 @@
1
+ /*
2
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+ import { char, isWhitespaceChar } from '@amazon/vinyl-util';
6
+ const LF = char('\n');
7
+ const CR = char('\r');
8
+ /**
9
+ * Reads a line from the StringReader, advancing past the line ending.
10
+ * Returns the line content without the line ending.
11
+ */
12
+ export function readLine(reader) {
13
+ const line = reader.substringUntil((cc) => cc === LF || cc === CR);
14
+ if (reader.hasNext() && reader.peek === CR)
15
+ reader.next();
16
+ if (reader.hasNext() && reader.peek === LF)
17
+ reader.next();
18
+ return line;
19
+ }
20
+ /**
21
+ * Skips whitespace-only content at the current position up to and including
22
+ * the next line ending. Returns true if a whitespace-only line was skipped.
23
+ */
24
+ export function skipWhitespaceLine(reader) {
25
+ const start = reader.position;
26
+ reader.while((cc) => isWhitespaceChar(cc) && cc !== LF && cc !== CR);
27
+ if (!reader.hasNext() || reader.peek === LF || reader.peek === CR) {
28
+ if (reader.hasNext()) {
29
+ if (reader.peek === CR)
30
+ reader.next();
31
+ if (reader.hasNext() && reader.peek === LF)
32
+ reader.next();
33
+ }
34
+ return true;
35
+ }
36
+ reader.position = start;
37
+ return false;
38
+ }
39
+ /**
40
+ * Conditionally adds properties from an attribute map to an object.
41
+ * Only adds a property if the corresponding attribute key exists and is truthy.
42
+ */
43
+ export function addIfPresent(obj, attrs, mappings) {
44
+ for (const [attrKey, objKey] of Object.entries(mappings)) {
45
+ if (attrs[attrKey]) {
46
+ ;
47
+ obj[objKey] = attrs[attrKey];
48
+ }
49
+ }
50
+ return obj;
51
+ }