Shukr Docs
Shukr Public Media Service

Playback & streaming

Get HLS/DASH playback URLs for video assets, batch-resolve many, and serve manifests, segments, embeds, and thumbnails.

Video (stream) assets are delivered as adaptive-bitrate HLS and DASH. There are two layers:

  1. Authenticated playback endpoints (/api/v1/media/...) — resolve the playback URLs for an asset you own.
  2. Public delivery endpoints (/api/v1/playback/...) — the URLs a player actually hits for manifests, segments, the embed page, and thumbnails. These are not behind the auth guard; access is governed by the asset's visibility.

Playback only returns usable URLs once stream_status is ready (lifecycle).


Get playback URLs

GET /api/v1/media/{id}/playback200 OK

What it is — Returns freshly signed HLS / DASH / embed / thumbnail URLs for one video asset.

When to use it — Right before playing a single video (a place walkthrough, a user video post, a feedback clip). Bunny/Cloudflare URLs are re-signed live on every call, so fetch them just before playback; on-prem returns stable proxy URLs.

How to use it — GET by id, then feed playback.hls to an HLS player, playback.dash to a DASH player, or drop playback.embed into an <iframe>. Returns 400 until stream_status is ready.

Scope — Token owner or service key. Stream (video) assets only — file assets return 400. Errors: 400 / 401 / 403 / 404 / 500.

curl https://media.example.com/api/v1/media/770e8400-e29b-41d4-a716-4466554400ff/playback \
  -H "Authorization: Bearer $ACCESS_TOKEN"

Batch playback URLs

POST /api/v1/media/playbacks200 OK

What it is — Resolves freshly signed playback URLs for many video assets at once, with partial success.

When to use it — Rendering a grid or feed of videos in one round-trip — e.g. all video posts on a place, or a mix of clips where you don't want a request each.

How to use it — POST asset_ids (≤ 50) and read each item's playback and error. Partial success: file assets come back with error: "not_stream"; assets still transcoding with error: "not_ready".

ScopeUser token (own assets only) or service key (owner_user_id required).

Request body

FieldTypeRequiredNotes
asset_idsuuid[]1–100 ids (deduped). Capped by BATCH_MEDIA_MAX_ASSETS (default 50).
owner_user_idstring (1–255)⚠️Required for service-key callers.

Per-item error is one of not_found, forbidden, not_ready, not_stream, or null.

curl -X POST https://media.example.com/api/v1/media/playbacks \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "asset_ids": ["770e8400-e29b-41d4-a716-4466554400ff"] }'

Public delivery endpoints

These are the URLs a video player requests. They live under /api/v1/playback/{assetId} and, for on-prem streams, are rewritten by the service so segment requests route back through it (and get signed R2 redirects). They return raw bodies — not the JSON envelope — and are not behind the auth guard; a private asset is refused with the standard error envelope (401 / 403).

EndpointReturnsNotes
GET /playback/{assetId}/hls.m3u8HLS master playlist (application/vnd.apple.mpegurl)Rewritten; Cache-Control: public, max-age=60.
GET /playback/{assetId}/hls/*Child manifest or 302 to a presigned R2 segmentSub-resources referenced by the master playlist.
GET /playback/{assetId}/dash.mpdDASH manifest (application/dash+xml)Rewritten; max-age=60.
GET /playback/{assetId}/dash/{filename}302 redirect to a presigned R2 segmente.g. chunk-video-00001.m4s.
GET /playback/{assetId}/embedIframe-friendly HTML player (text/html)On-prem embed page; max-age=300.
GET /playback/{assetId}/thumb.jpg302 redirect to a presigned R2 thumbnailPoster image.
ffplay "https://media.example.com/api/v1/playback/770e8400-e29b-41d4-a716-4466554400ff/hls.m3u8"

Manifests are cached for 60 seconds and segments/thumbnails are served via 302 redirects to short-lived presigned R2 URLs — so players fetch bytes straight from storage, not through the service.

On this page