Skip to content

Commit f6825e4

Browse files
committed
chore: Added RFC for stitcher API
1 parent d07955d commit f6825e4

File tree

6 files changed

+69
-43
lines changed

6 files changed

+69
-43
lines changed

apps/stitcher/src/interstitials.ts

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import type { Asset, TimedEvent } from "./types";
88

99
interface Group {
1010
showTimeline: boolean;
11-
inlineDuration?: number;
11+
duration?: number;
1212
}
1313

1414
export function getStaticDateRanges(
@@ -36,18 +36,22 @@ export function getStaticDateRanges(
3636
const assetListUrl = createUrl(context, "out/asset-list.json", {
3737
dt: dateTime.toISO(),
3838
sid: session.id,
39-
mdur: group.inlineDuration,
39+
mdur: group.duration,
4040
});
4141

4242
const clientAttributes: Record<string, number | string> = {
4343
RESTRICT: "SKIP,JUMP",
4444
"ASSET-LIST": assetListUrl,
4545
"CONTENT-MAY-VARY": "YES",
4646
"TIMELINE-STYLE": group.showTimeline ? "HIGHLIGHT" : "PRIMARY",
47-
"TIMELINE-OCCUPIES": group.inlineDuration ? "RANGE" : "POINT",
48-
"RESUME-OFFSET": group.inlineDuration ?? 0,
47+
"TIMELINE-OCCUPIES": group.duration ? "RANGE" : "POINT",
48+
"RESUME-OFFSET": group.duration ?? 0,
4949
};
5050

51+
if (group.duration) {
52+
clientAttributes["PLAYOUT-LIMIT"] = group.duration;
53+
}
54+
5155
const cue: string[] = [];
5256
if (dateTime.equals(session.startTime)) {
5357
cue.push("PRE");
@@ -61,7 +65,7 @@ export function getStaticDateRanges(
6165
classId: "com.apple.hls.interstitial",
6266
id: `sprs.${dateTime.toMillis()}`,
6367
startDate: dateTime,
64-
duration: group.inlineDuration,
68+
duration: group.duration,
6569
clientAttributes,
6670
});
6771
});
@@ -80,8 +84,10 @@ function groupEvent(groups: Map<number, Group>, event: TimedEvent) {
8084
groups.set(ts, group);
8185
}
8286

