Skip to content

Commit 17ffe6a

Browse files
authored
Add suport for content steering (#162)
- Add tag EXT-X-CONTENT-STEERING - Add EXT-X-STREAM-INF's attribute PATHWAY-ID
1 parent 730e4f0 commit 17ffe6a

File tree

3 files changed

+53
-5
lines changed

3 files changed

+53
-5
lines changed

parse.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ import {
1818
Segment,
1919
PartialSegment,
2020
PrefetchSegment,
21-
RenditionReport
21+
RenditionReport,
22+
ContentSteering
2223
} from './types';
2324

2425
function unquote(str: string | undefined) {
@@ -31,6 +32,7 @@ function getTagCategory(tagName: string): TagCategory {
3132
switch (tagName) {
3233
case 'EXTM3U':
3334
case 'EXT-X-VERSION':
35+
case 'EXT-X-CONTENT-STEERING':
3436
return 'Basic';
3537
case 'EXTINF':
3638
case 'EXT-X-BYTERANGE':
@@ -284,7 +286,8 @@ function parseRendition({attributes}: Tag): Rendition {
284286
forced: attributes['FORCED'],
285287
instreamId: attributes['INSTREAM-ID'],
286288
characteristics: attributes['CHARACTERISTICS'],
287-
channels: attributes['CHANNELS']
289+
channels: attributes['CHANNELS'],
290+
pathwayId: attributes['PATHWAY-ID']
288291
});
289292
return rendition;
290293
}
@@ -342,6 +345,7 @@ function parseVariant(lines, variantAttrs, uri: string, iFrameOnly: boolean, par
342345
allowedCpc: variantAttrs['ALLOWED-CPC'],
343346
videoRange: variantAttrs['VIDEO-RANGE'],
344347
stableVariantId: variantAttrs['STABLE-VARIANT-ID'],
348+
pathwayId: variantAttrs['STABLE-PATHWAY-ID'],
345349
programId: variantAttrs['PROGRAM-ID']
346350
});
347351
for (const line of lines) {
@@ -407,6 +411,12 @@ function parseMasterPlaylist(lines: Line[], params: Record<string, any>): Master
407411
const {name, value, attributes} = mapTo<Tag>(line);
408412
if (name === 'EXT-X-VERSION') {
409413
playlist.version = value;
414+
} else if (name === 'EXT-X-CONTENT-STEERING-SERVER') {
415+
const contentSteering = new ContentSteering({
416+
serverUri: attributes['SERVER-URI'],
417+
pathwayId: attributes['PATHWAY-ID']
418+
});
419+
playlist.contentSteering = contentSteering;
410420
} else if (name === 'EXT-X-STREAM-INF') {
411421
const uri = lines[index + 1];
412422
if (typeof uri !== 'string' || uri.startsWith('#EXT')) {

stringify.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
SpliceInfo,
1313
Variant,
1414
PostProcess,
15+
ContentSteering,
1516
} from './types';
1617

1718
const ALLOW_REDUNDANCY = [
@@ -88,6 +89,9 @@ function getNumberOfDecimalPlaces(num: number) {
8889
}
8990

9091
function buildMasterPlaylist(lines: LineArray, playlist: MasterPlaylist, postProcess: PostProcess | undefined) {
92+
if (playlist.contentSteering) {
93+
lines.push(buildContentSteeringServer(playlist.contentSteering));
94+
}
9195
for (const sessionData of playlist.sessionDataList) {
9296
lines.push(buildSessionData(sessionData));
9397
}
@@ -103,6 +107,14 @@ function buildMasterPlaylist(lines: LineArray, playlist: MasterPlaylist, postPro
103107
}
104108
}
105109

110+
function buildContentSteeringServer(contentSteering: ContentSteering) {
111+
const attrs = [
112+
`SERVER-URI="${contentSteering.serverUri}"`,
113+
`PATHWAY-ID="${contentSteering.pathwayId}"`
114+
];
115+
return `#EXT-X-CONTENT-STEERING:${attrs.join(',')}`;
116+
}
117+
106118
function buildSessionData(sessionData: SessionData) {
107119
const attrs = [`DATA-ID="${sessionData.id}"`];
108120
if (sessionData.language) {
@@ -200,6 +212,9 @@ function buildVariant(lines: LineArray, variant: Variant) {
200212
if (variant.stableVariantId) {
201213
attrs.push(`STABLE-VARIANT-ID="${variant.stableVariantId}"`);
202214
}
215+
if (variant.pathwayId) {
216+
attrs.push(`PATHWAY-ID="${variant.pathwayId}"`);
217+
}
203218
if (variant.programId) {
204219
attrs.push(`PROGRAM-ID=${variant.programId}`);
205220
}

types.ts

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ class Rendition {
1515
instreamId?: string;
1616
characteristics?: string;
1717
channels?: string;
18+
pathwayId?: string;
1819

1920
constructor({
2021
type, // required
@@ -28,7 +29,8 @@ class Rendition {
2829
forced,
2930
instreamId, // required if type=CLOSED-CAPTIONS
3031
characteristics,
31-
channels
32+
channels,
33+
pathwayId
3234
}: Rendition) {
3335
utils.PARAMCHECK(type, groupId, name);
3436
utils.CONDITIONALASSERT([type === 'SUBTITLES', uri], [type === 'CLOSED-CAPTIONS', instreamId], [type === 'CLOSED-CAPTIONS', !uri], [forced, type === 'SUBTITLES']);
@@ -44,6 +46,7 @@ class Rendition {
4446
this.instreamId = instreamId;
4547
this.characteristics = characteristics;
4648
this.channels = channels;
49+
this.pathwayId = pathwayId;
4750
}
4851
}
4952

@@ -60,6 +63,7 @@ class Variant {
6063
allowedCpc: AllowedCpc[];
6164
videoRange: 'SDR' | 'HLG' | 'PQ';
6265
stableVariantId: string;
66+
pathwayId: string;
6367
programId: any;
6468
audio: (Rendition & {type: 'AUDIO'})[];
6569
video: (Rendition & {type: 'VIDEO'})[];
@@ -80,6 +84,7 @@ class Variant {
8084
allowedCpc,
8185
videoRange,
8286
stableVariantId,
87+
pathwayId,
8388
programId,
8489
audio = [],
8590
video = [],
@@ -101,6 +106,7 @@ class Variant {
101106
this.allowedCpc = allowedCpc;
102107
this.videoRange = videoRange;
103108
this.stableVariantId = stableVariantId;
109+
this.pathwayId = pathwayId;
104110
this.programId = programId;
105111
this.audio = audio;
106112
this.video = video;
@@ -156,6 +162,19 @@ class Key {
156162
}
157163
}
158164

165+
class ContentSteering {
166+
serverUri: string;
167+
pathwayId: string;
168+
169+
constructor({
170+
serverUri,
171+
pathwayId
172+
}: ContentSteering) {
173+
this.serverUri = serverUri;
174+
this.pathwayId = pathwayId;
175+
}
176+
}
177+
159178
export type Byterange = {
160179
length: number;
161180
offset: number;
@@ -281,19 +300,22 @@ class MasterPlaylist extends Playlist {
281300
currentVariant?: number;
282301
sessionDataList: SessionData[];
283302
sessionKeyList: Key[];
303+
contentSteering?: ContentSteering;
284304

285305
constructor(params: Partial<MasterPlaylist> = {}) {
286306
super({...params, isMasterPlaylist: true});
287307
const {
288308
variants = [],
289309
currentVariant,
290310
sessionDataList = [],
291-
sessionKeyList = []
311+
sessionKeyList = [],
312+
contentSteering = undefined
292313
} = params;
293314
this.variants = variants;
294315
this.currentVariant = currentVariant;
295316
this.sessionDataList = sessionDataList;
296317
this.sessionKeyList = sessionKeyList;
318+
this.contentSteering = contentSteering;
297319
}
298320
}
299321

@@ -493,7 +515,8 @@ export {
493515
Segment,
494516
PartialSegment,
495517
PrefetchSegment,
496-
RenditionReport
518+
RenditionReport,
519+
ContentSteering
497520
};
498521

499522
export type AllowedCpc = {

0 commit comments

Comments
 (0)