73 lines
2.5 KiB
TypeScript
73 lines
2.5 KiB
TypeScript
/**
|
|
* Turns a Bandcamp page into a queue of streams for the site's own player.
|
|
* `usePlayRichContent` calls it when a Bandcamp link is played in native mode,
|
|
* and `streamSources.ts` again when the streams' signed URLs expire.
|
|
*
|
|
* @module
|
|
*/
|
|
|
|
import { API_URL } from "../../config/api.ts";
|
|
import type { PlayerItem } from "../../contexts/player/PlayerContext.ts";
|
|
import type { Tralbum, TralbumTrack } from "../../../shared/player.ts";
|
|
|
|
/**
|
|
* Passed to {@link resolveBandcampQueue} by its callers and copied onto every
|
|
* item of the queue, since the server's tracklist knows nothing of the dump.
|
|
*/
|
|
export interface BandcampContext {
|
|
dumpHref?: string;
|
|
/** Lets PlayerProvider fall back to Bandcamp's iframe if the streams fail. */
|
|
embedUrl?: string;
|
|
/** Used only if the page carries no album art of its own. */
|
|
fallbackArtworkUrl?: string;
|
|
}
|
|
|
|
/**
|
|
* Resolves the Bandcamp page `pageUrl` into one `stream` item per streamable
|
|
* track, through `GET /api/bandcamp/tracks`, each carrying `ctx`. `force`
|
|
* bypasses the server's cache, for re-resolving once the signed URLs expire.
|
|
*
|
|
* @throws {Error} When the request fails or the page has no streamable track.
|
|
* Callers then fall back to the embed.
|
|
*/
|
|
export async function resolveBandcampQueue(
|
|
pageUrl: string,
|
|
ctx: BandcampContext = {},
|
|
{ force = false }: { force?: boolean } = {},
|
|
): Promise<PlayerItem[]> {
|
|
const res = await fetch(
|
|
`${API_URL}/api/bandcamp/tracks?url=${encodeURIComponent(pageUrl)}${
|
|
force ? "&force=1" : ""
|
|
}`,
|
|
);
|
|
if (!res.ok) throw new Error(`resolve failed: ${res.status}`);
|
|
|
|
const body = await res.json() as { success: boolean; data?: Tralbum };
|
|
const album = body.data;
|
|
if (!body.success || !album) throw new Error("resolve returned no data");
|
|
|
|
const items = album.tracks
|
|
.filter((t): t is TralbumTrack & { streamUrl: string } =>
|
|
t.streamable && typeof t.streamUrl === "string"
|
|
)
|
|
.map((t): PlayerItem => ({
|
|
kind: "stream",
|
|
streamUrl: t.streamUrl,
|
|
type: "bandcamp",
|
|
// The header and the tracklist name the track; the artist goes below.
|
|
title: t.title,
|
|
subtitle: album.artist,
|
|
duration: t.duration,
|
|
trackNum: t.trackNum,
|
|
artworkUrl: album.artworkUrl ?? ctx.fallbackArtworkUrl,
|
|
dumpHref: ctx.dumpHref,
|
|
resolveUrl: album.sourceUrl,
|
|
resolveIndex: t.index,
|
|
resolvedAt: album.resolvedAt,
|
|
embedUrl: ctx.embedUrl,
|
|
}));
|
|
|
|
if (items.length === 0) throw new Error("no streamable tracks");
|
|
return items;
|
|
}
|