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 ( - - - +### 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 ` + + +### 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. - - +