Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 24 additions & 1 deletion src/parse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
RenditionReport,
ByteRange,
Resolution,
CustomTags,
} from './types';

export interface ParseParams {
Expand All @@ -25,6 +26,7 @@ export interface ParseParams {
compatibleVersion: number;
isClosedCaptionsNone: boolean;
hash: Record<string, any>;
customTags: CustomTags;
}

export interface Tag {
Expand All @@ -36,6 +38,7 @@ export interface Tag {
export type TagName =
// Basic
| 'EXTM3U'
| 'EXT-X-CUSTOM-TAGS'
| 'EXT-X-VERSION'
// Segment
| 'EXTINF'
Expand Down Expand Up @@ -102,7 +105,8 @@ export type TagParams =
| [ExtInf, null]
| [ByteRange, null]
| ['EVENT' | 'VOID', null]
| [unknown, null];
| [unknown, null]
| [CustomTags, null];

function unquote(str) {
return utils.trim(str, '"');
Expand All @@ -112,6 +116,7 @@ function getTagCategory(tagName: TagName | string): TagCategory {
switch (tagName) {
case 'EXTM3U':
case 'EXT-X-VERSION':
case 'EXT-X-CUSTOM-TAGS':
return 'Basic';
case 'EXTINF':
case 'EXT-X-BYTERANGE':
Expand Down Expand Up @@ -300,6 +305,8 @@ function parseTagParam(name: TagName, param: string): TagParams {
case 'EXT-X-INDEPENDENT-SEGMENTS':
case 'EXT-X-CUE-IN':
return [null, null];
case 'EXT-X-CUSTOM-TAGS':
return [parseCustomTag(param), null];
case 'EXT-X-VERSION':
case 'EXT-X-TARGETDURATION':
case 'EXT-X-MEDIA-SEQUENCE':
Expand Down Expand Up @@ -490,6 +497,8 @@ function parseMasterPlaylist(lines, params) {
for (const [index, { name, value, attributes }] of lines.entries()) {
if (name === 'EXT-X-VERSION') {
playlist.version = value;
} else if (name === 'EXT-X-CUSTOM-TAGS') {
playlist.customTags = params.value;
} else if (name === 'EXT-X-STREAM-INF') {
const uri = lines[index + 1];
if (typeof uri !== 'string' || uri.startsWith('#EXT')) {
Expand Down Expand Up @@ -780,6 +789,8 @@ function parseMediaPlaylist(lines, params) {
} else {
utils.INVALIDPLAYLIST('A Playlist file MUST NOT contain more than one EXT-X-VERSION tag.');
}
} else if (name === 'EXT-X-CUSTOM-TAGS') {
playlist.customTags = params.value;
} else if (name === 'EXT-X-TARGETDURATION') {
playlist.targetDuration = params.targetDuration = value;
} else if (name === 'EXT-X-MEDIA-SEQUENCE') {
Expand Down Expand Up @@ -1118,6 +1129,17 @@ function CHECKTAGCATEGORY(category, params) {
// category === 'Basic' or 'MediaorMasterPlaylist' or 'Unknown'
}

function parseCustomTag(params: string): CustomTags {
const customTags = {};
if (params) {
for (const pair of params.split(';')) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any sort of escape character we need to worry about? Like #EXT-X-CUSTOM-TAGS:math=a\=b

const [k, v] = pair.split('=');
customTags[k] = JSON.parse(v);
}
}
return customTags;
}

function parseTag(line: string, params: ParseParams): Tag {
const [name, param] = splitTag(line);
const category = getTagCategory(name);
Expand Down Expand Up @@ -1195,6 +1217,7 @@ export function parse(text) {
compatibleVersion: 1,
isClosedCaptionsNone: false,
hash: {},
customTags: {},
};

const lines = lexicalParse(text, params);
Expand Down
12 changes: 10 additions & 2 deletions src/stringify.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Key } from './types';
import { CustomTags, Key, MasterPlaylist, MediaPlaylist } from './types';
import * as utils from './utils';

const ALLOW_REDUNDANCY = [
Expand Down Expand Up @@ -428,7 +428,12 @@ function buildParts(lines, parts) {
return hint;
}

export function stringify(playlist) {
function buildCustomTags(lines: string[], customTags: CustomTags) {
const pairs = Object.entries(customTags).map(([k, v]) => `${k}=${v}`);
lines.push(`#EXT-X-CUSTOM-TAGS:${pairs.join(';')}`);
}

export function stringify(playlist: MasterPlaylist | MediaPlaylist) {
utils.PARAMCHECK(playlist);
utils.ASSERT('Not a playlist', playlist.type === 'playlist');
const lines = new LineArray(playlist.uri);
Expand All @@ -446,6 +451,9 @@ export function stringify(playlist) {
}`,
);
}
if (playlist.customTags) {
buildCustomTags(lines, playlist.customTags);
}
if (playlist.isMasterPlaylist) {
buildMasterPlaylist(lines, playlist);
} else {
Expand Down
8 changes: 7 additions & 1 deletion src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -417,17 +417,20 @@ export interface PlaylistStart {
precise: boolean;
}

export type CustomTags = Record<string, string | boolean | number>;

export interface PlaylistProperties extends Data {
isMasterPlaylist: boolean;
uri: string;
version: number;
independentSegments: boolean;
start: PlaylistStart;
source: string;
customTags: CustomTags;
}

export type PlaylistOptionalConstructorProperties = Partial<
Pick<PlaylistProperties, 'uri' | 'version' | 'independentSegments' | 'start' | 'source'>
Pick<PlaylistProperties, 'uri' | 'version' | 'independentSegments' | 'start' | 'source' | 'customTags'>
>;
export type PlaylistRequiredConstructorProperties = Pick<PlaylistProperties, 'isMasterPlaylist'>;
export type PlaylistConstructorProperties = PlaylistOptionalConstructorProperties &
Expand All @@ -440,6 +443,7 @@ export class Playlist extends Data implements PlaylistProperties {
public independentSegments: boolean;
public start: PlaylistStart;
public source: string;
public customTags: CustomTags;

constructor({
isMasterPlaylist, // required
Expand All @@ -448,6 +452,7 @@ export class Playlist extends Data implements PlaylistProperties {
independentSegments = false,
start,
source,
customTags = {},
}: PlaylistConstructorProperties) {
super('playlist');
utils.PARAMCHECK(isMasterPlaylist);
Expand All @@ -457,6 +462,7 @@ export class Playlist extends Data implements PlaylistProperties {
this.independentSegments = independentSegments;
this.start = start;
this.source = source;
this.customTags = customTags;
}
}

Expand Down