Playback
One rule: nobody builds video URLs by hand. You ask
GET /v1/videos/{id}/playback and give hls_url to your player. Our own
dashboard works the same way.
When to call it
Call /playback when the player opens, never earlier. On Strict, a link has
ten minutes to start; a link prepared in advance may be dead by the time the
viewer presses play.
| You call from | Use it for |
|---|---|
| Your web page or app, no key | Public videos on Open or Protected |
| Your backend, with the secret key | Private videos and Strict workspaces |
Optional query parameters:
viewer_id: an opaque id of your own (your user id, a course seat). It travels inside the link and lands in our logs, so a leaked link can be traced back to whoever received it.
What you get
| Field | What it is |
|---|---|
hls_url |
Master playlist for the player. null while rendering or on original. |
original_url |
The untouched original. Same expiry and security as hls_url. |
poster_url |
Still image for the player poster, or null. |
thumbnails_url |
WebVTT sprite for seek previews, or null. |
embed_url |
Our hosted player (iframe). On Strict it only works with this exact URL. |
expires_at |
When the link stops working (ISO 8601), or null on Open. |
security |
The level that applied: open, protected, strict or maximum. |
delivery |
hls or original. See below. |
duration_sec |
Video length in seconds. |
The token lives in the path (/hls/t/{token}/…) because AVPlayer, Chromecast
and native apps cannot send headers. Every playlist and segment under that path
is already signed; you never sign anything yourself.
Original or HLS
Every video keeps its original file exactly as you uploaded or imported it.
We never transcode the original. You choose a delivery when you upload or
import:
hls(default): XonaPlay renders adaptive HLS from the original. Playhls_url.original: no transcoding at all. The original is what you play and download. Playoriginal_url.
While HLS is still rendering, hls_url is null and original_url already
works. original_url supports byte-range requests. With original, hls_url
is always null.
XonaPlay guarantees hls_url on every device. Which players can play
original_url depends on its format: mp4 with H.264 plays almost everywhere,
mkv and HEVC may not. That choice is yours.
Without HLS, poster_url and thumbnails_url are null, unless the owner set
a custom poster.
Videos stored on your own server (BYO) return original_url: null.
To move a video uploaded with original to HLS, send PATCH /v1/videos/{id}
with "delivery": "hls". It works one way only, and original_url keeps
working in the meantime.
Expiry
| Level | The link works for |
|---|---|
| Open | Forever |
| Protected | Video length + 1 hour (2 to 24 hours) |
| Strict | 10 minutes to start, then video length + 30 minutes (1 to 24 hours) |
A paused tab can outlive its link. That is expected: the player recovers.
Reload rule
Every recipe in these docs follows the same four steps:
- On any fatal player error (a 403 on a segment, a network error, a decode
error), call
/playbackagain — at most once per minute — and resume at the same second. - If the response has
hls_url, load it and seek back. - If the response is an error, show its
message. Offer a Reload button only when thecodeis retryable (see the table below). - If the player fails again within 60 seconds, do not call anything: show Reload, which goes back to step 1.
The decision comes from the JSON of /playback, which every HTTP client can
read. Do not rely on HTTP headers from the video host: AVPlayer cannot read
them.
Errors
Errors use the same envelope as the rest of the API:
{ "status": "error", "code": "KEY_REQUIRED", "message": "This video needs a secret key." }code |
HTTP | Retry? |
|---|---|---|
NOT_FOUND |
404 | No |
KEY_REQUIRED |
401 | No. Call it from your backend with the key. |
INVALID_KEY |
401 | No |
DOMAIN_NOT_ALLOWED |
403 | No |
WORKSPACE_UNAVAILABLE |
403 | No |
ACCESS_DENIED |
403 | No |
VIDEO_NOT_READY |
409 | Yes, later |
LIVE_NOT_ACTIVE |
409 | Yes, later |
RATE_LIMITED |
429 | Yes, after Retry-After |
| 5xx or a response without JSON | — | Yes |
The video host (not /playback) answers a dead link with a plain 401/403/503
and an X-Xona-Error header — TOKEN_EXPIRED, TOKEN_REVOKED,
NETWORK_CHANGED, MAXIMUM_UNAVAILABLE… It is there for debugging; your
player just follows the reload rule, and MAXIMUM_UNAVAILABLE (503) is
retryable like the rest.
Live
Live streams use the same contract:
curl https://api.xonaplay.com/v1/live/{STREAM_ID}/playbackLIVE_NOT_ACTIVE means the stream is not on air yet. Retry later or show a
countdown.