Streaming / Video Overview
Provider-backed video transcoding and HLS playback for property tours and other long-form video.
The Streaming Service transcodes uploaded videos into adaptive-bitrate HLS plus downloadable MP4 renditions, then notifies caller services via webhook when the asset is ready to play. Today it's backed by Mux; the dispatcher is built so a second provider (Cloudflare Stream, Bunny.net, etc.) can be added without changing the caller-facing API.
What it does
- Transcoding — phone-camera videos arrive in wildly different codecs (iPhone HEVC in
.mov, Android VP9 in.webm, WhatsApp's.mp4). The streaming service hands the file to Mux which normalises everything toh.264 + AACHLS with multiple bitrate variants. - Adaptive bitrate playback — the player picks 480p on mobile, 1080p on desktop, automatically.
- Thumbnails — Mux auto-generates a still-frame thumbnail and an animated GIF preview.
- Faststart MP4 — for share-by-link or fallback when HLS isn't available.
What it does not do
- AI upscaling / denoise / "enhancement" — Mux is a transcoder, not an enhancer. If you need to repair a low-quality source, that's a different vendor (Topaz Video AI, Adobe Firefly).
- Live streaming — the current configuration is on-demand only.
- Signed / private playback — assets use
playback_policy: ['public']for now. Signed playback can be added when paywalled content lands.
Auto-orchestration with Storage
If a caller service uses the Storage service to upload a video (via POST /api/v1/storage/media/upload or the sign-upload + register direct path), the storage dispatcher automatically submits the video to streaming. Callers don't need to call the streaming API explicitly — the storage upload response includes streamingAssetId for follow-up lookups.
For callers that own their own upload path (e.g. Lettings uploading directly to its own Supabase bucket without going through PlatformXe storage), call POST /api/v1/streaming/video/submit with the public URL.
Scopes
| Scope | Required for |
|---|---|
streaming:submit | POST /api/v1/streaming/video/submit |
streaming:read | GET /api/v1/streaming/video/assets/[id] |
Both are granted by default to lettings and concierge (see Scope Catalog).
Pipeline at a glance
Caller PlatformXe Mux
│ │ │
│ POST submit │ │
├─────────────────►│ │
│ { sourceUrl } │ HEAD sourceUrl │
│ │ pre-check │
│ │ ─ ─ ─ ─ ─ ─ ─ ► │
│ │ POST /video/v1/ │
│ │ assets {input} │
│ ├──────────────────►│
│ │ { id } │
│ │◄──────────────────┤
│ 201 { asset, │ │
│ status:pending}│ │
│◄─────────────────┤ │
│ │
│ ── Mux transcodes asynchronously ── │
│ │
│ POST webhooks/mux │
│ ◄───────────────────┤
│ │
│ ── PlatformXe relays via │
│ WebhookAPI to subscribers ── │
│ │
│ POST /api/webhooks/<your-app>/streaming
│ ◄─── { event: 'streaming.video.ready',
│ muxPlaybackId, durationSeconds, … }
Webhook events
| Event | When | Payload |
|---|---|---|
streaming.video.ready | Mux finished transcoding successfully | Full StreamingVideoEventPayload with muxPlaybackId populated |
streaming.video.errored | Mux rejected the source (bad codec, truncated file, content-moderation) | Same shape with errorMessage populated |
See Submit a video for the request schema and Playback URLs for the URLs you can build from a muxPlaybackId.
Failure modes
- Mux credentials missing on PlatformXe —
submitsucceeds but the row is created withstatus: 'errored'and an error message. No call to Mux is attempted. - Source URL unfetchable —
submitreturns400withSOURCE_UNREACHABLE(or related probe codes) before the Mux call when the soft probe has positive evidence. CallPOST /api/v1/media/probein strict mode earlier in your flow for partner-facing errors. - Webhook missed — the next
GET /assets/[id]poll detectsstatus: 'pending'and refreshes from Mux's authoritative state, recovering automatically. - Provider outage mid-transcode — Mux retries internally; if it gives up, fires
streaming.video.erroredwhich PlatformXe relays. Caller decides whether to resubmit.