UTD Live Room Kit
A Flutter package for building video live-room experiences, powered by the UTD Stream Engine. It puts a host camera on a full-bleed stage with up to 3 invited co-publisher tiles — give it the required data and you get a fully interactive live room with no extra UI code.
The engine treats a live room as the live_stream product type: an unbounded,
seatless stage. There is no seat grid and no participant cap — anyone can
watch. The host invites co-publishers onto the stage (audio/video publish),
while the room owner promotes moderators. The kit only caps how many guest
tiles it draws (maxGuestTiles, default 3); the engine itself is
uncapped.
Roles are server-authoritative. Only the verified room owner joins as the publishing
host; everyone else joins asaudience(watch + chat). The engine clamps any non-owner's claimed role toaudience— a client cannot self-grant publish or moderation. Publishing and moderation are decoupled: being added to the stage grants publish only; being promoted toadmingrants moderation only (admins are never on camera).
Features
- Drop-in live-room UI — host camera + up to 3 co-publisher video tiles from required data alone.
- Stage flow — the host invites viewers onto the stage to co-publish;
viewers raise-hand (
stage/request) and the host adds them. No server-side queue — a request is a transient_stage_requestnotification to host/admins. - Owner-promoted moderators — the owner promotes configured
adminIdstoadmin(moderate-only, not on camera) via the engine role endpoint. - Host force-control — host can mute/unmute or stop a co-publisher's camera/mic, and remove them from the stage.
- Media controls — microphone & camera with reactive state (Bluetooth-preferring audio routing).
- Real-time chat — data-channel messages with batching and dedup.
- Reconnection — tiered: light sync (<15s), full sync (<60s), default force-exit (>60s).
- Minimize / PiP — minimize to a floating overlay; optional Android OS Picture-in-Picture.
- Video effects — pairs with
utd_video_effects_kitviaUTDLiveRoomConfig.buildVideoProcessor(a video track processor) for live filters & beauty effects.
Getting started
Add the dependency:
dependencies:
utd_live_room_kit: ^1.4.0
Audio engine setup (required)
The native media engine is built once per process, and the first thing
that touches it decides its audio profile. Claim the media profile before any
screen runs — first line of main():
import 'package:utd_media_client/utd_media_client.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await UtdmClient.initialize(mediaAudioProfile: true);
runApp(const MyApp());
}
Without this, any earlier WebRTC touch (a camera preview, a video renderer,
device enumeration) locks the process into the call profile — earpiece
routing, call volume — and no later call can undo it. If you ever see
[UtdmClient] the media engine was already initialized in the console,
something ran before this line; report it to us with the log.
Credentials — pass your appKey (no backend required)
Ship
appId+appKey(the project's publishable app key, from the UTD console). The kit mints tokens directly from the engine withX-App-Key; the engine signs the returned per-useruser_tokenwith the projectserver_secretserver-side, so the secret never ships in the app. The kit uses thatuser_tokenas theAuthorization: Bearerfor all in-room calls. No token server to stand up.appKeycan't sign tokens or call the S2S API and rotates on its own.
import 'package:utd_live_room_kit/utd_live_room_kit.dart';
UTDLiveRoom(
appId: '<utd-app-id>',
appKey: '<utd-app-key>', // publishable; the kit mints tokens itself, no backend needed
userId: 'user123',
userName: 'Jane',
roomId: 'room456',
roomOwnerId: 'owner789',
// Identities the OWNER will promote to `admin` (moderators) once they join.
// Optional. The engine clamps non-owners to `audience`, so these are only
// honored because the owner promotes them server-side after join.
adminIds: const {'mod1', 'mod2'},
config: const UTDLiveRoomConfig(maxGuestTiles: 3),
);
The live room is
type: live_stream. The kit mints its token withtype: live_streamand sends no seat parameters (seat_count,seat_mode,host_seatare ignored for a live stream — the room is uncapped). The project must havelive_streamin itsenabled_typesin the UTD console, otherwisePOST /api/v1/tokenreturns403 "Type 'live_stream' is not enabled for this project". The sameappId/appKeyworks for every product type the project has enabled — the type is a per-request field, not a separate credential.
See the exported API in
lib/utd_live_room_kit.dart — UTDLiveRoom,
UTDRoomController, UTDStageApi, UTDMediaController, UTDChatController, and
the room/role/participant models.
Stage & roles
A live room has four server-authoritative roles:
| Role | Publishes | Moderates | How you get it |
|---|---|---|---|
host |
✅ | ✅ | The verified room owner (userId == roomOwnerId). Force-upgraded by the engine. |
guest |
✅ | ❌ | Host/admin invites you onto the stage. |
admin |
❌ | ✅ | The owner promotes you (moderate-only, never on camera). |
audience |
❌ | ❌ | Default for everyone non-owner — watch + chat. |
Going on stage (co-publish). A viewer raises their hand
(POST /api/v1/rooms/:name/stage/request), which fires a transient
_stage_request data message to the host/admins (there is no persisted queue).
A host/admin then adds them (.../stage/add → role becomes guest, publish
granted). Removal (.../stage/remove) or self step-down (.../stage/leave)
sends them back to audience. The current publisher set is
GET /api/v1/rooms/:name/stage → { members: [{ identity, name, role }] }, and
roster changes broadcast a _stage_update data message. These endpoints are
live_stream-only (a seated room returns 400).
The kit surfaces the stage through UTDRoomController — inviteToSpeak,
requestToSpeak, approveSpeakerRequest, rejectSpeakerRequest — and the raw
endpoints via controller.stageApi (UTDStageApi).
Promoting a moderator. Moderation uses the owner-only role endpoint
PUT /api/v1/rooms/:name/participants/:identity/role with { role: 'admin' }.
The kit does this for you: pass adminIds (or adminIdsResolver /
adminIdsNow), and the host client promotes those identities as they join via
controller.changeRole. In a stage room this grants moderation but not
publish, and the engine refuses (409) an attempt to add an admin to the
stage — the two capabilities stay disjoint.
maxGuestTilesis a UI-only cap. It sizes the on-screen tile strip (host + N co-publishers); the engine stage itself is unbounded. There is no seat grid — the host video is the full-bleed background and co-publishers are floating tiles above it.
Config
UTDLiveRoomConfig controls the live experience. Notable fields:
maxGuestTiles(default3) — how many co-publisher tiles to draw (UI cap only; the engine is uncapped).autoHostCamera(defaultfalse) — publish on connect vs. self-preview → "Go Live".showGoLiveButton(defaulttrue) — render the built-in "Go Live" button during host preview, or let the app driveUTDMediaController.goLive.frontCameraOnJoin/mirrorLocalVideo/hostFit/guestFit— camera & video-fit options.videoQuality(defaultUTDVideoQuality.hd, 720p) — the video publish quality for the local camera (capture resolution + publish bitrate/framerate). Tiers:low(360p ~450 kbps),sd(540p ~800 kbps),hd(720p ~1.7 Mbps),fullHd(1080p ~3 Mbps). Applied at connect; also settable headlessly viaUTDRoomController.setVideoQualitybeforeconnect.buildVideoProcessor— a video track processor factory for live filters & beauty effects (pairs withutd_video_effects_kit).- Section/tile overrides —
headerWidget,messagesWidget,controlsBarWidget,hostTileBuilder,guestTileBuilder,guestTileFooterBuilder,onStageTap.
Requirements
- A UTD Stream Engine token endpoint, with
live_streamin the project'senabled_types. - Camera & microphone runtime permissions (handled via
permission_handler).