83-
if (event.inlineDuration) {
84-
group.inlineDuration = event.inlineDuration;
87+
// If we have another event with a duration, we'll take the largest one to cover
88+
// the entire interstitial.
89+
if (event.duration && (!group.duration || event.duration > group.duration)) {
90+
group.duration = event.duration;
8591
}
8692

8793
if (event.vast) {
@@ -99,7 +105,7 @@ function getTimedEventsFromSegments(segments: Segment[]) {
99105

100106
events.push({
101107
dateTime: segment.programDateTime,
102-
inlineDuration: segment.spliceInfo.duration,
108+
duration: segment.spliceInfo.duration,
103109
});
104110
}
105111

apps/stitcher/src/routes/sessions.ts

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ export const sessionsApp = new Hono().post(
3636
z.intersection(
3737
z.object({
3838
time: z.union([z.number(), z.string()]),
39+
duration: z.number().optional(),
3940
}),
4041
z.discriminatedUnion("type", [
4142
z.object({
@@ -46,18 +47,13 @@ export const sessionsApp = new Hono().post(
4647
type: z.literal("vast"),
4748
url: z.string(),
4849
}),
50+
z.object({
51+
type: z.literal("metadata"),
52+
}),
4953
]),
5054
),
5155
)
5256
.optional(),
53-
regions: z
54-
.array(
55-
z.object({
56-
time: z.union([z.number(), z.string()]),
57-
inlineDuration: z.number().optional(),
58-
}),
59-
)
60-
.optional(),
6157
filter: z
6258
.object({
6359
resolution: z.string().optional(),

apps/stitcher/src/session.ts

Lines changed: 2 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ export interface Session {
2828

2929
type InterstitialInput = {
3030
time: string | number;
31+
duration?: number;
3132
} & (
3233
| {
3334
type: "asset";
@@ -39,11 +40,6 @@ type InterstitialInput = {
3940
}
4041
);
4142

42-
interface RegionInput {
43-
time: string | number;
44-
inlineDuration?: number;
45-
}
46-
4743
interface CreateSessionParams {
4844
uri: string;
4945
expiry: number;
@@ -54,7 +50,6 @@ interface CreateSessionParams {
5450
url?: string;
5551
};
5652
interstitials?: InterstitialInput[];
57-
regions?: RegionInput[];
5853
}
5954

6055
export async function createSession(
@@ -84,13 +79,6 @@ export async function createSession(
8479
session.events.push(...events);
8580
}
8681

87-
if (params.regions) {
88-
const events = params.regions.map((region) =>
89-
mapRegionToTimedEvent(startTime, region),
90-
);
91-
session.events.push(...events);
92-
}
93-
9482
const value = JSON.stringify(session);
9583
await context.kv.set(`session:${id}`, value, session.expiry);
9684

@@ -119,6 +107,7 @@ export async function mapInterstitialToTimedEvent(
119107

120108
const event: TimedEvent = {
121109
dateTime,
110+
duration: interstitial.duration,
122111
};
123112

124113
if (interstitial.type === "asset") {
@@ -138,18 +127,6 @@ export async function mapInterstitialToTimedEvent(
138127
return event;
139128
}
140129

141-
export function mapRegionToTimedEvent(
142-
startTime: DateTime,
143-
region: RegionInput,
144-
): TimedEvent {
145-
const dateTime = toDateTime(startTime, region.time);
146-
147-
return {
148-
dateTime,
149-
inlineDuration: region.inlineDuration,
150-
};
151-
}
152-
153130
function toDateTime(startTime: DateTime, time: string | number) {
154131
return typeof time === "string"
155132
? DateTime.fromISO(time)

apps/stitcher/src/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,5 +18,5 @@ export interface TimedEvent {
1818
dateTime: DateTime;
1919
vast?: VastParams;
2020
asset?: Asset;
21-
inlineDuration?: number;
21+
duration?: number;
2222
}

packages/player/src/hls-player.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ export class HlsPlayer {
4949

5050
this.state_ = new State({
5151
onEvent: (event: Events) => this.emit_(event),
52-
getTiming: () => hls.interstitialsManager?.primary,
52+
getTiming: () => hls.interstitialsManager?.integrated,
5353
});
5454

5555
hls.attachMedia(this.media_);

rfc/001_Stitcher_API.md

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# Stitcher API
2+
3+
For VOD, without replacement (ad insertion)
4+
5+
```
6+
|-------X-------|
7+
0 20
8+
```
9+
10+
The interstitial structure is:
11+
12+
- No DURATION
13+
- RESUME-OFFSET: 0
14+
- No PLAYOUT-LIMIT (ad server decides)
15+
16+
For VOD, with replacement (ad replacement)
17+
18+
```
19+
|-------X####----|
20+
0 20 30
21+
```
22+
23+
The interstitial structure is:
24+
25+
- DURATION: 10
26+
- RESUME-OFFSET: 10 (equal to the duration)
27+
- No PLAYOUT-LIMIT (ad server decides)
28+
29+
Due to the DURATION tag, HLS.js treats the linear ad breaks as if they are not there in the first place. Unless we add CUE=ONCE.
30+
31+
For live, with replacement (ad replacement)
32+
33+
```
34+
|-------X####---->
35+
0 20 30
36+
```
37+
38+
The interstitial structure is:
39+
40+
- No DURATION
41+
- RESUME-OFFSET: 10 (equal to the duration)
42+
- PLAYOUT-LIMIT: 10 (equal to the duration)
43+
44+
## Questions
45+
46+
- What when we seek over an interstitial, and we want to end up to the original seek target time? How does a dynamic resume offset work? Can this be a seek when the interstitial ended?
47+
- What with (pre)buffering of the primary content in the case above?

0 commit comments

Comments
 (0)