Contribute a camera from a phone (SRT)
A phone can join a TRaX studio as a camera source and publish its camera and mic over SRT. The phone is not compositing anything — the cloud encoder owns the live program, so the show keeps running even if the app backgrounds, the screen locks, or the signal drops for a moment. This is the ingest path the TRaX iOS app uses, and it is open to your own client.
The model in one sentence: a contributing phone is a participant whose device
source has transport = srt. Media rides SRT; identity and control ride the
studio WebSocket. You reuse everything the platform already does with
participants — the person grouping, the mixer strip, the canvas tile, mute and
kick — instead of inventing a new kind of source.
There are now two ways to do this, and this is the older one. Go live from a phone over
/v1provisions the same input and hands back the same SRT credential in ONE call on the public API, authenticated with ansk_live_key or an OIDC bearer like everything else in/v1. It is the same server-side path — the same source row, the same canvas tile, the same reconnection behaviour — so the two are interchangeable rather than parallel implementations.Prefer
/v1for a new client. Stay here when you also need the studio WebSocket for identity and control, or the walk-up QR guest flow, neither of which/v1carries.
What you talk to
Contribution is a first-party flow on the studio control plane (the studio
API), not the /v1 REST gateway. Three hosts are involved:
| Development | Production | |
|---|---|---|
| Studio API (control plane) | https://studio-api-dev.traxstreaming.live |
https://studio-api.traxstreaming.live |
| Identity (OAuth login) | https://auth-dev.traxstreaming.live |
https://auth.traxstreaming.live |
| SRT ingest | srt://ingest.traxstreaming.live:8890 |
srt://ingest.traxstreaming.live:8890 |
Always publish to the exact srtIngestUrl the provisioning response hands you
rather than hardcoding the ingest host.
Authenticate as the user
Every call below carries the user's access token: Authorization: Bearer <jwt>,
the OAuth 2.0 + PKCE token described in Build a client.
It is the same bearer you use for the studio WebSocket and for /v1. The one
exception is the walk-up QR guest path, which uses a scoped greenroom session
instead of an account (see Walk-up guests below).
1. Provision an SRT ingest
Ask the studio for a scoped ingest, identifying the device that is about to publish:
POST /api/v1/studios/{studioId}/contribute/ingest
Authorization: Bearer <jwt>
Content-Type: application/json
{ "deviceId": "9C1B...stable-per-install-id", "deviceLabel": "EJ's iPhone" }
deviceId(required) is a stable id your app mints once and keeps in the Keychain / Keystore. It is what makes a reconnection resolve to the same camera tile instead of a new one.deviceLabelis the human name shown in the studio ("EJ's iPhone").
You must be a member of the studio (any role). If contribution is not enabled
for the studio, the endpoint returns 404.
The 201 response is the descriptor you publish with:
{
"source": { "id": "mc-…", "name": "EJ's iPhone", "type": "mobile_contrib", "…": "…" },
"path": "inputs/mobile-contrib-mc-8f3c…",
"streamId": "publish:inputs/mobile-contrib-mc-8f3c…:<streamKey>:<streamKey>",
"srtIngestUrl": "srt://ingest.traxstreaming.live:8890?streamid=publish:inputs/mobile-contrib-mc-8f3c…:<streamKey>:<streamKey>",
"autoSeat": true,
"autoGoLive": true,
"deviceId": "9C1B…"
}
| Field | What it is |
|---|---|
srtIngestUrl |
The full SRT URL to publish to — streamid included. Use it verbatim. |
streamId |
The SRT streamid on its own, if your SRT stack takes host and streamid separately. |
path |
The stream path this contribution lives at (inputs/mobile-contrib-<id>). |
autoSeat |
Whether the source auto-seats on the canvas when it starts publishing (see below). |
autoGoLive |
Deprecated alias of autoSeat — same value, removed in a future release. Read autoSeat. |
source |
The participant device source the feed is bound to — it appears in the studio's Sources immediately, offline until SRT flows. |
2. Publish over SRT
Open an SRT publisher (libsrt on iOS/Android, or ffmpeg from a workstation)
to srtIngestUrl. The credential is entirely inside the streamid:
publish:<path>:<streamKey>:<streamKey>
The whole capability is the scoped per-path stream key — user and password positions are both the key. There is no JWT in the streamid, which keeps it well under libsrt's 512-byte cap. Do not URL-encode the streamid; libsrt wants the literal colons. See SRT: the streamid contract for the field grammar and the failure modes.
ffmpeg sanity check from a laptop:
ffmpeg -re -i camera.mp4 -c:v libx264 -c:a aac -f mpegts \
"srt://ingest.traxstreaming.live:8890?streamid=publish:inputs/mobile-contrib-mc-8f3c…:<streamKey>:<streamKey>"
What happens on the studio side
- The feed auto-seats as a participant device source under the contributing person, inheriting their name, avatar, and color. No host click is needed.
- The cloud encoder is the authority. If the phone backgrounds or loses signal, the source goes offline and the program falls back to a slate; when the phone comes back it re-seats the same tile. The broadcast itself never depended on the phone.
autoSeatseats the source, it does not start the broadcast. Bringing a camera onto the canvas is separate from going live to your destinations. The studio's master go-live control stays authoritative — a contributing phone can never force the program on air.
Reconnection and the device registry
Each phone is a durable device, not a per-connection source. List a user's devices and their history:
GET /api/v1/contribute/devices
Authorization: Bearer <jwt>
→ 200 { "devices": [ { "deviceId": "9C1B…", "label": "EJ's iPhone",
"lastSeenAt": "…", "connectionCount": 12, "…": "…" } ] }
Because the source id is derived from the studio plus the durable device, the
same deviceId reconnecting into the same studio re-provisions the same
path and re-seats the same tile — layout and position survive drops and
app restarts.
3. Release the input when you're done
When the phone is finished contributing, hand its input back to the studio:
DELETE /v1/studios/{studioId}/contribute
Authorization: Bearer <token>
Content-Type: application/json
{ "deviceId": "9C1B…" }
→ 204 No Content
This is the teardown twin of the provision call. It stops the cloud encoder
from re-arming a pull against a path the phone is no longer publishing to. Call
it from applicationWillTerminate (or your platform's equivalent) so a clean
exit gives the session back instead of leaving it to time out.
Keep-source. Release deactivates the input; it does not delete it. The
source row, its media path, and its canvas placement survive, so a later
provision with the same deviceId re-seats the same tile exactly like a
reconnection does. Use DELETE /v1/studios/{studioId}/sources/{sourceId} when
you actually want the input removed from the studio.
Safe from a background teardown. The call answers 204 on everything that
is merely "nothing to do" — an unknown deviceId, one already released, one
that never published, or an empty body. A shutting-down app has no screen to
show an error on and no chance to retry, so a teardown never fails on request
framing it cannot fix. The only answers you must not ignore are 403 (the
deviceId belongs to another user), 401 (no auth), and 5xx (the release
genuinely did not happen).
Unlike provisioning, release is not gated by the deployment's contribute switch: that flag stops new sessions being created, but a session that already exists must always be releasable.
Walk-up guests (QR)
A host can let someone contribute without an account. The host mints a short-lived join code (admin role required):
POST /api/v1/studios/{studioId}/contribute/qr
Authorization: Bearer <jwt>
→ 201 {
"studioId": "…",
"token": "…",
"deepLink": "traxstreaming://contribute?studio=…&token=…",
"joinUrl": "https://studio-api.traxstreaming.live/m/contribute?studio=…&token=…",
"expiresAt": "2026-08-11T20:15:00Z"
}
Render the token (or the deep link) as a QR code. The token is the only way
in — there is no enumeration — and it expires in 15 minutes with a small use
cap; re-mint to rotate. A guest scans it, enters a name, and their app
provisions an ingest against the greenroom session:
POST /api/v1/greenroom/contribute/ingest
Content-Type: application/json
Cookie: <greenroom session established by the join token>
{ "deviceId": "…", "deviceLabel": "Guest — Sam" }
The response is the same SRT descriptor as the authenticated path, and the guest auto-seats as a participant device source. Guest ingests are rate-limited, and the guest is kickable and forgotten on leave.
Control the studio while you publish
Contribution is only the camera leg. The same app holds a WebSocket to the studio for tally ("you're live"), mute, flip-camera, and stop — bidirectional control that has nothing to do with the media plane. That surface, and how to authenticate it, is in Build a client → Controlling a studio.
Where to go next
- Build a client — the transport map, native OAuth + PKCE, and the studio-control WebSocket.
- Stream to a live input — the
/v1path when the feed should be a plain input rather than a participant, driven by an API key instead of a user login. - Drive the studio canvas — control layout, sources, presets, and scenes over the studio WebSocket while you publish.
- Monitor the program output — watch the composited program feed over WHEP.
- SRT: the streamid contract — the streamid grammar, the 512-byte cap, and how publishes fail.
- Stream paths — the path grammar shared by every media transport.
- Authentication & session tokens — the user-login (OIDC + PKCE) and media-token models in full.