Skip to content

Commit

Permalink
feat: Added operations to resolution filtering for stitcher
Browse files Browse the repository at this point in the history
  • Loading branch information
matvp91 committed Sep 16, 2024
1 parent 82d6be4 commit 2b986be
Show file tree
Hide file tree
Showing 5 changed files with 75 additions and 21 deletions.
2 changes: 1 addition & 1 deletion packages/stitcher/src/contract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ const sessionParams = z.object({
}),
)
.optional(),
maxResolution: z.coerce.number().optional(),
resolution: z.string().optional(),
});

export const postSessionBodySchema = z
Expand Down
35 changes: 35 additions & 0 deletions packages/stitcher/src/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { env } from "./env.js";
import type * as hlsParser from "../extern/hls-parser/index.js";

export async function isAssetAvailable(assetId: string) {
const response = await fetch(
Expand All @@ -9,3 +10,37 @@ export async function isAssetAvailable(assetId: string) {
);
return response.ok;
}

export function filterByString(items: hlsParser.types.Variant[], str: string) {
const [operator, value] = str.split(" ");

if (operator === "<") {
return items.filter(
(item) =>
item.resolution && item.resolution?.height < parseInt(value, 10),
);
}

if (operator === "<=") {
return items.filter(
(item) =>
item.resolution && item.resolution?.height <= parseInt(value, 10),
);
}

if (operator === ">") {
return items.filter(
(item) =>
item.resolution && item.resolution?.height > parseInt(value, 10),
);
}

if (operator === ">=") {
return items.filter(
(item) =>
item.resolution && item.resolution?.height >= parseInt(value, 10),
);
}

throw new Error("Invalid filter");
}
27 changes: 21 additions & 6 deletions packages/stitcher/src/playlist.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +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 { filterByString } from "./helpers.js";
import type { Session, Interstitial, InterstitialType } from "./types.js";

const PlaylistUnavailableError = createError<[string]>(
Expand All @@ -18,6 +19,13 @@ const NoVariantsError = createError(
400,
);

const NoInterstitialsError = createError(
"NO_INTERSTITIALS",
"The playlist does not contain interstitials",
);

const InvalidFilter = createError("INVALID_FILTER", "Invalid filter");

async function fetchPlaylist<T>(url: string) {
try {
const response = await fetch(url);
Expand All @@ -33,12 +41,13 @@ export async function formatMasterPlaylist(session: Session) {

const master = await fetchPlaylist<MasterPlaylist>(url);

master.variants = master.variants.filter((variant) => {
if (!variant.resolution) {
return true;
if (session.resolution) {
try {
master.variants = filterByString(master.variants, session.resolution);
} catch {
throw new InvalidFilter();
}
return variant.resolution.height <= session.maxResolution;
});
}

const filePath = parseFilepath(url);
for (const v of master.variants) {
Expand All @@ -64,12 +73,18 @@ export async function formatMediaPlaylist(session: Session, path: string) {

rewriteSegmentUrls(media, url);

addInterstitials(media, session.interstitials, session.id);
if (session.interstitials) {
addInterstitials(media, session.interstitials, session.id);
}

return hlsParser.stringify(media);
}

export async function formatAssetList(session: Session, timeOffset: number) {
if (!session.interstitials) {
throw new NoInterstitialsError();
}

const interstitials = session.interstitials.filter(
(ad) => ad.timeOffset === timeOffset,
);
Expand Down
28 changes: 16 additions & 12 deletions packages/stitcher/src/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import { randomUUID } from "crypto";
import { extractInterstitialFromVmapAdbreak } from "./vast.js";
import { getVmap } from "./vmap.js";
import createError from "@fastify/error";
import type { Session, Interstitial } from "./types.js";
import { isAssetAvailable } from "./helpers.js";
import type { Session, Interstitial } from "./types.js";

const NoSessionError = createError<[string]>(
"NO_SESSION",
Expand All @@ -30,46 +30,50 @@ export async function createSession(data: {
url: string;
};
interstitials?: Interstitial[];
maxResolution?: number;
resolution?: string;
}) {
if (!(await isAssetAvailable(data.assetId))) {
throw new PlaylistUnavailableError(data.assetId);
}

const sessionId = randomUUID();

const interstitials: Interstitial[] = [];
let interstitials: Interstitial[] | undefined;

if (data.vmap) {
const vmap = await getVmap(data.vmap.url);

for (const adBreak of vmap.adBreaks) {
if (!interstitials) {
interstitials = [];
}

interstitials.push(
...(await extractInterstitialFromVmapAdbreak(adBreak)),
);
}
}

if (data.interstitials) {
interstitials.push(...data.interstitials);
}
if (data.interstitials?.length) {
if (!interstitials) {
interstitials = [];
}

let maxResolution = data.maxResolution;
if (!maxResolution) {
const MAX_RESOLUTION_8K = 4320;
maxResolution = MAX_RESOLUTION_8K;
interstitials.push(...data.interstitials);
}

const session = {
id: sessionId,
assetId: data.assetId,
interstitials,
maxResolution,
resolution: data.resolution,
} satisfies Session;

const redisKey = getRedisKey(sessionId);

await client.json.set(redisKey, `$`, session);
const rawSession = JSON.parse(JSON.stringify(session));

await client.json.set(redisKey, `$`, rawSession);
await client.expire(redisKey, 60 * 60 * 6);

return session;
Expand Down
4 changes: 2 additions & 2 deletions packages/stitcher/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
export type Session = {
id: string;
assetId: string;
interstitials: Interstitial[];
maxResolution: number;
interstitials?: Interstitial[];
resolution?: string;
};

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

0 comments on commit 2b986be

Please sign in to comment.