diff --git a/parse.ts b/parse.ts index c9c4067..d8e54d9 100644 --- a/parse.ts +++ b/parse.ts @@ -1,6 +1,11 @@ import * as utils from './utils'; import { + AllowedCpc, + ExtInfo, Rendition, + Resolution, + TagParam, + UserAttribute, Variant, SessionData, Key, @@ -71,7 +76,7 @@ function getTagCategory(tagName: string): TagCategory { } } -function parseEXTINF(param: string) { +function parseEXTINF(param: string): ExtInfo { const pair = utils.splitAt(param, ',') as [string, string]; return {duration: utils.toNumber(pair[0]), title: decodeURIComponent(escape(pair[1]))}; } @@ -81,18 +86,18 @@ function parseBYTERANGE(param: string): Byterange { return {length: utils.toNumber(pair[0]), offset: pair[1] ? utils.toNumber(pair[1]) : -1}; } -function parseResolution(str: string) { +function parseResolution(str: string): Resolution { const pair = utils.splitAt(str, 'x') as [string, string]; return {width: utils.toNumber(pair[0]), height: utils.toNumber(pair[1])}; } -function parseAllowedCpc(str: string) { +function parseAllowedCpc(str: string): AllowedCpc[] { const message = 'ALLOWED-CPC: Each entry must consit of KEYFORMAT and Content Protection Configuration'; const list = str.split(','); if (list.length === 0) { utils.INVALIDPLAYLIST(message); } - const allowedCpcList: {format: string; cpcList: string[]}[] = []; + const allowedCpcList: AllowedCpc[] = []; for (const item of list) { const [format, cpcText] = utils.splitAt(item, ':'); if (!format || !cpcText) { @@ -112,9 +117,9 @@ function parseIV(str: string): Uint8Array { return iv; } -function parseUserAttribute(str: string) { +function parseUserAttribute(str: string): UserAttribute { if (str.startsWith('"')) { - return unquote(str); + return unquote(str)!; } if (str.startsWith('0x') || str.startsWith('0X')) { return utils.hexToByteSequence(str); @@ -131,7 +136,7 @@ function setCompatibleVersionOfKey(params: Record, attributes: Reco } } -function parseAttributeList(param) { +function parseAttributeList(param): Record { const attributes = {}; for (const item of utils.splitByCommaWithPreservingQuotes(param)) { const [key, value] = utils.splitAt(item, '='); @@ -201,7 +206,7 @@ function parseAttributeList(param) { return attributes; } -function parseTagParam(name, param) { +function parseTagParam(name: string, param): TagParam { switch (name) { case 'EXTM3U': case 'EXT-X-DISCONTINUITY': @@ -282,7 +287,7 @@ function parseRendition({attributes}: Tag): Rendition { return rendition; } -function checkRedundantRendition(renditions, rendition) { +function checkRedundantRendition(renditions, rendition): string { let defaultFound = false; for (const item of renditions) { if (item.name === rendition.name) { @@ -322,7 +327,7 @@ function matchTypes(attrs, variant, params) { } } -function parseVariant(lines, variantAttrs, uri, iFrameOnly, params) { +function parseVariant(lines, variantAttrs, uri: string, iFrameOnly: boolean, params: Record): Variant { const variant = new Variant({ uri, bandwidth: variantAttrs['BANDWIDTH'], @@ -362,7 +367,7 @@ function parseVariant(lines, variantAttrs, uri, iFrameOnly, params) { return variant; } -function sameKey(key1: Key, key2: Key) { +function sameKey(key1: Key, key2: Key): boolean { if (key1.method !== key2.method) { return false; } @@ -404,7 +409,7 @@ function parseMasterPlaylist(lines: Line[], params: Record): Master if (typeof uri !== 'string' || uri.startsWith('#EXT')) { utils.INVALIDPLAYLIST('EXT-X-STREAM-INF must be followed by a URI line'); } - const variant = parseVariant(lines, attributes, uri, false, params); + const variant = parseVariant(lines, attributes, uri as string, false, params); if (variant) { if (typeof variant.score === 'number') { variantIsScored = true; @@ -478,7 +483,7 @@ function parseMasterPlaylist(lines: Line[], params: Record): Master return playlist; } -function parseSegment(lines: Line[], uri: string, start: number, end: number, mediaSequenceNumber: number, discontinuitySequence: number, params: Record) { +function parseSegment(lines: Line[], uri: string, start: number, end: number, mediaSequenceNumber: number, discontinuitySequence: number, params: Record): Segment { const segment = new Segment({uri, mediaSequenceNumber, discontinuitySequence}); let mapHint = false; let partHint = false; @@ -605,7 +610,7 @@ function parseSegment(lines: Line[], uri: string, start: number, end: number, me return segment; } -function parsePrefetchSegment(lines: Line[], uri: any, start: number, end: number, mediaSequenceNumber: number, discontinuitySequence: number, params: Record) { +function parsePrefetchSegment(lines: Line[], uri: any, start: number, end: number, mediaSequenceNumber: number, discontinuitySequence: number, params: Record): PrefetchSegment { const segment = new PrefetchSegment({uri, mediaSequenceNumber, discontinuitySequence}); for (let i = start; i <= end; i++) { const {name, attributes} = lines[i] as Tag; @@ -631,7 +636,7 @@ function parsePrefetchSegment(lines: Line[], uri: any, start: number, end: numbe return segment; } -function parseMediaPlaylist(lines: Line[], params: Record) { +function parseMediaPlaylist(lines: Line[], params: Record): MediaPlaylist { const playlist = new MediaPlaylist(); let segmentStart = -1; let mediaSequence = 0; @@ -956,7 +961,7 @@ function parseTag(line: string, params: Record): Tag | null { type Line = string | Tag; -function lexicalParse(text: string, params: Record) { +function lexicalParse(text: string, params: Record): Line[] { const lines: Line[] = []; for (const l of text.split('\n')) { // V8 has garbage collection issues when cleaning up substrings split from strings greater @@ -987,7 +992,7 @@ function lexicalParse(text: string, params: Record) { return lines; } -function semanticParse(lines: Line[], params: Record) { +function semanticParse(lines: Line[], params: Record): MasterPlaylist | MediaPlaylist { let playlist; if (params.isMasterPlaylist) { playlist = parseMasterPlaylist(lines, params); diff --git a/stringify.ts b/stringify.ts index 6d73f72..ba61bef 100644 --- a/stringify.ts +++ b/stringify.ts @@ -102,7 +102,7 @@ function buildSessionData(sessionData: SessionData) { return `#EXT-X-SESSION-DATA:${attrs.join(',')}`; } -function buildKey(key: Key, isSessionKey?: any) { +function buildKey(key: Key, isSessionKey?: boolean) { const name = isSessionKey ? '#EXT-X-SESSION-KEY' : '#EXT-X-KEY'; const attrs = [`METHOD=${key.method}`]; if (key.uri) { diff --git a/types.ts b/types.ts index b65df11..a3867dd 100644 --- a/types.ts +++ b/types.ts @@ -54,17 +54,17 @@ class Variant { averageBandwidth?: number; score: number; codecs?: string; - resolution?: { width: number; height: number }; + resolution?: Resolution; frameRate?: number; hdcpLevel?: string; - allowedCpc: { format: string, cpcList: string[] }[]; + allowedCpc: AllowedCpc[]; videoRange: 'SDR' | 'HLG' | 'PQ'; stableVariantId: string; programId: any; - audio: Rendition[]; - video: Rendition[]; - subtitles: Rendition[]; - closedCaptions: Rendition[]; + audio: (Rendition & {type: 'AUDIO'})[]; + video: (Rendition & {type: 'VIDEO'})[]; + subtitles: (Rendition & {type: 'SUBTITLES'})[]; + closedCaptions: (Rendition & {type: 'CLOSED-CAPTIONS'})[]; currentRenditions: { audio: number; video: number; subtitles: number; closedCaptions: number; }; constructor({ @@ -219,7 +219,7 @@ class SpliceInfo { type: string; duration?: number; tagName?: string; - value?: any; + value?: string; constructor({ type, // required @@ -490,3 +490,28 @@ export { PrefetchSegment, RenditionReport }; + +export type AllowedCpc = { + format: string; + cpcList: string[]; +}; + +export type ExtInfo = { + duration: number; + title: string; +}; + +export type Resolution = { + width: number; + height: number; +}; + +export type TagParam = + | [ null, null ] + | [ number, null ] + | [ null, Record ] + | [ ExtInfo, null ] + | [ Byterange, null ] + | [ Date, null ]; + +export type UserAttribute = number | string | Uint8Array;