Skip to content

Commit

Permalink
Made sessionId optional
Browse files Browse the repository at this point in the history
  • Loading branch information
matvp91 committed Nov 22, 2024
1 parent 1704fbd commit ae5bc84
Show file tree
Hide file tree
Showing 5 changed files with 121 additions and 58 deletions.
15 changes: 10 additions & 5 deletions packages/stitcher/src/filters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,19 @@ export interface Filter {
audioLanguage?: string;
}

export function formatFilterToQueryParam(filter: Filter) {
export function formatFilterToQueryParam(filter?: Filter) {
if (!filter) {
return undefined;
}
return btoa(JSON.stringify(filter));
}

export const filterSchema = t
.Transform(t.String())
.Decode((value) => JSON.parse(atob(value)) as Filter)
.Encode(formatFilterToQueryParam);
export const filterSchema = t.Optional(
t
.Transform(t.String())
.Decode((value) => JSON.parse(atob(value)) as Filter)
.Encode((filter) => btoa(JSON.stringify(filter))),
);

function parseRange(input: string): [number, number] | null {
const match = input.match(/^(\d+)-(\d+)$/);
Expand Down
2 changes: 1 addition & 1 deletion packages/stitcher/src/parser/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { Rendition, Variant } from "./types";

export function groupRenditions(variants: Variant[]) {
export function getRenditions(variants: Variant[]) {
const group = new Set<Rendition>();
variants.forEach((variant) => {
variant.audio.forEach((rendition) => {
Expand Down
2 changes: 1 addition & 1 deletion packages/stitcher/src/parser/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export { parseMasterPlaylist, parseMediaPlaylist } from "./parse";
export { stringifyMasterPlaylist, stringifyMediaPlaylist } from "./stringify";
export { groupRenditions } from "./helpers";
export { getRenditions } from "./helpers";

export * from "./types";
81 changes: 47 additions & 34 deletions packages/stitcher/src/playlist.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,40 +4,41 @@ import { getAssets, getStaticDateRanges } from "./interstitials";
import { encrypt } from "./lib/crypto";
import { joinUrl, makeUrl, resolveUri } from "./lib/url";
import {
groupRenditions,
parseMasterPlaylist,
parseMediaPlaylist,
stringifyMasterPlaylist,
stringifyMediaPlaylist,
} from "./parser";
import { getRenditions } from "./parser/helpers";
import type { Filter } from "./filters";
import type { Session } from "./session";

export async function formatMasterPlaylist(
masterUrl: string,
options: {
filter: Filter;
session?: Session;
},
) {
const master = await fetchMasterPlaylist(masterUrl);
export async function formatMasterPlaylist(params: {
origUrl: string;
sessionId?: string;
filter?: Filter;
}) {
const master = await fetchMasterPlaylist(params.origUrl);

filterMasterPlaylist(master, options.filter);
if (params.filter) {
filterMasterPlaylist(master, params.filter);
}

for (const variant of master.variants) {
const url = joinUrl(masterUrl, variant.uri);
const url = joinUrl(params.origUrl, variant.uri);
variant.uri = makeMediaUrl({
url,
session: options.session,
sessionId: params.sessionId,
});
}

const renditions = groupRenditions(master.variants);
const renditions = getRenditions(master.variants);

renditions.forEach((rendition) => {
const url = joinUrl(masterUrl, rendition.uri);
const url = joinUrl(params.origUrl, rendition.uri);
rendition.uri = makeMediaUrl({
url,
session: options.session,
sessionId: params.sessionId,
type: rendition.type,
});
});
Expand All @@ -46,31 +47,33 @@ export async function formatMasterPlaylist(
}

export async function formatMediaPlaylist(
session: Session,
mediaUrl: string,
session?: Session,
renditionType?: string,
) {
const { startTime } = session;
assert(startTime, "No startTime in session");

const media = await fetchMediaPlaylist(mediaUrl);

// We're in a video playlist when we have no renditionType passed along,
// this means it does not belong to EXT-X-MEDIA, or when we explicitly VIDEO.
const videoPlaylist = !renditionType || renditionType === "VIDEO";
const firstSegment = media.segments[0];

if (media.endlist) {
assert(firstSegment);
firstSegment.programDateTime = startTime;
}
if (session) {
// If we have a session, we must have a startTime thus meaning we started.
assert(session.startTime);

if (media.endlist) {
assert(firstSegment);
firstSegment.programDateTime = session.startTime;
}

if (videoPlaylist && firstSegment?.programDateTime) {
// If we have an endlist and a PDT, we can add static date ranges based on this.
media.dateRanges = getStaticDateRanges(
firstSegment.programDateTime,
session,
);
if (videoPlaylist && firstSegment?.programDateTime) {
// If we have an endlist and a PDT, we can add static date ranges based on this.
media.dateRanges = getStaticDateRanges(
firstSegment.programDateTime,
session,
);
}
}

media.segments.forEach((segment) => {
Expand Down Expand Up @@ -119,24 +122,34 @@ export async function fetchDuration(uri: string) {

export function makeMasterUrl(params: {
url: string;
filter: Filter;
filter?: Filter;
session?: Session;
}) {
return makeUrl("out/master.m3u8", {
const fil = formatFilterToQueryParam(params.filter);

const outUrl = makeUrl("out/master.m3u8", {
eurl: encrypt(params.url),
sid: params.session?.id,
fil: formatFilterToQueryParam(params.filter),
fil,
});

const url = params.session
? makeUrl(`session/${params.session.id}/master.m3u8`, {
fil,
})
: undefined;

return { url, outUrl };
}

function makeMediaUrl(params: {
url: string;
session?: Session;
sessionId?: string;
type?: string;
}) {
return makeUrl("out/playlist.m3u8", {
eurl: encrypt(params.url),
sid: params.session?.id,
sid: params.sessionId,
type: params.type,
});
}
79 changes: 62 additions & 17 deletions packages/stitcher/src/routes/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,22 +12,43 @@ import {
getSession,
processSessionOnMasterReq,
} from "../session";
import type { Filter } from "../filters";
import type { Session } from "../session";

async function handleMasterPlaylist(
origUrl: string,
session?: Session,
filter?: Filter,
) {
if (session) {
await processSessionOnMasterReq(session);
}

const sessionId = session?.id;
const playlist = await formatMasterPlaylist({
origUrl,
sessionId,
filter,
});

return playlist;
}

export const sessionRoutes = new Elysia()
.post(
"/session",
async ({ body }) => {
const session = await createSession(body);

const filter = body.filter ?? {};
const filter = body.filter;

const url = makeMasterUrl({
const { url, outUrl } = makeMasterUrl({
url: session.url,
filter,
session,
});

return { url };
return { url, outUrl };
},
{
detail: {
Expand Down Expand Up @@ -89,17 +110,36 @@ export const sessionRoutes = new Elysia()
},
)
.get(
"/out/master.m3u8",
async ({ set, query }) => {
const session = await getSession(query.sid);
"/session/:sessionId/master.m3u8",
async ({ set, params, query }) => {
const session = await getSession(params.sessionId);

const playlist = await handleMasterPlaylist(
session.url,
session,
query.fil,
);

await processSessionOnMasterReq(session);
set.headers["content-type"] = "application/vnd.apple.mpegurl";

return playlist;
},
{
params: t.Object({
sessionId: t.String(),
}),
query: t.Object({
fil: filterSchema,
}),
},
)
.get(
"/out/master.m3u8",
async ({ set, query }) => {
const url = decrypt(query.eurl);
const playlist = await formatMasterPlaylist(url, {
session,
filter: query.fil,
});

const session = query.sid ? await getSession(query.sid) : undefined;
const playlist = await handleMasterPlaylist(url, session, query.fil);

set.headers["content-type"] = "application/vnd.apple.mpegurl";

Expand All @@ -111,18 +151,20 @@ export const sessionRoutes = new Elysia()
},
query: t.Object({
eurl: t.String(),
sid: t.String(),
sid: t.Optional(t.String()),
fil: filterSchema,
}),
},
)
.get(
"/out/playlist.m3u8",
async ({ set, query }) => {
const session = await getSession(query.sid);
const session = query.sid ? await getSession(query.sid) : undefined;

const url = decrypt(query.eurl);
const playlist = await formatMediaPlaylist(session, url, query.type);
const type = query.type;

const playlist = await formatMediaPlaylist(url, session, type);

set.headers["content-type"] = "application/vnd.apple.mpegurl";

Expand All @@ -134,17 +176,20 @@ export const sessionRoutes = new Elysia()
},
query: t.Object({
eurl: t.String(),
sid: t.String(),
sid: t.Optional(t.String()),
type: t.Optional(t.String()),
}),
},
)
.get(
"/out/asset-list.json",
async ({ query }) => {
const session = await getSession(query.sid);
const sessionId = query.sid;
const timeOffset = query.timeOffset;

const session = await getSession(sessionId);

return await formatAssetList(session, query.timeOffset);
return await formatAssetList(session, timeOffset);
},
{
detail: {
Expand Down

0 comments on commit ae5bc84

Please sign in to comment.