Skip to content

Commit

Permalink
feat: Stitcher tests (#12)
Browse files Browse the repository at this point in the history
  • Loading branch information
matvp91 authored Aug 29, 2024
1 parent c2299c3 commit e478731
Show file tree
Hide file tree
Showing 62 changed files with 533 additions and 205 deletions.
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
SpliceInfo,
Variant,
PostProcess,
} from "./types";
} from "./types.js";

const ALLOW_REDUNDANCY = [
"#EXTINF",
Expand Down
23 changes: 17 additions & 6 deletions packages/stitcher/jest.config.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,22 @@
import { createRequire } from "node:module";

const require = createRequire(import.meta.url);

/** @type {import('ts-jest').JestConfigWithTsJest} **/
export default {
testEnvironment: "node",
transform: {
"^.+.tsx?$": ["ts-jest", {}],
},
moduleNameMapper: {
"^@src(.*)$": "<rootDir>/src$1",
"^(\\.{1,2}/.*)\\.js$": "$1",
"@mixwave/artisan/producer":
"<rootDir>/test/mocks/import-artisan-producer.ts",
},
extensionsToTreatAsEsm: [".ts"],
transform: {
"^.+\\.(mt|t|cj|j)s$": [
"ts-jest",
{
useESM: true,
},
],
},
resolver: "jest-ts-webcompat-resolver",
prettierPath: require.resolve("jest-prettier"),
};
8 changes: 5 additions & 3 deletions packages/stitcher/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,23 @@
"build": "tsc",
"start": "node ./dist/index.js",
"dev": "tsc-watch --noClear --onSuccess \"node ./dist/index.js\"",
"test": "jest"
"test": "NODE_OPTIONS=--experimental-vm-modules jest"
},
"devDependencies": {
"@babel/preset-env": "^7.25.4",
"@jest/globals": "^29.7.0",
"@types/find-config": "^1.0.4",
"@types/hh-mm-ss": "^1.2.3",
"@types/node": "^22.1.0",
"@types/parse-filepath": "^1.0.2",
"@types/uuid": "^10.0.0",
"esm": "^3.2.25",
"fetch-mock": "^11.1.1",
"jest": "^29.7.0",
"jest-ts-webcompat-resolver": "^1.0.0",
"ts-jest": "^29.2.5",
"tsc-watch": "^6.2.0",
"typescript": "^5.5.4"
"typescript": "^5.5.4",
"jest-prettier": "npm:prettier@^2"
},
"dependencies": {
"@fastify/cors": "^9.0.1",
Expand Down
6 changes: 3 additions & 3 deletions packages/stitcher/src/playlist.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { parse, stringify } from "./extern/hls-parser/index.js";
import { parse, stringify } from "../extern/hls-parser/index.js";
import parseFilepath from "parse-filepath";
import { Interstitial } from "./extern/hls-parser/types.js";
import { Interstitial } from "../extern/hls-parser/types.js";
import { env } from "./env.js";
import { MasterPlaylist, MediaPlaylist } from "./extern/hls-parser/types.js";
import { MasterPlaylist, MediaPlaylist } from "../extern/hls-parser/types.js";
import type { Session } from "./types.js";

async function fetchPlaylist<T>(url: string) {
Expand Down
26 changes: 20 additions & 6 deletions packages/stitcher/src/session.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import { client } from "./redis.js";
import { randomUUID } from "crypto";
import { getInterstitialsFromVmap } from "./vmap.js";
import { extractInterstitialFromVmapAdbreak } from "./vast.js";
import { getVmap } from "./vmap.js";
import type { Session, Interstitial } from "./types.js";

const REDIS_PREFIX = `stitcher:session`;

const key = (sessionId: string) => `${REDIS_PREFIX}:${sessionId}`;
function getRedisKey(sessionId: string) {
return `${REDIS_PREFIX}:${sessionId}`;
}

export async function createSession(data: {
assetId: string;
Expand All @@ -18,8 +21,15 @@ export async function createSession(data: {
const interstitials: Interstitial[] = [];

if (data.vmapUrl) {
interstitials.push(...(await getInterstitialsFromVmap(data.vmapUrl)));
const vmap = await getVmap(data.vmapUrl);

for (const adBreak of vmap.adBreaks) {
interstitials.push(
...(await extractInterstitialFromVmapAdbreak(adBreak)),
);
}
}

if (data.interstitials) {
interstitials.push(...data.interstitials);
}
Expand All @@ -38,17 +48,21 @@ export async function createSession(data: {
interstitials,
} satisfies Session;

await client.json.set(key(sessionId), `$`, session);
const redisKey = getRedisKey(sessionId);

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

return session;
}

export async function getSession(sessionId: string) {
const data = await client.json.get(key(sessionId));
const redisKey = getRedisKey(sessionId);

const data = await client.json.get(redisKey);
if (!data) {
throw new Error("No session found for id");
}

return data as Session;
}
42 changes: 22 additions & 20 deletions packages/stitcher/src/vast.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,30 @@
import { env } from "./env.js";
import { addTranscodeJob } from "@mixwave/artisan/producer";
import { VASTClient } from "./extern/vast-client/index.js";
import { VASTClient } from "../extern/vast-client/index.js";
import { DOMParser } from "@xmldom/xmldom";
import * as uuid from "uuid";
import { NAMESPACE_UUID_AD } from "./const.js";
import type { VmapAdBreak, VmapResponse } from "./vmap.js";
import type { VmapAdBreak } from "./vmap.js";
import type { Interstitial } from "./types.js";
import type {
VastResponse,
VastCreativeLinear,
VastAd,
} from "./extern/vast-client/index.js";
} from "../extern/vast-client/index.js";

export async function extractInterstitialsFromVmap(vmapResponse: VmapResponse) {
export async function extractInterstitialFromVmapAdbreak(adBreak: VmapAdBreak) {
const interstitials: Interstitial[] = [];

for (const adBreak of vmapResponse.adBreaks) {
const adMedias = await getAdMedias(adBreak);

for (const adMedia of adMedias) {
if (await isPackaged(adMedia.assetId)) {
interstitials.push({
timeOffset: adBreak.timeOffset,
assetId: adMedia.assetId,
});
} else {
scheduleForPackage(adMedia);
}
const adMedias = await getAdMedias(adBreak);

for (const adMedia of adMedias) {
if (await isPackaged(adMedia.assetId)) {
interstitials.push({
timeOffset: adBreak.timeOffset,
assetId: adMedia.assetId,
});
} else {
scheduleForPackage(adMedia);
}
}

Expand Down Expand Up @@ -138,10 +136,14 @@ function getCreative(ad: VastAd) {
}

function getAdId(creative: VastCreativeLinear) {
// Do not change this, or we'll have a mismatch between the already encoded ad's and the other.
// See https://iabtechlab.com/guidance-for-uniquely-identifying-creative-asset-ids-in-vast-2/
const adId = [creative.adId, creative.id].join(".");
return uuid.v5(adId, NAMESPACE_UUID_AD);
if (creative.adId && creative.id) {
// Do not change this, or we'll have a mismatch between the already encoded ad's and the other.
// See https://iabtechlab.com/guidance-for-uniquely-identifying-creative-asset-ids-in-vast-2/
const adId = [creative.adId, creative.id].join(".");
return uuid.v5(adId, NAMESPACE_UUID_AD);
}

throw new Error("Failed to generate adId");
}

type AdMedia = {
Expand Down
10 changes: 2 additions & 8 deletions packages/stitcher/src/vmap.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,10 @@
import { DOMParser, XMLSerializer } from "@xmldom/xmldom";
import { extractInterstitialsFromVmap } from "./vast.js";
import timeFormat from "hh-mm-ss";
import * as timeFormat from "hh-mm-ss";

const USER_AGENT =
"Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/38.0.2125.111 Safari/537.36";

export async function getInterstitialsFromVmap(url: string) {
const vmap = await getVmap(url);
return await extractInterstitialsFromVmap(vmap);
}

async function getVmap(url: string): Promise<VmapResponse> {
export async function getVmap(url: string): Promise<VmapResponse> {
const doc = await getXml(url);
const rootElement = doc.documentElement;

Expand Down
7 changes: 0 additions & 7 deletions packages/stitcher/test/__snapshots__/playlist.test.ts.snap

This file was deleted.

3 changes: 3 additions & 0 deletions packages/stitcher/test/mocks/import-artisan-producer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { jest } from "@jest/globals";

export const addTranscodeJob = jest.fn();
File renamed without changes.
Loading

0 comments on commit e478731

Please sign in to comment.