Skip to content

Commit

Permalink
Added assetList type
Browse files Browse the repository at this point in the history
  • Loading branch information
matvp91 committed Dec 9, 2024
1 parent 46a7297 commit 7553b32
Show file tree
Hide file tree
Showing 6 changed files with 178 additions and 134 deletions.
123 changes: 72 additions & 51 deletions packages/stitcher/src/interstitials.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,20 @@
import { DateTime } from "luxon";
import { createUrl } from "./lib/url";
import { fetchDuration } from "./playlist";
import { getAdMediasFromVast } from "./vast";
import { getAssetsFromVast } from "./vast";
import type { DateRange } from "./parser";
import type { Session } from "./session";
import type { DateTime } from "luxon";
import type { Interstitial, InterstitialAsset } from "./types";

export function getStaticDateRanges(session: Session, isLive: boolean) {
const group: {
dateTime: DateTime;
}[] = [];

for (const interstitial of session.interstitials) {
let item = group.find((item) =>
item.dateTime.equals(interstitial.dateTime),
);

if (!item) {
item = {
dateTime: interstitial.dateTime,
};
group.push(item);
}
}
const group = getGroupedInterstitials(session.interstitials);

return group.map((item) => {
const assetListUrl = createAssetListUrl({
dateTime: item.dateTime,
session,
});
const dateRanges: DateRange[] = [];

for (const [ts, interstitials] of group.entries()) {
const startDate = DateTime.fromMillis(ts);

const assetListUrl = getAssetListUrl(startDate, interstitials, session);
const kinds = getInterstitialsKinds(interstitials);

const clientAttributes: Record<string, number | string> = {
RESTRICT: "SKIP,JUMP",
Expand All @@ -38,55 +26,88 @@ export function getStaticDateRanges(session: Session, isLive: boolean) {
clientAttributes["RESUME-OFFSET"] = 0;
}

const isPreroll = item.dateTime.equals(session.startTime);
if (isPreroll) {
const atStart = startDate.equals(session.startTime);
if (atStart) {
clientAttributes["CUE"] += ",PRE";
}

return {
if (kinds.length) {
clientAttributes["SPRS-INCLUDES-KIND"] = kinds.join(",");
}

dateRanges.push({
classId: "com.apple.hls.interstitial",
id: `${item.dateTime.toUnixInteger()}`,
startDate: item.dateTime,
id: `${ts}`,
startDate,
clientAttributes,
};
});
});
}

return dateRanges;
}

export async function getAssets(session: Session, dateTime: DateTime) {
const assets: {
URI: string;
DURATION: number;
}[] = [];
const assets: InterstitialAsset[] = [];

const interstitials = session.interstitials.filter((interstitial) =>
interstitial.dateTime.equals(dateTime),
);

for (const interstitial of interstitials) {
if (interstitial.type === "vast") {
const adMedias = await getAdMediasFromVast(interstitial);
for (const adMedia of adMedias) {
assets.push({
URI: adMedia.masterUrl,
DURATION: adMedia.duration,
});
}
if (interstitial.vast) {
const nextAssets = await getAssetsFromVast(interstitial.vast);
assets.push(...nextAssets);
}

if (interstitial.type === "asset") {
assets.push({
URI: interstitial.url,
DURATION: await fetchDuration(interstitial.url),
});
if (interstitial.asset) {
assets.push(interstitial.asset);
}
}

return assets;
}

function createAssetListUrl(params: { dateTime: DateTime; session?: Session }) {
function getGroupedInterstitials(interstitials: Interstitial[]) {
const result = new Map<number, Interstitial[]>();

for (const interstitial of interstitials) {
const ts = interstitial.dateTime.toMillis();
let items = result.get(ts);
if (!items) {
items = [];
result.set(ts, items);
}
items.push(interstitial);
}

return result;
}

function getAssetListUrl(
dateTime: DateTime,
interstitials: Interstitial[],
session?: Session,
) {
if (interstitials.length === 1 && interstitials[0]?.assetList) {
return interstitials[0].assetList.url;
}

return createUrl("out/asset-list.json", {
dt: params.dateTime.toISO(),
sid: params.session?.id,
dt: dateTime.toISO(),
sid: session?.id,
});
}

function getInterstitialsKinds(interstitials: Interstitial[]) {
return interstitials
.map((interstitial) => {
if (interstitial.asset?.kind) {
return interstitial.asset.kind;
}
if (interstitial.vast) {
return "ad";
}
return null;
})
.filter((kind) => kind !== null);
}
18 changes: 14 additions & 4 deletions packages/stitcher/src/playlist.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,17 @@ export async function formatMediaPlaylist(

export async function formatAssetList(session: Session, dateTime: DateTime) {
const assets = await getAssets(session, dateTime);

const assetsPromises = assets.map(async (asset) => {
return {
URI: asset.url,
DURATION: asset.duration ?? (await fetchDuration(asset.url)),
"SPRS-KIND": asset.kind,
};
});

return {
ASSETS: assets,
ASSETS: await Promise.all(assetsPromises),
};
}

Expand Down Expand Up @@ -207,9 +216,10 @@ export function mapAdBreaksToSessionInterstitials(

session.interstitials.push({
dateTime,
type: "vast",
url: adBreak.vastUrl,
data: adBreak.vastData,
vast: {
url: adBreak.vastUrl,
data: adBreak.vastData,
},
});
}
}
3 changes: 3 additions & 0 deletions packages/stitcher/src/routes/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ export const sessionRoutes = new Elysia()
t.Object({
type: t.Literal("asset"),
uri: t.String(),
kind: t.Optional(
t.Union([t.Literal("ad"), t.Literal("bumper")]),
),
}),
t.Object({
type: t.Literal("vast"),
Expand Down
88 changes: 51 additions & 37 deletions packages/stitcher/src/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,25 +17,22 @@ export interface Session {
interstitials: Interstitial[];
}

type SessionInterstitial = {
time: number | string;
} & (
| {
type: "asset";
uri: string;
kind?: "ad" | "bumper";
}
| { type: "vast"; url: string }
| { type: "assetList"; url: string }
);

export async function createSession(params: {
uri: string;
vmap?: VmapParams;
interstitials?: ({
time: number | string;
} & (
| {
type: "asset";
uri: string;
}
| {
type: "vast";
url: string;
}
| {
type: "assetList";
url: string;
}
))[];
interstitials?: SessionInterstitial[];
expiry?: number;
}) {
const id = randomUUID();
Expand All @@ -52,27 +49,10 @@ export async function createSession(params: {
};

if (params.interstitials) {
session.interstitials = params.interstitials.map<Interstitial>((item) => {
const { time, ...rest } = item;
const dateTime =
typeof time === "string"
? DateTime.fromISO(time)
: startTime.plus({ seconds: time });

// TODO: Below is heavily untyped. Find an explicit way to map input to an |Interstitial|.
let params;
if (rest.type === "asset") {
const { uri, ...assetRest } = rest;
params = { url: resolveUri(uri), ...assetRest };
} else {
params = rest;
}

return {
dateTime,
...params,
};
});
session.interstitials = mapSessionInterstitials(
startTime,
params.interstitials,
);
}

// We'll initially store the session for 10 minutes, if it's not been consumed
Expand All @@ -95,3 +75,37 @@ export async function updateSession(session: Session) {
const value = JSON.stringify(session);
await kv.set(`session:${session.id}`, value, session.expiry);
}

function mapSessionInterstitials(
startTime: DateTime,
interstitials: SessionInterstitial[],
): Interstitial[] {
return interstitials.reduce<Interstitial[]>((acc, item) => {
const dateTime =
typeof item.time === "string"
? DateTime.fromISO(item.time)
: startTime.plus({ seconds: item.time });

if (item.type === "asset") {
acc.push({
dateTime,
asset: {
url: resolveUri(item.uri),
kind: item.kind,
},
});
} else if (item.type === "vast") {
acc.push({
dateTime,
vast: { url: item.url },
});
} else if (item.type === "assetList") {
acc.push({
dateTime,
assetList: { url: item.url },
});
}

return acc;
}, []);
}
34 changes: 18 additions & 16 deletions packages/stitcher/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
import type { DateTime } from "luxon";

export type Interstitial = {
export interface InterstitialVast {
url?: string;
data?: string;
}

export interface InterstitialAsset {
url: string;
duration?: number;
kind?: "ad" | "bumper";
}

export interface Interstitial {
dateTime: DateTime;
} & (
| {
type: "asset";
url: string;
}
| {
type: "vast";
url?: string;
data?: string;
}
| {
type: "assetList";
url: string;
}
);
asset?: InterstitialAsset;
vast?: InterstitialVast;
assetList?: {
url: string;
};
}
Loading

0 comments on commit 7553b32

Please sign in to comment.