diff --git a/src/content/docs/realtime/realtimekit/core/local-participant.mdx b/src/content/docs/realtime/realtimekit/core/local-participant.mdx
index 7d6b8dc79699ffb..283ab266b7a80e5 100644
--- a/src/content/docs/realtime/realtimekit/core/local-participant.mdx
+++ b/src/content/docs/realtime/realtimekit/core/local-participant.mdx
@@ -6,12 +6,13 @@ sidebar:
order: 4
---
-import { Tabs, TabItem } from "~/components";
+import RTKSDKSelector from "~/components/realtimekit/RTKSDKSelector/RTKSDKSelector.astro";
+import RTKCodeSnippet from "~/components/realtimekit/RTKCodeSnippet/RTKCodeSnippet.astro";
-This guide covers how to manage the local user's media devices, control audio/video/screenshare, handle events, and work with media tracks in your RealtimeKit meetings.
+Manage local user media devices, control audio, video, and screenshare, and handle events in RealtimeKit meetings.
:::note[Prerequisites]
-This page assumes you've already initialized the SDK and understand the meeting object structure. Refer to [Initialize SDK](/realtime/realtimekit/core/) and [Meeting Object Explained](/realtime/realtimekit/core/meeting-object-explained/) if needed.
+Initialize the SDK and understand the meeting object structure. Refer to [Initialize SDK](/realtime/realtimekit/core/) and [Meeting Object Explained](/realtime/realtimekit/core/meeting-object-explained/).
:::
## Introduction
@@ -20,11 +21,14 @@ The local user is accessible via `meeting.self` and contains all information and
## Properties
-
-
+
### Metadata Properties
+Access participant identifiers and display information:
+
+
+
```js
// Participant identifiers
meeting.self.id; // Peer ID (unique per session)
@@ -34,9 +38,81 @@ meeting.self.name; // Display name
meeting.self.picture; // Display picture URL
```
+
+
+
+```jsx
+import { useRealtimeKitSelector } from "@cloudflare/realtimekit-react";
+
+// Participant identifiers
+const id = useRealtimeKitSelector((m) => m.self.id);
+const userId = useRealtimeKitSelector((m) => m.self.userId);
+const customParticipantId = useRealtimeKitSelector(
+ (m) => m.self.customParticipantId,
+);
+const name = useRealtimeKitSelector((m) => m.self.name);
+const picture = useRealtimeKitSelector((m) => m.self.picture);
+```
+
+
+
+
+```kotlin
+// Participant identifiers
+meeting.localUser.id // Peer ID (unique per session)
+meeting.localUser.userId // User ID (persistent across sessions)
+meeting.localUser.customParticipantId // Custom identifier set by developer
+meeting.localUser.name // Display name
+meeting.localUser.picture // Display picture URL
+```
+
+
+
+
+```swift
+// Participant identifiers
+meeting.localUser.id // Peer ID (unique per session)
+meeting.localUser.userId // User ID (persistent across sessions)
+meeting.localUser.customParticipantId // Custom identifier set by developer
+meeting.localUser.name // Display name
+meeting.localUser.picture // Display picture URL
+```
+
+
+
+
+```jsx
+import { useRealtimeKitSelector } from "@cloudflare/realtimekit-react-native";
+
+// Participant identifiers
+const id = useRealtimeKitSelector((m) => m.self.id);
+const userId = useRealtimeKitSelector((m) => m.self.userId);
+const customParticipantId = useRealtimeKitSelector(
+ (m) => m.self.customParticipantId,
+);
+const name = useRealtimeKitSelector((m) => m.self.name);
+const picture = useRealtimeKitSelector((m) => m.self.picture);
+```
+
+
+
+
+```dart
+// Participant identifiers
+meeting.localUser.id // Peer ID (unique per session)
+meeting.localUser.userId // User ID (persistent across sessions)
+meeting.localUser.customParticipantId // Custom identifier set by developer
+meeting.localUser.name // Display name
+meeting.localUser.picture // Display picture URL
+```
+
+
+
### Media Properties
-The local user's media tracks and states:
+Access the local user's media tracks and states:
+
+
```js
// Media state flags
@@ -53,53 +129,58 @@ meeting.self.screenShareTracks; // Object: { video: MediaStreamTrack, audio?: Me
meeting.self.mediaPermissions; // Current audio/video permissions
```
-### State Properties
+
+
-```js
-// Room state
-meeting.self.roomJoined; // Boolean: Has joined the meeting?
-meeting.self.roomState; // Current room state (see possible values below)
-meeting.self.isPinned; // Boolean: Is the local user pinned?
+```jsx
+// Media state flags
+const audioEnabled = useRealtimeKitSelector((m) => m.self.audioEnabled);
+const videoEnabled = useRealtimeKitSelector((m) => m.self.videoEnabled);
+const screenShareEnabled = useRealtimeKitSelector(
+ (m) => m.self.screenShareEnabled,
+);
-// Permissions and config
-meeting.self.permissions; // Capabilities defined by preset
-meeting.self.config; // Configuration for meeting appearance
-```
+// Media tracks (MediaStreamTrack objects)
+const audioTrack = useRealtimeKitSelector((m) => m.self.audioTrack);
+const videoTrack = useRealtimeKitSelector((m) => m.self.videoTrack);
+const screenShareTracks = useRealtimeKitSelector(
+ (m) => m.self.screenShareTracks,
+);
-**Room State Values:**
+// Permissions granted by user
+const mediaPermissions = useRealtimeKitSelector((m) => m.self.mediaPermissions);
+```
-The `roomState` property can have the following values:
+
+
-- `'init'` - Initialized but not joined
-- `'joined'` - Successfully joined the meeting
-- `'waitlisted'` - Waiting in the waiting room
-- `'rejected'` - Entry rejected
-- `'kicked'` - Removed from meeting
-- `'left'` - Left the meeting
-- `'ended'` - Meeting has ended
-- `'disconnected'` - Disconnected from meeting
+```kotlin
+// Media state flags
+meeting.localUser.audioEnabled // Boolean: Is audio enabled?
+meeting.localUser.videoEnabled // Boolean: Is video enabled?
+meeting.localUser.screenShareEnabled // Boolean: Is screen share active?
-
-
+// Permissions granted by user
+meeting.localUser.isCameraPermissionGranted // Camera permission status
+meeting.localUser.isMicrophonePermissionGranted // Microphone permission status
+```
-### Metadata Properties
+
+
-```jsx
-import { useRealtimeKitSelector } from "@cloudflare/realtimekit-react";
+```swift
+// Media state flags
+meeting.localUser.audioEnabled // Boolean: Is audio enabled?
+meeting.localUser.videoEnabled // Boolean: Is video enabled?
+meeting.localUser.screenShareEnabled // Boolean: Is screen share active?
-// Participant identifiers
-const id = useRealtimeKitSelector((m) => m.self.id);
-const userId = useRealtimeKitSelector((m) => m.self.userId);
-const customParticipantId = useRealtimeKitSelector(
- (m) => m.self.customParticipantId,
-);
-const name = useRealtimeKitSelector((m) => m.self.name);
-const picture = useRealtimeKitSelector((m) => m.self.picture);
+// Permissions granted by user
+meeting.localUser.isCameraPermissionGranted // Camera permission status
+meeting.localUser.isMicrophonePermissionGranted // Microphone permission status
```
-### Media Properties
-
-The local user's media tracks and states:
+
+
```jsx
// Media state flags
@@ -120,8 +201,53 @@ const screenShareTracks = useRealtimeKitSelector(
const mediaPermissions = useRealtimeKitSelector((m) => m.self.mediaPermissions);
```
+
+
+
+```dart
+// Media state flags
+meeting.localUser.audioEnabled // Boolean: Is audio enabled?
+meeting.localUser.videoEnabled // Boolean: Is video enabled?
+meeting.localUser.screenShareEnabled // Boolean: Is screen share active?
+
+// Permissions granted by user
+meeting.localUser.isCameraPermissionGranted // Camera permission status
+meeting.localUser.isMicrophonePermissionGranted // Microphone permission status
+```
+
+
+
### State Properties
+Access room state and participant status:
+
+
+
+```js
+// Room state
+meeting.self.roomJoined; // Boolean: Has joined the meeting?
+meeting.self.roomState; // Current room state (see possible values below)
+meeting.self.isPinned; // Boolean: Is the local user pinned?
+
+// Permissions and config
+meeting.self.permissions; // Capabilities defined by preset
+meeting.self.config; // Configuration for meeting appearance
+```
+
+**Room state values:**
+
+- `'init'` - Initialized but not joined
+- `'joined'` - Successfully joined the meeting
+- `'waitlisted'` - Waiting in the waiting room
+- `'rejected'` - Entry rejected
+- `'kicked'` - Removed from meeting
+- `'left'` - Left the meeting
+- `'ended'` - Meeting has ended
+- `'disconnected'` - Disconnected from meeting
+
+
+
+
```jsx
// Room state
const roomJoined = useRealtimeKitSelector((m) => m.self.roomJoined);
@@ -147,9 +273,76 @@ return (
);
```
-**Room State Values:**
+**Room state values:**
+
+- `'init'` - Initialized but not joined
+- `'joined'` - Successfully joined the meeting
+- `'waitlisted'` - Waiting in the waiting room
+- `'rejected'` - Entry rejected
+- `'kicked'` - Removed from meeting
+- `'left'` - Left the meeting
+- `'ended'` - Meeting has ended
+- `'disconnected'` - Disconnected from meeting
+
+
+
+
+```kotlin
+// Room state
+meeting.localUser.roomJoined // Boolean: Has joined the meeting?
+meeting.localUser.waitListStatus // Waitlist status (None, Waiting, Accepted, Rejected)
+meeting.localUser.isPinned // Boolean: Is the local user pinned?
+
+// Permissions and config
+meeting.localUser.permissions // Capabilities defined by preset
+meeting.localUser.presetName // Name of preset for local user
+meeting.localUser.presetInfo // Typed object representing preset information
+```
+
+
+
+
+```swift
+// Room state
+meeting.localUser.roomJoined // Boolean: Has joined the meeting?
+meeting.localUser.waitListStatus // Waitlist status (None, Waiting, Accepted, Rejected)
+meeting.localUser.isPinned // Boolean: Is the local user pinned?
+
+// Permissions and config
+meeting.localUser.permissions // Capabilities defined by preset
+meeting.localUser.presetName // Name of preset for local user
+meeting.localUser.presetInfo // Typed object representing preset information
+```
+
+
+
+
+```jsx
+// Room state
+const roomJoined = useRealtimeKitSelector((m) => m.self.roomJoined);
+const roomState = useRealtimeKitSelector((m) => m.self.roomState);
+const isPinned = useRealtimeKitSelector((m) => m.self.isPinned);
+
+// Permissions and config
+const permissions = useRealtimeKitSelector((m) => m.self.permissions);
+const config = useRealtimeKitSelector((m) => m.self.config);
+```
+
+**Example: Conditional rendering based on room state**
+
+```jsx
+const roomState = useRealtimeKitSelector((m) => m.self.roomState);
+
+return (
+ <>
+ {roomState === "disconnected" && You are disconnected}
+ {roomState === "waitlisted" && Waiting for host to admit you}
+ {roomState === "joined" && You are in the meeting}
+ >
+);
+```
-The `roomState` property can have the following values:
+**Room state values:**
- `'init'` - Initialized but not joined
- `'joined'` - Successfully joined the meeting
@@ -160,18 +353,29 @@ The `roomState` property can have the following values:
- `'ended'` - Meeting has ended
- `'disconnected'` - Disconnected from meeting
-
-
+
+
-## Media Controls
+```dart
+// Room state
+meeting.localUser.isHost // Boolean: Is the local user a host?
+meeting.localUser.isPinned // Boolean: Is the local user pinned?
+meeting.localUser.stageStatus // Stage status of the local user
-
-
+// Permissions and flags
+meeting.localUser.flags // ParticipantFlags (recorder, hidden)
+```
+
+
+
+## Media Controls
-### Audio Control
+### Audio control
Mute and unmute the microphone:
+
+
```js
// Enable audio (unmute)
await meeting.self.enableAudio();
@@ -183,57 +387,64 @@ await meeting.self.disableAudio();
const isAudioEnabled = meeting.self.audioEnabled;
```
-### Video Control
+
+
-Enable and disable the camera:
+```jsx
+import { useRealtimeKitClient } from "@cloudflare/realtimekit-react";
-```js
-// Enable video
-await meeting.self.enableVideo();
+function AudioControls() {
+ const [meeting] = useRealtimeKitClient();
+ const audioEnabled = useRealtimeKitSelector((m) => m.self.audioEnabled);
-// Disable video
-await meeting.self.disableVideo();
+ const toggleAudio = async () => {
+ if (audioEnabled) {
+ await meeting.self.disableAudio();
+ } else {
+ await meeting.self.enableAudio();
+ }
+ };
-// Check current status
-const isVideoEnabled = meeting.self.videoEnabled;
+ return (
+
+ );
+}
```
-### Screen Share Control
-
-Start and stop screen sharing:
+
+
-```js
-// Enable screen share
-await meeting.self.enableScreenShare();
+```kotlin
+// Enable audio (unmute)
+meeting.localUser.enableAudio { error: AudioError? -> }
-// Disable screen share
-await meeting.self.disableScreenShare();
+// Disable audio (mute)
+meeting.localUser.disableAudio { error: AudioError? -> }
// Check current status
-const isScreenShareEnabled = meeting.self.screenShareEnabled;
+val isAudioEnabled = meeting.localUser.audioEnabled
```
-### Change Display Name
-
-Change the user's display name (only works before joining):
-
-```js
-await meeting.self.setName("New Name");
-```
+
+
-:::note
-The name change will only reflect across all participants if done **before** joining the meeting.
-:::
+```swift
+// Enable audio (unmute)
+meeting.localUser.enableAudio { err in }
-
-
+// Disable audio (mute)
+meeting.localUser.disableAudio { err in }
-### Audio Control
+// Check current status
+let isAudioEnabled = meeting.localUser.audioEnabled
+```
-Mute and unmute the microphone:
+
+
```jsx
-import { useRealtimeKitClient } from "@cloudflare/realtimekit-react";
+import { useRealtimeKitClient, useRealtimeKitSelector } from "@cloudflare/realtimekit-react-native";
+import { TouchableHighlight, Text } from "react-native";
function AudioControls() {
const [meeting] = useRealtimeKitClient();
@@ -248,15 +459,53 @@ function AudioControls() {
};
return (
-
+
+ {audioEnabled ? "Mute" : "Unmute"}
+
);
}
```
-### Video Control
+
+
+
+```dart
+// Enable audio (unmute)
+meeting.localUser.enableAudio(onResult: (e) {
+ // handle error if any
+});
+
+// Disable audio (mute)
+meeting.localUser.disableAudio(onResult: (e) {
+ // handle error if any
+});
+
+// Check current status
+final isAudioEnabled = meeting.localUser.audioEnabled;
+```
+
+
+
+### Video control
Enable and disable the camera:
+
+
+```js
+// Enable video
+await meeting.self.enableVideo();
+
+// Disable video
+await meeting.self.disableVideo();
+
+// Check current status
+const isVideoEnabled = meeting.self.videoEnabled;
+```
+
+
+
+
```jsx
function VideoControls() {
const [meeting] = useRealtimeKitClient();
@@ -278,15 +527,103 @@ function VideoControls() {
}
```
-### Screen Share Control
+
+
-Start and stop screen sharing:
+```kotlin
+// Enable video
+meeting.localUser.enableVideo { error: VideoError? -> }
-```jsx
-function ScreenShareControls() {
- const [meeting] = useRealtimeKitClient();
- const screenShareEnabled = useRealtimeKitSelector(
- (m) => m.self.screenShareEnabled,
+// Disable video
+meeting.localUser.disableVideo { error: VideoError? -> }
+
+// Check current status
+val isVideoEnabled = meeting.localUser.videoEnabled
+```
+
+
+
+
+```swift
+// Enable video
+meeting.localUser.enableVideo { err in }
+
+// Disable video
+meeting.localUser.disableVideo { err in }
+
+// Check current status
+let isVideoEnabled = meeting.localUser.videoEnabled
+```
+
+
+
+
+```jsx
+function VideoControls() {
+ const [meeting] = useRealtimeKitClient();
+ const videoEnabled = useRealtimeKitSelector((m) => m.self.videoEnabled);
+
+ const toggleVideo = async () => {
+ if (videoEnabled) {
+ await meeting.self.disableVideo();
+ } else {
+ await meeting.self.enableVideo();
+ }
+ };
+
+ return (
+
+ {videoEnabled ? "Stop Video" : "Start Video"}
+
+ );
+}
+```
+
+
+
+
+```dart
+// Enable video
+meeting.localUser.enableVideo(onResult: (e) {
+ // handle error if any
+});
+
+// Disable video
+meeting.localUser.disableVideo(onResult: (e) {
+ // handle error if any
+});
+
+// Check current status
+final isVideoEnabled = meeting.localUser.videoEnabled;
+```
+
+
+
+### Screen share control
+
+Start and stop screen sharing:
+
+
+
+```js
+// Enable screen share
+await meeting.self.enableScreenShare();
+
+// Disable screen share
+await meeting.self.disableScreenShare();
+
+// Check current status
+const isScreenShareEnabled = meeting.self.screenShareEnabled;
+```
+
+
+
+
+```jsx
+function ScreenShareControls() {
+ const [meeting] = useRealtimeKitClient();
+ const screenShareEnabled = useRealtimeKitSelector(
+ (m) => m.self.screenShareEnabled,
);
const toggleScreenShare = async () => {
@@ -305,27 +642,170 @@ function ScreenShareControls() {
}
```
-### Change Display Name
+
+
+
+```kotlin
+// Enable screen share
+meeting.localUser.enableScreenShare()
+
+// Disable screen share
+meeting.localUser.disableScreenShare()
+
+// Check current status
+val isScreenShareEnabled = meeting.localUser.screenShareEnabled
+```
+
+:::note[Android API 14 and above]
+Declare the following permission in your app's AndroidManifest.xml to use screenshare on Android devices running Android API 14 and above:
+
+```
+
+```
+
+Adding this permission requires extra steps on Google Play Console. Refer to [Google's documentation](https://support.google.com/googleplay/android-developer/answer/13392821?hl=en#declare) for more information.
+:::
+
+
+
+
+```swift
+// Enable screen share
+let err: ScreenShareError? = meeting.localUser.enableScreenShare()
+
+// Disable screen share
+meeting.localUser.disableScreenShare()
+```
+
+Refer to the [Screen Share Setup (iOS)](#screen-share-setup-ios) section for platform-specific configuration.
+
+
+
+
+```jsx
+function ScreenShareControls() {
+ const [meeting] = useRealtimeKitClient();
+ const screenShareEnabled = useRealtimeKitSelector(
+ (m) => m.self.screenShareEnabled,
+ );
+
+ const toggleScreenShare = async () => {
+ if (screenShareEnabled) {
+ await meeting.self.disableScreenShare();
+ } else {
+ await meeting.self.enableScreenShare();
+ }
+ };
+
+ return (
+
+ {screenShareEnabled ? "Stop Sharing" : "Share Screen"}
+
+ );
+}
+```
+
+
+
+
+```dart
+// Enable screen share
+meeting.localUser.enableScreenShare();
+
+// Disable screen share
+meeting.localUser.disableScreenShare();
+```
+
+:::note[Platform-specific setup]
+**Android:** Declare the following permission in your app's AndroidManifest.xml to use screenshare on Android devices running Android API 14 and above:
+
+```xml
+
+```
+
+**iOS:** Refer to the [Screen Share Setup (iOS)](#screen-share-setup-ios) section for additional configuration.
+:::
+
+
+
+### Change display name
+
+Update the display name before joining the meeting:
+
+
+
+```js
+await meeting.self.setName("New Name");
+```
+
+:::note
+Name changes only reflect across all participants if done before joining the meeting.
+:::
+
+
+
+
+```jsx
+await meeting.self.setName("New Name");
+```
+
+:::note
+Name changes only reflect across all participants if done before joining the meeting.
+:::
+
+
+
+
+```kotlin
+meeting.localUser.setDisplayName("New Name")
+```
+
+:::note
+Name changes only reflect across all participants if done before joining the meeting.
+:::
+
+
+
+
+```swift
+meeting.localUser.setDisplayName(name: "New Name")
+```
+
+:::note
+Name changes only reflect across all participants if done before joining the meeting.
+:::
-Change the user's display name (only works before joining):
+
+
```jsx
await meeting.self.setName("New Name");
```
:::note
-The name change will only reflect across all participants if done **before** joining the meeting.
+Name changes only reflect across all participants if done before joining the meeting.
+:::
+
+
+
+
+```dart
+if (meeting.permissions.miscellaneous.canEditDisplayName) {
+ meeting.localUser.setDisplayName("New Name");
+}
+```
+
+:::note
+Name changes only reflect across all participants if done before joining the meeting and the local user has preset permission to change the name.
:::
-
-
+
-## Manage Media Devices
+## Manage media devices
-
-
+### Get available devices
-### Get Available Devices
+
```js
// Get all media devices
@@ -348,22 +828,8 @@ const currentDevices = meeting.self.getCurrentDevices();
// Returns: { audio: MediaDeviceInfo, video: MediaDeviceInfo, speaker: MediaDeviceInfo }
```
-### Change Device
-
-Switch to a different media device:
-
-```js
-// Get all devices
-const devices = await meeting.self.getAllDevices();
-
-// Set a specific device (replaces device of the same kind)
-await meeting.self.setDevice(devices[0]);
-```
-
-
-
-
-### Get Available Devices
+
+
```jsx
import { useRealtimeKitClient } from "@cloudflare/realtimekit-react";
@@ -412,132 +878,811 @@ function DeviceSelector() {
}
```
-### Get Current Devices
+Get current devices being used:
```jsx
-// Get current devices being used
const currentDevices = meeting.self.getCurrentDevices();
// Returns: { audio: MediaDeviceInfo, video: MediaDeviceInfo, speaker: MediaDeviceInfo }
```
-
-
-
-## Display Local Video
+
+
-
-
+```kotlin
+// Get all audio devices
+val audioDevices: List = meeting.localUser.getAudioDevices()
-### Register Video Element
+// Get all video devices
+val videoDevices: List = meeting.localUser.getVideoDevices()
-Play the local user's video track on a `
+
-// For local preview (not sent to other users), pass true as second argument
-meeting.self.registerVideoElement(videoElement, true);
-```
+```swift
+// Get all audio devices
+let audioDevices = meeting.localUser.getAudioDevices()
-### Deregister Video Element
+// Get all video devices
+let videoDevices = meeting.localUser.getVideoDevices()
-Clean up when the video element is no longer needed:
+// Get currently selected audio device
+let selectedAudioDevice = meeting.localUser.getSelectedAudioDevice()
-```js
-meeting.self.deregisterVideoElement(videoElement);
+// Get currently selected video device
+let selectedVideoDevice = meeting.localUser.getSelectedVideoDevice()
```
-
-
+
+
-### Using UI Kit Component
+```jsx
+import { useRealtimeKitClient } from "@cloudflare/realtimekit-react-native";
+import { useState, useEffect } from "react";
+import { FlatList, TouchableHighlight, Text, View } from "react-native";
-The simplest way to display local video in React is using the UI Kit's video tile component:
+function DeviceSelector() {
+ const [meeting] = useRealtimeKitClient();
+ const [audioDevices, setAudioDevices] = useState([]);
+ const [videoDevices, setVideoDevices] = useState([]);
-```jsx
-import { RtkParticipantTile } from "@cloudflare/realtimekit-react-ui";
-import { useRealtimeKitSelector } from "@cloudflare/realtimekit-react";
+ useEffect(() => {
+ if (!meeting) return;
-function LocalVideo() {
- const localUser = useRealtimeKitSelector((m) => m.self);
+ const loadDevices = async () => {
+ const audio = await meeting.self.getAudioDevices();
+ const video = await meeting.self.getVideoDevices();
+ setAudioDevices(audio);
+ setVideoDevices(video);
+ };
- return ;
+ loadDevices();
+ }, [meeting]);
+
+ const handleDeviceChange = async (device) => {
+ await meeting.self.setDevice(device);
+ };
+
+ return (
+
+
+ handleDeviceChange(item)}>
+ {item.label}
+
+ }
+ keyExtractor={item => item.deviceId}
+ />
+
+ );
}
```
-### Manual Video Element Management
-
-For custom implementations:
+Get current devices being used:
```jsx
-import {
- useRealtimeKitClient,
- useRealtimeKitSelector,
-} from "@cloudflare/realtimekit-react";
-import { useEffect, useRef } from "react";
+const currentDevices = meeting.self.getCurrentDevices();
+// Returns: { audio: MediaDeviceInfo, video: MediaDeviceInfo, speaker: MediaDeviceInfo }
+```
-function LocalVideoCustom() {
- const [meeting] = useRealtimeKitClient();
- const videoEnabled = useRealtimeKitSelector((m) => m.self.videoEnabled);
- const videoTrack = useRealtimeKitSelector((m) => m.self.videoTrack);
- const videoRef = useRef(null);
+
+
- useEffect(() => {
- if (!videoRef.current || !meeting) return;
+```dart
+// Get all audio devices
+final audioDevices = await meeting.localUser.getAudioDevices();
- // Register video element
- meeting.self.registerVideoElement(videoRef.current);
+// Get all video devices
+final videoDevices = await meeting.localUser.getVideoDevices();
- return () => {
- // Cleanup: deregister on unmount
- meeting.self.deregisterVideoElement(videoRef.current);
- };
- }, [meeting]);
+// Get currently selected audio device
+final selectedAudioDevice = meeting.localUser.getSelectedAudioDevice();
- return (
-
- );
-}
+// Get currently selected video device
+final selectedVideoDevice = meeting.localUser.getSelectedVideoDevice();
```
-
-
-
-## Events
+
-
-
+### Change device
-### Room Joined
+Switch to a different media device:
-Triggered when the local user successfully joins the meeting:
+
```js
-meeting.self.on("roomJoined", () => {
- console.log("Successfully joined the meeting");
-});
+// Get all devices
+const devices = await meeting.self.getAllDevices();
+
+// Set a specific device (replaces device of the same kind)
+await meeting.self.setDevice(devices[0]);
```
-### Room Left
+
+
-Triggered when the local user leaves the meeting:
+Use the device selector example from the previous section. The `handleDeviceChange` function demonstrates how to switch devices.
-```js
-meeting.self.on("roomLeft", ({ state }) => {
- console.log("Left the meeting with state:", state);
+
+
+
+```kotlin
+// Get all audio devices
+val audioDevices = meeting.localUser.getAudioDevices()
+
+// Set audio device
+meeting.localUser.setAudioDevice(audioDevices[0])
+
+// Get all video devices
+val videoDevices = meeting.localUser.getVideoDevices()
+
+// Set video device
+meeting.localUser.setVideoDevice(videoDevices[0])
+
+// Switch between front and back camera on devices with 2 cameras
+meeting.localUser.switchCamera()
+```
+
+
+
+
+```swift
+// Set audio device
+meeting.localUser.setAudioDevice(device)
+
+// Set video device
+meeting.localUser.setVideoDevice(videoDevice: device)
+
+// Switch between front and back camera
+meeting.localUser.switchCamera()
+```
+
+
+
+
+Use the device selector example from the previous section. The `handleDeviceChange` function demonstrates how to switch devices.
+
+```js
+const handleDeviceChange = async (device) => {
+ await meeting.self.setDevice(device);
+};
+```
+
+
+
+
+```dart
+// Get all available audio devices
+final audioDevices = await meeting.localUser.getAudioDevices();
+
+// Switch audio device
+await meeting.localUser.setAudioDevice(audioDevices[1]);
+
+// Get all available video devices
+final videoDevices = await meeting.localUser.getVideoDevices();
+
+// Switch video device
+await meeting.localUser.setVideoDevice(videoDevices[1]);
+
+// Switch between available camera sources
+meeting.localUser.switchCamera();
+```
+
+
+
+## Display local video
+
+
+
+### Register video element
+
+Attach the local video track to a `` element:
+
+```html
+
+```
+
+```js
+const videoElement = document.getElementById("local-video");
+
+// Register the video element to display video
+meeting.self.registerVideoElement(videoElement);
+
+// For local preview (not sent to other users), pass true as second argument
+meeting.self.registerVideoElement(videoElement, true);
+```
+
+### Deregister video element
+
+Remove the video element when no longer needed:
+
+```js
+meeting.self.deregisterVideoElement(videoElement);
+```
+
+
+
+
+### Use UI Kit component
+
+Display local video with the UI Kit video tile component:
+
+```jsx
+import { RtkParticipantTile } from "@cloudflare/realtimekit-react-ui";
+import { useRealtimeKitSelector } from "@cloudflare/realtimekit-react";
+
+function LocalVideo() {
+ const localUser = useRealtimeKitSelector((m) => m.self);
+
+ return ;
+}
+```
+
+### Manage video element manually
+
+Create custom video element implementations:
+
+```jsx
+import {
+ useRealtimeKitClient,
+ useRealtimeKitSelector,
+} from "@cloudflare/realtimekit-react";
+import { useEffect, useRef } from "react";
+
+function LocalVideoCustom() {
+ const [meeting] = useRealtimeKitClient();
+ const videoEnabled = useRealtimeKitSelector((m) => m.self.videoEnabled);
+ const videoTrack = useRealtimeKitSelector((m) => m.self.videoTrack);
+ const videoRef = useRef(null);
+
+ useEffect(() => {
+ if (!videoRef.current || !meeting) return;
+
+ // Register video element
+ meeting.self.registerVideoElement(videoRef.current);
+
+ return () => {
+ // Cleanup: deregister on unmount
+ meeting.self.deregisterVideoElement(videoRef.current);
+ };
+ }, [meeting]);
+
+ return (
+
+ );
+}
+```
+
+
+
+
+### Get video view
+
+Retrieve a self-preview video view that renders the local camera stream:
+
+```kotlin
+// Get the self-preview video view
+val videoView = meeting.localUser.getSelfPreview()
+```
+
+For rendering other participants' video, use:
+
+```kotlin
+// Get video view for camera stream
+val participantVideoView = participant.getVideoView()
+
+// Get video view for screenshare stream
+val screenshareView = participant.getScreenShareVideoView()
+```
+
+### Manage lifecycle
+
+Control video rendering with lifecycle methods:
+
+```kotlin
+// Start rendering video
+videoView.renderVideo()
+
+// Stop rendering video (but keep the view)
+videoView.stopVideoRender()
+
+// Release native resources when done
+videoView.release()
+```
+
+### Complete Example
+
+```kotlin
+import android.os.Bundle
+import android.widget.FrameLayout
+import androidx.appcompat.app.AppCompatActivity
+import io.dyte.core.VideoView
+
+class MainActivity : AppCompatActivity() {
+ private lateinit var videoView: VideoView
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ // Get the self-preview video view
+ videoView = meeting.localUser.getSelfPreview()
+
+ // Add to your layout
+ val container = findViewById(R.id.video_container)
+ container.addView(videoView)
+
+ // Start rendering
+ videoView.renderVideo()
+ }
+
+ override fun onPause() {
+ super.onPause()
+ // Stop rendering when activity is paused
+ videoView.stopVideoRender()
+ }
+
+ override fun onResume() {
+ super.onResume()
+ // Resume rendering when activity is resumed
+ videoView.renderVideo()
+ }
+
+ override fun onDestroy() {
+ super.onDestroy()
+ // Clean up resources
+ videoView.release()
+ }
+}
+```
+
+
+
+
+### Get video view
+
+Retrieve video views that render the participant's video streams:
+
+```swift
+// Get video view for local camera stream
+let videoView = meeting.localUser.getVideoView()
+
+// Get video view for screenshare stream
+let screenshareView = meeting.localUser.getScreenShareVideoView()
+```
+
+### Manage lifecycle
+
+The `UIView` handles its own lifecycle automatically and cleans up native resources when it exits the current window. No manual cleanup is required.
+
+### Example
+
+```swift
+import UIKit
+import RealtimeKit
+
+class VideoViewController: UIViewController {
+ private var videoView: UIView?
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+
+ // Get the video view for local camera
+ videoView = meeting.localUser.getVideoView()
+
+ // Add to your view hierarchy
+ if let videoView = videoView {
+ videoView.frame = view.bounds
+ videoView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
+ view.addSubview(videoView)
+ }
+ }
+}
+```
+
+For screenshare:
+
+```swift
+// Get and display screenshare view
+let screenshareView = meeting.localUser.getScreenShareVideoView()
+if let screenshareView = screenshareView {
+ screenshareView.frame = view.bounds
+ screenshareView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
+ view.addSubview(screenshareView)
+}
+```
+
+
+
+
+```jsx
+import React from "react";
+import { useRealtimeKitSelector } from "@cloudflare/realtimekit-react-native";
+import { MediaStream, RTCView } from "@cloudflare/react-native-webrtc";
+
+export default function VideoView() {
+ const { videoTrack } = useRealtimeKitSelector(
+ (m) => m.participants.active,
+ ).toArray()[0];
+ const stream = new MediaStream(undefined);
+ stream.addTrack(videoTrack);
+ return (
+
+ );
+}
+```
+
+
+
+
+### VideoView widget
+
+Display video streams with the `VideoView` widget:
+
+```dart
+import 'package:realtimekit_core/realtimekit_core.dart';
+import 'package:flutter/material.dart';
+
+class LocalVideoView extends StatelessWidget {
+ final RtkMeetingParticipant localUser;
+
+ const LocalVideoView({Key? key, required this.localUser}) : super(key: key);
+
+ @override
+ Widget build(BuildContext context) {
+ return VideoView(
+ meetingParticipant: localUser,
+ isSelfParticipant: true,
+ );
+ }
+}
+```
+
+### VideoView parameters
+
+The `VideoView` widget accepts the following parameters:
+
+- `meetingParticipant` (required): The `RtkMeetingParticipant` whose video should be displayed
+- `isSelfParticipant` (optional): Set to `true` for the local participant's self-preview, defaults to `false`
+- `key` (optional): Widget key for Flutter's widget tree management
+
+### Example
+
+```dart
+import 'package:flutter/material.dart';
+import 'package:realtimekit_core/realtimekit_core.dart';
+
+class MeetingScreen extends StatelessWidget {
+ final RtkMeeting meeting;
+
+ const MeetingScreen({Key? key, required this.meeting}) : super(key: key);
+
+ @override
+ Widget build(BuildContext context) {
+ return Scaffold(
+ appBar: AppBar(title: Text('Video Preview')),
+ body: Container(
+ child: VideoView(
+ meetingParticipant: meeting.localUser,
+ isSelfParticipant: true,
+ ),
+ ),
+ );
+ }
+}
+```
+
+For displaying other participants' video:
+
+```dart
+// Display remote participant video
+VideoView(
+ meetingParticipant: remoteParticipant,
+ isSelfParticipant: false,
+)
+```
+
+The `VideoView` widget automatically handles video rendering and resource cleanup based on Flutter's widget lifecycle.
+
+
+
+## Screen share setup (iOS)
+
+
+
+### Add broadcast upload extension
+
+In Xcode, add a Broadcast Upload Extension through `File` → `New` → `Target`. Choose `iOS` → `Broadcast Upload Extension` and fill out the required information.
+
+### Configure app groups
+
+Add your extension to an app group:
+
+1. Go to your extension's target in the project
+2. In the Signings & Capabilities tab, click the + button in the top left
+3. Add App Groups
+4. Add App Groups to your main app as well, ensuring the App Group identifier is the same for both
+
+### Configure SampleHandler
+
+Edit your SampleHandler class:
+
+```swift
+import RealtimeKit
+
+class SampleHandler: RtkSampleHandler {}
+```
+
+### Update Info.plist
+
+Ensure **both** App and Extension Info.plist files contain these keys:
+
+```xml
+RTKRTCAppGroupIdentifier
+(name of the group you have created)
+```
+
+Add this key inside the Info.plist of the main App:
+
+```xml
+RTKRTCScreenSharingExtension
+(Bundle Identifier of the Broadcast upload extension)
+```
+
+### Enable screen share
+
+Launch the broadcast extension and enable screen share:
+
+```swift
+meeting.localUser.enableScreenShare()
+```
+
+To stop the screen share:
+
+```swift
+meeting.localUser.disableScreenShare()
+```
+
+
+
+
+### Add broadcast upload extension
+
+In Xcode, add a Broadcast Upload Extension through `File` → `New` → `Target`. Choose `iOS` → `Broadcast Upload Extension` and fill out the required information.
+
+### Configure app groups
+
+Add your extension to an app group:
+
+1. Go to your extension's target in the project
+2. In the Signings & Capabilities tab, click the + button in the top left
+3. Add App Groups
+4. Add App Groups to your main app as well, ensuring the App Group identifier is the same for both
+
+### Configure SampleHandler
+
+1. Place the `RtkSampleHandler.swift` file from [GitHub](https://github.com/dyte-io/iOS-ScreenShare/blob/main/RtkSampleHandler.swift) in the `ios//` folder
+2. Create or replace `SampleHandler.swift`:
+
+```swift
+import ReplayKit
+
+class SampleHandler: RtkSampleHandler {
+}
+```
+
+### Update Info.plist
+
+Ensure **both** App and Extension Info.plist files contain these keys:
+
+```xml
+RTKRTCAppGroupIdentifier
+(name of the group you have created)
+```
+
+Add this key inside the Info.plist of the main App:
+
+```xml
+RTKRTCScreenSharingExtension
+(Bundle Identifier of the Broadcast upload extension)
+```
+
+### Enable screen share
+
+Launch the broadcast extension and enable screen share:
+
+```dart
+meeting.localUser.enableScreenShare()
+```
+
+To stop the screen share:
+
+```dart
+meeting.localUser.disableScreenShare()
+```
+
+
+
+
+### Add broadcast upload extension
+
+In Xcode, add a Broadcast Upload Extension through `File` → `New` → `Target`. Choose `iOS` → `Broadcast Upload Extension` and fill out the required information.
+
+### Configure app groups
+
+Add your extension to an app group:
+
+1. Go to your extension's target in the project
+2. In the Signings & Capabilities tab, click the + button in the top left
+3. Add App Groups
+4. Add App Groups to your main app as well, ensuring the App Group identifier is the same for both
+
+### Configure SampleHandler
+
+Edit your SampleHandler class:
+
+```swift
+import RealtimeKitCore
+
+class SampleHandler: RTKScreenshareHandler {
+ override init() {
+ super.init(appGroupIdentifier: "", bundleIdentifier: "")
+ }
+}
+```
+
+### Update Info.plist
+
+Ensure **both** App and Extension Info.plist files contain these keys:
+
+```xml
+RTCAppGroupIdentifier
+(YOUR_APP_GROUP_IDENTIFIER)
+```
+
+Add this key inside the Info.plist of the main App:
+
+```xml
+RTCAppScreenSharingExtension
+(Bundle Identifier of the Broadcast upload extension)
+```
+
+### Enable screen share
+
+Launch the broadcast extension and enable screen share:
+
+```js
+meeting.self.enableScreenShare()
+```
+
+To stop the screen share:
+
+```js
+meeting.self.disableScreenShare()
+```
+
+
+
+## Events
+
+### Room joined
+
+Fires when the local user joins the meeting:
+
+
+
+```js
+meeting.self.on("roomJoined", () => {
+ console.log("Successfully joined the meeting");
+});
+```
+
+
+
+
+```jsx
+const roomJoined = useRealtimeKitSelector((m) => m.self.roomJoined);
+
+useEffect(() => {
+ if (roomJoined) {
+ console.log("Successfully joined the meeting");
+ }
+}, [roomJoined]);
+```
+
+Or use event listener:
+
+```jsx
+useEffect(() => {
+ if (!meeting) return;
+
+ const handleRoomJoined = () => {
+ console.log("Successfully joined the meeting");
+ };
+
+ meeting.self.on("roomJoined", handleRoomJoined);
+
+ return () => {
+ meeting.self.off("roomJoined", handleRoomJoined);
+ };
+}, [meeting]);
+```
+
+
+
+
+Android SDK uses a different event model. Monitor `roomJoined` property changes or use listeners for state changes.
+
+
+
+
+iOS SDK uses a different event model. Monitor `roomJoined` property changes or use listeners for state changes.
+
+
+
+
+```jsx
+const roomJoined = useRealtimeKitSelector((m) => m.self.roomJoined);
+
+useEffect(() => {
+ if (roomJoined) {
+ console.log("Successfully joined the meeting");
+ }
+}, [roomJoined]);
+```
+
+Or use event listener:
+
+```jsx
+useEffect(() => {
+ if (!meeting) return;
+
+ const handleRoomJoined = () => {
+ console.log("Successfully joined the meeting");
+ };
+
+ meeting.self.on("roomJoined", handleRoomJoined);
+
+ return () => {
+ meeting.self.off("roomJoined", handleRoomJoined);
+ };
+}, [meeting]);
+```
+
+
+
+
+Flutter SDK uses a different event model. Monitor `roomJoined` property changes or use listeners for state changes.
+
+
+
+### Room left
+
+Fires when the local user leaves the meeting:
+
+
+
+```js
+meeting.self.on("roomLeft", ({ state }) => {
+ console.log("Left the meeting with state:", state);
// Handle different leave states
if (state === "left") {
@@ -554,9 +1699,97 @@ meeting.self.on("roomLeft", ({ state }) => {
**Possible state values:** `'left'`, `'kicked'`, `'ended'`, `'rejected'`, `'disconnected'`, `'failed'`
-### Video Update
+
+
+
+```jsx
+const roomJoined = useRealtimeKitSelector((m) => m.self.roomJoined);
+
+useEffect(() => {
+ if (!roomJoined) {
+ console.log("Left the meeting");
+ }
+}, [roomJoined]);
+```
+
+Or use event listener for detailed state:
+
+```jsx
+meeting.self.on("roomLeft", ({ state }) => {
+ if (state === "left") {
+ console.log("User voluntarily left");
+ } else if (state === "kicked") {
+ console.log("User was kicked");
+ }
+});
+```
+
+
+
+
+Use `RtkSelfEventListener` to monitor when the local user is removed from the meeting:
+
+```kotlin
+meeting.addSelfEventListener(object : RtkSelfEventListener {
+ override fun onRemovedFromMeeting() {
+ // display alert that user is no longer in the meeting
+ }
+})
+```
+
+
+
+
+iOS SDK uses a different event model. Monitor `roomJoined` property changes or use listeners for state changes.
+
+
+
+
+```jsx
+const roomJoined = useRealtimeKitSelector((m) => m.self.roomJoined);
+
+useEffect(() => {
+ if (!roomJoined) {
+ console.log("Left the meeting");
+ }
+}, [roomJoined]);
+```
+
+Or use event listener for detailed state:
-Triggered when video is enabled or disabled:
+```jsx
+meeting.self.on("roomLeft", ({ state }) => {
+ if (state === "left") {
+ console.log("User voluntarily left");
+ } else if (state === "kicked") {
+ console.log("User was kicked");
+ }
+});
+```
+
+
+
+
+```dart
+class MeetingSelfListener extends RtkSelfEventListener {
+ @override
+ void onRemovedFromMeeting() {
+ // User was removed from the meeting (kicked or meeting ended)
+ // Display alert or navigate to exit screen
+ }
+}
+
+// Add the listener
+meeting.addSelfEventListener(MeetingSelfListener());
+```
+
+
+
+### Video update
+
+Fires when video is enabled or disabled:
+
+
```js
meeting.self.on("videoUpdate", ({ videoEnabled, videoTrack }) => {
@@ -570,12 +1803,81 @@ meeting.self.on("videoUpdate", ({ videoEnabled, videoTrack }) => {
videoElement.srcObject = stream;
videoElement.play();
}
-});
+});
+```
+
+
+
+
+```jsx
+const videoEnabled = useRealtimeKitSelector((m) => m.self.videoEnabled);
+const videoTrack = useRealtimeKitSelector((m) => m.self.videoTrack);
+
+useEffect(() => {
+ if (videoEnabled && videoTrack) {
+ console.log("Video is enabled");
+ // Handle video track
+ }
+}, [videoEnabled, videoTrack]);
+```
+
+
+
+
+```kotlin
+meeting.addSelfEventListener(object : RtkSelfEventListener {
+ override fun onVideoUpdate(isEnabled: Boolean) {
+ if (isEnabled) {
+ // video is enabled, other participants can see local user
+ } else {
+ // video is disabled, other participants cannot see local user
+ }
+ }
+})
+```
+
+
+
+
+```swift
+extension MeetingViewModel: RtkSelfEventListener {
+ func onVideoUpdate(isEnabled: Bool) {
+ if (isEnabled) {
+ // video is enabled, other participants can see local user
+ } else {
+ // video is disabled, other participants cannot see local user
+ }
+ }
+}
+```
+
+
+
+
+```jsx
+const videoEnabled = useRealtimeKitSelector((m) => m.self.videoEnabled);
+const videoTrack = useRealtimeKitSelector((m) => m.self.videoTrack);
+
+useEffect(() => {
+ if (videoEnabled && videoTrack) {
+ console.log("Video is enabled");
+ // Handle video track
+ }
+}, [videoEnabled, videoTrack]);
```
-### Audio Update
+
+
+
+Flutter SDK uses a different event model. Monitor `videoEnabled` property changes.
+
+
+
+### Audio update
-Triggered when audio is enabled or disabled:
+Fires when audio is enabled or disabled:
+
+
```js
meeting.self.on("audioUpdate", ({ audioEnabled, audioTrack }) => {
@@ -588,9 +1890,78 @@ meeting.self.on("audioUpdate", ({ audioEnabled, audioTrack }) => {
});
```
-### Screen Share Update
+
+
+
+```jsx
+const audioEnabled = useRealtimeKitSelector((m) => m.self.audioEnabled);
+const audioTrack = useRealtimeKitSelector((m) => m.self.audioTrack);
+
+useEffect(() => {
+ if (audioEnabled && audioTrack) {
+ console.log("Audio is enabled");
+ // Handle audio track
+ }
+}, [audioEnabled, audioTrack]);
+```
+
+
+
+
+```kotlin
+meeting.addSelfEventListener(object : RtkSelfEventListener {
+ override fun onAudioUpdate(isEnabled: Boolean) {
+ if (isEnabled) {
+ // audio is enabled, other participants can hear local user
+ } else {
+ // audio is disabled, other participants cannot hear local user
+ }
+ }
+})
+```
+
+
+
+
+```swift
+extension MeetingViewModel: RtkSelfEventListener {
+ func onAudioUpdate(isEnabled: Bool) {
+ if (isEnabled) {
+ // audio is enabled, other participants can hear local user
+ } else {
+ // audio is disabled, other participants cannot hear local user
+ }
+ }
+}
+```
+
+
+
+
+```jsx
+const audioEnabled = useRealtimeKitSelector((m) => m.self.audioEnabled);
+const audioTrack = useRealtimeKitSelector((m) => m.self.audioTrack);
+
+useEffect(() => {
+ if (audioEnabled && audioTrack) {
+ console.log("Audio is enabled");
+ // Handle audio track
+ }
+}, [audioEnabled, audioTrack]);
+```
+
+
+
-Triggered when screen sharing starts or stops:
+Flutter SDK uses a different event model. Monitor `audioEnabled` property changes.
+
+
+
+### Screen share update
+
+Fires when screen sharing starts or stops:
+
+
```js
meeting.self.on(
@@ -613,9 +1984,100 @@ meeting.self.on(
);
```
-### Device Update
+
+
+
+```jsx
+const screenShareEnabled = useRealtimeKitSelector(
+ (m) => m.self.screenShareEnabled,
+);
+const screenShareTracks = useRealtimeKitSelector(
+ (m) => m.self.screenShareTracks,
+);
+
+useEffect(() => {
+ if (screenShareEnabled && screenShareTracks) {
+ console.log("Screen sharing is active");
+ // Handle screen share tracks
+ }
+}, [screenShareEnabled, screenShareTracks]);
+```
+
+
+
+
+```kotlin
+meeting.addSelfEventListener(object : RtkSelfEventListener {
+ override fun onScreenShareStartFailed(reason: String) {
+ // screen share failed to start
+ }
+
+ override fun onScreenShareUpdate(isEnabled: Boolean) {
+ if (isEnabled) {
+ // screen share is enabled
+ } else {
+ // screen share is disabled
+ }
+ }
+})
+```
+
+
+
+
+```swift
+meeting.addSelfEventListener(self)
+
+extension MeetingViewModel: RtkSelfEventListener {
+ func onRemovedFromMeeting() {
+ // User was removed from the meeting (kicked or meeting ended)
+ // Display alert or navigate to exit screen
+ }
+
+ func onMeetingRoomDisconnected() {
+ // Lost connection to the meeting room
+ // Display reconnection UI or error message
+ }
+}
+```
+
+You can also monitor the `roomJoined` property for state changes:
+
+```swift
+let isInMeeting = meeting.localUser.roomJoined
+```
+
+
+
+
+```jsx
+const screenShareEnabled = useRealtimeKitSelector(
+ (m) => m.self.screenShareEnabled,
+);
+const screenShareTracks = useRealtimeKitSelector(
+ (m) => m.self.screenShareTracks,
+);
+
+useEffect(() => {
+ if (screenShareEnabled && screenShareTracks) {
+ console.log("Screen sharing is active");
+ // Handle screen share tracks
+ }
+}, [screenShareEnabled, screenShareTracks]);
+```
+
+
+
+
+Flutter SDK uses a different event model. Monitor `screenShareEnabled` property changes.
+
+
+
+### Device update
-Triggered when the active device changes:
+Fires when the active device changes:
+
+
```js
meeting.self.on("deviceUpdate", ({ device }) => {
@@ -630,9 +2092,116 @@ meeting.self.on("deviceUpdate", ({ device }) => {
});
```
+
+
+
+```jsx
+useEffect(() => {
+ if (!meeting) return;
+
+ const handleDeviceUpdate = ({ device }) => {
+ if (device.kind === "audioinput") {
+ console.log("Microphone changed:", device.label);
+ } else if (device.kind === "videoinput") {
+ console.log("Camera changed:", device.label);
+ }
+ };
+
+ meeting.self.on("deviceUpdate", handleDeviceUpdate);
+
+ return () => {
+ meeting.self.off("deviceUpdate", handleDeviceUpdate);
+ };
+}, [meeting]);
+```
+
+
+
+
+```kotlin
+meeting.self.addSelfEventListener(object : RtkSelfEventListener() {
+ override fun onAudioDeviceChanged(device: AudioDevice) {
+ // Handle audio device change
+ println("Audio device changed: ${device.label}")
+ }
+
+ override fun onVideoDeviceChanged(device: VideoDevice) {
+ // Handle video device change
+ println("Video device changed: ${device.label}")
+ }
+})
+```
+
+
+
+
+```swift
+meeting.self.addSelfEventListener(self)
+
+// RtkSelfEventListener implementation
+func onAudioDeviceChanged(device: AudioDevice) {
+ // Handle audio device change
+ print("Audio device changed: \(device.label)")
+}
+
+func onVideoDeviceChanged(device: VideoDevice) {
+ // Handle video device change
+ print("Video device changed: \(device.label)")
+}
+```
+
+
+
+
+```jsx
+useEffect(() => {
+ if (!meeting) return;
+
+ const handleDeviceUpdate = ({ device }) => {
+ if (device.kind === "audioinput") {
+ console.log("Microphone changed:", device.label);
+ } else if (device.kind === "videoinput") {
+ console.log("Camera changed:", device.label);
+ }
+ };
+
+ meeting.self.on("deviceUpdate", handleDeviceUpdate);
+
+ return () => {
+ meeting.self.off("deviceUpdate", handleDeviceUpdate);
+ };
+}, [meeting]);
+```
+
+
+
+
+```dart
+class DeviceChangeListener extends RtkSelfEventListener {
+ @override
+ void onAudioDeviceChanged(AudioDevice audioDevice) {
+ // Handle audio device change
+ print('Audio device changed: ${audioDevice.label}');
+ }
+
+ @override
+ void onVideoDeviceChanged(VideoDevice videoDevice) {
+ // Handle video device change
+ print('Video device changed: ${videoDevice.label}');
+ }
+}
+
+// Add the listener
+meeting.addSelfEventListener(DeviceChangeListener());
+```
+
+
+
### Device List Update
-Triggered when the list of available devices changes (device plugged in/out):
+Triggered when the list of available devices changes (device plugged in or out):
+
+
```js
meeting.self.on("deviceListUpdate", ({ added, removed, devices }) => {
@@ -643,10 +2212,110 @@ meeting.self.on("deviceListUpdate", ({ added, removed, devices }) => {
});
```
+
+
+
+```jsx
+useEffect(() => {
+ if (!meeting) return;
+
+ const handleDeviceListUpdate = ({ added, removed, devices }) => {
+ console.log("Device list updated");
+ console.log("Added devices:", added);
+ console.log("Removed devices:", removed);
+ console.log("All devices:", devices);
+ };
+
+ meeting.self.on("deviceListUpdate", handleDeviceListUpdate);
+
+ return () => {
+ meeting.self.off("deviceListUpdate", handleDeviceListUpdate);
+ };
+}, [meeting]);
+```
+
+
+
+
+```kotlin
+meeting.addSelfEventListener(object : RtkSelfEventListener {
+ // Triggered when audio devices are added or removed
+ override fun onAudioDevicesUpdated() {
+ val audioDevices = meeting.localUser.getAudioDevices()
+ // Update UI with new audio device list
+ }
+})
+```
+
+
+
+
+```swift
+meeting.addSelfEventListener(object: RtkSelfEventListener {
+ // Triggered when audio devices are added or removed
+ func onAudioDevicesUpdated() {
+ let audioDevices = meeting.localUser.getAudioDevices()
+ // Update UI with new audio device list
+ }
+})
+```
+
+
+
+
+```jsx
+useEffect(() => {
+ if (!meeting) return;
+
+ const handleDeviceListUpdate = ({ added, removed, devices }) => {
+ console.log("Device list updated");
+ console.log("Added devices:", added);
+ console.log("Removed devices:", removed);
+ console.log("All devices:", devices);
+ };
+
+ meeting.self.on("deviceListUpdate", handleDeviceListUpdate);
+
+ return () => {
+ meeting.self.off("deviceListUpdate", handleDeviceListUpdate);
+ };
+}, [meeting]);
+```
+
+
+
+
+```dart
+class DeviceListListener extends RtkSelfEventListener {
+ final RealtimekitClient meeting;
+
+ DeviceListListener(this.meeting);
+
+ @override
+ void onAudioDevicesUpdated(List devices) {
+ // Triggered when audio devices are added or removed
+ // Update UI with new audio device list
+ }
+
+ @override
+ void onVideoDeviceChanged(VideoDevice videoDevice) {
+ // Handle video device change
+ print('Video device changed to: ${videoDevice.label}');
+ }
+}
+
+// Add the listener
+meeting.addSelfEventListener(DeviceListListener(meeting));
+```
+
+
+
### Network Quality Score
Monitor your own network quality:
+
+
```js
meeting.self.on(
"mediaScoreUpdate",
@@ -708,10 +2377,95 @@ The `scoreStats` object provides detailed statistics:
}
```
+
+
+
+```jsx
+useEffect(() => {
+ if (!meeting) return;
+
+ const handleMediaScoreUpdate = ({
+ kind,
+ isScreenshare,
+ score,
+ scoreStats,
+ }) => {
+ if (kind === "video") {
+ console.log(
+ `Your ${isScreenshare ? "screenshare" : "video"} quality score is`,
+ score,
+ );
+ }
+
+ if (score < 5) {
+ console.log("Your media quality is poor");
+ }
+ };
+
+ meeting.self.on("mediaScoreUpdate", handleMediaScoreUpdate);
+
+ return () => {
+ meeting.self.off("mediaScoreUpdate", handleMediaScoreUpdate);
+ };
+}, [meeting]);
+```
+
+
+
+
+Android SDK does not currently expose network quality scores.
+
+
+
+
+iOS SDK does not currently expose network quality scores.
+
+
+
+
+```jsx
+useEffect(() => {
+ if (!meeting) return;
+
+ const handleMediaScoreUpdate = ({
+ kind,
+ isScreenshare,
+ score,
+ scoreStats,
+ }) => {
+ if (kind === "video") {
+ console.log(
+ `Your ${isScreenshare ? "screenshare" : "video"} quality score is`,
+ score,
+ );
+ }
+
+ if (score < 5) {
+ console.log("Your media quality is poor");
+ }
+ };
+
+ meeting.self.on("mediaScoreUpdate", handleMediaScoreUpdate);
+
+ return () => {
+ meeting.self.off("mediaScoreUpdate", handleMediaScoreUpdate);
+ };
+}, [meeting]);
+```
+
+
+
+
+Flutter SDK does not currently expose network quality scores.
+
+
+
### Permission Updates
Triggered when permissions are updated dynamically:
+
+
```js
// Listen to specific permission updates
meeting.self.permissions.on("chatUpdate", () => {
@@ -733,10 +2487,55 @@ meeting.self.permissions.on("*", () => {
});
```
+
+
+
+Monitor permissions using selectors:
+
+```jsx
+const permissions = useRealtimeKitSelector((m) => m.self.permissions);
+
+useEffect(() => {
+ console.log("Permissions updated:", permissions);
+}, [permissions]);
+```
+
+
+
+
+Android SDK uses a different permissions model. Refer to the Android-specific documentation.
+
+
+
+
+iOS SDK uses a different permissions model. Refer to the iOS-specific documentation.
+
+
+
+
+Monitor permissions using selectors:
+
+```jsx
+const permissions = useRealtimeKitSelector((m) => m.self.permissions);
+
+useEffect(() => {
+ console.log("Permissions updated:", permissions);
+}, [permissions]);
+```
+
+
+
+
+Flutter SDK uses a different permissions model. Refer to the Flutter-specific documentation.
+
+
+
### Media Permission Errors
Triggered when media permissions are denied or media capture fails:
+
+
```js
meeting.self.on("mediaPermissionError", ({ message, kind }) => {
console.log(`Failed to capture ${kind}: ${message}`);
@@ -757,354 +2556,344 @@ meeting.self.on("mediaPermissionError", ({ message, kind }) => {
- `message`: `'DENIED'`, `'SYSTEM_DENIED'`, `'COULD_NOT_START'`
- `kind`: `'audio'`, `'video'`, `'screenshare'`
-
-
-
-### Room Joined
+
+
```jsx
-const roomJoined = useRealtimeKitSelector((m) => m.self.roomJoined);
-
useEffect(() => {
- if (roomJoined) {
- console.log("Successfully joined the meeting");
- }
-}, [roomJoined]);
+ if (!meeting) return;
+
+ const handlePermissionError = ({ message, kind }) => {
+ console.log(`Failed to capture ${kind}: ${message}`);
+
+ if (message === "DENIED") {
+ // Show UI to guide user to grant permissions
+ }
+ };
+
+ meeting.self.on("mediaPermissionError", handlePermissionError);
+
+ return () => {
+ meeting.self.off("mediaPermissionError", handlePermissionError);
+ };
+}, [meeting]);
```
-Or use event listener:
+
+
+
+```kotlin
+meeting.addSelfEventListener(object : RtkSelfEventListener {
+ override fun onMeetingRoomJoinedWithoutCameraPermission() {
+ // meeting joined without camera permission
+ }
+
+ override fun onMeetingRoomJoinedWithoutMicPermission() {
+ // meeting joined without microphone permission
+ }
+})
+```
+
+
+
+
+```swift
+meeting.addSelfEventListener(self)
+
+extension MeetingViewModel: RtkSelfEventListener {
+ func onMeetingRoomJoinedWithoutCameraPermission() {
+ // meeting joined without camera permission
+ }
+
+ func onMeetingRoomJoinedWithoutMicPermission() {
+ // meeting joined without microphone permission
+ }
+}
+```
+
+You can also check permission status using properties:
+
+```swift
+let hasCameraPermission = meeting.localUser.isCameraPermissionGranted
+let hasMicPermission = meeting.localUser.isMicrophonePermissionGranted
+```
+
+
+
```jsx
useEffect(() => {
if (!meeting) return;
- const handleRoomJoined = () => {
- console.log("Successfully joined the meeting");
+ const handlePermissionError = ({ message, kind }) => {
+ console.log(`Failed to capture ${kind}: ${message}`);
+
+ if (message === "DENIED") {
+ // Show UI to guide user to grant permissions
+ }
};
- meeting.self.on("roomJoined", handleRoomJoined);
+ meeting.self.on("mediaPermissionError", handlePermissionError);
return () => {
- meeting.self.off("roomJoined", handleRoomJoined);
+ meeting.self.off("mediaPermissionError", handlePermissionError);
};
}, [meeting]);
```
-### Room Left
+
+
-```jsx
-const roomJoined = useRealtimeKitSelector((m) => m.self.roomJoined);
+```dart
+class PermissionListener extends RtkSelfEventListener {
+ @override
+ void onMeetingRoomJoinedWithoutCameraPermission() {
+ // Meeting joined without camera permission
+ }
-useEffect(() => {
- if (!roomJoined) {
- console.log("Left the meeting");
- }
-}, [roomJoined]);
+ @override
+ void onMeetingRoomJoinedWithoutMicPermission() {
+ // Meeting joined without microphone permission
+ }
+}
+
+// Add the listener
+meeting.addSelfEventListener(PermissionListener());
```
-Or use event listener for detailed state:
+You can also check permission status using properties:
-```jsx
-meeting.self.on("roomLeft", ({ state }) => {
- if (state === "left") {
- console.log("User voluntarily left");
- } else if (state === "kicked") {
- console.log("User was kicked");
- }
-});
+```dart
+final hasCameraPermission = meeting.localUser.isCameraPermissionGranted;
+final hasMicPermission = meeting.localUser.isMicrophonePermissionGranted;
```
-### Video Update
+
+
+### Waitlist Status
+
+For meetings with waiting room enabled:
+
+
+
+Monitor the `roomState` property for waitlist status. The value `'waitlisted'` indicates the user is in the waiting room.
+
+
+
```jsx
-const videoEnabled = useRealtimeKitSelector((m) => m.self.videoEnabled);
-const videoTrack = useRealtimeKitSelector((m) => m.self.videoTrack);
+const roomState = useRealtimeKitSelector((m) => m.self.roomState);
useEffect(() => {
- if (videoEnabled && videoTrack) {
- console.log("Video is enabled");
- // Handle video track
+ if (roomState === "waitlisted") {
+ console.log("Waiting for host to admit you");
}
-}, [videoEnabled, videoTrack]);
+}, [roomState]);
```
-### Audio Update
+
+
-```jsx
-const audioEnabled = useRealtimeKitSelector((m) => m.self.audioEnabled);
-const audioTrack = useRealtimeKitSelector((m) => m.self.audioTrack);
+```kotlin
+// Get current waitlist status
+val waitListStatus = meeting.localUser.waitListStatus
-useEffect(() => {
- if (audioEnabled && audioTrack) {
- console.log("Audio is enabled");
- // Handle audio track
- }
-}, [audioEnabled, audioTrack]);
+// Listen to waitlist status changes
+meeting.addSelfEventListener(object : RtkSelfEventListener {
+ override fun onWaitListStatusUpdate(waitListStatus: WaitListStatus) {
+ // handle waitlist status here
+ }
+})
```
-### Screen Share Update
+
+
-```jsx
-const screenShareEnabled = useRealtimeKitSelector(
- (m) => m.self.screenShareEnabled,
-);
-const screenShareTracks = useRealtimeKitSelector(
- (m) => m.self.screenShareTracks,
-);
+```swift
+// Get current waitlist status
+let waitListStatus = meeting.localUser.waitListStatus
-useEffect(() => {
- if (screenShareEnabled && screenShareTracks) {
- console.log("Screen sharing is active");
- // Handle screen share tracks
- }
-}, [screenShareEnabled, screenShareTracks]);
+// Listen to waitlist status changes
+extension MeetingViewModel: RtkSelfEventListener {
+ func onWaitlistedUpdate() {
+ // handle waitlist update
+ }
+}
```
-### Device Update
+
+
```jsx
+const roomState = useRealtimeKitSelector((m) => m.self.roomState);
+
useEffect(() => {
- if (!meeting) return;
+ if (roomState === "waitlisted") {
+ console.log("Waiting for host to admit you");
+ }
+}, [roomState]);
+```
- const handleDeviceUpdate = ({ device }) => {
- if (device.kind === "audioinput") {
- console.log("Microphone changed:", device.label);
- } else if (device.kind === "videoinput") {
- console.log("Camera changed:", device.label);
- }
- };
+
+
- meeting.self.on("deviceUpdate", handleDeviceUpdate);
+Flutter SDK uses a different event model. Monitor `stageStatus` or relevant properties for waitlist status.
- return () => {
- meeting.self.off("deviceUpdate", handleDeviceUpdate);
- };
-}, [meeting]);
-```
+
-### Network Quality Score
+### iOS-Specific Events
-```jsx
-useEffect(() => {
- if (!meeting) return;
+The iOS SDK provides additional platform-specific events:
- const handleMediaScoreUpdate = ({
- kind,
- isScreenshare,
- score,
- scoreStats,
- }) => {
- if (kind === "video") {
- console.log(
- `Your ${isScreenshare ? "screenshare" : "video"} quality score is`,
- score,
- );
- }
+
- if (score < 5) {
- console.log("Your media quality is poor");
- }
- };
+#### Proximity Sensor
- meeting.self.on("mediaScoreUpdate", handleMediaScoreUpdate);
+Triggered when the proximity sensor detects a change (useful for earpiece detection):
- return () => {
- meeting.self.off("mediaScoreUpdate", handleMediaScoreUpdate);
- };
-}, [meeting]);
+```swift
+extension MeetingViewModel: RtkSelfEventListener {
+ func onProximityChanged() {
+ // Handle proximity sensor change
+ // Useful for detecting when device is near user's ear
+ }
+}
```
-### Media Permission Errors
-
-```jsx
-useEffect(() => {
- if (!meeting) return;
-
- const handlePermissionError = ({ message, kind }) => {
- console.log(`Failed to capture ${kind}: ${message}`);
+#### Webinar Events
- if (message === "DENIED") {
- // Show UI to guide user to grant permissions
- }
- };
+For webinar-specific functionality:
- meeting.self.on("mediaPermissionError", handlePermissionError);
+```swift
+extension MeetingViewModel: RtkSelfEventListener {
+ func onWebinarPresentRequestReceived() {
+ // Handle request to present in webinar
+ }
- return () => {
- meeting.self.off("mediaPermissionError", handlePermissionError);
- };
-}, [meeting]);
+ func onStoppedPresenting() {
+ // Handle stopped presenting in webinar
+ }
+}
```
-
-
+#### Room Messages
-## Advanced Features
+Listen to broadcast messages in the room:
-
-
+```swift
+extension MeetingViewModel: RtkSelfEventListener {
+ func onRoomMessage() {
+ // Handle room broadcast message
+ }
+}
+```
-### Update Media Resolution
+
-Change video or screen share resolution at runtime:
+## Pin and Unpin
-```js
-// Update camera resolution
-await meeting.self.updateVideoConstraints({
- width: { ideal: 1920 },
- height: { ideal: 1080 },
-});
+Pin or unpin yourself in the meeting (requires appropriate permissions):
-// Update screen share resolution
-await meeting.self.updateScreenshareConstraints({
- width: { ideal: 1920 },
- height: { ideal: 1080 },
-});
-```
+
-### Video Middlewares
+Web SDK does not currently support pinning the local participant.
-Add effects and filters to your video stream:
+
+
-```js
-// Create a middleware (e.g., retro filter)
-function RetroTheme() {
- return (canvas, ctx) => {
- ctx.filter = "grayscale(1)";
- ctx.shadowColor = "#000";
- ctx.shadowBlur = 20;
- ctx.lineWidth = 50;
- ctx.strokeStyle = "#000";
- ctx.strokeRect(0, 0, canvas.width, canvas.height);
- };
-}
+Web SDK does not currently support pinning the local participant.
-// Add the video middleware
-meeting.self.addVideoMiddleware(RetroTheme);
+
+
-// Remove the video middleware
-meeting.self.removeVideoMiddleware(RetroTheme);
-```
+Android SDK does not currently support pinning the local participant.
-### Audio Middlewares
+
+
-Process audio streams with custom middlewares:
+```swift
+// Pin yourself
+meeting.localUser.pin()
-```js
-// Add audio middleware
-meeting.self.addAudioMiddleware(YourAudioMiddleware);
+// Unpin yourself
+meeting.localUser.unpin()
-// Remove audio middleware
-meeting.self.removeAudioMiddleware(YourAudioMiddleware);
+// Check if pinned
+let isPinned = meeting.localUser.isPinned
```
-### Pin/Unpin Self
-
-Pin or unpin yourself in the meeting:
+
+
-```js
+```jsx
// Pin yourself
await meeting.self.pin();
// Unpin yourself
await meeting.self.unpin();
-// Check pinned status
+// Check if pinned
const isPinned = meeting.self.isPinned;
```
-
-
+
+
-### Update Media Resolution
+Flutter SDK does not currently support pinning the local participant.
-Change video or screen share resolution at runtime:
+
-```jsx
-// Update camera resolution
-await meeting.self.updateVideoConstraints({
- width: { ideal: 1920 },
- height: { ideal: 1080 },
-});
+## Update Media Constraints
-// Update screen share resolution
-await meeting.self.updateScreenshareConstraints({
- width: { ideal: 1920 },
- height: { ideal: 1080 },
-});
-```
+Update video or screenshare resolution at runtime:
-### Video Middlewares
+
-Add effects and filters to your video stream:
+Web SDK does not currently expose runtime constraint updates for local participant.
-```jsx
-// Create a middleware (e.g., retro filter)
-function RetroTheme() {
- return (canvas, ctx) => {
- ctx.filter = "grayscale(1)";
- ctx.shadowColor = "#000";
- ctx.shadowBlur = 20;
- ctx.lineWidth = 50;
- ctx.strokeStyle = "#000";
- ctx.strokeRect(0, 0, canvas.width, canvas.height);
- };
-}
+
+
-function VideoEffects() {
- const [meeting] = useRealtimeKitClient();
+Web SDK does not currently expose runtime constraint updates for local participant.
- const addRetroEffect = () => {
- meeting.self.addVideoMiddleware(RetroTheme);
- };
+
+
- const removeRetroEffect = () => {
- meeting.self.removeVideoMiddleware(RetroTheme);
- };
+Android SDK does not currently expose runtime constraint updates.
- return (
- <>
-
-
- >
- );
-}
-```
+
+
-### Audio Middlewares
+iOS SDK does not currently expose runtime constraint updates.
-Process audio streams with custom middlewares:
+
+
-```jsx
-// Add audio middleware
-meeting.self.addAudioMiddleware(YourAudioMiddleware);
+### Update Video Constraints
+
+Update camera resolution while already streaming:
-// Remove audio middleware
-meeting.self.removeAudioMiddleware(YourAudioMiddleware);
+```jsx
+meeting.self.updateVideoConstraints({
+ width: { ideal: 1920 },
+ height: { ideal: 1080 },
+});
```
-### Pin/Unpin Self
+### Update Screenshare Constraints
-Pin or unpin yourself in the meeting:
+Update screenshare resolution while already streaming:
```jsx
-function PinControls() {
- const [meeting] = useRealtimeKitClient();
- const isPinned = useRealtimeKitSelector((m) => m.self.isPinned);
+meeting.self.updateScreenshareConstraints({
+ width: { ideal: 1920 },
+ height: { ideal: 1080 },
+});
+```
- const togglePin = async () => {
- if (isPinned) {
- await meeting.self.unpin();
- } else {
- await meeting.self.pin();
- }
- };
+
+
- return (
-
- );
-}
-```
+Flutter SDK does not currently expose runtime constraint updates.
-
-
+