From b55664d4ae817490151c6128e100174249df7352 Mon Sep 17 00:00:00 2001 From: Matthias Van Parijs Date: Fri, 30 Aug 2024 17:15:59 +0200 Subject: [PATCH] feat: Direct master.m3u8 with query params --- packages/stitcher/package.json | 5 ++-- packages/stitcher/src/contract.ts | 50 +++++++++++++++++++++++++++++-- packages/stitcher/src/index.ts | 30 +++++++++++-------- pnpm-lock.yaml | 7 +++++ 4 files changed, 75 insertions(+), 17 deletions(-) diff --git a/packages/stitcher/package.json b/packages/stitcher/package.json index 620a690f..2acfbd23 100644 --- a/packages/stitcher/package.json +++ b/packages/stitcher/package.json @@ -18,10 +18,10 @@ "esm": "^3.2.25", "fetch-mock": "^11.1.1", "jest": "^29.7.0", + "jest-prettier": "npm:prettier@^2", "ts-jest": "^29.2.5", "tsc-watch": "^6.2.0", - "typescript": "^5.5.4", - "jest-prettier": "npm:prettier@^2" + "typescript": "^5.5.4" }, "dependencies": { "@fastify/cors": "^9.0.1", @@ -35,6 +35,7 @@ "fastify": "^4.28.1", "find-config": "^1.0.0", "hh-mm-ss": "^1.2.0", + "hi-base64": "^0.3.1", "parse-filepath": "^1.0.2", "redis": "^4.7.0", "uuid": "^10.0.0", diff --git a/packages/stitcher/src/contract.ts b/packages/stitcher/src/contract.ts index 6c367cb8..832fd8ab 100644 --- a/packages/stitcher/src/contract.ts +++ b/packages/stitcher/src/contract.ts @@ -1,10 +1,10 @@ import { initContract } from "@ts-rest/core"; import * as z from "zod"; +import base64 from "hi-base64"; const c = initContract(); -export const postSessionBodySchema = z.object({ - assetId: z.string(), +const sessionParams = z.object({ vmapUrl: z.string().optional(), interstitials: z .array( @@ -15,9 +15,21 @@ export const postSessionBodySchema = z.object({ ) .optional(), bumperAssetId: z.string().optional(), - maxResolution: z.number().optional(), + maxResolution: z.coerce.number().optional(), }); +export const postSessionBodySchema = z + .object({ + assetId: z.string(), + }) + .merge(sessionParams); + +export const getDirectMasterPlaylistQuerySchema = z + .object({ + params: base64Type(sessionParams).optional(), + }) + .merge(sessionParams); + export const contract = c.router({ postSession: { method: "POST", @@ -25,6 +37,12 @@ export const contract = c.router({ body: postSessionBodySchema, responses: {}, }, + getDirectMasterPlaylist: { + method: "GET", + path: "/direct/:assetId/master.m3u8", + responses: {}, + query: getDirectMasterPlaylistQuerySchema, + }, getMasterPlaylist: { method: "GET", path: "/session/:sessionId/master.m3u8", @@ -49,3 +67,29 @@ export const contract = c.router({ responses: {}, }, }); + +function base64Type(schema: T) { + return z.string().transform((value) => { + const raw = base64.decode(value); + const obj = JSON.parse(raw); + return schema.parse(obj); + }); +} + +function recursiveToCamel(item: unknown) { + if (Array.isArray(item)) { + return item.map((el: unknown) => recursiveToCamel(el)); + } else if (typeof item === "function" || item !== Object(item)) { + return item; + } + return Object.fromEntries( + Object.entries(item as Record).map( + ([key, value]: [string, unknown]) => [ + key.replace(/([-_][a-z])/gi, (c) => + c.toUpperCase().replace(/[-_]/g, ""), + ), + recursiveToCamel(value), + ], + ), + ); +} diff --git a/packages/stitcher/src/index.ts b/packages/stitcher/src/index.ts index 4c926d46..5370c4b8 100644 --- a/packages/stitcher/src/index.ts +++ b/packages/stitcher/src/index.ts @@ -31,27 +31,33 @@ async function buildServer() { }, }; }, + getDirectMasterPlaylist: async ({ request, params, query, reply }) => { + const body = { + assetId: params.assetId, + // Use params from base64 payload first. + ...query.params, + // Overwrite them with query params. + ...query, + }; + + const session = await createSession(body); + + return reply.redirect( + `${request.protocol}://${request.hostname}/session/${session.id}/master.m3u8`, + 302, + ); + }, getMasterPlaylist: async ({ params, reply }) => { const session = await getSession(params.sessionId); const response = await formatMasterPlaylist(session); - reply.type("application/x-mpegURL"); - - return { - status: 200, - body: response, - }; + return reply.type("application/x-mpegURL").send(response); }, getMediaPlaylist: async ({ params, reply }) => { const session = await getSession(params.sessionId); const response = await formatMediaPlaylist(session, params.path); - reply.type("application/x-mpegURL"); - - return { - status: 200, - body: response, - }; + return reply.type("application/x-mpegURL").send(response); }, getAssetList: async ({ query, params }) => { const session = await getSession(params.sessionId); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9cd341de..5cfc0730 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -320,6 +320,9 @@ importers: hh-mm-ss: specifier: ^1.2.0 version: 1.2.0 + hi-base64: + specifier: ^0.3.1 + version: 0.3.1 parse-filepath: specifier: ^1.0.2 version: 1.0.2 @@ -7906,6 +7909,10 @@ packages: zero-fill: 2.2.4 dev: false + /hi-base64@0.3.1: + resolution: {integrity: sha512-1dcNjxp2VHFnQkDn/Jrvz4IITgpalWbH6HlaTrZGXuZ5jA1+oIjJtGXYjem6V5rD0tCDGCVNYpD+mJRaNi0Jcw==} + dev: false + /highlight.js@11.10.0: resolution: {integrity: sha512-SYVnVFswQER+zu1laSya563s+F8VDGt7o35d4utbamowvUNLLMovFqwCLSocpZTz3MgaSRA1IbqRWZv97dtErQ==} engines: {node: '>=12.0.0'}