Assets Guide — Serving Media URLs
How a storageKey (or thumbnailKey/manifestKey/smilKey) actually turns into a URL something can fetch, and which of the two mechanisms to use where.
Usage today is genuinely sparse — only a handful of files reference either mechanism at all (utils/cdn.js, the playlist pages, MediaPickerSheet.jsx, MediaDetailPanel.jsx, MediaList.jsx). Most of the app never needs a media URL directly (channel/distribution config deals with streamer config, not media bytes). This guide is the reference for the two places that do, and for anything new that needs to.
Two separate mechanisms — don't conflate them
1. Same-origin, in-app preview — VITE_ASSET_BASE_URL
${VITE_ASSET_BASE_URL}/assets/${bucket}/${storageKey}
/assets/ → proxy_pass ${ASSET_SERVING_URL} → cproxy_vod → SeaweedFS.
- Use for: media library thumbnails/previews, anything rendered inside PlayoutAdmin itself where a same-origin request is fine.
- Set at Docker build time (VITE_ASSET_BASE_URL build-arg), e.g. http://localhost:15173 locally.
2. Public / shareable / CDN link — VITE_CDN_HOST
import { cdnUrl } from "@/utils/cdn"
cdnUrl(storageKey) // → `${VITE_CDN_HOST}/${key}`
cproxy_vod's passthrough route directly (bypasses this app's nginx entirely).
- Use for: anything that needs to work outside PlayoutAdmin's own origin — external players, shared links, direct <video src>/<img src> where you don't want a proxy hop through this app.
- cdnMp4Url/cdnHlsUrl/cdnSmilUrl are the same function, named for call-site clarity (storageKey/manifestKey/smilKey respectively).
- ⚠️ Double-check this port before relying on it — PlayoutAdmin.yml currently sets VITE_CDN_HOST to cproxy_vod's API port (9221, config/traffic/purge), not its proxy/content-serving port (9222). Confirm which one your deployment actually needs before assuming asset links resolve correctly through it.
What NOT to use — CoreAPI's own ServeAsset
GET /assets/* (and /assets/*/presign) still exist and work — direct minio.GetObject/StatObject/presigned-URL, no cproxy involved — but this is the old, pre-cproxy data path. nginx.conf.template's own comment says /assets/ now proxies to cproxy_vod instead of CoreAPI specifically to keep CoreAPI + its nginx reverse proxy out of the hot path for serving bytes. Don't add new code that calls CoreAPI's ServeAsset directly — route through VITE_ASSET_BASE_URL (mechanism 1 above) so cproxy stays the one actually serving bytes.
Quick reference — which key, which mechanism
| Key | Typical use | Mechanism |
|---|---|---|
storageKey |
Original file — direct playback, media library preview | Either, depending on same-origin vs. external |
thumbnailKey |
Grid/list thumbnails | Mechanism 1 (same-origin) |
manifestKey |
HLS master playlist — browser preview of a transcoded ladder | Either |
smilKey |
Not a URL you fetch from the frontend at all — this is what the Castis Streamer reads server-side for scheduled/fallback playlists | N/A |
Verifying end-to-end (local dev)
# object actually landed in SeaweedFS
curl -I http://localhost:8333/playtelly/food/video/kitchen.mp4
# cproxy_vod passthrough (what VITE_CDN_HOST points at)
curl -I http://localhost:9222/playtelly/food/video/kitchen.mp4
# this app's own nginx → cproxy (what VITE_ASSET_BASE_URL points at)
curl -I http://localhost:15173/assets/playtelly/food/video/kitchen.mp4
200 OK for a ready media item.