@amazon/vinyl-hls-parser 3.2.2 → 3.2.3-next.20261001224604
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/index.cjs.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/test/benchmark.js.map +2 -2
- package/dist/types/current/tsconfig.tsbuildinfo +1 -1
- package/dist/types/current/types/HlsDateRange.d.ts +6 -4
- package/dist/types/current/types/HlsDateRange.d.ts.map +1 -1
- package/dist/types/ts3.7/types/HlsDateRange.d.ts +6 -4
- package/dist/types/ts4.1/types/HlsDateRange.d.ts +6 -4
- package/dist/types/ts4.7/types/HlsDateRange.d.ts +6 -4
- package/package.json +5 -5
package/dist/index.cjs.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/index.ts", "../src/parse/parseAttributes.ts", "../src/parse/parseMainPlaylist.ts", "../src/types/AlternativeRendition.ts", "../src/parse/parseMediaPlaylist.ts", "../src/types/EncryptionKey.ts", "../src/parse/parseUtil.ts", "../src/types/HlsDateRange.ts"],
|
|
4
|
-
"sourcesContent": ["/** Auto-generated from build scripts */\nexport * from './parse/parseAttributes'\nexport * from './parse/parseMainPlaylist'\nexport * from './parse/parseMediaPlaylist'\nexport * from './parse/parseUtil'\nexport * from './types/AlternativeRendition'\nexport * from './types/EncryptionKey'\nexport * from './types/HlsDateRange'\nexport * from './types/HlsMainPlaylist'\nexport * from './types/HlsMediaPlaylist'\nexport * from './types/HlsSegment'\nexport * from './types/SessionData'\nexport * from './types/VariantStream'\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, substitute, ValidationError } from '@amazon/vinyl-util'\nimport type { AlternativeRendition } from '../types/AlternativeRendition'\nimport { renditionTypeValidator } from '../types/AlternativeRendition'\nimport type { HlsMainPlaylist } from '../types/HlsMainPlaylist'\nimport type { SessionData } from '../types/SessionData'\nimport type { VariantStream } from '../types/VariantStream'\nimport { HLS_VARIABLE_PATTERN } from './parseMediaPlaylist'\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 * @param queryParams Optional query parameters from the playlist's own URL,\n * used to resolve `#EXT-X-DEFINE:QUERYPARAM=\"name\"` entries (RFC 8216\n * \u00A74.4.5.1). Resolved values are exposed via {@link HlsMainPlaylist.defines} so\n * child media playlists can `IMPORT` them. Interstitial ad manifests (e.g.\n * AWS MediaTailor) carry session tokens this way.\n * @returns A readonly HlsMainPlaylist structure.\n * @throws StringParseError if the manifest is malformed.\n */\nexport function parseMainPlaylist(\n text: string,\n queryParams?: Readonly<Record<string, string>>\n): HlsMainPlaylist {\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: substituteHlsVariables(uri, defines),\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 if (attrs['URI']) {\n attrs['URI'] = substituteHlsVariables(attrs['URI'], defines)\n }\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 ...(attrs['FORCED'] && {\n forced: attrs['FORCED'] === 'YES',\n }),\n ...(attrs['CHARACTERISTICS'] && {\n characteristics: attrs['CHARACTERISTICS']\n .split(',')\n .map((c) => c.trim())\n .filter((c) => c.length > 0),\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 const queryParamName = attrs['QUERYPARAM']\n if (queryParamName) {\n // Per RFC 8216 \u00A74.4.5.1: QUERYPARAM copies the value from the\n // named query parameter of the playlist's own URL. Ignore\n // silently if the parameter is absent.\n const queryValue = queryParams?.[queryParamName]\n if (queryValue !== undefined) {\n defines[queryParamName] = queryValue\n }\n } else 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 * Applies EXT-X-DEFINE variable substitution (per RFC 8216 \u00A74.2) to a manifest\n * value, replacing `{$NAME}` tokens with the values declared earlier in the\n * playlist. Returns the input unchanged when no variables have been declared.\n */\nfunction substituteHlsVariables(\n value: string,\n variables: Readonly<Record<string, string>>\n): string {\n if (!value || Object.keys(variables).length === 0) return value\n return substitute(value, variables, HLS_VARIABLE_PATTERN)\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", "/*\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 * Whether this is a forced rendition (FORCED=YES): it presents essential\n * text (e.g. translations of foreign dialogue) that should be shown even\n * when the user has not enabled subtitles. Only meaningful for SUBTITLES.\n */\n readonly forced?: boolean\n\n /**\n * Media Characteristic Tags (CHARACTERISTICS), a comma-separated list of\n * Uniform Type Identifiers describing the rendition, e.g.\n * `public.accessibility.transcribes-spoken-dialog`. Parsed into individual\n * tags. Absent when the attribute is not present.\n */\n readonly characteristics?: readonly string[]\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 { StringReader, substitute } from '@amazon/vinyl-util'\nimport type { HlsDateRange } from '../types/HlsDateRange'\nimport type { EncryptionKey } from '../types/EncryptionKey'\nimport { encryptionMethodValidator } from '../types/EncryptionKey'\nimport type { HlsSegment, HlsMap } from '../types/HlsSegment'\nimport type { HlsMediaPlaylist } from '../types/HlsMediaPlaylist'\nimport { parseAttributes } from './parseAttributes'\nimport { readLine, skipWhitespaceLine, addIfPresent } from './parseUtil'\n\n// The variable-name class excludes `{` (and `}`) so a match attempt can't\n// scan across token boundaries. With `[^}]` a crafted value such as\n// `{$|{$|{$|\u2026` makes replace() do polynomial work (ReDoS); `[^{}]` keeps it\n// linear, and HLS variable names never contain `{`/`}`.\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:'\nconst EXT_X_DEFINE = '#EXT-X-DEFINE:'\nconst EXT_X_DATERANGE = '#EXT-X-DATERANGE:'\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 parent multivariant\n * playlist's #EXT-X-DEFINE tags. Media-playlist `#EXT-X-DEFINE:IMPORT=\"name\"`\n * entries resolve against this map. Local `#EXT-X-DEFINE:NAME=,VALUE=`\n * entries are also supported and take precedence.\n * @param queryParams Optional query parameters from the playlist's own URL,\n * used to resolve `#EXT-X-DEFINE:QUERYPARAM=\"name\"` entries (RFC 8216\n * \u00A74.4.5.1). Interstitial ad manifests (e.g. AWS MediaTailor) commonly carry\n * session tokens this way and reference them as `{$name}` in child URIs.\n * @returns A readonly HlsMediaPlaylist structure.\n * @throws StringParseError if the manifest is malformed.\n */\nexport function parseMediaPlaylist(\n text: string,\n variables?: Readonly<Record<string, string>>,\n queryParams?: Readonly<Record<string, string>>\n): HlsMediaPlaylist {\n const defines: Record<string, string> = { ...(variables ?? {}) }\n const substituteVars = (v: string): string =>\n Object.keys(defines).length === 0\n ? v\n : substitute(v, defines, HLS_VARIABLE_PATTERN)\n\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 const dateRanges: HlsDateRange[] = []\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 = substituteVars(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 } 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 const importName = attrs['IMPORT']\n const queryParamName = attrs['QUERYPARAM']\n if (importName) {\n // Per RFC 8216 \u00A74.4.2.3: IMPORT copies the value from the\n // parent multivariant's DEFINE map. Ignore silently if unknown.\n const imported = variables?.[importName]\n if (imported !== undefined) defines[importName] = imported\n } else if (queryParamName) {\n // Per RFC 8216 \u00A74.4.5.1: QUERYPARAM copies the value from the\n // named query parameter of the playlist's own URL. Ignore\n // silently if the parameter is absent.\n const queryValue = queryParams?.[queryParamName]\n if (queryValue !== undefined) {\n defines[queryParamName] = queryValue\n }\n } else if (attrs['NAME'] && attrs['VALUE']) {\n defines[attrs['NAME']] = attrs['VALUE']\n }\n } else if (trimmed.startsWith(EXT_X_DATERANGE)) {\n const dateRange = parseHlsDateRange(\n trimmed.substring(EXT_X_DATERANGE.length),\n substituteVars\n )\n if (dateRange) dateRanges.push(dateRange)\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 dateRanges,\n }\n}\n\n/**\n * Attribute names that map to typed {@link HlsDateRange} fields and are therefore\n * excluded from the free-form {@link HlsDateRange.clientAttributes} bag.\n */\nconst DATERANGE_RESERVED_ATTRS = /* @__PURE__ */ new Set([\n 'ID',\n 'CLASS',\n 'START-DATE',\n 'END-DATE',\n 'DURATION',\n 'PLANNED-DURATION',\n 'END-ON-NEXT',\n])\n\n/**\n * Parses the attribute list of an EXT-X-DATERANGE tag. Returns undefined when\n * the tag is missing the required ID or START-DATE attributes (per RFC 8216\n * \u00A74.3.2.7 a Date Range without both is not usable), so malformed tags are\n * skipped rather than throwing.\n *\n * The `X-` client-defined attributes are collected verbatim into\n * {@link HlsDateRange.clientAttributes}. Interstitial attributes such as\n * `X-ASSET-URI` frequently reference EXT-X-DEFINE variables, so string values\n * are passed through the same substitution applied to segment URIs.\n */\nfunction parseHlsDateRange(\n attrStr: string,\n substituteVars: (v: string) => string\n): HlsDateRange | undefined {\n const attrs = parseAttributes(new StringReader(attrStr))\n\n const id = attrs['ID']\n // START-DATE may be absent (the Record index type hides this), so gate on\n // key presence rather than a nullish fallback.\n const startDate = 'START-DATE' in attrs ? attrs['START-DATE'] : ''\n // END-ON-NEXT ranges anchor to the following range and may legitimately\n // omit START-DATE; RFC requires ID and CLASS in that case.\n const endOnNext = attrs['END-ON-NEXT'] === 'YES'\n if (!id) return undefined\n if (!startDate && !endOnNext) return undefined\n\n const clientAttributes: Record<string, string> = {}\n for (const [key, value] of Object.entries(attrs)) {\n if (DATERANGE_RESERVED_ATTRS.has(key)) continue\n clientAttributes[key] = substituteVars(value)\n }\n\n return {\n id,\n startDate,\n ...(attrs['CLASS'] && { classId: attrs['CLASS'] }),\n ...(attrs['END-DATE'] && { endDate: attrs['END-DATE'] }),\n ...(attrs['DURATION'] && { duration: Number(attrs['DURATION']) }),\n ...(attrs['PLANNED-DURATION'] && {\n plannedDuration: Number(attrs['PLANNED-DURATION']),\n }),\n ...(endOnNext && { endOnNext: true }),\n clientAttributes,\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 readonly length: number\n readonly 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 { 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' | 'AES-128' | 'SAMPLE-AES' | '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 { 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\n/**\n * The CLASS value that identifies an {@link HlsDateRange} as an HLS Interstitial,\n * the server-guided ad insertion (SGAI) primitive defined by Apple.\n *\n * @see https://developer.apple.com/streaming/GettingStartedWithHLSInterstitials.pdf\n */\nexport const HLS_INTERSTITIAL_CLASS = 'com.apple.hls.interstitial'\n\n/**\n * A parsed EXT-X-DATERANGE tag from an HLS Media Playlist (RFC 8216 \u00A74.3.2.7).\n *\n * A Date Range associates a span of the media timeline, anchored to a wall-clock\n * START-DATE, with a set of application-defined attributes. It is the transport\n * for HLS Interstitials (SGAI): an interstitial is a Date Range whose\n * {@link classId} is {@link HLS_INTERSTITIAL_CLASS} and which carries the\n * `X-ASSET-URI` or `X-ASSET-LIST` client attributes describing the ad content.\n *\n * @see https://datatracker.ietf.org/doc/html/rfc8216#section-4.3.2.7\n */\nexport interface HlsDateRange {\n /** A unique identifier for this Date Range within the playlist (ID). */\n readonly id: string\n\n /** The `CLASS` attribute, a client-defined category for the range, if present. */\n readonly classId?: string\n\n /** The ISO-8601 wall-clock time at which the range begins (START-DATE). */\n readonly startDate: string\n\n /** The ISO-8601 wall-clock time at which the range ends (END-DATE), if present. */\n readonly endDate?: string\n\n /** The duration of the range in seconds (DURATION), if present. */\n readonly duration?: number\n\n /**\n * The expected duration of the Date Range in seconds.\n * This attribute SHOULD be used to indicate the expected duration of a Date Range whose actual\n * duration is not yet known.\n * It must be positive.\n */\n readonly plannedDuration?: number\n\n /**\n * Whether the END-DATE of a live range equals the START-DATE of the\n * range that follows it (END-ON-NEXT=YES). Ranges with this flag must\n * carry a {@link classId} and omit {@link duration} and {@link endDate}.\n */\n readonly endOnNext?: boolean\n\n /**\n * The client-defined `X-` attributes of the range, keyed by their full\n * attribute name (e.g. `X-ASSET-URI`). Values are the raw parsed strings\n * with surrounding quotes removed. Numeric and hex `X-` attributes are\n * kept verbatim so callers can interpret them per their own schema.\n */\n readonly clientAttributes: Readonly<Record<string, string>>\n}\n"],
|
|
4
|
+
"sourcesContent": ["/** Auto-generated from build scripts */\nexport * from './parse/parseAttributes'\nexport * from './parse/parseMainPlaylist'\nexport * from './parse/parseMediaPlaylist'\nexport * from './parse/parseUtil'\nexport * from './types/AlternativeRendition'\nexport * from './types/EncryptionKey'\nexport * from './types/HlsDateRange'\nexport * from './types/HlsMainPlaylist'\nexport * from './types/HlsMediaPlaylist'\nexport * from './types/HlsSegment'\nexport * from './types/SessionData'\nexport * from './types/VariantStream'\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, substitute, ValidationError } from '@amazon/vinyl-util'\nimport type { AlternativeRendition } from '../types/AlternativeRendition'\nimport { renditionTypeValidator } from '../types/AlternativeRendition'\nimport type { HlsMainPlaylist } from '../types/HlsMainPlaylist'\nimport type { SessionData } from '../types/SessionData'\nimport type { VariantStream } from '../types/VariantStream'\nimport { HLS_VARIABLE_PATTERN } from './parseMediaPlaylist'\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 * @param queryParams Optional query parameters from the playlist's own URL,\n * used to resolve `#EXT-X-DEFINE:QUERYPARAM=\"name\"` entries (RFC 8216\n * \u00A74.4.5.1). Resolved values are exposed via {@link HlsMainPlaylist.defines} so\n * child media playlists can `IMPORT` them. Interstitial ad manifests (e.g.\n * AWS MediaTailor) carry session tokens this way.\n * @returns A readonly HlsMainPlaylist structure.\n * @throws StringParseError if the manifest is malformed.\n */\nexport function parseMainPlaylist(\n text: string,\n queryParams?: Readonly<Record<string, string>>\n): HlsMainPlaylist {\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: substituteHlsVariables(uri, defines),\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 if (attrs['URI']) {\n attrs['URI'] = substituteHlsVariables(attrs['URI'], defines)\n }\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 ...(attrs['FORCED'] && {\n forced: attrs['FORCED'] === 'YES',\n }),\n ...(attrs['CHARACTERISTICS'] && {\n characteristics: attrs['CHARACTERISTICS']\n .split(',')\n .map((c) => c.trim())\n .filter((c) => c.length > 0),\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 const queryParamName = attrs['QUERYPARAM']\n if (queryParamName) {\n // Per RFC 8216 \u00A74.4.5.1: QUERYPARAM copies the value from the\n // named query parameter of the playlist's own URL. Ignore\n // silently if the parameter is absent.\n const queryValue = queryParams?.[queryParamName]\n if (queryValue !== undefined) {\n defines[queryParamName] = queryValue\n }\n } else 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 * Applies EXT-X-DEFINE variable substitution (per RFC 8216 \u00A74.2) to a manifest\n * value, replacing `{$NAME}` tokens with the values declared earlier in the\n * playlist. Returns the input unchanged when no variables have been declared.\n */\nfunction substituteHlsVariables(\n value: string,\n variables: Readonly<Record<string, string>>\n): string {\n if (!value || Object.keys(variables).length === 0) return value\n return substitute(value, variables, HLS_VARIABLE_PATTERN)\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", "/*\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 * Whether this is a forced rendition (FORCED=YES): it presents essential\n * text (e.g. translations of foreign dialogue) that should be shown even\n * when the user has not enabled subtitles. Only meaningful for SUBTITLES.\n */\n readonly forced?: boolean\n\n /**\n * Media Characteristic Tags (CHARACTERISTICS), a comma-separated list of\n * Uniform Type Identifiers describing the rendition, e.g.\n * `public.accessibility.transcribes-spoken-dialog`. Parsed into individual\n * tags. Absent when the attribute is not present.\n */\n readonly characteristics?: readonly string[]\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 { StringReader, substitute } from '@amazon/vinyl-util'\nimport type { HlsDateRange } from '../types/HlsDateRange'\nimport type { EncryptionKey } from '../types/EncryptionKey'\nimport { encryptionMethodValidator } from '../types/EncryptionKey'\nimport type { HlsSegment, HlsMap } from '../types/HlsSegment'\nimport type { HlsMediaPlaylist } from '../types/HlsMediaPlaylist'\nimport { parseAttributes } from './parseAttributes'\nimport { readLine, skipWhitespaceLine, addIfPresent } from './parseUtil'\n\n// The variable-name class excludes `{` (and `}`) so a match attempt can't\n// scan across token boundaries. With `[^}]` a crafted value such as\n// `{$|{$|{$|\u2026` makes replace() do polynomial work (ReDoS); `[^{}]` keeps it\n// linear, and HLS variable names never contain `{`/`}`.\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:'\nconst EXT_X_DEFINE = '#EXT-X-DEFINE:'\nconst EXT_X_DATERANGE = '#EXT-X-DATERANGE:'\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 parent multivariant\n * playlist's #EXT-X-DEFINE tags. Media-playlist `#EXT-X-DEFINE:IMPORT=\"name\"`\n * entries resolve against this map. Local `#EXT-X-DEFINE:NAME=,VALUE=`\n * entries are also supported and take precedence.\n * @param queryParams Optional query parameters from the playlist's own URL,\n * used to resolve `#EXT-X-DEFINE:QUERYPARAM=\"name\"` entries (RFC 8216\n * \u00A74.4.5.1). Interstitial ad manifests (e.g. AWS MediaTailor) commonly carry\n * session tokens this way and reference them as `{$name}` in child URIs.\n * @returns A readonly HlsMediaPlaylist structure.\n * @throws StringParseError if the manifest is malformed.\n */\nexport function parseMediaPlaylist(\n text: string,\n variables?: Readonly<Record<string, string>>,\n queryParams?: Readonly<Record<string, string>>\n): HlsMediaPlaylist {\n const defines: Record<string, string> = { ...(variables ?? {}) }\n const substituteVars = (v: string): string =>\n Object.keys(defines).length === 0\n ? v\n : substitute(v, defines, HLS_VARIABLE_PATTERN)\n\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 const dateRanges: HlsDateRange[] = []\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 = substituteVars(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 } 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 const importName = attrs['IMPORT']\n const queryParamName = attrs['QUERYPARAM']\n if (importName) {\n // Per RFC 8216 \u00A74.4.2.3: IMPORT copies the value from the\n // parent multivariant's DEFINE map. Ignore silently if unknown.\n const imported = variables?.[importName]\n if (imported !== undefined) defines[importName] = imported\n } else if (queryParamName) {\n // Per RFC 8216 \u00A74.4.5.1: QUERYPARAM copies the value from the\n // named query parameter of the playlist's own URL. Ignore\n // silently if the parameter is absent.\n const queryValue = queryParams?.[queryParamName]\n if (queryValue !== undefined) {\n defines[queryParamName] = queryValue\n }\n } else if (attrs['NAME'] && attrs['VALUE']) {\n defines[attrs['NAME']] = attrs['VALUE']\n }\n } else if (trimmed.startsWith(EXT_X_DATERANGE)) {\n const dateRange = parseHlsDateRange(\n trimmed.substring(EXT_X_DATERANGE.length),\n substituteVars\n )\n if (dateRange) dateRanges.push(dateRange)\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 dateRanges,\n }\n}\n\n/**\n * Attribute names that map to typed {@link HlsDateRange} fields and are therefore\n * excluded from the free-form {@link HlsDateRange.clientAttributes} bag.\n */\nconst DATERANGE_RESERVED_ATTRS = /* @__PURE__ */ new Set([\n 'ID',\n 'CLASS',\n 'START-DATE',\n 'END-DATE',\n 'DURATION',\n 'PLANNED-DURATION',\n 'END-ON-NEXT',\n])\n\n/**\n * Parses the attribute list of an EXT-X-DATERANGE tag. Returns undefined when\n * the tag is missing the required ID or START-DATE attributes (per RFC 8216\n * \u00A74.3.2.7 a Date Range without both is not usable), so malformed tags are\n * skipped rather than throwing.\n *\n * The `X-` client-defined attributes are collected verbatim into\n * {@link HlsDateRange.clientAttributes}. Interstitial attributes such as\n * `X-ASSET-URI` frequently reference EXT-X-DEFINE variables, so string values\n * are passed through the same substitution applied to segment URIs.\n */\nfunction parseHlsDateRange(\n attrStr: string,\n substituteVars: (v: string) => string\n): HlsDateRange | undefined {\n const attrs = parseAttributes(new StringReader(attrStr))\n\n const id = attrs['ID']\n // START-DATE may be absent (the Record index type hides this), so gate on\n // key presence rather than a nullish fallback.\n const startDate = 'START-DATE' in attrs ? attrs['START-DATE'] : ''\n // END-ON-NEXT ranges anchor to the following range and may legitimately\n // omit START-DATE; RFC requires ID and CLASS in that case.\n const endOnNext = attrs['END-ON-NEXT'] === 'YES'\n if (!id) return undefined\n if (!startDate && !endOnNext) return undefined\n\n const clientAttributes: Record<string, string> = {}\n for (const [key, value] of Object.entries(attrs)) {\n if (DATERANGE_RESERVED_ATTRS.has(key)) continue\n clientAttributes[key] = substituteVars(value)\n }\n\n return {\n id,\n startDate,\n ...(attrs['CLASS'] && { classId: attrs['CLASS'] }),\n ...(attrs['END-DATE'] && { endDate: attrs['END-DATE'] }),\n ...(attrs['DURATION'] && { duration: Number(attrs['DURATION']) }),\n ...(attrs['PLANNED-DURATION'] && {\n plannedDuration: Number(attrs['PLANNED-DURATION']),\n }),\n ...(endOnNext && { endOnNext: true }),\n clientAttributes,\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 readonly length: number\n readonly 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 { 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' | 'AES-128' | 'SAMPLE-AES' | '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 { 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\n/**\n * The CLASS value that identifies an {@link HlsDateRange} as an HLS Interstitial,\n * the server-guided ad insertion (SGAI) primitive defined by Apple.\n *\n * @see https://developer.apple.com/streaming/GettingStartedWithHLSInterstitials.pdf\n */\nexport const HLS_INTERSTITIAL_CLASS = 'com.apple.hls.interstitial'\n\n/**\n * A parsed EXT-X-DATERANGE tag from an HLS Media Playlist (RFC 8216 \u00A74.3.2.7).\n *\n * A Date Range associates a span of the media timeline, anchored to a wall-clock\n * START-DATE, with a set of application-defined attributes. It is the transport\n * for HLS Interstitials (SGAI): an interstitial is a Date Range whose\n * {@link classId} is {@link HLS_INTERSTITIAL_CLASS} and which carries the\n * `X-ASSET-URI` or `X-ASSET-LIST` client attributes describing the ad content.\n *\n * @see https://datatracker.ietf.org/doc/html/rfc8216#section-4.3.2.7\n */\nexport interface HlsDateRange {\n /** A unique identifier for this Date Range within the playlist (ID). */\n readonly id: string\n\n /** The `CLASS` attribute, a client-defined category for the range, if present. */\n readonly classId?: string\n\n /** The ISO-8601 wall-clock time at which the range begins (START-DATE). */\n readonly startDate: string\n\n /** The ISO-8601 wall-clock time at which the range ends (END-DATE), if present. */\n readonly endDate?: string\n\n /** The duration of the range in seconds (DURATION), if present. */\n readonly duration?: number\n\n /**\n * The expected duration of the Date Range in seconds.\n * This attribute SHOULD be used to indicate the expected duration of a Date Range whose actual\n * duration is not yet known.\n * It must be positive.\n */\n readonly plannedDuration?: number\n\n /**\n * Whether the END-DATE of a live range equals the START-DATE of the\n * range that follows it (END-ON-NEXT=YES). Ranges with this flag must\n * carry a {@link classId} and omit {@link duration} and {@link endDate}.\n */\n readonly endOnNext?: boolean\n\n /**\n * Every attribute of the range that does not map to a typed field above,\n * keyed by its full attribute name \u2014 the client-defined `X-` attributes\n * (e.g. `X-ASSET-URI`) plus the remaining standard ones such as `CUE` and\n * `SCTE35-*`. Values are the raw parsed strings with surrounding quotes\n * removed. Numeric and hex attributes are kept verbatim so callers can\n * interpret them per their own schema.\n */\n readonly clientAttributes: Readonly<Record<string, string>>\n}\n"],
|
|
5
5
|
"mappings": "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",
|
|
6
6
|
"names": ["import_vinyl_util", "import_vinyl_util", "import_vinyl_validation", "import_vinyl_util", "EXTM3U", "EXT_X_DEFINE"]
|
|
7
7
|
}
|
package/dist/index.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/parse/parseAttributes.ts", "../src/parse/parseMainPlaylist.ts", "../src/types/AlternativeRendition.ts", "../src/parse/parseMediaPlaylist.ts", "../src/types/EncryptionKey.ts", "../src/parse/parseUtil.ts", "../src/types/HlsDateRange.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 { 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, substitute, ValidationError } from '@amazon/vinyl-util'\nimport type { AlternativeRendition } from '../types/AlternativeRendition'\nimport { renditionTypeValidator } from '../types/AlternativeRendition'\nimport type { HlsMainPlaylist } from '../types/HlsMainPlaylist'\nimport type { SessionData } from '../types/SessionData'\nimport type { VariantStream } from '../types/VariantStream'\nimport { HLS_VARIABLE_PATTERN } from './parseMediaPlaylist'\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 * @param queryParams Optional query parameters from the playlist's own URL,\n * used to resolve `#EXT-X-DEFINE:QUERYPARAM=\"name\"` entries (RFC 8216\n * \u00A74.4.5.1). Resolved values are exposed via {@link HlsMainPlaylist.defines} so\n * child media playlists can `IMPORT` them. Interstitial ad manifests (e.g.\n * AWS MediaTailor) carry session tokens this way.\n * @returns A readonly HlsMainPlaylist structure.\n * @throws StringParseError if the manifest is malformed.\n */\nexport function parseMainPlaylist(\n text: string,\n queryParams?: Readonly<Record<string, string>>\n): HlsMainPlaylist {\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: substituteHlsVariables(uri, defines),\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 if (attrs['URI']) {\n attrs['URI'] = substituteHlsVariables(attrs['URI'], defines)\n }\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 ...(attrs['FORCED'] && {\n forced: attrs['FORCED'] === 'YES',\n }),\n ...(attrs['CHARACTERISTICS'] && {\n characteristics: attrs['CHARACTERISTICS']\n .split(',')\n .map((c) => c.trim())\n .filter((c) => c.length > 0),\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 const queryParamName = attrs['QUERYPARAM']\n if (queryParamName) {\n // Per RFC 8216 \u00A74.4.5.1: QUERYPARAM copies the value from the\n // named query parameter of the playlist's own URL. Ignore\n // silently if the parameter is absent.\n const queryValue = queryParams?.[queryParamName]\n if (queryValue !== undefined) {\n defines[queryParamName] = queryValue\n }\n } else 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 * Applies EXT-X-DEFINE variable substitution (per RFC 8216 \u00A74.2) to a manifest\n * value, replacing `{$NAME}` tokens with the values declared earlier in the\n * playlist. Returns the input unchanged when no variables have been declared.\n */\nfunction substituteHlsVariables(\n value: string,\n variables: Readonly<Record<string, string>>\n): string {\n if (!value || Object.keys(variables).length === 0) return value\n return substitute(value, variables, HLS_VARIABLE_PATTERN)\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", "/*\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 * Whether this is a forced rendition (FORCED=YES): it presents essential\n * text (e.g. translations of foreign dialogue) that should be shown even\n * when the user has not enabled subtitles. Only meaningful for SUBTITLES.\n */\n readonly forced?: boolean\n\n /**\n * Media Characteristic Tags (CHARACTERISTICS), a comma-separated list of\n * Uniform Type Identifiers describing the rendition, e.g.\n * `public.accessibility.transcribes-spoken-dialog`. Parsed into individual\n * tags. Absent when the attribute is not present.\n */\n readonly characteristics?: readonly string[]\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 { StringReader, substitute } from '@amazon/vinyl-util'\nimport type { HlsDateRange } from '../types/HlsDateRange'\nimport type { EncryptionKey } from '../types/EncryptionKey'\nimport { encryptionMethodValidator } from '../types/EncryptionKey'\nimport type { HlsSegment, HlsMap } from '../types/HlsSegment'\nimport type { HlsMediaPlaylist } from '../types/HlsMediaPlaylist'\nimport { parseAttributes } from './parseAttributes'\nimport { readLine, skipWhitespaceLine, addIfPresent } from './parseUtil'\n\n// The variable-name class excludes `{` (and `}`) so a match attempt can't\n// scan across token boundaries. With `[^}]` a crafted value such as\n// `{$|{$|{$|\u2026` makes replace() do polynomial work (ReDoS); `[^{}]` keeps it\n// linear, and HLS variable names never contain `{`/`}`.\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:'\nconst EXT_X_DEFINE = '#EXT-X-DEFINE:'\nconst EXT_X_DATERANGE = '#EXT-X-DATERANGE:'\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 parent multivariant\n * playlist's #EXT-X-DEFINE tags. Media-playlist `#EXT-X-DEFINE:IMPORT=\"name\"`\n * entries resolve against this map. Local `#EXT-X-DEFINE:NAME=,VALUE=`\n * entries are also supported and take precedence.\n * @param queryParams Optional query parameters from the playlist's own URL,\n * used to resolve `#EXT-X-DEFINE:QUERYPARAM=\"name\"` entries (RFC 8216\n * \u00A74.4.5.1). Interstitial ad manifests (e.g. AWS MediaTailor) commonly carry\n * session tokens this way and reference them as `{$name}` in child URIs.\n * @returns A readonly HlsMediaPlaylist structure.\n * @throws StringParseError if the manifest is malformed.\n */\nexport function parseMediaPlaylist(\n text: string,\n variables?: Readonly<Record<string, string>>,\n queryParams?: Readonly<Record<string, string>>\n): HlsMediaPlaylist {\n const defines: Record<string, string> = { ...(variables ?? {}) }\n const substituteVars = (v: string): string =>\n Object.keys(defines).length === 0\n ? v\n : substitute(v, defines, HLS_VARIABLE_PATTERN)\n\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 const dateRanges: HlsDateRange[] = []\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 = substituteVars(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 } 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 const importName = attrs['IMPORT']\n const queryParamName = attrs['QUERYPARAM']\n if (importName) {\n // Per RFC 8216 \u00A74.4.2.3: IMPORT copies the value from the\n // parent multivariant's DEFINE map. Ignore silently if unknown.\n const imported = variables?.[importName]\n if (imported !== undefined) defines[importName] = imported\n } else if (queryParamName) {\n // Per RFC 8216 \u00A74.4.5.1: QUERYPARAM copies the value from the\n // named query parameter of the playlist's own URL. Ignore\n // silently if the parameter is absent.\n const queryValue = queryParams?.[queryParamName]\n if (queryValue !== undefined) {\n defines[queryParamName] = queryValue\n }\n } else if (attrs['NAME'] && attrs['VALUE']) {\n defines[attrs['NAME']] = attrs['VALUE']\n }\n } else if (trimmed.startsWith(EXT_X_DATERANGE)) {\n const dateRange = parseHlsDateRange(\n trimmed.substring(EXT_X_DATERANGE.length),\n substituteVars\n )\n if (dateRange) dateRanges.push(dateRange)\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 dateRanges,\n }\n}\n\n/**\n * Attribute names that map to typed {@link HlsDateRange} fields and are therefore\n * excluded from the free-form {@link HlsDateRange.clientAttributes} bag.\n */\nconst DATERANGE_RESERVED_ATTRS = /* @__PURE__ */ new Set([\n 'ID',\n 'CLASS',\n 'START-DATE',\n 'END-DATE',\n 'DURATION',\n 'PLANNED-DURATION',\n 'END-ON-NEXT',\n])\n\n/**\n * Parses the attribute list of an EXT-X-DATERANGE tag. Returns undefined when\n * the tag is missing the required ID or START-DATE attributes (per RFC 8216\n * \u00A74.3.2.7 a Date Range without both is not usable), so malformed tags are\n * skipped rather than throwing.\n *\n * The `X-` client-defined attributes are collected verbatim into\n * {@link HlsDateRange.clientAttributes}. Interstitial attributes such as\n * `X-ASSET-URI` frequently reference EXT-X-DEFINE variables, so string values\n * are passed through the same substitution applied to segment URIs.\n */\nfunction parseHlsDateRange(\n attrStr: string,\n substituteVars: (v: string) => string\n): HlsDateRange | undefined {\n const attrs = parseAttributes(new StringReader(attrStr))\n\n const id = attrs['ID']\n // START-DATE may be absent (the Record index type hides this), so gate on\n // key presence rather than a nullish fallback.\n const startDate = 'START-DATE' in attrs ? attrs['START-DATE'] : ''\n // END-ON-NEXT ranges anchor to the following range and may legitimately\n // omit START-DATE; RFC requires ID and CLASS in that case.\n const endOnNext = attrs['END-ON-NEXT'] === 'YES'\n if (!id) return undefined\n if (!startDate && !endOnNext) return undefined\n\n const clientAttributes: Record<string, string> = {}\n for (const [key, value] of Object.entries(attrs)) {\n if (DATERANGE_RESERVED_ATTRS.has(key)) continue\n clientAttributes[key] = substituteVars(value)\n }\n\n return {\n id,\n startDate,\n ...(attrs['CLASS'] && { classId: attrs['CLASS'] }),\n ...(attrs['END-DATE'] && { endDate: attrs['END-DATE'] }),\n ...(attrs['DURATION'] && { duration: Number(attrs['DURATION']) }),\n ...(attrs['PLANNED-DURATION'] && {\n plannedDuration: Number(attrs['PLANNED-DURATION']),\n }),\n ...(endOnNext && { endOnNext: true }),\n clientAttributes,\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 readonly length: number\n readonly 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 { 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' | 'AES-128' | 'SAMPLE-AES' | '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 { 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\n/**\n * The CLASS value that identifies an {@link HlsDateRange} as an HLS Interstitial,\n * the server-guided ad insertion (SGAI) primitive defined by Apple.\n *\n * @see https://developer.apple.com/streaming/GettingStartedWithHLSInterstitials.pdf\n */\nexport const HLS_INTERSTITIAL_CLASS = 'com.apple.hls.interstitial'\n\n/**\n * A parsed EXT-X-DATERANGE tag from an HLS Media Playlist (RFC 8216 \u00A74.3.2.7).\n *\n * A Date Range associates a span of the media timeline, anchored to a wall-clock\n * START-DATE, with a set of application-defined attributes. It is the transport\n * for HLS Interstitials (SGAI): an interstitial is a Date Range whose\n * {@link classId} is {@link HLS_INTERSTITIAL_CLASS} and which carries the\n * `X-ASSET-URI` or `X-ASSET-LIST` client attributes describing the ad content.\n *\n * @see https://datatracker.ietf.org/doc/html/rfc8216#section-4.3.2.7\n */\nexport interface HlsDateRange {\n /** A unique identifier for this Date Range within the playlist (ID). */\n readonly id: string\n\n /** The `CLASS` attribute, a client-defined category for the range, if present. */\n readonly classId?: string\n\n /** The ISO-8601 wall-clock time at which the range begins (START-DATE). */\n readonly startDate: string\n\n /** The ISO-8601 wall-clock time at which the range ends (END-DATE), if present. */\n readonly endDate?: string\n\n /** The duration of the range in seconds (DURATION), if present. */\n readonly duration?: number\n\n /**\n * The expected duration of the Date Range in seconds.\n * This attribute SHOULD be used to indicate the expected duration of a Date Range whose actual\n * duration is not yet known.\n * It must be positive.\n */\n readonly plannedDuration?: number\n\n /**\n * Whether the END-DATE of a live range equals the START-DATE of the\n * range that follows it (END-ON-NEXT=YES). Ranges with this flag must\n * carry a {@link classId} and omit {@link duration} and {@link endDate}.\n */\n readonly endOnNext?: boolean\n\n /**\n * The client-defined `X-` attributes of the range, keyed by their full\n * attribute name (e.g. `X-ASSET-URI`). Values are the raw parsed strings\n * with surrounding quotes removed. Numeric and hex `X-` attributes are\n * kept verbatim so callers can interpret them per their own schema.\n */\n readonly clientAttributes: Readonly<Record<string, string>>\n}\n"],
|
|
4
|
+
"sourcesContent": ["/*\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, substitute, ValidationError } from '@amazon/vinyl-util'\nimport type { AlternativeRendition } from '../types/AlternativeRendition'\nimport { renditionTypeValidator } from '../types/AlternativeRendition'\nimport type { HlsMainPlaylist } from '../types/HlsMainPlaylist'\nimport type { SessionData } from '../types/SessionData'\nimport type { VariantStream } from '../types/VariantStream'\nimport { HLS_VARIABLE_PATTERN } from './parseMediaPlaylist'\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 * @param queryParams Optional query parameters from the playlist's own URL,\n * used to resolve `#EXT-X-DEFINE:QUERYPARAM=\"name\"` entries (RFC 8216\n * \u00A74.4.5.1). Resolved values are exposed via {@link HlsMainPlaylist.defines} so\n * child media playlists can `IMPORT` them. Interstitial ad manifests (e.g.\n * AWS MediaTailor) carry session tokens this way.\n * @returns A readonly HlsMainPlaylist structure.\n * @throws StringParseError if the manifest is malformed.\n */\nexport function parseMainPlaylist(\n text: string,\n queryParams?: Readonly<Record<string, string>>\n): HlsMainPlaylist {\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: substituteHlsVariables(uri, defines),\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 if (attrs['URI']) {\n attrs['URI'] = substituteHlsVariables(attrs['URI'], defines)\n }\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 ...(attrs['FORCED'] && {\n forced: attrs['FORCED'] === 'YES',\n }),\n ...(attrs['CHARACTERISTICS'] && {\n characteristics: attrs['CHARACTERISTICS']\n .split(',')\n .map((c) => c.trim())\n .filter((c) => c.length > 0),\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 const queryParamName = attrs['QUERYPARAM']\n if (queryParamName) {\n // Per RFC 8216 \u00A74.4.5.1: QUERYPARAM copies the value from the\n // named query parameter of the playlist's own URL. Ignore\n // silently if the parameter is absent.\n const queryValue = queryParams?.[queryParamName]\n if (queryValue !== undefined) {\n defines[queryParamName] = queryValue\n }\n } else 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 * Applies EXT-X-DEFINE variable substitution (per RFC 8216 \u00A74.2) to a manifest\n * value, replacing `{$NAME}` tokens with the values declared earlier in the\n * playlist. Returns the input unchanged when no variables have been declared.\n */\nfunction substituteHlsVariables(\n value: string,\n variables: Readonly<Record<string, string>>\n): string {\n if (!value || Object.keys(variables).length === 0) return value\n return substitute(value, variables, HLS_VARIABLE_PATTERN)\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", "/*\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 * Whether this is a forced rendition (FORCED=YES): it presents essential\n * text (e.g. translations of foreign dialogue) that should be shown even\n * when the user has not enabled subtitles. Only meaningful for SUBTITLES.\n */\n readonly forced?: boolean\n\n /**\n * Media Characteristic Tags (CHARACTERISTICS), a comma-separated list of\n * Uniform Type Identifiers describing the rendition, e.g.\n * `public.accessibility.transcribes-spoken-dialog`. Parsed into individual\n * tags. Absent when the attribute is not present.\n */\n readonly characteristics?: readonly string[]\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 { StringReader, substitute } from '@amazon/vinyl-util'\nimport type { HlsDateRange } from '../types/HlsDateRange'\nimport type { EncryptionKey } from '../types/EncryptionKey'\nimport { encryptionMethodValidator } from '../types/EncryptionKey'\nimport type { HlsSegment, HlsMap } from '../types/HlsSegment'\nimport type { HlsMediaPlaylist } from '../types/HlsMediaPlaylist'\nimport { parseAttributes } from './parseAttributes'\nimport { readLine, skipWhitespaceLine, addIfPresent } from './parseUtil'\n\n// The variable-name class excludes `{` (and `}`) so a match attempt can't\n// scan across token boundaries. With `[^}]` a crafted value such as\n// `{$|{$|{$|\u2026` makes replace() do polynomial work (ReDoS); `[^{}]` keeps it\n// linear, and HLS variable names never contain `{`/`}`.\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:'\nconst EXT_X_DEFINE = '#EXT-X-DEFINE:'\nconst EXT_X_DATERANGE = '#EXT-X-DATERANGE:'\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 parent multivariant\n * playlist's #EXT-X-DEFINE tags. Media-playlist `#EXT-X-DEFINE:IMPORT=\"name\"`\n * entries resolve against this map. Local `#EXT-X-DEFINE:NAME=,VALUE=`\n * entries are also supported and take precedence.\n * @param queryParams Optional query parameters from the playlist's own URL,\n * used to resolve `#EXT-X-DEFINE:QUERYPARAM=\"name\"` entries (RFC 8216\n * \u00A74.4.5.1). Interstitial ad manifests (e.g. AWS MediaTailor) commonly carry\n * session tokens this way and reference them as `{$name}` in child URIs.\n * @returns A readonly HlsMediaPlaylist structure.\n * @throws StringParseError if the manifest is malformed.\n */\nexport function parseMediaPlaylist(\n text: string,\n variables?: Readonly<Record<string, string>>,\n queryParams?: Readonly<Record<string, string>>\n): HlsMediaPlaylist {\n const defines: Record<string, string> = { ...(variables ?? {}) }\n const substituteVars = (v: string): string =>\n Object.keys(defines).length === 0\n ? v\n : substitute(v, defines, HLS_VARIABLE_PATTERN)\n\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 const dateRanges: HlsDateRange[] = []\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 = substituteVars(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 } 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 const importName = attrs['IMPORT']\n const queryParamName = attrs['QUERYPARAM']\n if (importName) {\n // Per RFC 8216 \u00A74.4.2.3: IMPORT copies the value from the\n // parent multivariant's DEFINE map. Ignore silently if unknown.\n const imported = variables?.[importName]\n if (imported !== undefined) defines[importName] = imported\n } else if (queryParamName) {\n // Per RFC 8216 \u00A74.4.5.1: QUERYPARAM copies the value from the\n // named query parameter of the playlist's own URL. Ignore\n // silently if the parameter is absent.\n const queryValue = queryParams?.[queryParamName]\n if (queryValue !== undefined) {\n defines[queryParamName] = queryValue\n }\n } else if (attrs['NAME'] && attrs['VALUE']) {\n defines[attrs['NAME']] = attrs['VALUE']\n }\n } else if (trimmed.startsWith(EXT_X_DATERANGE)) {\n const dateRange = parseHlsDateRange(\n trimmed.substring(EXT_X_DATERANGE.length),\n substituteVars\n )\n if (dateRange) dateRanges.push(dateRange)\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 dateRanges,\n }\n}\n\n/**\n * Attribute names that map to typed {@link HlsDateRange} fields and are therefore\n * excluded from the free-form {@link HlsDateRange.clientAttributes} bag.\n */\nconst DATERANGE_RESERVED_ATTRS = /* @__PURE__ */ new Set([\n 'ID',\n 'CLASS',\n 'START-DATE',\n 'END-DATE',\n 'DURATION',\n 'PLANNED-DURATION',\n 'END-ON-NEXT',\n])\n\n/**\n * Parses the attribute list of an EXT-X-DATERANGE tag. Returns undefined when\n * the tag is missing the required ID or START-DATE attributes (per RFC 8216\n * \u00A74.3.2.7 a Date Range without both is not usable), so malformed tags are\n * skipped rather than throwing.\n *\n * The `X-` client-defined attributes are collected verbatim into\n * {@link HlsDateRange.clientAttributes}. Interstitial attributes such as\n * `X-ASSET-URI` frequently reference EXT-X-DEFINE variables, so string values\n * are passed through the same substitution applied to segment URIs.\n */\nfunction parseHlsDateRange(\n attrStr: string,\n substituteVars: (v: string) => string\n): HlsDateRange | undefined {\n const attrs = parseAttributes(new StringReader(attrStr))\n\n const id = attrs['ID']\n // START-DATE may be absent (the Record index type hides this), so gate on\n // key presence rather than a nullish fallback.\n const startDate = 'START-DATE' in attrs ? attrs['START-DATE'] : ''\n // END-ON-NEXT ranges anchor to the following range and may legitimately\n // omit START-DATE; RFC requires ID and CLASS in that case.\n const endOnNext = attrs['END-ON-NEXT'] === 'YES'\n if (!id) return undefined\n if (!startDate && !endOnNext) return undefined\n\n const clientAttributes: Record<string, string> = {}\n for (const [key, value] of Object.entries(attrs)) {\n if (DATERANGE_RESERVED_ATTRS.has(key)) continue\n clientAttributes[key] = substituteVars(value)\n }\n\n return {\n id,\n startDate,\n ...(attrs['CLASS'] && { classId: attrs['CLASS'] }),\n ...(attrs['END-DATE'] && { endDate: attrs['END-DATE'] }),\n ...(attrs['DURATION'] && { duration: Number(attrs['DURATION']) }),\n ...(attrs['PLANNED-DURATION'] && {\n plannedDuration: Number(attrs['PLANNED-DURATION']),\n }),\n ...(endOnNext && { endOnNext: true }),\n clientAttributes,\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 readonly length: number\n readonly 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 { 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' | 'AES-128' | 'SAMPLE-AES' | '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 { 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\n/**\n * The CLASS value that identifies an {@link HlsDateRange} as an HLS Interstitial,\n * the server-guided ad insertion (SGAI) primitive defined by Apple.\n *\n * @see https://developer.apple.com/streaming/GettingStartedWithHLSInterstitials.pdf\n */\nexport const HLS_INTERSTITIAL_CLASS = 'com.apple.hls.interstitial'\n\n/**\n * A parsed EXT-X-DATERANGE tag from an HLS Media Playlist (RFC 8216 \u00A74.3.2.7).\n *\n * A Date Range associates a span of the media timeline, anchored to a wall-clock\n * START-DATE, with a set of application-defined attributes. It is the transport\n * for HLS Interstitials (SGAI): an interstitial is a Date Range whose\n * {@link classId} is {@link HLS_INTERSTITIAL_CLASS} and which carries the\n * `X-ASSET-URI` or `X-ASSET-LIST` client attributes describing the ad content.\n *\n * @see https://datatracker.ietf.org/doc/html/rfc8216#section-4.3.2.7\n */\nexport interface HlsDateRange {\n /** A unique identifier for this Date Range within the playlist (ID). */\n readonly id: string\n\n /** The `CLASS` attribute, a client-defined category for the range, if present. */\n readonly classId?: string\n\n /** The ISO-8601 wall-clock time at which the range begins (START-DATE). */\n readonly startDate: string\n\n /** The ISO-8601 wall-clock time at which the range ends (END-DATE), if present. */\n readonly endDate?: string\n\n /** The duration of the range in seconds (DURATION), if present. */\n readonly duration?: number\n\n /**\n * The expected duration of the Date Range in seconds.\n * This attribute SHOULD be used to indicate the expected duration of a Date Range whose actual\n * duration is not yet known.\n * It must be positive.\n */\n readonly plannedDuration?: number\n\n /**\n * Whether the END-DATE of a live range equals the START-DATE of the\n * range that follows it (END-ON-NEXT=YES). Ranges with this flag must\n * carry a {@link classId} and omit {@link duration} and {@link endDate}.\n */\n readonly endOnNext?: boolean\n\n /**\n * Every attribute of the range that does not map to a typed field above,\n * keyed by its full attribute name \u2014 the client-defined `X-` attributes\n * (e.g. `X-ASSET-URI`) plus the remaining standard ones such as `CUE` and\n * `SCTE35-*`. Values are the raw parsed strings with surrounding quotes\n * removed. Numeric and hex attributes are kept verbatim so callers can\n * interpret them per their own schema.\n */\n readonly clientAttributes: Readonly<Record<string, string>>\n}\n"],
|
|
5
5
|
"mappings": "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",
|
|
6
6
|
"names": ["StringReader", "substitute", "isOneOf", "char", "EXTM3U", "EXT_X_DEFINE", "StringReader", "substitute"]
|
|
7
7
|
}
|