/** * API client for the at-run VOD bundle */ const RUNNER_URL = import.meta.env.VITE_RUNNER_URL || "http://code-atrun-vyl7lg-cc8112-157-173-113-6.traefik.me" const BUNDLE_PATH = import.meta.env.VITE_BUNDLE_PATH || "bundle/did:plc:p3cygo5s7wru2argeci6wfv6/atmosphereconf-vod/latest" export interface Video { uri: string title: string creator: string duration: number createdAt: string } export interface StreamInfo { quality: string width: number height: number bandwidth: number codecs: string frameRate?: number } export interface VideoStreamsResult { uri: string streams: StreamInfo[] audioTracks: Array<{ name: string; default: boolean }> } export interface VideoMetadata { thumbnail: string // base64 encoded JPEG (single frame) spriteSheet: string // base64 encoded JPEG (sprite grid for preview) vtt: string // base64 encoded WebVTT } export interface Profile { did: string handle: string displayName?: string avatar?: string description?: string } async function callEndpoint(endpoint: string, args: unknown = {}): Promise { const url = `${RUNNER_URL}/${BUNDLE_PATH}/${endpoint}` const res = await fetch(url, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(args), }) if (!res.ok) { const error = await res.json().catch(() => ({ error: res.statusText })) throw new Error(error.error || "Request failed") } return res.json() } export async function listVideos( limit?: number, cursor?: string ): Promise<{ videos: Video[]; cursor?: string }> { return callEndpoint("listVideos", { limit, cursor }) } export async function getVideo(uri: string): Promise