utd_stream_sdk 0.1.0
utd_stream_sdk: ^0.1.0 copied to clipboard
Headless LiveKit-based audio/video SDK for Flutter (audio call, video call, live stream, audio room) as logic-only sessions with no UI — build your own interface.
UTD Stream SDK (headless) #
LiveKit-based audio/video for Flutter with no UI. You get the logic — sessions, state, and
control methods — and build your own interface. One set of credentials (app_id + app_key) works
for every product type your project enabled; the type is chosen per session, never per credential.
The four types #
| Type | What it is | Session |
|---|---|---|
audio_call |
1:1 audio | UTDCallSession |
video_call |
1:1 audio + video | UTDCallSession |
live_stream |
one host, unlimited viewers, engine-uncapped co-publishers | UTDLiveStreamSession |
audio_room |
seat-based speakers + listeners | UTDAudioRoomSession |
Install #
dependencies:
utd_stream_sdk: ^0.1.0
Quick start #
final client = UTDStreamClient(appId: '1234567890', appKey: 'a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6');
// ── Audio room ──
final room = await client.joinAudioRoom(identity: 'u1', roomName: 'lobby', asHost: true, seatCount: 8);
await room.takeSeat(0); // become a speaker (engine grants publish, mic turns on)
room.remoteParticipants.addListener(rebuildUI);
// ── Live stream ──
final live = await client.joinLiveStream(identity: 'host1', roomName: 'show', asHost: true);
await live.goLive(); // host publishes camera + mic
await live.inviteToStage('u2'); // invite a viewer to co-publish — no engine count limit
// viewers join as `audience` (subscribe only) and can raise a hand:
// await live.requestStage();
// ── 1:1 call ──
await client.authenticate(identity: 'u1'); // mint a bearer (calls have no room to mint first)
final call = client.callSession(UTDStreamType.videoCall);
final created = await call.start(calleeIdentity: 'u2');
// callee side: client.authenticate(identity:'u2'); await call.accept(created.callId);
live_stream: roles & the stage #
live_stream is the seatless, unbounded broadcast. There is no seat grid — seat_count /
seat_mode / host_seat are ignored. The engine creates the LiveKit room uncapped
(max_participants = 0) so viewers are unlimited; your app decides how many co-publisher tiles to
surface (UTDLiveStreamSession.stage).
Publishing is decoupled from moderation. Four roles:
| Role | Publishes? | Moderates? | How you get it |
|---|---|---|---|
host |
yes | yes | the verified room owner (asHost: true + roomOwnerId) |
guest |
yes | no | a host-invited co-publisher (inviteToStage) |
admin |
no | yes | a host-promoted moderator — never on camera |
audience |
no | no | the default for everyone else |
Every non-owner joins as audience. Roles are server-authoritative: the engine clamps a
non-owner's claimed role to audience, so a viewer can't self-grant publish or moderation by asserting
role: 'host' — promotion happens server-side only, and the SDK merely reacts to the resulting
permission change (it never elevates itself).
The stage is the publisher set (host + guests). The stage REST surface moves people between
audience ↔ guest only — it never touches the admin tier:
UTDLiveStreamSession |
Engine endpoint | Who |
|---|---|---|
refreshStage() |
GET /api/v1/rooms/:name/stage → { members:[{identity,name,role}] } |
anyone |
inviteToStage(id) |
POST /api/v1/rooms/:name/stage/add { target_identity } |
host/admin |
removeFromStage(id) |
POST /api/v1/rooms/:name/stage/remove { target_identity } |
host/admin |
leaveStage() |
POST /api/v1/rooms/:name/stage/leave |
self step-down |
requestStage() |
POST /api/v1/rooms/:name/stage/request |
viewer raise-hand |
The actor is resolved from the per-user bearer (the verified user), not a client-asserted body field.
Adding an admin to the stage is refused (409) — moderation and publishing stay disjoint. Stage ops
on a seated or non-live_stream room are 400; if live_stream isn't enabled for the project they
are 403.
Moderator (admin) promotion is owner-only and not part of this SDK's stage API. It uses the
engine role route PUT /api/v1/rooms/:name/participants/:identity/role { role: 'admin' } (owner-only),
which grants moderation but not publish. Drive it through your moderation backend / the full kit if
you need it.
Data messages the engine broadcasts on the room: _stage_update (the current roster, mirrors the
stage notifier) and _stage_request (a viewer raised a hand — delivered to the host/admins). Read
them off the raw events stream.
State (build any UI from these) #
Every session exposes ValueListenables — connected, canPublish, micEnabled, cameraEnabled,
remoteParticipants — plus a raw events stream of LiveKit RoomEvents. Permission changes
(seat/stage promote-demote) are server-authoritative: the SDK reacts to them, it never elevates
itself.
Architecture #
UTDStreamClient— entry point; holds credentials, mints tokens, produces sessions.UTDApi/UTDApiClient— engine REST (mint host usesX-App-Key; privileged host uses the server-mintedAuthorization: Bearer). No secret ever ships in the app.UTDRoom— headless LiveKitRoomwrapper (connect, publish, events).session/*— one control surface per product type.
Not included (host-app responsibility) #
- Incoming-call delivery. Receiving a ringing call (the
call_id) arrives out-of-band via your push pipeline or the engine presence WebSocket; wire it, then callUTDCallSession.accept(callId). - UI, by design.