v3: youtube playlists get a tracklist too — the embed keeps playing the list, so the rows come from two halves: /api/youtube/playlist scrapes titles and lengths off the playlist page (current lockup rows and the older renderer, cached 6h), while the IFrame API attached to the iframe reports the position and takes row clicks and ⏮/⏭; the header names the playing video with the dump's title below it, like a bandcamp track; either half can fail — no scrape leaves the bare embed, no API leaves the rows as a table of contents — and the pause an API-enabled embed gives itself when the tab goes to the background is undone within a short window, so a later pause still stands
All checks were successful
Build and Publish Docker Image / build-and-push (push) Successful in 42s
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>
This commit is contained in:
58
src/utils/youtube.ts
Normal file
58
src/utils/youtube.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
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;
|
||||
}
|
||||
Reference in New Issue
Block a user