diff --git a/src/content/docs/realtime/realtimekit/core/remote-participant.mdx b/src/content/docs/realtime/realtimekit/core/remote-participant.mdx index 578163189591678..9299a82ab334263 100644 --- a/src/content/docs/realtime/realtimekit/core/remote-participant.mdx +++ b/src/content/docs/realtime/realtimekit/core/remote-participant.mdx @@ -6,7 +6,8 @@ sidebar: order: 5 --- -import { Tabs, TabItem } from "~/components"; +import RTKSDKSelector from "~/components/realtimekit/RTKSDKSelector/RTKSDKSelector.astro"; +import RTKCodeSnippet from "~/components/realtimekit/RTKCodeSnippet/RTKCodeSnippet.astro"; This guide explains how to access participant data, display videos, handle events, and manage participant permissions in your RealtimeKit meetings. @@ -16,11 +17,85 @@ This page assumes you've already initialized the SDK and understand the meeting ## Participant Maps - - + The data regarding all meeting participants is stored under `meeting.participants`. These do not include the local user. + + +The `meeting.participants` object contains the following maps: + +- **`joined`** - All participants currently in the meeting (excluding the local user) +- **`waitlisted`** - All participants waiting to join the meeting +- **`active`** - All participants whose media is subscribed to (participants that should be displayed on screen) +- **`pinned`** - All pinned participants in the meeting + +If you're building a video/audio grid, you'd use the `active` map. To display a list of all participants, use the `joined` map. + +Each participant in these maps is of type `RTKParticipant`. + + + + + +The `meeting.participants` object contains the following maps: + +- **`joined`** - All participants currently in the meeting (excluding the local user) +- **`waitlisted`** - All participants waiting to join the meeting +- **`active`** - All participants whose media is subscribed to (participants that should be displayed on screen) +- **`pinned`** - All pinned participants in the meeting + +If you're building a video/audio grid, you'd use the `active` map. To display a list of all participants, use the `joined` map. + +Each participant in these maps is of type `RTKParticipant`. + + + + + +The `meeting.participants` object contains the following lists: + +- **`joined`** - All participants currently in the meeting (excluding the local user) +- **`waitlisted`** - All participants waiting to join the meeting +- **`active`** - All participants whose media is subscribed to (participants that should be displayed on screen) +- **`pinned`** - All pinned participants in the meeting +- **`screenShares`** - All participants who are sharing their screen + +If you're building a video/audio grid, you'd use the `active` list. To display a list of all participants, use the `joined` list. + + + + + +The `meeting.participants` object contains the following lists: + +- **`joined`** - All participants currently in the meeting (excluding the local user) +- **`waitlisted`** - All participants waiting to join the meeting +- **`active`** - All participants whose media is subscribed to (participants that should be displayed on screen) +- **`pinned`** - All pinned participants in the meeting +- **`screenShares`** - All participants who are sharing their screen + +If you're building a video/audio grid, you'd use the `active` list. To display a list of all participants, use the `joined` list. + + + + + +The `meeting.participants` object contains the following lists: + +- **`joined`** - All participants currently in the meeting (excluding the local user) +- **`waitlisted`** - All participants waiting to join the meeting +- **`active`** - All participants whose media is subscribed to (participants that should be displayed on screen) +- **`pinned`** - All pinned participants in the meeting + +If you're building a video/audio grid, you'd use the `active` list. To display a list of all participants, use the `joined` list. + +Each participant in these lists is of type `RtkRemoteParticipant`. + + + + + The `meeting.participants` object contains the following maps: - **`joined`** - All participants currently in the meeting (excluding the local user) @@ -32,8 +107,12 @@ If you're building a video/audio grid, you'd use the `active` map. To display a Each participant in these maps is of type `RTKParticipant`. + + ### Access Participant Maps + + ```js // Get all joined participants const joinedParticipants = meeting.participants.joined; @@ -48,60 +127,98 @@ const pinnedParticipants = meeting.participants.pinned; const waitlistedParticipants = meeting.participants.waitlisted; ``` -### Listen to Participant Map Events + -Each participant map emits `participantJoined` and `participantLeft` events: + -```js -// Listen for when a participant gets pinned -meeting.participants.pinned.on("participantJoined", (participant) => { - console.log(`Participant ${participant.name} got pinned`); -}); +Use the `useRealtimeKitSelector` hook to access participant maps: -// Listen for when a participant gets unpinned -meeting.participants.pinned.on("participantLeft", (participant) => { - console.log(`Participant ${participant.name} got unpinned`); -}); +```jsx +import { useRealtimeKitSelector } from "@cloudflare/realtimekit-react"; + +// Get all joined participants +const joinedParticipants = useRealtimeKitSelector((m) => m.participants.joined); + +// Get active participants (those on screen) +const activeParticipants = useRealtimeKitSelector((m) => m.participants.active); + +// Get pinned participants +const pinnedParticipants = useRealtimeKitSelector((m) => m.participants.pinned); + +// Get waitlisted participants +const waitlistedParticipants = useRealtimeKitSelector( + (m) => m.participants.waitlisted, +); ``` -### Participant Map Properties + -```js -// Number of participants joined in the meeting -console.log(meeting.participants.count); + -// Number of pages available in paginated mode -console.log(meeting.participants.pageCount); +```kotlin +// Get all joined participants +val joinedParticipants: List = meeting.participants.joined -// Maximum number of participants in active state -console.log(meeting.participants.maxActiveParticipantsCount); +// Get active participants (those on screen) +val activeParticipants: List = meeting.participants.active -// ParticipantId of the last participant who spoke -console.log(meeting.participants.lastActiveSpeaker); +// Get pinned participants +val pinnedParticipants: List = meeting.participants.pinned + +// Get waitlisted participants +val waitlistedParticipants: List = meeting.participants.waitlisted + +// Get screen sharing participants +val screenShareParticipants: List = meeting.participants.screenShares ``` - - + -The data regarding all meeting participants is stored under `meeting.participants`. These do not include the local user. + -The `meeting.participants` object contains the following maps: +```swift +// Get all joined participants +var joinedParticipants: [RtkRemoteParticipant] = meeting.participants.joined -- **`joined`** - All participants currently in the meeting (excluding the local user) -- **`waitlisted`** - All participants waiting to join the meeting -- **`active`** - All participants whose media is subscribed to (participants that should be displayed on screen) -- **`pinned`** - All pinned participants in the meeting +// Get active participants (those on screen) +var activeParticipants: [RtkRemoteParticipant] = meeting.participants.active -If you're building a video/audio grid, you'd use the `active` map. To display a list of all participants, use the `joined` map. +// Get pinned participants +var pinnedParticipants: [RtkRemoteParticipant] = meeting.participants.pinned -Each participant in these maps is of type `RTKParticipant`. +// Get waitlisted participants +var waitlistedParticipants: [RtkRemoteParticipant] = meeting.participants.waitlisted -### Access Participant Maps +// Get screen sharing participants +var screenShareParticipants: [RtkRemoteParticipant] = meeting.participants.screenShares +``` + + + + + +```dart +// Get all joined participants +final joinedParticipants = meeting.participants.joined; + +// Get active participants (those on screen) +final activeParticipants = meeting.participants.active; + +// Get pinned participants +final pinnedParticipants = meeting.participants.pinned; + +// Get waitlisted participants +final waitlistedParticipants = meeting.participants.waitlisted; +``` + + + + Use the `useRealtimeKitSelector` hook to access participant maps: -```jsx -import { useRealtimeKitSelector } from "@cloudflare/realtimekit-react"; +```tsx +import { useRealtimeKitSelector } from "@cloudflare/realtimekit-react-native"; // Get all joined participants const joinedParticipants = useRealtimeKitSelector((m) => m.participants.joined); @@ -118,12 +235,35 @@ const waitlistedParticipants = useRealtimeKitSelector( ); ``` + + ### Listen to Participant Map Events + + +Each participant map emits `participantJoined` and `participantLeft` events: + +```js +// Listen for when a participant gets pinned +meeting.participants.pinned.on("participantJoined", (participant) => { + console.log(`Participant ${participant.name} got pinned`); +}); + +// Listen for when a participant gets unpinned +meeting.participants.pinned.on("participantLeft", (participant) => { + console.log(`Participant ${participant.name} got unpinned`); +}); +``` + + + + + You can also use event listeners for specific actions: ```jsx import { useRealtimeKitClient } from "@cloudflare/realtimekit-react"; +import { useEffect } from "react"; function ParticipantListener() { const [meeting] = useRealtimeKitClient(); @@ -150,483 +290,1839 @@ function ParticipantListener() { } ``` -### Participant Map Properties + -```jsx -// Number of participants joined in the meeting -const participantCount = useRealtimeKitSelector((m) => m.participants.count); + -// Number of pages available in paginated mode -const pageCount = useRealtimeKitSelector((m) => m.participants.pageCount); +Use event listeners to monitor participant changes: -// Maximum number of participants in active state -const maxActiveCount = useRealtimeKitSelector( - (m) => m.participants.maxActiveParticipantsCount, -); +```kotlin +meeting.addParticipantsEventListener(object : RtkParticipantsEventListener { + override fun onParticipantJoin(participant: RtkRemoteParticipant) { + // Called when a participant joins the meeting + } -// ParticipantId of the last participant who spoke -const lastActiveSpeaker = useRealtimeKitSelector( - (m) => m.participants.lastActiveSpeaker, -); -``` + override fun onParticipantLeave(participant: RtkRemoteParticipant) { + // Called when a participant leaves the meeting + } - - + override fun onActiveParticipantsChanged(active: List) { + // Called when active participants change + } -## Participant View Modes + override fun onParticipantPinned(participant: RtkRemoteParticipant) { + // Called when a participant is pinned + } -The view mode indicates whether participants are populated in `ACTIVE_GRID` mode or `PAGINATED` mode. + override fun onParticipantUnpinned(participant: RtkRemoteParticipant) { + // Called when a participant is unpinned + } -- **`ACTIVE_GRID` mode** - Participants are automatically replaced in `meeting.participants.active` based on who is speaking or who has their video turned on -- **`PAGINATED` mode** - Participants in `meeting.participants.active` are fixed. Use `setPage()` to change the active participants + // ... other methods +}) +``` - - + -### Set View Mode + -```js -// Set the view mode to paginated -await meeting.participants.setViewMode("PAGINATED"); +Use event listeners to monitor participant changes: -// Set the view mode to active grid -await meeting.participants.setViewMode("ACTIVE_GRID"); -``` +```swift +extension MeetingViewModel: RtkParticipantsEventListener { + func onParticipantJoin(participant: RtkRemoteParticipant) { + // Called when a participant joins the meeting + } -### Set Page in Paginated Mode + func onParticipantLeave(participant: RtkRemoteParticipant) { + // Called when a participant leaves the meeting + } -```js -// Switch to second page -await meeting.participants.setPage(2); -``` + func onActiveParticipantsChanged(active: [RtkRemoteParticipant]) { + // Called when active participants change + } - - + func onParticipantPinned(participant: RtkRemoteParticipant) { + // Called when a participant is pinned + } -### Set View Mode + func onParticipantUnpinned(participant: RtkRemoteParticipant) { + // Called when a participant is unpinned + } -```jsx -// Set the view mode to paginated -await meeting.participants.setViewMode("PAGINATED"); + // ... other methods +} -// Set the view mode to active grid -await meeting.participants.setViewMode("ACTIVE_GRID"); +meeting.addParticipantsEventListener(self) ``` -### Set Page in Paginated Mode - -```jsx -// Switch to second page -await meeting.participants.setPage(2); -``` + -### Monitor View Mode + -```jsx -const viewMode = useRealtimeKitSelector((m) => m.participants.viewMode); -const currentPage = useRealtimeKitSelector((m) => m.participants.currentPage); -``` +Use event listeners to monitor participant changes: - - +```dart +class ParticipantsNotifier extends RtkParticipantsEventListener { + @override + void onParticipantJoin(RtkRemoteParticipant participant) { + // Called when a participant joins the meeting + } -## Waiting Room Methods + @override + void onParticipantLeave(RtkRemoteParticipant participant) { + // Called when a participant leaves the meeting + } - - + @override + void onActiveParticipantsChanged(List active) { + // Called when active participants change + } -### Accept Waiting Room Request + @override + void onParticipantPinned(RtkRemoteParticipant participant) { + // Called when a participant is pinned + } -```js -await meeting.participants.acceptWaitingRoomRequest(participantId); -``` + @override + void onParticipantUnpinned(RtkRemoteParticipant participant) { + // Called when a participant is unpinned + } -### Reject Waiting Room Request + // ... other methods +} -```js -await meeting.participants.rejectWaitingRoomRequest(participantId); +meeting.addParticipantsEventListener(ParticipantsNotifier()); ``` - - + -### Accept Waiting Room Request + -```jsx -await meeting.participants.acceptWaitingRoomRequest(participantId); -``` +You can also use event listeners for specific actions: -### Reject Waiting Room Request +```tsx +import { useRealtimeKitClient } from "@cloudflare/realtimekit-react-native"; +import { useEffect } from "react"; -```jsx -await meeting.participants.rejectWaitingRoomRequest(participantId); -``` +function ParticipantListener() { + const [meeting] = useRealtimeKitClient(); - - + useEffect(() => { + if (!meeting) return; -## Participant Object + const handleParticipantPinned = (participant) => { + console.log(`Participant ${participant.name} got pinned`); + }; -The participant object contains all information related to a particular participant, including their video/audio/screenshare streams, name, and state variables. + meeting.participants.pinned.on( + "participantJoined", + handleParticipantPinned, + ); -### Participant Properties + return () => { + meeting.participants.pinned.off( + "participantJoined", + handleParticipantPinned, + ); + }; + }, [meeting]); +} +``` -**Media Properties:** + -- `videoEnabled` - Set to `true` if the participant's camera is on -- `audioEnabled` - Set to `true` if the participant is unmuted -- `screenShareEnabled` - Set to `true` if the participant is sharing their screen -- `videoTrack` - The video track of the participant -- `audioTrack` - The audio track of the participant -- `screenShareTracks` - The video and audio tracks of the participant's screen share +### Participant Map Properties + + + +```js +// Number of participants joined in the meeting +console.log(meeting.participants.count); + +// Number of pages available in paginated mode +console.log(meeting.participants.pageCount); + +// Maximum number of participants in active state +console.log(meeting.participants.maxActiveParticipantsCount); + +// ParticipantId of the last participant who spoke +console.log(meeting.participants.lastActiveSpeaker); +``` + + + + + +```jsx +// Number of participants joined in the meeting +const participantCount = useRealtimeKitSelector((m) => m.participants.count); + +// Number of pages available in paginated mode +const pageCount = useRealtimeKitSelector((m) => m.participants.pageCount); + +// Maximum number of participants in active state +const maxActiveCount = useRealtimeKitSelector( + (m) => m.participants.maxActiveParticipantsCount, +); + +// ParticipantId of the last participant who spoke +const lastActiveSpeaker = useRealtimeKitSelector( + (m) => m.participants.lastActiveSpeaker, +); +``` + + + + + +```kotlin +// Number of participants joined in the meeting +val participantCount = meeting.participants.joined.size + +// Access pagination properties +val maxNumberOnScreen = meeting.participants.maxNumberOnScreen +val currentPageNumber = meeting.participants.currentPageNumber +val pageCount = meeting.participants.pageCount +val canGoNextPage = meeting.participants.canGoNextPage +val canGoPreviousPage = meeting.participants.canGoPreviousPage +``` + + + + + +```swift +// Number of participants joined in the meeting +let participantCount = meeting.participants.joined.count + +// Access pagination properties +let maxNumberOnScreen = meeting.participants.maxNumberOnScreen +let currentPageNumber = meeting.participants.currentPageNumber +let pageCount = meeting.participants.pageCount +let canGoNextPage = meeting.participants.canGoNextPage +let canGoPreviousPage = meeting.participants.canGoPreviousPage +``` + + + + + +```dart +// Number of participants joined in the meeting +final participantCount = meeting.participants.joined.length; + +// Access pagination properties +final currentPageNumber = meeting.participants.currentPageNumber; +final pageCount = meeting.participants.pageCount; +final canGoNextPage = meeting.participants.isNextPagePossible; +final canGoPreviousPage = meeting.participants.isPreviousPagePossible; +``` + + + + + +```tsx +// Number of participants joined in the meeting +const participantCount = useRealtimeKitSelector((m) => m.participants.count); + +// Number of pages available in paginated mode +const pageCount = useRealtimeKitSelector((m) => m.participants.pageCount); + +// Maximum number of participants in active state +const maxActiveCount = useRealtimeKitSelector( + (m) => m.participants.maxActiveParticipantsCount, +); + +// ParticipantId of the last participant who spoke +const lastActiveSpeaker = useRealtimeKitSelector( + (m) => m.participants.lastActiveSpeaker, +); +``` + + + +## Participant View Modes + +The view mode indicates whether participants are populated in `ACTIVE_GRID` mode or `PAGINATED` mode. + +- **`ACTIVE_GRID` mode** - Participants are automatically replaced in `meeting.participants.active` based on who is speaking or who has their video turned on +- **`PAGINATED` mode** - Participants in `meeting.participants.active` are fixed. Use `setPage()` to change the active participants + +### Set View Mode + + + +```js +// Set the view mode to paginated +await meeting.participants.setViewMode("PAGINATED"); + +// Set the view mode to active grid +await meeting.participants.setViewMode("ACTIVE_GRID"); +``` + + + + + +```jsx +// Set the view mode to paginated +await meeting.participants.setViewMode("PAGINATED"); + +// Set the view mode to active grid +await meeting.participants.setViewMode("ACTIVE_GRID"); +``` + + + + + +Android SDK uses paginated mode by default. + + + + + +iOS SDK uses paginated mode by default. + + + + + +Flutter SDK uses paginated mode by default. + + + + + +```tsx +// Set the view mode to paginated +await meeting.participants.setViewMode("PAGINATED"); + +// Set the view mode to active grid +await meeting.participants.setViewMode("ACTIVE_GRID"); +``` + + + +### Set Page in Paginated Mode + + + +```js +// Switch to second page +await meeting.participants.setPage(2); +``` + + + + + +```jsx +// Switch to second page +await meeting.participants.setPage(2); +``` + + + + + +```kotlin +// Switch to first page +meeting.participants.setPage(1) +``` + + + + + +```swift +// Switch to first page +meeting.participants.setPage(1) +``` + + + + + +Flutter SDK automatically manages participant pagination. + + + + + +```tsx +// Switch to second page +await meeting.participants.setPage(2); +``` + + + +### Monitor View Mode + + + +```jsx +const viewMode = useRealtimeKitSelector((m) => m.participants.viewMode); +const currentPage = useRealtimeKitSelector((m) => m.participants.currentPage); +``` + + + + + +```tsx +const viewMode = useRealtimeKitSelector((m) => m.participants.viewMode); +const currentPage = useRealtimeKitSelector((m) => m.participants.currentPage); +``` + + + +## Waiting Room Methods + +Use the following methods to manage participants in the waiting room. + +### Accept Waiting Room Request + + + +```js +await meeting.participants.acceptWaitingRoomRequest(participantId); +``` + + + + + +```jsx +await meeting.participants.acceptWaitingRoomRequest(participantId); +``` + + + + + +```kotlin +meeting.participants.acceptWaitingRoomRequest(participantId) +``` + + + + + +```swift +meeting.participants.acceptWaitingRoomRequest(id: participantId) +``` + + + + + +```dart +final participant = meeting.participants.waitlisted[0]; +meeting.participants.acceptWaitlistedParticipant(participant); +``` + + + + + +```tsx +await meeting.participants.acceptWaitingRoomRequest(participantId); +``` + + + +### Reject Waiting Room Request + + + +```js +await meeting.participants.rejectWaitingRoomRequest(participantId); +``` + + + + + +```jsx +await meeting.participants.rejectWaitingRoomRequest(participantId); +``` + + + + + +```kotlin +meeting.participants.rejectWaitingRoomRequest(participantId) +``` + + + + + +```swift +meeting.participants.rejectWaitingRoomRequest(participantId) +``` + + + + + +```dart +final participant = meeting.participants.waitlisted[0]; +meeting.participants.rejectWaitlistedParticipant(participant); +``` + + + + + +```tsx +await meeting.participants.rejectWaitingRoomRequest(participantId); +``` + + + +## Participant Object + +The participant object contains all information related to a particular participant, including their video/audio/screenshare streams, name, and state variables. + +### Participant Properties + + + +**Media Properties:** + +- `videoEnabled` - Whether the participant's camera is currently enabled +- `audioEnabled` - Whether the participant's microphone is currently unmuted +- `screenShareEnabled` - Whether the participant is currently sharing their screen +- `videoTrack` - Video track object of the participant's camera stream +- `audioTrack` - Audio track object of the participant's microphone stream +- `screenShareTracks` - Video and audio track objects of the participant's screen share stream + +**Metadata Properties:** + +- `id` - Session-specific identifier generated when the participant joins meeting session (also known as `peerId`) +- `userId` - Permanent identifier of the participant generated when adding the participant to a meeting +- `name` - Display name of the participant +- `picture` - String URL to the participant's display picture (if any) +- `customParticipantId` - Custom identifier that can be set while adding participant to a meeting by customer +- `isPinned` - Whether this participant is currently pinned in the meeting +- `presetName` - Name of the preset applied to this participant while adding to meeting + + + + + +**Media Properties:** + +- `videoEnabled` - Whether the participant's camera is currently enabled +- `audioEnabled` - Whether the participant's microphone is currently unmuted +- `screenShareEnabled` - Whether the participant is currently sharing their screen +- `videoTrack` - Video track object of the participant's camera stream +- `audioTrack` - Audio track object of the participant's microphone stream +- `screenShareTracks` - Video and audio track objects of the participant's screen share stream **Metadata Properties:** -- `id` - The `participantId` of the participant (aka `peerId`) -- `userId` - The `userId` of the participant -- `name` - The participant's name -- `picture` - The participant's picture (if any) -- `customParticipantId` - An arbitrary ID that can be set to identify the participant -- `isPinned` - Set to `true` if the participant is pinned -- `presetName` - Name of the preset associated with the participant +- `id` - Session-specific identifier generated when the participant joins meeting session (also known as `peerId`) +- `userId` - Permanent identifier of the participant generated when adding the participant to a meeting +- `name` - Display name of the participant +- `picture` - String URL to the participant's display picture (if any) +- `customParticipantId` - Custom identifier that can be set while adding participant to a meeting by customer +- `isPinned` - Whether this participant is currently pinned in the meeting +- `presetName` - Name of the preset applied to this participant while adding to meeting + + + + + +**Media Properties:** + +- `videoEnabled` - Whether the participant's camera is currently enabled +- `audioEnabled` - Whether the participant's microphone is currently unmuted +- `screenshareEnabled` - Whether the participant is currently sharing their screen + +**Metadata Properties:** + +- `id` - Session-specific identifier generated when the participant joins meeting session (also known as `peerId`) +- `userId` - Permanent identifier of the participant generated when adding the participant to a meeting +- `name` - Display name of the participant +- `picture` - String URL to the participant's display picture (if any) +- `customParticipantId` - Custom identifier that can be set while adding participant to a meeting by customer +- `isHost` - Boolean value whether this participant has host privileges +- `isPinned` - Whether this participant is currently pinned in the meeting +- `presetName` - Name of the preset applied to this participant while adding to meeting +- `stageStatus` - Indicates the participant's current stage status (applicable only in stage-enabled meetings) + + + + + +**Media Properties:** + +- `videoEnabled` - Whether the participant's camera is currently enabled +- `audioEnabled` - Whether the participant's microphone is currently unmuted +- `screenshareEnabled` - Whether the participant is currently sharing their screen + +**Metadata Properties:** + +- `id` - Session-specific identifier generated when the participant joins meeting session (also known as `peerId`) +- `userId` - Permanent identifier of the participant generated when adding the participant to a meeting +- `name` - Display name of the participant +- `picture` - String URL to the participant's display picture (if any) +- `customParticipantId` - Custom identifier that can be set while adding participant to a meeting by customer +- `isHost` - Boolean value whether this participant has host privileges +- `isPinned` - Whether this participant is currently pinned in the meeting +- `presetName` - Name of the preset applied to this participant while adding to meeting +- `stageStatus` - Indicates the participant's current stage status (applicable only in stage-enabled meetings) + + + + + +**Media Properties:** + +- `videoEnabled` - Whether the participant's camera is currently enabled +- `audioEnabled` - Whether the participant's microphone is currently unmuted +- `screenshareEnabled` - Whether the participant is currently sharing their screen + +**Metadata Properties:** + +- `id` - Session-specific identifier generated when the participant joins meeting session (also known as `peerId`) +- `userId` - Permanent identifier of the participant generated when adding the participant to a meeting +- `name` - Display name of the participant +- `picture` - String URL to the participant's display picture (if any) +- `isHost` - Boolean value whether this participant has host privileges +- `customParticipantId` - Custom identifier that can be set while adding participant to a meeting by customer +- `stageStatus` - Indicates the participant's current stage status (applicable only in stage-enabled meetings) +- `isPinned` - Whether this participant is currently pinned in the meeting +- `presetName` - Name of the preset applied to this participant while adding to meeting + + + + + +**Media Properties:** + +- `videoEnabled` - Whether the participant's camera is currently enabled +- `audioEnabled` - Whether the participant's microphone is currently unmuted +- `screenShareEnabled` - Whether the participant is currently sharing their screen +- `videoTrack` - Video track object of the participant's camera stream +- `audioTrack` - Audio track object of the participant's microphone stream +- `screenShareTracks` - Video and audio track objects of the participant's screen share stream + +**Metadata Properties:** + +- `id` - Session-specific identifier generated when the participant joins meeting session (also known as `peerId`) +- `userId` - Permanent identifier of the participant generated when adding the participant to a meeting +- `name` - Display name of the participant +- `picture` - String URL to the participant's display picture (if any) +- `clientSpecificId` - Custom identifier that can be set while adding participant to a meeting by customer +- `isPinned` - Whether this participant is currently pinned in the meeting +- `presetName` - Name of the preset applied to this participant while adding to meeting + + + +### Access Participant Object + + + +```js +const participant = meeting.participants.joined.get(participantId); + +// Access participant properties +console.log(participant.name); +console.log(participant.videoEnabled); +console.log(participant.audioEnabled); +``` + + + + + +```jsx +// Get a specific participant +const participant = useRealtimeKitSelector((m) => + m.participants.joined.get(participantId), +); + +// Access participant properties +const participantName = participant?.name; +const isVideoEnabled = participant?.videoEnabled; +const isAudioEnabled = participant?.audioEnabled; +``` + + + + + +```kotlin +// Find a participant by peer ID +val participant = meeting.participants.joined.firstOrNull { it.id == participantId } + +// Access participant properties +participant?.let { + println("Participant: ${it.name}") + println("Video: ${it.videoEnabled}") + println("Audio: ${it.audioEnabled}") +} +``` + + + + + +```swift +// Find a participant by peer ID +if let participant = meeting.participants.joined.first(where: { $0.id == participantId }) { + // Access participant properties + print("Participant: \(participant.name)") + print("Video: \(participant.videoEnabled)") + print("Audio: \(participant.audioEnabled)") +} +``` + + + + + +```dart +// Find a participant by peer ID +final participant = meeting.participants.joined + .where((p) => p.id == "") + .firstOrNull; + +// Access participant properties +if (participant != null) { + print('Participant: ${participant.name} (ID: ${participant.id})'); + print('Audio: ${participant.audioEnabled ? "On" : "Off"}'); + print('Video: ${participant.videoEnabled ? "On" : "Off"}'); +} +``` + + + + + +```tsx +// Get a specific participant +const participant = useRealtimeKitSelector((m) => + m.participants.joined.get(participantId), +); + +// Access participant properties +const participantName = participant?.name; +const isVideoEnabled = participant?.videoEnabled; +const isAudioEnabled = participant?.audioEnabled; +``` + + + +### Listen to Participant Events + + + +Each participant object is an event emitter: + +```js +meeting.participants.joined + .get(participantId) + .on("audioUpdate", ({ audioEnabled, audioTrack }) => { + console.log( + "The participant with id", + participantId, + "has toggled their mic to", + audioEnabled, + ); + }); +``` + +Alternatively, listen on the participant map for all participants: + +```js +meeting.participants.joined.on( + "audioUpdate", + (participant, { audioEnabled, audioTrack }) => { + console.log( + "The participant with id", + participant.id, + "has toggled their mic to", + audioEnabled, + ); + }, +); +``` + + + + + +```jsx +import { useRealtimeKitClient } from "@cloudflare/realtimekit-react"; +import { useEffect } from "react"; + +function ParticipantAudioListener({ participantId }) { + const [meeting] = useRealtimeKitClient(); + + useEffect(() => { + if (!meeting) return; + + const handleAudioUpdate = ({ audioEnabled, audioTrack }) => { + console.log( + "The participant with id", + participantId, + "has toggled their mic to", + audioEnabled, + ); + }; + + const participant = meeting.participants.joined.get(participantId); + participant.on("audioUpdate", handleAudioUpdate); + + return () => { + participant.off("audioUpdate", handleAudioUpdate); + }; + }, [meeting, participantId]); +} +``` + +Or use the selector for specific properties: + +```jsx +const audioEnabled = useRealtimeKitSelector( + (m) => m.participants.joined.get(participantId)?.audioEnabled, +); +``` + + + + + +Implement the `RtkParticipantEventListener` interface to receive participant event updates: + +```kotlin +meeting.addParticipantEventListener(object : RtkParticipantEventListener { + override fun onVideoUpdate(participant: RtkRemoteParticipant, isEnabled: Boolean) { + // Called when participant's video state changes + } + + override fun onAudioUpdate(participant: RtkRemoteParticipant, isEnabled: Boolean) { + // Called when participant's audio state changes + } + + override fun onScreenShareUpdate(participant: RtkRemoteParticipant, isEnabled: Boolean) { + // Called when participant's screen share state changes + } +}) +``` + + + + + +Implement the `RtkParticipantEventListener` protocol to receive participant event updates: + +```swift +extension MeetingViewModel: RtkParticipantEventListener { + func onVideoUpdate(participant: RtkRemoteParticipant, isEnabled: Bool) { + // Called when participant's video state changes + } + + func onAudioUpdate(participant: RtkRemoteParticipant, isEnabled: Bool) { + // Called when participant's audio state changes + } + + func onScreenShareUpdate(participant: RtkRemoteParticipant, isEnabled: Bool) { + // Called when participant's screen share state changes + } +} + +meeting.addParticipantEventListener(self) +``` + + + + + +Implement the `RtkParticipantUpdateListener` interface and add the listener on a participant: + +```dart +class ParticipantUpdateHandler extends RtkParticipantUpdateListener { + @override + void onVideoUpdate(RtkRemoteParticipant participant, bool isEnabled) { + print("${participant.name}'s video is now ${isEnabled ? 'on' : 'off'}"); + } + + @override + void onAudioUpdate(RtkRemoteParticipant participant, bool isEnabled) { + print("${participant.name}'s audio is now ${isEnabled ? 'on' : 'off'}"); + } + + @override + void onPinned(RtkRemoteParticipant participant) { + print("${participant.name} was pinned"); + } + + @override + void onUnpinned(RtkRemoteParticipant participant) { + print("${participant.name} was unpinned"); + } + + @override + void onScreenShareUpdate(RtkRemoteParticipant participant, bool isEnabled) { + print("${participant.name}'s screen-share is now ${isEnabled ? 'on' : 'off'}"); + } + + @override + void onUpdate(RtkRemoteParticipant participant) { + print("${participant.name} was updated"); + } +} + +// Register the listener with a specific participant +final listener = ParticipantUpdateHandler(); +participant.addParticipantUpdateListener(listener); + +// When done listening, remove the listener +participant.removeParticipantUpdateListener(listener); +``` + + + + + +```tsx +import { useRealtimeKitClient } from "@cloudflare/realtimekit-react-native"; +import { useEffect } from "react"; + +function ParticipantAudioListener({ participantId }) { + const [meeting] = useRealtimeKitClient(); + + useEffect(() => { + if (!meeting) return; + + const handleAudioUpdate = ({ audioEnabled, audioTrack }) => { + console.log( + "The participant with id", + participantId, + "has toggled their mic to", + audioEnabled, + ); + }; + + const participant = meeting.participants.joined.get(participantId); + participant.on("audioUpdate", handleAudioUpdate); + + return () => { + participant.off("audioUpdate", handleAudioUpdate); + }; + }, [meeting, participantId]); +} +``` + +Or use the selector for specific properties: + +```tsx +const audioEnabled = useRealtimeKitSelector( + (m) => m.participants.joined.get(participantId)?.audioEnabled, +); +``` + + + +### Host Control Methods + +If you have the relevant permissions, you can control participant media. + + + +```js +const participant = meeting.participants.joined.get(participantId); + +// Disable a participant's video stream +participant.disableVideo(); + +// Disable a participant's audio stream +participant.disableAudio(); + +// Kick a participant from the meeting +participant.kick(); +``` + + + + + +```jsx +const participant = meeting.participants.joined.get(participantId); + +// Disable a participant's video stream +participant.disableVideo(); + +// Disable a participant's audio stream +participant.disableAudio(); + +// Kick a participant from the meeting +participant.kick(); +``` + + + + + +```kotlin +val participant = meeting.participants.joined.firstOrNull { it.id == participantId } + +participant?.let { pcpt -> + // Disable a participant's video stream + val videoError = pcpt.disableVideo() + + // Disable a participant's audio stream + val audioError = pcpt.disableAudio() + + // Kick a participant from the meeting + val kickError = pcpt.kick() +} +``` + + + + + +```swift +if let participant = meeting.participants.joined.first(where: { $0.id == participantId }) { + // Disable a participant's video stream + let videoError: HostError? = participant.disableVideo() + + // Disable a participant's audio stream + let audioError: HostError? = participant.disableAudio() + + // Kick a participant from the meeting + let kickError: HostError? = participant.kick() +} +``` + + + + + +```dart +// Disable a remote participant's video +participant.disableVideo(onResult: (e) { + // handle error if any +}); + +// Disable a remote participant's audio +participant.disableAudio(onResult: (e) { + // handle error if any +}); + +// Remove the participant from the meeting +participant.kick(); +``` + +**Required Permission**: `permissions.host.canDisableVideo`, `permissions.host.canDisableAudio` must be `true` + + + + + +```tsx +const participant = meeting.participants.joined.get(participantId); + +// Disable a participant's video stream +participant.disableVideo(); + +// Disable a participant's audio stream +participant.disableAudio(); + +// Kick a participant from the meeting +participant.kick(); +``` + + + +### Pin and Unpin Participants + + + +```js +const participant = meeting.participants.joined.get(participantId); + +// Pin a participant +await participant.pin(); + +// Unpin a participant +await participant.unpin(); +``` + + + + + +```jsx +const participant = meeting.participants.joined.get(participantId); + +// Pin a participant +await participant.pin(); + +// Unpin a participant +await participant.unpin(); +``` + + + + + +```kotlin +val participant = meeting.participants.joined.firstOrNull { it.id == participantId } + +participant?.let { pcpt -> + // Pin a participant + val pinError = pcpt.pin() + + // Unpin a participant + val unpinError = pcpt.unpin() +} +``` + + + + + +```swift +if let participant = meeting.participants.joined.first(where: { $0.id == participantId }) { + // Pin a participant + let pinError: HostError? = participant.pin() + + // Unpin a participant + let unpinError: HostError? = participant.unpin() +} +``` + + + + + +```dart +// Pin a remote participant +participant.pin(); + +// Unpin a previously pinned participant +participant.unpin(); +``` + +**Required Permission**: `permissions.host.canPinParticipant` must be `true` + + + + + +```tsx +const participant = meeting.participants.joined.get(participantId); + +// Pin a participant +await participant.pin(); + +// Unpin a participant +await participant.unpin(); +``` + + + +## Display Participant Videos + + + +To play a participant's video track on a ` + + + +Call `participant.getVideoView()` which returns a `View` that renders the participant's video stream: + +```kotlin +// Get video view of a given participant +val videoView = participant.getVideoView() + +// Get screen share video view +val screenShareView = participant.getScreenShareVideoView() +``` + + + + + +Call `participant.getVideoView()` which returns a `UIView` that renders the participant's video stream: + +```swift +// Get video view of a given participant +let videoView = participant.getVideoView() + +// Get screen share video view +let screenShareView = participant.getScreenShareVideoView() +``` + + + + + +Use the video view methods which return a `Widget` that you can place directly in your UI hierarchy: + +```dart +// Create a widget to display the participant's camera video +final cameraView = VideoView(meetingParticipant: participant); + +// Create a widget to display the participant's screen share +final screenShareView = ScreenshareView(meetingParticipant: participant); +``` + + + + + +For custom video rendering, use the `RtkParticipantTile` component: + +```jsx +import { RtkParticipantTile } from "@cloudflare/realtimekit-react-ui"; +import { useRealtimeKitSelector } from "@cloudflare/realtimekit-react"; + +function CustomVideoGrid() { + const participants = useRealtimeKitSelector((m) => + m.participants.active.toArray(), + ); + + return ( +
+ {participants.map((participant) => ( + + ))} +
+ ); +} +``` + +
+ + + +For custom video rendering, use the `RtkParticipantTile` component: + +```tsx +import { RtkParticipantTile } from "@cloudflare/realtimekit-react-native-ui"; +import { useRealtimeKitSelector } from "@cloudflare/realtimekit-react-native"; +import { View, StyleSheet } from "react-native"; + +function CustomVideoGrid() { + const participants = useRealtimeKitSelector((m) => + m.participants.active.toArray(), + ); + + return ( + + {participants.map((participant) => ( + + ))} + + ); +} + +const styles = StyleSheet.create({ + grid: { + flexDirection: "row", + flexWrap: "wrap", + }, +}); +``` + + + +## Participant events + +### View Mode Change + + + +Triggered when the view mode changes between `ACTIVE_GRID` and `PAGINATED`. + +```js +meeting.participants.on( + "viewModeChanged", + ({ viewMode, currentPage, pageCount }) => { + console.log("view mode changed", viewMode); + }, +); +``` + + + + + +Triggered when the view mode changes between `ACTIVE_GRID` and `PAGINATED`. + +```jsx +const viewMode = useRealtimeKitSelector((m) => m.participants.viewMode); +``` + +Or use event listener: + +```jsx +meeting.participants.on( + "viewModeChanged", + ({ viewMode, currentPage, pageCount }) => { + console.log("view mode changed", viewMode); + }, +); +``` + + + + + +This event is not available on Android. + + + + + +This event is not available on iOS. + + + + + +This event is not available on Flutter. + + + + + +Triggered when the view mode changes between `ACTIVE_GRID` and `PAGINATED`. + +```tsx +const viewMode = useRealtimeKitSelector((m) => m.participants.viewMode); +``` + +Or use event listener: + +```tsx +meeting.participants.on( + "viewModeChanged", + ({ viewMode, currentPage, pageCount }) => { + console.log("view mode changed", viewMode); + }, +); +``` + + + +### Page Change + + + +Triggered when the page changes in paginated mode. + +```js +meeting.participants.on( + "pageChanged", + ({ viewMode, currentPage, pageCount }) => { + console.log("page changed", currentPage); + }, +); +``` + + + + + +Triggered when the page changes in paginated mode. + +```jsx +const currentPage = useRealtimeKitSelector((m) => m.participants.currentPage); +const pageCount = useRealtimeKitSelector((m) => m.participants.pageCount); +``` + + + + + +This event is not available on Android. + + + + + +This event is not available on iOS. + + + + + +This event is not available on Flutter. + + + + + +Triggered when the page changes in paginated mode. + +```tsx +const currentPage = useRealtimeKitSelector((m) => m.participants.currentPage); +const pageCount = useRealtimeKitSelector((m) => m.participants.pageCount); +``` + + + +### Active Speaker - - +Triggered when a participant starts speaking. -### Access Participant Object + ```js -const participant = meeting.participants.joined.get(participantId); - -// Access participant properties -console.log(participant.name); -console.log(participant.videoEnabled); -console.log(participant.audioEnabled); +meeting.participants.on("activeSpeaker", (participant) => { + console.log(`${participant.id} is currently speaking`); +}); ``` -### Listen to Participant Events + -Each participant object is an event emitter: + -```js -meeting.participants.joined - .get(participantId) - .on("audioUpdate", ({ audioEnabled, audioTrack }) => { - console.log( - "The participant with id", - participantId, - "has toggled their mic to", - audioEnabled, - ); - }); +```jsx +const activeSpeaker = useRealtimeKitSelector( + (m) => m.participants.lastActiveSpeaker, +); ``` -Alternatively, listen on the participant map for all participants: +Or use event listener: -```js -meeting.participants.joined.on( - "audioUpdate", - (participant, { audioEnabled, audioTrack }) => { - console.log( - "The participant with id", - participant.id, - "has toggled their mic to", - audioEnabled, - ); - }, -); +```jsx +meeting.participants.on("activeSpeaker", (participant) => { + console.log(`${participant.id} is currently speaking`); +}); ``` -### Host Control Methods + -If you have the relevant permissions, you can control participant media: + -```js -const participant = meeting.participants.joined.get(participantId); +```kotlin +meeting.addParticipantsEventListener(object : RtkParticipantsEventListener { + override fun onActiveSpeakerChanged(participant: RtkRemoteParticipant?) { + participant?.let { + println("${it.id} is currently speaking") + } + } +}) +``` -// Disable a participant's video stream -participant.disableVideo(); + -// Disable a participant's audio stream -participant.disableAudio(); + -// Kick a participant from the meeting -participant.kick(); +```swift +extension MeetingViewModel: RtkParticipantsEventListener { + func onActiveSpeakerChanged(participant: RtkRemoteParticipant?) { + if let participant = participant { + print("\(participant.id) is currently speaking") + } + } +} + +meeting.addParticipantsEventListener(self) ``` -### Pin and Unpin Participants + -```js -const participant = meeting.participants.joined.get(participantId); + -// Pin a participant -await participant.pin(); +```dart +class ParticipantsNotifier extends RtkParticipantsEventListener { + @override + void onActiveSpeakerChanged(RtkRemoteParticipant? participant) { + if (participant != null) { + print('${participant.id} is currently speaking'); + } + } +} -// Unpin a participant -await participant.unpin(); +meeting.addParticipantsEventListener(ParticipantsNotifier()); ``` - - + -### Access Participant Object + -```jsx -// Get a specific participant -const participant = useRealtimeKitSelector((m) => - m.participants.joined.get(participantId), +```tsx +const activeSpeaker = useRealtimeKitSelector( + (m) => m.participants.lastActiveSpeaker, ); - -// Access participant properties -const participantName = participant?.name; -const isVideoEnabled = participant?.videoEnabled; -const isAudioEnabled = participant?.audioEnabled; ``` -### Listen to Participant Events +Or use event listener: -```jsx -import { useRealtimeKitClient } from "@cloudflare/realtimekit-react"; +```tsx +meeting.participants.on("activeSpeaker", (participant) => { + console.log(`${participant.id} is currently speaking`); +}); +``` -function ParticipantAudioListener({ participantId }) { - const [meeting] = useRealtimeKitClient(); + - useEffect(() => { - if (!meeting) return; +### Participant Joined - const handleAudioUpdate = ({ audioEnabled, audioTrack }) => { - console.log( - "The participant with id", - participantId, - "has toggled their mic to", - audioEnabled, - ); - }; +Triggered when any participant joins the meeting. - const participant = meeting.participants.joined.get(participantId); - participant.on("audioUpdate", handleAudioUpdate); + - return () => { - participant.off("audioUpdate", handleAudioUpdate); - }; - }, [meeting, participantId]); -} +```js +meeting.participants.joined.on("participantJoined", (participant) => { + console.log(`A participant with id "${participant.id}" has joined`); +}); ``` -Or use the selector for specific properties: + + + ```jsx -const audioEnabled = useRealtimeKitSelector( - (m) => m.participants.joined.get(participantId)?.audioEnabled, -); +const joinedParticipants = useRealtimeKitSelector((m) => m.participants.joined); ``` -### Host Control Methods - -If you have the relevant permissions, you can control participant media: +Or use event listener: ```jsx -const participant = meeting.participants.joined.get(participantId); +meeting.participants.joined.on("participantJoined", (participant) => { + console.log(`A participant with id "${participant.id}" has joined`); +}); +``` -// Disable a participant's video stream -participant.disableVideo(); + -// Disable a participant's audio stream -participant.disableAudio(); + -// Kick a participant from the meeting -participant.kick(); +```kotlin +meeting.addParticipantsEventListener(object : RtkParticipantsEventListener { + override fun onParticipantJoin(participant: RtkRemoteParticipant) { + println("A participant with id ${participant.id} has joined") + } +}) ``` -### Pin and Unpin Participants + -```jsx -const participant = meeting.participants.joined.get(participantId); + -// Pin a participant -await participant.pin(); +```swift +extension MeetingViewModel: RtkParticipantsEventListener { + func onParticipantJoin(participant: RtkRemoteParticipant) { + print("A participant with id \(participant.id) has joined") + } +} -// Unpin a participant -await participant.unpin(); +meeting.addParticipantsEventListener(self) ``` - - + -## Display Participant Videos + - - +```dart +class ParticipantsNotifier extends RtkParticipantsEventListener { + @override + void onParticipantJoin(RtkRemoteParticipant participant) { + print('A participant with id ${participant.id} has joined'); + } +} -### Register Video Element +meeting.addParticipantsEventListener(ParticipantsNotifier()); +``` -To play a participant's video track on a ` -```html - + + +```tsx +const joinedParticipants = useRealtimeKitSelector((m) => m.participants.joined); +``` + +Or use event listener: + +```tsx +meeting.participants.joined.on("participantJoined", (participant) => { + console.log(`A participant with id "${participant.id}" has joined`); +}); ``` + + +### Participant Left + +Triggered when any participant leaves the meeting. + + + ```js -// Get the video element -const videoElement = document.getElementById("participant-video"); +meeting.participants.joined.on("participantLeft", (participant) => { + console.log(`A participant with id "${participant.id}" has left the meeting`); +}); +``` -// Get the participant -const participant = meeting.participants.joined.get(participantId); + -// Register the video element -participant.registerVideoElement(videoElement); + + +```jsx +const joinedParticipants = useRealtimeKitSelector((m) => m.participants.joined); ``` -For local user preview (video not sent to other users): +Or use event listener: -```js -meeting.self.registerVideoElement(videoElement, true); +```jsx +meeting.participants.joined.on("participantLeft", (participant) => { + console.log(`A participant with id "${participant.id}" has left the meeting`); +}); ``` -### Deregister Video Element + -Clean up when the video element is no longer needed: + -```js -participant.deregisterVideoElement(videoElement); +```kotlin +meeting.addParticipantsEventListener(object : RtkParticipantsEventListener { + override fun onParticipantLeave(participant: RtkRemoteParticipant) { + println("A participant with id ${participant.id} has left the meeting") + } +}) ``` - - + -### Using UI Kit Grid Components + -RealtimeKit provides ready-to-use grid components for displaying participant videos: +```swift +extension MeetingViewModel: RtkParticipantsEventListener { + func onParticipantLeave(participant: RtkRemoteParticipant) { + print("A participant with id \(participant.id) has left the meeting") + } +} -```jsx -import { RtkGrid } from "@cloudflare/realtimekit-react-ui"; +meeting.addParticipantsEventListener(self) +``` -function MeetingGrid() { - return ; + + + + +```dart +class ParticipantsNotifier extends RtkParticipantsEventListener { + @override + void onParticipantLeave(RtkRemoteParticipant participant) { + print('A participant with id ${participant.id} has left the meeting'); + } } + +meeting.addParticipantsEventListener(ParticipantsNotifier()); ``` -The `RtkGrid` component handles all grid-related events and data automatically. It supports: + -- **`RtkSimpleGrid`** - Renders participant tiles in a simple grid -- **`RtkSpotlightGrid`** - Renders pinned participants in a spotlight area with others in a smaller grid -- **`RtkMixedGrid`** - Renders screenshares and plugins in the main view with a configurable smaller grid + -### Manual Video Rendering +```tsx +const joinedParticipants = useRealtimeKitSelector((m) => m.participants.joined); +``` -For custom video rendering, use the `RtkParticipantTile` component: +Or use event listener: + +```tsx +meeting.participants.joined.on("participantLeft", (participant) => { + console.log(`A participant with id "${participant.id}" has left the meeting`); +}); +``` + + + +### Participant Pinned + +Triggered when a participant is pinned. + + + +```js +meeting.participants.joined.on("pinned", (participant) => { + console.log(`Participant with id "${participant.id}" was pinned`); +}); +``` + + + + ```jsx -import { RtkParticipantTile } from "@cloudflare/realtimekit-react-ui"; -import { useRealtimeKitSelector } from "@cloudflare/realtimekit-react"; +const pinnedParticipants = useRealtimeKitSelector((m) => m.participants.pinned); +``` -function CustomVideoGrid() { - const participants = useRealtimeKitSelector((m) => - m.participants.active.toArray(), - ); + - return ( -
- {participants.map((participant) => ( - - ))} -
- ); + + +```kotlin +meeting.addParticipantsEventListener(object : RtkParticipantsEventListener { + override fun onParticipantPinned(participant: RtkRemoteParticipant) { + println("Participant with id ${participant.id} was pinned") + } +}) +``` + + + + + +```swift +extension MeetingViewModel: RtkParticipantsEventListener { + func onParticipantPinned(participant: RtkRemoteParticipant) { + print("Participant with id \(participant.id) was pinned") + } } + +meeting.addParticipantsEventListener(self) ``` - - + + + + +```dart +class ParticipantsNotifier extends RtkParticipantsEventListener { + @override + void onParticipantPinned(RtkRemoteParticipant participant) { + print('Participant with id ${participant.id} was pinned'); + } +} + +meeting.addParticipantsEventListener(ParticipantsNotifier()); +``` -## Participant Events + - - + -### View Mode Change +```tsx +const pinnedParticipants = useRealtimeKitSelector((m) => m.participants.pinned); +``` -Triggered when the view mode changes: + -```js -meeting.participants.on( - "viewModeChanged", - ({ viewMode, currentPage, pageCount }) => { - console.log("view mode changed", viewMode); - }, -); -``` +### Participant Unpinned -### Page Change +Triggered when a participant is unpinned. -Triggered when the page changes in paginated mode: + ```js -meeting.participants.on( - "pageChanged", - ({ viewMode, currentPage, pageCount }) => { - console.log("page changed", currentPage); - }, -); +meeting.participants.joined.on("unpinned", (participant) => { + console.log(`Participant with id "${participant.id}" was unpinned`); +}); ``` -### Active Speaker + -Triggered when a participant starts speaking: + -```js -meeting.participants.on("activeSpeaker", (participant) => { - console.log(`${participant.id} is currently speaking`); -}); -``` +This event is not available on React. -### Participant Joined + -Triggered when any participant joins the meeting: + -```js -meeting.participants.joined.on("participantJoined", (participant) => { - console.log(`A participant with id "${participant.id}" has joined`); -}); +```kotlin +meeting.addParticipantsEventListener(object : RtkParticipantsEventListener { + override fun onParticipantUnpinned(participant: RtkRemoteParticipant) { + println("Participant with id ${participant.id} was unpinned") + } +}) ``` -### Participant Left + -Triggered when any participant leaves the meeting: + -```js -meeting.participants.joined.on("participantLeft", (participant) => { - console.log(`A participant with id "${participant.id}" has left the meeting`); -}); +```swift +extension MeetingViewModel: RtkParticipantsEventListener { + func onParticipantUnpinned(participant: RtkRemoteParticipant) { + print("Participant with id \(participant.id) was unpinned") + } +} + +meeting.addParticipantsEventListener(self) ``` -### Participant Pinned + -Triggered when a participant is pinned: + -```js -meeting.participants.joined.on("pinned", (participant) => { - console.log(`Participant with id "${participant.id}" was pinned`); -}); +```dart +class ParticipantsNotifier extends RtkParticipantsEventListener { + @override + void onParticipantUnpinned(RtkRemoteParticipant participant) { + print('Participant with id ${participant.id} was unpinned'); + } +} + +meeting.addParticipantsEventListener(ParticipantsNotifier()); ``` -### Participant Unpinned + -Triggered when a participant is unpinned: + -```js -meeting.participants.joined.on("unpinned", (participant) => { - console.log(`Participant with id "${participant.id}" was unpinned`); -}); -``` +This event is not available on React Native. + + ### Video Update -Triggered when any participant starts/stops video: +Triggered when any participant starts/stops video. + + ```js meeting.participants.joined.on("videoUpdate", (participant) => { @@ -642,9 +2138,86 @@ meeting.participants.joined.on("videoUpdate", (participant) => { }); ``` + + + + +```jsx +// Check for one participant +const videoEnabled = useRealtimeKitSelector( + (m) => m.participants.joined.get(participantId)?.videoEnabled, +); + +// All video enabled participants +const videoEnabledParticipants = useRealtimeKitSelector((m) => + m.participants.joined.toArray().filter((p) => p.videoEnabled), +); +``` + + + + + +```kotlin +meeting.addParticipantEventListener(object : RtkParticipantEventListener { + override fun onVideoUpdate(participant: RtkRemoteParticipant, isEnabled: Boolean) { + println("Participant ${participant.id} video is now ${if (isEnabled) "enabled" else "disabled"}") + } +}) +``` + + + + + +```swift +extension MeetingViewModel: RtkParticipantEventListener { + func onVideoUpdate(participant: RtkRemoteParticipant, isEnabled: Bool) { + print("Participant \(participant.id) video is now \(isEnabled ? "enabled" : "disabled")") + } +} + +meeting.addParticipantEventListener(self) +``` + + + + + +```dart +class ParticipantUpdateHandler extends RtkParticipantUpdateListener { + @override + void onVideoUpdate(RtkRemoteParticipant participant, bool isEnabled) { + print('Participant ${participant.id} video is now ${isEnabled ? "enabled" : "disabled"}'); + } +} + +participant.addParticipantUpdateListener(ParticipantUpdateHandler()); +``` + + + + + +```tsx +// Check for one participant +const videoEnabled = useRealtimeKitSelector( + (m) => m.participants.joined.get(participantId)?.videoEnabled, +); + +// All video enabled participants +const videoEnabledParticipants = useRealtimeKitSelector((m) => + m.participants.joined.toArray().filter((p) => p.videoEnabled), +); +``` + + + ### Audio Update -Triggered when any participant starts/stops audio: +Triggered when any participant starts/stops audio. + + ```js meeting.participants.joined.on("audioUpdate", (participant) => { @@ -660,9 +2233,86 @@ meeting.participants.joined.on("audioUpdate", (participant) => { }); ``` + + + + +```jsx +// Check for one participant +const audioEnabled = useRealtimeKitSelector( + (m) => m.participants.joined.get(participantId)?.audioEnabled, +); + +// All audio enabled participants +const audioEnabledParticipants = useRealtimeKitSelector((m) => + m.participants.joined.toArray().filter((p) => p.audioEnabled), +); +``` + + + + + +```kotlin +meeting.addParticipantEventListener(object : RtkParticipantEventListener { + override fun onAudioUpdate(participant: RtkRemoteParticipant, isEnabled: Boolean) { + println("Participant ${participant.id} audio is now ${if (isEnabled) "enabled" else "disabled"}") + } +}) +``` + + + + + +```swift +extension MeetingViewModel: RtkParticipantEventListener { + func onAudioUpdate(participant: RtkRemoteParticipant, isEnabled: Bool) { + print("Participant \(participant.id) audio is now \(isEnabled ? "enabled" : "disabled")") + } +} + +meeting.addParticipantEventListener(self) +``` + + + + + +```dart +class ParticipantUpdateHandler extends RtkParticipantUpdateListener { + @override + void onAudioUpdate(RtkRemoteParticipant participant, bool isEnabled) { + print('Participant ${participant.id} audio is now ${isEnabled ? "enabled" : "disabled"}'); + } +} + +participant.addParticipantUpdateListener(ParticipantUpdateHandler()); +``` + + + + + +```tsx +// Check for one participant +const audioEnabled = useRealtimeKitSelector( + (m) => m.participants.joined.get(participantId)?.audioEnabled, +); + +// All audio enabled participants +const audioEnabledParticipants = useRealtimeKitSelector((m) => + m.participants.joined.toArray().filter((p) => p.audioEnabled), +); +``` + + + ### Screen Share Update -Triggered when any participant starts/stops screen share: +Triggered when any participant starts/stops screen share. + + ```js meeting.participants.joined.on("screenShareUpdate", (participant) => { @@ -678,9 +2328,86 @@ meeting.participants.joined.on("screenShareUpdate", (participant) => { }); ``` + + + + +```jsx +// Check for one participant +const screensharingParticipant = useRealtimeKitSelector((m) => + m.participants.joined.toArray().find((p) => p.screenShareEnabled), +); + +// All screen sharing participants +const screenSharingParticipants = useRealtimeKitSelector((m) => + m.participants.joined.toArray().filter((p) => p.screenShareEnabled), +); +``` + + + + + +```kotlin +meeting.addParticipantEventListener(object : RtkParticipantEventListener { + override fun onScreenShareUpdate(participant: RtkRemoteParticipant, isEnabled: Boolean) { + println("Participant ${participant.id} screen share is now ${if (isEnabled) "enabled" else "disabled"}") + } +}) +``` + + + + + +```swift +extension MeetingViewModel: RtkParticipantEventListener { + func onScreenShareUpdate(participant: RtkRemoteParticipant, isEnabled: Bool) { + print("Participant \(participant.id) screen share is now \(isEnabled ? "enabled" : "disabled")") + } +} + +meeting.addParticipantEventListener(self) +``` + + + + + +```dart +class ParticipantUpdateHandler extends RtkParticipantUpdateListener { + @override + void onScreenShareUpdate(RtkRemoteParticipant participant, bool isEnabled) { + print('Participant ${participant.id} screen share is now ${isEnabled ? "enabled" : "disabled"}'); + } +} + +participant.addParticipantUpdateListener(ParticipantUpdateHandler()); +``` + + + + + +```tsx +// Check for one participant +const screensharingParticipant = useRealtimeKitSelector((m) => + m.participants.joined.toArray().find((p) => p.screenShareEnabled), +); + +// All screen sharing participants +const screenSharingParticipants = useRealtimeKitSelector((m) => + m.participants.joined.toArray().filter((p) => p.screenShareEnabled), +); +``` + + + ### Network Quality Score -Monitor participant network quality: + + +Monitor participant network quality using the `mediaScoreUpdate` event. ```js meeting.participants.joined.on( @@ -707,128 +2434,73 @@ meeting.participants.joined.on( ); ``` - - - -### View Mode Change - -```jsx -const viewMode = useRealtimeKitSelector((m) => m.participants.viewMode); -``` - -Or use event listener: - -```jsx -meeting.participants.on( - "viewModeChanged", - ({ viewMode, currentPage, pageCount }) => { - console.log("view mode changed", viewMode); - }, -); -``` - -### Page Change - -```jsx -const currentPage = useRealtimeKitSelector((m) => m.participants.currentPage); -const pageCount = useRealtimeKitSelector((m) => m.participants.pageCount); -``` - -### Active Speaker - -```jsx -const activeSpeaker = useRealtimeKitSelector( - (m) => m.participants.lastActiveSpeaker, -); -``` - -Or use event listener: - -```jsx -meeting.participants.on("activeSpeaker", (participant) => { - console.log(`${participant.id} is currently speaking`); -}); -``` - -### Participant Joined - -```jsx -const joinedParticipants = useRealtimeKitSelector((m) => m.participants.joined); -``` - -Or use event listener: + -```jsx -meeting.participants.joined.on("participantJoined", (participant) => { - console.log(`A participant with id "${participant.id}" has joined`); -}); -``` + -### Participant Left +Monitor participant network quality using the `mediaScoreUpdate` event. ```jsx -const joinedParticipants = useRealtimeKitSelector((m) => m.participants.joined); -``` +import { useEffect } from "react"; -Or use event listener: +// Use event listener for media score updates +useEffect(() => { + if (!meeting) return; -```jsx -meeting.participants.joined.on("participantLeft", (participant) => { - console.log(`A participant with id "${participant.id}" has left the meeting`); -}); -``` + const handleMediaScoreUpdate = ({ + participantId, + kind, + isScreenshare, + score, + scoreStats, + }) => { + if (kind === "video") { + console.log( + `Participant ${participantId}'s ${isScreenshare ? "screenshare" : "video"} quality score is`, + score, + ); + } -### Participant Pinned/Unpinned + if (score < 5) { + console.log(`Participant ${participantId}'s media quality is poor`); + } + }; -```jsx -const pinnedParticipants = useRealtimeKitSelector((m) => m.participants.pinned); + meeting.participants.joined.on("mediaScoreUpdate", handleMediaScoreUpdate); + + return () => { + meeting.participants.joined.off("mediaScoreUpdate", handleMediaScoreUpdate); + }; +}, [meeting]); ``` -### Video Update + -```jsx -// Check for one participant -const videoEnabled = useRealtimeKitSelector( - (m) => m.participants.joined.get(participantId)?.videoEnabled, -); + -// All video enabled participants -const videoEnabledParticipants = useRealtimeKitSelector((m) => - m.participants.joined.toArray().filter((p) => p.videoEnabled), -); -``` +This event is not available on Android. -### Audio Update + -```jsx -// Check for one participant -const audioEnabled = useRealtimeKitSelector( - (m) => m.participants.joined.get(participantId)?.audioEnabled, -); + -// All audio enabled participants -const audioEnabledParticipants = useRealtimeKitSelector((m) => - m.participants.joined.toArray().filter((p) => p.audioEnabled), -); -``` +This event is not available on iOS. -### Screen Share Update + -```jsx -// Check for one participant -const screensharingParticipant = useRealtimeKitSelector((m) => - m.participants.joined.toArray().find((p) => p.screenShareEnabled), -); + -// All screen sharing participants -const screenSharingParticipants = useRealtimeKitSelector((m) => - m.participants.joined.toArray().filter((p) => p.screenShareEnabled), -); -``` +This event is not available on Flutter. -### Network Quality Score + + + + +Monitor participant network quality using the `mediaScoreUpdate` event. + +```tsx +import { useEffect } from "react"; -```jsx // Use event listener for media score updates useEffect(() => { if (!meeting) return; @@ -860,8 +2532,7 @@ useEffect(() => { }, [meeting]); ``` - - + ## Picture-in-Picture @@ -871,60 +2542,150 @@ Picture-in-Picture API allows you to render `meeting.participants.active` partic Supported in Chrome/Edge/Chromium-based browsers only. ::: - - - ### Check if Supported + + ```js const isSupported = meeting.participants.pip.isSupported(); ``` + + + + +```jsx +const isSupported = meeting.participants.pip.isSupported(); +``` + + + + + +Picture-in-Picture is not available on Android. + + + + + +Picture-in-Picture is not available on iOS. + + + + + +Picture-in-Picture is not available on Flutter. + + + + + +```tsx +const isSupported = meeting.participants.pip.isSupported(); +``` + + + ### Enable Picture-in-Picture + + ```js await meeting.participants.pip.enable(); ``` + + + + +```jsx +await meeting.participants.pip.enable(); +``` + + + + + +Picture-in-Picture is not available on Android. + + + + + +Picture-in-Picture is not available on iOS. + + + + + +Picture-in-Picture is not available on Flutter. + + + + + +```tsx +await meeting.participants.pip.enable(); +``` + + + ### Disable Picture-in-Picture + + ```js await meeting.participants.pip.disable(); ``` - - + -### Check if Supported + ```jsx -const isSupported = meeting.participants.pip.isSupported(); +await meeting.participants.pip.disable(); ``` -### Enable Picture-in-Picture + -```jsx -await meeting.participants.pip.enable(); -``` + -### Disable Picture-in-Picture +Picture-in-Picture is not available on Android. -```jsx + + + + +Picture-in-Picture is not available on iOS. + + + + + +Picture-in-Picture is not available on Flutter. + + + + + +```tsx await meeting.participants.pip.disable(); ``` - - + ## Update Participant Permissions Permissions for a participant are defined by the preset, but can be updated during a meeting by calling `updatePermissions` for remote participants. - - +:::note[Web Platforms Only] +This feature is currently available for web-based platforms (Web Components, React, and React Native). +::: ### Find Target Participants + + ```js const participantIds = meeting.participants.joined .toArray() @@ -932,8 +2693,52 @@ const participantIds = meeting.participants.joined .map((p) => p.id); ``` + + + + +```jsx +const participantIds = meeting.participants.joined + .toArray() + .filter((e) => e.name.startsWith("John")) + .map((p) => p.id); +``` + + + + + +Updating participant permissions is not available on Android. + + + + + +Updating participant permissions is not available on iOS. + + + + + +Updating participant permissions is not available on Flutter. + + + + + +```tsx +const participantIds = meeting.participants.joined + .toArray() + .filter((e) => e.name.startsWith("John")) + .map((p) => p.id); +``` + + + ### Update Permissions + + ```js // Allow file upload permissions in public chat const newPermissions = { @@ -947,8 +2752,64 @@ const newPermissions = { meeting.participants.updatePermissions(participantIds, newPermissions); ``` + + + + +```jsx +// Allow file upload permissions in public chat +const newPermissions = { + chat: { + public: { + files: true, + }, + }, +}; + +meeting.participants.updatePermissions(participantIds, newPermissions); +``` + + + + + +Updating participant permissions is not available on Android. + + + + + +Updating participant permissions is not available on iOS. + + + + + +Updating participant permissions is not available on Flutter. + + + + + +```tsx +// Allow file upload permissions in public chat +const newPermissions = { + chat: { + public: { + files: true, + }, + }, +}; + +meeting.participants.updatePermissions(participantIds, newPermissions); +``` + + + ### Available Permission Fields + + ```typescript interface UpdatedPermissions { polls?: { @@ -974,34 +2835,56 @@ interface UpdatedPermissions { } ``` - - + -### Find Target Participants + -```jsx -const participantIds = meeting.participants.joined - .toArray() - .filter((e) => e.name.startsWith("John")) - .map((p) => p.id); +```typescript +interface UpdatedPermissions { + polls?: { + canCreate?: boolean; + canVote?: boolean; + }; + plugins?: { + canClose?: boolean; + canStart?: boolean; + }; + chat?: { + public?: { + canSend?: boolean; + text?: boolean; + files?: boolean; + }; + private?: { + canSend?: boolean; + text?: boolean; + files?: boolean; + }; + }; +} ``` -### Update Permissions + -```jsx -// Allow file upload permissions in public chat -const newPermissions = { - chat: { - public: { - files: true, - }, - }, -}; + -meeting.participants.updatePermissions(participantIds, newPermissions); -``` +Updating participant permissions is not available on Android. -### Available Permission Fields + + + + +Updating participant permissions is not available on iOS. + + + + + +Updating participant permissions is not available on Flutter. + + + + ```typescript interface UpdatedPermissions { @@ -1028,5 +2911,4 @@ interface UpdatedPermissions { } ``` - - +