guides

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 /v1 provisions the same input and hands back the same SRT credential in ONE call on the public API, authenticated with an sk_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 /v1 for 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 /v1 carries.

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.
  • deviceLabel is 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.
  • autoSeat seats 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

  1. Build a client — the transport map, native OAuth + PKCE, and the studio-control WebSocket.
  2. Stream to a live input — the /v1 path when the feed should be a plain input rather than a participant, driven by an API key instead of a user login.
  3. Drive the studio canvas — control layout, sources, presets, and scenes over the studio WebSocket while you publish.
  4. Monitor the program output — watch the composited program feed over WHEP.
  5. SRT: the streamid contract — the streamid grammar, the 512-byte cap, and how publishes fail.
  6. Stream paths — the path grammar shared by every media transport.
  7. Authentication & session tokens — the user-login (OIDC + PKCE) and media-token models in full.