Preview. The Dart package source is in private development —
flutter analyze + flutter test both pass (21/21), and a
pub.dev publish is on the v0.3
roadmap. The API surface below is stable; only the distribution channel is in flight. Need source
access during the preview window? Email
[email protected].
Flutter SDK for LevelChat. Mirrors the @levelchat/web concepts — LevelChat, Room, tracks, events — adapted to idiomatic Dart with Stream, Future, and sealed event hierarchies. Naming follows Dart idiom: events are a sealed class RoomEvent (ParticipantJoined, TrackPublished) on a broadcast Stream, and you publish with room.publishMicrophone(). See the SDK parity matrix for the full per-platform naming table.
MIT-licensed. Real WebRTC via flutter_webrtc.
Install — preview
The standard hosted install (the shape below) lands with v0.3:
dependencies:
levelchat: ^0.3.0
flutter_webrtc: ^0.11.0 # peer depflutter pub getDuring the preview window the package is distributed under NDA — email
[email protected] with your use case and we'll send back
the private mirror credentials + a path-dependency snippet for your pubspec.yaml.
Platform setup
iOS
Add to ios/Runner/Info.plist:
<key>NSCameraUsageDescription</key>
<string>Used for video calls</string>
<key>NSMicrophoneUsageDescription</key>
<string>Used for voice + video calls</string>Set the deployment target to iOS 12+ in your Podfile:
platform :ios, '12.0'Android
Add to android/app/src/main/AndroidManifest.xml:
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />Set minSdkVersion 21 in android/app/build.gradle.
The end-to-end path
The full lifecycle is the same five steps as every LevelChat SDK: install → token → join → publish → handle events → leave. Here it is, top to bottom.
1. Mint a token
Tokens are minted on your backend with your project API key — never embedded in the app
bundle, which can be decompiled. The issueToken helper on the client exists only so
getting-started tutorials can run without a backend; production apps fetch the token from their
own server. See the Quickstart for the POST /v1/auth/tokens/room request shape.
// Production: fetch from YOUR backend.
final token = await fetchTokenFromYourBackend();2. Construct the client + join
LevelChat.joinRoom(roomId, opts) is the public entry point — it resolves once the connection
reaches connected. (The client also has joinLive, but joinRoom is the stable, fully
exported surface; reach for it first.)
import 'package:levelchat/levelchat.dart';
final client = LevelChat(const LevelChatConfig(region: 'eu-fsn'));
// `joinRoom` takes the room id first, then the options. The room id is
// whatever your backend scoped the token to.
final room = await client.joinRoom(
'r_demo',
JoinRoomOptions(token: token),
);JoinRoomOptions mirrors the web SDK: token (required), preferredCodec, simulcast
(default true), svc, signalingUrl, and iceServers for TURN injection.
3. Handle events
room.events is a broadcast Stream<RoomEvent> — listen anywhere, as many times as you like.
RoomEvent is a sealed hierarchy, so a switch (or is checks) covers every case:
room.events.listen((event) {
switch (event) {
case ParticipantJoined(:final participant):
print('joined: ${participant.identity}');
case ParticipantLeft(:final participantId):
print('left: $participantId');
case TrackSubscribed(:final track):
// A remote track is ready — bind it to an RTCVideoRenderer.
attachRenderer(track);
case TrackUnsubscribed(:final trackId):
detachRenderer(trackId);
case QualityChanged(:final score):
// score ∈ QualityLabel.excellent | good | fair | poor | disconnected
updateNetworkIndicator(score);
case RoomReconnecting(:final attempt):
showReconnectingBanner(attempt);
case RoomReconnected():
hideReconnectingBanner();
case RoomErrorEvent(:final error):
print('error: $error');
default:
break;
}
});Connection-state transitions arrive as their own events — RoomConnected, RoomReconnecting,
RoomReconnected, RoomDisconnected, RoomClosed — rather than a single state enum, which
keeps the switch exhaustive.
4. Publish camera + mic
Note the Dart-idiomatic name: it's publishMicrophone(), not publishMic(). Both methods take
an optional options object and return a LocalTrack whose nativeTrack you bind to an
RTCVideoRenderer for the local preview:
final cam = await room.publishCamera(
const PublishCameraOptions(resolution: '720p'),
);
final mic = await room.publishMicrophone();
// Bind the camera's native track to a renderer for the self-view.
final renderer = RTCVideoRenderer();
await renderer.initialize();
renderer.srcObject = cam.nativeTrack.mediaStream;Device selection is via the deviceId field on PublishCameraOptions / PublishMicOptions.
Each source can only be published once — a second publishCamera() throws
RoomError(roomDuplicatePublish).
5. Screen share
final screen = await room.publishScreen();Backed by getDisplayMedia() — on iOS this requires a Broadcast Upload Extension target, on
Android it triggers the system MediaProjection prompt. The SDK's screen_share module wraps the
permission-error mapping for both platforms; handle a thrown LevelChatError if the user
declines.
6. Stop a track + leave
unpublish takes the TrackSource (not a track id) — TrackSource.camera,
.microphone, or .screen. close() tears the room down completely:
await room.unpublish(TrackSource.camera);
// When the user leaves the call:
await room.close();UI Kit
The package exports brand-pinned Flutter widgets matching the React web kit's component set.
import 'package:levelchat/levelchat.dart';
VideoTile(
name: 'Mert',
quality: NetworkQuality.good,
micOn: true,
isSpeaking: speaker == myId,
);The widget kit ships VideoTile, ControlBar, PreJoin, SpeakerStage, Chat, PollPanel, QAPanel, and Whiteboard — every component the React web kit exports has a Flutter counterpart with the same name and brand tokens.
Public API
class LevelChat— entrypoint;joinRoom(roomId, opts)/issueToken(dev only).class Room—connect/close/publishCamera/publishMicrophone/publishScreen/unpublish(TrackSource)/setSpeakerphoneOn.Stream<RoomEvent> room.events— sealed event hierarchy (ParticipantJoined,TrackSubscribed,QualityChanged,RoomReconnecting,RoomErrorEvent, …).
In-call chat and server-side recording are exposed on Web / iOS / Android today; the Flutter
Room surface is publish + subscribe + lifecycle. Track the gap on the
SDK parity matrix.
Audio session
Auto-configured via flutter_webrtc's Helper.setSpeakerphoneOn wrapper. Toggle the speaker at
runtime with room.setSpeakerphoneOn(true).
Network resilience
The SDK auto-reconnects on transient signaling loss and restarts ICE on resume — you don't drive
recovery yourself. Surface it in your UI by listening for the RoomReconnecting /
RoomReconnected events (see step 3), and render a per-tile network indicator from
QualityChanged.
License
The SDK itself is MIT-licensed — embed in any app, commercial or personal, no royalties. The LevelChat server is BUSL-licensed and requires a paid commercial license for production use beyond the trial threshold. See Licensing.