Something went wrong. Try again.
An entry for the streamplace vod showcase
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825/** * VOD Bundle: AtmosphereConf video backend * * Handles video listing, muxing, and quality selection */
import { endpoint, manifest, v } from "@at-run/runtime"import { AtpAgent } from "@atproto/api"import { IdResolver } from "@atproto/identity"import { AtUri } from "@atproto/syntax"import { Parser } from "m3u8-parser"
// Re-export v for sandbox validationexport { v }
const VOD_BETA_URL = "https://vod-beta.stream.place"const VOD_XRPC_BASE = `${VOD_BETA_URL}/xrpc/`const CACHE_DIR = "/tmp/vod-cache"
// =============================================================================// Bundle Manifest// =============================================================================
export const bundle = manifest({ name: "atmosphereconf-vod", description: "AtmosphereConf 2026 VOD backend - video listing, muxing, and quality selection", permissions: { net: [ "vod-beta.stream.place", // HLS playlists and segments "iameli.com", // stream.place PDS "plc.directory", // DID resolution "public.api.bsky.app", // Bluesky public API for profiles "*.modal.run", // Modal accelerator endpoints "*.mainasara.dev", // AppView (at-run hosted) "cdn.bsky.app", // Bluesky CDN for avatars ], write: ["/tmp/vod-cache/"], read: ["/tmp/vod-cache/"], env: ["APPVIEW_URL"], },})
// Identity resolver for DID -> PDS resolutionconst idResolver = new IdResolver()
async function sha256(data: string | Uint8Array): Promise<string> { const bytes = typeof data === "string" ? new TextEncoder().encode(data) : data const hash = await crypto.subtle.digest("SHA-256", bytes.buffer as ArrayBuffer) return [...new Uint8Array(hash)].map(b => b.toString(16).padStart(2, "0")).join("")}
async function exists(path: string): Promise<boolean> { try { await Deno.stat(path) return true } catch { return false }}
/** * Get an AtpAgent for a given DID's PDS */async function getAgentForDid(did: string): Promise<AtpAgent> { const didDoc = await idResolver.did.resolve(did) if (!didDoc) { throw new Error(`Failed to resolve DID: ${did}`) }
const pdsUrl = didDoc.service?.find( (s) => s.id === "#atproto_pds" || s.id === `${did}#atproto_pds` )?.serviceEndpoint as string | undefined
if (!pdsUrl) { throw new Error("No PDS service found in DID document") }
return new AtpAgent({ service: pdsUrl })}
// Types for parsed HLS manifestinterface ParsedPlaylist { attributes: { BANDWIDTH: number CODECS: string RESOLUTION?: { width: number; height: number } "FRAME-RATE"?: number AUDIO?: string } uri: string}
interface ParsedAudioTrack { default: boolean autoselect: boolean uri: string}
interface ParsedManifest { playlists: ParsedPlaylist[] mediaGroups: { AUDIO: Record<string, Record<string, ParsedAudioTrack>> }}
/** * Rewrite relative URIs in m3u8 content to absolute URLs */function rewritePlaylistUrls(content: string, baseUrl: string): string { return content .split("\n") .map((line) => { const trimmed = line.trim() // Skip empty lines, comments, and tags (except URI= attributes) if (!trimmed || trimmed.startsWith("#")) { // Handle URI= attributes in tags like #EXT-X-MEDIA if (trimmed.includes('URI="') && !trimmed.includes('URI="http')) { return trimmed.replace(/URI="([^"]+)"/, `URI="${baseUrl}$1"`) } return line } // Non-comment, non-empty lines are URIs - make them absolute if relative if (!trimmed.startsWith("http")) { return baseUrl + trimmed } return line }) .join("\n")}
/** * Fetch and parse the master HLS playlist for a video */async function fetchMasterPlaylist(videoUri: string): Promise<any> { let masterPlaylist = "" const basePath = `/tmp/vod-cache/${await sha256(videoUri)}` const playlistPath = `${basePath}/master.m3u8`
if (!(await exists(playlistPath))) { // Ensure cache directory exists await Deno.mkdir(basePath, { recursive: true })
const playlistUrl = `${VOD_XRPC_BASE}place.stream.playback.getVideoPlaylist?uri=${encodeURIComponent(videoUri)}`
const res = await fetch(playlistUrl) if (!res.ok) { throw new Error(`Failed to fetch playlist: ${res.status}`) }
const rawPlaylist = await res.text() // Rewrite relative URIs to absolute URLs for local ffmpeg access masterPlaylist = rewritePlaylistUrls(rawPlaylist, VOD_XRPC_BASE) await Deno.writeTextFile(playlistPath, masterPlaylist) } else { masterPlaylist = await Deno.readTextFile(playlistPath) }
const parser = new Parser() parser.push(masterPlaylist) parser.end()
return parser.manifest as any}
/** * Generate an HLS master playlist with absolute URLs */function generateMasterPlaylist( manifest: ParsedManifest, options?: { quality?: string }): string { const lines: string[] = ["#EXTM3U", "#EXT-X-VERSION:6"]
// Find audio tracks and add them const audioGroups = manifest.mediaGroups?.AUDIO || {} for (const [groupId, tracks] of Object.entries(audioGroups)) { for (const [trackName, track] of Object.entries(tracks)) { const absoluteUri = track.uri.startsWith("http") ? track.uri : `${VOD_XRPC_BASE}${track.uri}`
lines.push( `#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="${groupId}",NAME="${trackName}",` + `DEFAULT=${track.default ? "YES" : "NO"},AUTOSELECT=${track.autoselect ? "YES" : "NO"},` + `URI="${absoluteUri}"` ) } }
// Filter and add video playlists let playlists = manifest.playlists || []
// Filter by quality if specified if (options?.quality) { const targetHeight = parseInt(options.quality.replace("p", ""), 10) const filtered = playlists.filter( (p) => p.attributes.RESOLUTION?.height === targetHeight ) if (filtered.length > 0) { playlists = filtered } }
// Sort by bandwidth (highest first) playlists.sort((a, b) => b.attributes.BANDWIDTH - a.attributes.BANDWIDTH)
for (const playlist of playlists) { const attrs = playlist.attributes const parts: string[] = []
if (attrs.BANDWIDTH) parts.push(`BANDWIDTH=${attrs.BANDWIDTH}`) if (attrs.CODECS) parts.push(`CODECS="${attrs.CODECS}"`) if (attrs.RESOLUTION) { parts.push(`RESOLUTION=${attrs.RESOLUTION.width}x${attrs.RESOLUTION.height}`) } if (attrs["FRAME-RATE"]) parts.push(`FRAME-RATE=${attrs["FRAME-RATE"]}`) if (attrs.AUDIO) parts.push(`AUDIO="${attrs.AUDIO}"`)
lines.push(`#EXT-X-STREAM-INF:${parts.join(",")}`)
const absoluteUri = playlist.uri.startsWith("http") ? playlist.uri : `${VOD_XRPC_BASE}${playlist.uri}` lines.push(absoluteUri) }
return lines.join("\n")}
// ============================================================================// Schemas & Types// ============================================================================
const AtUriSchema = v.pipe( v.string(), v.startsWith("at://", "Must be a valid AT URI"))
const ListVideosSchema = v.object({ limit: v.optional(v.pipe(v.number(), v.minValue(1), v.maxValue(100))), cursor: v.optional(v.string()),})
const UriOnlySchema = v.object({ uri: AtUriSchema,})
const GetPlaylistSchema = v.object({ uri: AtUriSchema, quality: v.optional(v.picklist(["1080p", "720p", "480p", "240p"])),})
interface Video { uri: string title: string creator: string duration: number createdAt: string}
interface StreamInfo { quality: string width: number height: number bandwidth: number codecs: string frameRate?: number}
interface VideoStreamsResult { uri: string streams: StreamInfo[] audioTracks: Array<{ name: string; default: boolean }>}
const DidSchema = v.object({ did: v.pipe(v.string(), v.startsWith("did:", "Must be a valid DID")),})
interface Profile { did: string handle: string displayName?: string avatar?: string description?: string}
// ============================================================================// Endpoints// ============================================================================
/** * List all VODs from server cache with cursor-based pagination */export const listVideos = endpoint<v.InferOutput<typeof ListVideosSchema>, { videos: Video[]; cursor?: string }>({ input: ListVideosSchema, async handler(args) { const limit = args.limit ?? 50 const cursorDate = args.cursor ? new Date(args.cursor) : null
// Read all videos from server cache const entries = Deno.readDir(CACHE_DIR) const allVideos: Video[] = []
for await (const entry of entries) { if (!entry.isDirectory) continue try { const path = `${CACHE_DIR}/${entry.name}/video.json` const content = await Deno.readTextFile(path) const video = JSON.parse(content) as Video allVideos.push(video) } catch { // Skip entries without valid video.json } }
// Sort by createdAt descending (newest first) allVideos.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime() )
// Filter by cursor if provided let filtered = allVideos if (cursorDate) { filtered = allVideos.filter(v => new Date(v.createdAt) < cursorDate) }
// Apply limit + 1 to check if there's more const hasMore = filtered.length > limit const videos = filtered.slice(0, limit)
// Next cursor is the createdAt of the last video returned const nextCursor = hasMore && videos.length > 0 ? videos[videos.length - 1].createdAt : undefined
return { videos, cursor: nextCursor, } },})
/** * Get available streams for a video (for UI to show quality options) */export const getVideoStreams = endpoint<v.InferOutput<typeof UriOnlySchema>, VideoStreamsResult>({ input: UriOnlySchema, async handler(args) { const manifest = await fetchMasterPlaylist(args.uri)
const streams: StreamInfo[] = (manifest.playlists || []).map((p: any) => ({ quality: p.attributes.RESOLUTION ? `${p.attributes.RESOLUTION.height}p` : "unknown", width: p.attributes.RESOLUTION?.width || 0, height: p.attributes.RESOLUTION?.height || 0, bandwidth: p.attributes.BANDWIDTH, codecs: p.attributes.CODECS, frameRate: p.attributes["FRAME-RATE"], }))
const audioTracks: Array<{ name: string; default: boolean }> = [] const audioGroups = manifest.mediaGroups?.AUDIO || {} for (const tracks of Object.values(audioGroups)) { for (const [name, track] of Object.entries(tracks as Array<any>)) { audioTracks.push({ name, default: track.default }) } }
return { uri: args.uri, streams, audioTracks, } },})
/** * Get an HLS master playlist for a video with video+audio muxed * * Returns a valid HLS master playlist with absolute URLs that can be * directly consumed by video players (hls.js, native Safari, etc.) * * @param uri - AT URI of the video record * @param quality - Optional quality filter (e.g., "1080p", "720p") */export const getPlaylist = endpoint<v.InferOutput<typeof GetPlaylistSchema>, Response>({ input: GetPlaylistSchema, async handler(args) { const manifest = await fetchMasterPlaylist(args.uri) const playlist = generateMasterPlaylist(manifest, { quality: args.quality })
return new Response(playlist, { headers: { "Content-Type": "application/vnd.apple.mpegurl", "Access-Control-Allow-Origin": "*", "Cache-Control": "public, max-age=300", }, }) },})
/** * Get video metadata */export const getVideo = endpoint<v.InferOutput<typeof UriOnlySchema>, Video>({ input: UriOnlySchema, async handler(args) { const atUri = new AtUri(args.uri) const agent = await getAgentForDid(atUri.host)
const response = await agent.com.atproto.repo.getRecord({ repo: atUri.host, collection: atUri.collection, rkey: atUri.rkey, })
const value = response.data.value as { title: string creator: string duration: number createdAt: string }
return { uri: response.data.uri, title: value.title, creator: value.creator, duration: value.duration, createdAt: value.createdAt, } },})
/** * Get thumbnail image for a video (browser-cacheable) * Returns raw JPEG with cache headers */export const getThumbnail = endpoint<v.InferOutput<typeof UriOnlySchema>, Response>({ input: UriOnlySchema, async handler({ uri }) { const hash = await sha256(uri) const thumbnailPath = `/tmp/vod-cache/${hash}/thumbnail.jpg`
if (!(await exists(thumbnailPath))) { return new Response("Not found", { status: 404 }) }
const bytes = await Deno.readFile(thumbnailPath) return new Response(bytes.buffer as ArrayBuffer, { headers: { "Content-Type": "image/jpeg", "Cache-Control": "public, max-age=86400, immutable", "Access-Control-Allow-Origin": "*", }, }) },})
/** * Get sprite sheet for a video (browser-cacheable) * Returns raw JPEG with cache headers */export const getSprite = endpoint<v.InferOutput<typeof UriOnlySchema>, Response>({ input: UriOnlySchema, async handler({ uri }) { const hash = await sha256(uri) const spritePath = `/tmp/vod-cache/${hash}/sprites.jpg`
if (!(await exists(spritePath))) { return new Response("Not found", { status: 404 }) }
const bytes = await Deno.readFile(spritePath) return new Response(bytes.buffer as ArrayBuffer, { headers: { "Content-Type": "image/jpeg", "Cache-Control": "public, max-age=86400, immutable", // 24 hours, immutable "Access-Control-Allow-Origin": "*", }, }) },})
/** * Get VTT file for sprite coordinates (browser-cacheable) * Returns raw VTT with cache headers */export const getVtt = endpoint<v.InferOutput<typeof UriOnlySchema>, Response>({ input: UriOnlySchema, async handler({ uri }) { const hash = await sha256(uri) const vttPath = `/tmp/vod-cache/${hash}/sprites.vtt`
if (!(await exists(vttPath))) { return new Response("Not found", { status: 404 }) }
const content = await Deno.readTextFile(vttPath) return new Response(content, { headers: { "Content-Type": "text/vtt", "Cache-Control": "public, max-age=86400, immutable", // 24 hours, immutable "Access-Control-Allow-Origin": "*", }, }) },})
/** * Check if video metadata is ready (lightweight check) */export const isMetadataReady = endpoint<v.InferOutput<typeof UriOnlySchema>, { ready: boolean }>({ input: UriOnlySchema, async handler({ uri }) { const hash = await sha256(uri) const spritePath = `/tmp/vod-cache/${hash}/sprites.jpg` const ready = await exists(spritePath) return { ready } },})
/** * Batch check metadata readiness for multiple videos */const BatchMetadataSchema = v.object({ uris: v.array(v.string()),})
export const batchMetadataReady = endpoint<v.InferOutput<typeof BatchMetadataSchema>, Record<string, boolean>>({ input: BatchMetadataSchema, async handler({ uris }) { const results: Record<string, boolean> = {} await Promise.all( uris.map(async (uri) => { const hash = await sha256(uri) const spritePath = `/tmp/vod-cache/${hash}/sprites.jpg` results[uri] = await exists(spritePath) }) ) return results },})
/** * Batch fetch profiles for multiple DIDs */const BatchProfilesSchema = v.object({ dids: v.array(v.pipe(v.string(), v.startsWith("did:"))),})
export const batchProfiles = endpoint<v.InferOutput<typeof BatchProfilesSchema>, Record<string, Profile | null>>({ input: BatchProfilesSchema, async handler({ dids }) { const results: Record<string, Profile | null> = {} await Promise.all( dids.map(async (did) => { try { const url = `https://public.api.bsky.app/xrpc/app.bsky.actor.getProfile?actor=${encodeURIComponent(did)}` const res = await fetch(url) if (!res.ok) { results[did] = null return } const data = await res.json() as { did: string handle: string displayName?: string avatar?: string description?: string } results[did] = { did: data.did, handle: data.handle, displayName: data.displayName, avatar: data.avatar, description: data.description, } } catch { results[did] = null } }) ) return results },})
/** * Get user profile from Bluesky (browser-cacheable) */export const getProfile = endpoint<v.InferOutput<typeof DidSchema>, Response>({ input: DidSchema, async handler({ did }) { const url = `https://public.api.bsky.app/xrpc/app.bsky.actor.getProfile?actor=${encodeURIComponent(did)}`
const res = await fetch(url) if (!res.ok) { throw new Error(`Failed to fetch profile: ${res.status}`) }
const data = await res.json() as { did: string handle: string displayName?: string avatar?: string description?: string }
const profile: Profile = { did: data.did, handle: data.handle, displayName: data.displayName, avatar: data.avatar, description: data.description, }
return new Response(JSON.stringify(profile), { headers: { "Content-Type": "application/json", "Cache-Control": "public, max-age=3600", // 1 hour (profiles can change) "Access-Control-Allow-Origin": "*", }, }) },})
// ============================================================================// Share Pages (OG meta tags + redirect)// ============================================================================
const FRONTEND_URL = "https://streamhut.wisp.place"const API_BASE_URL = "https://at-run.mainasara.dev/bundle/did:plc:p3cygo5s7wru2argeci6wfv6/atmosphereconf-vod/latest"
/** * Format duration in nanoseconds to human-readable string */function formatDurationForOG(durationNs: number): string { const totalSeconds = Math.floor(durationNs / 1_000_000_000) const hours = Math.floor(totalSeconds / 3600) const minutes = Math.floor((totalSeconds % 3600) / 60) const seconds = totalSeconds % 60
if (hours > 0) { return `${hours}:${String(minutes).padStart(2, "0")}:${String(seconds).padStart(2, "0")}` } return `${minutes}:${String(seconds).padStart(2, "0")}`}
/** * Generate share page HTML with OG meta tags */function generateShareHTML(options: { title: string description: string imageUrl: string canonicalUrl: string redirectUrl: string type?: "video.other" | "profile"}): string { const { title, description, imageUrl, canonicalUrl, redirectUrl, type = "video.other" } = options
return `<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Streamhut | ${escapeHtml(title)}</title>
<!-- Primary Meta Tags --> <meta name="title" content="Streamhut | ${escapeHtml(title)}"> <meta name="description" content="${escapeHtml(description)}">
<!-- Open Graph / Facebook --> <meta property="og:type" content="${type}"> <meta property="og:url" content="${escapeHtml(canonicalUrl)}"> <meta property="og:title" content="Streamhut | ${escapeHtml(title)}"> <meta property="og:description" content="${escapeHtml(description)}"> <meta property="og:image" content="${escapeHtml(imageUrl)}"> <meta property="og:site_name" content="Streamhut">
<!-- Twitter --> <meta name="twitter:card" content="summary_large_image"> <meta name="twitter:url" content="${escapeHtml(canonicalUrl)}"> <meta name="twitter:title" content="Streamhut | ${escapeHtml(title)}"> <meta name="twitter:description" content="${escapeHtml(description)}"> <meta name="twitter:image" content="${escapeHtml(imageUrl)}">
<!-- Redirect --> <meta http-equiv="refresh" content="0;url=${escapeHtml(redirectUrl)}"> <link rel="canonical" href="${escapeHtml(canonicalUrl)}">
<style> body { font-family: system-ui, -apple-system, sans-serif; display: flex; align-items: center; justify-content: center; min-height: 100vh; margin: 0; background: #0a0a0a; color: #fff; } a { color: #60a5fa; } </style></head><body> <p>Redirecting to <a href="${escapeHtml(redirectUrl)}">Streamhut</a>...</p> <script>window.location.href = ${JSON.stringify(redirectUrl)};</script></body></html>`}
function escapeHtml(str: string): string { return str .replace(/&/g, "&") .replace(/</g, "<") .replace(/>/g, ">") .replace(/"/g, """) .replace(/'/g, "'")}
/** * Share page for videos - returns HTML with OG tags and redirects to frontend */export const shareVideo = endpoint<v.InferOutput<typeof UriOnlySchema>, Response>({ input: UriOnlySchema, async handler({ uri }) { // Fetch video metadata from cache const hash = await sha256(uri) const metaPath = `${CACHE_DIR}/${hash}/video.json`
let video: Video try { const content = await Deno.readTextFile(metaPath) video = JSON.parse(content) as Video } catch { return new Response("Video not found", { status: 404 }) }
// Fetch creator profile for description let creatorName = "Unknown Creator" try { const profileUrl = `https://public.api.bsky.app/xrpc/app.bsky.actor.getProfile?actor=${encodeURIComponent(video.creator)}` const res = await fetch(profileUrl) if (res.ok) { const profile = await res.json() as { displayName?: string; handle: string } creatorName = profile.displayName || `@${profile.handle}` } } catch { // Use default }
const duration = formatDurationForOG(video.duration) const description = `${duration} video by ${creatorName}` const imageUrl = `${API_BASE_URL}/getThumbnail?uri=${encodeURIComponent(uri)}` const canonicalUrl = `${API_BASE_URL}/shareVideo?uri=${encodeURIComponent(uri)}` const redirectUrl = `${FRONTEND_URL}/watch?v=${encodeURIComponent(uri)}`
const html = generateShareHTML({ title: video.title, description, imageUrl, canonicalUrl, redirectUrl, type: "video.other", })
return new Response(html, { headers: { "Content-Type": "text/html; charset=utf-8", "Cache-Control": "public, max-age=3600", }, }) },})
/** * Share page for creators - returns HTML with OG tags and redirects to frontend */export const shareCreator = endpoint<v.InferOutput<typeof DidSchema>, Response>({ input: DidSchema, async handler({ did }) { // Fetch creator profile const profileUrl = `https://public.api.bsky.app/xrpc/app.bsky.actor.getProfile?actor=${encodeURIComponent(did)}` const res = await fetch(profileUrl)
if (!res.ok) { return new Response("Creator not found", { status: 404 }) }
const profile = await res.json() as { did: string handle: string displayName?: string avatar?: string description?: string }
const displayName = profile.displayName || `@${profile.handle}` const description = profile.description || `Watch videos from ${displayName} on Streamhut` const imageUrl = profile.avatar || "" const canonicalUrl = `${API_BASE_URL}/shareCreator?did=${encodeURIComponent(did)}` const redirectUrl = `${FRONTEND_URL}/profile/${encodeURIComponent(did)}`
const html = generateShareHTML({ title: displayName, description, imageUrl, canonicalUrl, redirectUrl, type: "profile", })
return new Response(html, { headers: { "Content-Type": "text/html; charset=utf-8", "Cache-Control": "public, max-age=3600", }, }) },})