streams.assets.list({ orgId, page, perPage }), which joins
each stream_asset row with its 24-hour play count from the
clutchcall.stream_asset_play_1m_q rollup in ClickHouse. The screen
re-queries the list every 30 s, so state changes appear without a reload.
This page explains what happens to an asset between upload and playback, and
which URL to hand to which consumer.
Asset states
The playback endpoints modal is only meaningful for a
ready asset. Open an
asset that has no playback_id and the modal says so explicitly — “No playback
id yet — the asset is still <status>” — instead of rendering URLs that would
resolve to nothing.
How an asset is created
The library holds both recordings and direct uploads. Uploads go through a resumable TUS2 client against the control-plane endpoint/tus/uploads. Because the upload is tracked server-side rather than
in the browser tab, an upload started elsewhere still shows up here:
streams.tusUploads.inflight({ orgId }) returns the in-flight rows and the
screen polls it every 5 s while uploads are moving.
Each in-flight row exposes:
When the transfer completes, the upload becomes a
stream_asset row and moves
to processing. Org-level VOD defaults — the default poster position and the
auto-transcode flag — are applied at that point, so change them before you
upload if you want them to take effect on the new asset.
streams.assets.remove({ orgId, id }) deletes an asset from the library.
Passthrough versus auto-transcode (ABR)
Auto-transcode is an org-wide opt-in, stored instream_org_settings and read
and written through streams.settings.get / streams.settings.update:
Passthrough is the fast path: there is no transcode to wait for. ABR costs
processing time on ingest and buys adaptive delivery for viewers on variable
networks.
The console decides which protocols to advertise from the asset’s probe
dimensions: an asset with a
width or height has been probed and carries a
ladder, so HLS and DASH links render. A passthrough upload has neither, so those
links are suppressed and the modal tells you why rather than offering URLs that
would 404.
Poster frames: org default and per-asset override
A poster position is stored as a fraction of the asset’s duration, not as an absolute timestamp, so it stays valid regardless of how long the asset is.- Org default —
defaultThumbnailTimestampPctin the VOD defaults bar. It applies to every new upload. Saved withstreams.settings.update({ orgId, defaultThumbnailTimestampPct }). - Per-asset override — the slider on each asset card, in steps of 0.01 over
the range 0–1. Saved with
streams.assets.setThumbnail({ orgId, id, thumbnailTimestampPct }), which persiststhumbnail_timestamp_pcton the row.
setThumbnail returns a requeued flag:
An asset renders a poster on its player only once
thumbnail_key is set on the
row. Until then the player starts with a black frame.
The playback endpoints
Every URL for a ready asset hangs off itsplayback_id. With
base = https://streams.clutchcall.dev/api/streams/playback/<playback_id>:
HLS and DASH ride the same CMAF segments that auto-transcode produces, which is
why they appear and disappear together.
There is also a hosted player at
https://streams.clutchcall.dev/api/streams/embed/<playback_id>. This is
the “copy a link and it plays anywhere” URL: it negotiates the transport ladder
(MoQ → HLS → MP4) for the viewer’s device and network, so it works for
passthrough assets that do not have every protocol. Prefer it over hand-picking
a protocol unless you are integrating a specific player.
The console also offers the matching embed snippet:
external_asset_id,
falling back to the internal id) is what you pass to the streams.assets.*
procedures. The playback id is what appears in public URLs.
Signed URLs and token lifetime
Each asset carries arequire_signed_urls flag, toggled with:
?tok=<token>. The console mints one
with a one-hour TTL when it opens the endpoints modal for a gated asset, and
uses it for both the inline player and the copy-link fields. If the mint fails,
the modal surfaces the error and offers a retry rather than waiting forever.
For a one-off share of the source file, streams.assets.playbackUrl({ orgId, id })
returns a complete signed MP4 URL. The console copies it to the clipboard and
notes that it expires in 10 minutes.
Never embed a long-lived control-plane API key in a page in order to fetch these
tokens from the browser — mint the token on your server and pass it down. See
Authentication.
Play telemetry: startup, rebuffer and position samples
The in-console player is a real player and reports real watch data. It POSTs batches of samples to<base>/events as { "rows": [ … ] }, which land in the
stream_asset_play_sample table in ClickHouse and feed the analytics screens.
Each sample row:
Emission points:
- Heartbeat — one sample every 10 s, but only while the video is genuinely playing (not paused, not ended, playhead past zero).
- Ended — a final sample when playback reaches the end.
- Teardown — a last sample when the player unmounts, if anything happened at all (a startup time, accumulated rebuffer, or a non-zero playhead).
navigator.sendBeacon where available, falling
back to a keepalive fetch, so the final numbers survive a tab close. Every
send is best-effort: a failed POST is swallowed and never interrupts playback.
Transport selection in the player
The inline player picks its transport in this order:- Native HLS, where the browser reports it can play
application/vnd.apple.mpegurl— Safari takes this path. - hls.js, lazy-imported so it stays out of the initial bundle, loading the
CMAF
vod/master.m3u8. - Progressive MP4 at
<base>/source, used when hls.js is unsupported, when the import fails, or when hls.js raises a fatal error mid-playback — in which case the player tears down hls.js and re-points the element at the MP4.
When playback is unavailable
Related
- Telemetry — the other operational data streams
- Authentication — API keys, relay tokens, signed playback
- Modalities overview

