Skip to content

Commit

Permalink
feat: Interstitials MIX-TYPE support for players to figure out what k…
Browse files Browse the repository at this point in the history
…ind of interstitial it is, such as bumper or ad
  • Loading branch information
matvp91 committed Sep 8, 2024
1 parent c460a15 commit 2ccb6d0
Show file tree
Hide file tree
Showing 9 changed files with 67 additions and 11 deletions.
6 changes: 6 additions & 0 deletions packages/hlsjs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,9 @@ Using https://www.svgrepo.com/collection/solar-broken-line-icons for icons.
- Selecting audio and quality during interstitials works for the main asset, but subtitles not.

- Seeking to `this.getInterstitialsManager_().integrated.duration` does not work properly.

- InterstitialEvent should contain custom tags (like X-MIX-TYPES).

- Need to talk about ABR & interstitials, do they share the same instance? Same bandwidth estimation? That might give troubles as for all we know eg; interstitials might be on a different CDN.

- Custom HLS tags? Is this needed?
14 changes: 12 additions & 2 deletions packages/hlsjs/lib/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import Hls from "hls.js";
import update from "immutability-helper";
import EventEmitter from "eventemitter3";
import type { Spec } from "immutability-helper";
import type { Level, MediaPlaylist } from "hls.js";
import type { InterstitialScheduleItem, Level, MediaPlaylist } from "hls.js";

export { Root as HlsUi } from "./ui";

Expand Down Expand Up @@ -123,10 +123,20 @@ export class HlsFacade extends EventEmitter<HlsFacadeEvent> {
});

hls.on(Hls.Events.INTERSTITIALS_UPDATED, (_, data) => {
const isAd = (item: InterstitialScheduleItem) => {
const types = item.event?.dateRange.attr.enumeratedStringList(
"X-MIX-TYPES",
{
ad: false,
},
);
return !!types?.ad;
};

this.setState_({
cuePoints: {
$set: data.schedule.reduce<number[]>((acc, item) => {
if (!acc.includes(item.start)) {
if (isAd(item) && !acc.includes(item.start)) {
acc.push(item.start);
}
return acc;
Expand Down
5 changes: 4 additions & 1 deletion packages/hlsjs/src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,11 @@ Object.assign(window, { facade });

hls.loadSource(
// "https://streamer.ams3.cdn.digitaloceanspaces.com/package/846ed9ef-b11f-43a4-9d31-0cecc1b7468c/hls/master.m3u8",
"https://playertest.longtailvideo.com/adaptive/elephants_dream_v4/redundant.m3u8",
// "https://playertest.longtailvideo.com/adaptive/elephants_dream_v4/redundant.m3u8",
// "http://127.0.0.1:52002/session/260c015b-966f-4cc4-8373-ab859379a27d/master.m3u8",
// "http://127.0.0.1:52002/session/d8f508ab-8c93-41fa-9afe-63a784477d8b/master.m3u8",
"https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8",
// "http://127.0.0.1:52002/session/a7b1551f-5baf-4859-b783-b3c674a77690/master.m3u8",
);

const root = ReactDOM.createRoot(document.getElementById("root")!);
Expand Down
6 changes: 6 additions & 0 deletions packages/stitcher/extern/hls-parser/stringify.ts
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,12 @@ function buildMediaPlaylist(
`X-RESTRICT="${interstitial.restrict}"`,
);

if (interstitial.custom) {
Object.entries(interstitial.custom).forEach(([key, value]) => {
params.push(`X-${key}="${value}"`);
});
}

lines.push(`#EXT-X-DATERANGE:${params.join(",")}`);
}
}
Expand Down
3 changes: 3 additions & 0 deletions packages/stitcher/extern/hls-parser/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ class Interstitial {
startDate: Date;
resumeOffset?: number;
restrict?: string;
custom?: Record<string, string>;

constructor({
id,
Expand All @@ -69,6 +70,7 @@ class Interstitial {
startDate,
resumeOffset = 0,
restrict = "SKIP,JUMP",
custom,
}: any) {
this.id = id;
this.uri = uri;
Expand All @@ -77,6 +79,7 @@ class Interstitial {
this.startDate = startDate;
this.resumeOffset = resumeOffset;
this.restrict = restrict;
this.custom = custom;
}
}

Expand Down
39 changes: 31 additions & 8 deletions packages/stitcher/src/playlist.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import parseFilepath from "parse-filepath";
import { env } from "./env.js";
import { MasterPlaylist, MediaPlaylist } from "../extern/hls-parser/types.js";
import createError from "@fastify/error";
import type { Session, Interstitial } from "./types.js";
import type { Session, Interstitial, InterstitialType } from "./types.js";

const PlaylistUnavailableError = createError<[string]>(
"PLAYLIST_UNAVAILABLE",
Expand Down Expand Up @@ -80,6 +80,7 @@ export async function formatAssetList(session: Session, timeOffset: number) {
return {
URI: uri,
DURATION: await getDuration(uri),
"MIX-TYPE": interstitial.type,
};
}),
);
Expand Down Expand Up @@ -118,18 +119,40 @@ function addInterstitials(
media.segments[0].programDateTime = new Date(now);

interstitials
.reduce<number[]>((acc, interstitial) => {
if (!acc.includes(interstitial.timeOffset)) {
acc.push(interstitial.timeOffset);
.reduce<
{
timeOffset: number;
types: InterstitialType[];
}[]
>((acc, interstitial) => {
let foundItem = acc.find(
(item) => item.timeOffset === interstitial.timeOffset,
);
if (!foundItem) {
foundItem = {
timeOffset: interstitial.timeOffset,
types: [],
};
acc.push(foundItem);
}

if (interstitial.type && !foundItem.types.includes(interstitial.type)) {
foundItem.types.push(interstitial.type);
}

return acc;
}, [])
.forEach((timeOffset) => {
.forEach((item) => {
const custom = {
"MIX-TYPES": item.types.join(","),
};

media.interstitials.push(
new hlsParser.types.Interstitial({
id: `${timeOffset}`,
startDate: new Date(now + timeOffset * 1000),
list: `/session/${sessionId}/asset-list.json?timeOffset=${timeOffset}`,
id: `${item.timeOffset}`,
startDate: new Date(now + item.timeOffset * 1000),
list: `/session/${sessionId}/asset-list.json?timeOffset=${item.timeOffset}`,
custom,
}),
);
});
Expand Down
1 change: 1 addition & 0 deletions packages/stitcher/src/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ export async function createSession(data: {
interstitials.push({
timeOffset: 0,
assetId: data.bumperAssetId,
type: "bumper",
});
}

Expand Down
3 changes: 3 additions & 0 deletions packages/stitcher/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@ export type Session = {
maxResolution: number;
};

export type InterstitialType = "ad" | "bumper";

export type Interstitial = {
timeOffset: number;
assetId: string;
type?: InterstitialType;
};
1 change: 1 addition & 0 deletions packages/stitcher/src/vast.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export async function extractInterstitialFromVmapAdbreak(adBreak: VmapAdBreak) {
interstitials.push({
timeOffset: adBreak.timeOffset,
assetId: adMedia.assetId,
type: "ad",
});
} else {
scheduleForPackage(adMedia);
Expand Down

0 comments on commit 2ccb6d0

Please sign in to comment.