Skip to content

Commit 89bec23

Browse files
committed
feat: Added interstitial to player and delay
1 parent a1a65dc commit 89bec23

File tree

7 files changed

+95
-22
lines changed

7 files changed

+95
-22
lines changed

apps/stitcher/src/interstitials.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,13 @@ export async function getAssets(
114114
if (timedEvent.assets) {
115115
assets.push(...timedEvent.assets);
116116
}
117+
118+
if (timedEvent.delay) {
119+
// When we have a delay on purpose, wait it out.
120+
await new Promise((resolve) => {
121+
setTimeout(resolve, timedEvent.delay);
122+
});
123+
}
117124
}
118125

119126
// If we have a generic vast config on our session, use that one to resolve (eg; for live streams)

apps/stitcher/src/routes/sessions.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ export const sessionsApp = new Hono()
3838
z.object({
3939
time: z.union([z.number(), z.string()]),
4040
duration: z.number().optional(),
41+
delay: z.number().optional(),
4142
assets: z
4243
.array(
4344
z.object({

apps/stitcher/src/session.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ export interface Session {
2929
interface InterstitialInput {
3030
time: string | number;
3131
duration?: number;
32+
delay?: number;
3233
assets?: {
3334
uri: string;
3435
}[];
@@ -105,6 +106,7 @@ export async function mapInterstitialToTimedEvent(
105106
return {
106107
dateTime: toDateTime(startTime, interstitial.time),
107108
duration: interstitial.duration,
109+
delay: interstitial.delay,
108110
assets: interstitial.assets
109111
? await Promise.all(
110112
interstitial.assets.map(async (asset) => {

apps/stitcher/src/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export interface AssetResolver {
1818
export interface TimedEvent {
1919
dateTime: DateTime;
2020
duration?: number;
21+
delay?: number;
2122
assets?: Asset[];
2223
vast?: VastParams;
2324
}

packages/player/src/hls-player.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ export class HlsPlayer {
5050
this.state_ = new State({
5151
onEvent: (event: Events) => this.emit_(event),
5252
getTiming: () => hls.interstitialsManager?.integrated,
53+
getInterstitialTiming: () => hls.interstitialsManager?.bufferingPlayer,
5354
});
5455

5556
hls.attachMedia(this.media_);
@@ -225,6 +226,10 @@ export class HlsPlayer {
225226
return getState(this.state_, "timeline");
226227
}
227228

229+
get interstitial() {
230+
return getState(this.state_, "interstitial");
231+
}
232+
228233
private createHls_() {
229234
const hls = new Hls();
230235

@@ -269,7 +274,7 @@ export class HlsPlayer {
269274
acc.push({
270275
start: item.integrated.start,
271276
duration: item.integrated.end - item.integrated.start,
272-
rangeDuration: item.event.dateRange.plannedDuration ?? undefined,
277+
plannedDuration: item.event.dateRange.plannedDuration ?? undefined,
273278
});
274279

275280
return acc;
@@ -278,7 +283,11 @@ export class HlsPlayer {
278283
});
279284

280285
listen(Hls.Events.INTERSTITIAL_STARTED, () => {
281-
this.state_?.setSeeking(false);
286+
this.state_?.setInterstitial({});
287+
});
288+
289+
listen(Hls.Events.INTERSTITIAL_ENDED, () => {
290+
this.state_?.setInterstitial(null);
282291
});
283292

284293
return hls;

packages/player/src/state.ts

Lines changed: 65 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1+
import { assert } from "shared/assert";
12
import { preciseFloat } from "./helpers";
23
import { Events } from "./types";
34
import type {
45
AudioTrack,
6+
Interstitial,
57
Playhead,
68
Quality,
79
SubtitleTrack,
@@ -16,7 +18,8 @@ interface Timing {
1618

1719
interface StateParams {
1820
onEvent(event: Events): void;
19-
getTiming(): undefined | Timing;
21+
getTiming(): undefined | Timing | null;
22+
getInterstitialTiming(): undefined | Omit<Timing, "seekableStart"> | null;
2023
}
2124

2225
interface StateProperties {
@@ -34,6 +37,7 @@ interface StateProperties {
3437
seeking: boolean;
3538
live: boolean;
3639
timeline: TimelineItem[];
40+
interstitial: Interstitial | null;
3741
}
3842

3943
const noState: StateProperties = {
@@ -51,6 +55,7 @@ const noState: StateProperties = {
5155
seeking: false,
5256
live: false,
5357
timeline: [],
58+
interstitial: null,
5459
};
5560

5661
export class State implements StateProperties {
@@ -121,7 +126,8 @@ export class State implements StateProperties {
121126

122127
if (
123128
// TODO: Come up with a generic logical check.
124-
(!this.subtitleTracks.length && subtitleTracks.length) ||
129+
!this.subtitleTracks.length ||
130+
subtitleTracks.length ||
125131
diff(this.subtitleTracks) !== diff(subtitleTracks)
126132
) {
127133
this.subtitleTracks = subtitleTracks;
@@ -151,34 +157,73 @@ export class State implements StateProperties {
151157
this.params_.onEvent(Events.TIMELINE_CHANGE);
152158
}
153159

154-
requestTimingSync() {
160+
setInterstitial(
161+
interstitial: Omit<Interstitial, "currentTime" | "duration"> | null,
162+
) {
163+
if (interstitial) {
164+
this.setSeeking(false);
165+
}
166+
167+
this.interstitial = interstitial
168+
? {
169+
...interstitial,
170+
currentTime: 0,
171+
duration: NaN,
172+
}
173+
: null;
174+
175+
this.requestTimingSync(/* skipEvent= */ true);
176+
177+
this.params_.onEvent(Events.INTERSTITIAL_CHANGE);
178+
}
179+
180+
requestTimingSync(skipEvent?: boolean) {
155181
clearTimeout(this.timerId_);
156182
this.timerId_ = window.setTimeout(() => {
157183
this.requestTimingSync();
158184
}, 250);
185+
let emitEvent = false;
159186

160187
const timing = this.params_.getTiming();
161-
if (!timing) {
162-
return;
188+
if (timing) {
189+
const currentTime = preciseFloat(timing.currentTime);
190+
const duration = preciseFloat(timing.duration);
191+
const seekableStart = preciseFloat(timing.seekableStart);
192+
193+
if (
194+
currentTime !== this.currentTime ||
195+
duration !== this.duration ||
196+
seekableStart !== this.seekableStart
197+
) {
198+
emitEvent = true;
199+
}
200+
201+
this.currentTime = currentTime;
202+
this.duration = duration;
203+
this.seekableStart = seekableStart;
163204
}
164205

165-
const currentTime = preciseFloat(timing.currentTime);
166-
const duration = preciseFloat(timing.duration);
167-
const seekableStart = preciseFloat(timing.seekableStart);
206+
const interstitialTiming = this.params_.getInterstitialTiming();
207+
if (interstitialTiming) {
208+
assert(this.interstitial);
168209

169-
if (
170-
currentTime === this.currentTime &&
171-
duration === this.duration &&
172-
seekableStart === this.seekableStart
173-
) {
174-
return;
175-
}
210+
const currentTime = preciseFloat(interstitialTiming.currentTime);
211+
const duration = preciseFloat(interstitialTiming.duration);
176212

177-
this.currentTime = currentTime;
178-
this.duration = duration;
179-
this.seekableStart = seekableStart;
213+
if (
214+
currentTime !== this.interstitial.currentTime ||
215+
duration !== this.interstitial.duration
216+
) {
217+
emitEvent = true;
218+
}
180219

181-
this.params_.onEvent(Events.TIME_CHANGE);
220+
this.interstitial.currentTime = currentTime;
221+
this.interstitial.duration = duration;
222+
}
223+
224+
if (!skipEvent && emitEvent) {
225+
this.params_.onEvent(Events.TIME_CHANGE);
226+
}
182227
}
183228

184229
ready = noState.ready;
@@ -195,6 +240,7 @@ export class State implements StateProperties {
195240
seeking = noState.seeking;
196241
live = noState.live;
197242
timeline = noState.timeline;
243+
interstitial = noState.interstitial;
198244
}
199245

200246
export function getState<N extends keyof StateProperties>(

packages/player/src/types.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ export enum Events {
1515
AUTO_QUALITY_CHANGE = "autoQualityChange",
1616
SEEKING_CHANGE = "seekingChange",
1717
TIMELINE_CHANGE = "timelineChange",
18+
INTERSTITIAL_CHANGE = "interstitialChange",
1819
}
1920

2021
export type HlsPlayerEventMap = {
@@ -30,6 +31,7 @@ export type HlsPlayerEventMap = {
3031
[Events.AUTO_QUALITY_CHANGE]: () => void;
3132
[Events.SEEKING_CHANGE]: () => void;
3233
[Events.TIMELINE_CHANGE]: () => void;
34+
[Events.INTERSTITIAL_CHANGE]: () => void;
3335
} & {
3436
"*": (event: Events) => void;
3537
};
@@ -57,5 +59,10 @@ export interface SubtitleTrack {
5759
export interface TimelineItem {
5860
start: number;
5961
duration: number;
60-
rangeDuration?: number;
62+
plannedDuration?: number;
63+
}
64+
65+
export interface Interstitial {
66+
currentTime: number;
67+
duration: number;
6168
}

0 commit comments

Comments
 (0)