qriib_meet
Flutter room and meeting APIs for Qriib applications.
Getting started
Add the package to your Flutter application, create a single client from
trusted credentials, create or join a room, then open the fresh final_link
returned by Qriib. The SDK owns request signing and the embedded meeting
experience; your application owns its navigation, identity, and secure
credential delivery.
QriibMeetClient keeps room REST calls and the meeting experience together.
The identifiers and room settings below are application-owned values. Obtain them from authenticated application state or your backend; do not copy user data, credentials, or environment-specific values into source code.
Use the built-in newClientRoomId() for every create-room request. It creates
a cryptographically random UUID-style string and contains no user data,
credentials, or room metadata.
final qriib = QriibMeetClient.withProjectCredentials(
apiKey: const String.fromEnvironment('QRIIB_API_KEY'),
secretKey: const String.fromEnvironment('QRIIB_SECRET_KEY'),
enableNetworkLogging: false,
);
final response = await qriib.rooms.createQuickVideoRoom(
projectId: projectId,
moderatorId: moderatorId,
name: participantName,
maxParticipants: roomCapacity,
emptyTimeout: emptyRoomTimeoutSeconds,
clientRoomId: newClientRoomId(),
metadata: QriibRoomMetadata(
roomTitle: roomTitle,
welcomeMessage: welcomeMessage,
),
);
final finalLink = finalLinkFromResponse(response);
if (finalLink == null) {
throw const QriibMeetException('The response has no final_link to open.');
}
await qriib.meetings.join(
context: context,
finalLink: finalLink,
userName: participantName,
configuration: meetingConfiguration,
);
Use the built-in finalLinkFromResponse to read the fresh final_link out of
any create, join, or start response. It accepts both the
response['data']['final_link'] envelope and a top-level final_link, and
returns null when no usable link is present.
QriibMeetingConfiguration is optional. Without it, the package displays its
premium default meeting view. The supplied builders receive Qriib session and
participant types only; consumers do not need to import the media engine.
Always obtain a fresh final_link before joining. Do not store or log it.
Meeting UI configuration
Pass a QriibMeetingConfiguration to join to style the built-in meeting
surface or replace individual regions. All options are optional; omit a
builder to retain that part of the premium default UI.
final meetingConfiguration = QriibMeetingConfiguration(
title: meetingTitle,
theme: QriibMeetingTheme(
backgroundColor: meetingBackgroundColor,
surfaceColor: meetingSurfaceColor,
primaryColor: brandColor,
dangerColor: leaveButtonColor,
tileColor: participantTileColor,
participantNameStyle: participantNameTextStyle,
topBarTextStyle: topBarTextStyle,
cameraOnIcon: cameraOnIcon,
cameraOffIcon: cameraOffIcon,
microphoneOnIcon: microphoneOnIcon,
microphoneOffIcon: microphoneOffIcon,
leaveIcon: leaveIcon,
),
showTopBar: showTopBar,
showParticipantNames: showParticipantNames,
showCameraControl: showCameraControl,
showMicrophoneControl: showMicrophoneControl,
showAudioDeviceControl: showAudioDeviceControl,
showLeaveControl: showLeaveControl,
videoAvailable: roomAllowsVideo,
onCameraChanged: (session, enabled) {
// Update app-owned UI or analytics without recording sensitive media data.
},
onMicrophoneChanged: (session, enabled) {
// Update app-owned UI or analytics without recording sensitive media data.
},
onLeaveRequested: (session) async {
// Return false to keep the participant in the meeting.
return await confirmLeaveWithUser();
},
);
Visibility and audio-only behavior
showTopBar, showParticipantNames, showCameraControl,
showMicrophoneControl, showAudioDeviceControl, and showLeaveControl hide
default elements. videoAvailable describes whether the meeting has video at
all: set it to false for rooms created with createQuickAudioRoom or
createScheduledAudioRoom so your own builders can render an audio-only
surface instead of a camera frame.
When you replace controls for an audio-only room, keep a visible, disabled camera-off control with an accessible explanation rather than hiding it, so participants can tell that video is unavailable rather than broken.
Pre-join screen
The lobby screen is opt-in. Enable it to let participants confirm their camera, microphone, and audio devices before the meeting connects:
final meetingConfiguration = QriibMeetingConfiguration(
preJoin: QriibPreJoinConfiguration(
enabled: true,
title: meetingTitle,
showDeviceSelector: true,
),
);
The camera and microphone states chosen in the lobby are carried into the meeting. Cancelling the lobby aborts the join without connecting.
Replace individual regions
The three builders receive neutral Qriib types and a QriibMeetingSession:
final meetingConfiguration = QriibMeetingConfiguration(
participantBuilder: (context, participant, video, session) {
// `video` is the SDK-provided renderer. Do not retain it when local video
// is disabled; listen to the neutral session state instead.
return ValueListenableBuilder<QriibMeetingState>(
valueListenable: session.state,
builder: (context, state, child) {
final localCameraOff = participant.isLocal && !state.cameraEnabled;
if (!localCameraOff) return video;
final initial = participant.label.isEmpty
? fallbackInitial
: participant.label.characters.first.toUpperCase();
return ColoredBox(
color: participantTileColor,
child: Center(child: CircleAvatar(child: Text(initial))),
);
},
);
},
topBarBuilder: (context, state, session) => MeetingHeader(
title: meetingTitle,
participantCount: state.participants.length,
),
controlsBuilder: (context, state, session) => MeetingControls(
cameraEnabled: state.cameraEnabled,
microphoneEnabled: state.microphoneEnabled,
onCameraPressed: () => session.setCameraEnabled(!state.cameraEnabled),
onMicrophonePressed: () =>
session.setMicrophoneEnabled(!state.microphoneEnabled),
onLeavePressed: session.leave,
),
);
When replacing controls, provide the same accessible labels, focus behavior, disabled video indication for audio-only rooms, and a way to leave. When replacing participant tiles, do not render a camera frame when the relevant camera state is off; show an initial/avatar on a fixed background instead.
Audio device configuration
Participants can choose which microphone they are heard through and which speaker they hear the meeting on, both before joining and mid-call.
The built-in audio-route control
The default meeting view ships an audio-route button in its bottom control bar, placed after the microphone control and before the leave control. Tapping it opens a picker whose contents depend on what the platform reports:
- Mobile — a single Speaker switch that routes audio between the earpiece and the loudspeaker. Wired headsets and Bluetooth earbuds still take priority while they are connected. The button icon follows the current route.
- Desktop and web — a Microphone list and a Speaker list, each showing the OS-provided device names with the active device marked. Selecting an entry switches to it immediately.
The button only renders when the platform reports at least one of these
capabilities, so nothing appears on a device with a single fixed route. Set
QriibMeetingConfiguration(showAudioDeviceControl: false) to hide it and
build your own picker instead.
The opt-in pre-join lobby offers the same choices before connecting: a
speaker toggle on mobile, and microphone/speaker chips on desktop and web when
QriibPreJoinConfiguration(showDeviceSelector: true). The device chosen in
the lobby carries into the meeting.
Driving it yourself
QriibMeetingSession (and QriibPreJoinSession before joining) exposes the
same state and operations to your own builders:
final state = session.state.value;
// Mobile: toggle the speaker on/off; wired/Bluetooth devices still take
// priority when connected.
await session.setSpeakerphoneOn(!state.speakerOn);
// Desktop/web: target a specific device by id.
await session.selectAudioInput(state.audioInputs.first);
await session.selectAudioOutput(state.audioOutputs.first);
state.audioInputs and state.audioOutputs are lists of QriibAudioDevice
(id, label, and a kind of QriibAudioDeviceKind.input or .output);
state.selectedAudioInputId and state.selectedAudioOutputId identify the
active ones. Check state.canToggleSpeakerphone (mobile) or
state.canSelectSpecificDevice (desktop/web) to decide which control to show
— rendering a device list on a platform that only supports a speaker toggle
leaves the user with a picker that cannot change anything.
Host permissions
Add these declarations to the host app's
android/app/src/main/AndroidManifest.xml, directly under <manifest>:
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
On iOS, add purpose strings to the host application's
ios/Runner/Info.plist:
<key>NSCameraUsageDescription</key>
<string>Use the camera to share video in a meeting.</string>
<key>NSMicrophoneUsageDescription</key>
<string>Use the microphone to share audio in a meeting.</string>
permission_handler_apple also requires the iOS host's Podfile to enable the
camera and microphone permission macros. Add these definitions to every Pods
build configuration in the post_install hook:
config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= [
'$(inherited)',
'PERMISSION_CAMERA=1',
'PERMISSION_MICROPHONE=1',
]
Transport and credentials
qriib_meet owns the signed Qriib V4 transport. Applications do not create a
second HTTP client, manually set key or hash-signature, or depend on a
separate V.cloud package. Keep project API credentials and fresh final_link
values out of source control and application logs.
Room workflows
Use a single QriibMeetClient for the lifetime of the host widget, then call
close() from dispose().
Quick rooms
Supply a unique clientRoomId for each create request. Quick video rooms can
be opened immediately from the response:
final response = await qriib.rooms.createQuickVideoRoom(
projectId: projectId,
clientRoomId: newClientRoomId(),
moderatorId: moderatorId,
name: participantName,
maxParticipants: roomCapacity,
emptyTimeout: emptyRoomTimeoutSeconds,
metadata: QriibRoomMetadata(
roomTitle: roomTitle,
welcomeMessage: welcomeMessage,
),
);
final finalLink = finalLinkFromResponse(response);
Use createQuickAudioRoom for an audio-only room, then join it with a
configuration whose videoAvailable is false.
Scheduled rooms
Use createScheduledVideoRoom or createScheduledAudioRoom with the same room
fields plus an ISO-8601 startAt value. When it is time to open the room,
call:
final response = await qriib.rooms.startScheduledRoom(
roomId,
name: participantName,
);
Read the fresh final_link from that response with finalLinkFromResponse
before joining.
Join and manage rooms
await qriib.rooms.joinRoom(
roomId: roomId,
userInfo: const QriibUserInfo(
name: participantName,
role: 'attendee',
isAdmin: false,
isHidden: false,
),
);
The client also provides createInvitationLink, endRoom, getRoomStatus,
getActiveRoomInfo, getActiveRoomsInfo, and fetchPastRooms. Catch
QriibApiException and QriibMeetException to show their user-safe message
values.
Complete Rooms API map
Inputs marked "room ID" must refer to the room returned by a previous create response.
| Operation | Method and required inputs | Result / next step |
|---|---|---|
| Quick video | createQuickVideoRoom(projectId, clientRoomId, metadata, name, moderatorId, maxParticipants, emptyTimeout) |
Read final_link, then join with video enabled. |
| Quick audio | createQuickAudioRoom(...) with the same fields |
Read final_link, then join with videoAvailable: false. |
| Scheduled video | createScheduledVideoRoom(projectId, clientRoomId, startAt, metadata, maxParticipants, emptyTimeout) |
Store the returned room ID and start it later. |
| Scheduled audio | createScheduledAudioRoom(...) with the same scheduled fields |
Start later, then join with videoAvailable: false. |
| Start scheduled | startScheduledRoom(roomId, name: participantName) |
Read its fresh final_link; match the configuration to the room type. |
| Join | joinRoom(roomId: roomId, userInfo: QriibUserInfo(...)) |
Read its fresh final_link and open the meeting. |
| Invitation | createInvitationLink(roomId: roomId, role: QriibInvitationRole.attendee) |
Share the returned HTTP(S) link only with the intended participant. |
| Inspect active room | getActiveRoomInfo(roomId) |
Returns current active-room details. |
| Inspect all active rooms | getActiveRoomsInfo() |
Returns the active-room collection. |
| Status | getRoomStatus(roomId) |
Returns the current lifecycle status. |
| Past rooms | fetchPastRooms(projectId: projectId, from: offset, limit: pageSize, orderBy: QriibPastRoomsOrder.desc) |
Returns a paged, newest-first history. |
| End | endRoom(roomId) |
Ends an active room; confirm this action with the user before invoking it in production. |
final_link is short-lived meeting access data. Read it with
finalLinkFromResponse, use it immediately, and do not persist it.
Run the example
The example app is a generated Android/iOS smoke host. It reads
QRIIB_API_KEY, QRIIB_SECRET_KEY, and a fresh QRIIB_FINAL_LINK from
compile-time environment definitions; it contains no credential or link
values.
Run it on a trusted device with values supplied only at build time:
cd example
flutter run \
--dart-define=QRIIB_API_KEY=your-project-api-key \
--dart-define=QRIIB_SECRET_KEY=your-project-secret \
--dart-define=QRIIB_FINAL_LINK=a-fresh-final-link
Supply these through your development or CI secret store. Treat all credentials and final links as sensitive: do not commit, log, or send them through analytics.