@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
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { HlsSegment } from './HlsSegment';
|
|
2
|
+
/**
|
|
3
|
+
* A parsed HLS Media Playlist as defined by RFC 8216.
|
|
4
|
+
*
|
|
5
|
+
* A Media Playlist contains a list of media segments that, when played sequentially, form a continuous stream.
|
|
6
|
+
*
|
|
7
|
+
* @see https://datatracker.ietf.org/doc/html/rfc8216#section-4
|
|
8
|
+
*/
|
|
9
|
+
export interface MediaPlaylist {
|
|
10
|
+
/** The HLS protocol version required by this playlist (EXT-X-VERSION). */
|
|
11
|
+
readonly version: number;
|
|
12
|
+
/** The upper bound on the duration of any segment in the playlist, in seconds (EXT-X-TARGETDURATION). */
|
|
13
|
+
readonly targetDuration: number;
|
|
14
|
+
/** The sequence number of the first segment in the playlist (EXT-X-MEDIA-SEQUENCE). */
|
|
15
|
+
readonly mediaSequence: number;
|
|
16
|
+
/** The mutability type of the playlist: VOD (static), EVENT (append-only), or LIVE (sliding window). */
|
|
17
|
+
readonly playlistType: MediaPlaylistType;
|
|
18
|
+
/** Whether the playlist is complete, indicated by the presence of EXT-X-ENDLIST. */
|
|
19
|
+
readonly ended: boolean;
|
|
20
|
+
/** The ordered list of media segments in this playlist. */
|
|
21
|
+
readonly segments: readonly HlsSegment[];
|
|
22
|
+
}
|
|
23
|
+
/** The mutability type of Media Playlist. */
|
|
24
|
+
export type MediaPlaylistType = 'VOD' | 'EVENT' | 'LIVE';
|
|
25
|
+
//# sourceMappingURL=MediaPlaylist.d.ts.map
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session-level metadata in an HLS Master Playlist (EXT-X-SESSION-DATA).
|
|
3
|
+
*
|
|
4
|
+
* Carries application-defined key/value data that applies to the entire presentation,
|
|
5
|
+
* such as content identifiers or localized metadata.
|
|
6
|
+
*
|
|
7
|
+
* @see https://datatracker.ietf.org/doc/html/rfc8216#section-4.3.4.4
|
|
8
|
+
*/
|
|
9
|
+
export interface SessionData {
|
|
10
|
+
/** A unique identifier for this session data entry (DATA-ID). */
|
|
11
|
+
readonly dataId: string;
|
|
12
|
+
/** The data value as a string (VALUE). Mutually exclusive with {@link uri}. */
|
|
13
|
+
readonly value?: string;
|
|
14
|
+
/** A URI pointing to a JSON resource containing the data (URI). Mutually exclusive with {@link value}. */
|
|
15
|
+
readonly uri?: string;
|
|
16
|
+
/** The language of the data as a BCP 47 tag (LANGUAGE). */
|
|
17
|
+
readonly language?: string;
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=SessionData.d.ts.map
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A variant stream in an HLS Master Playlist (EXT-X-STREAM-INF).
|
|
3
|
+
*
|
|
4
|
+
* Each variant represents the same presentation at a particular bitrate and resolution,
|
|
5
|
+
* optionally referencing alternative rendition groups for audio, video, subtitles, or closed captions.
|
|
6
|
+
*
|
|
7
|
+
* @see https://datatracker.ietf.org/doc/html/rfc8216#section-4.3.4.2
|
|
8
|
+
*/
|
|
9
|
+
export interface VariantStream {
|
|
10
|
+
/** The peak bitrate of the variant stream in bits per second (BANDWIDTH). */
|
|
11
|
+
readonly bandwidth: number;
|
|
12
|
+
/** The URI of the Media Playlist for this variant. */
|
|
13
|
+
readonly uri: string;
|
|
14
|
+
/** A comma-separated list of codec strings describing the media in this variant (CODECS). */
|
|
15
|
+
readonly codecs?: string;
|
|
16
|
+
/** The horizontal resolution of the video in pixels (RESOLUTION width). */
|
|
17
|
+
readonly width?: number;
|
|
18
|
+
/** The vertical resolution of the video in pixels (RESOLUTION height). */
|
|
19
|
+
readonly height?: number;
|
|
20
|
+
/** The maximum frame rate of the video in frames per second (FRAME-RATE). */
|
|
21
|
+
readonly frameRate?: number;
|
|
22
|
+
/** The GROUP-ID of the audio alternative renditions to use with this variant (AUDIO). */
|
|
23
|
+
readonly audioGroup?: string;
|
|
24
|
+
/** The GROUP-ID of the video alternative renditions to use with this variant (VIDEO). */
|
|
25
|
+
readonly videoGroup?: string;
|
|
26
|
+
/** The GROUP-ID of the subtitle alternative renditions to use with this variant (SUBTITLES). */
|
|
27
|
+
readonly subtitlesGroup?: string;
|
|
28
|
+
/** The GROUP-ID of the closed-caption alternative renditions to use with this variant (CLOSED-CAPTIONS). */
|
|
29
|
+
readonly closedCaptionsGroup?: string;
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=VariantStream.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../test/unit/index.ts"],"names":[],"mappings":"AAAA,wCAAwC;AACxC,OAAO,SAAS,CAAA;AAChB,OAAO,wBAAwB,CAAA;AAC/B,OAAO,iCAAiC,CAAA;AACxC,OAAO,gCAAgC,CAAA;AACvC,OAAO,8BAA8B,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parseAttributes.test.d.ts","sourceRoot":"","sources":["../../../../../test/unit/parse/parseAttributes.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
import { StringReader } from '@amazon/vinyl-util';
|
|
6
|
+
import { parseAttributes } from '@amazon/vinyl-hls-parser';
|
|
7
|
+
describe('parseAttributes', () => {
|
|
8
|
+
it('parses simple key-value pairs', () => {
|
|
9
|
+
const reader = new StringReader('BANDWIDTH=1280000,CODECS="avc1.42e00a"');
|
|
10
|
+
const attrs = parseAttributes(reader);
|
|
11
|
+
expect(attrs['BANDWIDTH']).toBe('1280000');
|
|
12
|
+
expect(attrs['CODECS']).toBe('avc1.42e00a');
|
|
13
|
+
});
|
|
14
|
+
it('handles quoted values with embedded commas', () => {
|
|
15
|
+
const reader = new StringReader('CODECS="avc1.42e00a,mp4a.40.2",BANDWIDTH=1280000');
|
|
16
|
+
const attrs = parseAttributes(reader);
|
|
17
|
+
expect(attrs['CODECS']).toBe('avc1.42e00a,mp4a.40.2');
|
|
18
|
+
expect(attrs['BANDWIDTH']).toBe('1280000');
|
|
19
|
+
});
|
|
20
|
+
it('handles a single unquoted attribute', () => {
|
|
21
|
+
const reader = new StringReader('BANDWIDTH=1280000');
|
|
22
|
+
const attrs = parseAttributes(reader);
|
|
23
|
+
expect(attrs['BANDWIDTH']).toBe('1280000');
|
|
24
|
+
});
|
|
25
|
+
it('handles a single quoted attribute', () => {
|
|
26
|
+
const reader = new StringReader('URI="segment.ts"');
|
|
27
|
+
const attrs = parseAttributes(reader);
|
|
28
|
+
expect(attrs['URI']).toBe('segment.ts');
|
|
29
|
+
});
|
|
30
|
+
it('returns empty record for empty input', () => {
|
|
31
|
+
const reader = new StringReader('');
|
|
32
|
+
const attrs = parseAttributes(reader);
|
|
33
|
+
expect(Object.keys(attrs).length).toBe(0);
|
|
34
|
+
});
|
|
35
|
+
it('handles multiple quoted values', () => {
|
|
36
|
+
const reader = new StringReader('TYPE=AUDIO,GROUP-ID="audio",NAME="English",LANGUAGE="en"');
|
|
37
|
+
const attrs = parseAttributes(reader);
|
|
38
|
+
expect(attrs['TYPE']).toBe('AUDIO');
|
|
39
|
+
expect(attrs['GROUP-ID']).toBe('audio');
|
|
40
|
+
expect(attrs['NAME']).toBe('English');
|
|
41
|
+
expect(attrs['LANGUAGE']).toBe('en');
|
|
42
|
+
});
|
|
43
|
+
it('skips empty key', () => {
|
|
44
|
+
const reader = new StringReader('=value,KEY=test');
|
|
45
|
+
const attrs = parseAttributes(reader);
|
|
46
|
+
expect(Object.keys(attrs).length).toBe(0);
|
|
47
|
+
});
|
|
48
|
+
it('handles missing equals sign', () => {
|
|
49
|
+
const reader = new StringReader('NOEQUALS');
|
|
50
|
+
const attrs = parseAttributes(reader);
|
|
51
|
+
expect(Object.keys(attrs).length).toBe(0);
|
|
52
|
+
});
|
|
53
|
+
it('handles quoted value without closing quote', () => {
|
|
54
|
+
const reader = new StringReader('KEY="unclosed');
|
|
55
|
+
const attrs = parseAttributes(reader);
|
|
56
|
+
expect(attrs['KEY']).toBe('unclosed');
|
|
57
|
+
});
|
|
58
|
+
it('stops at newline', () => {
|
|
59
|
+
const reader = new StringReader('KEY=value\nIGNORED=ignored');
|
|
60
|
+
const attrs = parseAttributes(reader);
|
|
61
|
+
expect(attrs['KEY']).toBe('value');
|
|
62
|
+
expect(attrs['IGNORED']).toBeUndefined();
|
|
63
|
+
});
|
|
64
|
+
it('stops at carriage return', () => {
|
|
65
|
+
const reader = new StringReader('KEY=value\rIGNORED=ignored');
|
|
66
|
+
const attrs = parseAttributes(reader);
|
|
67
|
+
expect(attrs['KEY']).toBe('value');
|
|
68
|
+
expect(attrs['IGNORED']).toBeUndefined();
|
|
69
|
+
});
|
|
70
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parseMainPlaylist.test.d.ts","sourceRoot":"","sources":["../../../../../test/unit/parse/parseMainPlaylist.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
import { StringParseError, ValidationError } from '@amazon/vinyl-util';
|
|
6
|
+
import { parseMainPlaylist } from '@amazon/vinyl-hls-parser';
|
|
7
|
+
describe('parseMainPlaylist', () => {
|
|
8
|
+
it('throws StringParseError when #EXTM3U is missing', () => {
|
|
9
|
+
expect(() => parseMainPlaylist('invalid')).toThrowError(StringParseError);
|
|
10
|
+
});
|
|
11
|
+
it('parses variant streams with bandwidth and uri', () => {
|
|
12
|
+
const manifest = [
|
|
13
|
+
'#EXTM3U',
|
|
14
|
+
'#EXT-X-STREAM-INF:BANDWIDTH=1280000',
|
|
15
|
+
'low/index.m3u8',
|
|
16
|
+
'#EXT-X-STREAM-INF:BANDWIDTH=2560000',
|
|
17
|
+
'mid/index.m3u8',
|
|
18
|
+
].join('\n');
|
|
19
|
+
const result = parseMainPlaylist(manifest);
|
|
20
|
+
expect(result.variants.length).toBe(2);
|
|
21
|
+
expect(result.variants[0].bandwidth).toBe(1280000);
|
|
22
|
+
expect(result.variants[0].uri).toBe('low/index.m3u8');
|
|
23
|
+
expect(result.variants[1].bandwidth).toBe(2560000);
|
|
24
|
+
expect(result.variants[1].uri).toBe('mid/index.m3u8');
|
|
25
|
+
});
|
|
26
|
+
it('parses variant streams with optional attributes', () => {
|
|
27
|
+
const manifest = [
|
|
28
|
+
'#EXTM3U',
|
|
29
|
+
'#EXT-X-STREAM-INF:BANDWIDTH=1280000,CODECS="avc1.42e00a,mp4a.40.2",RESOLUTION=640x360,FRAME-RATE=29.97,AUDIO="audio-group",VIDEO="video-group",SUBTITLES="subs"',
|
|
30
|
+
'variant.m3u8',
|
|
31
|
+
].join('\n');
|
|
32
|
+
const result = parseMainPlaylist(manifest);
|
|
33
|
+
const v = result.variants[0];
|
|
34
|
+
expect(v.bandwidth).toBe(1280000);
|
|
35
|
+
expect(v.codecs).toBe('avc1.42e00a,mp4a.40.2');
|
|
36
|
+
expect(v.width).toBe(640);
|
|
37
|
+
expect(v.height).toBe(360);
|
|
38
|
+
expect(v.frameRate).toBe(29.97);
|
|
39
|
+
expect(v.audioGroup).toBe('audio-group');
|
|
40
|
+
expect(v.videoGroup).toBe('video-group');
|
|
41
|
+
expect(v.subtitlesGroup).toBe('subs');
|
|
42
|
+
});
|
|
43
|
+
it('parses alternative renditions', () => {
|
|
44
|
+
const manifest = [
|
|
45
|
+
'#EXTM3U',
|
|
46
|
+
'#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="audio",NAME="English",LANGUAGE="en",DEFAULT=YES,AUTOSELECT=YES,URI="audio/en.m3u8"',
|
|
47
|
+
'#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="audio",NAME="Spanish",LANGUAGE="es",DEFAULT=NO,AUTOSELECT=NO',
|
|
48
|
+
].join('\n');
|
|
49
|
+
const result = parseMainPlaylist(manifest);
|
|
50
|
+
expect(result.alternativeRenditions.length).toBe(2);
|
|
51
|
+
const en = result.alternativeRenditions[0];
|
|
52
|
+
expect(en.type).toBe('AUDIO');
|
|
53
|
+
expect(en.groupId).toBe('audio');
|
|
54
|
+
expect(en.name).toBe('English');
|
|
55
|
+
expect(en.language).toBe('en');
|
|
56
|
+
expect(en.default).toBe(true);
|
|
57
|
+
expect(en.autoSelect).toBe(true);
|
|
58
|
+
expect(en.uri).toBe('audio/en.m3u8');
|
|
59
|
+
const es = result.alternativeRenditions[1];
|
|
60
|
+
expect(es.default).toBe(false);
|
|
61
|
+
expect(es.autoSelect).toBe(false);
|
|
62
|
+
expect(es.uri).toBeUndefined();
|
|
63
|
+
});
|
|
64
|
+
it('parses session data', () => {
|
|
65
|
+
const manifest = [
|
|
66
|
+
'#EXTM3U',
|
|
67
|
+
'#EXT-X-SESSION-DATA:DATA-ID="com.example.title",VALUE="Episode 1",LANGUAGE="en"',
|
|
68
|
+
'#EXT-X-SESSION-DATA:DATA-ID="com.example.poster",URI="/poster.jpg"',
|
|
69
|
+
].join('\n');
|
|
70
|
+
const result = parseMainPlaylist(manifest);
|
|
71
|
+
expect(result.sessionData.length).toBe(2);
|
|
72
|
+
expect(result.sessionData[0].dataId).toBe('com.example.title');
|
|
73
|
+
expect(result.sessionData[0].value).toBe('Episode 1');
|
|
74
|
+
expect(result.sessionData[0].language).toBe('en');
|
|
75
|
+
expect(result.sessionData[0].uri).toBeUndefined();
|
|
76
|
+
expect(result.sessionData[1].dataId).toBe('com.example.poster');
|
|
77
|
+
expect(result.sessionData[1].uri).toBe('/poster.jpg');
|
|
78
|
+
expect(result.sessionData[1].value).toBeUndefined();
|
|
79
|
+
});
|
|
80
|
+
it('skips blank lines and comments', () => {
|
|
81
|
+
const manifest = [
|
|
82
|
+
'#EXTM3U',
|
|
83
|
+
'',
|
|
84
|
+
'# This is a comment',
|
|
85
|
+
'#EXT-X-STREAM-INF:BANDWIDTH=1280000',
|
|
86
|
+
'',
|
|
87
|
+
'low/index.m3u8',
|
|
88
|
+
].join('\n');
|
|
89
|
+
const result = parseMainPlaylist(manifest);
|
|
90
|
+
expect(result.variants.length).toBe(1);
|
|
91
|
+
expect(result.variants[0].uri).toBe('low/index.m3u8');
|
|
92
|
+
});
|
|
93
|
+
it('handles lines with only whitespace', () => {
|
|
94
|
+
const manifest = [
|
|
95
|
+
'#EXTM3U',
|
|
96
|
+
' ',
|
|
97
|
+
'\t\t',
|
|
98
|
+
'#EXT-X-STREAM-INF:BANDWIDTH=1280000',
|
|
99
|
+
' \t ',
|
|
100
|
+
'low/index.m3u8',
|
|
101
|
+
].join('\n');
|
|
102
|
+
const result = parseMainPlaylist(manifest);
|
|
103
|
+
expect(result.variants.length).toBe(1);
|
|
104
|
+
expect(result.variants[0].uri).toBe('low/index.m3u8');
|
|
105
|
+
});
|
|
106
|
+
it('handles empty manifest after header', () => {
|
|
107
|
+
const manifest = '#EXTM3U\n';
|
|
108
|
+
const result = parseMainPlaylist(manifest);
|
|
109
|
+
expect(result.variants.length).toBe(0);
|
|
110
|
+
expect(result.alternativeRenditions.length).toBe(0);
|
|
111
|
+
expect(result.sessionData.length).toBe(0);
|
|
112
|
+
});
|
|
113
|
+
it('handles line with only whitespace in main playlist', () => {
|
|
114
|
+
const manifest = `#EXTM3U
|
|
115
|
+
|
|
116
|
+
#EXT-X-STREAM-INF:BANDWIDTH=1280000
|
|
117
|
+
low/index.m3u8`;
|
|
118
|
+
const result = parseMainPlaylist(manifest);
|
|
119
|
+
expect(result.variants.length).toBe(1);
|
|
120
|
+
});
|
|
121
|
+
it('handles line that becomes empty after trimming', () => {
|
|
122
|
+
// Create a line with non-breaking space that might not be caught by skipWhitespaceLine
|
|
123
|
+
const manifest = '#EXTM3U\n\u00A0\n#EXT-X-STREAM-INF:BANDWIDTH=1280000\nlow/index.m3u8';
|
|
124
|
+
const result = parseMainPlaylist(manifest);
|
|
125
|
+
expect(result.variants.length).toBe(1);
|
|
126
|
+
});
|
|
127
|
+
it('skips unrecognized tags', () => {
|
|
128
|
+
const manifest = [
|
|
129
|
+
'#EXTM3U',
|
|
130
|
+
'#EXT-X-INDEPENDENT-SEGMENTS',
|
|
131
|
+
'#EXT-X-STREAM-INF:BANDWIDTH=1280000',
|
|
132
|
+
'low/index.m3u8',
|
|
133
|
+
].join('\n');
|
|
134
|
+
const result = parseMainPlaylist(manifest);
|
|
135
|
+
expect(result.variants.length).toBe(1);
|
|
136
|
+
});
|
|
137
|
+
it('tolerates leading whitespace before #EXTM3U', () => {
|
|
138
|
+
const manifest = ' \n#EXTM3U\n#EXT-X-STREAM-INF:BANDWIDTH=1280000\nlow/index.m3u8';
|
|
139
|
+
const result = parseMainPlaylist(manifest);
|
|
140
|
+
expect(result.variants.length).toBe(1);
|
|
141
|
+
});
|
|
142
|
+
it('returns empty arrays when no variants, renditions, or session data', () => {
|
|
143
|
+
const result = parseMainPlaylist('#EXTM3U\n');
|
|
144
|
+
expect(result.variants.length).toBe(0);
|
|
145
|
+
expect(result.alternativeRenditions.length).toBe(0);
|
|
146
|
+
expect(result.sessionData.length).toBe(0);
|
|
147
|
+
});
|
|
148
|
+
it('handles invalid resolution format', () => {
|
|
149
|
+
const manifest = [
|
|
150
|
+
'#EXTM3U',
|
|
151
|
+
'#EXT-X-STREAM-INF:BANDWIDTH=1280000,RESOLUTION=640',
|
|
152
|
+
'variant.m3u8',
|
|
153
|
+
].join('\n');
|
|
154
|
+
expect(() => parseMainPlaylist(manifest)).toThrowError(ValidationError);
|
|
155
|
+
});
|
|
156
|
+
it('handles invalid resolution values', () => {
|
|
157
|
+
const manifest = [
|
|
158
|
+
'#EXTM3U',
|
|
159
|
+
'#EXT-X-STREAM-INF:BANDWIDTH=1280000,RESOLUTION=0x360',
|
|
160
|
+
'variant.m3u8',
|
|
161
|
+
].join('\n');
|
|
162
|
+
expect(() => parseMainPlaylist(manifest)).toThrowError(ValidationError);
|
|
163
|
+
});
|
|
164
|
+
it('parses valid resolution correctly', () => {
|
|
165
|
+
const manifest = [
|
|
166
|
+
'#EXTM3U',
|
|
167
|
+
'#EXT-X-STREAM-INF:BANDWIDTH=1280000,RESOLUTION=1920x1080',
|
|
168
|
+
'variant.m3u8',
|
|
169
|
+
].join('\n');
|
|
170
|
+
const result = parseMainPlaylist(manifest);
|
|
171
|
+
expect(result.variants[0].width).toBe(1920);
|
|
172
|
+
expect(result.variants[0].height).toBe(1080);
|
|
173
|
+
});
|
|
174
|
+
it('parses #EXT-X-DEFINE variables', () => {
|
|
175
|
+
const manifest = [
|
|
176
|
+
'#EXTM3U',
|
|
177
|
+
'#EXT-X-DEFINE:NAME="baseUrl",VALUE="https://cdn.example.com/"',
|
|
178
|
+
'#EXT-X-STREAM-INF:BANDWIDTH=500000',
|
|
179
|
+
'low.m3u8',
|
|
180
|
+
].join('\n');
|
|
181
|
+
const result = parseMainPlaylist(manifest);
|
|
182
|
+
expect(result.defines).toEqual({ baseUrl: 'https://cdn.example.com/' });
|
|
183
|
+
});
|
|
184
|
+
it('ignores #EXT-X-DEFINE without VALUE', () => {
|
|
185
|
+
const manifest = [
|
|
186
|
+
'#EXTM3U',
|
|
187
|
+
'#EXT-X-DEFINE:NAME="incomplete"',
|
|
188
|
+
'#EXT-X-STREAM-INF:BANDWIDTH=500000',
|
|
189
|
+
'low.m3u8',
|
|
190
|
+
].join('\n');
|
|
191
|
+
const result = parseMainPlaylist(manifest);
|
|
192
|
+
expect(result.defines).toEqual({});
|
|
193
|
+
});
|
|
194
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parseMediaPlaylist.test.d.ts","sourceRoot":"","sources":["../../../../../test/unit/parse/parseMediaPlaylist.test.ts"],"names":[],"mappings":""}
|