All checks were successful
Build and Publish Docker Image / build-and-push (push) Successful in 42s
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
59 lines
1.7 KiB
TypeScript
59 lines
1.7 KiB
TypeScript
import { API_URL } from "../config/api.ts";
|
|
|
|
/** Mirrors one entry of GET /api/youtube/playlist. */
|
|
export interface YouTubeTrack {
|
|
videoId: string;
|
|
title: string;
|
|
/** Seconds. */
|
|
duration?: number;
|
|
}
|
|
|
|
/**
|
|
* The playlist an embed URL plays, if any.
|
|
*
|
|
* Covers both embed shapes the provider produces: `/embed/videoseries?list=…`
|
|
* for a bare playlist, and `/embed/<id>?list=…` for a video opened in one.
|
|
*/
|
|
export function embedListId(embedUrl: string): string | null {
|
|
try {
|
|
return new URL(embedUrl).searchParams.get("list");
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Add the parameters the IFrame Player API needs to talk to the embed.
|
|
*
|
|
* `enablejsapi` has to be in the src the iframe was created with — it cannot be
|
|
* turned on afterwards — so it is added for every playlist embed, whether or
|
|
* not the API script ends up loading.
|
|
*/
|
|
export function withJsApi(url: string): string {
|
|
try {
|
|
const u = new URL(url);
|
|
u.searchParams.set("enablejsapi", "1");
|
|
u.searchParams.set("origin", globalThis.location.origin);
|
|
return u.toString();
|
|
} catch {
|
|
return url;
|
|
}
|
|
}
|
|
|
|
/** Titles and lengths for a playlist. Throws; callers fall back to no list. */
|
|
export async function fetchPlaylistTracks(
|
|
listId: string,
|
|
): Promise<YouTubeTrack[]> {
|
|
const res = await fetch(
|
|
`${API_URL}/api/youtube/playlist?list=${encodeURIComponent(listId)}`,
|
|
);
|
|
if (!res.ok) throw new Error(`playlist fetch failed: ${res.status}`);
|
|
const body = await res.json() as {
|
|
success: boolean;
|
|
data?: { tracks?: YouTubeTrack[] };
|
|
};
|
|
const tracks = body.data?.tracks;
|
|
if (!body.success || !tracks?.length) throw new Error("no tracks");
|
|
return tracks;
|
|
}
|