Skip to content

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}
- Routed through this app's own nginx/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.

import { cdnUrl } from "@/utils/cdn"
cdnUrl(storageKey)   // → `${VITE_CDN_HOST}/${key}`
- Points at 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 itPlayoutAdmin.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
All three should return 200 OK for a ready media item.