UTD Audio Room Kit
A Flutter package for building live audio-room experiences, powered by LiveKit and the UTD Stream Engine. It is a drop-in replacement for zego_uikit_prebuilt_live_audio_room: give it the required data and you get a fully interactive room — seats, chat, mic/speaker controls, seat actions, member list, speak requests, and moderation — with no extra UI code.
Features
- Drop-in room UI — a complete, interactive room from required data alone.
- Seat management — take, leave, switch, lock, unlock, kick, mute, swap.
- Built-in seat actions — tap a seat to take/leave it; host/admin get a moderation sheet (mute, kick, lock, invite).
- Speak requests — audience apply-to-speak; host approve/reject queue with a live badge.
- Member list — participants with host actions (mute, kick, invite, ban, promote/demote).
- Media controls — microphone & speaker with reactive state (Bluetooth-preferring 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.
- Theming & i18n —
UTDRoomTheme(colors) +UTDRoomStrings(EN/AR built in) without replacing widgets. - Full customization — replace any section (header, messages, controls, background, seats) with your own widgets.
Installation
dependencies:
utd_audio_room_kit:
path: packages/utd_audio_room_kit
Platform setup
iOS — add to ios/Runner/Info.plist:
<key>NSMicrophoneUsageDescription</key>
<string>Required for audio room participation</string>
For background audio + PiP, enable Background Modes → Audio, AirPlay, and Picture in Picture in Xcode.
Android — add to android/app/src/main/AndroidManifest.xml:
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
Set minSdkVersion to at least 21 (31+ for OS PiP).
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 then uses thatuser_tokenas theAuthorization: Bearerfor all in-room calls. No token server to stand up.
appKey is not the server_secret: it can't sign tokens or call the server-to-server API, and
it can be rotated on its own from the console. Treat a leak as a project-scoped incident (rotate +
ship a new build).
Type-first engine. The engine groups products by a per-request
type, not by credential — oneappId/appKeyworks for every type enabled on the project. This kit always mintstype: 'audio_room'onPOST /api/v1/token; the project just needsaudio_roomin its enabled types (set per project in the UTD console). The seat model below is theaudio_roommodel — unchanged. (Live video broadcasts are a separatelive_streamtype served by the standalone live kit; this package does not use it.)
Quick start (drop-in, no backend)
import 'package:utd_audio_room_kit/utd_audio_room_kit.dart';
UTDAudioRoom(
appId: '<utd-app-id>',
appKey: '<utd-app-key>', // publishable; the kit mints tokens itself, no backend needed
userId: 'user123',
userName: 'John Doe',
roomId: 'room456',
roomOwnerId: 'owner789', // userId == roomOwnerId ⇒ this user is the host
);
With only this, the package renders: a dark background, the seat grid (avatars + names), a header (minimize + exit), the chat list, and a role-aware controls bar. Tapping a seat takes/leaves it; audience can apply to speak; host/admin get member-list, the speak-request queue, and per-seat moderation. A connect failure shows an error + retry instead of an endless spinner.
The host is detected as
userId == roomOwnerId; identities inadminIdsare treated as admins. By default a host/admin joins mic-on (autoHostMic) and is auto-seated onhostSeatIndexif the backend didn't seat them (autoSeatHost).
Constructor
| Param | Required | Description |
|---|---|---|
appId |
✅ | UTD Stream Engine app id (public identifier, safe to ship) |
appKey |
✅ | Publishable app key — the kit mints tokens directly from the engine (no backend). |
userId |
✅ | Local user identity |
userName |
✅ | Display name |
roomId |
✅ | Room to join/create |
roomOwnerId |
✅ | Owner identity (host detection + owner-only actions) |
adminIds |
– | Identities to request the admin role for |
layoutMode |
– | Mode id to use (default '3' = 9 seats); see Layout modes |
modes |
– | Custom UTDRoomModes to register |
config |
– | UTDAudioRoomConfig (see below) |
controller |
– | Reuse an existing UTDRoomController (e.g. after minimize) |
onControllerReady |
– | Get the controller (wire navigatorKey, minimize, etc.) |
onConnectionChanged |
– | (bool isConnected) |
onSeatTap |
– | Override the built-in seat action sheet |
onSeatChanged |
– | (List<SeatState> seats) |
onConnectError |
– | Override the built-in connect-error view |
Configuration (UTDAudioRoomConfig)
UTDAudioRoom(
// …required data…
config: UTDAudioRoomConfig(
// behavior
showControlsBar: true,
showSeatNames: true, // render occupant names under seats
enableMinimize: true,
turnOnMicrophoneWhenJoining: false,
useSpeakerWhenJoining: true,
hostSeatIndex: 0,
autoHostMic: true, // host/admin joins mic-on
autoSeatHost: true, // host auto-takes hostSeatIndex if empty
useDefaultBackground: true, // paints theme.background when no backgroundWidget
// theming & strings
theme: UTDRoomTheme(), // or UTDRoomTheme().copyWith(primary: …)
strings: UTDRoomStrings.ar(), // null ⇒ English
// user attributes shared with others (name/avatar are read on seats)
userInRoomAttributes: {'avatar': 'https://…', 'name': 'John'},
// section overrides (null ⇒ built-in default)
headerWidget: null,
messagesWidget: null,
controlsBarWidget: null,
backgroundWidget: null,
foregroundWidget: null,
pkWidget: null,
// seat builders (null ⇒ built-in default avatar/name)
seatBuilder: null,
avatarBuilder: null,
emptySeatBuilder: null,
lockedSeatBuilder: null,
),
);
Every built-in default is off when you override it — supplying a controlsBarWidget,
avatarBuilder, onSeatTap, etc. fully replaces the corresponding default.
Theming & localization
UTDRoomTheme is an all-defaulted color set (dark by default, so the white default content
is never invisible). UTDRoomStrings.en() / .ar() cover every default label; pass your own
UTDRoomStrings(...) to translate or relabel:
config: UTDAudioRoomConfig(
theme: const UTDRoomTheme().copyWith(primary: Colors.teal, background: const Color(0xFF101010)),
strings: UTDRoomStrings.ar(),
);
Controller API
onControllerReady gives you a UTDRoomController for programmatic control:
final c = controller;
// Seats
await c.seatController.takeSeat(0, c.localIdentity!);
await c.seatController.leaveSeat(c.localIdentity!);
await c.seatController.lockSeat(2, identity: c.localIdentity!);
// Speak requests
await c.requestToSpeak();
await c.approveSpeakerRequest(requestId);
await c.inviteToSpeak(targetId, seatIndex: 3);
// Roles & bans (gated server-side)
await c.changeRole(targetIdentity: id, role: 'admin'); // OWNER-only (403 otherwise)
await c.banUser(id, reason: '…'); // host/admin-gated
// Media
await c.mediaController.toggleMicrophone();
// Reactive state
c.seatController.seats; // ValueNotifier<List<SeatState>>
c.seatController.pendingRequests; // ValueNotifier<List<SpeakerRequest>>
c.activeSpeakers; // ValueNotifier<Set<String>>
c.participantsStream; // Stream<List<UTDParticipant>>
Minimize / PiP
Wrap your app with UTDMiniPopScope and mount UTDMiniOverlayPage at the root, then
configure minimize in onControllerReady:
onControllerReady: (controller) {
controller.navigatorKey = navKey; // for default dialogs
controller.minimize.configure(
UTDMinimizeConfig(
enableOSPip: true,
onClose: () => myExitRoom(),
),
);
},
Layout modes
The package ships one default mode (id: '3', 9 seats, 3×3). Register custom layouts via
modes and select with layoutMode:
UTDAudioRoom(
layoutMode: 'party',
modes: const [
UTDRoomMode(id: 'party', seatCount: 8, rows: [[0,1,2,3],[4,5,6,7]]),
],
);
Architecture
UTDRoomController
├── UTDRoomManager — LiveKit connection & lifecycle
├── UTDSeatController — seat state (ValueNotifier) + speak requests
├── UTDMediaController — mic / speaker
├── UTDChatController — data-channel chat
└── UTDMinimizeController / UTDPipController
All state is exposed via ValueNotifier/Stream, so it integrates with any state-management
approach. See the Quick start snippet above for a minimal drop-in app.