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 & i18nUTDRoomTheme (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 with X-App-Key; the engine signs the returned per-user user_token with the project server_secret server-side, so the secret never ships in the app. The kit then uses that user_token as the Authorization: Bearer for 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 — one appId/appKey works for every type enabled on the project. This kit always mints type: 'audio_room' on POST /api/v1/token; the project just needs audio_room in its enabled types (set per project in the UTD console). The seat model below is the audio_room model — unchanged. (Live video broadcasts are a separate live_stream type 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 in adminIds are treated as admins. By default a host/admin joins mic-on (autoHostMic) and is auto-seated on hostSeatIndex if 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.

Libraries

utd_audio_room_kit