qriib_meet 0.1.1 copy "qriib_meet: ^0.1.1" to clipboard
qriib_meet: ^0.1.1 copied to clipboard

Qriib Flutter room management and embedded meeting APIs.

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.