Skip to content

Commit b55664d

Browse files
committed
feat: Direct master.m3u8 with query params
1 parent 7526da6 commit b55664d

File tree

4 files changed

+75
-17
lines changed

4 files changed

+75
-17
lines changed

packages/stitcher/package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,10 @@
1818
"esm": "^3.2.25",
1919
"fetch-mock": "^11.1.1",
2020
"jest": "^29.7.0",
21+
"jest-prettier": "npm:prettier@^2",
2122
"ts-jest": "^29.2.5",
2223
"tsc-watch": "^6.2.0",
23-
"typescript": "^5.5.4",
24-
"jest-prettier": "npm:prettier@^2"
24+
"typescript": "^5.5.4"
2525
},
2626
"dependencies": {
2727
"@fastify/cors": "^9.0.1",
@@ -35,6 +35,7 @@
3535
"fastify": "^4.28.1",
3636
"find-config": "^1.0.0",
3737
"hh-mm-ss": "^1.2.0",
38+
"hi-base64": "^0.3.1",
3839
"parse-filepath": "^1.0.2",
3940
"redis": "^4.7.0",
4041
"uuid": "^10.0.0",

packages/stitcher/src/contract.ts

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import { initContract } from "@ts-rest/core";
22
import * as z from "zod";
3+
import base64 from "hi-base64";
34

45
const c = initContract();
56

6-
export const postSessionBodySchema = z.object({
7-
assetId: z.string(),
7+
const sessionParams = z.object({
88
vmapUrl: z.string().optional(),
99
interstitials: z
1010
.array(
@@ -15,16 +15,34 @@ export const postSessionBodySchema = z.object({
1515
)
1616
.optional(),
1717
bumperAssetId: z.string().optional(),
18-
maxResolution: z.number().optional(),
18+
maxResolution: z.coerce.number().optional(),
1919
});
2020

21+
export const postSessionBodySchema = z
22+
.object({
23+
assetId: z.string(),
24+
})
25+
.merge(sessionParams);
26+
27+
export const getDirectMasterPlaylistQuerySchema = z
28+
.object({
29+
params: base64Type(sessionParams).optional(),
30+
})
31+
.merge(sessionParams);
32+
2133
export const contract = c.router({
2234
postSession: {
2335
method: "POST",
2436
path: "/session",
2537
body: postSessionBodySchema,
2638
responses: {},
2739
},
40+
getDirectMasterPlaylist: {
41+
method: "GET",
42+
path: "/direct/:assetId/master.m3u8",
43+
responses: {},
44+
query: getDirectMasterPlaylistQuerySchema,
45+
},
2846
getMasterPlaylist: {
2947
method: "GET",
3048
path: "/session/:sessionId/master.m3u8",
@@ -49,3 +67,29 @@ export const contract = c.router({
4967
responses: {},
5068
},
5169
});
70+
71+
function base64Type<T extends z.AnyZodObject>(schema: T) {
72+
return z.string().transform((value) => {
73+
const raw = base64.decode(value);
74+
const obj = JSON.parse(raw);
75+
return schema.parse(obj);
76+
});
77+
}
78+
79+
function recursiveToCamel(item: unknown) {
80+
if (Array.isArray(item)) {
81+
return item.map((el: unknown) => recursiveToCamel(el));
82+
} else if (typeof item === "function" || item !== Object(item)) {
83+
return item;
84+
}
85+
return Object.fromEntries(
86+
Object.entries(item as Record<string, unknown>).map(
87+
([key, value]: [string, unknown]) => [
88+
key.replace(/([-_][a-z])/gi, (c) =>
89+
c.toUpperCase().replace(/[-_]/g, ""),
90+
),
91+
recursiveToCamel(value),
92+
],
93+
),
94+
);
95+
}

packages/stitcher/src/index.ts

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -31,27 +31,33 @@ async function buildServer() {
3131
},
3232
};
3333
},
34+
getDirectMasterPlaylist: async ({ request, params, query, reply }) => {
35+
const body = {
36+
assetId: params.assetId,
37+
// Use params from base64 payload first.
38+
...query.params,
39+
// Overwrite them with query params.
40+
...query,
41+
};
42+
43+
const session = await createSession(body);
44+
45+
return reply.redirect(
46+
`${request.protocol}://${request.hostname}/session/${session.id}/master.m3u8`,
47+
302,
48+
);
49+
},
3450
getMasterPlaylist: async ({ params, reply }) => {
3551
const session = await getSession(params.sessionId);
3652
const response = await formatMasterPlaylist(session);
3753

38-
reply.type("application/x-mpegURL");
39-
40-
return {
41-
status: 200,
42-
body: response,
43-
};
54+
return reply.type("application/x-mpegURL").send(response);
4455
},
4556
getMediaPlaylist: async ({ params, reply }) => {
4657
const session = await getSession(params.sessionId);
4758
const response = await formatMediaPlaylist(session, params.path);
4859

49-
reply.type("application/x-mpegURL");
50-
51-
return {
52-
status: 200,
53-
body: response,
54-
};
60+
return reply.type("application/x-mpegURL").send(response);
5561
},
5662
getAssetList: async ({ query, params }) => {
5763
const session = await getSession(params.sessionId);

pnpm-lock.yaml

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)