@@ -33,280 +33,134 @@ import { CardGroup, Card, Icon, Badge, Steps, Columns, AccordionGroup, Accordion
-
- Step 1
-
-
-
Add CometChat to Your Frontend
-
Use our pre-built SDKs to add calls and Voice to your website or mobile app instantly.
+
What is CometChat Calls?
+
The CometChat Calls SDK provides a complete voice and video calling solution built on WebRTC.
+
+
+
+
+
+ Pre-built call screens, controls, and participant views
+
+
+ Mute, screen share, recording, layouts, and more
+
+
+ Web, iOS, Android, React Native, and Flutter
+
+
+ Enterprise-grade media servers handle all complexity
+
+
-{/* SDKs Section */}
-
- SDKs (Includes UI)
-
-
-
- Access every calling capability with our SDKs. The Calls SDK ships with a drop‑in UI so you can go live fast.
-
-
-
+ Choose Your Platform
+ Get started with the Calls SDK on your preferred platform. Each SDK provides the same core calling features with platform-specific optimizations.
+
-
- Initialize and connect to CometChat in your frontend application.
+
+ Add the Calls SDK to your project via npm, CocoaPods, Gradle, or pub.
+
+
+ Configure with your App ID and Region, then log in users with their CometChat credentials.
-
- Create UI and flows from the ground up—exactly how you want them.
+
+ Generate a session token and join—the SDK renders a complete call UI automatically.
-
- Full control over every aspect of the chat experience.
+
+ Adjust layouts, controls, and styling to match your app's design.
-
- Live in minutes, not days.
- No framework lock‑in or build steps.
- Configure without code; theme when needed.
+ Pre-built call screens, controls, and participant views—no UI work required.
+ Enterprise-grade media infrastructure handles all the complexity.
+ Same features and API patterns across all platforms.
+ Get up and running in minutes with simple setup and clear documentation.
+
-
-
-
- } href="/sdk/javascript/calling-overview" horizontal />
- } href="/sdk/react-native/calling-overview" horizontal />
- } href="/sdk/ios/calling-overview" horizontal />
- } href="/sdk/android/calling-overview" horizontal />
- } href="/sdk/flutter/calling-overview" horizontal />
- } href="/sdk/ionic/calling-overview" horizontal />
-
-
-
-
- Step 2
-
-
-
-
- Sync Your Users
-
-
-
- Sync your user database with CometChat for a seamless experience.
-
-
-
-
-
- Add users directly from the CometChat Dashboard.
- Ideal for quick testing or small teams.
-
+
+ } href="/calls/javascript/overview">
+ Vanilla JS or any web framework
-
-
-
- Create users via the SDK methods.
- Perfect for auto-provisioning during sign-up or login.
-
+ } href="/calls/javascript/react-integration">
+ React web applications
-
-
-
- Create users using the REST API.
- Best for batch imports or admin workflows.
-
+ } href="/calls/javascript/angular-integration">
+ Angular web applications
-
-
-{/*
- Verify the Integration
-
-
-
-
- - Log in as **two test users** and exchange a few messages.
- - Confirm **delivery** and **read** states render correctly.
-
-
-
- - Post in a **test group** and verify messages reach **all members**.
- - Check **join/leave** events (if enabled) appear as expected.
-
-
-
- - Ensure **typing indicators** show while the other user composes.
- - Verify **read receipts** update after viewing.
- - Confirm **presence** toggles between **Online / Offline**.
-
-
-
- - **Refresh the browser**; the session should recover gracefully.
- - Toggle **network offline/online** briefly; messages should re-sync.
- - Validate **error toasts/logging** for any failed sends.
-
-
-
-
- All steps passing? You’re ready for staging or production.
- */}
-
+
} href="/calls/javascript/vue-integration">
+ Vue.js web applications
+
+
} href="/calls/javascript/nextjs-integration">
+ Next.js with SSR support
+
+
} href="/calls/javascript/ionic-integration">
+ Ionic hybrid mobile apps
+
+
} href="/calls/react-native/overview">
+ Cross-platform mobile apps
+
+
} href="/calls/ios/overview">
+ Native Swift/Objective-C apps
+
+
} href="/calls/android/overview">
+ Native Kotlin/Java apps
+
+
} href="/calls/flutter/overview">
+ Cross-platform with Dart
+
+
-{/* Sample Apps Section */}
-
- Sample Apps & Demos
-
-
-
- See CometChat in action. Clone these sample apps to get started quickly.
-
+
Sample Apps
+
Explore our open-source sample apps to see the Calls SDK in action. Clone, run, and customize for your needs.
-
-
-
-
-
-
-
-
-
-
+
+
+ Web calling with React, Vue, and vanilla JS
+
+
+ Native Android with Kotlin/Java
+
+
+ Native iOS with Swift
+
+
+ Cross-platform mobile app
+
+
+ Cross-platform with Dart
+
+
-{/* Resources Section */}
-{/*
-
- Help & Resources
-
-
-
- Get help, explore demos, and stay updated with the latest features.
-
-
-
-
- Search our knowledge base for answers to all your questions.
-
-
- Experience CometChat in action with our live demo.
-
-
- Get notified about new features and improvements.
-
-
- Monitor service status and any ongoing issues.
-
-
-
*/}
-
-{/* Office Hours */}
-{/*
-
-
- Get personalized guidance from our solution engineers to optimize your integration.
-
-
-
*/}
+
+
Resources
+
+
+ Features, compatibility, and platform comparison
+
+
+ Troubleshooting guides and FAQs
+
+
-{/* Footer */}
-
-
-
- 2025 © CometChat
-
-
-
diff --git a/calls/android/actions.mdx b/calls/android/actions.mdx
new file mode 100644
index 00000000..04f10bd9
--- /dev/null
+++ b/calls/android/actions.mdx
@@ -0,0 +1,415 @@
+---
+title: "Actions"
+sidebarTitle: "Actions"
+---
+
+Use call actions to create your own custom controls or trigger call functionality dynamically based on your use case. All actions are called on the `CallSession` singleton instance during an active call session.
+
+## Get CallSession Instance
+
+The `CallSession` is a singleton that manages the active call. All actions are accessed through this instance.
+
+
+
+```kotlin
+val callSession = CallSession.getInstance()
+```
+
+
+```java
+CallSession callSession = CallSession.getInstance();
+```
+
+
+
+
+Always check `isSessionActive()` before calling actions to ensure there's an active call.
+
+
+## Actions
+
+### Mute Audio
+
+Mutes your local microphone, stopping audio transmission to other participants.
+
+
+
+```kotlin
+callSession.muteAudio()
+```
+
+
+```java
+callSession.muteAudio();
+```
+
+
+
+### Unmute Audio
+
+Unmutes your local microphone, resuming audio transmission.
+
+
+
+```kotlin
+callSession.unMuteAudio()
+```
+
+
+```java
+callSession.unMuteAudio();
+```
+
+
+
+### Set Audio Mode
+
+Changes the audio output device during a call.
+
+
+
+```kotlin
+callSession.setAudioMode(AudioMode.SPEAKER)
+callSession.setAudioMode(AudioMode.EARPIECE)
+callSession.setAudioMode(AudioMode.BLUETOOTH)
+callSession.setAudioMode(AudioMode.HEADPHONES)
+```
+
+
+```java
+callSession.setAudioMode(AudioMode.SPEAKER);
+callSession.setAudioMode(AudioMode.EARPIECE);
+callSession.setAudioMode(AudioMode.BLUETOOTH);
+callSession.setAudioMode(AudioMode.HEADPHONES);
+```
+
+
+
+
+| Value | Description |
+|-------|-------------|
+| `SPEAKER` | Routes audio through device loudspeaker |
+| `EARPIECE` | Routes audio through phone earpiece |
+| `BLUETOOTH` | Routes audio through connected Bluetooth device |
+| `HEADPHONES` | Routes audio through wired headphones |
+
+
+### Pause Video
+
+Turns off your local camera, stopping video transmission. Other participants see your avatar.
+
+
+
+```kotlin
+callSession.pauseVideo()
+```
+
+
+```java
+callSession.pauseVideo();
+```
+
+
+
+### Resume Video
+
+Turns on your local camera, resuming video transmission.
+
+
+
+```kotlin
+callSession.resumeVideo()
+```
+
+
+```java
+callSession.resumeVideo();
+```
+
+
+
+### Switch Camera
+
+Toggles between front and back cameras without interrupting the video stream.
+
+
+
+```kotlin
+callSession.switchCamera()
+```
+
+
+```java
+callSession.switchCamera();
+```
+
+
+
+### Start Recording
+
+Begins server-side recording of the call. All participants are notified.
+
+
+
+```kotlin
+callSession.startRecording()
+```
+
+
+```java
+callSession.startRecording();
+```
+
+
+
+
+Recording requires the feature to be enabled for your CometChat app.
+
+
+### Stop Recording
+
+Stops the current recording. The recording is saved and accessible via the dashboard.
+
+
+
+```kotlin
+callSession.stopRecording()
+```
+
+
+```java
+callSession.stopRecording();
+```
+
+
+
+### Mute Participant
+
+Mutes a specific participant's audio. This is a moderator action.
+
+
+
+```kotlin
+callSession.muteParticipant(participant.uid)
+```
+
+
+```java
+callSession.muteParticipant(participant.getUid());
+```
+
+
+
+### Pause Participant Video
+
+Pauses a specific participant's video. This is a moderator action.
+
+
+
+```kotlin
+callSession.pauseParticipantVideo(participant.uid)
+```
+
+
+```java
+callSession.pauseParticipantVideo(participant.getUid());
+```
+
+
+
+### Pin Participant
+
+Pins a participant to keep them prominently displayed regardless of who is speaking.
+
+
+
+```kotlin
+callSession.pinParticipant(participant.uid)
+```
+
+
+```java
+callSession.pinParticipant(participant.getUid());
+```
+
+
+
+### Unpin Participant
+
+Removes the pin, returning to automatic speaker highlighting.
+
+
+
+```kotlin
+callSession.unPinParticipant()
+```
+
+
+```java
+callSession.unPinParticipant();
+```
+
+
+
+### Set Layout
+
+Changes the call layout. Each participant can choose their own layout independently.
+
+
+
+```kotlin
+callSession.setLayout(LayoutType.TILE)
+callSession.setLayout(LayoutType.SPOTLIGHT)
+callSession.setLayout(LayoutType.SIDEBAR)
+```
+
+
+```java
+callSession.setLayout(LayoutType.TILE);
+callSession.setLayout(LayoutType.SPOTLIGHT);
+callSession.setLayout(LayoutType.SIDEBAR);
+```
+
+
+
+
+| Value | Description |
+|-------|-------------|
+| `TILE` | Grid layout with equally-sized tiles |
+| `SPOTLIGHT` | Large view for active speaker, small tiles for others |
+| `SIDEBAR` | Main speaker with participants in a sidebar |
+
+
+### Enable Picture In Picture Layout
+
+Enables PiP mode, allowing the call to continue in a floating window.
+
+
+
+```kotlin
+callSession.enablePictureInPictureLayout()
+```
+
+
+```java
+callSession.enablePictureInPictureLayout();
+```
+
+
+
+### Disable Picture In Picture Layout
+
+Disables PiP mode, returning to full-screen call interface.
+
+
+
+```kotlin
+callSession.disablePictureInPictureLayout()
+```
+
+
+```java
+callSession.disablePictureInPictureLayout();
+```
+
+
+
+### Set Chat Button Unread Count
+
+Updates the badge count on the chat button. Pass 0 to hide the badge.
+
+
+
+```kotlin
+callSession.setChatButtonUnreadCount(5)
+```
+
+
+```java
+callSession.setChatButtonUnreadCount(5);
+```
+
+
+
+### Is Session Active
+
+Returns `true` if a call session is active, `false` otherwise.
+
+
+
+```kotlin
+val isActive = callSession.isSessionActive()
+```
+
+
+```java
+boolean isActive = callSession.isSessionActive();
+```
+
+
+
+### Leave Session
+
+Ends your participation and disconnects gracefully. The call continues for other participants.
+
+
+
+```kotlin
+callSession.leaveSession()
+```
+
+
+```java
+callSession.leaveSession();
+```
+
+
+
+### Raise Hand
+
+Shows a hand-raised indicator to get attention from other participants.
+
+
+
+```kotlin
+callSession.raiseHand()
+```
+
+
+```java
+callSession.raiseHand();
+```
+
+
+
+### Lower Hand
+
+Removes the hand-raised indicator.
+
+
+
+```kotlin
+callSession.lowerHand()
+```
+
+
+```java
+callSession.lowerHand();
+```
+
+
+
+
+| Property | Type | Description |
+|----------|------|-------------|
+| `uid` | `String` | Unique identifier (CometChat user ID) |
+| `name` | `String` | Display name |
+| `avatar` | `String` | URL of avatar image |
+| `pid` | `String` | Participant ID for this call session |
+| `role` | `String` | Role in the call |
+| `audioMuted` | `Boolean` | Whether audio is muted |
+| `videoPaused` | `Boolean` | Whether video is paused |
+| `isPinned` | `Boolean` | Whether pinned in layout |
+| `isPresenting` | `Boolean` | Whether screen sharing |
+| `raisedHandTimestamp` | `Long` | Timestamp when hand was raised |
+
diff --git a/calls/android/audio-controls.mdx b/calls/android/audio-controls.mdx
new file mode 100644
index 00000000..0a548c07
--- /dev/null
+++ b/calls/android/audio-controls.mdx
@@ -0,0 +1,239 @@
+---
+title: "Audio Controls"
+sidebarTitle: "Audio Controls"
+---
+
+Control audio during an active call session. These methods allow you to mute/unmute the local microphone and change the audio output device.
+
+## Prerequisites
+
+- An active [call session](/calls/android/join-session)
+- Access to the `CallSession` instance
+
+## Get CallSession Instance
+
+All audio control methods are called on the `CallSession` singleton:
+
+
+
+```kotlin
+val callSession = CallSession.getInstance()
+```
+
+
+```java
+CallSession callSession = CallSession.getInstance();
+```
+
+
+
+---
+
+## Mute Audio
+
+Mute the local microphone. Other participants will no longer hear you.
+
+
+
+```kotlin
+callSession.muteAudio()
+```
+
+
+```java
+callSession.muteAudio();
+```
+
+
+
+
+When you mute your audio, the `onAudioMuted()` callback is triggered on your `MediaEventsListener`.
+
+
+---
+
+## Unmute Audio
+
+Unmute the local microphone to resume transmitting audio.
+
+
+
+```kotlin
+callSession.unMuteAudio()
+```
+
+
+```java
+callSession.unMuteAudio();
+```
+
+
+
+
+When you unmute your audio, the `onAudioUnMuted()` callback is triggered on your `MediaEventsListener`.
+
+
+---
+
+## Set Audio Mode
+
+Change the audio output device during a call. This allows users to switch between speaker, earpiece, Bluetooth, or headphones.
+
+
+
+```kotlin
+// Switch to speaker
+callSession.setAudioMode(AudioMode.SPEAKER)
+
+// Switch to earpiece
+callSession.setAudioMode(AudioMode.EARPIECE)
+
+// Switch to Bluetooth
+callSession.setAudioMode(AudioMode.BLUETOOTH)
+
+// Switch to headphones
+callSession.setAudioMode(AudioMode.HEADPHONES)
+```
+
+
+```java
+// Switch to speaker
+callSession.setAudioMode(AudioMode.SPEAKER);
+
+// Switch to earpiece
+callSession.setAudioMode(AudioMode.EARPIECE);
+
+// Switch to Bluetooth
+callSession.setAudioMode(AudioMode.BLUETOOTH);
+
+// Switch to headphones
+callSession.setAudioMode(AudioMode.HEADPHONES);
+```
+
+
+
+### AudioMode Enum
+
+| Value | Description |
+|-------|-------------|
+| `SPEAKER` | Route audio through the device speaker |
+| `EARPIECE` | Route audio through the phone earpiece |
+| `BLUETOOTH` | Route audio through a connected Bluetooth device |
+| `HEADPHONES` | Route audio through connected wired headphones |
+
+
+When the audio mode changes, the `onAudioModeChanged(AudioMode)` callback is triggered on your `MediaEventsListener`.
+
+
+---
+
+## Listen for Audio Events
+
+Register a `MediaEventsListener` to receive callbacks when audio state changes:
+
+
+
+```kotlin
+callSession.addMediaEventsListener(this, object : MediaEventsListener {
+ override fun onAudioMuted() {
+ Log.d(TAG, "Audio muted")
+ // Update UI to show muted state
+ }
+
+ override fun onAudioUnMuted() {
+ Log.d(TAG, "Audio unmuted")
+ // Update UI to show unmuted state
+ }
+
+ override fun onAudioModeChanged(audioMode: AudioMode) {
+ Log.d(TAG, "Audio mode changed to: ${audioMode.value}")
+ // Update UI to reflect new audio mode
+ }
+
+ // Other MediaEventsListener callbacks...
+ override fun onRecordingStarted() {}
+ override fun onRecordingStopped() {}
+ override fun onScreenShareStarted() {}
+ override fun onScreenShareStopped() {}
+ override fun onCameraFacingChanged(cameraFacing: CameraFacing) {}
+ override fun onVideoPaused() {}
+ override fun onVideoResumed() {}
+})
+```
+
+
+```java
+callSession.addMediaEventsListener(this, new MediaEventsListener() {
+ @Override
+ public void onAudioMuted() {
+ Log.d(TAG, "Audio muted");
+ // Update UI to show muted state
+ }
+
+ @Override
+ public void onAudioUnMuted() {
+ Log.d(TAG, "Audio unmuted");
+ // Update UI to reflect unmuted state
+ }
+
+ @Override
+ public void onAudioModeChanged(AudioMode audioMode) {
+ Log.d(TAG, "Audio mode changed to: " + audioMode.getValue());
+ // Update UI to reflect new audio mode
+ }
+
+ // Other MediaEventsListener callbacks...
+ @Override
+ public void onRecordingStarted() {}
+ @Override
+ public void onRecordingStopped() {}
+ @Override
+ public void onScreenShareStarted() {}
+ @Override
+ public void onScreenShareStopped() {}
+ @Override
+ public void onCameraFacingChanged(CameraFacing cameraFacing) {}
+ @Override
+ public void onVideoPaused() {}
+ @Override
+ public void onVideoResumed() {}
+});
+```
+
+
+
+---
+
+## Initial Audio Settings
+
+You can configure the initial audio state when joining a session using `SessionSettings`:
+
+
+
+```kotlin
+val sessionSettings = CometChatCalls.SessionSettingsBuilder()
+ .startAudioMuted(true) // Start with microphone muted
+ .setAudioMode(AudioMode.SPEAKER) // Start with speaker output
+ .build()
+```
+
+
+```java
+SessionSettings sessionSettings = new CometChatCalls.SessionSettingsBuilder()
+ .startAudioMuted(true) // Start with microphone muted
+ .setAudioMode(AudioMode.SPEAKER) // Start with speaker output
+ .build();
+```
+
+
+
+## Next Steps
+
+
+
+ Control video during calls
+
+
+ Handle all media events
+
+
diff --git a/calls/android/audio-modes.mdx b/calls/android/audio-modes.mdx
new file mode 100644
index 00000000..91f8dc11
--- /dev/null
+++ b/calls/android/audio-modes.mdx
@@ -0,0 +1,195 @@
+---
+title: "Audio Modes"
+sidebarTitle: "Audio Modes"
+---
+
+Control audio output routing during calls. Switch between speaker, earpiece, Bluetooth, and wired headphones based on user preference or device availability.
+
+## Available Audio Modes
+
+| Mode | Description |
+|------|-------------|
+| `SPEAKER` | Routes audio through the device loudspeaker |
+| `EARPIECE` | Routes audio through the phone earpiece (for private calls) |
+| `BLUETOOTH` | Routes audio through a connected Bluetooth device |
+| `HEADPHONES` | Routes audio through wired headphones |
+
+## Set Initial Audio Mode
+
+Configure the audio mode when joining a session:
+
+
+
+```kotlin
+val sessionSettings = CometChatCalls.SessionSettingsBuilder()
+ .setAudioMode(AudioMode.SPEAKER)
+ .build()
+
+CometChatCalls.joinSession(sessionId, sessionSettings, callViewContainer,
+ object : CometChatCalls.CallbackListener() {
+ override fun onSuccess(callSession: CallSession) {
+ Log.d(TAG, "Joined with speaker mode")
+ }
+
+ override fun onError(e: CometChatException) {
+ Log.e(TAG, "Failed: ${e.message}")
+ }
+ }
+)
+```
+
+
+```java
+SessionSettings sessionSettings = new CometChatCalls.SessionSettingsBuilder()
+ .setAudioMode(AudioMode.SPEAKER)
+ .build();
+
+CometChatCalls.joinSession(sessionId, sessionSettings, callViewContainer,
+ new CometChatCalls.CallbackListener() {
+ @Override
+ public void onSuccess(CallSession callSession) {
+ Log.d(TAG, "Joined with speaker mode");
+ }
+
+ @Override
+ public void onError(CometChatException e) {
+ Log.e(TAG, "Failed: " + e.getMessage());
+ }
+ }
+);
+```
+
+
+
+## Change Audio Mode During Call
+
+Switch audio modes dynamically during an active call:
+
+
+
+```kotlin
+val callSession = CallSession.getInstance()
+
+// Switch to speaker
+callSession.setAudioMode(AudioMode.SPEAKER)
+
+// Switch to earpiece
+callSession.setAudioMode(AudioMode.EARPIECE)
+
+// Switch to Bluetooth
+callSession.setAudioMode(AudioMode.BLUETOOTH)
+
+// Switch to wired headphones
+callSession.setAudioMode(AudioMode.HEADPHONES)
+```
+
+
+```java
+CallSession callSession = CallSession.getInstance();
+
+// Switch to speaker
+callSession.setAudioMode(AudioMode.SPEAKER);
+
+// Switch to earpiece
+callSession.setAudioMode(AudioMode.EARPIECE);
+
+// Switch to Bluetooth
+callSession.setAudioMode(AudioMode.BLUETOOTH);
+
+// Switch to wired headphones
+callSession.setAudioMode(AudioMode.HEADPHONES);
+```
+
+
+
+## Listen for Audio Mode Changes
+
+Monitor audio mode changes using `MediaEventsListener`:
+
+
+
+```kotlin
+val callSession = CallSession.getInstance()
+
+callSession.addMediaEventsListener(this, object : MediaEventsListener() {
+ override fun onAudioModeChanged(audioMode: AudioMode) {
+ when (audioMode) {
+ AudioMode.SPEAKER -> Log.d(TAG, "Switched to speaker")
+ AudioMode.EARPIECE -> Log.d(TAG, "Switched to earpiece")
+ AudioMode.BLUETOOTH -> Log.d(TAG, "Switched to Bluetooth")
+ AudioMode.HEADPHONES -> Log.d(TAG, "Switched to headphones")
+ }
+ // Update audio mode button icon
+ updateAudioModeIcon(audioMode)
+ }
+
+ // Other callbacks...
+ override fun onAudioMuted() {}
+ override fun onAudioUnMuted() {}
+ override fun onVideoPaused() {}
+ override fun onVideoResumed() {}
+ override fun onRecordingStarted() {}
+ override fun onRecordingStopped() {}
+ override fun onScreenShareStarted() {}
+ override fun onScreenShareStopped() {}
+ override fun onCameraFacingChanged(facing: CameraFacing) {}
+})
+```
+
+
+```java
+CallSession callSession = CallSession.getInstance();
+
+callSession.addMediaEventsListener(this, new MediaEventsListener() {
+ @Override
+ public void onAudioModeChanged(AudioMode audioMode) {
+ switch (audioMode) {
+ case SPEAKER:
+ Log.d(TAG, "Switched to speaker");
+ break;
+ case EARPIECE:
+ Log.d(TAG, "Switched to earpiece");
+ break;
+ case BLUETOOTH:
+ Log.d(TAG, "Switched to Bluetooth");
+ break;
+ case HEADPHONES:
+ Log.d(TAG, "Switched to headphones");
+ break;
+ }
+ // Update audio mode button icon
+ updateAudioModeIcon(audioMode);
+ }
+
+ // Other callbacks...
+});
+```
+
+
+
+## Hide Audio Mode Button
+
+To prevent users from changing the audio mode, hide the button in the call UI:
+
+
+
+```kotlin
+val sessionSettings = CometChatCalls.SessionSettingsBuilder()
+ .setAudioMode(AudioMode.SPEAKER) // Fixed audio mode
+ .hideAudioModeButton(true) // Hide toggle button
+ .build()
+```
+
+
+```java
+SessionSettings sessionSettings = new CometChatCalls.SessionSettingsBuilder()
+ .setAudioMode(AudioMode.SPEAKER) // Fixed audio mode
+ .hideAudioModeButton(true) // Hide toggle button
+ .build();
+```
+
+
+
+
+The SDK automatically detects connected audio devices. If Bluetooth or wired headphones are connected, they become available as audio mode options.
+
diff --git a/calls/android/authentication.mdx b/calls/android/authentication.mdx
new file mode 100644
index 00000000..e2d53d89
--- /dev/null
+++ b/calls/android/authentication.mdx
@@ -0,0 +1,215 @@
+---
+title: "Authentication"
+sidebarTitle: "Authentication"
+---
+
+Before users can make or receive calls, they must be authenticated with the CometChat Calls SDK. This guide covers the login and logout methods.
+
+
+**Sample Users**
+
+CometChat provides 5 test users: `cometchat-uid-1`, `cometchat-uid-2`, `cometchat-uid-3`, `cometchat-uid-4`, and `cometchat-uid-5`.
+
+
+## Check Login Status
+
+Before calling `login()`, check if a user is already logged in using `getLoggedInUser()`. The SDK maintains the session internally, so you only need to login once per user session.
+
+
+
+```kotlin
+val loggedInUser = CometChatCalls.getLoggedInUser()
+
+if (loggedInUser != null) {
+ // User is already logged in
+ Log.d(TAG, "User already logged in: ${loggedInUser.uid}")
+} else {
+ // No user logged in, proceed with login
+}
+```
+
+
+```java
+CallUser loggedInUser = CometChatCalls.getLoggedInUser();
+
+if (loggedInUser != null) {
+ // User is already logged in
+ Log.d(TAG, "User already logged in: " + loggedInUser.getUid());
+} else {
+ // No user logged in, proceed with login
+}
+```
+
+
+
+The `getLoggedInUser()` method returns a `CallUser` object if a user is logged in, or `null` if no session exists.
+
+## Login with UID and API Key
+
+This method is suitable for development and testing. For production apps, use [Auth Token login](#login-with-auth-token) instead.
+
+
+**Security Notice**
+
+Using the API Key directly in client code is not recommended for production. Use Auth Token authentication for enhanced security.
+
+
+
+
+```kotlin
+val uid = "cometchat-uid-1" // Replace with your user's UID
+val apiKey = "API_KEY" // Replace with your API Key
+
+if (CometChatCalls.getLoggedInUser() == null) {
+ CometChatCalls.login(uid, apiKey, object : CometChatCalls.CallbackListener() {
+ override fun onSuccess(user: CallUser) {
+ Log.d(TAG, "Login successful: ${user.uid}")
+ }
+
+ override fun onError(e: CometChatException) {
+ Log.e(TAG, "Login failed: ${e.message}")
+ }
+ })
+} else {
+ // User already logged in
+}
+```
+
+
+```java
+String uid = "cometchat-uid-1"; // Replace with your user's UID
+String apiKey = "API_KEY"; // Replace with your API Key
+
+if (CometChatCalls.getLoggedInUser() == null) {
+ CometChatCalls.login(uid, apiKey, new CometChatCalls.CallbackListener() {
+ @Override
+ public void onSuccess(CallUser user) {
+ Log.d(TAG, "Login successful: " + user.getUid());
+ }
+
+ @Override
+ public void onError(CometChatException e) {
+ Log.e(TAG, "Login failed: " + e.getMessage());
+ }
+ });
+} else {
+ // User already logged in
+}
+```
+
+
+
+| Parameter | Type | Description |
+|-----------|------|-------------|
+| `uid` | String | The unique identifier of the user to login |
+| `apiKey` | String | Your CometChat API Key |
+| `listener` | CallbackListener | Callback for success/error handling |
+
+## Login with Auth Token
+
+This is the recommended authentication method for production applications. The Auth Token is generated server-side, keeping your API Key secure.
+
+### Auth Token Flow
+
+1. User authenticates with your backend
+2. Your backend calls the [CometChat Create Auth Token API](https://api-explorer.cometchat.com/reference/create-authtoken)
+3. Your backend returns the Auth Token to the client
+4. Client uses the Auth Token to login
+
+
+
+```kotlin
+val authToken = "AUTH_TOKEN" // Token received from your backend
+
+CometChatCalls.login(authToken, object : CometChatCalls.CallbackListener() {
+ override fun onSuccess(user: CallUser) {
+ Log.d(TAG, "Login successful: ${user.uid}")
+ }
+
+ override fun onError(e: CometChatException) {
+ Log.e(TAG, "Login failed: ${e.message}")
+ }
+})
+```
+
+
+```java
+String authToken = "AUTH_TOKEN"; // Token received from your backend
+
+CometChatCalls.login(authToken, new CometChatCalls.CallbackListener() {
+ @Override
+ public void onSuccess(CallUser user) {
+ Log.d(TAG, "Login successful: " + user.getUid());
+ }
+
+ @Override
+ public void onError(CometChatException e) {
+ Log.e(TAG, "Login failed: " + e.getMessage());
+ }
+});
+```
+
+
+
+| Parameter | Type | Description |
+|-----------|------|-------------|
+| `authToken` | String | Auth Token generated via CometChat API |
+| `listener` | CallbackListener | Callback for success/error handling |
+
+## CallUser Object
+
+On successful login, the callback returns a `CallUser` object containing user information:
+
+| Property | Type | Description |
+|----------|------|-------------|
+| `uid` | String | Unique identifier of the user |
+| `name` | String | Display name of the user |
+| `avatar` | String | URL of the user's avatar image |
+| `status` | String | User's online status |
+
+## Logout
+
+Call `logout()` when the user signs out of your application. This clears the local session and disconnects from CometChat services.
+
+
+
+```kotlin
+CometChatCalls.logout(object : CometChatCalls.CallbackListener() {
+ override fun onSuccess(message: String) {
+ Log.d(TAG, "Logout successful")
+ }
+
+ override fun onError(e: CometChatException) {
+ Log.e(TAG, "Logout failed: ${e.message}")
+ }
+})
+```
+
+
+```java
+CometChatCalls.logout(new CometChatCalls.CallbackListener() {
+ @Override
+ public void onSuccess(String message) {
+ Log.d(TAG, "Logout successful");
+ }
+
+ @Override
+ public void onError(CometChatException e) {
+ Log.e(TAG, "Logout failed: " + e.getMessage());
+ }
+});
+```
+
+
+
+## Error Handling
+
+Common authentication errors:
+
+| Error Code | Description |
+|------------|-------------|
+| `ERROR_INVALID_UID` | The provided UID is empty or invalid |
+| `ERROR_UID_WITH_SPACE` | The UID contains spaces (not allowed) |
+| `ERROR_API_KEY_NOT_FOUND` | The API Key is missing or invalid |
+| `ERROR_BLANK_AUTHTOKEN` | The Auth Token is empty |
+| `ERROR_LOGIN_IN_PROGRESS` | A login operation is already in progress |
diff --git a/calls/android/background-handling.mdx b/calls/android/background-handling.mdx
new file mode 100644
index 00000000..c507d0bd
--- /dev/null
+++ b/calls/android/background-handling.mdx
@@ -0,0 +1,478 @@
+---
+title: "Background Handling"
+sidebarTitle: "Background Handling"
+---
+
+Keep calls alive when users navigate away from your app. Background handling ensures the call continues running when users press the home button, switch to another app, or lock their device.
+
+## Overview
+
+When a user leaves your call activity, Android may terminate it to free resources. The SDK provides `CometChatOngoingCallService` - a foreground service that:
+- Keeps the call session active in the background
+- Shows an ongoing notification in the status bar
+- Allows users to return to the call with a single tap
+- Provides a hangup action directly from the notification
+
+```mermaid
+flowchart LR
+ subgraph "User in Call"
+ A[Call Activity] --> B{User Action}
+ end
+
+ B -->|Stays in app| A
+ B -->|Enters PiP| C[PiP Window]
+ B -->|Presses HOME| D[OngoingCallService]
+ B -->|Opens other app| D
+
+ D --> E[Ongoing Notification]
+ E -->|Tap| A
+ E -->|Hangup| F[Call Ends]
+```
+
+## When to Use
+
+| Scenario | Solution |
+|----------|----------|
+| User stays in call activity | No action needed |
+| User enters Picture-in-Picture | [PiP Mode](/calls/android/picture-in-picture) handles this |
+| User presses HOME during call | **Use OngoingCallService** |
+| User switches to another app | **Use OngoingCallService** |
+| Receiving calls when app is killed | [VoIP Calling](/calls/android/voip-calling) handles this |
+
+
+Background Handling is different from [VoIP Calling](/calls/android/voip-calling). VoIP handles **receiving** calls when the app is not running. Background Handling keeps an **active** call alive when the user leaves the app.
+
+
+---
+
+## Implementation
+
+### Step 1: Add Manifest Permissions
+
+Add the required permissions and service declaration to your `AndroidManifest.xml`:
+
+```xml
+
+
+
+
+
+
+
+
+
+
+```
+
+### Step 2: Start Service on Session Join
+
+Start the ongoing call service when the user successfully joins a call session:
+
+
+
+```kotlin
+import com.cometchat.calls.services.CometChatOngoingCallService
+import com.cometchat.calls.utils.OngoingNotification
+
+class CallActivity : AppCompatActivity() {
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_call)
+
+ val callSession = CallSession.getInstance()
+
+ callSession.addSessionStatusListener(this, object : SessionStatusListener() {
+ override fun onSessionJoined() {
+ // Start the foreground service to keep call alive in background
+ CometChatOngoingCallService.launch(this@CallActivity)
+ }
+
+ override fun onSessionLeft() {
+ // Stop the service when call ends
+ CometChatOngoingCallService.abort(this@CallActivity)
+ finish()
+ }
+
+ override fun onConnectionClosed() {
+ CometChatOngoingCallService.abort(this@CallActivity)
+ }
+ })
+
+ // Join the session...
+ }
+
+ override fun onDestroy() {
+ // Always stop the service when activity is destroyed
+ CometChatOngoingCallService.abort(this)
+ super.onDestroy()
+ }
+}
+```
+
+
+```java
+import com.cometchat.calls.services.CometChatOngoingCallService;
+import com.cometchat.calls.utils.OngoingNotification;
+
+public class CallActivity extends AppCompatActivity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_call);
+
+ CallSession callSession = CallSession.getInstance();
+
+ callSession.addSessionStatusListener(this, new SessionStatusListener() {
+ @Override
+ public void onSessionJoined() {
+ // Start the foreground service to keep call alive in background
+ CometChatOngoingCallService.launch(CallActivity.this);
+ }
+
+ @Override
+ public void onSessionLeft() {
+ // Stop the service when call ends
+ CometChatOngoingCallService.abort(CallActivity.this);
+ finish();
+ }
+
+ @Override
+ public void onConnectionClosed() {
+ CometChatOngoingCallService.abort(CallActivity.this);
+ }
+ });
+
+ // Join the session...
+ }
+
+ @Override
+ protected void onDestroy() {
+ // Always stop the service when activity is destroyed
+ CometChatOngoingCallService.abort(this);
+ super.onDestroy();
+ }
+}
+```
+
+
+
+---
+
+## Custom Notification
+
+Customize the ongoing call notification to match your app's branding:
+
+
+
+```kotlin
+import com.cometchat.calls.utils.OngoingNotification
+
+private fun buildCustomNotification(): Notification {
+ val channelId = "CometChat_Call_Ongoing_Conference"
+
+ // Intent to return to call when notification is tapped
+ val intent = Intent(this, CallActivity::class.java).apply {
+ flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP
+ }
+ val pendingIntent = PendingIntent.getActivity(
+ this, 0, intent,
+ PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
+ )
+
+ return NotificationCompat.Builder(this, channelId)
+ .setSmallIcon(R.drawable.ic_call)
+ .setContentTitle("Ongoing Call")
+ .setContentText("Tap to return to your call")
+ .setPriority(NotificationCompat.PRIORITY_HIGH)
+ .setContentIntent(pendingIntent)
+ .setOngoing(true)
+ .setAutoCancel(false)
+ .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
+ .build()
+}
+
+// Use custom notification before launching service
+callSession.addSessionStatusListener(this, object : SessionStatusListener() {
+ override fun onSessionJoined() {
+ OngoingNotification.buildOngoingConferenceNotification(buildCustomNotification())
+ CometChatOngoingCallService.launch(this@CallActivity)
+ }
+})
+```
+
+
+```java
+import com.cometchat.calls.utils.OngoingNotification;
+
+private Notification buildCustomNotification() {
+ String channelId = "CometChat_Call_Ongoing_Conference";
+
+ // Intent to return to call when notification is tapped
+ Intent intent = new Intent(this, CallActivity.class);
+ intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
+ PendingIntent pendingIntent = PendingIntent.getActivity(
+ this, 0, intent,
+ PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE
+ );
+
+ return new NotificationCompat.Builder(this, channelId)
+ .setSmallIcon(R.drawable.ic_call)
+ .setContentTitle("Ongoing Call")
+ .setContentText("Tap to return to your call")
+ .setPriority(NotificationCompat.PRIORITY_HIGH)
+ .setContentIntent(pendingIntent)
+ .setOngoing(true)
+ .setAutoCancel(false)
+ .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
+ .build();
+}
+
+// Use custom notification before launching service
+callSession.addSessionStatusListener(this, new SessionStatusListener() {
+ @Override
+ public void onSessionJoined() {
+ OngoingNotification.buildOngoingConferenceNotification(buildCustomNotification());
+ CometChatOngoingCallService.launch(CallActivity.this);
+ }
+});
+```
+
+
+
+
+The notification channel ID must be `CometChat_Call_Ongoing_Conference` to work with the SDK's service.
+
+
+---
+
+## Complete Example
+
+
+
+```kotlin
+class CallActivity : AppCompatActivity() {
+
+ private lateinit var callSession: CallSession
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_call)
+
+ callSession = CallSession.getInstance()
+
+ setupSessionListener()
+ joinCall()
+ }
+
+ private fun setupSessionListener() {
+ callSession.addSessionStatusListener(this, object : SessionStatusListener() {
+ override fun onSessionJoined() {
+ // Build custom notification (optional)
+ OngoingNotification.buildOngoingConferenceNotification(buildCustomNotification())
+
+ // Start foreground service
+ CometChatOngoingCallService.launch(this@CallActivity)
+ }
+
+ override fun onSessionLeft() {
+ CometChatOngoingCallService.abort(this@CallActivity)
+ finish()
+ }
+
+ override fun onConnectionClosed() {
+ CometChatOngoingCallService.abort(this@CallActivity)
+ }
+ })
+ }
+
+ private fun joinCall() {
+ val sessionId = intent.getStringExtra("SESSION_ID") ?: return
+ val container = findViewById(R.id.callContainer)
+
+ val sessionSettings = CometChatCalls.SessionSettingsBuilder()
+ .setTitle("My Call")
+ .build()
+
+ CometChatCalls.joinSession(
+ sessionId = sessionId,
+ sessionSettings = sessionSettings,
+ view = container,
+ context = this,
+ listener = object : CometChatCalls.CallbackListener() {
+ override fun onSuccess(session: CallSession) {
+ Log.d(TAG, "Joined call successfully")
+ }
+
+ override fun onError(e: CometChatException) {
+ Log.e(TAG, "Failed to join: ${e.message}")
+ finish()
+ }
+ }
+ )
+ }
+
+ private fun buildCustomNotification(): Notification {
+ val channelId = "CometChat_Call_Ongoing_Conference"
+
+ val intent = Intent(this, CallActivity::class.java).apply {
+ flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP
+ }
+ val pendingIntent = PendingIntent.getActivity(
+ this, 0, intent,
+ PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
+ )
+
+ return NotificationCompat.Builder(this, channelId)
+ .setSmallIcon(R.drawable.ic_call)
+ .setContentTitle("Ongoing Call")
+ .setContentText("Tap to return to your call")
+ .setPriority(NotificationCompat.PRIORITY_HIGH)
+ .setContentIntent(pendingIntent)
+ .setOngoing(true)
+ .setAutoCancel(false)
+ .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
+ .build()
+ }
+
+ override fun onDestroy() {
+ CometChatOngoingCallService.abort(this)
+ super.onDestroy()
+ }
+
+ companion object {
+ private const val TAG = "CallActivity"
+ }
+}
+```
+
+
+```java
+public class CallActivity extends AppCompatActivity {
+
+ private static final String TAG = "CallActivity";
+ private CallSession callSession;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_call);
+
+ callSession = CallSession.getInstance();
+
+ setupSessionListener();
+ joinCall();
+ }
+
+ private void setupSessionListener() {
+ callSession.addSessionStatusListener(this, new SessionStatusListener() {
+ @Override
+ public void onSessionJoined() {
+ // Build custom notification (optional)
+ OngoingNotification.buildOngoingConferenceNotification(buildCustomNotification());
+
+ // Start foreground service
+ CometChatOngoingCallService.launch(CallActivity.this);
+ }
+
+ @Override
+ public void onSessionLeft() {
+ CometChatOngoingCallService.abort(CallActivity.this);
+ finish();
+ }
+
+ @Override
+ public void onConnectionClosed() {
+ CometChatOngoingCallService.abort(CallActivity.this);
+ }
+ });
+ }
+
+ private void joinCall() {
+ String sessionId = getIntent().getStringExtra("SESSION_ID");
+ if (sessionId == null) return;
+
+ FrameLayout container = findViewById(R.id.callContainer);
+
+ SessionSettings sessionSettings = new CometChatCalls.SessionSettingsBuilder()
+ .setTitle("My Call")
+ .build();
+
+ CometChatCalls.joinSession(
+ sessionId,
+ sessionSettings,
+ container,
+ this,
+ new CometChatCalls.CallbackListener() {
+ @Override
+ public void onSuccess(CallSession session) {
+ Log.d(TAG, "Joined call successfully");
+ }
+
+ @Override
+ public void onError(CometChatException e) {
+ Log.e(TAG, "Failed to join: " + e.getMessage());
+ finish();
+ }
+ }
+ );
+ }
+
+ private Notification buildCustomNotification() {
+ String channelId = "CometChat_Call_Ongoing_Conference";
+
+ Intent intent = new Intent(this, CallActivity.class);
+ intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
+ PendingIntent pendingIntent = PendingIntent.getActivity(
+ this, 0, intent,
+ PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE
+ );
+
+ return new NotificationCompat.Builder(this, channelId)
+ .setSmallIcon(R.drawable.ic_call)
+ .setContentTitle("Ongoing Call")
+ .setContentText("Tap to return to your call")
+ .setPriority(NotificationCompat.PRIORITY_HIGH)
+ .setContentIntent(pendingIntent)
+ .setOngoing(true)
+ .setAutoCancel(false)
+ .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
+ .build();
+ }
+
+ @Override
+ protected void onDestroy() {
+ CometChatOngoingCallService.abort(this);
+ super.onDestroy();
+ }
+}
+```
+
+
+
+---
+
+## API Reference
+
+### CometChatOngoingCallService
+
+| Method | Description |
+|--------|-------------|
+| `launch(context)` | Starts the foreground service with an ongoing notification |
+| `abort(context)` | Stops the foreground service and removes the notification |
+
+### OngoingNotification
+
+| Method | Description |
+|--------|-------------|
+| `buildOngoingConferenceNotification(notification)` | Sets a custom notification to display. Call before `launch()` |
+| `createNotificationChannel(activity)` | Creates the notification channel (called automatically) |
+
+---
+
+## Related Documentation
+
+- [Picture-in-Picture](/calls/android/picture-in-picture) - Keep call visible while using other apps
+- [VoIP Calling](/calls/android/voip-calling) - Receive calls when app is killed
+- [Session Status Listener](/calls/android/session-status-listener) - Listen for session events
diff --git a/calls/android/button-click-listener.mdx b/calls/android/button-click-listener.mdx
new file mode 100644
index 00000000..6beadb97
--- /dev/null
+++ b/calls/android/button-click-listener.mdx
@@ -0,0 +1,799 @@
+---
+title: "Button Click Listener"
+sidebarTitle: "Button Click Listener"
+---
+
+Intercept UI button clicks with `ButtonClickListener`. This listener provides callbacks when users tap buttons in the call UI, allowing you to implement custom behavior or show confirmation dialogs.
+
+## Prerequisites
+
+- An active [call session](/calls/android/join-session)
+- Access to the `CallSession` instance
+
+## Register Listener
+
+Register a `ButtonClickListener` to receive button click callbacks:
+
+
+
+```kotlin
+val callSession = CallSession.getInstance()
+
+callSession.addButtonClickListener(this, object : ButtonClickListener() {
+ override fun onLeaveSessionButtonClicked() {
+ Log.d(TAG, "Leave button clicked")
+ }
+
+ override fun onToggleAudioButtonClicked() {
+ Log.d(TAG, "Audio toggle button clicked")
+ }
+
+ override fun onToggleVideoButtonClicked() {
+ Log.d(TAG, "Video toggle button clicked")
+ }
+
+ // Additional callbacks...
+})
+```
+
+
+```java
+CallSession callSession = CallSession.getInstance();
+
+callSession.addButtonClickListener(this, new ButtonClickListener() {
+ @Override
+ public void onLeaveSessionButtonClicked() {
+ Log.d(TAG, "Leave button clicked");
+ }
+
+ @Override
+ public void onToggleAudioButtonClicked() {
+ Log.d(TAG, "Audio toggle button clicked");
+ }
+
+ @Override
+ public void onToggleVideoButtonClicked() {
+ Log.d(TAG, "Video toggle button clicked");
+ }
+
+ // Additional callbacks...
+});
+```
+
+
+
+
+The listener is automatically removed when the `LifecycleOwner` (Activity/Fragment) is destroyed, preventing memory leaks.
+
+
+---
+
+## Callbacks
+
+### onLeaveSessionButtonClicked
+
+Triggered when the user taps the leave session button.
+
+
+
+```kotlin
+override fun onLeaveSessionButtonClicked() {
+ Log.d(TAG, "Leave button clicked")
+ // Show confirmation dialog before leaving
+ showLeaveConfirmationDialog()
+}
+
+private fun showLeaveConfirmationDialog() {
+ AlertDialog.Builder(this)
+ .setTitle("Leave Call")
+ .setMessage("Are you sure you want to leave this call?")
+ .setPositiveButton("Leave") { _, _ ->
+ callSession.leaveSession()
+ }
+ .setNegativeButton("Cancel", null)
+ .show()
+}
+```
+
+
+```java
+@Override
+public void onLeaveSessionButtonClicked() {
+ Log.d(TAG, "Leave button clicked");
+ // Show confirmation dialog before leaving
+ showLeaveConfirmationDialog();
+}
+
+private void showLeaveConfirmationDialog() {
+ new AlertDialog.Builder(this)
+ .setTitle("Leave Call")
+ .setMessage("Are you sure you want to leave this call?")
+ .setPositiveButton("Leave", (dialog, which) -> {
+ callSession.leaveSession();
+ })
+ .setNegativeButton("Cancel", null)
+ .show();
+}
+```
+
+
+
+**Use Cases:**
+- Show confirmation dialog before leaving
+- Log analytics event
+- Perform cleanup before leaving
+
+---
+
+### onToggleAudioButtonClicked
+
+Triggered when the user taps the audio mute/unmute button.
+
+
+
+```kotlin
+override fun onToggleAudioButtonClicked() {
+ Log.d(TAG, "Audio toggle clicked")
+ // Track audio toggle analytics
+ analytics.logEvent("audio_toggled")
+}
+```
+
+
+```java
+@Override
+public void onToggleAudioButtonClicked() {
+ Log.d(TAG, "Audio toggle clicked");
+ // Track audio toggle analytics
+ analytics.logEvent("audio_toggled");
+}
+```
+
+
+
+**Use Cases:**
+- Log analytics events
+- Show tooltip on first use
+- Implement custom audio toggle logic
+
+---
+
+### onToggleVideoButtonClicked
+
+Triggered when the user taps the video on/off button.
+
+
+
+```kotlin
+override fun onToggleVideoButtonClicked() {
+ Log.d(TAG, "Video toggle clicked")
+ // Track video toggle analytics
+ analytics.logEvent("video_toggled")
+}
+```
+
+
+```java
+@Override
+public void onToggleVideoButtonClicked() {
+ Log.d(TAG, "Video toggle clicked");
+ // Track video toggle analytics
+ analytics.logEvent("video_toggled");
+}
+```
+
+
+
+**Use Cases:**
+- Log analytics events
+- Check camera permissions
+- Implement custom video toggle logic
+
+---
+
+### onSwitchCameraButtonClicked
+
+Triggered when the user taps the switch camera button.
+
+
+
+```kotlin
+override fun onSwitchCameraButtonClicked() {
+ Log.d(TAG, "Switch camera clicked")
+ // Track camera switch analytics
+}
+```
+
+
+```java
+@Override
+public void onSwitchCameraButtonClicked() {
+ Log.d(TAG, "Switch camera clicked");
+ // Track camera switch analytics
+}
+```
+
+
+
+**Use Cases:**
+- Log analytics events
+- Show camera switching animation
+- Track front/back camera usage
+
+---
+
+### onRaiseHandButtonClicked
+
+Triggered when the user taps the raise hand button.
+
+
+
+```kotlin
+override fun onRaiseHandButtonClicked() {
+ Log.d(TAG, "Raise hand clicked")
+ // Show hand raised confirmation
+ Toast.makeText(this, "Hand raised", Toast.LENGTH_SHORT).show()
+}
+```
+
+
+```java
+@Override
+public void onRaiseHandButtonClicked() {
+ Log.d(TAG, "Raise hand clicked");
+ // Show hand raised confirmation
+ Toast.makeText(this, "Hand raised", Toast.LENGTH_SHORT).show();
+}
+```
+
+
+
+**Use Cases:**
+- Show confirmation feedback
+- Log analytics events
+- Implement custom hand raise behavior
+
+---
+
+### onShareInviteButtonClicked
+
+Triggered when the user taps the share invite button.
+
+
+
+```kotlin
+override fun onShareInviteButtonClicked() {
+ Log.d(TAG, "Share invite clicked")
+ // Show custom share dialog
+ showShareDialog()
+}
+
+private fun showShareDialog() {
+ val shareIntent = Intent(Intent.ACTION_SEND).apply {
+ type = "text/plain"
+ putExtra(Intent.EXTRA_TEXT, "Join my call: https://example.com/call/$sessionId")
+ }
+ startActivity(Intent.createChooser(shareIntent, "Share call link"))
+}
+```
+
+
+```java
+@Override
+public void onShareInviteButtonClicked() {
+ Log.d(TAG, "Share invite clicked");
+ // Show custom share dialog
+ showShareDialog();
+}
+
+private void showShareDialog() {
+ Intent shareIntent = new Intent(Intent.ACTION_SEND);
+ shareIntent.setType("text/plain");
+ shareIntent.putExtra(Intent.EXTRA_TEXT, "Join my call: https://example.com/call/" + sessionId);
+ startActivity(Intent.createChooser(shareIntent, "Share call link"));
+}
+```
+
+
+
+**Use Cases:**
+- Show custom share sheet
+- Generate and share invite link
+- Copy link to clipboard
+
+---
+
+### onChangeLayoutButtonClicked
+
+Triggered when the user taps the change layout button.
+
+
+
+```kotlin
+override fun onChangeLayoutButtonClicked() {
+ Log.d(TAG, "Change layout clicked")
+ // Show layout options dialog
+ showLayoutOptionsDialog()
+}
+
+private fun showLayoutOptionsDialog() {
+ val layouts = arrayOf("Tile", "Spotlight")
+ AlertDialog.Builder(this)
+ .setTitle("Select Layout")
+ .setItems(layouts) { _, which ->
+ val layoutType = if (which == 0) LayoutType.TILE else LayoutType.SPOTLIGHT
+ callSession.setLayout(layoutType)
+ }
+ .show()
+}
+```
+
+
+```java
+@Override
+public void onChangeLayoutButtonClicked() {
+ Log.d(TAG, "Change layout clicked");
+ // Show layout options dialog
+ showLayoutOptionsDialog();
+}
+
+private void showLayoutOptionsDialog() {
+ String[] layouts = {"Tile", "Spotlight"};
+ new AlertDialog.Builder(this)
+ .setTitle("Select Layout")
+ .setItems(layouts, (dialog, which) -> {
+ LayoutType layoutType = (which == 0) ? LayoutType.TILE : LayoutType.SPOTLIGHT;
+ callSession.setLayout(layoutType);
+ })
+ .show();
+}
+```
+
+
+
+**Use Cases:**
+- Show custom layout picker
+- Log layout change analytics
+- Implement custom layout switching
+
+---
+
+### onParticipantListButtonClicked
+
+Triggered when the user taps the participant list button.
+
+
+
+```kotlin
+override fun onParticipantListButtonClicked() {
+ Log.d(TAG, "Participant list clicked")
+ // Track participant list views
+ analytics.logEvent("participant_list_opened")
+}
+```
+
+
+```java
+@Override
+public void onParticipantListButtonClicked() {
+ Log.d(TAG, "Participant list clicked");
+ // Track participant list views
+ analytics.logEvent("participant_list_opened");
+}
+```
+
+
+
+**Use Cases:**
+- Log analytics events
+- Show custom participant list UI
+- Track feature usage
+
+---
+
+### onChatButtonClicked
+
+Triggered when the user taps the chat button.
+
+
+
+```kotlin
+override fun onChatButtonClicked() {
+ Log.d(TAG, "Chat button clicked")
+ // Open custom chat UI
+ openChatScreen()
+}
+
+private fun openChatScreen() {
+ // Navigate to chat screen or show chat overlay
+ val intent = Intent(this, ChatActivity::class.java)
+ intent.putExtra("sessionId", sessionId)
+ startActivity(intent)
+}
+```
+
+
+```java
+@Override
+public void onChatButtonClicked() {
+ Log.d(TAG, "Chat button clicked");
+ // Open custom chat UI
+ openChatScreen();
+}
+
+private void openChatScreen() {
+ // Navigate to chat screen or show chat overlay
+ Intent intent = new Intent(this, ChatActivity.class);
+ intent.putExtra("sessionId", sessionId);
+ startActivity(intent);
+}
+```
+
+
+
+**Use Cases:**
+- Open custom chat interface
+- Show in-call messaging overlay
+- Navigate to chat screen
+
+---
+
+### onRecordingToggleButtonClicked
+
+Triggered when the user taps the recording button.
+
+
+
+```kotlin
+override fun onRecordingToggleButtonClicked() {
+ Log.d(TAG, "Recording toggle clicked")
+ // Show recording consent dialog
+ showRecordingConsentDialog()
+}
+
+private fun showRecordingConsentDialog() {
+ AlertDialog.Builder(this)
+ .setTitle("Start Recording")
+ .setMessage("All participants will be notified that this call is being recorded.")
+ .setPositiveButton("Start") { _, _ ->
+ callSession.startRecording()
+ }
+ .setNegativeButton("Cancel", null)
+ .show()
+}
+```
+
+
+```java
+@Override
+public void onRecordingToggleButtonClicked() {
+ Log.d(TAG, "Recording toggle clicked");
+ // Show recording consent dialog
+ showRecordingConsentDialog();
+}
+
+private void showRecordingConsentDialog() {
+ new AlertDialog.Builder(this)
+ .setTitle("Start Recording")
+ .setMessage("All participants will be notified that this call is being recorded.")
+ .setPositiveButton("Start", (dialog, which) -> {
+ callSession.startRecording();
+ })
+ .setNegativeButton("Cancel", null)
+ .show();
+}
+```
+
+
+
+**Use Cases:**
+- Show recording consent dialog
+- Check recording permissions
+- Log recording analytics
+
+---
+
+## Complete Example
+
+Here's a complete example handling all button click events:
+
+
+
+```kotlin
+class CallActivity : AppCompatActivity() {
+ private lateinit var callSession: CallSession
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_call)
+
+ callSession = CallSession.getInstance()
+ setupButtonClickListener()
+ }
+
+ private fun setupButtonClickListener() {
+ callSession.addButtonClickListener(this, object : ButtonClickListener() {
+ override fun onLeaveSessionButtonClicked() {
+ showLeaveConfirmationDialog()
+ }
+
+ override fun onToggleAudioButtonClicked() {
+ Log.d(TAG, "Audio toggle clicked")
+ }
+
+ override fun onToggleVideoButtonClicked() {
+ Log.d(TAG, "Video toggle clicked")
+ }
+
+ override fun onSwitchCameraButtonClicked() {
+ Log.d(TAG, "Switch camera clicked")
+ }
+
+ override fun onRaiseHandButtonClicked() {
+ Toast.makeText(
+ this@CallActivity,
+ "Hand raised",
+ Toast.LENGTH_SHORT
+ ).show()
+ }
+
+ override fun onShareInviteButtonClicked() {
+ shareCallLink()
+ }
+
+ override fun onChangeLayoutButtonClicked() {
+ showLayoutOptionsDialog()
+ }
+
+ override fun onParticipantListButtonClicked() {
+ Log.d(TAG, "Participant list opened")
+ }
+
+ override fun onChatButtonClicked() {
+ openChatScreen()
+ }
+
+ override fun onRecordingToggleButtonClicked() {
+ showRecordingConsentDialog()
+ }
+ })
+ }
+
+ private fun showLeaveConfirmationDialog() {
+ AlertDialog.Builder(this)
+ .setTitle("Leave Call")
+ .setMessage("Are you sure you want to leave?")
+ .setPositiveButton("Leave") { _, _ ->
+ callSession.leaveSession()
+ }
+ .setNegativeButton("Cancel", null)
+ .show()
+ }
+
+ private fun shareCallLink() {
+ val shareIntent = Intent(Intent.ACTION_SEND).apply {
+ type = "text/plain"
+ putExtra(Intent.EXTRA_TEXT, "Join my call!")
+ }
+ startActivity(Intent.createChooser(shareIntent, "Share"))
+ }
+
+ private fun showLayoutOptionsDialog() {
+ val layouts = arrayOf("Tile", "Spotlight")
+ AlertDialog.Builder(this)
+ .setTitle("Select Layout")
+ .setItems(layouts) { _, which ->
+ val layoutType = if (which == 0) LayoutType.TILE else LayoutType.SPOTLIGHT
+ callSession.setLayout(layoutType)
+ }
+ .show()
+ }
+
+ private fun openChatScreen() {
+ // Open chat UI
+ }
+
+ private fun showRecordingConsentDialog() {
+ AlertDialog.Builder(this)
+ .setTitle("Start Recording")
+ .setMessage("All participants will be notified.")
+ .setPositiveButton("Start") { _, _ ->
+ callSession.startRecording()
+ }
+ .setNegativeButton("Cancel", null)
+ .show()
+ }
+
+ companion object {
+ private const val TAG = "CallActivity"
+ }
+}
+```
+
+
+```java
+public class CallActivity extends AppCompatActivity {
+ private static final String TAG = "CallActivity";
+ private CallSession callSession;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_call);
+
+ callSession = CallSession.getInstance();
+ setupButtonClickListener();
+ }
+
+ private void setupButtonClickListener() {
+ callSession.addButtonClickListener(this, new ButtonClickListener() {
+ @Override
+ public void onLeaveSessionButtonClicked() {
+ showLeaveConfirmationDialog();
+ }
+
+ @Override
+ public void onToggleAudioButtonClicked() {
+ Log.d(TAG, "Audio toggle clicked");
+ }
+
+ @Override
+ public void onToggleVideoButtonClicked() {
+ Log.d(TAG, "Video toggle clicked");
+ }
+
+ @Override
+ public void onSwitchCameraButtonClicked() {
+ Log.d(TAG, "Switch camera clicked");
+ }
+
+ @Override
+ public void onRaiseHandButtonClicked() {
+ Toast.makeText(
+ CallActivity.this,
+ "Hand raised",
+ Toast.LENGTH_SHORT
+ ).show();
+ }
+
+ @Override
+ public void onShareInviteButtonClicked() {
+ shareCallLink();
+ }
+
+ @Override
+ public void onChangeLayoutButtonClicked() {
+ showLayoutOptionsDialog();
+ }
+
+ @Override
+ public void onParticipantListButtonClicked() {
+ Log.d(TAG, "Participant list opened");
+ }
+
+ @Override
+ public void onChatButtonClicked() {
+ openChatScreen();
+ }
+
+ @Override
+ public void onRecordingToggleButtonClicked() {
+ showRecordingConsentDialog();
+ }
+ });
+ }
+
+ private void showLeaveConfirmationDialog() {
+ new AlertDialog.Builder(this)
+ .setTitle("Leave Call")
+ .setMessage("Are you sure you want to leave?")
+ .setPositiveButton("Leave", (dialog, which) -> {
+ callSession.leaveSession();
+ })
+ .setNegativeButton("Cancel", null)
+ .show();
+ }
+
+ private void shareCallLink() {
+ Intent shareIntent = new Intent(Intent.ACTION_SEND);
+ shareIntent.setType("text/plain");
+ shareIntent.putExtra(Intent.EXTRA_TEXT, "Join my call!");
+ startActivity(Intent.createChooser(shareIntent, "Share"));
+ }
+
+ private void showLayoutOptionsDialog() {
+ String[] layouts = {"Tile", "Spotlight"};
+ new AlertDialog.Builder(this)
+ .setTitle("Select Layout")
+ .setItems(layouts, (dialog, which) -> {
+ LayoutType layoutType = (which == 0) ? LayoutType.TILE : LayoutType.SPOTLIGHT;
+ callSession.setLayout(layoutType);
+ })
+ .show();
+ }
+
+ private void openChatScreen() {
+ // Open chat UI
+ }
+
+ private void showRecordingConsentDialog() {
+ new AlertDialog.Builder(this)
+ .setTitle("Start Recording")
+ .setMessage("All participants will be notified.")
+ .setPositiveButton("Start", (dialog, which) -> {
+ callSession.startRecording();
+ })
+ .setNegativeButton("Cancel", null)
+ .show();
+ }
+}
+```
+
+
+
+---
+
+## Callbacks Summary
+
+| Callback | Description |
+|----------|-------------|
+| `onLeaveSessionButtonClicked` | Leave session button was tapped |
+| `onToggleAudioButtonClicked` | Audio mute/unmute button was tapped |
+| `onToggleVideoButtonClicked` | Video on/off button was tapped |
+| `onSwitchCameraButtonClicked` | Switch camera button was tapped |
+| `onRaiseHandButtonClicked` | Raise hand button was tapped |
+| `onShareInviteButtonClicked` | Share invite button was tapped |
+| `onChangeLayoutButtonClicked` | Change layout button was tapped |
+| `onParticipantListButtonClicked` | Participant list button was tapped |
+| `onChatButtonClicked` | Chat button was tapped |
+| `onRecordingToggleButtonClicked` | Recording toggle button was tapped |
+
+## Hide Buttons
+
+You can hide specific buttons using [SessionSettings](/calls/android/session-settings):
+
+
+
+```kotlin
+val sessionSettings = CometChatCalls.SessionSettingsBuilder()
+ .hideLeaveSessionButton(false)
+ .hideToggleAudioButton(false)
+ .hideToggleVideoButton(false)
+ .hideSwitchCameraButton(false)
+ .hideRaiseHandButton(false)
+ .hideShareInviteButton(true)
+ .hideChangeLayoutButton(false)
+ .hideParticipantListButton(false)
+ .hideChatButton(true)
+ .hideRecordingButton(true)
+ .build()
+```
+
+
+```java
+SessionSettings sessionSettings = new CometChatCalls.SessionSettingsBuilder()
+ .hideLeaveSessionButton(false)
+ .hideToggleAudioButton(false)
+ .hideToggleVideoButton(false)
+ .hideSwitchCameraButton(false)
+ .hideRaiseHandButton(false)
+ .hideShareInviteButton(true)
+ .hideChangeLayoutButton(false)
+ .hideParticipantListButton(false)
+ .hideChatButton(true)
+ .hideRecordingButton(true)
+ .build();
+```
+
+
+
+## Next Steps
+
+
+
+ Handle layout change events
+
+
+ Configure button visibility
+
+
diff --git a/calls/android/call-layouts.mdx b/calls/android/call-layouts.mdx
new file mode 100644
index 00000000..49d96683
--- /dev/null
+++ b/calls/android/call-layouts.mdx
@@ -0,0 +1,181 @@
+---
+title: "Call Layouts"
+sidebarTitle: "Call Layouts"
+---
+
+Choose how participants are displayed during a call. The SDK provides multiple layout options to suit different use cases like team meetings, presentations, or one-on-one calls.
+
+## Available Layouts
+
+| Layout | Description | Best For |
+|--------|-------------|----------|
+| `TILE` | Grid layout with equally-sized tiles for all participants | Team meetings, group discussions |
+| `SPOTLIGHT` | Large view for the other participant, small tile for yourself | One-on-one calls, presentations, webinars |
+| `SIDEBAR` | Main speaker with participants in a sidebar | Interviews, panel discussions |
+
+## Set Initial Layout
+
+Configure the layout when joining a session using `SessionSettingsBuilder`:
+
+
+
+```kotlin
+val sessionSettings = CometChatCalls.SessionSettingsBuilder()
+ .setLayout(LayoutType.TILE)
+ .build()
+
+CometChatCalls.joinSession(sessionId, sessionSettings, callViewContainer,
+ object : CometChatCalls.CallbackListener() {
+ override fun onSuccess(callSession: CallSession) {
+ Log.d(TAG, "Joined with TILE layout")
+ }
+
+ override fun onError(e: CometChatException) {
+ Log.e(TAG, "Failed: ${e.message}")
+ }
+ }
+)
+```
+
+
+```java
+SessionSettings sessionSettings = new CometChatCalls.SessionSettingsBuilder()
+ .setLayout(LayoutType.TILE)
+ .build();
+
+CometChatCalls.joinSession(sessionId, sessionSettings, callViewContainer,
+ new CometChatCalls.CallbackListener() {
+ @Override
+ public void onSuccess(CallSession callSession) {
+ Log.d(TAG, "Joined with TILE layout");
+ }
+
+ @Override
+ public void onError(CometChatException e) {
+ Log.e(TAG, "Failed: " + e.getMessage());
+ }
+ }
+);
+```
+
+
+
+## Change Layout During Call
+
+Switch layouts dynamically during an active call using `setLayout()`:
+
+
+
+```kotlin
+val callSession = CallSession.getInstance()
+
+// Switch to Spotlight layout
+callSession.setLayout(LayoutType.SPOTLIGHT)
+
+// Switch to Tile layout
+callSession.setLayout(LayoutType.TILE)
+
+// Switch to Sidebar layout
+callSession.setLayout(LayoutType.SIDEBAR)
+```
+
+
+```java
+CallSession callSession = CallSession.getInstance();
+
+// Switch to Spotlight layout
+callSession.setLayout(LayoutType.SPOTLIGHT);
+
+// Switch to Tile layout
+callSession.setLayout(LayoutType.TILE);
+
+// Switch to Sidebar layout
+callSession.setLayout(LayoutType.SIDEBAR);
+```
+
+
+
+
+Each participant can choose their own layout independently. Changing your layout does not affect other participants.
+
+
+## Listen for Layout Changes
+
+Monitor layout changes using `LayoutListener`:
+
+
+
+```kotlin
+val callSession = CallSession.getInstance()
+
+callSession.addLayoutListener(this, object : LayoutListener() {
+ override fun onCallLayoutChanged(layoutType: LayoutType) {
+ when (layoutType) {
+ LayoutType.TILE -> Log.d(TAG, "Switched to Tile layout")
+ LayoutType.SPOTLIGHT -> Log.d(TAG, "Switched to Spotlight layout")
+ LayoutType.SIDEBAR -> Log.d(TAG, "Switched to Sidebar layout")
+ }
+ // Update layout toggle button icon
+ updateLayoutIcon(layoutType)
+ }
+
+ override fun onParticipantListVisible() {}
+ override fun onParticipantListHidden() {}
+ override fun onPictureInPictureLayoutEnabled() {}
+ override fun onPictureInPictureLayoutDisabled() {}
+})
+```
+
+
+```java
+CallSession callSession = CallSession.getInstance();
+
+callSession.addLayoutListener(this, new LayoutListener() {
+ @Override
+ public void onCallLayoutChanged(LayoutType layoutType) {
+ switch (layoutType) {
+ case TILE:
+ Log.d(TAG, "Switched to Tile layout");
+ break;
+ case SPOTLIGHT:
+ Log.d(TAG, "Switched to Spotlight layout");
+ break;
+ case SIDEBAR:
+ Log.d(TAG, "Switched to Sidebar layout");
+ break;
+ }
+ // Update layout toggle button icon
+ updateLayoutIcon(layoutType);
+ }
+
+ @Override public void onParticipantListVisible() {}
+ @Override public void onParticipantListHidden() {}
+ @Override public void onPictureInPictureLayoutEnabled() {}
+ @Override public void onPictureInPictureLayoutDisabled() {}
+});
+```
+
+
+
+## Hide Layout Toggle Button
+
+To prevent users from changing the layout, hide the layout toggle button in the call UI:
+
+
+
+```kotlin
+val sessionSettings = CometChatCalls.SessionSettingsBuilder()
+ .setLayout(LayoutType.SPOTLIGHT) // Fixed layout
+ .hideChangeLayoutButton(true) // Hide toggle button
+ .build()
+```
+
+
+```java
+SessionSettings sessionSettings = new CometChatCalls.SessionSettingsBuilder()
+ .setLayout(LayoutType.SPOTLIGHT) // Fixed layout
+ .hideChangeLayoutButton(true) // Hide toggle button
+ .build();
+```
+
+
diff --git a/calls/android/call-logs.mdx b/calls/android/call-logs.mdx
new file mode 100644
index 00000000..de1b1401
--- /dev/null
+++ b/calls/android/call-logs.mdx
@@ -0,0 +1,290 @@
+---
+title: "Call Logs"
+sidebarTitle: "Call Logs"
+---
+
+Retrieve call history for your application. Call logs provide detailed information about past calls including duration, participants, recordings, and status.
+
+## Fetch Call Logs
+
+Use `CallLogRequest` to fetch call logs with pagination support. The builder pattern allows you to filter results by various criteria.
+
+
+
+```kotlin
+val callLogRequest = CallLogRequest.CallLogRequestBuilder()
+ .setLimit(30)
+ .build()
+
+callLogRequest.fetchNext(object : CometChatCalls.CallbackListener>() {
+ override fun onSuccess(callLogs: List) {
+ for (callLog in callLogs) {
+ Log.d(TAG, "Session: ${callLog.sessionID}")
+ Log.d(TAG, "Duration: ${callLog.totalDuration}")
+ Log.d(TAG, "Status: ${callLog.status}")
+ }
+ }
+
+ override fun onError(e: CometChatException) {
+ Log.e(TAG, "Error: ${e.message}")
+ }
+})
+```
+
+
+```java
+CallLogRequest callLogRequest = new CallLogRequest.CallLogRequestBuilder()
+ .setLimit(30)
+ .build();
+
+callLogRequest.fetchNext(new CometChatCalls.CallbackListener>() {
+ @Override
+ public void onSuccess(List callLogs) {
+ for (CallLog callLog : callLogs) {
+ Log.d(TAG, "Session: " + callLog.getSessionID());
+ Log.d(TAG, "Duration: " + callLog.getTotalDuration());
+ Log.d(TAG, "Status: " + callLog.getStatus());
+ }
+ }
+
+ @Override
+ public void onError(CometChatException e) {
+ Log.e(TAG, "Error: " + e.getMessage());
+ }
+});
+```
+
+
+
+## CallLogRequestBuilder
+
+Configure the request using the builder methods:
+
+| Method | Type | Description |
+|--------|------|-------------|
+| `setLimit(int)` | int | Number of call logs to fetch per request (default: 30, max: 100) |
+| `setSessionType(String)` | String | Filter by call type: `video` or `audio` |
+| `setCallStatus(String)` | String | Filter by call status |
+| `setHasRecording(boolean)` | boolean | Filter calls that have recordings |
+| `setCallCategory(String)` | String | Filter by category: `call` or `meet` |
+| `setCallDirection(String)` | String | Filter by direction: `incoming` or `outgoing` |
+| `setUid(String)` | String | Filter calls with a specific user |
+| `setGuid(String)` | String | Filter calls with a specific group |
+
+### Filter Examples
+
+
+
+```kotlin
+// Fetch only video calls
+val videoCallsRequest = CallLogRequest.CallLogRequestBuilder()
+ .setSessionType("video")
+ .setLimit(20)
+ .build()
+
+// Fetch calls with recordings
+val recordedCallsRequest = CallLogRequest.CallLogRequestBuilder()
+ .setHasRecording(true)
+ .build()
+
+// Fetch missed incoming calls
+val missedCallsRequest = CallLogRequest.CallLogRequestBuilder()
+ .setCallStatus("missed")
+ .setCallDirection("incoming")
+ .build()
+
+// Fetch calls with a specific user
+val userCallsRequest = CallLogRequest.CallLogRequestBuilder()
+ .setUid("user_id")
+ .build()
+```
+
+
+```java
+// Fetch only video calls
+CallLogRequest videoCallsRequest = new CallLogRequest.CallLogRequestBuilder()
+ .setSessionType("video")
+ .setLimit(20)
+ .build();
+
+// Fetch calls with recordings
+CallLogRequest recordedCallsRequest = new CallLogRequest.CallLogRequestBuilder()
+ .setHasRecording(true)
+ .build();
+
+// Fetch missed incoming calls
+CallLogRequest missedCallsRequest = new CallLogRequest.CallLogRequestBuilder()
+ .setCallStatus("missed")
+ .setCallDirection("incoming")
+ .build();
+
+// Fetch calls with a specific user
+CallLogRequest userCallsRequest = new CallLogRequest.CallLogRequestBuilder()
+ .setUid("user_id")
+ .build();
+```
+
+
+
+## Pagination
+
+Use `fetchNext()` and `fetchPrevious()` for pagination:
+
+
+
+```kotlin
+// Fetch next page
+callLogRequest.fetchNext(object : CometChatCalls.CallbackListener>() {
+ override fun onSuccess(callLogs: List) {
+ // Handle next page
+ }
+
+ override fun onError(e: CometChatException) {
+ Log.e(TAG, "Error: ${e.message}")
+ }
+})
+
+// Fetch previous page
+callLogRequest.fetchPrevious(object : CometChatCalls.CallbackListener>() {
+ override fun onSuccess(callLogs: List) {
+ // Handle previous page
+ }
+
+ override fun onError(e: CometChatException) {
+ Log.e(TAG, "Error: ${e.message}")
+ }
+})
+```
+
+
+```java
+// Fetch next page
+callLogRequest.fetchNext(new CometChatCalls.CallbackListener>() {
+ @Override
+ public void onSuccess(List callLogs) {
+ // Handle next page
+ }
+
+ @Override
+ public void onError(CometChatException e) {
+ Log.e(TAG, "Error: " + e.getMessage());
+ }
+});
+
+// Fetch previous page
+callLogRequest.fetchPrevious(new CometChatCalls.CallbackListener>() {
+ @Override
+ public void onSuccess(List callLogs) {
+ // Handle previous page
+ }
+
+ @Override
+ public void onError(CometChatException e) {
+ Log.e(TAG, "Error: " + e.getMessage());
+ }
+});
+```
+
+
+
+## CallLog Object
+
+Each `CallLog` object contains detailed information about a call:
+
+| Property | Type | Description |
+|----------|------|-------------|
+| `sessionID` | String | Unique identifier for the call session |
+| `initiator` | CallEntity | User who initiated the call |
+| `receiver` | CallEntity | User or group that received the call |
+| `receiverType` | String | `user` or `group` |
+| `type` | String | Call type: `video` or `audio` |
+| `status` | String | Final status of the call |
+| `callCategory` | String | Category: `call` or `meet` |
+| `initiatedAt` | long | Timestamp when call was initiated |
+| `endedAt` | long | Timestamp when call ended |
+| `totalDuration` | String | Human-readable duration (e.g., "5:30") |
+| `totalDurationInMinutes` | double | Duration in minutes |
+| `totalAudioMinutes` | double | Audio duration in minutes |
+| `totalVideoMinutes` | double | Video duration in minutes |
+| `totalParticipants` | int | Number of participants |
+| `hasRecording` | boolean | Whether the call was recorded |
+| `recordings` | List\
| List of recording objects |
+| `participantInfoList` | List\ | List of participant details |
+
+## Access Recordings
+
+If a call has recordings, access them through the `recordings` property:
+
+
+
+```kotlin
+callLogRequest.fetchNext(object : CometChatCalls.CallbackListener>() {
+ override fun onSuccess(callLogs: List) {
+ for (callLog in callLogs) {
+ if (callLog.isHasRecording) {
+ callLog.recordings?.forEach { recording ->
+ Log.d(TAG, "Recording ID: ${recording.rid}")
+ Log.d(TAG, "Recording URL: ${recording.recordingURL}")
+ Log.d(TAG, "Duration: ${recording.duration} seconds")
+ }
+ }
+ }
+ }
+
+ override fun onError(e: CometChatException) {
+ Log.e(TAG, "Error: ${e.message}")
+ }
+})
+```
+
+
+```java
+callLogRequest.fetchNext(new CometChatCalls.CallbackListener>() {
+ @Override
+ public void onSuccess(List callLogs) {
+ for (CallLog callLog : callLogs) {
+ if (callLog.isHasRecording()) {
+ for (Recording recording : callLog.getRecordings()) {
+ Log.d(TAG, "Recording ID: " + recording.getRid());
+ Log.d(TAG, "Recording URL: " + recording.getRecordingURL());
+ Log.d(TAG, "Duration: " + recording.getDuration() + " seconds");
+ }
+ }
+ }
+ }
+
+ @Override
+ public void onError(CometChatException e) {
+ Log.e(TAG, "Error: " + e.getMessage());
+ }
+});
+```
+
+
+
+
+| Status | Description |
+|--------|-------------|
+| `ongoing` | Call is currently in progress |
+| `busy` | Receiver was busy |
+| `rejected` | Call was rejected |
+| `cancelled` | Call was cancelled by initiator |
+| `ended` | Call ended normally |
+| `missed` | Call was missed |
+| `initiated` | Call was initiated but not answered |
+| `unanswered` | Call was not answered |
+
+
+
+| Category | Description |
+|----------|-------------|
+| `call` | Direct call between users |
+| `meet` | Meeting/conference call |
+
+
+
+| Direction | Description |
+|-----------|-------------|
+| `incoming` | Call received by the user |
+| `outgoing` | Call initiated by the user |
+
diff --git a/calls/android/custom-control-panel.mdx b/calls/android/custom-control-panel.mdx
new file mode 100644
index 00000000..bdbc54ab
--- /dev/null
+++ b/calls/android/custom-control-panel.mdx
@@ -0,0 +1,660 @@
+---
+title: "Custom Control Panel"
+sidebarTitle: "Custom Control Panel"
+---
+
+Build a fully customized control panel for your call interface by hiding the default controls and implementing your own UI with call actions. This guide walks you through creating a custom control panel with essential call controls.
+
+## Overview
+
+Custom control panels allow you to:
+- Match your app's branding and design language
+- Simplify the interface by showing only relevant controls
+- Add custom functionality and workflows
+- Create unique user experiences
+
+This guide demonstrates building a basic custom control panel with:
+- Mute/Unmute audio button
+- Pause/Resume video button
+- Switch camera button
+- End call button
+
+## Prerequisites
+
+- CometChat Calls SDK installed and initialized
+- Active call session (see [Join Session](/calls/android/join-session))
+- Familiarity with [Actions](/calls/android/actions) and [Events](/calls/android/events)
+
+---
+
+## Step 1: Hide Default Controls
+
+Configure your session settings to hide the default control panel:
+
+
+
+```kotlin
+val sessionSettings = CometChatCalls.SessionSettingsBuilder()
+ .hideControlPanel(true)
+ .build()
+```
+
+
+```java
+SessionSettings sessionSettings = new CometChatCalls.SessionSettingsBuilder()
+ .hideControlPanel(true)
+ .build();
+```
+
+
+
+
+You can also hide individual buttons while keeping the control panel visible. See [SessionSettingsBuilder](/calls/android/session-settings) for all options.
+
+
+---
+
+## Step 2: Create Custom Layout
+
+Create an XML layout for your custom controls:
+
+
+
+```xml
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+
+
+**control_button_background.xml:**
+```xml
+
+
+
+
+```
+
+**end_call_button_background.xml:**
+```xml
+
+
+
+
+```
+
+
+---
+
+## Step 3: Implement Control Actions
+
+Set up button click listeners and call the appropriate actions:
+
+
+
+```kotlin
+class CallActivity : AppCompatActivity() {
+
+ private lateinit var callSession: CallSession
+ private var isAudioMuted = false
+ private var isVideoPaused = false
+
+ private lateinit var btnToggleAudio: ImageButton
+ private lateinit var btnToggleVideo: ImageButton
+ private lateinit var btnSwitchCamera: ImageButton
+ private lateinit var btnEndCall: ImageButton
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_call)
+
+ callSession = CallSession.getInstance()
+
+ btnToggleAudio = findViewById(R.id.btnToggleAudio)
+ btnToggleVideo = findViewById(R.id.btnToggleVideo)
+ btnSwitchCamera = findViewById(R.id.btnSwitchCamera)
+ btnEndCall = findViewById(R.id.btnEndCall)
+
+ setupControlListeners()
+ }
+
+ private fun setupControlListeners() {
+ btnToggleAudio.setOnClickListener {
+ if (isAudioMuted) {
+ callSession.unMuteAudio()
+ } else {
+ callSession.muteAudio()
+ }
+ }
+
+ btnToggleVideo.setOnClickListener {
+ if (isVideoPaused) {
+ callSession.resumeVideo()
+ } else {
+ callSession.pauseVideo()
+ }
+ }
+
+ btnSwitchCamera.setOnClickListener {
+ callSession.switchCamera()
+ }
+
+ btnEndCall.setOnClickListener {
+ callSession.leaveSession()
+ finish()
+ }
+ }
+}
+```
+
+
+```java
+public class CallActivity extends AppCompatActivity {
+
+ private CallSession callSession;
+ private boolean isAudioMuted = false;
+ private boolean isVideoPaused = false;
+
+ private ImageButton btnToggleAudio;
+ private ImageButton btnToggleVideo;
+ private ImageButton btnSwitchCamera;
+ private ImageButton btnEndCall;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_call);
+
+ callSession = CallSession.getInstance();
+
+ btnToggleAudio = findViewById(R.id.btnToggleAudio);
+ btnToggleVideo = findViewById(R.id.btnToggleVideo);
+ btnSwitchCamera = findViewById(R.id.btnSwitchCamera);
+ btnEndCall = findViewById(R.id.btnEndCall);
+
+ setupControlListeners();
+ }
+
+ private void setupControlListeners() {
+ btnToggleAudio.setOnClickListener(v -> {
+ if (isAudioMuted) {
+ callSession.unMuteAudio();
+ } else {
+ callSession.muteAudio();
+ }
+ });
+
+ btnToggleVideo.setOnClickListener(v -> {
+ if (isVideoPaused) {
+ callSession.resumeVideo();
+ } else {
+ callSession.pauseVideo();
+ }
+ });
+
+ btnSwitchCamera.setOnClickListener(v -> callSession.switchCamera());
+
+ btnEndCall.setOnClickListener(v -> {
+ callSession.leaveSession();
+ finish();
+ });
+ }
+}
+```
+
+
+
+
+---
+
+## Step 4: Handle State Updates
+
+Use `MediaEventsListener` to keep your UI synchronized with the actual call state. The listener is lifecycle-aware and automatically removed when the Activity is destroyed.
+
+
+
+```kotlin
+private fun setupMediaEventsListener() {
+ callSession.addMediaEventsListener(this, object : MediaEventsListener() {
+ override fun onAudioMuted() {
+ runOnUiThread {
+ isAudioMuted = true
+ btnToggleAudio.setImageResource(R.drawable.ic_mic_off)
+ }
+ }
+
+ override fun onAudioUnMuted() {
+ runOnUiThread {
+ isAudioMuted = false
+ btnToggleAudio.setImageResource(R.drawable.ic_mic_on)
+ }
+ }
+
+ override fun onVideoPaused() {
+ runOnUiThread {
+ isVideoPaused = true
+ btnToggleVideo.setImageResource(R.drawable.ic_video_off)
+ }
+ }
+
+ override fun onVideoResumed() {
+ runOnUiThread {
+ isVideoPaused = false
+ btnToggleVideo.setImageResource(R.drawable.ic_video_on)
+ }
+ }
+ })
+}
+```
+
+
+```java
+private void setupMediaEventsListener() {
+ callSession.addMediaEventsListener(this, new MediaEventsListener() {
+ @Override
+ public void onAudioMuted() {
+ runOnUiThread(() -> {
+ isAudioMuted = true;
+ btnToggleAudio.setImageResource(R.drawable.ic_mic_off);
+ });
+ }
+
+ @Override
+ public void onAudioUnMuted() {
+ runOnUiThread(() -> {
+ isAudioMuted = false;
+ btnToggleAudio.setImageResource(R.drawable.ic_mic_on);
+ });
+ }
+
+ @Override
+ public void onVideoPaused() {
+ runOnUiThread(() -> {
+ isVideoPaused = true;
+ btnToggleVideo.setImageResource(R.drawable.ic_video_off);
+ });
+ }
+
+ @Override
+ public void onVideoResumed() {
+ runOnUiThread(() -> {
+ isVideoPaused = false;
+ btnToggleVideo.setImageResource(R.drawable.ic_video_on);
+ });
+ }
+ });
+}
+```
+
+
+
+Use `SessionStatusListener` to handle session end events:
+
+
+
+```kotlin
+private fun setupSessionStatusListener() {
+ callSession.addSessionStatusListener(this, object : SessionStatusListener() {
+ override fun onSessionLeft() {
+ runOnUiThread { finish() }
+ }
+
+ override fun onConnectionClosed() {
+ runOnUiThread { finish() }
+ }
+ })
+}
+```
+
+
+```java
+private void setupSessionStatusListener() {
+ callSession.addSessionStatusListener(this, new SessionStatusListener() {
+ @Override
+ public void onSessionLeft() {
+ runOnUiThread(() -> finish());
+ }
+
+ @Override
+ public void onConnectionClosed() {
+ runOnUiThread(() -> finish());
+ }
+ });
+}
+```
+
+
+
+---
+
+## Complete Example
+
+Here's the full implementation combining all steps:
+
+
+
+```kotlin
+class CallActivity : AppCompatActivity() {
+
+ private lateinit var callSession: CallSession
+ private var isAudioMuted = false
+ private var isVideoPaused = false
+
+ private lateinit var btnToggleAudio: ImageButton
+ private lateinit var btnToggleVideo: ImageButton
+ private lateinit var btnSwitchCamera: ImageButton
+ private lateinit var btnEndCall: ImageButton
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_call)
+
+ callSession = CallSession.getInstance()
+
+ btnToggleAudio = findViewById(R.id.btnToggleAudio)
+ btnToggleVideo = findViewById(R.id.btnToggleVideo)
+ btnSwitchCamera = findViewById(R.id.btnSwitchCamera)
+ btnEndCall = findViewById(R.id.btnEndCall)
+
+ setupControlListeners()
+ setupMediaEventsListener()
+ setupSessionStatusListener()
+ joinCall()
+ }
+
+ private fun joinCall() {
+ val sessionSettings = CometChatCalls.SessionSettingsBuilder()
+ .setDisplayName("John Doe")
+ .setType(SessionType.VIDEO)
+ .hideControlPanel(true)
+ .build()
+
+ val callContainer = findViewById(R.id.callContainer)
+
+ CometChatCalls.joinSession(
+ sessionId = "SESSION_ID",
+ sessionSettings = sessionSettings,
+ view = callContainer,
+ context = this,
+ listener = object : CometChatCalls.CallbackListener() {
+ override fun onSuccess(p0: Void?) {
+ Log.d(TAG, "Joined call successfully")
+ }
+
+ override fun onError(exception: CometChatException) {
+ Log.e(TAG, "Failed to join: ${exception.message}")
+ finish()
+ }
+ }
+ )
+ }
+
+ private fun setupControlListeners() {
+ btnToggleAudio.setOnClickListener {
+ if (isAudioMuted) callSession.unMuteAudio()
+ else callSession.muteAudio()
+ }
+
+ btnToggleVideo.setOnClickListener {
+ if (isVideoPaused) callSession.resumeVideo()
+ else callSession.pauseVideo()
+ }
+
+ btnSwitchCamera.setOnClickListener {
+ callSession.switchCamera()
+ }
+
+ btnEndCall.setOnClickListener {
+ callSession.leaveSession()
+ finish()
+ }
+ }
+
+ private fun setupMediaEventsListener() {
+ callSession.addMediaEventsListener(this, object : MediaEventsListener() {
+ override fun onAudioMuted() {
+ runOnUiThread {
+ isAudioMuted = true
+ btnToggleAudio.setImageResource(R.drawable.ic_mic_off)
+ }
+ }
+
+ override fun onAudioUnMuted() {
+ runOnUiThread {
+ isAudioMuted = false
+ btnToggleAudio.setImageResource(R.drawable.ic_mic_on)
+ }
+ }
+
+ override fun onVideoPaused() {
+ runOnUiThread {
+ isVideoPaused = true
+ btnToggleVideo.setImageResource(R.drawable.ic_video_off)
+ }
+ }
+
+ override fun onVideoResumed() {
+ runOnUiThread {
+ isVideoPaused = false
+ btnToggleVideo.setImageResource(R.drawable.ic_video_on)
+ }
+ }
+ })
+ }
+
+ private fun setupSessionStatusListener() {
+ callSession.addSessionStatusListener(this, object : SessionStatusListener() {
+ override fun onSessionLeft() {
+ runOnUiThread { finish() }
+ }
+
+ override fun onConnectionClosed() {
+ runOnUiThread { finish() }
+ }
+ })
+ }
+
+ companion object {
+ private const val TAG = "CallActivity"
+ }
+}
+```
+
+
+```java
+public class CallActivity extends AppCompatActivity {
+
+ private static final String TAG = "CallActivity";
+
+ private CallSession callSession;
+ private boolean isAudioMuted = false;
+ private boolean isVideoPaused = false;
+
+ private ImageButton btnToggleAudio;
+ private ImageButton btnToggleVideo;
+ private ImageButton btnSwitchCamera;
+ private ImageButton btnEndCall;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_call);
+
+ callSession = CallSession.getInstance();
+
+ btnToggleAudio = findViewById(R.id.btnToggleAudio);
+ btnToggleVideo = findViewById(R.id.btnToggleVideo);
+ btnSwitchCamera = findViewById(R.id.btnSwitchCamera);
+ btnEndCall = findViewById(R.id.btnEndCall);
+
+ setupControlListeners();
+ setupMediaEventsListener();
+ setupSessionStatusListener();
+ joinCall();
+ }
+
+ private void joinCall() {
+ SessionSettings sessionSettings = new CometChatCalls.SessionSettingsBuilder()
+ .setDisplayName("John Doe")
+ .setType(SessionType.VIDEO)
+ .hideControlPanel(true)
+ .build();
+
+ FrameLayout callContainer = findViewById(R.id.callContainer);
+
+ CometChatCalls.joinSession(
+ "SESSION_ID",
+ sessionSettings,
+ callContainer,
+ this,
+ new CometChatCalls.CallbackListener() {
+ @Override
+ public void onSuccess(Void unused) {
+ Log.d(TAG, "Joined call successfully");
+ }
+
+ @Override
+ public void onError(CometChatException e) {
+ Log.e(TAG, "Failed to join: " + e.getMessage());
+ finish();
+ }
+ }
+ );
+ }
+
+ private void setupControlListeners() {
+ btnToggleAudio.setOnClickListener(v -> {
+ if (isAudioMuted) callSession.unMuteAudio();
+ else callSession.muteAudio();
+ });
+
+ btnToggleVideo.setOnClickListener(v -> {
+ if (isVideoPaused) callSession.resumeVideo();
+ else callSession.pauseVideo();
+ });
+
+ btnSwitchCamera.setOnClickListener(v -> callSession.switchCamera());
+
+ btnEndCall.setOnClickListener(v -> {
+ callSession.leaveSession();
+ finish();
+ });
+ }
+
+ private void setupMediaEventsListener() {
+ callSession.addMediaEventsListener(this, new MediaEventsListener() {
+ @Override
+ public void onAudioMuted() {
+ runOnUiThread(() -> {
+ isAudioMuted = true;
+ btnToggleAudio.setImageResource(R.drawable.ic_mic_off);
+ });
+ }
+
+ @Override
+ public void onAudioUnMuted() {
+ runOnUiThread(() -> {
+ isAudioMuted = false;
+ btnToggleAudio.setImageResource(R.drawable.ic_mic_on);
+ });
+ }
+
+ @Override
+ public void onVideoPaused() {
+ runOnUiThread(() -> {
+ isVideoPaused = true;
+ btnToggleVideo.setImageResource(R.drawable.ic_video_off);
+ });
+ }
+
+ @Override
+ public void onVideoResumed() {
+ runOnUiThread(() -> {
+ isVideoPaused = false;
+ btnToggleVideo.setImageResource(R.drawable.ic_video_on);
+ });
+ }
+ });
+ }
+
+ private void setupSessionStatusListener() {
+ callSession.addSessionStatusListener(this, new SessionStatusListener() {
+ @Override
+ public void onSessionLeft() {
+ runOnUiThread(() -> finish());
+ }
+
+ @Override
+ public void onConnectionClosed() {
+ runOnUiThread(() -> finish());
+ }
+ });
+ }
+}
+```
+
+
diff --git a/calls/android/custom-participant-list.mdx b/calls/android/custom-participant-list.mdx
new file mode 100644
index 00000000..6c793987
--- /dev/null
+++ b/calls/android/custom-participant-list.mdx
@@ -0,0 +1,928 @@
+---
+title: "Custom Participant List"
+sidebarTitle: "Custom Participant List"
+---
+
+Build a custom participant list UI that displays real-time participant information with full control over layout and interactions. This guide demonstrates how to hide the default participant list and create your own using participant events and actions.
+
+## Overview
+
+The SDK provides participant data through events, allowing you to build custom UIs for:
+- Participant roster with search and filtering
+- Custom participant cards with role badges or metadata
+- Moderation dashboards with quick access to controls
+- Attendance tracking and engagement monitoring
+
+## Prerequisites
+
+- CometChat Calls SDK installed and initialized
+- Active call session (see [Join Session](/calls/android/join-session))
+- Basic understanding of RecyclerView and adapters
+
+---
+
+## Step 1: Hide Default Participant List
+
+Configure session settings to hide the default participant list button:
+
+
+
+```kotlin
+val sessionSettings = CometChatCalls.SessionSettingsBuilder()
+ .hideParticipantListButton(true)
+ .build()
+```
+
+
+```java
+SessionSettings sessionSettings = new CometChatCalls.SessionSettingsBuilder()
+ .hideParticipantListButton(true)
+ .build();
+```
+
+
+
+
+---
+
+## Step 2: Create Participant List Layout
+
+Create a layout with RecyclerView for displaying participants:
+
+
+```xml
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+
+
+```xml
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+
+
+---
+
+## Step 3: Create Participant Adapter
+
+Build a RecyclerView adapter to display participant data:
+
+
+
+```kotlin
+class ParticipantAdapter(
+ private val onMuteClick: (Participant) -> Unit,
+ private val onPauseVideoClick: (Participant) -> Unit,
+ private val onPinClick: (Participant) -> Unit
+) : RecyclerView.Adapter() {
+
+ private var participants = listOf()
+ private var filteredParticipants = listOf()
+
+ class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
+ val avatar: ImageView = view.findViewById(R.id.participantAvatar)
+ val name: TextView = view.findViewById(R.id.participantName)
+ val status: TextView = view.findViewById(R.id.statusIndicator)
+ val muteButton: ImageButton = view.findViewById(R.id.muteButton)
+ val videoPauseButton: ImageButton = view.findViewById(R.id.videoPauseButton)
+ val pinButton: ImageButton = view.findViewById(R.id.pinButton)
+ }
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
+ val view = LayoutInflater.from(parent.context)
+ .inflate(R.layout.item_participant, parent, false)
+ return ViewHolder(view)
+ }
+
+ override fun onBindViewHolder(holder: ViewHolder, position: Int) {
+ val participant = filteredParticipants[position]
+
+ // Set name
+ holder.name.text = participant.name
+
+ // Load avatar (use your image loading library)
+ // Glide.with(holder.avatar).load(participant.avatar).into(holder.avatar)
+
+ // Build status text
+ val statusParts = mutableListOf()
+ if (participant.isAudioMuted) statusParts.add("🔇 Muted")
+ if (participant.isVideoPaused) statusParts.add("📹 Video Off")
+ if (participant.isPresenting) statusParts.add("🖥️ Presenting")
+ if (participant.raisedHandTimestamp > 0) statusParts.add("✋ Hand Raised")
+ if (participant.isPinned) statusParts.add("📌 Pinned")
+
+ holder.status.text = if (statusParts.isEmpty()) "Active" else statusParts.joinToString(" • ")
+
+ // Action buttons
+ holder.muteButton.setOnClickListener { onMuteClick(participant) }
+ holder.videoPauseButton.setOnClickListener { onPauseVideoClick(participant) }
+ holder.pinButton.setOnClickListener { onPinClick(participant) }
+
+ // Update button states
+ holder.muteButton.alpha = if (participant.isAudioMuted) 0.5f else 1.0f
+ holder.videoPauseButton.alpha = if (participant.isVideoPaused) 0.5f else 1.0f
+ holder.pinButton.alpha = if (participant.isPinned) 1.0f else 0.5f
+ }
+
+ override fun getItemCount() = filteredParticipants.size
+
+ fun updateParticipants(newParticipants: List) {
+ participants = newParticipants
+ filteredParticipants = newParticipants
+ notifyDataSetChanged()
+ }
+
+ fun filter(query: String) {
+ filteredParticipants = if (query.isEmpty()) {
+ participants
+ } else {
+ participants.filter {
+ it.name.contains(query, ignoreCase = true)
+ }
+ }
+ notifyDataSetChanged()
+ }
+}
+```
+
+
+```java
+public class ParticipantAdapter extends RecyclerView.Adapter {
+
+ private List participants = new ArrayList<>();
+ private List filteredParticipants = new ArrayList<>();
+ private final OnActionListener listener;
+
+ public interface OnActionListener {
+ void onMuteClick(Participant participant);
+ void onPauseVideoClick(Participant participant);
+ void onPinClick(Participant participant);
+ }
+
+ public ParticipantAdapter(OnActionListener listener) {
+ this.listener = listener;
+ }
+
+ static class ViewHolder extends RecyclerView.ViewHolder {
+ ImageView avatar;
+ TextView name;
+ TextView status;
+ ImageButton muteButton;
+ ImageButton videoPauseButton;
+ ImageButton pinButton;
+
+ ViewHolder(View view) {
+ super(view);
+ avatar = view.findViewById(R.id.participantAvatar);
+ name = view.findViewById(R.id.participantName);
+ status = view.findViewById(R.id.statusIndicator);
+ muteButton = view.findViewById(R.id.muteButton);
+ videoPauseButton = view.findViewById(R.id.videoPauseButton);
+ pinButton = view.findViewById(R.id.pinButton);
+ }
+ }
+
+ @Override
+ public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+ View view = LayoutInflater.from(parent.getContext())
+ .inflate(R.layout.item_participant, parent, false);
+ return new ViewHolder(view);
+ }
+
+ @Override
+ public void onBindViewHolder(ViewHolder holder, int position) {
+ Participant participant = filteredParticipants.get(position);
+
+ // Set name
+ holder.name.setText(participant.getName());
+
+ // Load avatar (use your image loading library)
+ // Glide.with(holder.avatar).load(participant.getAvatar()).into(holder.avatar);
+
+ // Build status text
+ List statusParts = new ArrayList<>();
+ if (participant.isAudioMuted()) statusParts.add("🔇 Muted");
+ if (participant.isVideoPaused()) statusParts.add("📹 Video Off");
+ if (participant.isPresenting()) statusParts.add("🖥️ Presenting");
+ if (participant.getRaisedHandTimestamp() > 0) statusParts.add("✋ Hand Raised");
+ if (participant.isPinned()) statusParts.add("📌 Pinned");
+
+ holder.status.setText(statusParts.isEmpty() ? "Active" : String.join(" • ", statusParts));
+
+ // Action buttons
+ holder.muteButton.setOnClickListener(v -> listener.onMuteClick(participant));
+ holder.videoPauseButton.setOnClickListener(v -> listener.onPauseVideoClick(participant));
+ holder.pinButton.setOnClickListener(v -> listener.onPinClick(participant));
+
+ // Update button states
+ holder.muteButton.setAlpha(participant.isAudioMuted() ? 0.5f : 1.0f);
+ holder.videoPauseButton.setAlpha(participant.isVideoPaused() ? 0.5f : 1.0f);
+ holder.pinButton.setAlpha(participant.isPinned() ? 1.0f : 0.5f);
+ }
+
+ @Override
+ public int getItemCount() {
+ return filteredParticipants.size();
+ }
+
+ public void updateParticipants(List newParticipants) {
+ participants = new ArrayList<>(newParticipants);
+ filteredParticipants = new ArrayList<>(newParticipants);
+ notifyDataSetChanged();
+ }
+
+ public void filter(String query) {
+ if (query.isEmpty()) {
+ filteredParticipants = new ArrayList<>(participants);
+ } else {
+ filteredParticipants = new ArrayList<>();
+ for (Participant p : participants) {
+ if (p.getName().toLowerCase().contains(query.toLowerCase())) {
+ filteredParticipants.add(p);
+ }
+ }
+ }
+ notifyDataSetChanged();
+ }
+}
+```
+
+
+
+
+---
+
+## Step 4: Implement Participant Events
+
+Listen for participant updates and handle actions in your Activity:
+
+
+
+```kotlin
+class CallActivity : AppCompatActivity() {
+
+ private lateinit var participantAdapter: ParticipantAdapter
+ private lateinit var callSession: CallSession
+ private var isParticipantPanelVisible = false
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_call)
+
+ callSession = CallSession.getInstance()
+
+ // Setup RecyclerView
+ val recyclerView = findViewById(R.id.participantRecyclerView)
+ recyclerView.layoutManager = LinearLayoutManager(this)
+
+ participantAdapter = ParticipantAdapter(
+ onMuteClick = { participant ->
+ callSession.muteParticipant(participant.uid)
+ },
+ onPauseVideoClick = { participant ->
+ callSession.pauseParticipantVideo(participant.uid)
+ },
+ onPinClick = { participant ->
+ if (participant.isPinned) {
+ callSession.unPinParticipant()
+ } else {
+ callSession.pinParticipant(participant.uid)
+ }
+ }
+ )
+ recyclerView.adapter = participantAdapter
+
+ // Setup search
+ val searchInput = findViewById(R.id.searchInput)
+ searchInput.addTextChangedListener(object : TextWatcher {
+ override fun afterTextChanged(s: Editable?) {
+ participantAdapter.filter(s.toString())
+ }
+ override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
+ override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
+ })
+
+ // Setup toggle button
+ findViewById(R.id.toggleParticipantListButton).setOnClickListener {
+ toggleParticipantPanel()
+ }
+
+ // Setup close button
+ findViewById(R.id.closeButton).setOnClickListener {
+ toggleParticipantPanel()
+ }
+
+ // Listen for participant events
+ setupParticipantListener()
+ }
+
+ private fun setupParticipantListener() {
+ callSession.addParticipantEventListener(this, object : ParticipantEventListener() {
+ override fun onParticipantListChanged(participants: List) {
+ runOnUiThread {
+ participantAdapter.updateParticipants(participants)
+ updateParticipantCount(participants.size)
+ }
+ }
+
+ override fun onParticipantJoined(participant: Participant) {
+ Log.d(TAG, "${participant.name} joined")
+ }
+
+ override fun onParticipantLeft(participant: Participant) {
+ Log.d(TAG, "${participant.name} left")
+ }
+
+ override fun onParticipantAudioMuted(participant: Participant) {
+ // Adapter will update automatically via onParticipantListChanged
+ }
+
+ override fun onParticipantAudioUnmuted(participant: Participant) {
+ // Adapter will update automatically via onParticipantListChanged
+ }
+
+ override fun onParticipantVideoPaused(participant: Participant) {
+ // Adapter will update automatically via onParticipantListChanged
+ }
+
+ override fun onParticipantVideoResumed(participant: Participant) {
+ // Adapter will update automatically via onParticipantListChanged
+ }
+
+ override fun onParticipantHandRaised(participant: Participant) {
+ // Adapter will update automatically via onParticipantListChanged
+ }
+
+ override fun onParticipantHandLowered(participant: Participant) {
+ // Adapter will update automatically via onParticipantListChanged
+ }
+ })
+ }
+
+ private fun toggleParticipantPanel() {
+ val panel = findViewById(R.id.participantPanel)
+ isParticipantPanelVisible = !isParticipantPanelVisible
+ panel.visibility = if (isParticipantPanelVisible) View.VISIBLE else View.GONE
+ }
+
+ private fun updateParticipantCount(count: Int) {
+ findViewById(R.id.participantCount).text = "Participants ($count)"
+ }
+
+ companion object {
+ private const val TAG = "CallActivity"
+ }
+}
+```
+
+
+```java
+public class CallActivity extends AppCompatActivity {
+
+ private ParticipantAdapter participantAdapter;
+ private CallSession callSession;
+ private boolean isParticipantPanelVisible = false;
+ private static final String TAG = "CallActivity";
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_call);
+
+ callSession = CallSession.getInstance();
+
+ // Setup RecyclerView
+ RecyclerView recyclerView = findViewById(R.id.participantRecyclerView);
+ recyclerView.setLayoutManager(new LinearLayoutManager(this));
+
+ participantAdapter = new ParticipantAdapter(new ParticipantAdapter.OnActionListener() {
+ @Override
+ public void onMuteClick(Participant participant) {
+ callSession.muteParticipant(participant.getUid());
+ }
+
+ @Override
+ public void onPauseVideoClick(Participant participant) {
+ callSession.pauseParticipantVideo(participant.getUid());
+ }
+
+ @Override
+ public void onPinClick(Participant participant) {
+ if (participant.isPinned()) {
+ callSession.unPinParticipant();
+ } else {
+ callSession.pinParticipant(participant.getUid());
+ }
+ }
+ });
+ recyclerView.setAdapter(participantAdapter);
+
+ // Setup search
+ EditText searchInput = findViewById(R.id.searchInput);
+ searchInput.addTextChangedListener(new TextWatcher() {
+ @Override
+ public void afterTextChanged(Editable s) {
+ participantAdapter.filter(s.toString());
+ }
+
+ @Override
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
+
+ @Override
+ public void onTextChanged(CharSequence s, int start, int before, int count) {}
+ });
+
+ // Setup toggle button
+ findViewById(R.id.toggleParticipantListButton).setOnClickListener(v -> toggleParticipantPanel());
+
+ // Setup close button
+ findViewById(R.id.closeButton).setOnClickListener(v -> toggleParticipantPanel());
+
+ // Listen for participant events
+ setupParticipantListener();
+ }
+
+ private void setupParticipantListener() {
+ callSession.addParticipantEventListener(this, new ParticipantEventListener() {
+ @Override
+ public void onParticipantListChanged(List participants) {
+ runOnUiThread(() -> {
+ participantAdapter.updateParticipants(participants);
+ updateParticipantCount(participants.size());
+ });
+ }
+
+ @Override
+ public void onParticipantJoined(Participant participant) {
+ Log.d(TAG, participant.getName() + " joined");
+ }
+
+ @Override
+ public void onParticipantLeft(Participant participant) {
+ Log.d(TAG, participant.getName() + " left");
+ }
+
+ @Override
+ public void onParticipantAudioMuted(Participant participant) {
+ // Adapter will update automatically via onParticipantListChanged
+ }
+
+ @Override
+ public void onParticipantAudioUnmuted(Participant participant) {
+ // Adapter will update automatically via onParticipantListChanged
+ }
+
+ @Override
+ public void onParticipantVideoPaused(Participant participant) {
+ // Adapter will update automatically via onParticipantListChanged
+ }
+
+ @Override
+ public void onParticipantVideoResumed(Participant participant) {
+ // Adapter will update automatically via onParticipantListChanged
+ }
+
+ @Override
+ public void onParticipantHandRaised(Participant participant) {
+ // Adapter will update automatically via onParticipantListChanged
+ }
+
+ @Override
+ public void onParticipantHandLowered(Participant participant) {
+ // Adapter will update automatically via onParticipantListChanged
+ }
+ });
+ }
+
+ private void toggleParticipantPanel() {
+ LinearLayout panel = findViewById(R.id.participantPanel);
+ isParticipantPanelVisible = !isParticipantPanelVisible;
+ panel.setVisibility(isParticipantPanelVisible ? View.VISIBLE : View.GONE);
+ }
+
+ private void updateParticipantCount(int count) {
+ TextView countView = findViewById(R.id.participantCount);
+ countView.setText("Participants (" + count + ")");
+ }
+}
+```
+
+
+
+
+---
+
+## Complete Example
+
+Here's the full implementation with all components:
+
+
+
+```kotlin
+class CallActivity : AppCompatActivity() {
+
+ private lateinit var participantAdapter: ParticipantAdapter
+ private lateinit var callSession: CallSession
+ private var isParticipantPanelVisible = false
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_call)
+
+ callSession = CallSession.getInstance()
+ setupUI()
+ setupParticipantListener()
+ joinCall()
+ }
+
+ private fun setupUI() {
+ // Setup RecyclerView
+ val recyclerView = findViewById(R.id.participantRecyclerView)
+ recyclerView.layoutManager = LinearLayoutManager(this)
+
+ participantAdapter = ParticipantAdapter(
+ onMuteClick = { callSession.muteParticipant(it.uid) },
+ onPauseVideoClick = { callSession.pauseParticipantVideo(it.uid) },
+ onPinClick = {
+ if (it.isPinned) callSession.unPinParticipant()
+ else callSession.pinParticipant(it.uid)
+ }
+ )
+ recyclerView.adapter = participantAdapter
+
+ // Setup search
+ findViewById(R.id.searchInput).addTextChangedListener(object : TextWatcher {
+ override fun afterTextChanged(s: Editable?) {
+ participantAdapter.filter(s.toString())
+ }
+ override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
+ override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
+ })
+
+ // Setup buttons
+ findViewById(R.id.toggleParticipantListButton)
+ .setOnClickListener { toggleParticipantPanel() }
+ findViewById(R.id.closeButton)
+ .setOnClickListener { toggleParticipantPanel() }
+ }
+
+ private fun setupParticipantListener() {
+ callSession.addParticipantEventListener(this, object : ParticipantEventListener() {
+ override fun onParticipantListChanged(participants: List) {
+ runOnUiThread {
+ participantAdapter.updateParticipants(participants)
+ findViewById(R.id.participantCount).text =
+ "Participants (${participants.size})"
+ }
+ }
+
+ override fun onParticipantJoined(participant: Participant) {
+ Toast.makeText(this@CallActivity,
+ "${participant.name} joined", Toast.LENGTH_SHORT).show()
+ }
+
+ override fun onParticipantLeft(participant: Participant) {
+ Toast.makeText(this@CallActivity,
+ "${participant.name} left", Toast.LENGTH_SHORT).show()
+ }
+ })
+ }
+
+ private fun joinCall() {
+ val sessionSettings = CometChatCalls.SessionSettingsBuilder()
+ .hideParticipantListButton(true)
+ .setTitle("Team Meeting")
+ .build()
+
+ val callContainer = findViewById(R.id.callContainer)
+
+ CometChatCalls.joinSession(
+ sessionId = "SESSION_ID",
+ sessionSettings = sessionSettings,
+ view = callContainer,
+ context = this,
+ listener = object : CometChatCalls.CallbackListener() {
+ override fun onSuccess(p0: Void?) {
+ Log.d(TAG, "Joined call successfully")
+ }
+
+ override fun onError(exception: CometChatException) {
+ Log.e(TAG, "Failed to join: ${exception.message}")
+ Toast.makeText(this@CallActivity,
+ "Failed to join call", Toast.LENGTH_SHORT).show()
+ }
+ }
+ )
+ }
+
+ private fun toggleParticipantPanel() {
+ val panel = findViewById(R.id.participantPanel)
+ isParticipantPanelVisible = !isParticipantPanelVisible
+ panel.visibility = if (isParticipantPanelVisible) View.VISIBLE else View.GONE
+ }
+
+ companion object {
+ private const val TAG = "CallActivity"
+ }
+}
+```
+
+
+```java
+public class CallActivity extends AppCompatActivity {
+
+ private ParticipantAdapter participantAdapter;
+ private CallSession callSession;
+ private boolean isParticipantPanelVisible = false;
+ private static final String TAG = "CallActivity";
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_call);
+
+ callSession = CallSession.getInstance();
+ setupUI();
+ setupParticipantListener();
+ joinCall();
+ }
+
+ private void setupUI() {
+ // Setup RecyclerView
+ RecyclerView recyclerView = findViewById(R.id.participantRecyclerView);
+ recyclerView.setLayoutManager(new LinearLayoutManager(this));
+
+ participantAdapter = new ParticipantAdapter(new ParticipantAdapter.OnActionListener() {
+ @Override
+ public void onMuteClick(Participant participant) {
+ callSession.muteParticipant(participant.getUid());
+ }
+
+ @Override
+ public void onPauseVideoClick(Participant participant) {
+ callSession.pauseParticipantVideo(participant.getUid());
+ }
+
+ @Override
+ public void onPinClick(Participant participant) {
+ if (participant.isPinned()) {
+ callSession.unPinParticipant();
+ } else {
+ callSession.pinParticipant(participant.getUid());
+ }
+ }
+ });
+ recyclerView.setAdapter(participantAdapter);
+
+ // Setup search
+ EditText searchInput = findViewById(R.id.searchInput);
+ searchInput.addTextChangedListener(new TextWatcher() {
+ @Override
+ public void afterTextChanged(Editable s) {
+ participantAdapter.filter(s.toString());
+ }
+
+ @Override
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
+
+ @Override
+ public void onTextChanged(CharSequence s, int start, int before, int count) {}
+ });
+
+ // Setup buttons
+ findViewById(R.id.toggleParticipantListButton).setOnClickListener(v ->
+ toggleParticipantPanel());
+ findViewById(R.id.closeButton).setOnClickListener(v ->
+ toggleParticipantPanel());
+ }
+
+ private void setupParticipantListener() {
+ callSession.addParticipantEventListener(this, new ParticipantEventListener() {
+ @Override
+ public void onParticipantListChanged(List participants) {
+ runOnUiThread(() -> {
+ participantAdapter.updateParticipants(participants);
+ TextView countView = findViewById(R.id.participantCount);
+ countView.setText("Participants (" + participants.size() + ")");
+ });
+ }
+
+ @Override
+ public void onParticipantJoined(Participant participant) {
+ Toast.makeText(CallActivity.this,
+ participant.getName() + " joined", Toast.LENGTH_SHORT).show();
+ }
+
+ @Override
+ public void onParticipantLeft(Participant participant) {
+ Toast.makeText(CallActivity.this,
+ participant.getName() + " left", Toast.LENGTH_SHORT).show();
+ }
+ });
+ }
+
+ private void joinCall() {
+ SessionSettings sessionSettings = new CometChatCalls.SessionSettingsBuilder()
+ .hideParticipantListButton(true)
+ .setTitle("Team Meeting")
+ .build();
+
+ FrameLayout callContainer = findViewById(R.id.callContainer);
+
+ CometChatCalls.joinSession(
+ "SESSION_ID",
+ sessionSettings,
+ callContainer,
+ this,
+ new CometChatCalls.CallbackListener() {
+ @Override
+ public void onSuccess(Void unused) {
+ Log.d(TAG, "Joined call successfully");
+ }
+
+ @Override
+ public void onError(CometChatException e) {
+ Log.e(TAG, "Failed to join: " + e.getMessage());
+ Toast.makeText(CallActivity.this,
+ "Failed to join call", Toast.LENGTH_SHORT).show();
+ }
+ }
+ );
+ }
+
+ private void toggleParticipantPanel() {
+ LinearLayout panel = findViewById(R.id.participantPanel);
+ isParticipantPanelVisible = !isParticipantPanelVisible;
+ panel.setVisibility(isParticipantPanelVisible ? View.VISIBLE : View.GONE);
+ }
+}
+```
+
+
+
+
+| Property | Type | Description |
+|----------|------|-------------|
+| `uid` | String | Unique identifier (CometChat user ID) |
+| `name` | String | Display name |
+| `avatar` | String | URL of avatar image |
+| `pid` | String | Participant ID for this call session |
+| `role` | String | Role in the call |
+| `audioMuted` | Boolean | Whether audio is muted |
+| `videoPaused` | Boolean | Whether video is paused |
+| `isPinned` | Boolean | Whether pinned in layout |
+| `isPresenting` | Boolean | Whether screen sharing |
+| `raisedHandTimestamp` | Long | Timestamp when hand was raised (0 if not raised) |
+
diff --git a/calls/android/events.mdx b/calls/android/events.mdx
new file mode 100644
index 00000000..d0566d50
--- /dev/null
+++ b/calls/android/events.mdx
@@ -0,0 +1,469 @@
+---
+title: "Events"
+sidebarTitle: "Events"
+---
+
+Handle call session events to build responsive UIs. The SDK provides five event listener interfaces to monitor session status, participant activities, media changes, button clicks, and layout changes. Each listener is lifecycle-aware and automatically cleaned up when the Activity or Fragment is destroyed.
+
+## Get CallSession Instance
+
+The `CallSession` is a singleton that manages the active call. All event listener registrations and session control methods are accessed through this instance.
+
+
+
+```kotlin
+val callSession = CallSession.getInstance()
+```
+
+
+```java
+CallSession callSession = CallSession.getInstance();
+```
+
+
+
+
+All event listeners are lifecycle-aware and automatically removed when the `LifecycleOwner` (Activity/Fragment) is destroyed. You don't need to manually remove listeners.
+
+
+---
+
+## Session Events
+
+Monitor the call session lifecycle including join/leave events and connection status.
+
+
+
+```kotlin
+callSession.addSessionStatusListener(this, object : SessionStatusListener() {
+ override fun onSessionJoined() {
+ // Successfully connected to the session
+ }
+
+ override fun onSessionLeft() {
+ finish() // Navigate away
+ }
+
+ override fun onSessionTimedOut() {
+ // Session ended due to inactivity
+ finish()
+ }
+
+ override fun onConnectionLost() {
+ // Network interrupted, attempting reconnection
+ }
+
+ override fun onConnectionRestored() {
+ // Connection restored after being lost
+ }
+
+ override fun onConnectionClosed() {
+ // Connection permanently closed
+ finish()
+ }
+})
+```
+
+
+```java
+callSession.addSessionStatusListener(this, new SessionStatusListener() {
+ @Override
+ public void onSessionJoined() {
+ // Successfully connected to the session
+ }
+
+ @Override
+ public void onSessionLeft() {
+ finish(); // Navigate away
+ }
+
+ @Override
+ public void onSessionTimedOut() {
+ // Session ended due to inactivity
+ finish();
+ }
+
+ @Override
+ public void onConnectionLost() {
+ // Network interrupted, attempting reconnection
+ }
+
+ @Override
+ public void onConnectionRestored() {
+ // Connection restored after being lost
+ }
+
+ @Override
+ public void onConnectionClosed() {
+ // Connection permanently closed
+ finish();
+ }
+});
+```
+
+
+
+| Event | Description |
+|-------|-------------|
+| `onSessionJoined()` | Successfully connected and joined the session |
+| `onSessionLeft()` | Left the session via `leaveSession()` or was removed |
+| `onSessionTimedOut()` | Session ended due to inactivity timeout |
+| `onConnectionLost()` | Network interrupted, SDK attempting reconnection |
+| `onConnectionRestored()` | Connection restored after being lost |
+| `onConnectionClosed()` | Connection permanently closed, cannot reconnect |
+
+---
+
+## Participant Events
+
+Monitor participant activities including join/leave, audio/video state, hand raise, screen sharing, and recording.
+
+
+
+```kotlin
+callSession.addParticipantEventListener(this, object : ParticipantEventListener() {
+ override fun onParticipantJoined(participant: Participant) {
+ // A participant joined the call
+ }
+
+ override fun onParticipantLeft(participant: Participant) {
+ // A participant left the call
+ }
+
+ override fun onParticipantListChanged(participants: List) {
+ // Participant list updated
+ }
+
+ override fun onParticipantAudioMuted(participant: Participant) {}
+ override fun onParticipantAudioUnmuted(participant: Participant) {}
+ override fun onParticipantVideoPaused(participant: Participant) {}
+ override fun onParticipantVideoResumed(participant: Participant) {}
+ override fun onParticipantHandRaised(participant: Participant) {}
+ override fun onParticipantHandLowered(participant: Participant) {}
+ override fun onParticipantStartedScreenShare(participant: Participant) {}
+ override fun onParticipantStoppedScreenShare(participant: Participant) {}
+ override fun onParticipantStartedRecording(participant: Participant) {}
+ override fun onParticipantStoppedRecording(participant: Participant) {}
+ override fun onDominantSpeakerChanged(participant: Participant) {}
+})
+```
+
+
+```java
+callSession.addParticipantEventListener(this, new ParticipantEventListener() {
+ @Override
+ public void onParticipantJoined(Participant participant) {
+ // A participant joined the call
+ }
+
+ @Override
+ public void onParticipantLeft(Participant participant) {
+ // A participant left the call
+ }
+
+ @Override
+ public void onParticipantListChanged(List participants) {
+ // Participant list updated
+ }
+
+ // Other callbacks...
+});
+```
+
+
+
+| Event | Parameter | Description |
+|-------|-----------|-------------|
+| `onParticipantJoined` | `Participant` | A participant connected to the call |
+| `onParticipantLeft` | `Participant` | A participant disconnected from the call |
+| `onParticipantListChanged` | `List` | Participant list updated |
+| `onParticipantAudioMuted` | `Participant` | A participant muted their microphone |
+| `onParticipantAudioUnmuted` | `Participant` | A participant unmuted their microphone |
+| `onParticipantVideoPaused` | `Participant` | A participant turned off their camera |
+| `onParticipantVideoResumed` | `Participant` | A participant turned on their camera |
+| `onParticipantHandRaised` | `Participant` | A participant raised their hand |
+| `onParticipantHandLowered` | `Participant` | A participant lowered their hand |
+| `onParticipantStartedScreenShare` | `Participant` | A participant started screen sharing |
+| `onParticipantStoppedScreenShare` | `Participant` | A participant stopped screen sharing |
+| `onParticipantStartedRecording` | `Participant` | A participant started recording |
+| `onParticipantStoppedRecording` | `Participant` | A participant stopped recording |
+| `onDominantSpeakerChanged` | `Participant` | The active speaker changed |
+
+---
+
+## Media Events
+
+Monitor your local media state changes including audio/video status, recording, and device changes.
+
+
+
+```kotlin
+callSession.addMediaEventsListener(this, object : MediaEventsListener() {
+ override fun onAudioMuted() {
+ // Your microphone was muted
+ }
+
+ override fun onAudioUnMuted() {
+ // Your microphone was unmuted
+ }
+
+ override fun onVideoPaused() {
+ // Your camera was turned off
+ }
+
+ override fun onVideoResumed() {
+ // Your camera was turned on
+ }
+
+ override fun onRecordingStarted() {
+ // Call recording started
+ }
+
+ override fun onRecordingStopped() {
+ // Call recording stopped
+ }
+
+ override fun onScreenShareStarted() {}
+ override fun onScreenShareStopped() {}
+
+ override fun onAudioModeChanged(audioMode: AudioMode) {
+ // Audio output device changed
+ }
+
+ override fun onCameraFacingChanged(facing: CameraFacing) {
+ // Camera switched between front and back
+ }
+})
+```
+
+
+```java
+callSession.addMediaEventsListener(this, new MediaEventsListener() {
+ @Override
+ public void onAudioMuted() {
+ // Your microphone was muted
+ }
+
+ @Override
+ public void onAudioUnMuted() {
+ // Your microphone was unmuted
+ }
+
+ @Override
+ public void onVideoPaused() {
+ // Your camera was turned off
+ }
+
+ @Override
+ public void onVideoResumed() {
+ // Your camera was turned on
+ }
+
+ @Override
+ public void onRecordingStarted() {
+ // Call recording started
+ }
+
+ @Override
+ public void onRecordingStopped() {
+ // Call recording stopped
+ }
+
+ @Override
+ public void onAudioModeChanged(AudioMode audioMode) {
+ // Audio output device changed
+ }
+
+ @Override
+ public void onCameraFacingChanged(CameraFacing facing) {
+ // Camera switched between front and back
+ }
+
+ // Other callbacks...
+});
+```
+
+
+
+| Event | Parameter | Description |
+|-------|-----------|-------------|
+| `onAudioMuted` | - | Your microphone was muted |
+| `onAudioUnMuted` | - | Your microphone was unmuted |
+| `onVideoPaused` | - | Your camera was turned off |
+| `onVideoResumed` | - | Your camera was turned on |
+| `onRecordingStarted` | - | Call recording started |
+| `onRecordingStopped` | - | Call recording stopped |
+| `onScreenShareStarted` | - | You started screen sharing |
+| `onScreenShareStopped` | - | You stopped screen sharing |
+| `onAudioModeChanged` | `AudioMode` | Audio output device changed |
+| `onCameraFacingChanged` | `CameraFacing` | Camera switched between front and back |
+
+
+
+| Value | Description |
+|-------|-------------|
+| `AudioMode.SPEAKER` | Audio routed through device loudspeaker |
+| `AudioMode.EARPIECE` | Audio routed through phone earpiece |
+| `AudioMode.BLUETOOTH` | Audio routed through connected Bluetooth device |
+| `AudioMode.HEADPHONES` | Audio routed through wired headphones |
+
+
+
+| Value | Description |
+|-------|-------------|
+| `CameraFacing.FRONT` | Front-facing (selfie) camera is active |
+| `CameraFacing.BACK` | Rear-facing (main) camera is active |
+
+
+
+---
+
+## Button Click Events
+
+Intercept UI button clicks from the default call interface to add custom behavior or analytics.
+
+
+
+```kotlin
+callSession.addButtonClickListener(this, object : ButtonClickListener() {
+ override fun onLeaveSessionButtonClicked() {
+ // Leave button tapped
+ }
+
+ override fun onToggleAudioButtonClicked() {
+ // Mute/unmute button tapped
+ }
+
+ override fun onToggleVideoButtonClicked() {
+ // Video on/off button tapped
+ }
+
+ override fun onSwitchCameraButtonClicked() {}
+ override fun onRaiseHandButtonClicked() {}
+ override fun onShareInviteButtonClicked() {}
+ override fun onChangeLayoutButtonClicked() {}
+ override fun onParticipantListButtonClicked() {}
+ override fun onChatButtonClicked() {}
+ override fun onRecordingToggleButtonClicked() {}
+})
+```
+
+
+```java
+callSession.addButtonClickListener(this, new ButtonClickListener() {
+ @Override
+ public void onLeaveSessionButtonClicked() {
+ // Leave button tapped
+ }
+
+ @Override
+ public void onToggleAudioButtonClicked() {
+ // Mute/unmute button tapped
+ }
+
+ @Override
+ public void onToggleVideoButtonClicked() {
+ // Video on/off button tapped
+ }
+
+ // Other callbacks...
+});
+```
+
+
+
+| Event | Description |
+|-------|-------------|
+| `onLeaveSessionButtonClicked` | Leave/end call button was tapped |
+| `onToggleAudioButtonClicked` | Mute/unmute button was tapped |
+| `onToggleVideoButtonClicked` | Video on/off button was tapped |
+| `onSwitchCameraButtonClicked` | Camera switch button was tapped |
+| `onRaiseHandButtonClicked` | Raise hand button was tapped |
+| `onShareInviteButtonClicked` | Share/invite button was tapped |
+| `onChangeLayoutButtonClicked` | Layout change button was tapped |
+| `onParticipantListButtonClicked` | Participant list button was tapped |
+| `onChatButtonClicked` | In-call chat button was tapped |
+| `onRecordingToggleButtonClicked` | Recording toggle button was tapped |
+
+
+Button click events fire before the SDK's default action executes. Use these to add custom logic alongside default behavior.
+
+
+---
+
+## Layout Events
+
+Monitor layout changes including layout type switches and Picture-in-Picture mode transitions.
+
+
+
+```kotlin
+callSession.addLayoutListener(this, object : LayoutListener() {
+ override fun onCallLayoutChanged(layoutType: LayoutType) {
+ // Layout changed (TILE, SPOTLIGHT)
+ }
+
+ override fun onParticipantListVisible() {
+ // Participant list panel opened
+ }
+
+ override fun onParticipantListHidden() {
+ // Participant list panel closed
+ }
+
+ override fun onPictureInPictureLayoutEnabled() {
+ // Entered PiP mode
+ }
+
+ override fun onPictureInPictureLayoutDisabled() {
+ // Exited PiP mode
+ }
+})
+```
+
+
+```java
+callSession.addLayoutListener(this, new LayoutListener() {
+ @Override
+ public void onCallLayoutChanged(LayoutType layoutType) {
+ // Layout changed (TILE, SPOTLIGHT)
+ }
+
+ @Override
+ public void onParticipantListVisible() {
+ // Participant list panel opened
+ }
+
+ @Override
+ public void onParticipantListHidden() {
+ // Participant list panel closed
+ }
+
+ @Override
+ public void onPictureInPictureLayoutEnabled() {
+ // Entered PiP mode
+ }
+
+ @Override
+ public void onPictureInPictureLayoutDisabled() {
+ // Exited PiP mode
+ }
+});
+```
+
+
+
+| Event | Parameter | Description |
+|-------|-----------|-------------|
+| `onCallLayoutChanged` | `LayoutType` | Call layout changed |
+| `onParticipantListVisible` | - | Participant list panel was opened |
+| `onParticipantListHidden` | - | Participant list panel was closed |
+| `onPictureInPictureLayoutEnabled` | - | Call entered Picture-in-Picture mode |
+| `onPictureInPictureLayoutDisabled` | - | Call exited Picture-in-Picture mode |
+
+
+| Value | Description | Best For |
+|-------|-------------|----------|
+| `LayoutType.TILE` | Grid layout with equally-sized tiles | Group discussions, team meetings |
+| `LayoutType.SPOTLIGHT` | Large view for active speaker, small tiles for others | Presentations, one-on-one calls |
+
diff --git a/calls/android/idle-timeout.mdx b/calls/android/idle-timeout.mdx
new file mode 100644
index 00000000..9efb4d9d
--- /dev/null
+++ b/calls/android/idle-timeout.mdx
@@ -0,0 +1,154 @@
+---
+title: "Idle Timeout"
+sidebarTitle: "Idle Timeout"
+---
+
+Configure automatic session termination when a user is alone in a call. Idle timeout helps manage resources by ending sessions that have no active participants.
+
+## How Idle Timeout Works
+
+When a user is the only participant in a call session, the idle timeout countdown begins. If no other participant joins before the timeout expires, the session automatically ends and the `onSessionTimedOut` callback is triggered.
+
+The timer also restarts when other participants leave and only one user remains in the call.
+
+```mermaid
+flowchart LR
+ A[Alone in call] --> B[Timer starts]
+ B --> C{Participant joins?}
+ C -->|Yes| D[Timer stops]
+ C -->|No, timeout| E[Session ends]
+ D -->|Participant leaves| A
+```
+
+This is useful for:
+- Preventing abandoned call sessions from running indefinitely
+- Managing server resources efficiently
+- Providing a better user experience when the other party doesn't join
+
+## Configure Idle Timeout
+
+Set the idle timeout period using `setIdleTimeoutPeriod()` in `SessionSettingsBuilder`. The value is in seconds.
+
+
+
+```kotlin
+val sessionSettings = CometChatCalls.SessionSettingsBuilder()
+ .setIdleTimeoutPeriod(120) // 2 minutes
+ .setType(SessionType.VIDEO)
+ .build()
+
+CometChatCalls.joinSession(sessionId, sessionSettings, callViewContainer,
+ object : CometChatCalls.CallbackListener() {
+ override fun onSuccess(callSession: CallSession) {
+ Log.d(TAG, "Joined session")
+ }
+
+ override fun onError(e: CometChatException) {
+ Log.e(TAG, "Failed: ${e.message}")
+ }
+ }
+)
+```
+
+
+```java
+SessionSettings sessionSettings = new CometChatCalls.SessionSettingsBuilder()
+ .setIdleTimeoutPeriod(120) // 2 minutes
+ .setType(SessionType.VIDEO)
+ .build();
+
+CometChatCalls.joinSession(sessionId, sessionSettings, callViewContainer,
+ new CometChatCalls.CallbackListener() {
+ @Override
+ public void onSuccess(CallSession callSession) {
+ Log.d(TAG, "Joined session");
+ }
+
+ @Override
+ public void onError(CometChatException e) {
+ Log.e(TAG, "Failed: " + e.getMessage());
+ }
+ }
+);
+```
+
+
+
+| Parameter | Type | Default | Description |
+|-----------|------|---------|-------------|
+| `idleTimeoutPeriod` | int | 300 | Timeout in seconds when alone in the session |
+
+## Handle Session Timeout
+
+Listen for the `onSessionTimedOut` callback using `SessionStatusListener` to handle when the session ends due to idle timeout:
+
+
+
+```kotlin
+val callSession = CallSession.getInstance()
+
+callSession.addSessionStatusListener(this, object : SessionStatusListener() {
+ override fun onSessionTimedOut() {
+ Log.d(TAG, "Session ended due to idle timeout")
+ // Show message to user
+ showToast("Call ended - no other participants joined")
+ // Navigate away from call screen
+ finish()
+ }
+
+ override fun onSessionJoined() {}
+ override fun onSessionLeft() {}
+ override fun onConnectionLost() {}
+ override fun onConnectionRestored() {}
+ override fun onConnectionClosed() {}
+})
+```
+
+
+```java
+CallSession callSession = CallSession.getInstance();
+
+callSession.addSessionStatusListener(this, new SessionStatusListener() {
+ @Override
+ public void onSessionTimedOut() {
+ Log.d(TAG, "Session ended due to idle timeout");
+ // Show message to user
+ showToast("Call ended - no other participants joined");
+ // Navigate away from call screen
+ finish();
+ }
+
+ @Override public void onSessionJoined() {}
+ @Override public void onSessionLeft() {}
+ @Override public void onConnectionLost() {}
+ @Override public void onConnectionRestored() {}
+ @Override public void onConnectionClosed() {}
+});
+```
+
+
+
+## Disable Idle Timeout
+
+To disable idle timeout and allow sessions to run indefinitely, set a value of `0`:
+
+
+
+```kotlin
+val sessionSettings = CometChatCalls.SessionSettingsBuilder()
+ .setIdleTimeoutPeriod(0) // Disable idle timeout
+ .build()
+```
+
+
+```java
+SessionSettings sessionSettings = new CometChatCalls.SessionSettingsBuilder()
+ .setIdleTimeoutPeriod(0) // Disable idle timeout
+ .build();
+```
+
+
+
+
+Disabling idle timeout may result in sessions running indefinitely if participants don't join or leave properly. Use with caution.
+
diff --git a/calls/android/in-call-chat.mdx b/calls/android/in-call-chat.mdx
new file mode 100644
index 00000000..8274e55f
--- /dev/null
+++ b/calls/android/in-call-chat.mdx
@@ -0,0 +1,830 @@
+---
+title: "In-Call Chat"
+sidebarTitle: "In-Call Chat"
+---
+
+Add real-time messaging to your call experience using CometChat UI Kit. This allows participants to send text messages, share files, and communicate via chat while on a call.
+
+## Overview
+
+In-call chat creates a group conversation linked to the call session. When participants tap the chat button, they can:
+- Send and receive text messages
+- Share images, files, and media
+- See message history from the current call
+- Get unread message notifications via badge count
+
+```mermaid
+flowchart LR
+ subgraph "Call Session"
+ A[Call UI] --> B[Chat Button]
+ B --> C[Chat Activity]
+ end
+
+ subgraph "CometChat"
+ D[Group] --> E[Messages]
+ end
+
+ C <--> D
+ A -->|Session ID = Group GUID| D
+```
+
+## Prerequisites
+
+- CometChat Calls SDK integrated ([Setup](/calls/android/setup))
+- CometChat Chat SDK integrated ([Chat SDK](/sdk/android/overview))
+- CometChat UI Kit integrated ([UI Kit](/ui-kit/android/overview))
+
+
+The Chat SDK and UI Kit are separate from the Calls SDK. You'll need to add both dependencies to your project.
+
+
+---
+
+## Step 1: Add UI Kit Dependency
+
+Add the CometChat UI Kit to your `build.gradle`:
+
+```groovy
+dependencies {
+ implementation 'com.cometchat:chat-uikit-android:4.+'
+}
+```
+
+---
+
+## Step 2: Enable Chat Button
+
+Configure session settings to show the chat button:
+
+
+
+```kotlin
+val sessionSettings = CometChatCalls.SessionSettingsBuilder()
+ .hideChatButton(false) // Show the chat button
+ .build()
+```
+
+
+```java
+SessionSettings sessionSettings = new CometChatCalls.SessionSettingsBuilder()
+ .hideChatButton(false) // Show the chat button
+ .build();
+```
+
+
+
+---
+
+## Step 3: Create Chat Group
+
+Create or join a CometChat group using the session ID as the group GUID. This links the chat to the specific call session.
+
+
+
+```kotlin
+private fun setupChatGroup(sessionId: String, meetingName: String) {
+ // Try to get existing group first
+ CometChat.getGroup(sessionId, object : CometChat.CallbackListener() {
+ override fun onSuccess(group: Group) {
+ if (!group.isJoined) {
+ // Join the existing group
+ joinGroup(sessionId, group.groupType)
+ } else {
+ Log.d(TAG, "Already joined group: ${group.name}")
+ }
+ }
+
+ override fun onError(e: CometChatException) {
+ if (e.code == "ERR_GUID_NOT_FOUND") {
+ // Group doesn't exist, create it
+ createGroup(sessionId, meetingName)
+ } else {
+ Log.e(TAG, "Error getting group: ${e.message}")
+ }
+ }
+ })
+}
+
+private fun createGroup(guid: String, name: String) {
+ val group = Group(guid, name, CometChatConstants.GROUP_TYPE_PUBLIC, null)
+
+ CometChat.createGroup(group, object : CometChat.CallbackListener() {
+ override fun onSuccess(createdGroup: Group) {
+ Log.d(TAG, "Group created: ${createdGroup.name}")
+ }
+
+ override fun onError(e: CometChatException) {
+ Log.e(TAG, "Group creation failed: ${e.message}")
+ }
+ })
+}
+
+private fun joinGroup(guid: String, groupType: String) {
+ CometChat.joinGroup(guid, groupType, null, object : CometChat.CallbackListener() {
+ override fun onSuccess(joinedGroup: Group) {
+ Log.d(TAG, "Joined group: ${joinedGroup.name}")
+ }
+
+ override fun onError(e: CometChatException) {
+ Log.e(TAG, "Join group failed: ${e.message}")
+ }
+ })
+}
+```
+
+
+```java
+private void setupChatGroup(String sessionId, String meetingName) {
+ // Try to get existing group first
+ CometChat.getGroup(sessionId, new CometChat.CallbackListener() {
+ @Override
+ public void onSuccess(Group group) {
+ if (!group.isJoined()) {
+ // Join the existing group
+ joinGroup(sessionId, group.getGroupType());
+ } else {
+ Log.d(TAG, "Already joined group: " + group.getName());
+ }
+ }
+
+ @Override
+ public void onError(CometChatException e) {
+ if ("ERR_GUID_NOT_FOUND".equals(e.getCode())) {
+ // Group doesn't exist, create it
+ createGroup(sessionId, meetingName);
+ } else {
+ Log.e(TAG, "Error getting group: " + e.getMessage());
+ }
+ }
+ });
+}
+
+private void createGroup(String guid, String name) {
+ Group group = new Group(guid, name, CometChatConstants.GROUP_TYPE_PUBLIC, null);
+
+ CometChat.createGroup(group, new CometChat.CallbackListener() {
+ @Override
+ public void onSuccess(Group createdGroup) {
+ Log.d(TAG, "Group created: " + createdGroup.getName());
+ }
+
+ @Override
+ public void onError(CometChatException e) {
+ Log.e(TAG, "Group creation failed: " + e.getMessage());
+ }
+ });
+}
+
+private void joinGroup(String guid, String groupType) {
+ CometChat.joinGroup(guid, groupType, null, new CometChat.CallbackListener() {
+ @Override
+ public void onSuccess(Group joinedGroup) {
+ Log.d(TAG, "Joined group: " + joinedGroup.getName());
+ }
+
+ @Override
+ public void onError(CometChatException e) {
+ Log.e(TAG, "Join group failed: " + e.getMessage());
+ }
+ });
+}
+```
+
+
+
+---
+
+## Step 4: Handle Chat Button Click
+
+Listen for the chat button click and open your chat activity:
+
+
+
+```kotlin
+private var unreadMessageCount = 0
+
+private fun setupChatButtonListener() {
+ val callSession = CallSession.getInstance()
+
+ callSession.addButtonClickListener(this, object : ButtonClickListener() {
+ override fun onChatButtonClicked() {
+ // Reset unread count when opening chat
+ unreadMessageCount = 0
+ callSession.setChatButtonUnreadCount(0)
+
+ // Open chat activity
+ val intent = Intent(this@CallActivity, ChatActivity::class.java).apply {
+ putExtra("SESSION_ID", sessionId)
+ putExtra("MEETING_NAME", meetingName)
+ }
+ startActivity(intent)
+ }
+ })
+}
+```
+
+
+```java
+private int unreadMessageCount = 0;
+
+private void setupChatButtonListener() {
+ CallSession callSession = CallSession.getInstance();
+
+ callSession.addButtonClickListener(this, new ButtonClickListener() {
+ @Override
+ public void onChatButtonClicked() {
+ // Reset unread count when opening chat
+ unreadMessageCount = 0;
+ callSession.setChatButtonUnreadCount(0);
+
+ // Open chat activity
+ Intent intent = new Intent(CallActivity.this, ChatActivity.class);
+ intent.putExtra("SESSION_ID", sessionId);
+ intent.putExtra("MEETING_NAME", meetingName);
+ startActivity(intent);
+ }
+ });
+}
+```
+
+
+
+---
+
+## Step 5: Track Unread Messages
+
+Listen for incoming messages and update the badge count on the chat button:
+
+
+
+```kotlin
+private fun setupMessageListener() {
+ CometChat.addMessageListener(TAG, object : CometChat.MessageListener() {
+ override fun onTextMessageReceived(textMessage: TextMessage) {
+ // Check if message is for our call's group
+ val receiver = textMessage.receiver
+ if (receiver is Group && receiver.guid == sessionId) {
+ unreadMessageCount++
+ CallSession.getInstance().setChatButtonUnreadCount(unreadMessageCount)
+ }
+ }
+
+ override fun onMediaMessageReceived(mediaMessage: MediaMessage) {
+ val receiver = mediaMessage.receiver
+ if (receiver is Group && receiver.guid == sessionId) {
+ unreadMessageCount++
+ CallSession.getInstance().setChatButtonUnreadCount(unreadMessageCount)
+ }
+ }
+ })
+}
+
+override fun onDestroy() {
+ super.onDestroy()
+ CometChat.removeMessageListener(TAG)
+}
+```
+
+
+```java
+private void setupMessageListener() {
+ CometChat.addMessageListener(TAG, new CometChat.MessageListener() {
+ @Override
+ public void onTextMessageReceived(TextMessage textMessage) {
+ // Check if message is for our call's group
+ BaseMessage receiver = textMessage.getReceiver();
+ if (receiver instanceof Group && ((Group) receiver).getGuid().equals(sessionId)) {
+ unreadMessageCount++;
+ CallSession.getInstance().setChatButtonUnreadCount(unreadMessageCount);
+ }
+ }
+
+ @Override
+ public void onMediaMessageReceived(MediaMessage mediaMessage) {
+ BaseMessage receiver = mediaMessage.getReceiver();
+ if (receiver instanceof Group && ((Group) receiver).getGuid().equals(sessionId)) {
+ unreadMessageCount++;
+ CallSession.getInstance().setChatButtonUnreadCount(unreadMessageCount);
+ }
+ }
+ });
+}
+
+@Override
+protected void onDestroy() {
+ super.onDestroy();
+ CometChat.removeMessageListener(TAG);
+}
+```
+
+
+
+---
+
+## Step 6: Create Chat Activity
+
+Create a chat activity using UI Kit components:
+
+
+```xml
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+
+
+
+```kotlin
+class ChatActivity : AppCompatActivity() {
+
+ private lateinit var messageList: CometChatMessageList
+ private lateinit var messageComposer: CometChatMessageComposer
+ private lateinit var messageHeader: CometChatMessageHeader
+ private lateinit var progressBar: ProgressBar
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_chat)
+
+ messageList = findViewById(R.id.message_list)
+ messageComposer = findViewById(R.id.message_composer)
+ messageHeader = findViewById(R.id.message_header)
+ progressBar = findViewById(R.id.progress_bar)
+
+ val sessionId = intent.getStringExtra("SESSION_ID") ?: return
+ val meetingName = intent.getStringExtra("MEETING_NAME") ?: "Chat"
+
+ loadGroup(sessionId, meetingName)
+ }
+
+ private fun loadGroup(guid: String, meetingName: String) {
+ progressBar.visibility = View.VISIBLE
+
+ CometChat.getGroup(guid, object : CometChat.CallbackListener() {
+ override fun onSuccess(group: Group) {
+ if (!group.isJoined) {
+ joinAndSetGroup(guid, group.groupType)
+ } else {
+ setGroup(group)
+ }
+ }
+
+ override fun onError(e: CometChatException) {
+ if (e.code == "ERR_GUID_NOT_FOUND") {
+ createAndSetGroup(guid, meetingName)
+ } else {
+ progressBar.visibility = View.GONE
+ Log.e(TAG, "Error: ${e.message}")
+ }
+ }
+ })
+ }
+
+ private fun createAndSetGroup(guid: String, name: String) {
+ val group = Group(guid, name, CometChatConstants.GROUP_TYPE_PUBLIC, null)
+ CometChat.createGroup(group, object : CometChat.CallbackListener() {
+ override fun onSuccess(createdGroup: Group) {
+ setGroup(createdGroup)
+ }
+
+ override fun onError(e: CometChatException) {
+ progressBar.visibility = View.GONE
+ }
+ })
+ }
+
+ private fun joinAndSetGroup(guid: String, groupType: String) {
+ CometChat.joinGroup(guid, groupType, null, object : CometChat.CallbackListener() {
+ override fun onSuccess(joinedGroup: Group) {
+ setGroup(joinedGroup)
+ }
+
+ override fun onError(e: CometChatException) {
+ progressBar.visibility = View.GONE
+ }
+ })
+ }
+
+ private fun setGroup(group: Group) {
+ progressBar.visibility = View.GONE
+
+ messageList.setGroup(group)
+ messageComposer.setGroup(group)
+ messageHeader.setGroup(group)
+
+ // Hide auxiliary buttons (call, video) since we're already in a call
+ messageHeader.setAuxiliaryButtonView { _, _, _ ->
+ View(this).apply { visibility = View.GONE }
+ }
+ }
+
+ companion object {
+ private const val TAG = "ChatActivity"
+ }
+}
+```
+
+
+```java
+public class ChatActivity extends AppCompatActivity {
+
+ private static final String TAG = "ChatActivity";
+
+ private CometChatMessageList messageList;
+ private CometChatMessageComposer messageComposer;
+ private CometChatMessageHeader messageHeader;
+ private ProgressBar progressBar;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_chat);
+
+ messageList = findViewById(R.id.message_list);
+ messageComposer = findViewById(R.id.message_composer);
+ messageHeader = findViewById(R.id.message_header);
+ progressBar = findViewById(R.id.progress_bar);
+
+ String sessionId = getIntent().getStringExtra("SESSION_ID");
+ String meetingName = getIntent().getStringExtra("MEETING_NAME");
+
+ if (sessionId == null) return;
+ if (meetingName == null) meetingName = "Chat";
+
+ loadGroup(sessionId, meetingName);
+ }
+
+ private void loadGroup(String guid, String meetingName) {
+ progressBar.setVisibility(View.VISIBLE);
+
+ CometChat.getGroup(guid, new CometChat.CallbackListener() {
+ @Override
+ public void onSuccess(Group group) {
+ if (!group.isJoined()) {
+ joinAndSetGroup(guid, group.getGroupType());
+ } else {
+ setGroup(group);
+ }
+ }
+
+ @Override
+ public void onError(CometChatException e) {
+ if ("ERR_GUID_NOT_FOUND".equals(e.getCode())) {
+ createAndSetGroup(guid, meetingName);
+ } else {
+ progressBar.setVisibility(View.GONE);
+ Log.e(TAG, "Error: " + e.getMessage());
+ }
+ }
+ });
+ }
+
+ private void createAndSetGroup(String guid, String name) {
+ Group group = new Group(guid, name, CometChatConstants.GROUP_TYPE_PUBLIC, null);
+ CometChat.createGroup(group, new CometChat.CallbackListener() {
+ @Override
+ public void onSuccess(Group createdGroup) {
+ setGroup(createdGroup);
+ }
+
+ @Override
+ public void onError(CometChatException e) {
+ progressBar.setVisibility(View.GONE);
+ }
+ });
+ }
+
+ private void joinAndSetGroup(String guid, String groupType) {
+ CometChat.joinGroup(guid, groupType, null, new CometChat.CallbackListener() {
+ @Override
+ public void onSuccess(Group joinedGroup) {
+ setGroup(joinedGroup);
+ }
+
+ @Override
+ public void onError(CometChatException e) {
+ progressBar.setVisibility(View.GONE);
+ }
+ });
+ }
+
+ private void setGroup(Group group) {
+ progressBar.setVisibility(View.GONE);
+
+ messageList.setGroup(group);
+ messageComposer.setGroup(group);
+ messageHeader.setGroup(group);
+
+ // Hide auxiliary buttons since we're already in a call
+ messageHeader.setAuxiliaryButtonView((context, user, group1) -> {
+ View view = new View(context);
+ view.setVisibility(View.GONE);
+ return view;
+ });
+ }
+}
+```
+
+
+
+---
+
+## Complete Example
+
+Here's the complete CallActivity with in-call chat integration:
+
+
+
+```kotlin
+class CallActivity : AppCompatActivity() {
+
+ private lateinit var callSession: CallSession
+ private var sessionId: String = ""
+ private var meetingName: String = ""
+ private var unreadMessageCount = 0
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_call)
+
+ sessionId = intent.getStringExtra("SESSION_ID") ?: return
+ meetingName = intent.getStringExtra("MEETING_NAME") ?: "Meeting"
+
+ callSession = CallSession.getInstance()
+
+ // Setup chat group for this call
+ setupChatGroup(sessionId, meetingName)
+
+ // Listen for chat button clicks
+ setupChatButtonListener()
+
+ // Track incoming messages for badge
+ setupMessageListener()
+
+ // Join the call
+ joinCall()
+ }
+
+ private fun setupChatGroup(sessionId: String, meetingName: String) {
+ CometChat.getGroup(sessionId, object : CometChat.CallbackListener() {
+ override fun onSuccess(group: Group) {
+ if (!group.isJoined) {
+ CometChat.joinGroup(sessionId, group.groupType, null,
+ object : CometChat.CallbackListener() {
+ override fun onSuccess(g: Group) {}
+ override fun onError(e: CometChatException) {}
+ })
+ }
+ }
+
+ override fun onError(e: CometChatException) {
+ if (e.code == "ERR_GUID_NOT_FOUND") {
+ val group = Group(sessionId, meetingName,
+ CometChatConstants.GROUP_TYPE_PUBLIC, null)
+ CometChat.createGroup(group, object : CometChat.CallbackListener() {
+ override fun onSuccess(g: Group) {}
+ override fun onError(e: CometChatException) {}
+ })
+ }
+ }
+ })
+ }
+
+ private fun setupChatButtonListener() {
+ callSession.addButtonClickListener(this, object : ButtonClickListener() {
+ override fun onChatButtonClicked() {
+ unreadMessageCount = 0
+ callSession.setChatButtonUnreadCount(0)
+
+ startActivity(Intent(this@CallActivity, ChatActivity::class.java).apply {
+ putExtra("SESSION_ID", sessionId)
+ putExtra("MEETING_NAME", meetingName)
+ })
+ }
+ })
+ }
+
+ private fun setupMessageListener() {
+ CometChat.addMessageListener(TAG, object : CometChat.MessageListener() {
+ override fun onTextMessageReceived(textMessage: TextMessage) {
+ val receiver = textMessage.receiver
+ if (receiver is Group && receiver.guid == sessionId) {
+ unreadMessageCount++
+ callSession.setChatButtonUnreadCount(unreadMessageCount)
+ }
+ }
+ })
+ }
+
+ private fun joinCall() {
+ val container = findViewById(R.id.callContainer)
+
+ val sessionSettings = CometChatCalls.SessionSettingsBuilder()
+ .setTitle(meetingName)
+ .hideChatButton(false)
+ .build()
+
+ CometChatCalls.joinSession(
+ sessionId = sessionId,
+ sessionSettings = sessionSettings,
+ view = container,
+ context = this,
+ listener = object : CometChatCalls.CallbackListener() {
+ override fun onSuccess(session: CallSession) {
+ Log.d(TAG, "Joined call")
+ }
+
+ override fun onError(e: CometChatException) {
+ Log.e(TAG, "Join failed: ${e.message}")
+ }
+ }
+ )
+ }
+
+ override fun onDestroy() {
+ super.onDestroy()
+ CometChat.removeMessageListener(TAG)
+ }
+
+ companion object {
+ private const val TAG = "CallActivity"
+ }
+}
+```
+
+
+```java
+public class CallActivity extends AppCompatActivity {
+
+ private static final String TAG = "CallActivity";
+
+ private CallSession callSession;
+ private String sessionId;
+ private String meetingName;
+ private int unreadMessageCount = 0;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_call);
+
+ sessionId = getIntent().getStringExtra("SESSION_ID");
+ meetingName = getIntent().getStringExtra("MEETING_NAME");
+
+ if (sessionId == null) return;
+ if (meetingName == null) meetingName = "Meeting";
+
+ callSession = CallSession.getInstance();
+
+ // Setup chat group for this call
+ setupChatGroup(sessionId, meetingName);
+
+ // Listen for chat button clicks
+ setupChatButtonListener();
+
+ // Track incoming messages for badge
+ setupMessageListener();
+
+ // Join the call
+ joinCall();
+ }
+
+ private void setupChatGroup(String sessionId, String meetingName) {
+ CometChat.getGroup(sessionId, new CometChat.CallbackListener() {
+ @Override
+ public void onSuccess(Group group) {
+ if (!group.isJoined()) {
+ CometChat.joinGroup(sessionId, group.getGroupType(), null,
+ new CometChat.CallbackListener() {
+ @Override public void onSuccess(Group g) {}
+ @Override public void onError(CometChatException e) {}
+ });
+ }
+ }
+
+ @Override
+ public void onError(CometChatException e) {
+ if ("ERR_GUID_NOT_FOUND".equals(e.getCode())) {
+ Group group = new Group(sessionId, meetingName,
+ CometChatConstants.GROUP_TYPE_PUBLIC, null);
+ CometChat.createGroup(group, new CometChat.CallbackListener() {
+ @Override public void onSuccess(Group g) {}
+ @Override public void onError(CometChatException e) {}
+ });
+ }
+ }
+ });
+ }
+
+ private void setupChatButtonListener() {
+ callSession.addButtonClickListener(this, new ButtonClickListener() {
+ @Override
+ public void onChatButtonClicked() {
+ unreadMessageCount = 0;
+ callSession.setChatButtonUnreadCount(0);
+
+ Intent intent = new Intent(CallActivity.this, ChatActivity.class);
+ intent.putExtra("SESSION_ID", sessionId);
+ intent.putExtra("MEETING_NAME", meetingName);
+ startActivity(intent);
+ }
+ });
+ }
+
+ private void setupMessageListener() {
+ CometChat.addMessageListener(TAG, new CometChat.MessageListener() {
+ @Override
+ public void onTextMessageReceived(TextMessage textMessage) {
+ if (textMessage.getReceiver() instanceof Group) {
+ Group group = (Group) textMessage.getReceiver();
+ if (group.getGuid().equals(sessionId)) {
+ unreadMessageCount++;
+ callSession.setChatButtonUnreadCount(unreadMessageCount);
+ }
+ }
+ }
+ });
+ }
+
+ private void joinCall() {
+ FrameLayout container = findViewById(R.id.callContainer);
+
+ SessionSettings sessionSettings = new CometChatCalls.SessionSettingsBuilder()
+ .setTitle(meetingName)
+ .hideChatButton(false)
+ .build();
+
+ CometChatCalls.joinSession(
+ sessionId,
+ sessionSettings,
+ container,
+ this,
+ new CometChatCalls.CallbackListener() {
+ @Override
+ public void onSuccess(CallSession session) {
+ Log.d(TAG, "Joined call");
+ }
+
+ @Override
+ public void onError(CometChatException e) {
+ Log.e(TAG, "Join failed: " + e.getMessage());
+ }
+ }
+ );
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ CometChat.removeMessageListener(TAG);
+ }
+}
+```
+
+
+
+---
+
+## Related Documentation
+
+- [UI Kit Overview](/ui-kit/android/overview) - CometChat UI Kit components
+- [Button Click Listener](/calls/android/button-click-listener) - Handle button clicks
+- [SessionSettingsBuilder](/calls/android/session-settings) - Configure chat button visibility
diff --git a/calls/android/join-session.mdx b/calls/android/join-session.mdx
new file mode 100644
index 00000000..199d84fa
--- /dev/null
+++ b/calls/android/join-session.mdx
@@ -0,0 +1,298 @@
+---
+title: "Join Session"
+sidebarTitle: "Join Session"
+---
+
+Join a call session using one of two approaches: the quick start method with a session ID, or the advanced flow with manual token generation for more control.
+
+## Overview
+
+The CometChat Calls SDK provides two ways to join a session:
+
+| Approach | Best For | Complexity |
+|----------|----------|------------|
+| **Join with Session ID** | Most use cases - simple and straightforward | Low - One method call |
+| **Join with Token** | Custom token management, pre-generation, caching | Medium - Two-step process |
+
+
+Both approaches require a container view in your layout and properly configured [SessionSettings](/calls/android/session-settings).
+
+
+## Container Setup
+
+Add a container view to your layout where the call interface will be rendered:
+
+```xml
+
+```
+
+The call UI will be dynamically added to this container when you join the session.
+
+## Join with Session ID
+
+The simplest way to join a session. Pass a session ID and the SDK automatically generates the token and joins the call.
+
+
+
+```kotlin
+val sessionId = "SESSION_ID"
+val callViewContainer = findViewById(R.id.call_view_container)
+
+val sessionSettings = CometChatCalls.SessionSettingsBuilder()
+ .setDisplayName("John Doe")
+ .setType(SessionType.VIDEO)
+ .build()
+
+CometChatCalls.joinSession(sessionId, sessionSettings, callViewContainer,
+ object : CometChatCalls.CallbackListener() {
+ override fun onSuccess(callSession: CallSession) {
+ Log.d(TAG, "Joined session successfully")
+ }
+
+ override fun onError(e: CometChatException) {
+ Log.e(TAG, "Failed: ${e.message}")
+ }
+ }
+)
+```
+
+
+```java
+String sessionId = "SESSION_ID";
+RelativeLayout callViewContainer = findViewById(R.id.call_view_container);
+
+SessionSettings sessionSettings = new CometChatCalls.SessionSettingsBuilder()
+ .setDisplayName("John Doe")
+ .setType(SessionType.VIDEO)
+ .build();
+
+CometChatCalls.joinSession(sessionId, sessionSettings, callViewContainer,
+ new CometChatCalls.CallbackListener() {
+ @Override
+ public void onSuccess(CallSession callSession) {
+ Log.d(TAG, "Joined session successfully");
+ }
+
+ @Override
+ public void onError(CometChatException e) {
+ Log.e(TAG, "Failed: " + e.getMessage());
+ }
+ }
+);
+```
+
+
+
+| Parameter | Type | Description |
+|-----------|------|-------------|
+| `sessionId` | String | Unique identifier for the call session |
+| `sessionSettings` | SessionSettings | Configuration for the session |
+| `callViewContainer` | RelativeLayout | Container view for the call UI |
+| `listener` | CallbackListener | Callback for success/error handling |
+
+
+All participants joining the same call must use the same session ID.
+
+
+## Join with Token
+
+For scenarios requiring more control over token generation, such as pre-generating tokens, implementing custom caching strategies, or managing token lifecycle separately.
+
+**Step 1: Generate Token**
+
+Generate a call token for the session. Each token is unique to a specific session and user combination.
+
+
+
+```kotlin
+val sessionId = "SESSION_ID"
+
+CometChatCalls.generateToken(sessionId, object : CometChatCalls.CallbackListener() {
+ override fun onSuccess(token: GenerateToken) {
+ Log.d(TAG, "Token generated: ${token.token}")
+ // Store or use the token
+ }
+
+ override fun onError(e: CometChatException) {
+ Log.e(TAG, "Token generation failed: ${e.message}")
+ }
+})
+```
+
+
+```java
+String sessionId = "SESSION_ID";
+
+CometChatCalls.generateToken(sessionId, new CometChatCalls.CallbackListener() {
+ @Override
+ public void onSuccess(GenerateToken token) {
+ Log.d(TAG, "Token generated: " + token.getToken());
+ // Store or use the token
+ }
+
+ @Override
+ public void onError(CometChatException e) {
+ Log.e(TAG, "Token generation failed: " + e.getMessage());
+ }
+});
+```
+
+
+
+| Parameter | Type | Description |
+|-----------|------|-------------|
+| `sessionId` | String | Unique identifier for the call session |
+| `listener` | CallbackListener | Callback returning the generated token |
+
+**Step 2: Join with Token**
+
+Use the generated token to join the session. This gives you control over when and how the token is used.
+
+
+
+```kotlin
+val callViewContainer = findViewById(R.id.call_view_container)
+
+val sessionSettings = CometChatCalls.SessionSettingsBuilder()
+ .setDisplayName("John Doe")
+ .setType(SessionType.VIDEO)
+ .build()
+
+// Use the previously generated token
+CometChatCalls.joinSession(generatedToken, sessionSettings, callViewContainer,
+ object : CometChatCalls.CallbackListener() {
+ override fun onSuccess(callSession: CallSession) {
+ Log.d(TAG, "Joined session successfully")
+ }
+
+ override fun onError(e: CometChatException) {
+ Log.e(TAG, "Failed: ${e.message}")
+ }
+ }
+)
+```
+
+
+```java
+RelativeLayout callViewContainer = findViewById(R.id.call_view_container);
+
+SessionSettings sessionSettings = new CometChatCalls.SessionSettingsBuilder()
+ .setDisplayName("John Doe")
+ .setType(SessionType.VIDEO)
+ .build();
+
+// Use the previously generated token
+CometChatCalls.joinSession(generatedToken, sessionSettings, callViewContainer,
+ new CometChatCalls.CallbackListener() {
+ @Override
+ public void onSuccess(CallSession callSession) {
+ Log.d(TAG, "Joined session successfully");
+ }
+
+ @Override
+ public void onError(CometChatException e) {
+ Log.e(TAG, "Failed: " + e.getMessage());
+ }
+ }
+);
+```
+
+
+
+| Parameter | Type | Description |
+|-----------|------|-------------|
+| `callToken` | GenerateToken | Previously generated token object |
+| `sessionSettings` | SessionSettings | Configuration for the session |
+| `callViewContainer` | RelativeLayout | Container view for the call UI |
+| `listener` | CallbackListener | Callback for success/error handling |
+
+**Complete Example**
+
+
+
+```kotlin
+val sessionId = "SESSION_ID"
+val callViewContainer = findViewById(R.id.call_view_container)
+
+// Step 1: Generate token
+CometChatCalls.generateToken(sessionId, object : CometChatCalls.CallbackListener() {
+ override fun onSuccess(token: GenerateToken) {
+ // Step 2: Join with token
+ val sessionSettings = CometChatCalls.SessionSettingsBuilder()
+ .setDisplayName("John Doe")
+ .setType(SessionType.VIDEO)
+ .build()
+
+ CometChatCalls.joinSession(token, sessionSettings, callViewContainer,
+ object : CometChatCalls.CallbackListener() {
+ override fun onSuccess(callSession: CallSession) {
+ Log.d(TAG, "Joined session successfully")
+ }
+
+ override fun onError(e: CometChatException) {
+ Log.e(TAG, "Failed to join: ${e.message}")
+ }
+ }
+ )
+ }
+
+ override fun onError(e: CometChatException) {
+ Log.e(TAG, "Token generation failed: ${e.message}")
+ }
+})
+```
+
+
+```java
+String sessionId = "SESSION_ID";
+RelativeLayout callViewContainer = findViewById(R.id.call_view_container);
+
+// Step 1: Generate token
+CometChatCalls.generateToken(sessionId, new CometChatCalls.CallbackListener() {
+ @Override
+ public void onSuccess(GenerateToken token) {
+ // Step 2: Join with token
+ SessionSettings sessionSettings = new CometChatCalls.SessionSettingsBuilder()
+ .setDisplayName("John Doe")
+ .setType(SessionType.VIDEO)
+ .build();
+
+ CometChatCalls.joinSession(token, sessionSettings, callViewContainer,
+ new CometChatCalls.CallbackListener() {
+ @Override
+ public void onSuccess(CallSession callSession) {
+ Log.d(TAG, "Joined session successfully");
+ }
+
+ @Override
+ public void onError(CometChatException e) {
+ Log.e(TAG, "Failed to join: " + e.getMessage());
+ }
+ }
+ );
+ }
+
+ @Override
+ public void onError(CometChatException e) {
+ Log.e(TAG, "Token generation failed: " + e.getMessage());
+ }
+});
+```
+
+
+
+## Error Handling
+
+Common errors when joining a session:
+
+| Error Code | Description |
+|------------|-------------|
+| `ERROR_COMETCHAT_CALLS_SDK_INIT` | SDK not initialized - call `init()` first |
+| `ERROR_AUTH_TOKEN` | User not logged in or auth token invalid |
+| `ERROR_CALL_SESSION_ID` | Session ID is null or empty |
+| `ERROR_CALL_TOKEN` | Invalid or missing call token |
+| `ERROR_CALLING_VIEW_REF_NULL` | Container view is null |
+| `ERROR_JSON_EXCEPTION` | Invalid session settings or response parsing error |
diff --git a/calls/android/layout-listener.mdx b/calls/android/layout-listener.mdx
new file mode 100644
index 00000000..b72324a6
--- /dev/null
+++ b/calls/android/layout-listener.mdx
@@ -0,0 +1,562 @@
+---
+title: "Layout Listener"
+sidebarTitle: "Layout Listener"
+---
+
+Monitor layout changes with `LayoutListener`. This listener provides callbacks for call layout changes, participant list visibility, and Picture-in-Picture (PiP) mode state changes.
+
+## Prerequisites
+
+- An active [call session](/calls/android/join-session)
+- Access to the `CallSession` instance
+
+## Register Listener
+
+Register a `LayoutListener` to receive layout event callbacks:
+
+
+
+```kotlin
+val callSession = CallSession.getInstance()
+
+callSession.addLayoutListener(this, object : LayoutListener() {
+ override fun onCallLayoutChanged(layoutType: LayoutType) {
+ Log.d(TAG, "Layout changed to: $layoutType")
+ }
+
+ override fun onParticipantListVisible() {
+ Log.d(TAG, "Participant list is now visible")
+ }
+
+ override fun onParticipantListHidden() {
+ Log.d(TAG, "Participant list is now hidden")
+ }
+
+ override fun onPictureInPictureLayoutEnabled() {
+ Log.d(TAG, "PiP mode enabled")
+ }
+
+ override fun onPictureInPictureLayoutDisabled() {
+ Log.d(TAG, "PiP mode disabled")
+ }
+})
+```
+
+
+```java
+CallSession callSession = CallSession.getInstance();
+
+callSession.addLayoutListener(this, new LayoutListener() {
+ @Override
+ public void onCallLayoutChanged(LayoutType layoutType) {
+ Log.d(TAG, "Layout changed to: " + layoutType);
+ }
+
+ @Override
+ public void onParticipantListVisible() {
+ Log.d(TAG, "Participant list is now visible");
+ }
+
+ @Override
+ public void onParticipantListHidden() {
+ Log.d(TAG, "Participant list is now hidden");
+ }
+
+ @Override
+ public void onPictureInPictureLayoutEnabled() {
+ Log.d(TAG, "PiP mode enabled");
+ }
+
+ @Override
+ public void onPictureInPictureLayoutDisabled() {
+ Log.d(TAG, "PiP mode disabled");
+ }
+});
+```
+
+
+
+
+The listener is automatically removed when the `LifecycleOwner` (Activity/Fragment) is destroyed, preventing memory leaks.
+
+
+---
+
+## Callbacks
+
+### onCallLayoutChanged
+
+Triggered when the call layout changes between Tile and Spotlight modes.
+
+
+
+```kotlin
+override fun onCallLayoutChanged(layoutType: LayoutType) {
+ Log.d(TAG, "Layout changed to: $layoutType")
+
+ when (layoutType) {
+ LayoutType.TILE -> {
+ // Update UI for tile layout
+ updateLayoutIcon(R.drawable.ic_grid_view)
+ }
+ LayoutType.SPOTLIGHT -> {
+ // Update UI for spotlight layout
+ updateLayoutIcon(R.drawable.ic_spotlight)
+ }
+ }
+}
+```
+
+
+```java
+@Override
+public void onCallLayoutChanged(LayoutType layoutType) {
+ Log.d(TAG, "Layout changed to: " + layoutType);
+
+ switch (layoutType) {
+ case TILE:
+ // Update UI for tile layout
+ updateLayoutIcon(R.drawable.ic_grid_view);
+ break;
+ case SPOTLIGHT:
+ // Update UI for spotlight layout
+ updateLayoutIcon(R.drawable.ic_spotlight);
+ break;
+ }
+}
+```
+
+
+
+**LayoutType Values:**
+
+| Value | Description |
+|-------|-------------|
+| `TILE` | Grid layout showing all participants equally |
+| `SPOTLIGHT` | Focus on active speaker with others in sidebar |
+
+**Use Cases:**
+- Update layout toggle button icon
+- Adjust custom UI overlays
+- Log layout preference analytics
+
+---
+
+### onParticipantListVisible
+
+Triggered when the participant list panel becomes visible.
+
+
+
+```kotlin
+override fun onParticipantListVisible() {
+ Log.d(TAG, "Participant list opened")
+ // Track analytics
+ analytics.logEvent("participant_list_opened")
+ // Adjust UI if needed
+ adjustUIForParticipantList(isVisible = true)
+}
+```
+
+
+```java
+@Override
+public void onParticipantListVisible() {
+ Log.d(TAG, "Participant list opened");
+ // Track analytics
+ analytics.logEvent("participant_list_opened");
+ // Adjust UI if needed
+ adjustUIForParticipantList(true);
+}
+```
+
+
+
+**Use Cases:**
+- Log analytics events
+- Adjust custom UI elements
+- Pause other UI animations
+
+---
+
+### onParticipantListHidden
+
+Triggered when the participant list panel is hidden.
+
+
+
+```kotlin
+override fun onParticipantListHidden() {
+ Log.d(TAG, "Participant list closed")
+ // Restore UI
+ adjustUIForParticipantList(isVisible = false)
+}
+```
+
+
+```java
+@Override
+public void onParticipantListHidden() {
+ Log.d(TAG, "Participant list closed");
+ // Restore UI
+ adjustUIForParticipantList(false);
+}
+```
+
+
+
+**Use Cases:**
+- Restore UI elements
+- Resume animations
+- Update button states
+
+---
+
+### onPictureInPictureLayoutEnabled
+
+Triggered when Picture-in-Picture (PiP) mode is enabled.
+
+
+
+```kotlin
+override fun onPictureInPictureLayoutEnabled() {
+ Log.d(TAG, "PiP mode enabled")
+ // Hide non-essential UI elements
+ hideCallControls()
+ // Track PiP usage
+ analytics.logEvent("pip_enabled")
+}
+```
+
+
+```java
+@Override
+public void onPictureInPictureLayoutEnabled() {
+ Log.d(TAG, "PiP mode enabled");
+ // Hide non-essential UI elements
+ hideCallControls();
+ // Track PiP usage
+ analytics.logEvent("pip_enabled");
+}
+```
+
+
+
+**Use Cases:**
+- Hide call control buttons
+- Simplify UI for small window
+- Track PiP feature usage
+
+---
+
+### onPictureInPictureLayoutDisabled
+
+Triggered when Picture-in-Picture (PiP) mode is disabled.
+
+
+
+```kotlin
+override fun onPictureInPictureLayoutDisabled() {
+ Log.d(TAG, "PiP mode disabled")
+ // Restore full UI
+ showCallControls()
+}
+```
+
+
+```java
+@Override
+public void onPictureInPictureLayoutDisabled() {
+ Log.d(TAG, "PiP mode disabled");
+ // Restore full UI
+ showCallControls();
+}
+```
+
+
+
+**Use Cases:**
+- Restore call control buttons
+- Show full call UI
+- Resume normal layout
+
+---
+
+## Complete Example
+
+Here's a complete example handling all layout events:
+
+
+
+```kotlin
+class CallActivity : AppCompatActivity() {
+ private lateinit var callSession: CallSession
+ private lateinit var layoutButton: ImageButton
+ private lateinit var controlsContainer: View
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_call)
+
+ initViews()
+ callSession = CallSession.getInstance()
+ setupLayoutListener()
+ }
+
+ private fun initViews() {
+ layoutButton = findViewById(R.id.layoutButton)
+ controlsContainer = findViewById(R.id.controlsContainer)
+ }
+
+ private fun setupLayoutListener() {
+ callSession.addLayoutListener(this, object : LayoutListener() {
+ override fun onCallLayoutChanged(layoutType: LayoutType) {
+ runOnUiThread {
+ when (layoutType) {
+ LayoutType.TILE -> {
+ layoutButton.setImageResource(R.drawable.ic_grid_view)
+ layoutButton.contentDescription = "Switch to spotlight"
+ }
+ LayoutType.SPOTLIGHT -> {
+ layoutButton.setImageResource(R.drawable.ic_spotlight)
+ layoutButton.contentDescription = "Switch to tile"
+ }
+ }
+ }
+ }
+
+ override fun onParticipantListVisible() {
+ runOnUiThread {
+ Log.d(TAG, "Participant list visible")
+ // Dim background or adjust layout
+ }
+ }
+
+ override fun onParticipantListHidden() {
+ runOnUiThread {
+ Log.d(TAG, "Participant list hidden")
+ // Restore normal layout
+ }
+ }
+
+ override fun onPictureInPictureLayoutEnabled() {
+ runOnUiThread {
+ Log.d(TAG, "PiP enabled")
+ // Hide controls for PiP mode
+ controlsContainer.visibility = View.GONE
+ }
+ }
+
+ override fun onPictureInPictureLayoutDisabled() {
+ runOnUiThread {
+ Log.d(TAG, "PiP disabled")
+ // Show controls when exiting PiP
+ controlsContainer.visibility = View.VISIBLE
+ }
+ }
+ })
+ }
+
+ // Enable PiP when user presses home button
+ override fun onUserLeaveHint() {
+ super.onUserLeaveHint()
+ if (callSession.isSessionActive()) {
+ callSession.enablePictureInPictureLayout()
+ }
+ }
+
+ companion object {
+ private const val TAG = "CallActivity"
+ }
+}
+```
+
+
+```java
+public class CallActivity extends AppCompatActivity {
+ private static final String TAG = "CallActivity";
+ private CallSession callSession;
+ private ImageButton layoutButton;
+ private View controlsContainer;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_call);
+
+ initViews();
+ callSession = CallSession.getInstance();
+ setupLayoutListener();
+ }
+
+ private void initViews() {
+ layoutButton = findViewById(R.id.layoutButton);
+ controlsContainer = findViewById(R.id.controlsContainer);
+ }
+
+ private void setupLayoutListener() {
+ callSession.addLayoutListener(this, new LayoutListener() {
+ @Override
+ public void onCallLayoutChanged(LayoutType layoutType) {
+ runOnUiThread(() -> {
+ switch (layoutType) {
+ case TILE:
+ layoutButton.setImageResource(R.drawable.ic_grid_view);
+ layoutButton.setContentDescription("Switch to spotlight");
+ break;
+ case SPOTLIGHT:
+ layoutButton.setImageResource(R.drawable.ic_spotlight);
+ layoutButton.setContentDescription("Switch to tile");
+ break;
+ }
+ });
+ }
+
+ @Override
+ public void onParticipantListVisible() {
+ runOnUiThread(() -> {
+ Log.d(TAG, "Participant list visible");
+ // Dim background or adjust layout
+ });
+ }
+
+ @Override
+ public void onParticipantListHidden() {
+ runOnUiThread(() -> {
+ Log.d(TAG, "Participant list hidden");
+ // Restore normal layout
+ });
+ }
+
+ @Override
+ public void onPictureInPictureLayoutEnabled() {
+ runOnUiThread(() -> {
+ Log.d(TAG, "PiP enabled");
+ // Hide controls for PiP mode
+ controlsContainer.setVisibility(View.GONE);
+ });
+ }
+
+ @Override
+ public void onPictureInPictureLayoutDisabled() {
+ runOnUiThread(() -> {
+ Log.d(TAG, "PiP disabled");
+ // Show controls when exiting PiP
+ controlsContainer.setVisibility(View.VISIBLE);
+ });
+ }
+ });
+ }
+
+ // Enable PiP when user presses home button
+ @Override
+ protected void onUserLeaveHint() {
+ super.onUserLeaveHint();
+ if (callSession.isSessionActive()) {
+ callSession.enablePictureInPictureLayout();
+ }
+ }
+}
+```
+
+
+
+---
+
+## Controlling Layout Programmatically
+
+You can change the layout and PiP state programmatically:
+
+### Change Layout
+
+
+
+```kotlin
+// Switch to tile layout
+callSession.setLayout(LayoutType.TILE)
+
+// Switch to spotlight layout
+callSession.setLayout(LayoutType.SPOTLIGHT)
+```
+
+
+```java
+// Switch to tile layout
+callSession.setLayout(LayoutType.TILE);
+
+// Switch to spotlight layout
+callSession.setLayout(LayoutType.SPOTLIGHT);
+```
+
+
+
+### Enable/Disable PiP
+
+
+
+```kotlin
+// Enable Picture-in-Picture
+callSession.enablePictureInPictureLayout()
+
+// Disable Picture-in-Picture
+callSession.disablePictureInPictureLayout()
+```
+
+
+```java
+// Enable Picture-in-Picture
+callSession.enablePictureInPictureLayout();
+
+// Disable Picture-in-Picture
+callSession.disablePictureInPictureLayout();
+```
+
+
+
+---
+
+## Initial Layout Configuration
+
+Set the initial layout when joining a session:
+
+
+
+```kotlin
+val sessionSettings = CometChatCalls.SessionSettingsBuilder()
+ .setLayout(LayoutType.TILE) // or LayoutType.SPOTLIGHT
+ .hideChangeLayoutButton(false) // Allow users to change layout
+ .build()
+```
+
+
+```java
+SessionSettings sessionSettings = new CometChatCalls.SessionSettingsBuilder()
+ .setLayout(LayoutType.TILE) // or LayoutType.SPOTLIGHT
+ .hideChangeLayoutButton(false) // Allow users to change layout
+ .build();
+```
+
+
+
+---
+
+## Callbacks Summary
+
+| Callback | Parameter | Description |
+|----------|-----------|-------------|
+| `onCallLayoutChanged` | `LayoutType` | Call layout changed (TILE/SPOTLIGHT) |
+| `onParticipantListVisible` | - | Participant list panel opened |
+| `onParticipantListHidden` | - | Participant list panel closed |
+| `onPictureInPictureLayoutEnabled` | - | PiP mode was enabled |
+| `onPictureInPictureLayoutDisabled` | - | PiP mode was disabled |
+
+## Next Steps
+
+
+
+ Control layout programmatically
+
+
+ Configure initial layout settings
+
+
diff --git a/calls/android/layout-ui.mdx b/calls/android/layout-ui.mdx
new file mode 100644
index 00000000..838d98c3
--- /dev/null
+++ b/calls/android/layout-ui.mdx
@@ -0,0 +1,449 @@
+---
+title: "Layout & UI"
+sidebarTitle: "Layout & UI"
+---
+
+Control the call layout and UI elements during an active session. These methods allow you to change the call layout, enable Picture-in-Picture mode, and update UI badges.
+
+## Prerequisites
+
+- An active [call session](/calls/android/join-session)
+- Access to the `CallSession` instance
+
+## Get CallSession Instance
+
+Layout and UI methods are called on the `CallSession` singleton:
+
+
+
+```kotlin
+val callSession = CallSession.getInstance()
+```
+
+
+```java
+CallSession callSession = CallSession.getInstance();
+```
+
+
+
+---
+
+## Set Layout
+
+Change the call layout during an active session.
+
+
+
+```kotlin
+// Switch to tile layout (grid view)
+callSession.setLayout(LayoutType.TILE)
+
+// Switch to spotlight layout (active speaker focus)
+callSession.setLayout(LayoutType.SPOTLIGHT)
+
+// Switch to sidebar layout
+callSession.setLayout(LayoutType.SIDEBAR)
+```
+
+
+```java
+// Switch to tile layout (grid view)
+callSession.setLayout(LayoutType.TILE);
+
+// Switch to spotlight layout (active speaker focus)
+callSession.setLayout(LayoutType.SPOTLIGHT);
+
+// Switch to sidebar layout
+callSession.setLayout(LayoutType.SIDEBAR);
+```
+
+
+
+### LayoutType Enum
+
+| Value | Description |
+|-------|-------------|
+| `TILE` | Grid layout showing all participants equally sized |
+| `SPOTLIGHT` | Focus on the active speaker with others in smaller tiles |
+| `SIDEBAR` | Main speaker with participants in a sidebar |
+
+
+When the layout changes, the `onCallLayoutChanged(LayoutType)` callback is triggered on your `LayoutListener`.
+
+
+---
+
+## Picture-in-Picture Mode
+
+Enable Picture-in-Picture (PiP) mode to allow users to continue viewing the call while using other apps.
+
+### Enable PiP
+
+
+
+```kotlin
+callSession.enablePictureInPictureLayout()
+```
+
+
+```java
+callSession.enablePictureInPictureLayout();
+```
+
+
+
+### Disable PiP
+
+
+
+```kotlin
+callSession.disablePictureInPictureLayout()
+```
+
+
+```java
+callSession.disablePictureInPictureLayout();
+```
+
+
+
+### Android PiP Integration
+
+To fully support PiP on Android, you need to handle the activity lifecycle:
+
+
+
+```kotlin
+class CallActivity : AppCompatActivity() {
+ private var isInPipMode = false
+
+ override fun onPictureInPictureModeChanged(
+ isInPictureInPictureMode: Boolean,
+ newConfig: Configuration
+ ) {
+ super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig)
+ isInPipMode = isInPictureInPictureMode
+
+ if (isInPictureInPictureMode) {
+ CallSession.getInstance().enablePictureInPictureLayout()
+ } else {
+ CallSession.getInstance().disablePictureInPictureLayout()
+ }
+ }
+
+ override fun onUserLeaveHint() {
+ super.onUserLeaveHint()
+ // Enter PiP when user presses home button
+ if (CallSession.getInstance().isSessionActive()) {
+ enterPictureInPictureMode(
+ PictureInPictureParams.Builder()
+ .setAspectRatio(Rational(16, 9))
+ .build()
+ )
+ }
+ }
+
+ override fun onStop() {
+ super.onStop()
+ if (isInPipMode && !isChangingConfigurations) {
+ // PiP window was closed, end the call
+ CallSession.getInstance().leaveSession()
+ }
+ }
+}
+```
+
+
+```java
+public class CallActivity extends AppCompatActivity {
+ private boolean isInPipMode = false;
+
+ @Override
+ public void onPictureInPictureModeChanged(
+ boolean isInPictureInPictureMode,
+ Configuration newConfig) {
+ super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig);
+ isInPipMode = isInPictureInPictureMode;
+
+ if (isInPictureInPictureMode) {
+ CallSession.getInstance().enablePictureInPictureLayout();
+ } else {
+ CallSession.getInstance().disablePictureInPictureLayout();
+ }
+ }
+
+ @Override
+ protected void onUserLeaveHint() {
+ super.onUserLeaveHint();
+ // Enter PiP when user presses home button
+ if (CallSession.getInstance().isSessionActive()) {
+ enterPictureInPictureMode(
+ new PictureInPictureParams.Builder()
+ .setAspectRatio(new Rational(16, 9))
+ .build()
+ );
+ }
+ }
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+ if (isInPipMode && !isChangingConfigurations()) {
+ // PiP window was closed, end the call
+ CallSession.getInstance().leaveSession();
+ }
+ }
+}
+```
+
+
+
+
+Don't forget to add `android:supportsPictureInPicture="true"` to your activity in the AndroidManifest.xml.
+
+
+---
+
+## Set Chat Button Unread Count
+
+Update the badge count on the chat button to show unread messages.
+
+
+
+```kotlin
+// Set unread count
+callSession.setChatButtonUnreadCount(5)
+
+// Clear unread count
+callSession.setChatButtonUnreadCount(0)
+```
+
+
+```java
+// Set unread count
+callSession.setChatButtonUnreadCount(5);
+
+// Clear unread count
+callSession.setChatButtonUnreadCount(0);
+```
+
+
+
+| Parameter | Type | Description |
+|-----------|------|-------------|
+| `count` | int | Number of unread messages to display on the badge |
+
+
+The chat button must be visible (`hideChatButton(false)`) for the badge to appear.
+
+
+---
+
+## Listen for Layout Events
+
+Register a `LayoutListener` to receive callbacks when layout changes occur:
+
+
+
+```kotlin
+callSession.addLayoutListener(this, object : LayoutListener {
+ override fun onCallLayoutChanged(layoutType: LayoutType) {
+ Log.d(TAG, "Layout changed to: ${layoutType.value}")
+ }
+
+ override fun onParticipantListVisible() {
+ Log.d(TAG, "Participant list is now visible")
+ }
+
+ override fun onParticipantListHidden() {
+ Log.d(TAG, "Participant list is now hidden")
+ }
+
+ override fun onPictureInPictureLayoutEnabled() {
+ Log.d(TAG, "PiP mode enabled")
+ }
+
+ override fun onPictureInPictureLayoutDisabled() {
+ Log.d(TAG, "PiP mode disabled")
+ }
+})
+```
+
+
+```java
+callSession.addLayoutListener(this, new LayoutListener() {
+ @Override
+ public void onCallLayoutChanged(LayoutType layoutType) {
+ Log.d(TAG, "Layout changed to: " + layoutType.getValue());
+ }
+
+ @Override
+ public void onParticipantListVisible() {
+ Log.d(TAG, "Participant list is now visible");
+ }
+
+ @Override
+ public void onParticipantListHidden() {
+ Log.d(TAG, "Participant list is now hidden");
+ }
+
+ @Override
+ public void onPictureInPictureLayoutEnabled() {
+ Log.d(TAG, "PiP mode enabled");
+ }
+
+ @Override
+ public void onPictureInPictureLayoutDisabled() {
+ Log.d(TAG, "PiP mode disabled");
+ }
+});
+```
+
+
+
+---
+
+## Initial Layout Settings
+
+Configure the initial layout when joining a session:
+
+
+
+```kotlin
+val sessionSettings = CometChatCalls.SessionSettingsBuilder()
+ .setLayout(LayoutType.TILE) // Start with tile layout
+ .build()
+```
+
+
+```java
+SessionSettings sessionSettings = new CometChatCalls.SessionSettingsBuilder()
+ .setLayout(LayoutType.TILE) // Start with tile layout
+ .build();
+```
+
+
+
+---
+
+## Hide UI Elements
+
+Control the visibility of various UI elements:
+
+
+
+```kotlin
+val sessionSettings = CometChatCalls.SessionSettingsBuilder()
+ // Panels
+ .hideControlPanel(false) // Show bottom control bar
+ .hideHeaderPanel(false) // Show top header bar
+ .hideSessionTimer(false) // Show session duration timer
+
+ // Buttons
+ .hideChangeLayoutButton(false) // Show layout toggle button
+ .hideChatButton(false) // Show chat button
+ .hideParticipantListButton(false) // Show participant list button
+ .build()
+```
+
+
+```java
+SessionSettings sessionSettings = new CometChatCalls.SessionSettingsBuilder()
+ // Panels
+ .hideControlPanel(false) // Show bottom control bar
+ .hideHeaderPanel(false) // Show top header bar
+ .hideSessionTimer(false) // Show session duration timer
+
+ // Buttons
+ .hideChangeLayoutButton(false) // Show layout toggle button
+ .hideChatButton(false) // Show chat button
+ .hideParticipantListButton(false) // Show participant list button
+ .build();
+```
+
+
+
+---
+
+## Button Click Listeners
+
+Listen for UI button clicks to implement custom behavior:
+
+
+
+```kotlin
+callSession.addButtonClickListener(this, object : ButtonClickListener {
+ override fun onChangeLayoutButtonClicked() {
+ Log.d(TAG, "Layout button clicked")
+ }
+
+ override fun onChatButtonClicked() {
+ Log.d(TAG, "Chat button clicked")
+ // Open your chat UI
+ }
+
+ override fun onParticipantListButtonClicked() {
+ Log.d(TAG, "Participant list button clicked")
+ }
+
+ // Other callbacks...
+ override fun onLeaveSessionButtonClicked() {}
+ override fun onRaiseHandButtonClicked() {}
+ override fun onShareInviteButtonClicked() {}
+ override fun onToggleAudioButtonClicked() {}
+ override fun onToggleVideoButtonClicked() {}
+ override fun onSwitchCameraButtonClicked() {}
+ override fun onRecordingToggleButtonClicked() {}
+})
+```
+
+
+```java
+callSession.addButtonClickListener(this, new ButtonClickListener() {
+ @Override
+ public void onChangeLayoutButtonClicked() {
+ Log.d(TAG, "Layout button clicked");
+ }
+
+ @Override
+ public void onChatButtonClicked() {
+ Log.d(TAG, "Chat button clicked");
+ // Open your chat UI
+ }
+
+ @Override
+ public void onParticipantListButtonClicked() {
+ Log.d(TAG, "Participant list button clicked");
+ }
+
+ // Other callbacks...
+ @Override
+ public void onLeaveSessionButtonClicked() {}
+ @Override
+ public void onRaiseHandButtonClicked() {}
+ @Override
+ public void onShareInviteButtonClicked() {}
+ @Override
+ public void onToggleAudioButtonClicked() {}
+ @Override
+ public void onToggleVideoButtonClicked() {}
+ @Override
+ public void onSwitchCameraButtonClicked() {}
+ @Override
+ public void onRecordingToggleButtonClicked() {}
+});
+```
+
+
+
+## Next Steps
+
+
+
+ Leave session and hand raise controls
+
+
+ Handle all layout events
+
+
diff --git a/calls/android/media-events-listener.mdx b/calls/android/media-events-listener.mdx
new file mode 100644
index 00000000..f9de1e9d
--- /dev/null
+++ b/calls/android/media-events-listener.mdx
@@ -0,0 +1,751 @@
+---
+title: "Media Events Listener"
+sidebarTitle: "Media Events Listener"
+---
+
+Monitor local media state changes with `MediaEventsListener`. This listener provides callbacks for your own audio/video state changes, recording events, screen sharing, audio mode changes, and camera facing changes.
+
+## Prerequisites
+
+- An active [call session](/calls/android/join-session)
+- Access to the `CallSession` instance
+
+## Register Listener
+
+Register a `MediaEventsListener` to receive media event callbacks:
+
+
+
+```kotlin
+val callSession = CallSession.getInstance()
+
+callSession.addMediaEventsListener(this, object : MediaEventsListener() {
+ override fun onAudioMuted() {
+ Log.d(TAG, "Audio muted")
+ }
+
+ override fun onAudioUnMuted() {
+ Log.d(TAG, "Audio unmuted")
+ }
+
+ override fun onVideoPaused() {
+ Log.d(TAG, "Video paused")
+ }
+
+ override fun onVideoResumed() {
+ Log.d(TAG, "Video resumed")
+ }
+
+ // Additional callbacks...
+})
+```
+
+
+```java
+CallSession callSession = CallSession.getInstance();
+
+callSession.addMediaEventsListener(this, new MediaEventsListener() {
+ @Override
+ public void onAudioMuted() {
+ Log.d(TAG, "Audio muted");
+ }
+
+ @Override
+ public void onAudioUnMuted() {
+ Log.d(TAG, "Audio unmuted");
+ }
+
+ @Override
+ public void onVideoPaused() {
+ Log.d(TAG, "Video paused");
+ }
+
+ @Override
+ public void onVideoResumed() {
+ Log.d(TAG, "Video resumed");
+ }
+
+ // Additional callbacks...
+});
+```
+
+
+
+
+The listener is automatically removed when the `LifecycleOwner` (Activity/Fragment) is destroyed, preventing memory leaks.
+
+
+---
+
+## Callbacks
+
+### onAudioMuted
+
+Triggered when your local audio is muted.
+
+
+
+```kotlin
+override fun onAudioMuted() {
+ Log.d(TAG, "Audio muted")
+ // Update mute button UI state
+ updateMuteButtonState(isMuted = true)
+}
+```
+
+
+```java
+@Override
+public void onAudioMuted() {
+ Log.d(TAG, "Audio muted");
+ // Update mute button UI state
+ updateMuteButtonState(true);
+}
+```
+
+
+
+**Use Cases:**
+- Update mute button icon/state
+- Show muted indicator in UI
+- Sync UI with actual audio state
+
+---
+
+### onAudioUnMuted
+
+Triggered when your local audio is unmuted.
+
+
+
+```kotlin
+override fun onAudioUnMuted() {
+ Log.d(TAG, "Audio unmuted")
+ // Update mute button UI state
+ updateMuteButtonState(isMuted = false)
+}
+```
+
+
+```java
+@Override
+public void onAudioUnMuted() {
+ Log.d(TAG, "Audio unmuted");
+ // Update mute button UI state
+ updateMuteButtonState(false);
+}
+```
+
+
+
+**Use Cases:**
+- Update mute button icon/state
+- Hide muted indicator
+- Sync UI with actual audio state
+
+---
+
+### onVideoPaused
+
+Triggered when your local video is paused.
+
+
+
+```kotlin
+override fun onVideoPaused() {
+ Log.d(TAG, "Video paused")
+ // Update video button UI state
+ updateVideoButtonState(isPaused = true)
+ // Show avatar instead of video preview
+}
+```
+
+
+```java
+@Override
+public void onVideoPaused() {
+ Log.d(TAG, "Video paused");
+ // Update video button UI state
+ updateVideoButtonState(true);
+ // Show avatar instead of video preview
+}
+```
+
+
+
+**Use Cases:**
+- Update video toggle button state
+- Show avatar/placeholder in local preview
+- Sync UI with actual video state
+
+---
+
+### onVideoResumed
+
+Triggered when your local video is resumed.
+
+
+
+```kotlin
+override fun onVideoResumed() {
+ Log.d(TAG, "Video resumed")
+ // Update video button UI state
+ updateVideoButtonState(isPaused = false)
+ // Show video preview
+}
+```
+
+
+```java
+@Override
+public void onVideoResumed() {
+ Log.d(TAG, "Video resumed");
+ // Update video button UI state
+ updateVideoButtonState(false);
+ // Show video preview
+}
+```
+
+
+
+**Use Cases:**
+- Update video toggle button state
+- Show local video preview
+- Sync UI with actual video state
+
+---
+
+### onRecordingStarted
+
+Triggered when session recording starts.
+
+
+
+```kotlin
+override fun onRecordingStarted() {
+ Log.d(TAG, "Recording started")
+ // Show recording indicator
+ showRecordingIndicator(true)
+ // Notify user that recording is active
+}
+```
+
+
+```java
+@Override
+public void onRecordingStarted() {
+ Log.d(TAG, "Recording started");
+ // Show recording indicator
+ showRecordingIndicator(true);
+ // Notify user that recording is active
+}
+```
+
+
+
+**Use Cases:**
+- Display recording indicator (red dot)
+- Update recording button state
+- Show notification to participants
+
+---
+
+### onRecordingStopped
+
+Triggered when session recording stops.
+
+
+
+```kotlin
+override fun onRecordingStopped() {
+ Log.d(TAG, "Recording stopped")
+ // Hide recording indicator
+ showRecordingIndicator(false)
+}
+```
+
+
+```java
+@Override
+public void onRecordingStopped() {
+ Log.d(TAG, "Recording stopped");
+ // Hide recording indicator
+ showRecordingIndicator(false);
+}
+```
+
+
+
+**Use Cases:**
+- Hide recording indicator
+- Update recording button state
+- Show recording saved notification
+
+---
+
+### onScreenShareStarted
+
+Triggered when you start sharing your screen.
+
+
+
+```kotlin
+override fun onScreenShareStarted() {
+ Log.d(TAG, "Screen sharing started")
+ // Update screen share button state
+ updateScreenShareButtonState(isSharing = true)
+ // Show screen share preview
+}
+```
+
+
+```java
+@Override
+public void onScreenShareStarted() {
+ Log.d(TAG, "Screen sharing started");
+ // Update screen share button state
+ updateScreenShareButtonState(true);
+ // Show screen share preview
+}
+```
+
+
+
+**Use Cases:**
+- Update screen share button state
+- Show "You are sharing" indicator
+- Minimize local video preview
+
+---
+
+### onScreenShareStopped
+
+Triggered when you stop sharing your screen.
+
+
+
+```kotlin
+override fun onScreenShareStopped() {
+ Log.d(TAG, "Screen sharing stopped")
+ // Update screen share button state
+ updateScreenShareButtonState(isSharing = false)
+ // Restore normal view
+}
+```
+
+
+```java
+@Override
+public void onScreenShareStopped() {
+ Log.d(TAG, "Screen sharing stopped");
+ // Update screen share button state
+ updateScreenShareButtonState(false);
+ // Restore normal view
+}
+```
+
+
+
+**Use Cases:**
+- Update screen share button state
+- Hide "You are sharing" indicator
+- Restore local video preview
+
+---
+
+### onAudioModeChanged
+
+Triggered when the audio output mode changes (speaker, earpiece, bluetooth).
+
+
+
+```kotlin
+override fun onAudioModeChanged(audioMode: AudioMode) {
+ Log.d(TAG, "Audio mode changed to: $audioMode")
+ // Update audio mode button/icon
+ when (audioMode) {
+ AudioMode.SPEAKER -> updateAudioModeIcon(R.drawable.ic_speaker)
+ AudioMode.EARPIECE -> updateAudioModeIcon(R.drawable.ic_earpiece)
+ AudioMode.BLUETOOTH -> updateAudioModeIcon(R.drawable.ic_bluetooth)
+ }
+}
+```
+
+
+```java
+@Override
+public void onAudioModeChanged(AudioMode audioMode) {
+ Log.d(TAG, "Audio mode changed to: " + audioMode);
+ // Update audio mode button/icon
+ switch (audioMode) {
+ case SPEAKER:
+ updateAudioModeIcon(R.drawable.ic_speaker);
+ break;
+ case EARPIECE:
+ updateAudioModeIcon(R.drawable.ic_earpiece);
+ break;
+ case BLUETOOTH:
+ updateAudioModeIcon(R.drawable.ic_bluetooth);
+ break;
+ }
+}
+```
+
+
+
+**AudioMode Values:**
+
+| Value | Description |
+|-------|-------------|
+| `SPEAKER` | Audio output through device speaker |
+| `EARPIECE` | Audio output through earpiece |
+| `BLUETOOTH` | Audio output through connected Bluetooth device |
+
+**Use Cases:**
+- Update audio mode button icon
+- Show current audio output device
+- Handle Bluetooth connection/disconnection
+
+---
+
+### onCameraFacingChanged
+
+Triggered when the camera facing changes (front/back).
+
+
+
+```kotlin
+override fun onCameraFacingChanged(facing: CameraFacing) {
+ Log.d(TAG, "Camera facing changed to: $facing")
+ // Update camera switch button state
+ when (facing) {
+ CameraFacing.FRONT -> updateCameraIcon(R.drawable.ic_camera_front)
+ CameraFacing.BACK -> updateCameraIcon(R.drawable.ic_camera_back)
+ }
+}
+```
+
+
+```java
+@Override
+public void onCameraFacingChanged(CameraFacing facing) {
+ Log.d(TAG, "Camera facing changed to: " + facing);
+ // Update camera switch button state
+ switch (facing) {
+ case FRONT:
+ updateCameraIcon(R.drawable.ic_camera_front);
+ break;
+ case BACK:
+ updateCameraIcon(R.drawable.ic_camera_back);
+ break;
+ }
+}
+```
+
+
+
+**CameraFacing Values:**
+
+| Value | Description |
+|-------|-------------|
+| `FRONT` | Front-facing camera (selfie camera) |
+| `BACK` | Back-facing camera (main camera) |
+
+**Use Cases:**
+- Update camera switch button icon
+- Adjust UI for mirrored/non-mirrored preview
+- Track camera state
+
+---
+
+## Complete Example
+
+Here's a complete example handling all media events:
+
+
+
+```kotlin
+class CallActivity : AppCompatActivity() {
+ private lateinit var callSession: CallSession
+
+ private lateinit var muteButton: ImageButton
+ private lateinit var videoButton: ImageButton
+ private lateinit var screenShareButton: ImageButton
+ private lateinit var audioModeButton: ImageButton
+ private lateinit var recordingIndicator: View
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_call)
+
+ initViews()
+ callSession = CallSession.getInstance()
+ setupMediaEventsListener()
+ }
+
+ private fun initViews() {
+ muteButton = findViewById(R.id.muteButton)
+ videoButton = findViewById(R.id.videoButton)
+ screenShareButton = findViewById(R.id.screenShareButton)
+ audioModeButton = findViewById(R.id.audioModeButton)
+ recordingIndicator = findViewById(R.id.recordingIndicator)
+ }
+
+ private fun setupMediaEventsListener() {
+ callSession.addMediaEventsListener(this, object : MediaEventsListener() {
+ override fun onAudioMuted() {
+ runOnUiThread {
+ muteButton.setImageResource(R.drawable.ic_mic_off)
+ muteButton.isSelected = true
+ }
+ }
+
+ override fun onAudioUnMuted() {
+ runOnUiThread {
+ muteButton.setImageResource(R.drawable.ic_mic_on)
+ muteButton.isSelected = false
+ }
+ }
+
+ override fun onVideoPaused() {
+ runOnUiThread {
+ videoButton.setImageResource(R.drawable.ic_videocam_off)
+ videoButton.isSelected = true
+ }
+ }
+
+ override fun onVideoResumed() {
+ runOnUiThread {
+ videoButton.setImageResource(R.drawable.ic_videocam_on)
+ videoButton.isSelected = false
+ }
+ }
+
+ override fun onRecordingStarted() {
+ runOnUiThread {
+ recordingIndicator.visibility = View.VISIBLE
+ Toast.makeText(
+ this@CallActivity,
+ "Recording started",
+ Toast.LENGTH_SHORT
+ ).show()
+ }
+ }
+
+ override fun onRecordingStopped() {
+ runOnUiThread {
+ recordingIndicator.visibility = View.GONE
+ Toast.makeText(
+ this@CallActivity,
+ "Recording stopped",
+ Toast.LENGTH_SHORT
+ ).show()
+ }
+ }
+
+ override fun onScreenShareStarted() {
+ runOnUiThread {
+ screenShareButton.isSelected = true
+ Toast.makeText(
+ this@CallActivity,
+ "You are sharing your screen",
+ Toast.LENGTH_SHORT
+ ).show()
+ }
+ }
+
+ override fun onScreenShareStopped() {
+ runOnUiThread {
+ screenShareButton.isSelected = false
+ }
+ }
+
+ override fun onAudioModeChanged(audioMode: AudioMode) {
+ runOnUiThread {
+ val iconRes = when (audioMode) {
+ AudioMode.SPEAKER -> R.drawable.ic_volume_up
+ AudioMode.EARPIECE -> R.drawable.ic_phone_in_talk
+ AudioMode.BLUETOOTH -> R.drawable.ic_bluetooth_audio
+ }
+ audioModeButton.setImageResource(iconRes)
+ }
+ }
+
+ override fun onCameraFacingChanged(facing: CameraFacing) {
+ runOnUiThread {
+ Log.d(TAG, "Camera switched to: $facing")
+ }
+ }
+ })
+ }
+
+ companion object {
+ private const val TAG = "CallActivity"
+ }
+}
+```
+
+
+```java
+public class CallActivity extends AppCompatActivity {
+ private static final String TAG = "CallActivity";
+ private CallSession callSession;
+
+ private ImageButton muteButton;
+ private ImageButton videoButton;
+ private ImageButton screenShareButton;
+ private ImageButton audioModeButton;
+ private View recordingIndicator;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_call);
+
+ initViews();
+ callSession = CallSession.getInstance();
+ setupMediaEventsListener();
+ }
+
+ private void initViews() {
+ muteButton = findViewById(R.id.muteButton);
+ videoButton = findViewById(R.id.videoButton);
+ screenShareButton = findViewById(R.id.screenShareButton);
+ audioModeButton = findViewById(R.id.audioModeButton);
+ recordingIndicator = findViewById(R.id.recordingIndicator);
+ }
+
+ private void setupMediaEventsListener() {
+ callSession.addMediaEventsListener(this, new MediaEventsListener() {
+ @Override
+ public void onAudioMuted() {
+ runOnUiThread(() -> {
+ muteButton.setImageResource(R.drawable.ic_mic_off);
+ muteButton.setSelected(true);
+ });
+ }
+
+ @Override
+ public void onAudioUnMuted() {
+ runOnUiThread(() -> {
+ muteButton.setImageResource(R.drawable.ic_mic_on);
+ muteButton.setSelected(false);
+ });
+ }
+
+ @Override
+ public void onVideoPaused() {
+ runOnUiThread(() -> {
+ videoButton.setImageResource(R.drawable.ic_videocam_off);
+ videoButton.setSelected(true);
+ });
+ }
+
+ @Override
+ public void onVideoResumed() {
+ runOnUiThread(() -> {
+ videoButton.setImageResource(R.drawable.ic_videocam_on);
+ videoButton.setSelected(false);
+ });
+ }
+
+ @Override
+ public void onRecordingStarted() {
+ runOnUiThread(() -> {
+ recordingIndicator.setVisibility(View.VISIBLE);
+ Toast.makeText(
+ CallActivity.this,
+ "Recording started",
+ Toast.LENGTH_SHORT
+ ).show();
+ });
+ }
+
+ @Override
+ public void onRecordingStopped() {
+ runOnUiThread(() -> {
+ recordingIndicator.setVisibility(View.GONE);
+ Toast.makeText(
+ CallActivity.this,
+ "Recording stopped",
+ Toast.LENGTH_SHORT
+ ).show();
+ });
+ }
+
+ @Override
+ public void onScreenShareStarted() {
+ runOnUiThread(() -> {
+ screenShareButton.setSelected(true);
+ Toast.makeText(
+ CallActivity.this,
+ "You are sharing your screen",
+ Toast.LENGTH_SHORT
+ ).show();
+ });
+ }
+
+ @Override
+ public void onScreenShareStopped() {
+ runOnUiThread(() -> screenShareButton.setSelected(false));
+ }
+
+ @Override
+ public void onAudioModeChanged(AudioMode audioMode) {
+ runOnUiThread(() -> {
+ int iconRes;
+ switch (audioMode) {
+ case SPEAKER:
+ iconRes = R.drawable.ic_volume_up;
+ break;
+ case EARPIECE:
+ iconRes = R.drawable.ic_phone_in_talk;
+ break;
+ case BLUETOOTH:
+ iconRes = R.drawable.ic_bluetooth_audio;
+ break;
+ default:
+ iconRes = R.drawable.ic_volume_up;
+ }
+ audioModeButton.setImageResource(iconRes);
+ });
+ }
+
+ @Override
+ public void onCameraFacingChanged(CameraFacing facing) {
+ runOnUiThread(() -> Log.d(TAG, "Camera switched to: " + facing));
+ }
+ });
+ }
+}
+```
+
+
+
+---
+
+## Callbacks Summary
+
+| Callback | Parameter | Description |
+|----------|-----------|-------------|
+| `onAudioMuted` | - | Local audio was muted |
+| `onAudioUnMuted` | - | Local audio was unmuted |
+| `onVideoPaused` | - | Local video was paused |
+| `onVideoResumed` | - | Local video was resumed |
+| `onRecordingStarted` | - | Session recording started |
+| `onRecordingStopped` | - | Session recording stopped |
+| `onScreenShareStarted` | - | Local screen sharing started |
+| `onScreenShareStopped` | - | Local screen sharing stopped |
+| `onAudioModeChanged` | `AudioMode` | Audio output mode changed |
+| `onCameraFacingChanged` | `CameraFacing` | Camera facing changed |
+
+## Next Steps
+
+
+
+ Handle UI button click events
+
+
+ Control audio programmatically
+
+
diff --git a/calls/android/overview.mdx b/calls/android/overview.mdx
new file mode 100644
index 00000000..395688a8
--- /dev/null
+++ b/calls/android/overview.mdx
@@ -0,0 +1,106 @@
+---
+title: "Calls SDK"
+sidebarTitle: "Overview"
+---
+
+The CometChat Calls SDK enables real-time voice and video calling capabilities in your Android application. Built on top of WebRTC, it provides a complete calling solution with built-in UI components and extensive customization options.
+
+
+**Faster Integration with UI Kits**
+
+If you're using CometChat UI Kits, voice and video calling can be quickly integrated:
+- Incoming & outgoing call screens
+- Call buttons with one-tap calling
+- Call logs with history
+
+👉 [Android UI Kit Calling Integration](/ui-kit/android/calling-integration)
+
+Use this Calls SDK directly only if you need custom call UI or advanced control.
+
+
+## Prerequisites
+
+Before integrating the Calls SDK, ensure you have:
+
+1. **CometChat Account**: [Sign up](https://app.cometchat.com/signup) and create an app to get your App ID, Region, and API Key
+2. **CometChat Users**: Users must exist in CometChat to use calling features. For testing, create users via the [Dashboard](https://app.cometchat.com) or [REST API](/rest-api/chat-apis/users/create-user). Authentication is handled by the Calls SDK - see [Authentication](/calls/android/authentication)
+3. **Android Requirements**:
+ - Minimum SDK: API Level 24 (Android 7.0)
+ - AndroidX compatibility
+4. **Permissions**: Camera and microphone permissions for video/audio calls
+
+## Call Flow
+
+```mermaid
+sequenceDiagram
+ participant App
+ participant CometChatCalls
+ participant CallSession
+
+ App->>CometChatCalls: init()
+ App->>CometChatCalls: login()
+ App->>CometChatCalls: generateToken()
+ App->>CometChatCalls: joinSession()
+ CometChatCalls-->>App: CallSession
+ App->>CallSession: Actions (mute, pause, etc.)
+ CallSession-->>App: Event callbacks
+ App->>CallSession: leaveSession()
+```
+
+## Features
+
+
+
+
+ Incoming and outgoing call notifications with accept/reject functionality
+
+
+
+ Tile and Spotlight view modes for different call scenarios
+
+
+
+ Switch between speaker, earpiece, Bluetooth, and headphones
+
+
+
+ Record call sessions for later playback
+
+
+
+ Retrieve call history and details
+
+
+
+ Mute, pin, and manage call participants
+
+
+
+ View screen shares from web participants
+
+
+
+ Continue calls while using other apps
+
+
+
+ Signal to get attention during calls
+
+
+
+ Automatic session termination when alone in a call
+
+
+
+
+## Architecture
+
+The SDK is organized around these core components:
+
+| Component | Description |
+|-----------|-------------|
+| `CometChatCalls` | Main entry point for SDK initialization, authentication, and session management |
+| `CallAppSettings` | Configuration for SDK initialization (App ID, Region) |
+| `SessionSettings` | Configuration for individual call sessions |
+| `CallSession` | Singleton that manages the active call and provides control methods |
+| `Listeners` | Event interfaces for session, participant, media, and UI events |
diff --git a/calls/android/participant-actions.mdx b/calls/android/participant-actions.mdx
new file mode 100644
index 00000000..10e6c3a9
--- /dev/null
+++ b/calls/android/participant-actions.mdx
@@ -0,0 +1,353 @@
+---
+title: "Participant Actions"
+sidebarTitle: "Participant Actions"
+---
+
+Manage other participants during an active call session. These methods allow you to mute participants, pause their video, and pin/unpin them in the call layout.
+
+## Prerequisites
+
+- An active [call session](/calls/android/join-session)
+- Access to the `CallSession` instance
+- Appropriate permissions (typically host/moderator privileges)
+
+## Get CallSession Instance
+
+Participant action methods are called on the `CallSession` singleton:
+
+
+
+```kotlin
+val callSession = CallSession.getInstance()
+```
+
+
+```java
+CallSession callSession = CallSession.getInstance();
+```
+
+
+
+---
+
+## Mute Participant
+
+Mute a specific participant's audio. This prevents other participants from hearing them.
+
+
+
+```kotlin
+val participantId = "participant_uid"
+callSession.muteParticipant(participantId)
+```
+
+
+```java
+String participantId = "participant_uid";
+callSession.muteParticipant(participantId);
+```
+
+
+
+| Parameter | Type | Description |
+|-----------|------|-------------|
+| `participantId` | String | The unique identifier of the participant to mute |
+
+
+When a participant is muted, all participants receive the `onParticipantAudioMuted(Participant)` callback on their `ParticipantEventListener`.
+
+
+---
+
+## Pause Participant Video
+
+Pause a specific participant's video feed. Other participants will see a placeholder instead of their video.
+
+
+
+```kotlin
+val participantId = "participant_uid"
+callSession.pauseParticipantVideo(participantId)
+```
+
+
+```java
+String participantId = "participant_uid";
+callSession.pauseParticipantVideo(participantId);
+```
+
+
+
+| Parameter | Type | Description |
+|-----------|------|-------------|
+| `participantId` | String | The unique identifier of the participant whose video to pause |
+
+
+When a participant's video is paused, all participants receive the `onParticipantVideoPaused(Participant)` callback on their `ParticipantEventListener`.
+
+
+---
+
+## Pin Participant
+
+Pin a participant to keep them prominently displayed in the call layout, regardless of who is speaking.
+
+
+
+```kotlin
+callSession.pinParticipant()
+```
+
+
+```java
+callSession.pinParticipant();
+```
+
+
+
+
+Pinning is particularly useful in Spotlight layout mode where you want to keep a specific participant in focus.
+
+
+---
+
+## Unpin Participant
+
+Remove the pin from a participant, returning to the default layout behavior.
+
+
+
+```kotlin
+callSession.unPinParticipant()
+```
+
+
+```java
+callSession.unPinParticipant();
+```
+
+
+
+---
+
+## Listen for Participant Events
+
+Register a `ParticipantEventListener` to receive callbacks when participant states change:
+
+
+
+```kotlin
+callSession.addParticipantEventListener(this, object : ParticipantEventListener {
+ override fun onParticipantJoined(participant: Participant) {
+ Log.d(TAG, "${participant.name} joined the call")
+ }
+
+ override fun onParticipantLeft(participant: Participant) {
+ Log.d(TAG, "${participant.name} left the call")
+ }
+
+ override fun onParticipantAudioMuted(participant: Participant) {
+ Log.d(TAG, "${participant.name} was muted")
+ }
+
+ override fun onParticipantAudioUnmuted(participant: Participant) {
+ Log.d(TAG, "${participant.name} was unmuted")
+ }
+
+ override fun onParticipantVideoPaused(participant: Participant) {
+ Log.d(TAG, "${participant.name}'s video was paused")
+ }
+
+ override fun onParticipantVideoResumed(participant: Participant) {
+ Log.d(TAG, "${participant.name}'s video was resumed")
+ }
+
+ override fun onParticipantListChanged(participants: List) {
+ Log.d(TAG, "Participant list updated: ${participants.size} participants")
+ // Update your participant list UI
+ }
+
+ override fun onDominantSpeakerChanged(participant: Participant) {
+ Log.d(TAG, "Dominant speaker: ${participant.name}")
+ }
+
+ // Other callbacks...
+ override fun onParticipantHandRaised(participant: Participant) {}
+ override fun onParticipantHandLowered(participant: Participant) {}
+ override fun onParticipantStartedScreenShare(participant: Participant) {}
+ override fun onParticipantStoppedScreenShare(participant: Participant) {}
+ override fun onParticipantStartedRecording(participant: Participant) {}
+ override fun onParticipantStoppedRecording(participant: Participant) {}
+})
+```
+
+
+```java
+callSession.addParticipantEventListener(this, new ParticipantEventListener() {
+ @Override
+ public void onParticipantJoined(Participant participant) {
+ Log.d(TAG, participant.getName() + " joined the call");
+ }
+
+ @Override
+ public void onParticipantLeft(Participant participant) {
+ Log.d(TAG, participant.getName() + " left the call");
+ }
+
+ @Override
+ public void onParticipantAudioMuted(Participant participant) {
+ Log.d(TAG, participant.getName() + " was muted");
+ }
+
+ @Override
+ public void onParticipantAudioUnmuted(Participant participant) {
+ Log.d(TAG, participant.getName() + " was unmuted");
+ }
+
+ @Override
+ public void onParticipantVideoPaused(Participant participant) {
+ Log.d(TAG, participant.getName() + "'s video was paused");
+ }
+
+ @Override
+ public void onParticipantVideoResumed(Participant participant) {
+ Log.d(TAG, participant.getName() + "'s video was resumed");
+ }
+
+ @Override
+ public void onParticipantListChanged(List participants) {
+ Log.d(TAG, "Participant list updated: " + participants.size() + " participants");
+ // Update your participant list UI
+ }
+
+ @Override
+ public void onDominantSpeakerChanged(Participant participant) {
+ Log.d(TAG, "Dominant speaker: " + participant.getName());
+ }
+
+ // Other callbacks...
+ @Override
+ public void onParticipantHandRaised(Participant participant) {}
+ @Override
+ public void onParticipantHandLowered(Participant participant) {}
+ @Override
+ public void onParticipantStartedScreenShare(Participant participant) {}
+ @Override
+ public void onParticipantStoppedScreenShare(Participant participant) {}
+ @Override
+ public void onParticipantStartedRecording(Participant participant) {}
+ @Override
+ public void onParticipantStoppedRecording(Participant participant) {}
+});
+```
+
+
+
+---
+
+## Participant Object
+
+The `Participant` object contains information about a call participant:
+
+| Property | Type | Description |
+|----------|------|-------------|
+| `uid` | String | Unique identifier of the participant |
+| `name` | String | Display name of the participant |
+| `avatar` | String | URL of the participant's avatar image |
+| `isAudioMuted` | Boolean | Whether the participant's audio is muted |
+| `isVideoPaused` | Boolean | Whether the participant's video is paused |
+
+---
+
+## Show/Hide Participant List Button
+
+Control the visibility of the participant list button in the call UI:
+
+
+
+```kotlin
+val sessionSettings = CometChatCalls.SessionSettingsBuilder()
+ .hideParticipantListButton(false) // Show the participant list button
+ .build()
+```
+
+
+```java
+SessionSettings sessionSettings = new CometChatCalls.SessionSettingsBuilder()
+ .hideParticipantListButton(false) // Show the participant list button
+ .build();
+```
+
+
+
+---
+
+## Participant List Button Click Listener
+
+Listen for when users tap the participant list button:
+
+
+
+```kotlin
+callSession.addButtonClickListener(this, object : ButtonClickListener {
+ override fun onParticipantListButtonClicked() {
+ Log.d(TAG, "Participant list button clicked")
+ // Show custom participant list UI if needed
+ }
+
+ // Other ButtonClickListener callbacks...
+ override fun onLeaveSessionButtonClicked() {}
+ override fun onRaiseHandButtonClicked() {}
+ override fun onShareInviteButtonClicked() {}
+ override fun onChangeLayoutButtonClicked() {}
+ override fun onToggleAudioButtonClicked() {}
+ override fun onToggleVideoButtonClicked() {}
+ override fun onSwitchCameraButtonClicked() {}
+ override fun onChatButtonClicked() {}
+ override fun onRecordingToggleButtonClicked() {}
+})
+```
+
+
+```java
+callSession.addButtonClickListener(this, new ButtonClickListener() {
+ @Override
+ public void onParticipantListButtonClicked() {
+ Log.d(TAG, "Participant list button clicked");
+ // Show custom participant list UI if needed
+ }
+
+ // Other ButtonClickListener callbacks...
+ @Override
+ public void onLeaveSessionButtonClicked() {}
+ @Override
+ public void onRaiseHandButtonClicked() {}
+ @Override
+ public void onShareInviteButtonClicked() {}
+ @Override
+ public void onChangeLayoutButtonClicked() {}
+ @Override
+ public void onToggleAudioButtonClicked() {}
+ @Override
+ public void onToggleVideoButtonClicked() {}
+ @Override
+ public void onSwitchCameraButtonClicked() {}
+ @Override
+ public void onChatButtonClicked() {}
+ @Override
+ public void onRecordingToggleButtonClicked() {}
+});
+```
+
+
+
+## Next Steps
+
+
+
+ Control call layout and UI elements
+
+
+ Handle all participant events
+
+
diff --git a/calls/android/participant-event-listener.mdx b/calls/android/participant-event-listener.mdx
new file mode 100644
index 00000000..ae878872
--- /dev/null
+++ b/calls/android/participant-event-listener.mdx
@@ -0,0 +1,758 @@
+---
+title: "Participant Event Listener"
+sidebarTitle: "Participant Event Listener"
+---
+
+Monitor participant activities with `ParticipantEventListener`. This listener provides callbacks for participant join/leave events, audio/video state changes, hand raise actions, screen sharing, recording, and more.
+
+## Prerequisites
+
+- An active [call session](/calls/android/join-session)
+- Access to the `CallSession` instance
+
+## Register Listener
+
+Register a `ParticipantEventListener` to receive participant event callbacks:
+
+
+
+```kotlin
+val callSession = CallSession.getInstance()
+
+callSession.addParticipantEventListener(this, object : ParticipantEventListener() {
+ override fun onParticipantJoined(participant: Participant) {
+ Log.d(TAG, "${participant.name} joined the call")
+ }
+
+ override fun onParticipantLeft(participant: Participant) {
+ Log.d(TAG, "${participant.name} left the call")
+ }
+
+ override fun onParticipantListChanged(participants: List) {
+ Log.d(TAG, "Participant list updated: ${participants.size} participants")
+ }
+
+ // Additional callbacks...
+})
+```
+
+
+```java
+CallSession callSession = CallSession.getInstance();
+
+callSession.addParticipantEventListener(this, new ParticipantEventListener() {
+ @Override
+ public void onParticipantJoined(Participant participant) {
+ Log.d(TAG, participant.getName() + " joined the call");
+ }
+
+ @Override
+ public void onParticipantLeft(Participant participant) {
+ Log.d(TAG, participant.getName() + " left the call");
+ }
+
+ @Override
+ public void onParticipantListChanged(List participants) {
+ Log.d(TAG, "Participant list updated: " + participants.size() + " participants");
+ }
+
+ // Additional callbacks...
+});
+```
+
+
+
+
+The listener is automatically removed when the `LifecycleOwner` (Activity/Fragment) is destroyed, preventing memory leaks.
+
+
+---
+
+## Participant Object
+
+The `Participant` object contains information about a call participant:
+
+| Property | Type | Description |
+|----------|------|-------------|
+| `uid` | `String` | Unique identifier of the participant |
+| `name` | `String` | Display name of the participant |
+| `avatar` | `String` | Avatar URL of the participant |
+| `isAudioMuted` | `Boolean` | Whether audio is muted |
+| `isVideoPaused` | `Boolean` | Whether video is paused |
+| `isHandRaised` | `Boolean` | Whether hand is raised |
+| `isScreenSharing` | `Boolean` | Whether screen is being shared |
+
+---
+
+## Callbacks
+
+### onParticipantJoined
+
+Triggered when a new participant joins the call.
+
+
+
+```kotlin
+override fun onParticipantJoined(participant: Participant) {
+ Log.d(TAG, "${participant.name} joined the call")
+ // Show join notification
+ // Update participant grid
+}
+```
+
+
+```java
+@Override
+public void onParticipantJoined(Participant participant) {
+ Log.d(TAG, participant.getName() + " joined the call");
+ // Show join notification
+ // Update participant grid
+}
+```
+
+
+
+**Use Cases:**
+- Display join notification/toast
+- Update participant count
+- Play join sound
+
+---
+
+### onParticipantLeft
+
+Triggered when a participant leaves the call.
+
+
+
+```kotlin
+override fun onParticipantLeft(participant: Participant) {
+ Log.d(TAG, "${participant.name} left the call")
+ // Show leave notification
+ // Update participant grid
+}
+```
+
+
+```java
+@Override
+public void onParticipantLeft(Participant participant) {
+ Log.d(TAG, participant.getName() + " left the call");
+ // Show leave notification
+ // Update participant grid
+}
+```
+
+
+
+**Use Cases:**
+- Display leave notification
+- Update participant count
+- Play leave sound
+
+---
+
+### onParticipantListChanged
+
+Triggered when the participant list changes (join, leave, or any update).
+
+
+
+```kotlin
+override fun onParticipantListChanged(participants: List) {
+ Log.d(TAG, "Participants: ${participants.size}")
+ // Update participant list UI
+ updateParticipantGrid(participants)
+}
+```
+
+
+```java
+@Override
+public void onParticipantListChanged(List participants) {
+ Log.d(TAG, "Participants: " + participants.size());
+ // Update participant list UI
+ updateParticipantGrid(participants);
+}
+```
+
+
+
+**Use Cases:**
+- Refresh participant list/grid
+- Update participant count badge
+- Sync local state with server
+
+---
+
+### onParticipantAudioMuted
+
+Triggered when a participant mutes their audio.
+
+
+
+```kotlin
+override fun onParticipantAudioMuted(participant: Participant) {
+ Log.d(TAG, "${participant.name} muted their audio")
+ // Show muted indicator on participant tile
+}
+```
+
+
+```java
+@Override
+public void onParticipantAudioMuted(Participant participant) {
+ Log.d(TAG, participant.getName() + " muted their audio");
+ // Show muted indicator on participant tile
+}
+```
+
+
+
+---
+
+### onParticipantAudioUnmuted
+
+Triggered when a participant unmutes their audio.
+
+
+
+```kotlin
+override fun onParticipantAudioUnmuted(participant: Participant) {
+ Log.d(TAG, "${participant.name} unmuted their audio")
+ // Hide muted indicator on participant tile
+}
+```
+
+
+```java
+@Override
+public void onParticipantAudioUnmuted(Participant participant) {
+ Log.d(TAG, participant.getName() + " unmuted their audio");
+ // Hide muted indicator on participant tile
+}
+```
+
+
+
+---
+
+### onParticipantVideoPaused
+
+Triggered when a participant pauses their video.
+
+
+
+```kotlin
+override fun onParticipantVideoPaused(participant: Participant) {
+ Log.d(TAG, "${participant.name} paused their video")
+ // Show avatar or placeholder instead of video
+}
+```
+
+
+```java
+@Override
+public void onParticipantVideoPaused(Participant participant) {
+ Log.d(TAG, participant.getName() + " paused their video");
+ // Show avatar or placeholder instead of video
+}
+```
+
+
+
+---
+
+### onParticipantVideoResumed
+
+Triggered when a participant resumes their video.
+
+
+
+```kotlin
+override fun onParticipantVideoResumed(participant: Participant) {
+ Log.d(TAG, "${participant.name} resumed their video")
+ // Show video stream
+}
+```
+
+
+```java
+@Override
+public void onParticipantVideoResumed(Participant participant) {
+ Log.d(TAG, participant.getName() + " resumed their video");
+ // Show video stream
+}
+```
+
+
+
+---
+
+### onParticipantHandRaised
+
+Triggered when a participant raises their hand.
+
+
+
+```kotlin
+override fun onParticipantHandRaised(participant: Participant) {
+ Log.d(TAG, "${participant.name} raised their hand")
+ // Show hand raised indicator
+ // Optionally play notification sound
+}
+```
+
+
+```java
+@Override
+public void onParticipantHandRaised(Participant participant) {
+ Log.d(TAG, participant.getName() + " raised their hand");
+ // Show hand raised indicator
+ // Optionally play notification sound
+}
+```
+
+
+
+---
+
+### onParticipantHandLowered
+
+Triggered when a participant lowers their hand.
+
+
+
+```kotlin
+override fun onParticipantHandLowered(participant: Participant) {
+ Log.d(TAG, "${participant.name} lowered their hand")
+ // Hide hand raised indicator
+}
+```
+
+
+```java
+@Override
+public void onParticipantHandLowered(Participant participant) {
+ Log.d(TAG, participant.getName() + " lowered their hand");
+ // Hide hand raised indicator
+}
+```
+
+
+
+---
+
+### onParticipantStartedScreenShare
+
+Triggered when a participant starts sharing their screen.
+
+
+
+```kotlin
+override fun onParticipantStartedScreenShare(participant: Participant) {
+ Log.d(TAG, "${participant.name} started screen sharing")
+ // Switch to screen share view
+ // Show screen share indicator
+}
+```
+
+
+```java
+@Override
+public void onParticipantStartedScreenShare(Participant participant) {
+ Log.d(TAG, participant.getName() + " started screen sharing");
+ // Switch to screen share view
+ // Show screen share indicator
+}
+```
+
+
+
+---
+
+### onParticipantStoppedScreenShare
+
+Triggered when a participant stops sharing their screen.
+
+
+
+```kotlin
+override fun onParticipantStoppedScreenShare(participant: Participant) {
+ Log.d(TAG, "${participant.name} stopped screen sharing")
+ // Switch back to normal view
+ // Hide screen share indicator
+}
+```
+
+
+```java
+@Override
+public void onParticipantStoppedScreenShare(Participant participant) {
+ Log.d(TAG, participant.getName() + " stopped screen sharing");
+ // Switch back to normal view
+ // Hide screen share indicator
+}
+```
+
+
+
+---
+
+### onParticipantStartedRecording
+
+Triggered when a participant starts recording the session.
+
+
+
+```kotlin
+override fun onParticipantStartedRecording(participant: Participant) {
+ Log.d(TAG, "${participant.name} started recording")
+ // Show recording indicator
+ // Notify other participants
+}
+```
+
+
+```java
+@Override
+public void onParticipantStartedRecording(Participant participant) {
+ Log.d(TAG, participant.getName() + " started recording");
+ // Show recording indicator
+ // Notify other participants
+}
+```
+
+
+
+---
+
+### onParticipantStoppedRecording
+
+Triggered when a participant stops recording the session.
+
+
+
+```kotlin
+override fun onParticipantStoppedRecording(participant: Participant) {
+ Log.d(TAG, "${participant.name} stopped recording")
+ // Hide recording indicator
+}
+```
+
+
+```java
+@Override
+public void onParticipantStoppedRecording(Participant participant) {
+ Log.d(TAG, participant.getName() + " stopped recording");
+ // Hide recording indicator
+}
+```
+
+
+
+---
+
+### onDominantSpeakerChanged
+
+Triggered when the dominant speaker changes (the participant currently speaking the loudest).
+
+
+
+```kotlin
+override fun onDominantSpeakerChanged(participant: Participant) {
+ Log.d(TAG, "${participant.name} is now the dominant speaker")
+ // Highlight the dominant speaker's tile
+ // Auto-focus on dominant speaker in spotlight mode
+}
+```
+
+
+```java
+@Override
+public void onDominantSpeakerChanged(Participant participant) {
+ Log.d(TAG, participant.getName() + " is now the dominant speaker");
+ // Highlight the dominant speaker's tile
+ // Auto-focus on dominant speaker in spotlight mode
+}
+```
+
+
+
+**Use Cases:**
+- Highlight active speaker in UI
+- Auto-switch spotlight to dominant speaker
+- Show speaking indicator animation
+
+---
+
+## Complete Example
+
+Here's a complete example handling all participant events:
+
+
+
+```kotlin
+class CallActivity : AppCompatActivity() {
+ private lateinit var callSession: CallSession
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_call)
+
+ callSession = CallSession.getInstance()
+ setupParticipantEventListener()
+ }
+
+ private fun setupParticipantEventListener() {
+ callSession.addParticipantEventListener(this, object : ParticipantEventListener() {
+ override fun onParticipantJoined(participant: Participant) {
+ runOnUiThread {
+ showToast("${participant.name} joined")
+ }
+ }
+
+ override fun onParticipantLeft(participant: Participant) {
+ runOnUiThread {
+ showToast("${participant.name} left")
+ }
+ }
+
+ override fun onParticipantListChanged(participants: List) {
+ runOnUiThread {
+ updateParticipantCount(participants.size)
+ }
+ }
+
+ override fun onParticipantAudioMuted(participant: Participant) {
+ runOnUiThread {
+ updateParticipantAudioState(participant.uid, isMuted = true)
+ }
+ }
+
+ override fun onParticipantAudioUnmuted(participant: Participant) {
+ runOnUiThread {
+ updateParticipantAudioState(participant.uid, isMuted = false)
+ }
+ }
+
+ override fun onParticipantVideoPaused(participant: Participant) {
+ runOnUiThread {
+ updateParticipantVideoState(participant.uid, isPaused = true)
+ }
+ }
+
+ override fun onParticipantVideoResumed(participant: Participant) {
+ runOnUiThread {
+ updateParticipantVideoState(participant.uid, isPaused = false)
+ }
+ }
+
+ override fun onParticipantHandRaised(participant: Participant) {
+ runOnUiThread {
+ showHandRaisedIndicator(participant.uid, isRaised = true)
+ }
+ }
+
+ override fun onParticipantHandLowered(participant: Participant) {
+ runOnUiThread {
+ showHandRaisedIndicator(participant.uid, isRaised = false)
+ }
+ }
+
+ override fun onParticipantStartedScreenShare(participant: Participant) {
+ runOnUiThread {
+ showScreenShareView(participant)
+ }
+ }
+
+ override fun onParticipantStoppedScreenShare(participant: Participant) {
+ runOnUiThread {
+ hideScreenShareView()
+ }
+ }
+
+ override fun onParticipantStartedRecording(participant: Participant) {
+ runOnUiThread {
+ showRecordingIndicator(true)
+ }
+ }
+
+ override fun onParticipantStoppedRecording(participant: Participant) {
+ runOnUiThread {
+ showRecordingIndicator(false)
+ }
+ }
+
+ override fun onDominantSpeakerChanged(participant: Participant) {
+ runOnUiThread {
+ highlightDominantSpeaker(participant.uid)
+ }
+ }
+ })
+ }
+
+ // UI helper methods
+ private fun showToast(message: String) {
+ Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
+ }
+
+ private fun updateParticipantCount(count: Int) { /* ... */ }
+ private fun updateParticipantAudioState(uid: String, isMuted: Boolean) { /* ... */ }
+ private fun updateParticipantVideoState(uid: String, isPaused: Boolean) { /* ... */ }
+ private fun showHandRaisedIndicator(uid: String, isRaised: Boolean) { /* ... */ }
+ private fun showScreenShareView(participant: Participant) { /* ... */ }
+ private fun hideScreenShareView() { /* ... */ }
+ private fun showRecordingIndicator(show: Boolean) { /* ... */ }
+ private fun highlightDominantSpeaker(uid: String) { /* ... */ }
+
+ companion object {
+ private const val TAG = "CallActivity"
+ }
+}
+```
+
+
+```java
+public class CallActivity extends AppCompatActivity {
+ private static final String TAG = "CallActivity";
+ private CallSession callSession;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_call);
+
+ callSession = CallSession.getInstance();
+ setupParticipantEventListener();
+ }
+
+ private void setupParticipantEventListener() {
+ callSession.addParticipantEventListener(this, new ParticipantEventListener() {
+ @Override
+ public void onParticipantJoined(Participant participant) {
+ runOnUiThread(() -> showToast(participant.getName() + " joined"));
+ }
+
+ @Override
+ public void onParticipantLeft(Participant participant) {
+ runOnUiThread(() -> showToast(participant.getName() + " left"));
+ }
+
+ @Override
+ public void onParticipantListChanged(List participants) {
+ runOnUiThread(() -> updateParticipantCount(participants.size()));
+ }
+
+ @Override
+ public void onParticipantAudioMuted(Participant participant) {
+ runOnUiThread(() ->
+ updateParticipantAudioState(participant.getUid(), true));
+ }
+
+ @Override
+ public void onParticipantAudioUnmuted(Participant participant) {
+ runOnUiThread(() ->
+ updateParticipantAudioState(participant.getUid(), false));
+ }
+
+ @Override
+ public void onParticipantVideoPaused(Participant participant) {
+ runOnUiThread(() ->
+ updateParticipantVideoState(participant.getUid(), true));
+ }
+
+ @Override
+ public void onParticipantVideoResumed(Participant participant) {
+ runOnUiThread(() ->
+ updateParticipantVideoState(participant.getUid(), false));
+ }
+
+ @Override
+ public void onParticipantHandRaised(Participant participant) {
+ runOnUiThread(() ->
+ showHandRaisedIndicator(participant.getUid(), true));
+ }
+
+ @Override
+ public void onParticipantHandLowered(Participant participant) {
+ runOnUiThread(() ->
+ showHandRaisedIndicator(participant.getUid(), false));
+ }
+
+ @Override
+ public void onParticipantStartedScreenShare(Participant participant) {
+ runOnUiThread(() -> showScreenShareView(participant));
+ }
+
+ @Override
+ public void onParticipantStoppedScreenShare(Participant participant) {
+ runOnUiThread(() -> hideScreenShareView());
+ }
+
+ @Override
+ public void onParticipantStartedRecording(Participant participant) {
+ runOnUiThread(() -> showRecordingIndicator(true));
+ }
+
+ @Override
+ public void onParticipantStoppedRecording(Participant participant) {
+ runOnUiThread(() -> showRecordingIndicator(false));
+ }
+
+ @Override
+ public void onDominantSpeakerChanged(Participant participant) {
+ runOnUiThread(() -> highlightDominantSpeaker(participant.getUid()));
+ }
+ });
+ }
+
+ // UI helper methods
+ private void showToast(String message) {
+ Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
+ }
+
+ private void updateParticipantCount(int count) { /* ... */ }
+ private void updateParticipantAudioState(String uid, boolean isMuted) { /* ... */ }
+ private void updateParticipantVideoState(String uid, boolean isPaused) { /* ... */ }
+ private void showHandRaisedIndicator(String uid, boolean isRaised) { /* ... */ }
+ private void showScreenShareView(Participant participant) { /* ... */ }
+ private void hideScreenShareView() { /* ... */ }
+ private void showRecordingIndicator(boolean show) { /* ... */ }
+ private void highlightDominantSpeaker(String uid) { /* ... */ }
+}
+```
+
+
+
+---
+
+## Callbacks Summary
+
+| Callback | Parameter | Description |
+|----------|-----------|-------------|
+| `onParticipantJoined` | `Participant` | A participant joined the call |
+| `onParticipantLeft` | `Participant` | A participant left the call |
+| `onParticipantListChanged` | `List` | Participant list was updated |
+| `onParticipantAudioMuted` | `Participant` | A participant muted their audio |
+| `onParticipantAudioUnmuted` | `Participant` | A participant unmuted their audio |
+| `onParticipantVideoPaused` | `Participant` | A participant paused their video |
+| `onParticipantVideoResumed` | `Participant` | A participant resumed their video |
+| `onParticipantHandRaised` | `Participant` | A participant raised their hand |
+| `onParticipantHandLowered` | `Participant` | A participant lowered their hand |
+| `onParticipantStartedScreenShare` | `Participant` | A participant started screen sharing |
+| `onParticipantStoppedScreenShare` | `Participant` | A participant stopped screen sharing |
+| `onParticipantStartedRecording` | `Participant` | A participant started recording |
+| `onParticipantStoppedRecording` | `Participant` | A participant stopped recording |
+| `onDominantSpeakerChanged` | `Participant` | The dominant speaker changed |
+
+## Next Steps
+
+
+
+ Handle local media state changes
+
+
+ Control other participants
+
+
diff --git a/calls/android/participant-management.mdx b/calls/android/participant-management.mdx
new file mode 100644
index 00000000..a06256b1
--- /dev/null
+++ b/calls/android/participant-management.mdx
@@ -0,0 +1,238 @@
+---
+title: "Participant Management"
+sidebarTitle: "Participant Management"
+---
+
+Manage participants during a call with actions like muting, pausing video, and pinning. These features help maintain order in group calls and highlight important speakers.
+
+
+By default, all participants who join a call have moderator access and can perform these actions. Implementing role-based moderation (e.g., restricting actions to hosts only) is the responsibility of the app developer based on their use case.
+
+
+## Mute a Participant
+
+Mute a specific participant's audio. This affects the participant for all users in the call.
+
+
+
+```kotlin
+val callSession = CallSession.getInstance()
+callSession.muteParticipant(participant.uid)
+```
+
+
+```java
+CallSession callSession = CallSession.getInstance();
+callSession.muteParticipant(participant.getUid());
+```
+
+
+
+## Pause Participant Video
+
+Pause a specific participant's video. This affects the participant for all users in the call.
+
+
+
+```kotlin
+val callSession = CallSession.getInstance()
+callSession.pauseParticipantVideo(participant.uid)
+```
+
+
+```java
+CallSession callSession = CallSession.getInstance();
+callSession.pauseParticipantVideo(participant.getUid());
+```
+
+
+
+## Pin a Participant
+
+Pin a participant to keep them prominently displayed regardless of who is speaking. Useful for keeping focus on a presenter or important speaker.
+
+
+
+```kotlin
+val callSession = CallSession.getInstance()
+
+// Pin a participant
+callSession.pinParticipant(participant.uid)
+
+// Unpin (returns to automatic speaker highlighting)
+callSession.unPinParticipant()
+```
+
+
+```java
+CallSession callSession = CallSession.getInstance();
+
+// Pin a participant
+callSession.pinParticipant(participant.getUid());
+
+// Unpin (returns to automatic speaker highlighting)
+callSession.unPinParticipant();
+```
+
+
+
+
+Pinning a participant only affects your local view. Other participants can pin different users independently.
+
+
+## Listen for Participant Events
+
+Monitor participant state changes using `ParticipantEventListener`:
+
+
+
+```kotlin
+val callSession = CallSession.getInstance()
+
+callSession.addParticipantEventListener(this, object : ParticipantEventListener() {
+ override fun onParticipantJoined(participant: Participant) {
+ Log.d(TAG, "${participant.name} joined the call")
+ updateParticipantList()
+ }
+
+ override fun onParticipantLeft(participant: Participant) {
+ Log.d(TAG, "${participant.name} left the call")
+ updateParticipantList()
+ }
+
+ override fun onParticipantListChanged(participants: List) {
+ Log.d(TAG, "Participant count: ${participants.size}")
+ refreshParticipantList(participants)
+ }
+
+ override fun onParticipantAudioMuted(participant: Participant) {
+ Log.d(TAG, "${participant.name} was muted")
+ updateMuteIndicator(participant, muted = true)
+ }
+
+ override fun onParticipantAudioUnmuted(participant: Participant) {
+ Log.d(TAG, "${participant.name} was unmuted")
+ updateMuteIndicator(participant, muted = false)
+ }
+
+ override fun onParticipantVideoPaused(participant: Participant) {
+ Log.d(TAG, "${participant.name} video paused")
+ showParticipantAvatar(participant)
+ }
+
+ override fun onParticipantVideoResumed(participant: Participant) {
+ Log.d(TAG, "${participant.name} video resumed")
+ showParticipantVideo(participant)
+ }
+
+ override fun onDominantSpeakerChanged(participant: Participant) {
+ Log.d(TAG, "${participant.name} is now speaking")
+ highlightActiveSpeaker(participant)
+ }
+
+ // Other callbacks...
+ override fun onParticipantHandRaised(participant: Participant) {}
+ override fun onParticipantHandLowered(participant: Participant) {}
+ override fun onParticipantStartedScreenShare(participant: Participant) {}
+ override fun onParticipantStoppedScreenShare(participant: Participant) {}
+ override fun onParticipantStartedRecording(participant: Participant) {}
+ override fun onParticipantStoppedRecording(participant: Participant) {}
+})
+```
+
+
+```java
+CallSession callSession = CallSession.getInstance();
+
+callSession.addParticipantEventListener(this, new ParticipantEventListener() {
+ @Override
+ public void onParticipantJoined(Participant participant) {
+ Log.d(TAG, participant.getName() + " joined the call");
+ updateParticipantList();
+ }
+
+ @Override
+ public void onParticipantLeft(Participant participant) {
+ Log.d(TAG, participant.getName() + " left the call");
+ updateParticipantList();
+ }
+
+ @Override
+ public void onParticipantListChanged(List participants) {
+ Log.d(TAG, "Participant count: " + participants.size());
+ refreshParticipantList(participants);
+ }
+
+ @Override
+ public void onParticipantAudioMuted(Participant participant) {
+ Log.d(TAG, participant.getName() + " was muted");
+ updateMuteIndicator(participant, true);
+ }
+
+ @Override
+ public void onParticipantAudioUnmuted(Participant participant) {
+ Log.d(TAG, participant.getName() + " was unmuted");
+ updateMuteIndicator(participant, false);
+ }
+
+ @Override
+ public void onParticipantVideoPaused(Participant participant) {
+ Log.d(TAG, participant.getName() + " video paused");
+ showParticipantAvatar(participant);
+ }
+
+ @Override
+ public void onParticipantVideoResumed(Participant participant) {
+ Log.d(TAG, participant.getName() + " video resumed");
+ showParticipantVideo(participant);
+ }
+
+ @Override
+ public void onDominantSpeakerChanged(Participant participant) {
+ Log.d(TAG, participant.getName() + " is now speaking");
+ highlightActiveSpeaker(participant);
+ }
+
+ // Other callbacks...
+});
+```
+
+
+
+## Participant Object
+
+The `Participant` object contains information about each call participant:
+
+| Property | Type | Description |
+|----------|------|-------------|
+| `uid` | String | Unique identifier (CometChat user ID) |
+| `name` | String | Display name |
+| `avatar` | String | URL of avatar image |
+| `pid` | String | Participant ID for this call session |
+| `role` | String | Role in the call |
+| `audioMuted` | Boolean | Whether audio is muted |
+| `videoPaused` | Boolean | Whether video is paused |
+| `isPinned` | Boolean | Whether pinned in layout |
+| `isPresenting` | Boolean | Whether screen sharing |
+| `raisedHandTimestamp` | Long | Timestamp when hand was raised (0 if not raised) |
+
+## Hide Participant List Button
+
+To hide the participant list button in the call UI:
+
+
+
+```kotlin
+val sessionSettings = CometChatCalls.SessionSettingsBuilder()
+ .hideParticipantListButton(true)
+ .build()
+```
+
+
+```java
+SessionSettings sessionSettings = new CometChatCalls.SessionSettingsBuilder()
+ .hideParticipantListButton(true)
+ .build();
+```
+
+
diff --git a/calls/android/picture-in-picture.mdx b/calls/android/picture-in-picture.mdx
new file mode 100644
index 00000000..492d4187
--- /dev/null
+++ b/calls/android/picture-in-picture.mdx
@@ -0,0 +1,197 @@
+---
+title: "Picture-in-Picture"
+sidebarTitle: "Picture-in-Picture"
+---
+
+Enable Picture-in-Picture (PiP) mode to allow users to continue their call in a floating window while using other apps. PiP provides a seamless multitasking experience during calls.
+
+
+Picture-in-Picture implementation is handled at the app level using Android's PiP APIs. The Calls SDK only adjusts the call UI layout to fit the PiP window - it does not manage the PiP window itself.
+
+
+## How It Works
+
+1. Your app enters PiP mode using Android's `enterPictureInPictureMode()` API
+2. You notify the Calls SDK by calling `enablePictureInPictureLayout()`
+3. The SDK adjusts the call UI to fit the smaller PiP window (hides controls, optimizes layout)
+4. When exiting PiP, call `disablePictureInPictureLayout()` to restore the full UI
+
+## Enable Picture-in-Picture
+
+Enter PiP mode programmatically using the `enablePictureInPictureLayout()` action:
+
+
+
+```kotlin
+val callSession = CallSession.getInstance()
+callSession.enablePictureInPictureLayout()
+```
+
+
+```java
+CallSession callSession = CallSession.getInstance();
+callSession.enablePictureInPictureLayout();
+```
+
+
+
+## Disable Picture-in-Picture
+
+Exit PiP mode and return to the full-screen call interface:
+
+
+
+```kotlin
+val callSession = CallSession.getInstance()
+callSession.disablePictureInPictureLayout()
+```
+
+
+```java
+CallSession callSession = CallSession.getInstance();
+callSession.disablePictureInPictureLayout();
+```
+
+
+
+## Listen for PiP Events
+
+Monitor PiP mode transitions using `LayoutListener` to update your UI accordingly:
+
+
+
+```kotlin
+val callSession = CallSession.getInstance()
+
+callSession.addLayoutListener(this, object : LayoutListener() {
+ override fun onPictureInPictureLayoutEnabled() {
+ Log.d(TAG, "Entered PiP mode")
+ // Hide custom overlays or controls
+ hideCustomControls()
+ }
+
+ override fun onPictureInPictureLayoutDisabled() {
+ Log.d(TAG, "Exited PiP mode")
+ // Show custom overlays or controls
+ showCustomControls()
+ }
+
+ override fun onCallLayoutChanged(layoutType: LayoutType) {}
+ override fun onParticipantListVisible() {}
+ override fun onParticipantListHidden() {}
+})
+```
+
+
+```java
+CallSession callSession = CallSession.getInstance();
+
+callSession.addLayoutListener(this, new LayoutListener() {
+ @Override
+ public void onPictureInPictureLayoutEnabled() {
+ Log.d(TAG, "Entered PiP mode");
+ // Hide custom overlays or controls
+ hideCustomControls();
+ }
+
+ @Override
+ public void onPictureInPictureLayoutDisabled() {
+ Log.d(TAG, "Exited PiP mode");
+ // Show custom overlays or controls
+ showCustomControls();
+ }
+
+ @Override public void onCallLayoutChanged(LayoutType layoutType) {}
+ @Override public void onParticipantListVisible() {}
+ @Override public void onParticipantListHidden() {}
+});
+```
+
+
+
+## Auto-Enter PiP on Home Press
+
+To automatically enter PiP mode when the user presses the home button or navigates away, override `onUserLeaveHint()` in your Activity:
+
+
+
+```kotlin
+class CallActivity : AppCompatActivity() {
+
+ override fun onUserLeaveHint() {
+ super.onUserLeaveHint()
+ if (CallSession.getInstance().isSessionActive()) {
+ // Enter Android PiP mode
+ enterPictureInPictureMode(
+ PictureInPictureParams.Builder()
+ .setAspectRatio(Rational(16, 9))
+ .build()
+ )
+ // Notify SDK to adjust layout
+ CallSession.getInstance().enablePictureInPictureLayout()
+ }
+ }
+
+ override fun onPictureInPictureModeChanged(isInPiPMode: Boolean, config: Configuration) {
+ super.onPictureInPictureModeChanged(isInPiPMode, config)
+ if (isInPiPMode) {
+ CallSession.getInstance().enablePictureInPictureLayout()
+ } else {
+ CallSession.getInstance().disablePictureInPictureLayout()
+ }
+ }
+}
+```
+
+
+```java
+public class CallActivity extends AppCompatActivity {
+
+ @Override
+ protected void onUserLeaveHint() {
+ super.onUserLeaveHint();
+ if (CallSession.getInstance().isSessionActive()) {
+ // Enter Android PiP mode
+ enterPictureInPictureMode(
+ new PictureInPictureParams.Builder()
+ .setAspectRatio(new Rational(16, 9))
+ .build()
+ );
+ // Notify SDK to adjust layout
+ CallSession.getInstance().enablePictureInPictureLayout();
+ }
+ }
+
+ @Override
+ public void onPictureInPictureModeChanged(boolean isInPiPMode, Configuration config) {
+ super.onPictureInPictureModeChanged(isInPiPMode, config);
+ if (isInPiPMode) {
+ CallSession.getInstance().enablePictureInPictureLayout();
+ } else {
+ CallSession.getInstance().disablePictureInPictureLayout();
+ }
+ }
+}
+```
+
+
+
+## Android Manifest Configuration
+
+Add PiP support to your Activity in `AndroidManifest.xml`:
+
+```xml
+
+```
+
+| Attribute | Description |
+|-----------|-------------|
+| `supportsPictureInPicture` | Enables PiP support for the activity |
+| `configChanges` | Prevents activity restart during PiP transitions |
+
+
+PiP mode is only available on Android 8.0 (API level 26) and higher. On older devices, the PiP actions will have no effect.
+
diff --git a/calls/android/raise-hand.mdx b/calls/android/raise-hand.mdx
new file mode 100644
index 00000000..c51bf596
--- /dev/null
+++ b/calls/android/raise-hand.mdx
@@ -0,0 +1,167 @@
+---
+title: "Raise Hand"
+sidebarTitle: "Raise Hand"
+---
+
+Allow participants to raise their hand to get attention during calls. This feature is useful for large meetings, webinars, or any scenario where participants need to signal they want to speak.
+
+## Raise Hand
+
+Signal that you want to speak or get attention:
+
+
+
+```kotlin
+val callSession = CallSession.getInstance()
+callSession.raiseHand()
+```
+
+
+```java
+CallSession callSession = CallSession.getInstance();
+callSession.raiseHand();
+```
+
+
+
+## Lower Hand
+
+Remove the raised hand indicator:
+
+
+
+```kotlin
+val callSession = CallSession.getInstance()
+callSession.lowerHand()
+```
+
+
+```java
+CallSession callSession = CallSession.getInstance();
+callSession.lowerHand();
+```
+
+
+
+## Listen for Raise Hand Events
+
+Monitor when participants raise or lower their hands using `ParticipantEventListener`:
+
+
+
+```kotlin
+val callSession = CallSession.getInstance()
+
+callSession.addParticipantEventListener(this, object : ParticipantEventListener() {
+ override fun onParticipantHandRaised(participant: Participant) {
+ Log.d(TAG, "${participant.name} raised their hand")
+ // Show notification or visual indicator
+ showHandRaisedNotification(participant)
+ }
+
+ override fun onParticipantHandLowered(participant: Participant) {
+ Log.d(TAG, "${participant.name} lowered their hand")
+ // Remove notification or visual indicator
+ hideHandRaisedIndicator(participant)
+ }
+
+ // Other callbacks...
+ override fun onParticipantJoined(participant: Participant) {}
+ override fun onParticipantLeft(participant: Participant) {}
+ override fun onParticipantListChanged(participants: List) {}
+ override fun onParticipantAudioMuted(participant: Participant) {}
+ override fun onParticipantAudioUnmuted(participant: Participant) {}
+ override fun onParticipantVideoPaused(participant: Participant) {}
+ override fun onParticipantVideoResumed(participant: Participant) {}
+ override fun onParticipantStartedScreenShare(participant: Participant) {}
+ override fun onParticipantStoppedScreenShare(participant: Participant) {}
+ override fun onParticipantStartedRecording(participant: Participant) {}
+ override fun onParticipantStoppedRecording(participant: Participant) {}
+ override fun onDominantSpeakerChanged(participant: Participant) {}
+})
+```
+
+
+```java
+CallSession callSession = CallSession.getInstance();
+
+callSession.addParticipantEventListener(this, new ParticipantEventListener() {
+ @Override
+ public void onParticipantHandRaised(Participant participant) {
+ Log.d(TAG, participant.getName() + " raised their hand");
+ // Show notification or visual indicator
+ showHandRaisedNotification(participant);
+ }
+
+ @Override
+ public void onParticipantHandLowered(Participant participant) {
+ Log.d(TAG, participant.getName() + " lowered their hand");
+ // Remove notification or visual indicator
+ hideHandRaisedIndicator(participant);
+ }
+
+ // Other callbacks...
+});
+```
+
+
+
+## Check Raised Hand Status
+
+The `Participant` object includes a `raisedHandTimestamp` property to check if a participant has their hand raised:
+
+
+
+```kotlin
+callSession.addParticipantEventListener(this, object : ParticipantEventListener() {
+ override fun onParticipantListChanged(participants: List) {
+ val raisedHands = participants.filter { it.raisedHandTimestamp > 0 }
+ .sortedBy { it.raisedHandTimestamp }
+
+ // Display participants with raised hands in order
+ updateRaisedHandsList(raisedHands)
+ }
+})
+```
+
+
+```java
+callSession.addParticipantEventListener(this, new ParticipantEventListener() {
+ @Override
+ public void onParticipantListChanged(List participants) {
+ List raisedHands = new ArrayList<>();
+ for (Participant p : participants) {
+ if (p.getRaisedHandTimestamp() > 0) {
+ raisedHands.add(p);
+ }
+ }
+ // Sort by timestamp and display
+ Collections.sort(raisedHands, (a, b) ->
+ Long.compare(a.getRaisedHandTimestamp(), b.getRaisedHandTimestamp()));
+ updateRaisedHandsList(raisedHands);
+ }
+});
+```
+
+
+
+## Hide Raise Hand Button
+
+To disable the raise hand feature, hide the button in the call UI:
+
+
+
+```kotlin
+val sessionSettings = CometChatCalls.SessionSettingsBuilder()
+ .hideRaiseHandButton(true)
+ .build()
+```
+
+
+```java
+SessionSettings sessionSettings = new CometChatCalls.SessionSettingsBuilder()
+ .hideRaiseHandButton(true)
+ .build();
+```
+
+
diff --git a/calls/android/recording.mdx b/calls/android/recording.mdx
new file mode 100644
index 00000000..0f0dad66
--- /dev/null
+++ b/calls/android/recording.mdx
@@ -0,0 +1,265 @@
+---
+title: "Recording"
+sidebarTitle: "Recording"
+---
+
+Record call sessions for later playback. Recordings are stored server-side and can be accessed through call logs or the CometChat Dashboard.
+
+
+Recording must be enabled for your CometChat app. Contact support or check your Dashboard settings if recording is not available.
+
+
+## Start Recording
+
+Start recording during an active call session:
+
+
+
+```kotlin
+val callSession = CallSession.getInstance()
+callSession.startRecording()
+```
+
+
+```java
+CallSession callSession = CallSession.getInstance();
+callSession.startRecording();
+```
+
+
+
+All participants are notified when recording starts.
+
+## Stop Recording
+
+Stop an active recording:
+
+
+
+```kotlin
+val callSession = CallSession.getInstance()
+callSession.stopRecording()
+```
+
+
+```java
+CallSession callSession = CallSession.getInstance();
+callSession.stopRecording();
+```
+
+
+
+## Auto-Start Recording
+
+Configure calls to automatically start recording when the session begins:
+
+
+
+```kotlin
+val sessionSettings = CometChatCalls.SessionSettingsBuilder()
+ .enableAutoStartRecording(true)
+ .build()
+```
+
+
+```java
+SessionSettings sessionSettings = new CometChatCalls.SessionSettingsBuilder()
+ .enableAutoStartRecording(true)
+ .build();
+```
+
+
+
+## Hide Recording Button
+
+Hide the recording button from the default call UI:
+
+
+
+```kotlin
+val sessionSettings = CometChatCalls.SessionSettingsBuilder()
+ .hideRecordingButton(true)
+ .build()
+```
+
+
+```java
+SessionSettings sessionSettings = new CometChatCalls.SessionSettingsBuilder()
+ .hideRecordingButton(true)
+ .build();
+```
+
+
+
+## Listen for Recording Events
+
+Monitor recording state changes using `MediaEventsListener`:
+
+
+
+```kotlin
+val callSession = CallSession.getInstance()
+
+callSession.addMediaEventsListener(this, object : MediaEventsListener() {
+ override fun onRecordingStarted() {
+ Log.d(TAG, "Recording started")
+ showRecordingIndicator()
+ }
+
+ override fun onRecordingStopped() {
+ Log.d(TAG, "Recording stopped")
+ hideRecordingIndicator()
+ }
+
+ // Other callbacks...
+ override fun onAudioMuted() {}
+ override fun onAudioUnMuted() {}
+ override fun onVideoPaused() {}
+ override fun onVideoResumed() {}
+ override fun onScreenShareStarted() {}
+ override fun onScreenShareStopped() {}
+ override fun onAudioModeChanged(audioMode: AudioMode) {}
+ override fun onCameraFacingChanged(facing: CameraFacing) {}
+})
+```
+
+
+```java
+CallSession callSession = CallSession.getInstance();
+
+callSession.addMediaEventsListener(this, new MediaEventsListener() {
+ @Override
+ public void onRecordingStarted() {
+ Log.d(TAG, "Recording started");
+ showRecordingIndicator();
+ }
+
+ @Override
+ public void onRecordingStopped() {
+ Log.d(TAG, "Recording stopped");
+ hideRecordingIndicator();
+ }
+
+ // Other callbacks...
+ @Override public void onAudioMuted() {}
+ @Override public void onAudioUnMuted() {}
+ @Override public void onVideoPaused() {}
+ @Override public void onVideoResumed() {}
+ @Override public void onScreenShareStarted() {}
+ @Override public void onScreenShareStopped() {}
+ @Override public void onAudioModeChanged(AudioMode audioMode) {}
+ @Override public void onCameraFacingChanged(CameraFacing facing) {}
+});
+```
+
+
+
+## Track Participant Recording
+
+Monitor when other participants start or stop recording using `ParticipantEventListener`:
+
+
+
+```kotlin
+callSession.addParticipantEventListener(this, object : ParticipantEventListener() {
+ override fun onParticipantStartedRecording(participant: Participant) {
+ Log.d(TAG, "${participant.name} started recording")
+ }
+
+ override fun onParticipantStoppedRecording(participant: Participant) {
+ Log.d(TAG, "${participant.name} stopped recording")
+ }
+
+ // Other callbacks...
+})
+```
+
+
+```java
+callSession.addParticipantEventListener(this, new ParticipantEventListener() {
+ @Override
+ public void onParticipantStartedRecording(Participant participant) {
+ Log.d(TAG, participant.getName() + " started recording");
+ }
+
+ @Override
+ public void onParticipantStoppedRecording(Participant participant) {
+ Log.d(TAG, participant.getName() + " stopped recording");
+ }
+
+ // Other callbacks...
+});
+```
+
+
+
+## Access Recordings
+
+Recordings are available after the call ends. You can access them in two ways:
+
+1. **CometChat Dashboard**: Navigate to **Calls > Call Logs** in your [CometChat Dashboard](https://app.cometchat.com) to view and download recordings.
+
+2. **Programmatically**: Fetch recordings through [Call Logs](/calls/android/call-logs):
+
+
+
+```kotlin
+val callLogRequest = CallLogRequest.CallLogRequestBuilder()
+ .setHasRecording(true)
+ .build()
+
+callLogRequest.fetchNext(object : CometChatCalls.CallbackListener>() {
+ override fun onSuccess(callLogs: List) {
+ for (callLog in callLogs) {
+ callLog.recordings?.forEach { recording ->
+ Log.d(TAG, "Recording URL: ${recording.recordingURL}")
+ Log.d(TAG, "Duration: ${recording.duration} seconds")
+ Log.d(TAG, "Start Time: ${recording.startTime}")
+ Log.d(TAG, "End Time: ${recording.endTime}")
+ }
+ }
+ }
+
+ override fun onError(e: CometChatException) {
+ Log.e(TAG, "Error: ${e.message}")
+ }
+})
+```
+
+
+```java
+CallLogRequest callLogRequest = new CallLogRequest.CallLogRequestBuilder()
+ .setHasRecording(true)
+ .build();
+
+callLogRequest.fetchNext(new CometChatCalls.CallbackListener>() {
+ @Override
+ public void onSuccess(List callLogs) {
+ for (CallLog callLog : callLogs) {
+ for (Recording recording : callLog.getRecordings()) {
+ Log.d(TAG, "Recording URL: " + recording.getRecordingURL());
+ Log.d(TAG, "Duration: " + recording.getDuration() + " seconds");
+ Log.d(TAG, "Start Time: " + recording.getStartTime());
+ Log.d(TAG, "End Time: " + recording.getEndTime());
+ }
+ }
+ }
+
+ @Override
+ public void onError(CometChatException e) {
+ Log.e(TAG, "Error: " + e.getMessage());
+ }
+});
+```
+
+
+
+## Recording Object
+
+| Property | Type | Description |
+|----------|------|-------------|
+| `rid` | String | Unique recording identifier |
+| `recordingURL` | String | URL to download/stream the recording |
+| `startTime` | int | Timestamp when recording started |
+| `endTime` | int | Timestamp when recording ended |
+| `duration` | double | Recording duration in seconds |
diff --git a/calls/android/ringing.mdx b/calls/android/ringing.mdx
new file mode 100644
index 00000000..85e9b765
--- /dev/null
+++ b/calls/android/ringing.mdx
@@ -0,0 +1,498 @@
+---
+title: "Ringing"
+sidebarTitle: "Ringing"
+---
+
+Implement incoming and outgoing call notifications with accept/reject functionality. Ringing enables real-time call signaling between users, allowing them to initiate calls and respond to incoming call requests.
+
+
+Ringing functionality requires the CometChat Chat SDK to be integrated alongside the Calls SDK. The Chat SDK handles call signaling (initiating, accepting, rejecting calls), while the Calls SDK manages the actual call session.
+
+
+## How Ringing Works
+
+The ringing flow involves two SDKs working together:
+
+1. **Chat SDK** - Handles call signaling (initiate, accept, reject, cancel)
+2. **Calls SDK** - Manages the actual call session once accepted
+
+```mermaid
+sequenceDiagram
+ participant Caller
+ participant ChatSDK
+ participant Receiver
+ participant CallsSDK
+
+ Caller->>ChatSDK: initiateCall()
+ ChatSDK->>Receiver: onIncomingCallReceived
+ Receiver->>ChatSDK: acceptCall()
+ ChatSDK-->>Caller: onOutgoingCallAccepted
+ Caller->>CallsSDK: joinSession()
+ Receiver->>CallsSDK: joinSession()
+```
+
+## Initiate a Call
+
+Use the Chat SDK to initiate a call to a user or group:
+
+
+
+```kotlin
+val receiverID = "USER_ID"
+val receiverType = CometChatConstants.RECEIVER_TYPE_USER
+val callType = CometChatConstants.CALL_TYPE_VIDEO
+
+val call = Call(receiverID, receiverType, callType)
+
+CometChat.initiateCall(call, object : CometChat.CallbackListener() {
+ override fun onSuccess(call: Call) {
+ Log.d(TAG, "Call initiated: ${call.sessionId}")
+ // Show outgoing call UI
+ }
+
+ override fun onError(e: CometChatException) {
+ Log.e(TAG, "Call initiation failed: ${e.message}")
+ }
+})
+```
+
+
+```java
+String receiverID = "USER_ID";
+String receiverType = CometChatConstants.RECEIVER_TYPE_USER;
+String callType = CometChatConstants.CALL_TYPE_VIDEO;
+
+Call call = new Call(receiverID, receiverType, callType);
+
+CometChat.initiateCall(call, new CometChat.CallbackListener() {
+ @Override
+ public void onSuccess(Call call) {
+ Log.d(TAG, "Call initiated: " + call.getSessionId());
+ // Show outgoing call UI
+ }
+
+ @Override
+ public void onError(CometChatException e) {
+ Log.e(TAG, "Call initiation failed: " + e.getMessage());
+ }
+});
+```
+
+
+
+| Parameter | Type | Description |
+|-----------|------|-------------|
+| `receiverID` | String | UID of the user or GUID of the group to call |
+| `receiverType` | String | `CometChatConstants.RECEIVER_TYPE_USER` or `RECEIVER_TYPE_GROUP` |
+| `callType` | String | `CometChatConstants.CALL_TYPE_VIDEO` or `CALL_TYPE_AUDIO` |
+
+## Listen for Incoming Calls
+
+Register a call listener to receive incoming call notifications:
+
+
+
+```kotlin
+val listenerID = "UNIQUE_LISTENER_ID"
+
+CometChat.addCallListener(listenerID, object : CometChat.CallListener() {
+ override fun onIncomingCallReceived(call: Call) {
+ Log.d(TAG, "Incoming call from: ${call.callInitiator.name}")
+ // Show incoming call UI with accept/reject options
+ }
+
+ override fun onOutgoingCallAccepted(call: Call) {
+ Log.d(TAG, "Call accepted, joining session...")
+ joinCallSession(call.sessionId)
+ }
+
+ override fun onOutgoingCallRejected(call: Call) {
+ Log.d(TAG, "Call rejected")
+ // Dismiss outgoing call UI
+ }
+
+ override fun onIncomingCallCancelled(call: Call) {
+ Log.d(TAG, "Incoming call cancelled")
+ // Dismiss incoming call UI
+ }
+
+ override fun onCallEndedMessageReceived(call: Call) {
+ Log.d(TAG, "Call ended")
+ }
+})
+```
+
+
+```java
+String listenerID = "UNIQUE_LISTENER_ID";
+
+CometChat.addCallListener(listenerID, new CometChat.CallListener() {
+ @Override
+ public void onIncomingCallReceived(Call call) {
+ Log.d(TAG, "Incoming call from: " + call.getCallInitiator().getName());
+ // Show incoming call UI with accept/reject options
+ }
+
+ @Override
+ public void onOutgoingCallAccepted(Call call) {
+ Log.d(TAG, "Call accepted, joining session...");
+ joinCallSession(call.getSessionId());
+ }
+
+ @Override
+ public void onOutgoingCallRejected(Call call) {
+ Log.d(TAG, "Call rejected");
+ // Dismiss outgoing call UI
+ }
+
+ @Override
+ public void onIncomingCallCancelled(Call call) {
+ Log.d(TAG, "Incoming call cancelled");
+ // Dismiss incoming call UI
+ }
+
+ @Override
+ public void onCallEndedMessageReceived(Call call) {
+ Log.d(TAG, "Call ended");
+ }
+});
+```
+
+
+
+| Callback | Description |
+|----------|-------------|
+| `onIncomingCallReceived` | A new incoming call is received |
+| `onOutgoingCallAccepted` | The receiver accepted your outgoing call |
+| `onOutgoingCallRejected` | The receiver rejected your outgoing call |
+| `onIncomingCallCancelled` | The caller cancelled the incoming call |
+| `onCallEndedMessageReceived` | The call has ended |
+
+
+Remember to remove the call listener when it's no longer needed to prevent memory leaks:
+```kotlin
+CometChat.removeCallListener(listenerID)
+```
+
+
+## Accept a Call
+
+When an incoming call is received, accept it using the Chat SDK:
+
+
+
+```kotlin
+fun acceptIncomingCall(sessionId: String) {
+ CometChat.acceptCall(sessionId, object : CometChat.CallbackListener() {
+ override fun onSuccess(call: Call) {
+ Log.d(TAG, "Call accepted")
+ joinCallSession(call.sessionId)
+ }
+
+ override fun onError(e: CometChatException) {
+ Log.e(TAG, "Accept call failed: ${e.message}")
+ }
+ })
+}
+```
+
+
+```java
+void acceptIncomingCall(String sessionId) {
+ CometChat.acceptCall(sessionId, new CometChat.CallbackListener() {
+ @Override
+ public void onSuccess(Call call) {
+ Log.d(TAG, "Call accepted");
+ joinCallSession(call.getSessionId());
+ }
+
+ @Override
+ public void onError(CometChatException e) {
+ Log.e(TAG, "Accept call failed: " + e.getMessage());
+ }
+ });
+}
+```
+
+
+
+## Reject a Call
+
+Reject an incoming call:
+
+
+
+```kotlin
+fun rejectIncomingCall(sessionId: String) {
+ val status = CometChatConstants.CALL_STATUS_REJECTED
+
+ CometChat.rejectCall(sessionId, status, object : CometChat.CallbackListener() {
+ override fun onSuccess(call: Call) {
+ Log.d(TAG, "Call rejected")
+ // Dismiss incoming call UI
+ }
+
+ override fun onError(e: CometChatException) {
+ Log.e(TAG, "Reject call failed: ${e.message}")
+ }
+ })
+}
+```
+
+
+```java
+void rejectIncomingCall(String sessionId) {
+ String status = CometChatConstants.CALL_STATUS_REJECTED;
+
+ CometChat.rejectCall(sessionId, status, new CometChat.CallbackListener() {
+ @Override
+ public void onSuccess(Call call) {
+ Log.d(TAG, "Call rejected");
+ // Dismiss incoming call UI
+ }
+
+ @Override
+ public void onError(CometChatException e) {
+ Log.e(TAG, "Reject call failed: " + e.getMessage());
+ }
+ });
+}
+```
+
+
+
+## Cancel a Call
+
+Cancel an outgoing call before it's answered:
+
+
+
+```kotlin
+fun cancelOutgoingCall(sessionId: String) {
+ val status = CometChatConstants.CALL_STATUS_CANCELLED
+
+ CometChat.rejectCall(sessionId, status, object : CometChat.CallbackListener() {
+ override fun onSuccess(call: Call) {
+ Log.d(TAG, "Call cancelled")
+ // Dismiss outgoing call UI
+ }
+
+ override fun onError(e: CometChatException) {
+ Log.e(TAG, "Cancel call failed: ${e.message}")
+ }
+ })
+}
+```
+
+
+```java
+void cancelOutgoingCall(String sessionId) {
+ String status = CometChatConstants.CALL_STATUS_CANCELLED;
+
+ CometChat.rejectCall(sessionId, status, new CometChat.CallbackListener() {
+ @Override
+ public void onSuccess(Call call) {
+ Log.d(TAG, "Call cancelled");
+ // Dismiss outgoing call UI
+ }
+
+ @Override
+ public void onError(CometChatException e) {
+ Log.e(TAG, "Cancel call failed: " + e.getMessage());
+ }
+ });
+}
+```
+
+
+
+## Join the Call Session
+
+After accepting a call (or when your outgoing call is accepted), join the call session using the Calls SDK:
+
+
+
+```kotlin
+fun joinCallSession(sessionId: String) {
+ val callViewContainer = findViewById(R.id.call_view_container)
+
+ val sessionSettings = CometChatCalls.SessionSettingsBuilder()
+ .setType(SessionType.VIDEO)
+ .build()
+
+ CometChatCalls.joinSession(sessionId, sessionSettings, callViewContainer,
+ object : CometChatCalls.CallbackListener() {
+ override fun onSuccess(callSession: CallSession) {
+ Log.d(TAG, "Joined call session")
+ }
+
+ override fun onError(e: CometChatException) {
+ Log.e(TAG, "Failed to join: ${e.message}")
+ }
+ }
+ )
+}
+```
+
+
+```java
+void joinCallSession(String sessionId) {
+ RelativeLayout callViewContainer = findViewById(R.id.call_view_container);
+
+ SessionSettings sessionSettings = new CometChatCalls.SessionSettingsBuilder()
+ .setType(SessionType.VIDEO)
+ .build();
+
+ CometChatCalls.joinSession(sessionId, sessionSettings, callViewContainer,
+ new CometChatCalls.CallbackListener() {
+ @Override
+ public void onSuccess(CallSession callSession) {
+ Log.d(TAG, "Joined call session");
+ }
+
+ @Override
+ public void onError(CometChatException e) {
+ Log.e(TAG, "Failed to join: " + e.getMessage());
+ }
+ }
+ );
+}
+```
+
+
+
+
+For more details on session customization and controlling the call, see [Actions](/calls/android/actions) and [Listeners](/calls/android/listeners).
+
+
+## End a Call
+
+Properly ending a call requires coordination between both SDKs to ensure all participants are notified and call logs are recorded correctly.
+
+
+Always call `CometChat.endCall()` when ending a call. This notifies the other participant and ensures the call is properly logged. Without this, the other user won't know the call has ended and call logs may be incomplete.
+
+
+```mermaid
+sequenceDiagram
+ participant User
+ participant CallsSDK
+ participant ChatSDK
+ participant OtherParticipant
+
+ User->>CallsSDK: leaveSession()
+ User->>ChatSDK: endCall(sessionId)
+ ChatSDK->>OtherParticipant: onCallEndedMessageReceived
+ OtherParticipant->>CallsSDK: leaveSession()
+```
+
+When using the default call UI, listen for the end call button click using `ButtonClickListener` and call `endCall()`:
+
+
+
+```kotlin
+val callSession = CallSession.getInstance()
+
+// Listen for end call button click
+callSession.addButtonClickListener(this, object : ButtonClickListener() {
+ override fun onLeaveSessionButtonClicked() {
+ endCall(currentSessionId)
+ }
+ // Other callbacks...
+})
+
+fun endCall(sessionId: String) {
+ // 1. Leave the call session (Calls SDK)
+ CallSession.getInstance().leaveSession()
+
+ // 2. Notify other participants (Chat SDK)
+ CometChat.endCall(sessionId, object : CometChat.CallbackListener() {
+ override fun onSuccess(call: Call) {
+ Log.d(TAG, "Call ended successfully")
+ finish()
+ }
+
+ override fun onError(e: CometChatException) {
+ Log.e(TAG, "End call failed: ${e.message}")
+ finish()
+ }
+ })
+}
+```
+
+
+```java
+CallSession callSession = CallSession.getInstance();
+
+// Listen for end call button click
+callSession.addButtonClickListener(this, new ButtonClickListener() {
+ @Override
+ public void onLeaveSessionButtonClicked() {
+ endCall(currentSessionId);
+ }
+ // Other callbacks...
+});
+
+void endCall(String sessionId) {
+ // 1. Leave the call session (Calls SDK)
+ CallSession.getInstance().leaveSession();
+
+ // 2. Notify other participants (Chat SDK)
+ CometChat.endCall(sessionId, new CometChat.CallbackListener() {
+ @Override
+ public void onSuccess(Call call) {
+ Log.d(TAG, "Call ended successfully");
+ finish();
+ }
+
+ @Override
+ public void onError(CometChatException e) {
+ Log.e(TAG, "End call failed: " + e.getMessage());
+ finish();
+ }
+ });
+}
+```
+
+
+
+The other participant receives `onCallEndedMessageReceived` callback and should leave the session:
+
+
+
+```kotlin
+CometChat.addCallListener(listenerID, object : CometChat.CallListener() {
+ override fun onCallEndedMessageReceived(call: Call) {
+ CallSession.getInstance().leaveSession()
+ finish()
+ }
+ // Other callbacks...
+})
+```
+
+
+```java
+CometChat.addCallListener(listenerID, new CometChat.CallListener() {
+ @Override
+ public void onCallEndedMessageReceived(Call call) {
+ CallSession.getInstance().leaveSession();
+ finish();
+ }
+ // Other callbacks...
+});
+```
+
+
+
+## Call Status Values
+
+| Status | Description |
+|--------|-------------|
+| `initiated` | Call has been initiated but not yet answered |
+| `ongoing` | Call is currently in progress |
+| `busy` | Receiver is busy on another call |
+| `rejected` | Receiver rejected the call |
+| `cancelled` | Caller cancelled before receiver answered |
+| `ended` | Call ended normally |
+| `missed` | Receiver didn't answer in time |
+| `unanswered` | Call was not answered |
diff --git a/calls/android/screen-sharing.mdx b/calls/android/screen-sharing.mdx
new file mode 100644
index 00000000..9133f161
--- /dev/null
+++ b/calls/android/screen-sharing.mdx
@@ -0,0 +1,110 @@
+---
+title: "Screen Sharing"
+sidebarTitle: "Screen Sharing"
+---
+
+View screen shares from other participants during a call. The Android SDK can receive and display screen shares initiated from web clients.
+
+
+The Android Calls SDK does not support initiating screen sharing. Screen sharing can only be started from web clients. Android participants can view shared screens.
+
+
+## How It Works
+
+When a web participant starts screen sharing:
+1. The SDK receives the screen share stream
+2. The call layout automatically adjusts to display the shared screen prominently
+3. Android participants can view the shared content
+
+## Listen for Screen Share Events
+
+Monitor when participants start or stop screen sharing:
+
+
+
+```kotlin
+val callSession = CallSession.getInstance()
+
+callSession.addParticipantEventListener(this, object : ParticipantEventListener() {
+ override fun onParticipantStartedScreenShare(participant: Participant) {
+ Log.d(TAG, "${participant.name} started screen sharing")
+ // Layout automatically adjusts to show shared screen
+ }
+
+ override fun onParticipantStoppedScreenShare(participant: Participant) {
+ Log.d(TAG, "${participant.name} stopped screen sharing")
+ // Layout returns to normal view
+ }
+
+ // Other callbacks...
+ override fun onParticipantJoined(participant: Participant) {}
+ override fun onParticipantLeft(participant: Participant) {}
+ override fun onParticipantListChanged(participants: List) {}
+ override fun onParticipantAudioMuted(participant: Participant) {}
+ override fun onParticipantAudioUnmuted(participant: Participant) {}
+ override fun onParticipantVideoPaused(participant: Participant) {}
+ override fun onParticipantVideoResumed(participant: Participant) {}
+ override fun onParticipantStartedRecording(participant: Participant) {}
+ override fun onParticipantStoppedRecording(participant: Participant) {}
+ override fun onParticipantHandRaised(participant: Participant) {}
+ override fun onParticipantHandLowered(participant: Participant) {}
+ override fun onDominantSpeakerChanged(participant: Participant) {}
+})
+```
+
+
+```java
+CallSession callSession = CallSession.getInstance();
+
+callSession.addParticipantEventListener(this, new ParticipantEventListener() {
+ @Override
+ public void onParticipantStartedScreenShare(Participant participant) {
+ Log.d(TAG, participant.getName() + " started screen sharing");
+ // Layout automatically adjusts to show shared screen
+ }
+
+ @Override
+ public void onParticipantStoppedScreenShare(Participant participant) {
+ Log.d(TAG, participant.getName() + " stopped screen sharing");
+ // Layout returns to normal view
+ }
+
+ // Other callbacks...
+});
+```
+
+
+
+## Check Screen Share Status
+
+Use the `isPresenting` property on the `Participant` object to check if someone is sharing their screen:
+
+
+
+```kotlin
+callSession.addParticipantEventListener(this, object : ParticipantEventListener() {
+ override fun onParticipantListChanged(participants: List) {
+ val presenter = participants.find { it.isPresenting }
+ if (presenter != null) {
+ Log.d(TAG, "${presenter.name} is currently sharing their screen")
+ }
+ }
+})
+```
+
+
+```java
+callSession.addParticipantEventListener(this, new ParticipantEventListener() {
+ @Override
+ public void onParticipantListChanged(List participants) {
+ for (Participant p : participants) {
+ if (p.isPresenting()) {
+ Log.d(TAG, p.getName() + " is currently sharing their screen");
+ break;
+ }
+ }
+ }
+});
+```
+
+
diff --git a/calls/android/session-control.mdx b/calls/android/session-control.mdx
new file mode 100644
index 00000000..38d91d9f
--- /dev/null
+++ b/calls/android/session-control.mdx
@@ -0,0 +1,437 @@
+---
+title: "Session Control"
+sidebarTitle: "Session Control"
+---
+
+Control the call session lifecycle and participant interactions. These methods allow you to leave the session, raise/lower your hand, and check session status.
+
+## Prerequisites
+
+- An active [call session](/calls/android/join-session)
+- Access to the `CallSession` instance
+
+## Get CallSession Instance
+
+Session control methods are called on the `CallSession` singleton:
+
+
+
+```kotlin
+val callSession = CallSession.getInstance()
+```
+
+
+```java
+CallSession callSession = CallSession.getInstance();
+```
+
+
+
+---
+
+## Check Session Status
+
+Check if there is currently an active call session.
+
+
+
+```kotlin
+val isActive = callSession.isSessionActive()
+
+if (isActive) {
+ Log.d(TAG, "Call session is active")
+} else {
+ Log.d(TAG, "No active call session")
+}
+```
+
+
+```java
+boolean isActive = callSession.isSessionActive();
+
+if (isActive) {
+ Log.d(TAG, "Call session is active");
+} else {
+ Log.d(TAG, "No active call session");
+}
+```
+
+
+
+| Return Type | Description |
+|-------------|-------------|
+| `Boolean` | `true` if a session is active, `false` otherwise |
+
+
+Use this method to check session status before calling other `CallSession` methods, or to determine if you need to show call UI.
+
+
+---
+
+## Leave Session
+
+End your participation in the current call session.
+
+
+
+```kotlin
+callSession.leaveSession()
+```
+
+
+```java
+callSession.leaveSession();
+```
+
+
+
+
+When you leave the session, the `onSessionLeft()` callback is triggered on your `SessionStatusListener`. Other participants will receive the `onParticipantLeft(Participant)` callback.
+
+
+### Handling Session End
+
+
+
+```kotlin
+callSession.addSessionStatusListener(this, object : SessionStatusListener {
+ override fun onSessionLeft() {
+ Log.d(TAG, "Successfully left the session")
+ // Navigate away from call screen
+ finish()
+ }
+
+ override fun onSessionJoined() {}
+ override fun onSessionTimedOut() {}
+ override fun onConnectionLost() {}
+ override fun onConnectionRestored() {}
+ override fun onConnectionClosed() {}
+})
+```
+
+
+```java
+callSession.addSessionStatusListener(this, new SessionStatusListener() {
+ @Override
+ public void onSessionLeft() {
+ Log.d(TAG, "Successfully left the session");
+ // Navigate away from call screen
+ finish();
+ }
+
+ @Override
+ public void onSessionJoined() {}
+ @Override
+ public void onSessionTimedOut() {}
+ @Override
+ public void onConnectionLost() {}
+ @Override
+ public void onConnectionRestored() {}
+ @Override
+ public void onConnectionClosed() {}
+});
+```
+
+
+
+---
+
+## Raise Hand
+
+Raise your hand to get attention from other participants. This is useful in meetings or webinars when you want to ask a question or make a comment.
+
+
+
+```kotlin
+callSession.raiseHand()
+```
+
+
+```java
+callSession.raiseHand();
+```
+
+
+
+
+When you raise your hand, all participants receive the `onParticipantHandRaised(Participant)` callback on their `ParticipantEventListener`.
+
+
+---
+
+## Lower Hand
+
+Lower your previously raised hand.
+
+
+
+```kotlin
+callSession.lowerHand()
+```
+
+
+```java
+callSession.lowerHand();
+```
+
+
+
+
+When you lower your hand, all participants receive the `onParticipantHandLowered(Participant)` callback on their `ParticipantEventListener`.
+
+
+---
+
+## Listen for Hand Raise Events
+
+Register a `ParticipantEventListener` to receive callbacks when participants raise or lower their hands:
+
+
+
+```kotlin
+callSession.addParticipantEventListener(this, object : ParticipantEventListener {
+ override fun onParticipantHandRaised(participant: Participant) {
+ Log.d(TAG, "${participant.name} raised their hand")
+ // Show hand raised indicator in UI
+ }
+
+ override fun onParticipantHandLowered(participant: Participant) {
+ Log.d(TAG, "${participant.name} lowered their hand")
+ // Hide hand raised indicator in UI
+ }
+
+ // Other callbacks...
+ override fun onParticipantJoined(participant: Participant) {}
+ override fun onParticipantLeft(participant: Participant) {}
+ override fun onParticipantAudioMuted(participant: Participant) {}
+ override fun onParticipantAudioUnmuted(participant: Participant) {}
+ override fun onParticipantVideoPaused(participant: Participant) {}
+ override fun onParticipantVideoResumed(participant: Participant) {}
+ override fun onParticipantStartedScreenShare(participant: Participant) {}
+ override fun onParticipantStoppedScreenShare(participant: Participant) {}
+ override fun onParticipantStartedRecording(participant: Participant) {}
+ override fun onParticipantStoppedRecording(participant: Participant) {}
+ override fun onDominantSpeakerChanged(participant: Participant) {}
+ override fun onParticipantListChanged(participants: List) {}
+})
+```
+
+
+```java
+callSession.addParticipantEventListener(this, new ParticipantEventListener() {
+ @Override
+ public void onParticipantHandRaised(Participant participant) {
+ Log.d(TAG, participant.getName() + " raised their hand");
+ // Show hand raised indicator in UI
+ }
+
+ @Override
+ public void onParticipantHandLowered(Participant participant) {
+ Log.d(TAG, participant.getName() + " lowered their hand");
+ // Hide hand raised indicator in UI
+ }
+
+ // Other callbacks...
+ @Override
+ public void onParticipantJoined(Participant participant) {}
+ @Override
+ public void onParticipantLeft(Participant participant) {}
+ @Override
+ public void onParticipantAudioMuted(Participant participant) {}
+ @Override
+ public void onParticipantAudioUnmuted(Participant participant) {}
+ @Override
+ public void onParticipantVideoPaused(Participant participant) {}
+ @Override
+ public void onParticipantVideoResumed(Participant participant) {}
+ @Override
+ public void onParticipantStartedScreenShare(Participant participant) {}
+ @Override
+ public void onParticipantStoppedScreenShare(Participant participant) {}
+ @Override
+ public void onParticipantStartedRecording(Participant participant) {}
+ @Override
+ public void onParticipantStoppedRecording(Participant participant) {}
+ @Override
+ public void onDominantSpeakerChanged(Participant participant) {}
+ @Override
+ public void onParticipantListChanged(List participants) {}
+});
+```
+
+
+
+---
+
+## Button Click Listeners
+
+Listen for when users tap the leave or raise hand buttons:
+
+
+
+```kotlin
+callSession.addButtonClickListener(this, object : ButtonClickListener {
+ override fun onLeaveSessionButtonClicked() {
+ Log.d(TAG, "Leave button clicked")
+ // Optionally show confirmation dialog before leaving
+ }
+
+ override fun onRaiseHandButtonClicked() {
+ Log.d(TAG, "Raise hand button clicked")
+ // Handle custom raise hand logic if needed
+ }
+
+ // Other callbacks...
+ override fun onShareInviteButtonClicked() {}
+ override fun onChangeLayoutButtonClicked() {}
+ override fun onParticipantListButtonClicked() {}
+ override fun onToggleAudioButtonClicked() {}
+ override fun onToggleVideoButtonClicked() {}
+ override fun onSwitchCameraButtonClicked() {}
+ override fun onChatButtonClicked() {}
+ override fun onRecordingToggleButtonClicked() {}
+})
+```
+
+
+```java
+callSession.addButtonClickListener(this, new ButtonClickListener() {
+ @Override
+ public void onLeaveSessionButtonClicked() {
+ Log.d(TAG, "Leave button clicked");
+ // Optionally show confirmation dialog before leaving
+ }
+
+ @Override
+ public void onRaiseHandButtonClicked() {
+ Log.d(TAG, "Raise hand button clicked");
+ // Handle custom raise hand logic if needed
+ }
+
+ // Other callbacks...
+ @Override
+ public void onShareInviteButtonClicked() {}
+ @Override
+ public void onChangeLayoutButtonClicked() {}
+ @Override
+ public void onParticipantListButtonClicked() {}
+ @Override
+ public void onToggleAudioButtonClicked() {}
+ @Override
+ public void onToggleVideoButtonClicked() {}
+ @Override
+ public void onSwitchCameraButtonClicked() {}
+ @Override
+ public void onChatButtonClicked() {}
+ @Override
+ public void onRecordingToggleButtonClicked() {}
+});
+```
+
+
+
+---
+
+## Hide Session Control Buttons
+
+Control the visibility of session control buttons in the UI:
+
+
+
+```kotlin
+val sessionSettings = CometChatCalls.SessionSettingsBuilder()
+ .hideLeaveSessionButton(false) // Show leave button
+ .hideRaiseHandButton(false) // Show raise hand button
+ .hideShareInviteButton(true) // Hide share invite button
+ .build()
+```
+
+
+```java
+SessionSettings sessionSettings = new CometChatCalls.SessionSettingsBuilder()
+ .hideLeaveSessionButton(false) // Show leave button
+ .hideRaiseHandButton(false) // Show raise hand button
+ .hideShareInviteButton(true) // Hide share invite button
+ .build();
+```
+
+
+
+---
+
+## Session Timeout
+
+Configure the idle timeout period for when you're alone in a session:
+
+
+
+```kotlin
+val sessionSettings = CometChatCalls.SessionSettingsBuilder()
+ .setIdleTimeoutPeriod(300) // 300 seconds (5 minutes)
+ .build()
+```
+
+
+```java
+SessionSettings sessionSettings = new CometChatCalls.SessionSettingsBuilder()
+ .setIdleTimeoutPeriod(300) // 300 seconds (5 minutes)
+ .build();
+```
+
+
+
+When the timeout is reached, the `onSessionTimedOut()` callback is triggered:
+
+
+
+```kotlin
+callSession.addSessionStatusListener(this, object : SessionStatusListener {
+ override fun onSessionTimedOut() {
+ Log.d(TAG, "Session timed out due to inactivity")
+ // Handle timeout - navigate away from call screen
+ finish()
+ }
+
+ override fun onSessionJoined() {}
+ override fun onSessionLeft() {}
+ override fun onConnectionLost() {}
+ override fun onConnectionRestored() {}
+ override fun onConnectionClosed() {}
+})
+```
+
+
+```java
+callSession.addSessionStatusListener(this, new SessionStatusListener() {
+ @Override
+ public void onSessionTimedOut() {
+ Log.d(TAG, "Session timed out due to inactivity");
+ // Handle timeout - navigate away from call screen
+ finish();
+ }
+
+ @Override
+ public void onSessionJoined() {}
+ @Override
+ public void onSessionLeft() {}
+ @Override
+ public void onConnectionLost() {}
+ @Override
+ public void onConnectionRestored() {}
+ @Override
+ public void onConnectionClosed() {}
+});
+```
+
+
+
+## Next Steps
+
+
+
+ Handle all session lifecycle events
+
+
+ Handle all button click events
+
+
diff --git a/calls/android/session-settings.mdx b/calls/android/session-settings.mdx
new file mode 100644
index 00000000..24272810
--- /dev/null
+++ b/calls/android/session-settings.mdx
@@ -0,0 +1,686 @@
+---
+title: "SessionSettingsBuilder"
+sidebarTitle: "SessionSettingsBuilder"
+---
+
+The `SessionSettingsBuilder` is a powerful configuration tool that allows you to customize every aspect of your call session before participants join. From controlling the initial audio/video state to customizing the UI layout and hiding specific controls, this builder gives you complete control over the call experience.
+
+Proper session configuration is crucial for creating a seamless user experience tailored to your application's specific needs.
+
+
+These are pre-session configurations that must be set before joining a call. Once configured, pass the `SessionSettings` object to the `joinSession()` method. Settings cannot be changed after the session has started, though many features can be controlled dynamically during the call using call actions.
+
+
+
+
+```kotlin
+val sessionSettings = CometChatCalls.SessionSettingsBuilder()
+ .setTitle("Team Meeting")
+ .setDisplayName("John Doe")
+ .setType(SessionType.VIDEO)
+ .setLayout(LayoutType.TILE)
+ .startAudioMuted(false)
+ .startVideoPaused(false)
+ .build()
+```
+
+
+```java
+SessionSettings sessionSettings = new CometChatCalls.SessionSettingsBuilder()
+ .setTitle("Team Meeting")
+ .setDisplayName("John Doe")
+ .setType(SessionType.VIDEO)
+ .setLayout(LayoutType.TILE)
+ .startAudioMuted(false)
+ .startVideoPaused(false)
+ .build();
+```
+
+
+
+## Session Settings
+
+### Title
+
+**Method:** `setTitle(String)`
+
+Sets the title that appears in the call header. This helps participants identify the purpose or name of the call session. The title is displayed prominently at the top of the call interface.
+
+
+
+```kotlin
+.setTitle("Team Meeting")
+```
+
+
+```java
+.setTitle("Team Meeting")
+```
+
+
+
+| Parameter | Type | Default |
+|-----------|------|---------|
+| `title` | String | null |
+
+### Display Name
+
+**Method:** `setDisplayName(String)`
+
+Sets the display name that will be shown to other participants in the call. This name appears on your video tile and in the participant list, helping others identify you during the session.
+
+
+
+```kotlin
+.setDisplayName("John Doe")
+```
+
+
+```java
+.setDisplayName("John Doe")
+```
+
+
+
+| Parameter | Type | Default |
+|-----------|------|---------|
+| `displayName` | String | null |
+
+### Session Type
+
+**Method:** `setType(SessionType)`
+
+Defines the type of call session. Choose `VIDEO` for video calls with camera enabled, or `AUDIO` for audio-only calls. This setting determines whether video streaming is enabled by default.
+
+
+
+```kotlin
+.setType(SessionType.VIDEO)
+```
+
+
+```java
+.setType(SessionType.VIDEO)
+```
+
+
+
+| Parameter | Type | Default |
+|-----------|------|---------|
+| `type` | SessionType | VIDEO |
+
+
+| Value | Description |
+|-------|-------------|
+| `VIDEO` | Video call with camera enabled |
+| `AUDIO` | Audio-only call |
+
+
+### Layout Mode
+
+**Method:** `setLayout(LayoutType)`
+
+Sets the initial layout mode for displaying participants. `TILE` shows all participants in a grid, `SPOTLIGHT` focuses on the active speaker with others in a sidebar, and `SIDEBAR` displays the main speaker with participants in a side panel.
+
+
+
+```kotlin
+.setLayout(LayoutType.TILE)
+```
+
+
+```java
+.setLayout(LayoutType.TILE)
+```
+
+
+
+| Parameter | Type | Default |
+|-----------|------|---------|
+| `layout` | LayoutType | TILE |
+
+
+| Value | Description |
+|-------|-------------|
+| `TILE` | Grid layout showing all participants equally |
+| `SPOTLIGHT` | Focus on active speaker with others in sidebar |
+| `SIDEBAR` | Main speaker with participants in a sidebar |
+
+
+### Idle Timeout Period
+
+**Method:** `setIdleTimeoutPeriod(int)`
+
+Configures the timeout duration in seconds before automatically ending the session when you're the only participant. This prevents sessions from running indefinitely when others have left.
+
+
+
+```kotlin
+.setIdleTimeoutPeriod(300)
+```
+
+
+```java
+.setIdleTimeoutPeriod(300)
+```
+
+
+
+| Parameter | Type | Default |
+|-----------|------|---------|
+| `seconds` | int | 300 |
+
+### Start Audio Muted
+
+**Method:** `startAudioMuted(boolean)`
+
+Determines whether the microphone is muted when joining the session. Set to `true` to join with audio muted, requiring users to manually unmute. Useful for large meetings to prevent background noise.
+
+
+
+```kotlin
+.startAudioMuted(true)
+```
+
+
+```java
+.startAudioMuted(true)
+```
+
+
+
+| Parameter | Type | Default |
+|-----------|------|---------|
+| `muted` | boolean | false |
+
+### Start Video Paused
+
+**Method:** `startVideoPaused(boolean)`
+
+Controls whether the camera is turned off when joining the session. Set to `true` to join with video disabled, allowing users to enable it when ready. Helpful for privacy or bandwidth considerations.
+
+
+
+```kotlin
+.startVideoPaused(true)
+```
+
+
+```java
+.startVideoPaused(true)
+```
+
+
+
+| Parameter | Type | Default |
+|-----------|------|---------|
+| `paused` | boolean | false |
+
+### Audio Mode
+
+**Method:** `setAudioMode(AudioMode)`
+
+Sets the initial audio output device for the call. Options include `SPEAKER` for loudspeaker, `EARPIECE` for phone earpiece, `BLUETOOTH` for connected Bluetooth devices, or `HEADPHONES` for wired headphones.
+
+
+
+```kotlin
+.setAudioMode(AudioMode.SPEAKER)
+```
+
+
+```java
+.setAudioMode(AudioMode.SPEAKER)
+```
+
+
+
+| Parameter | Type | Default |
+|-----------|------|---------|
+| `audioMode` | AudioMode | SPEAKER |
+
+
+| Value | Description |
+|-------|-------------|
+| `SPEAKER` | Device loudspeaker |
+| `EARPIECE` | Phone earpiece |
+| `BLUETOOTH` | Connected Bluetooth device |
+| `HEADPHONES` | Wired headphones |
+
+
+### Initial Camera Facing
+
+**Method:** `setInitialCameraFacing(CameraFacing)`
+
+Specifies which camera to use when starting the session. Choose `FRONT` for the front-facing camera (selfie mode) or `BACK` for the rear camera. Users can switch cameras during the call.
+
+
+
+```kotlin
+.setInitialCameraFacing(CameraFacing.FRONT)
+```
+
+
+```java
+.setInitialCameraFacing(CameraFacing.FRONT)
+```
+
+
+
+| Parameter | Type | Default |
+|-----------|------|---------|
+| `cameraFacing` | CameraFacing | FRONT |
+
+
+| Value | Description |
+|-------|-------------|
+| `FRONT` | Front-facing camera |
+| `BACK` | Rear camera |
+
+
+### Low Bandwidth Mode
+
+**Method:** `enableLowBandwidthMode(boolean)`
+
+Enables optimization for poor network conditions. When enabled, the SDK reduces video quality and adjusts streaming parameters to maintain call stability on slow or unstable connections.
+
+
+
+```kotlin
+.enableLowBandwidthMode(true)
+```
+
+
+```java
+.enableLowBandwidthMode(true)
+```
+
+
+
+| Parameter | Type | Default |
+|-----------|------|---------|
+| `enabled` | boolean | false |
+
+### Auto Start Recording
+
+**Method:** `enableAutoStartRecording(boolean)`
+
+Automatically starts recording the session as soon as it begins. When enabled, recording starts without manual intervention, ensuring the entire session is captured from the start.
+
+
+
+```kotlin
+.enableAutoStartRecording(true)
+```
+
+
+```java
+.enableAutoStartRecording(true)
+```
+
+
+
+| Parameter | Type | Default |
+|-----------|------|---------|
+| `enabled` | boolean | false |
+
+### Hide Control Panel
+
+**Method:** `hideControlPanel(boolean)`
+
+Hides the bottom control bar that contains call action buttons. Set to `true` to remove the control panel entirely, useful for custom UI implementations or view-only modes.
+
+
+
+```kotlin
+.hideControlPanel(true)
+```
+
+
+```java
+.hideControlPanel(true)
+```
+
+
+
+| Parameter | Type | Default |
+|-----------|------|---------|
+| `hide` | boolean | false |
+
+### Hide Header Panel
+
+**Method:** `hideHeaderPanel(boolean)`
+
+Hides the top header bar that displays the call title and session information. Set to `true` to maximize the video viewing area or implement a custom header.
+
+
+
+```kotlin
+.hideHeaderPanel(true)
+```
+
+
+```java
+.hideHeaderPanel(true)
+```
+
+
+
+| Parameter | Type | Default |
+|-----------|------|---------|
+| `hide` | boolean | false |
+
+### Hide Session Timer
+
+**Method:** `hideSessionTimer(boolean)`
+
+Hides the session duration timer that shows how long the call has been active. Set to `true` to remove the timer display from the interface.
+
+
+
+```kotlin
+.hideSessionTimer(true)
+```
+
+
+```java
+.hideSessionTimer(true)
+```
+
+
+
+| Parameter | Type | Default |
+|-----------|------|---------|
+| `hide` | boolean | false |
+
+### Hide Leave Session Button
+
+**Method:** `hideLeaveSessionButton(boolean)`
+
+Hides the button that allows users to leave or end the call. Set to `true` to remove this button, requiring an alternative method to exit the session.
+
+
+
+```kotlin
+.hideLeaveSessionButton(true)
+```
+
+
+```java
+.hideLeaveSessionButton(true)
+```
+
+
+
+| Parameter | Type | Default |
+|-----------|------|---------|
+| `hide` | boolean | false |
+
+### Hide Toggle Audio Button
+
+**Method:** `hideToggleAudioButton(boolean)`
+
+Hides the microphone mute/unmute button from the control panel. Set to `true` to remove audio controls, useful when audio control should be managed programmatically.
+
+
+
+```kotlin
+.hideToggleAudioButton(true)
+```
+
+
+```java
+.hideToggleAudioButton(true)
+```
+
+
+
+| Parameter | Type | Default |
+|-----------|------|---------|
+| `hide` | boolean | false |
+
+### Hide Toggle Video Button
+
+**Method:** `hideToggleVideoButton(boolean)`
+
+Hides the camera on/off button from the control panel. Set to `true` to remove video controls, useful for audio-only calls or custom video control implementations.
+
+
+
+```kotlin
+.hideToggleVideoButton(true)
+```
+
+
+```java
+.hideToggleVideoButton(true)
+```
+
+
+
+| Parameter | Type | Default |
+|-----------|------|---------|
+| `hide` | boolean | false |
+
+### Hide Switch Camera Button
+
+**Method:** `hideSwitchCameraButton(boolean)`
+
+Hides the button that allows switching between front and rear cameras. Set to `true` to remove this control, useful for devices with single cameras or fixed camera requirements.
+
+
+
+```kotlin
+.hideSwitchCameraButton(true)
+```
+
+
+```java
+.hideSwitchCameraButton(true)
+```
+
+
+
+| Parameter | Type | Default |
+|-----------|------|---------|
+| `hide` | boolean | false |
+
+### Hide Recording Button
+
+**Method:** `hideRecordingButton(boolean)`
+
+Hides the recording start/stop button from the control panel. Set to `false` to show the recording button, allowing users to manually control session recording.
+
+
+
+```kotlin
+.hideRecordingButton(false)
+```
+
+
+```java
+.hideRecordingButton(false)
+```
+
+
+
+| Parameter | Type | Default |
+|-----------|------|---------|
+| `hide` | boolean | true |
+
+### Hide Screen Sharing Button
+
+**Method:** `hideScreenSharingButton(boolean)`
+
+Hides the screen sharing button from the control panel. Set to `true` to remove screen sharing controls, useful when screen sharing is not needed or managed separately.
+
+
+
+```kotlin
+.hideScreenSharingButton(true)
+```
+
+
+```java
+.hideScreenSharingButton(true)
+```
+
+
+
+| Parameter | Type | Default |
+|-----------|------|---------|
+| `hide` | boolean | false |
+
+### Hide Audio Mode Button
+
+**Method:** `hideAudioModeButton(boolean)`
+
+Hides the button that toggles between speaker, earpiece, and other audio output modes. Set to `true` to remove audio mode controls from the interface.
+
+
+
+```kotlin
+.hideAudioModeButton(true)
+```
+
+
+```java
+.hideAudioModeButton(true)
+```
+
+
+
+| Parameter | Type | Default |
+|-----------|------|---------|
+| `hide` | boolean | false |
+
+### Hide Raise Hand Button
+
+**Method:** `hideRaiseHandButton(boolean)`
+
+Hides the raise hand button that participants use to signal they want to speak. Set to `true` to remove this feature from the interface.
+
+
+
+```kotlin
+.hideRaiseHandButton(true)
+```
+
+
+```java
+.hideRaiseHandButton(true)
+```
+
+
+
+| Parameter | Type | Default |
+|-----------|------|---------|
+| `hide` | boolean | false |
+
+### Hide Share Invite Button
+
+**Method:** `hideShareInviteButton(boolean)`
+
+Hides the button that allows sharing session invite links with others. Set to `false` to show the invite button, enabling easy participant invitation.
+
+
+
+```kotlin
+.hideShareInviteButton(false)
+```
+
+
+```java
+.hideShareInviteButton(false)
+```
+
+
+
+| Parameter | Type | Default |
+|-----------|------|---------|
+| `hide` | boolean | true |
+
+### Hide Participant List Button
+
+**Method:** `hideParticipantListButton(boolean)`
+
+Hides the button that opens the participant list view. Set to `true` to remove access to the participant list, useful for simplified interfaces.
+
+
+
+```kotlin
+.hideParticipantListButton(true)
+```
+
+
+```java
+.hideParticipantListButton(true)
+```
+
+
+
+| Parameter | Type | Default |
+|-----------|------|---------|
+| `hide` | boolean | false |
+
+### Hide Change Layout Button
+
+**Method:** `hideChangeLayoutButton(boolean)`
+
+Hides the button that allows switching between different layout modes (tile, spotlight, sidebar). Set to `true` to lock the layout to the initial setting.
+
+
+
+```kotlin
+.hideChangeLayoutButton(true)
+```
+
+
+```java
+.hideChangeLayoutButton(true)
+```
+
+
+
+| Parameter | Type | Default |
+|-----------|------|---------|
+| `hide` | boolean | false |
+
+### Hide Chat Button
+
+**Method:** `hideChatButton(boolean)`
+
+Hides the button that opens the in-call chat interface. Set to `false` to show the chat button, enabling text communication during calls.
+
+
+
+```kotlin
+.hideChatButton(false)
+```
+
+
+```java
+.hideChatButton(false)
+```
+
+
+
+| Parameter | Type | Default |
+|-----------|------|---------|
+| `hide` | boolean | true |
+
+
+| Enum | Value | Description |
+|------|-------|-------------|
+| `SessionType` | `VIDEO` | Video call with camera enabled |
+| | `AUDIO` | Audio-only call |
+| `LayoutType` | `TILE` | Grid layout showing all participants equally |
+| | `SPOTLIGHT` | Focus on active speaker with others in sidebar |
+| | `SIDEBAR` | Main speaker with participants in a sidebar |
+| `AudioMode` | `SPEAKER` | Device loudspeaker |
+| | `EARPIECE` | Phone earpiece |
+| | `BLUETOOTH` | Connected Bluetooth device |
+| | `HEADPHONES` | Wired headphones |
+| `CameraFacing` | `FRONT` | Front-facing camera |
+| | `BACK` | Rear camera |
+
diff --git a/calls/android/session-status-listener.mdx b/calls/android/session-status-listener.mdx
new file mode 100644
index 00000000..71070673
--- /dev/null
+++ b/calls/android/session-status-listener.mdx
@@ -0,0 +1,503 @@
+---
+title: "Session Status Listener"
+sidebarTitle: "Session Status Listener"
+---
+
+Monitor the call session lifecycle with `SessionStatusListener`. This listener provides callbacks for session join/leave events, connection status changes, and session timeouts.
+
+## Prerequisites
+
+- An active [call session](/calls/android/join-session)
+- Access to the `CallSession` instance
+
+## Register Listener
+
+Register a `SessionStatusListener` to receive session status callbacks:
+
+
+
+```kotlin
+val callSession = CallSession.getInstance()
+
+callSession.addSessionStatusListener(this, object : SessionStatusListener() {
+ override fun onSessionJoined() {
+ Log.d(TAG, "Successfully joined the session")
+ }
+
+ override fun onSessionLeft() {
+ Log.d(TAG, "Left the session")
+ }
+
+ override fun onSessionTimedOut() {
+ Log.d(TAG, "Session timed out")
+ }
+
+ override fun onConnectionLost() {
+ Log.d(TAG, "Connection lost")
+ }
+
+ override fun onConnectionRestored() {
+ Log.d(TAG, "Connection restored")
+ }
+
+ override fun onConnectionClosed() {
+ Log.d(TAG, "Connection closed")
+ }
+})
+```
+
+
+```java
+CallSession callSession = CallSession.getInstance();
+
+callSession.addSessionStatusListener(this, new SessionStatusListener() {
+ @Override
+ public void onSessionJoined() {
+ Log.d(TAG, "Successfully joined the session");
+ }
+
+ @Override
+ public void onSessionLeft() {
+ Log.d(TAG, "Left the session");
+ }
+
+ @Override
+ public void onSessionTimedOut() {
+ Log.d(TAG, "Session timed out");
+ }
+
+ @Override
+ public void onConnectionLost() {
+ Log.d(TAG, "Connection lost");
+ }
+
+ @Override
+ public void onConnectionRestored() {
+ Log.d(TAG, "Connection restored");
+ }
+
+ @Override
+ public void onConnectionClosed() {
+ Log.d(TAG, "Connection closed");
+ }
+});
+```
+
+
+
+
+The listener is automatically removed when the `LifecycleOwner` (Activity/Fragment) is destroyed, preventing memory leaks.
+
+
+---
+
+## Callbacks
+
+### onSessionJoined
+
+Triggered when you successfully join a call session.
+
+
+
+```kotlin
+override fun onSessionJoined() {
+ Log.d(TAG, "Successfully joined the session")
+ // Update UI to show call screen
+ // Start any call-related services
+}
+```
+
+
+```java
+@Override
+public void onSessionJoined() {
+ Log.d(TAG, "Successfully joined the session");
+ // Update UI to show call screen
+ // Start any call-related services
+}
+```
+
+
+
+**Use Cases:**
+- Update UI to display the call interface
+- Start foreground service for ongoing call notification
+- Initialize call-related features
+
+---
+
+### onSessionLeft
+
+Triggered when you leave the call session (either by calling `leaveSession()` or being removed).
+
+
+
+```kotlin
+override fun onSessionLeft() {
+ Log.d(TAG, "Left the session")
+ // Clean up resources
+ // Navigate back to previous screen
+ finish()
+}
+```
+
+
+```java
+@Override
+public void onSessionLeft() {
+ Log.d(TAG, "Left the session");
+ // Clean up resources
+ // Navigate back to previous screen
+ finish();
+}
+```
+
+
+
+**Use Cases:**
+- Clean up call-related resources
+- Stop foreground service
+- Navigate away from call screen
+
+---
+
+### onSessionTimedOut
+
+Triggered when the session times out due to inactivity (e.g., being alone in the call for too long).
+
+
+
+```kotlin
+override fun onSessionTimedOut() {
+ Log.d(TAG, "Session timed out due to inactivity")
+ // Show timeout message to user
+ Toast.makeText(this, "Call ended due to inactivity", Toast.LENGTH_SHORT).show()
+ finish()
+}
+```
+
+
+```java
+@Override
+public void onSessionTimedOut() {
+ Log.d(TAG, "Session timed out due to inactivity");
+ // Show timeout message to user
+ Toast.makeText(this, "Call ended due to inactivity", Toast.LENGTH_SHORT).show();
+ finish();
+}
+```
+
+
+
+
+Configure the timeout period using `setIdleTimeoutPeriod()` in [SessionSettings](/calls/android/session-settings). Default is 300 seconds (5 minutes).
+
+
+**Use Cases:**
+- Display timeout notification to user
+- Clean up resources and navigate away
+- Log analytics event
+
+---
+
+### onConnectionLost
+
+Triggered when the connection to the call server is lost (e.g., network issues).
+
+
+
+```kotlin
+override fun onConnectionLost() {
+ Log.d(TAG, "Connection lost - attempting to reconnect")
+ // Show reconnecting indicator
+ showReconnectingUI()
+}
+```
+
+
+```java
+@Override
+public void onConnectionLost() {
+ Log.d(TAG, "Connection lost - attempting to reconnect");
+ // Show reconnecting indicator
+ showReconnectingUI();
+}
+```
+
+
+
+**Use Cases:**
+- Display "Reconnecting..." indicator
+- Disable call controls temporarily
+- Log connection issue for debugging
+
+---
+
+### onConnectionRestored
+
+Triggered when the connection is restored after being lost.
+
+
+
+```kotlin
+override fun onConnectionRestored() {
+ Log.d(TAG, "Connection restored")
+ // Hide reconnecting indicator
+ hideReconnectingUI()
+ // Re-enable call controls
+}
+```
+
+
+```java
+@Override
+public void onConnectionRestored() {
+ Log.d(TAG, "Connection restored");
+ // Hide reconnecting indicator
+ hideReconnectingUI();
+ // Re-enable call controls
+}
+```
+
+
+
+**Use Cases:**
+- Hide reconnecting indicator
+- Re-enable call controls
+- Show success notification
+
+---
+
+### onConnectionClosed
+
+Triggered when the connection is permanently closed (cannot be restored).
+
+
+
+```kotlin
+override fun onConnectionClosed() {
+ Log.d(TAG, "Connection closed permanently")
+ // Show error message
+ Toast.makeText(this, "Call connection lost", Toast.LENGTH_SHORT).show()
+ // Clean up and exit
+ finish()
+}
+```
+
+
+```java
+@Override
+public void onConnectionClosed() {
+ Log.d(TAG, "Connection closed permanently");
+ // Show error message
+ Toast.makeText(this, "Call connection lost", Toast.LENGTH_SHORT).show();
+ // Clean up and exit
+ finish();
+}
+```
+
+
+
+**Use Cases:**
+- Display error message to user
+- Clean up resources
+- Navigate away from call screen
+
+---
+
+## Complete Example
+
+Here's a complete example handling all session status events:
+
+
+
+```kotlin
+class CallActivity : AppCompatActivity() {
+ private lateinit var callSession: CallSession
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_call)
+
+ callSession = CallSession.getInstance()
+ setupSessionStatusListener()
+ }
+
+ private fun setupSessionStatusListener() {
+ callSession.addSessionStatusListener(this, object : SessionStatusListener() {
+ override fun onSessionJoined() {
+ Log.d(TAG, "Session joined")
+ runOnUiThread {
+ // Start ongoing call service
+ CometChatOngoingCallService.launch(this@CallActivity)
+ }
+ }
+
+ override fun onSessionLeft() {
+ Log.d(TAG, "Session left")
+ runOnUiThread {
+ CometChatOngoingCallService.abort(this@CallActivity)
+ finish()
+ }
+ }
+
+ override fun onSessionTimedOut() {
+ Log.d(TAG, "Session timed out")
+ runOnUiThread {
+ Toast.makeText(
+ this@CallActivity,
+ "Call ended due to inactivity",
+ Toast.LENGTH_SHORT
+ ).show()
+ CometChatOngoingCallService.abort(this@CallActivity)
+ finish()
+ }
+ }
+
+ override fun onConnectionLost() {
+ Log.d(TAG, "Connection lost")
+ runOnUiThread {
+ showReconnectingOverlay(true)
+ }
+ }
+
+ override fun onConnectionRestored() {
+ Log.d(TAG, "Connection restored")
+ runOnUiThread {
+ showReconnectingOverlay(false)
+ }
+ }
+
+ override fun onConnectionClosed() {
+ Log.d(TAG, "Connection closed")
+ runOnUiThread {
+ Toast.makeText(
+ this@CallActivity,
+ "Connection lost. Please try again.",
+ Toast.LENGTH_SHORT
+ ).show()
+ CometChatOngoingCallService.abort(this@CallActivity)
+ finish()
+ }
+ }
+ })
+ }
+
+ private fun showReconnectingOverlay(show: Boolean) {
+ // Show/hide reconnecting UI overlay
+ }
+
+ companion object {
+ private const val TAG = "CallActivity"
+ }
+}
+```
+
+
+```java
+public class CallActivity extends AppCompatActivity {
+ private static final String TAG = "CallActivity";
+ private CallSession callSession;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_call);
+
+ callSession = CallSession.getInstance();
+ setupSessionStatusListener();
+ }
+
+ private void setupSessionStatusListener() {
+ callSession.addSessionStatusListener(this, new SessionStatusListener() {
+ @Override
+ public void onSessionJoined() {
+ Log.d(TAG, "Session joined");
+ runOnUiThread(() -> {
+ // Start ongoing call service
+ CometChatOngoingCallService.launch(CallActivity.this);
+ });
+ }
+
+ @Override
+ public void onSessionLeft() {
+ Log.d(TAG, "Session left");
+ runOnUiThread(() -> {
+ CometChatOngoingCallService.abort(CallActivity.this);
+ finish();
+ });
+ }
+
+ @Override
+ public void onSessionTimedOut() {
+ Log.d(TAG, "Session timed out");
+ runOnUiThread(() -> {
+ Toast.makeText(
+ CallActivity.this,
+ "Call ended due to inactivity",
+ Toast.LENGTH_SHORT
+ ).show();
+ CometChatOngoingCallService.abort(CallActivity.this);
+ finish();
+ });
+ }
+
+ @Override
+ public void onConnectionLost() {
+ Log.d(TAG, "Connection lost");
+ runOnUiThread(() -> showReconnectingOverlay(true));
+ }
+
+ @Override
+ public void onConnectionRestored() {
+ Log.d(TAG, "Connection restored");
+ runOnUiThread(() -> showReconnectingOverlay(false));
+ }
+
+ @Override
+ public void onConnectionClosed() {
+ Log.d(TAG, "Connection closed");
+ runOnUiThread(() -> {
+ Toast.makeText(
+ CallActivity.this,
+ "Connection lost. Please try again.",
+ Toast.LENGTH_SHORT
+ ).show();
+ CometChatOngoingCallService.abort(CallActivity.this);
+ finish();
+ });
+ }
+ });
+ }
+
+ private void showReconnectingOverlay(boolean show) {
+ // Show/hide reconnecting UI overlay
+ }
+}
+```
+
+
+
+---
+
+## Callbacks Summary
+
+| Callback | Description |
+|----------|-------------|
+| `onSessionJoined()` | Successfully joined the call session |
+| `onSessionLeft()` | Left the call session |
+| `onSessionTimedOut()` | Session ended due to inactivity timeout |
+| `onConnectionLost()` | Connection to server lost (reconnecting) |
+| `onConnectionRestored()` | Connection restored after being lost |
+| `onConnectionClosed()` | Connection permanently closed |
+
+## Next Steps
+
+
+
+ Handle participant join/leave and state changes
+
+
+ Handle recording, screen share, and media state changes
+
+
diff --git a/calls/android/setup.mdx b/calls/android/setup.mdx
new file mode 100644
index 00000000..9c7cf0d9
--- /dev/null
+++ b/calls/android/setup.mdx
@@ -0,0 +1,192 @@
+---
+title: "Setup"
+sidebarTitle: "Setup"
+---
+
+This guide walks you through installing the CometChat Calls SDK and initializing it in your Android application.
+
+## Add the CometChat Dependency
+
+### Step 1: Add Repository
+
+Add the CometChat repository URL to your **project level** `build.gradle` file in the `repositories` block:
+
+
+
+```groovy
+allprojects {
+ repositories {
+ maven {
+ url "https://dl.cloudsmith.io/public/cometchat/cometchat/maven/"
+ }
+ }
+}
+```
+
+
+```kotlin
+allprojects {
+ repositories {
+ maven {
+ url = uri("https://dl.cloudsmith.io/public/cometchat/cometchat/maven/")
+ }
+ }
+}
+```
+
+
+
+### Step 2: Add Dependencies
+
+Add the Calls SDK dependency to your **app level** `build.gradle` file:
+
+
+
+```groovy
+dependencies {
+ implementation "com.cometchat:calls-sdk-android:5.0.0"
+}
+```
+
+
+```kotlin
+dependencies {
+ implementation("com.cometchat:calls-sdk-android:5.0.0")
+}
+```
+
+
+
+### Step 3: Configure Java Version
+
+Add Java 8 compatibility to the `android` section of your **app level** `build.gradle`:
+
+
+
+```groovy
+android {
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+}
+```
+
+
+```kotlin
+android {
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_1_8
+ targetCompatibility = JavaVersion.VERSION_1_8
+ }
+}
+```
+
+
+
+## Add Permissions
+
+Add the required permissions to your `AndroidManifest.xml`:
+
+```xml
+
+
+
+
+
+```
+
+
+For Android 6.0 (API level 23) and above, you must request camera and microphone permissions at runtime before starting a call.
+
+
+## Initialize CometChat Calls
+
+The `init()` method initializes the SDK with your app credentials. Call this method once when your application starts, typically in your `Application` class or main `Activity`.
+
+### CallAppSettings
+
+The `CallAppSettings` class configures the SDK initialization:
+
+| Parameter | Type | Required | Description |
+|-----------|------|----------|-------------|
+| `appId` | String | Yes | Your CometChat App ID |
+| `region` | String | Yes | Your app region (`us` or `eu`) |
+
+
+
+```kotlin
+import com.cometchat.calls.core.CometChatCalls
+import com.cometchat.calls.core.CallAppSettings
+
+val appId = "APP_ID" // Replace with your App ID
+val region = "REGION" // Replace with your Region ("us" or "eu")
+
+val callAppSettings = CallAppSettings.CallAppSettingBuilder(appId, region).build()
+
+CometChatCalls.init(this, callAppSettings, object : CometChatCalls.CallbackListener() {
+ override fun onSuccess(message: String) {
+ Log.d(TAG, "CometChat Calls SDK initialized successfully")
+ }
+
+ override fun onError(e: CometChatException) {
+ Log.e(TAG, "CometChat Calls SDK initialization failed: ${e.message}")
+ }
+})
+```
+
+
+```java
+import com.cometchat.calls.core.CometChatCalls;
+import com.cometchat.calls.core.CallAppSettings;
+
+String appId = "APP_ID"; // Replace with your App ID
+String region = "REGION"; // Replace with your Region ("us" or "eu")
+
+CallAppSettings callAppSettings = new CallAppSettings.CallAppSettingBuilder(appId, region).build();
+
+CometChatCalls.init(this, callAppSettings, new CometChatCalls.CallbackListener() {
+ @Override
+ public void onSuccess(String message) {
+ Log.d(TAG, "CometChat Calls SDK initialized successfully");
+ }
+
+ @Override
+ public void onError(CometChatException e) {
+ Log.e(TAG, "CometChat Calls SDK initialization failed: " + e.getMessage());
+ }
+});
+```
+
+
+
+| Parameter | Description |
+|-----------|-------------|
+| `this` | Android context (Application or Activity context) |
+| `callAppSettings` | Configuration object with App ID and Region |
+| `listener` | Callback listener for success/error handling |
+
+## Check Initialization Status
+
+You can verify if the SDK has been initialized using the `isInitialized()` method:
+
+
+
+```kotlin
+if (CometChatCalls.isInitialized()) {
+ // SDK is ready to use
+} else {
+ // Initialize the SDK first
+}
+```
+
+
+```java
+if (CometChatCalls.isInitialized()) {
+ // SDK is ready to use
+} else {
+ // Initialize the SDK first
+}
+```
+
+
diff --git a/calls/android/share-invite.mdx b/calls/android/share-invite.mdx
new file mode 100644
index 00000000..ec67277e
--- /dev/null
+++ b/calls/android/share-invite.mdx
@@ -0,0 +1,559 @@
+---
+title: "Share Invite"
+sidebarTitle: "Share Invite"
+---
+
+Enable participants to invite others to join a call by sharing a meeting link. The share invite button opens the system share sheet, allowing users to send the invite via any messaging app, email, or social media.
+
+## Overview
+
+The share invite feature:
+- Generates a shareable meeting link with session ID
+- Opens Android's native share sheet
+- Works with any app that supports text sharing
+- Can be triggered from the default button or custom UI
+
+```mermaid
+flowchart LR
+ A[Share Button] --> B[Generate Link]
+ B --> C[Share Sheet]
+ C --> D[WhatsApp]
+ C --> E[Email]
+ C --> F[SMS]
+ C --> G[Other Apps]
+```
+
+## Prerequisites
+
+- CometChat Calls SDK integrated ([Setup](/calls/android/setup))
+- Active call session ([Join Session](/calls/android/join-session))
+
+---
+
+## Step 1: Enable Share Button
+
+Configure session settings to show the share invite button:
+
+
+
+```kotlin
+val sessionSettings = CometChatCalls.SessionSettingsBuilder()
+ .hideShareInviteButton(false) // Show the share button
+ .build()
+```
+
+
+```java
+SessionSettings sessionSettings = new CometChatCalls.SessionSettingsBuilder()
+ .hideShareInviteButton(false) // Show the share button
+ .build();
+```
+
+
+
+---
+
+## Step 2: Handle Share Button Click
+
+Listen for the share button click using `ButtonClickListener`:
+
+
+
+```kotlin
+private fun setupShareButtonListener() {
+ val callSession = CallSession.getInstance()
+
+ callSession.addButtonClickListener(this, object : ButtonClickListener() {
+ override fun onShareInviteButtonClicked() {
+ shareInviteLink()
+ }
+ })
+}
+```
+
+
+```java
+private void setupShareButtonListener() {
+ CallSession callSession = CallSession.getInstance();
+
+ callSession.addButtonClickListener(this, new ButtonClickListener() {
+ @Override
+ public void onShareInviteButtonClicked() {
+ shareInviteLink();
+ }
+ });
+}
+```
+
+
+
+---
+
+## Step 3: Generate and Share Link
+
+Create the meeting invite URL and open the share sheet:
+
+
+
+```kotlin
+private fun shareInviteLink() {
+ val inviteUrl = generateInviteUrl(sessionId, meetingName)
+
+ val shareIntent = Intent(Intent.ACTION_SEND).apply {
+ type = "text/plain"
+ putExtra(Intent.EXTRA_SUBJECT, "Join my meeting: $meetingName")
+ putExtra(Intent.EXTRA_TEXT, inviteUrl)
+ }
+
+ startActivity(Intent.createChooser(shareIntent, "Share meeting link"))
+}
+
+private fun generateInviteUrl(sessionId: String, meetingName: String): String {
+ val encodedName = try {
+ java.net.URLEncoder.encode(meetingName, "UTF-8")
+ } catch (e: Exception) {
+ meetingName
+ }
+
+ // Replace with your app's deep link or web URL
+ return "https://yourapp.com/join?sessionId=$sessionId&name=$encodedName"
+}
+```
+
+
+```java
+private void shareInviteLink() {
+ String inviteUrl = generateInviteUrl(sessionId, meetingName);
+
+ Intent shareIntent = new Intent(Intent.ACTION_SEND);
+ shareIntent.setType("text/plain");
+ shareIntent.putExtra(Intent.EXTRA_SUBJECT, "Join my meeting: " + meetingName);
+ shareIntent.putExtra(Intent.EXTRA_TEXT, inviteUrl);
+
+ startActivity(Intent.createChooser(shareIntent, "Share meeting link"));
+}
+
+private String generateInviteUrl(String sessionId, String meetingName) {
+ String encodedName;
+ try {
+ encodedName = java.net.URLEncoder.encode(meetingName, "UTF-8");
+ } catch (Exception e) {
+ encodedName = meetingName;
+ }
+
+ // Replace with your app's deep link or web URL
+ return "https://yourapp.com/join?sessionId=" + sessionId + "&name=" + encodedName;
+}
+```
+
+
+
+---
+
+## Custom Share Message
+
+Customize the share message with more details:
+
+
+
+```kotlin
+private fun shareInviteLink() {
+ val inviteUrl = generateInviteUrl(sessionId, meetingName)
+
+ val shareMessage = """
+ 📞 Join my meeting: $meetingName
+
+ Click the link below to join:
+ $inviteUrl
+
+ Meeting ID: $sessionId
+ """.trimIndent()
+
+ val shareIntent = Intent(Intent.ACTION_SEND).apply {
+ type = "text/plain"
+ putExtra(Intent.EXTRA_SUBJECT, "Meeting Invite: $meetingName")
+ putExtra(Intent.EXTRA_TEXT, shareMessage)
+ }
+
+ startActivity(Intent.createChooser(shareIntent, "Share meeting link"))
+}
+```
+
+
+```java
+private void shareInviteLink() {
+ String inviteUrl = generateInviteUrl(sessionId, meetingName);
+
+ String shareMessage = "📞 Join my meeting: " + meetingName + "\n\n" +
+ "Click the link below to join:\n" +
+ inviteUrl + "\n\n" +
+ "Meeting ID: " + sessionId;
+
+ Intent shareIntent = new Intent(Intent.ACTION_SEND);
+ shareIntent.setType("text/plain");
+ shareIntent.putExtra(Intent.EXTRA_SUBJECT, "Meeting Invite: " + meetingName);
+ shareIntent.putExtra(Intent.EXTRA_TEXT, shareMessage);
+
+ startActivity(Intent.createChooser(shareIntent, "Share meeting link"));
+}
+```
+
+
+
+---
+
+## Deep Link Handling
+
+To allow users to join directly from the shared link, implement deep link handling in your app.
+
+### Configure Deep Links
+
+Add intent filters to your `AndroidManifest.xml`:
+
+```xml
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+### Handle Deep Link
+
+
+
+```kotlin
+class JoinActivity : AppCompatActivity() {
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ handleDeepLink(intent)
+ }
+
+ override fun onNewIntent(intent: Intent) {
+ super.onNewIntent(intent)
+ handleDeepLink(intent)
+ }
+
+ private fun handleDeepLink(intent: Intent) {
+ val data = intent.data ?: return
+
+ val sessionId = data.getQueryParameter("sessionId")
+ val meetingName = data.getQueryParameter("name")
+
+ if (sessionId != null) {
+ // Check if user is logged in
+ if (CometChat.getLoggedInUser() != null) {
+ joinCall(sessionId, meetingName ?: "Meeting")
+ } else {
+ // Save params and redirect to login
+ saveJoinParams(sessionId, meetingName)
+ startActivity(Intent(this, LoginActivity::class.java))
+ }
+ }
+ }
+
+ private fun joinCall(sessionId: String, meetingName: String) {
+ val intent = Intent(this, CallActivity::class.java).apply {
+ putExtra("SESSION_ID", sessionId)
+ putExtra("MEETING_NAME", meetingName)
+ }
+ startActivity(intent)
+ finish()
+ }
+
+ private fun saveJoinParams(sessionId: String, meetingName: String?) {
+ getSharedPreferences("join_params", MODE_PRIVATE).edit().apply {
+ putString("sessionId", sessionId)
+ putString("meetingName", meetingName)
+ apply()
+ }
+ }
+}
+```
+
+
+```java
+public class JoinActivity extends AppCompatActivity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ handleDeepLink(getIntent());
+ }
+
+ @Override
+ protected void onNewIntent(Intent intent) {
+ super.onNewIntent(intent);
+ handleDeepLink(intent);
+ }
+
+ private void handleDeepLink(Intent intent) {
+ Uri data = intent.getData();
+ if (data == null) return;
+
+ String sessionId = data.getQueryParameter("sessionId");
+ String meetingName = data.getQueryParameter("name");
+
+ if (sessionId != null) {
+ // Check if user is logged in
+ if (CometChat.getLoggedInUser() != null) {
+ joinCall(sessionId, meetingName != null ? meetingName : "Meeting");
+ } else {
+ // Save params and redirect to login
+ saveJoinParams(sessionId, meetingName);
+ startActivity(new Intent(this, LoginActivity.class));
+ }
+ }
+ }
+
+ private void joinCall(String sessionId, String meetingName) {
+ Intent intent = new Intent(this, CallActivity.class);
+ intent.putExtra("SESSION_ID", sessionId);
+ intent.putExtra("MEETING_NAME", meetingName);
+ startActivity(intent);
+ finish();
+ }
+
+ private void saveJoinParams(String sessionId, String meetingName) {
+ getSharedPreferences("join_params", MODE_PRIVATE)
+ .edit()
+ .putString("sessionId", sessionId)
+ .putString("meetingName", meetingName)
+ .apply();
+ }
+}
+```
+
+
+
+---
+
+## Custom Share Button
+
+If you want to use a custom share button instead of the default one, hide the default button and implement your own:
+
+
+
+```kotlin
+// Hide default share button
+val sessionSettings = CometChatCalls.SessionSettingsBuilder()
+ .hideShareInviteButton(true)
+ .build()
+
+// Add your custom button
+customShareButton.setOnClickListener {
+ shareInviteLink()
+}
+```
+
+
+```java
+// Hide default share button
+SessionSettings sessionSettings = new CometChatCalls.SessionSettingsBuilder()
+ .hideShareInviteButton(true)
+ .build();
+
+// Add your custom button
+customShareButton.setOnClickListener(v -> shareInviteLink());
+```
+
+
+
+---
+
+## Complete Example
+
+
+
+```kotlin
+class CallActivity : AppCompatActivity() {
+
+ private var sessionId: String = ""
+ private var meetingName: String = ""
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_call)
+
+ sessionId = intent.getStringExtra("SESSION_ID") ?: return
+ meetingName = intent.getStringExtra("MEETING_NAME") ?: "Meeting"
+
+ setupShareButtonListener()
+ joinCall()
+ }
+
+ private fun setupShareButtonListener() {
+ CallSession.getInstance().addButtonClickListener(this, object : ButtonClickListener() {
+ override fun onShareInviteButtonClicked() {
+ shareInviteLink()
+ }
+ })
+ }
+
+ private fun shareInviteLink() {
+ val inviteUrl = generateInviteUrl(sessionId, meetingName)
+
+ val shareMessage = """
+ 📞 Join my meeting: $meetingName
+
+ $inviteUrl
+ """.trimIndent()
+
+ val shareIntent = Intent(Intent.ACTION_SEND).apply {
+ type = "text/plain"
+ putExtra(Intent.EXTRA_TEXT, shareMessage)
+ }
+
+ startActivity(Intent.createChooser(shareIntent, "Share meeting link"))
+ }
+
+ private fun generateInviteUrl(sessionId: String, meetingName: String): String {
+ val encodedName = java.net.URLEncoder.encode(meetingName, "UTF-8")
+ return "https://yourapp.com/join?sessionId=$sessionId&name=$encodedName"
+ }
+
+ private fun joinCall() {
+ val container = findViewById(R.id.callContainer)
+
+ val sessionSettings = CometChatCalls.SessionSettingsBuilder()
+ .setTitle(meetingName)
+ .hideShareInviteButton(false)
+ .build()
+
+ CometChatCalls.joinSession(
+ sessionId = sessionId,
+ sessionSettings = sessionSettings,
+ view = container,
+ context = this,
+ listener = object : CometChatCalls.CallbackListener() {
+ override fun onSuccess(session: CallSession) {
+ Log.d(TAG, "Joined call")
+ }
+
+ override fun onError(e: CometChatException) {
+ Log.e(TAG, "Join failed: ${e.message}")
+ }
+ }
+ )
+ }
+
+ companion object {
+ private const val TAG = "CallActivity"
+ }
+}
+```
+
+
+```java
+public class CallActivity extends AppCompatActivity {
+
+ private static final String TAG = "CallActivity";
+ private String sessionId;
+ private String meetingName;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_call);
+
+ sessionId = getIntent().getStringExtra("SESSION_ID");
+ meetingName = getIntent().getStringExtra("MEETING_NAME");
+
+ if (sessionId == null) return;
+ if (meetingName == null) meetingName = "Meeting";
+
+ setupShareButtonListener();
+ joinCall();
+ }
+
+ private void setupShareButtonListener() {
+ CallSession.getInstance().addButtonClickListener(this, new ButtonClickListener() {
+ @Override
+ public void onShareInviteButtonClicked() {
+ shareInviteLink();
+ }
+ });
+ }
+
+ private void shareInviteLink() {
+ String inviteUrl = generateInviteUrl(sessionId, meetingName);
+
+ String shareMessage = "📞 Join my meeting: " + meetingName + "\n\n" + inviteUrl;
+
+ Intent shareIntent = new Intent(Intent.ACTION_SEND);
+ shareIntent.setType("text/plain");
+ shareIntent.putExtra(Intent.EXTRA_TEXT, shareMessage);
+
+ startActivity(Intent.createChooser(shareIntent, "Share meeting link"));
+ }
+
+ private String generateInviteUrl(String sessionId, String meetingName) {
+ try {
+ String encodedName = java.net.URLEncoder.encode(meetingName, "UTF-8");
+ return "https://yourapp.com/join?sessionId=" + sessionId + "&name=" + encodedName;
+ } catch (Exception e) {
+ return "https://yourapp.com/join?sessionId=" + sessionId;
+ }
+ }
+
+ private void joinCall() {
+ FrameLayout container = findViewById(R.id.callContainer);
+
+ SessionSettings sessionSettings = new CometChatCalls.SessionSettingsBuilder()
+ .setTitle(meetingName)
+ .hideShareInviteButton(false)
+ .build();
+
+ CometChatCalls.joinSession(
+ sessionId,
+ sessionSettings,
+ container,
+ this,
+ new CometChatCalls.CallbackListener() {
+ @Override
+ public void onSuccess(CallSession session) {
+ Log.d(TAG, "Joined call");
+ }
+
+ @Override
+ public void onError(CometChatException e) {
+ Log.e(TAG, "Join failed: " + e.getMessage());
+ }
+ }
+ );
+ }
+}
+```
+
+
+
+---
+
+## Related Documentation
+
+- [Button Click Listener](/calls/android/button-click-listener) - Handle button clicks
+- [SessionSettingsBuilder](/calls/android/session-settings) - Configure share button visibility
+- [Join Session](/calls/android/join-session) - Join a call session
diff --git a/calls/android/video-controls.mdx b/calls/android/video-controls.mdx
new file mode 100644
index 00000000..d512afe3
--- /dev/null
+++ b/calls/android/video-controls.mdx
@@ -0,0 +1,245 @@
+---
+title: "Video Controls"
+sidebarTitle: "Video Controls"
+---
+
+Control video during an active call session. These methods allow you to pause/resume the local camera and switch between front and back cameras.
+
+## Prerequisites
+
+- An active [call session](/calls/android/join-session)
+- Access to the `CallSession` instance
+- Camera permissions granted
+
+## Get CallSession Instance
+
+All video control methods are called on the `CallSession` singleton:
+
+
+
+```kotlin
+val callSession = CallSession.getInstance()
+```
+
+
+```java
+CallSession callSession = CallSession.getInstance();
+```
+
+
+
+---
+
+## Pause Video
+
+Turn off the local camera. Other participants will see a placeholder instead of your video feed.
+
+
+
+```kotlin
+callSession.pauseVideo()
+```
+
+
+```java
+callSession.pauseVideo();
+```
+
+
+
+
+When you pause your video, the `onVideoPaused()` callback is triggered on your `MediaEventsListener`.
+
+
+---
+
+## Resume Video
+
+Turn on the local camera to resume transmitting video.
+
+
+
+```kotlin
+callSession.resumeVideo()
+```
+
+
+```java
+callSession.resumeVideo();
+```
+
+
+
+
+When you resume your video, the `onVideoResumed()` callback is triggered on your `MediaEventsListener`.
+
+
+---
+
+## Switch Camera
+
+Toggle between the front-facing and rear cameras.
+
+
+
+```kotlin
+callSession.switchCamera()
+```
+
+
+```java
+callSession.switchCamera();
+```
+
+
+
+
+When the camera is switched, the `onCameraFacingChanged(CameraFacing)` callback is triggered on your `MediaEventsListener`.
+
+
+### CameraFacing Enum
+
+| Value | Description |
+|-------|-------------|
+| `FRONT` | Front-facing camera (selfie camera) |
+| `BACK` | Rear camera |
+
+---
+
+## Listen for Video Events
+
+Register a `MediaEventsListener` to receive callbacks when video state changes:
+
+
+
+```kotlin
+callSession.addMediaEventsListener(this, object : MediaEventsListener {
+ override fun onVideoPaused() {
+ Log.d(TAG, "Video paused")
+ // Update UI to show video off state
+ }
+
+ override fun onVideoResumed() {
+ Log.d(TAG, "Video resumed")
+ // Update UI to show video on state
+ }
+
+ override fun onCameraFacingChanged(cameraFacing: CameraFacing) {
+ Log.d(TAG, "Camera switched to: ${cameraFacing.value}")
+ // Update UI to reflect camera change
+ }
+
+ // Other MediaEventsListener callbacks...
+ override fun onRecordingStarted() {}
+ override fun onRecordingStopped() {}
+ override fun onScreenShareStarted() {}
+ override fun onScreenShareStopped() {}
+ override fun onAudioModeChanged(audioMode: AudioMode) {}
+ override fun onAudioMuted() {}
+ override fun onAudioUnMuted() {}
+})
+```
+
+
+```java
+callSession.addMediaEventsListener(this, new MediaEventsListener() {
+ @Override
+ public void onVideoPaused() {
+ Log.d(TAG, "Video paused");
+ // Update UI to show video off state
+ }
+
+ @Override
+ public void onVideoResumed() {
+ Log.d(TAG, "Video resumed");
+ // Update UI to show video on state
+ }
+
+ @Override
+ public void onCameraFacingChanged(CameraFacing cameraFacing) {
+ Log.d(TAG, "Camera switched to: " + cameraFacing.getValue());
+ // Update UI to reflect camera change
+ }
+
+ // Other MediaEventsListener callbacks...
+ @Override
+ public void onRecordingStarted() {}
+ @Override
+ public void onRecordingStopped() {}
+ @Override
+ public void onScreenShareStarted() {}
+ @Override
+ public void onScreenShareStopped() {}
+ @Override
+ public void onAudioModeChanged(AudioMode audioMode) {}
+ @Override
+ public void onAudioMuted() {}
+ @Override
+ public void onAudioUnMuted() {}
+});
+```
+
+
+
+---
+
+## Initial Video Settings
+
+You can configure the initial video state when joining a session using `SessionSettings`:
+
+
+
+```kotlin
+val sessionSettings = CometChatCalls.SessionSettingsBuilder()
+ .startVideoPaused(true) // Start with camera off
+ .setInitialCameraFacing(CameraFacing.FRONT) // Start with front camera
+ .setType(SessionType.VIDEO) // Video call (not audio-only)
+ .build()
+```
+
+
+```java
+SessionSettings sessionSettings = new CometChatCalls.SessionSettingsBuilder()
+ .startVideoPaused(true) // Start with camera off
+ .setInitialCameraFacing(CameraFacing.FRONT) // Start with front camera
+ .setType(SessionType.VIDEO) // Video call (not audio-only)
+ .build();
+```
+
+
+
+---
+
+## Hide Video Controls in UI
+
+You can hide the built-in video control buttons using `SessionSettings`:
+
+
+
+```kotlin
+val sessionSettings = CometChatCalls.SessionSettingsBuilder()
+ .hideToggleVideoButton(true) // Hide the video on/off button
+ .hideSwitchCameraButton(true) // Hide the camera flip button
+ .build()
+```
+
+
+```java
+SessionSettings sessionSettings = new CometChatCalls.SessionSettingsBuilder()
+ .hideToggleVideoButton(true) // Hide the video on/off button
+ .hideSwitchCameraButton(true) // Hide the camera flip button
+ .build();
+```
+
+
+
+## Next Steps
+
+
+
+ Record call sessions
+
+
+ Handle all media events
+
+
diff --git a/calls/android/voip-calling.mdx b/calls/android/voip-calling.mdx
new file mode 100644
index 00000000..a43b83ef
--- /dev/null
+++ b/calls/android/voip-calling.mdx
@@ -0,0 +1,2151 @@
+---
+title: "VoIP Calling"
+sidebarTitle: "VoIP Calling"
+---
+
+Implement native VoIP calling that works when your app is in the background or killed. This guide shows how to integrate Android's Telecom framework with CometChat to display system call UI, handle calls from the lock screen, and provide a native calling experience.
+
+## Overview
+
+VoIP calling differs from [basic in-app ringing](/calls/android/ringing) by leveraging Android's `ConnectionService` to:
+- Show incoming calls on lock screen with system UI
+- Handle calls when app is in background or killed
+- Integrate with Bluetooth, car systems, and wearables
+- Provide consistent call experience across Android devices
+
+```mermaid
+flowchart TB
+ subgraph "Incoming Call Flow"
+ A[FCM Push Notification] --> B[FirebaseMessagingService]
+ B --> C{App State?}
+ C -->|Background/Killed| D[ConnectionService]
+ C -->|Foreground| E[CometChat CallListener]
+ D --> F[System Call UI]
+ E --> G[In-App Call UI]
+ F --> H{User Action}
+ G --> H
+ H -->|Accept| I[CometChat.acceptCall]
+ H -->|Reject| J[CometChat.rejectCall]
+ I --> K[CometChatCalls.joinSession]
+ end
+```
+
+## Prerequisites
+
+Before implementing VoIP calling, ensure you have:
+
+- [CometChat Chat SDK](/sdk/android/overview) and [Calls SDK](/calls/android/setup) integrated
+- [Firebase Cloud Messaging (FCM)](/notifications/android-push-notifications) configured
+- [Push notifications enabled](/notifications/push-integration) in CometChat Dashboard
+- Android 8.0+ (API 26+) for ConnectionService support
+
+
+This documentation builds on the [Ringing](/calls/android/ringing) functionality. Make sure you understand basic call signaling before implementing VoIP.
+
+
+---
+
+## Architecture Overview
+
+The VoIP implementation consists of several components working together:
+
+```mermaid
+flowchart LR
+ subgraph "Your App"
+ A[FirebaseMessagingService]
+ B[ConnectionService]
+ C[CallActivity]
+ D[CallNotificationManager]
+ end
+
+ subgraph "CometChat"
+ E[Chat SDK]
+ F[Calls SDK]
+ end
+
+ subgraph "Android System"
+ G[Telecom Manager]
+ H[System Call UI]
+ end
+
+ A -->|Call Push| D
+ D -->|Background| B
+ B <-->|Register| G
+ G --> H
+ H -->|Accept/Reject| B
+ B -->|Accept| E
+ E -->|Session| F
+ F --> C
+```
+
+| Component | Purpose |
+|-----------|---------|
+| `FirebaseMessagingService` | Receives push notifications for incoming calls when app is in background |
+| `ConnectionService` | Android Telecom framework integration - manages call state with the system |
+| `CallNotificationManager` | Decides whether to show system call UI or fallback notification |
+| `PhoneAccount` | Registers your app as a calling app with Android's Telecom system |
+| `Connection` | Represents an individual call and handles user actions (accept/reject/hold) |
+
+---
+
+## Step 1: Configure Push Notifications
+
+Push notifications are essential for receiving incoming calls when your app is not in the foreground. When a call is initiated, CometChat sends a push notification to the receiver's device.
+
+
+For detailed FCM setup instructions, see the [Android Push Notifications](/notifications/android-push-notifications) documentation.
+
+
+### 1.1 Add FCM Dependencies
+
+Add Firebase Messaging to your `build.gradle`:
+
+```groovy
+dependencies {
+ implementation 'com.google.firebase:firebase-messaging:23.4.0'
+}
+```
+
+### 1.2 Create FirebaseMessagingService
+
+This service receives push notifications from FCM. When a call notification arrives, it extracts the call data and decides how to display the incoming call based on the app's state.
+
+
+
+
+```kotlin
+class CallFirebaseMessagingService : FirebaseMessagingService() {
+
+ override fun onMessageReceived(remoteMessage: RemoteMessage) {
+ val data = remoteMessage.data
+
+ // Check if this is a call notification by examining the "type" field
+ // CometChat sends call notifications with type="call"
+ if (data["type"] == "call") {
+ handleIncomingCall(data)
+ }
+ }
+
+ private fun handleIncomingCall(data: Map) {
+ // Extract call information from the push payload
+ // These fields are sent by CometChat when a call is initiated
+ val sessionId = data["sessionId"] ?: return
+ val callerName = data["senderName"] ?: "Unknown"
+ val callerUid = data["senderUid"] ?: return
+ val callType = data["callType"] ?: "video" // "audio" or "video"
+ val callerAvatar = data["senderAvatar"]
+
+ // Create a CallData object to pass call information between components
+ val callData = CallData(
+ sessionId = sessionId,
+ callerName = callerName,
+ callerUid = callerUid,
+ callType = callType,
+ callerAvatar = callerAvatar
+ )
+
+ // If app is in foreground, let CometChat's CallListener handle it
+ // This provides a seamless experience with in-app UI
+ if (isAppInForeground()) {
+ return
+ }
+
+ // App is in background/killed - show system call UI via ConnectionService
+ // This ensures the user sees the incoming call even when not using the app
+ CallNotificationManager.showIncomingCall(this, callData)
+ }
+
+ /**
+ * Checks if the app is currently visible to the user.
+ * We only want to use ConnectionService when the app is in background.
+ */
+ private fun isAppInForeground(): Boolean {
+ val activityManager = getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
+ val appProcesses = activityManager.runningAppProcesses ?: return false
+ val packageName = packageName
+ for (appProcess in appProcesses) {
+ if (appProcess.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND
+ && appProcess.processName == packageName) {
+ return true
+ }
+ }
+ return false
+ }
+
+ /**
+ * Called when FCM generates a new token.
+ * Register this token with CometChat to receive push notifications.
+ */
+ override fun onNewToken(token: String) {
+ // Register the FCM token with CometChat's push notification service
+ // This links the device to the logged-in user for push delivery
+ CometChat.registerTokenForPushNotification(token, object : CometChat.CallbackListener() {
+ override fun onSuccess(s: String) {
+ Log.d(TAG, "Push token registered successfully")
+ }
+ override fun onError(e: CometChatException) {
+ Log.e(TAG, "Token registration failed: ${e.message}")
+ }
+ })
+ }
+
+ companion object {
+ private const val TAG = "CallFCMService"
+ }
+}
+```
+
+
+```java
+public class CallFirebaseMessagingService extends FirebaseMessagingService {
+
+ private static final String TAG = "CallFCMService";
+
+ @Override
+ public void onMessageReceived(RemoteMessage remoteMessage) {
+ Map data = remoteMessage.getData();
+
+ // Check if this is a call notification by examining the "type" field
+ // CometChat sends call notifications with type="call"
+ if ("call".equals(data.get("type"))) {
+ handleIncomingCall(data);
+ }
+ }
+
+ private void handleIncomingCall(Map data) {
+ // Extract call information from the push payload
+ // These fields are sent by CometChat when a call is initiated
+ String sessionId = data.get("sessionId");
+ String callerName = data.get("senderName");
+ String callerUid = data.get("senderUid");
+ String callType = data.get("callType");
+ String callerAvatar = data.get("senderAvatar");
+
+ if (sessionId == null || callerUid == null) return;
+ if (callerName == null) callerName = "Unknown";
+ if (callType == null) callType = "video";
+
+ // Create a CallData object to pass call information between components
+ CallData callData = new CallData(
+ sessionId, callerName, callerUid, callType, callerAvatar
+ );
+
+ // If app is in foreground, let CometChat's CallListener handle it
+ if (isAppInForeground()) {
+ return;
+ }
+
+ // App is in background/killed - show system call UI via ConnectionService
+ CallNotificationManager.showIncomingCall(this, callData);
+ }
+
+ /**
+ * Checks if the app is currently visible to the user.
+ * We only want to use ConnectionService when the app is in background.
+ */
+ private boolean isAppInForeground() {
+ ActivityManager activityManager =
+ (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
+ List appProcesses =
+ activityManager.getRunningAppProcesses();
+ if (appProcesses == null) return false;
+
+ String packageName = getPackageName();
+ for (ActivityManager.RunningAppProcessInfo appProcess : appProcesses) {
+ if (appProcess.importance ==
+ ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND
+ && appProcess.processName.equals(packageName)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Called when FCM generates a new token.
+ * Register this token with CometChat to receive push notifications.
+ */
+ @Override
+ public void onNewToken(String token) {
+ // Register the FCM token with CometChat's push notification service
+ CometChat.registerTokenForPushNotification(token, new CometChat.CallbackListener() {
+ @Override
+ public void onSuccess(String s) {
+ Log.d(TAG, "Push token registered successfully");
+ }
+ @Override
+ public void onError(CometChatException e) {
+ Log.e(TAG, "Token registration failed: " + e.getMessage());
+ }
+ });
+ }
+}
+```
+
+
+
+### 1.3 Create CallData Model
+
+The `CallData` class is a simple data container that holds all information about an incoming or outgoing call. It implements `Parcelable` so it can be passed between Android components (Activities, Services, BroadcastReceivers).
+
+
+
+```kotlin
+/**
+ * Data class representing call information.
+ * Implements Parcelable to allow passing between Android components.
+ */
+data class CallData(
+ val sessionId: String, // Unique identifier for the call session
+ val callerName: String, // Display name of the caller
+ val callerUid: String, // CometChat UID of the caller
+ val callType: String, // "audio" or "video"
+ val callerAvatar: String? // URL to caller's avatar image (optional)
+) : Parcelable {
+
+ constructor(parcel: Parcel) : this(
+ parcel.readString() ?: "",
+ parcel.readString() ?: "",
+ parcel.readString() ?: "",
+ parcel.readString() ?: "",
+ parcel.readString()
+ )
+
+ override fun writeToParcel(parcel: Parcel, flags: Int) {
+ parcel.writeString(sessionId)
+ parcel.writeString(callerName)
+ parcel.writeString(callerUid)
+ parcel.writeString(callType)
+ parcel.writeString(callerAvatar)
+ }
+
+ override fun describeContents(): Int = 0
+
+ companion object CREATOR : Parcelable.Creator {
+ override fun createFromParcel(parcel: Parcel): CallData = CallData(parcel)
+ override fun newArray(size: Int): Array = arrayOfNulls(size)
+ }
+}
+```
+
+
+```java
+/**
+ * Data class representing call information.
+ * Implements Parcelable to allow passing between Android components.
+ */
+public class CallData implements Parcelable {
+ private final String sessionId; // Unique identifier for the call session
+ private final String callerName; // Display name of the caller
+ private final String callerUid; // CometChat UID of the caller
+ private final String callType; // "audio" or "video"
+ private final String callerAvatar; // URL to caller's avatar image (optional)
+
+ public CallData(String sessionId, String callerName, String callerUid,
+ String callType, String callerAvatar) {
+ this.sessionId = sessionId;
+ this.callerName = callerName;
+ this.callerUid = callerUid;
+ this.callType = callType;
+ this.callerAvatar = callerAvatar;
+ }
+
+ // Getters
+ public String getSessionId() { return sessionId; }
+ public String getCallerName() { return callerName; }
+ public String getCallerUid() { return callerUid; }
+ public String getCallType() { return callType; }
+ public String getCallerAvatar() { return callerAvatar; }
+
+ // Parcelable implementation for passing between components
+ protected CallData(Parcel in) {
+ sessionId = in.readString();
+ callerName = in.readString();
+ callerUid = in.readString();
+ callType = in.readString();
+ callerAvatar = in.readString();
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(sessionId);
+ dest.writeString(callerName);
+ dest.writeString(callerUid);
+ dest.writeString(callType);
+ dest.writeString(callerAvatar);
+ }
+
+ @Override
+ public int describeContents() { return 0; }
+
+ public static final Creator CREATOR = new Creator() {
+ @Override
+ public CallData createFromParcel(Parcel in) { return new CallData(in); }
+ @Override
+ public CallData[] newArray(int size) { return new CallData[size]; }
+ };
+}
+```
+
+
+
+---
+
+## Step 2: Register PhoneAccount
+
+A `PhoneAccount` tells Android that your app can handle phone calls. This registration is required for the system to route incoming calls to your app and display the native call UI.
+
+### 2.1 Create PhoneAccountManager
+
+This singleton class handles registering your app with Android's Telecom system. The `PhoneAccount` must be registered before you can receive or make VoIP calls.
+
+
+
+```kotlin
+/**
+ * Manages PhoneAccount registration with Android's Telecom system.
+ * Must be called once when the app starts (typically in Application.onCreate).
+ */
+object PhoneAccountManager {
+ private const val PHONE_ACCOUNT_ID = "cometchat_voip_account"
+ private var phoneAccountHandle: PhoneAccountHandle? = null
+
+ /**
+ * Registers your app as a calling app with Android's Telecom system.
+ * Call this in your Application.onCreate() method.
+ */
+ fun register(context: Context) {
+ val telecomManager = context.getSystemService(Context.TELECOM_SERVICE) as TelecomManager
+
+ // ComponentName points to your ConnectionService implementation
+ val componentName = ComponentName(context, CallConnectionService::class.java)
+
+ // PhoneAccountHandle uniquely identifies your calling account
+ phoneAccountHandle = PhoneAccountHandle(componentName, PHONE_ACCOUNT_ID)
+
+ // Build the PhoneAccount with required capabilities
+ val phoneAccount = PhoneAccount.builder(phoneAccountHandle, "CometChat Calls")
+ .setCapabilities(
+ // CAPABILITY_CALL_PROVIDER: Can make and receive calls
+ // CAPABILITY_SELF_MANAGED: Manages its own call UI (required for VoIP)
+ PhoneAccount.CAPABILITY_CALL_PROVIDER or
+ PhoneAccount.CAPABILITY_SELF_MANAGED
+ )
+ .build()
+
+ // Register with the system
+ telecomManager.registerPhoneAccount(phoneAccount)
+ }
+
+ /**
+ * Returns the PhoneAccountHandle for use with TelecomManager calls.
+ */
+ fun getPhoneAccountHandle(context: Context): PhoneAccountHandle {
+ if (phoneAccountHandle == null) {
+ val componentName = ComponentName(context, CallConnectionService::class.java)
+ phoneAccountHandle = PhoneAccountHandle(componentName, PHONE_ACCOUNT_ID)
+ }
+ return phoneAccountHandle!!
+ }
+
+ /**
+ * Checks if the user has enabled the PhoneAccount in system settings.
+ * Some devices require manual enabling in Settings > Apps > Phone > Calling accounts.
+ */
+ fun isPhoneAccountEnabled(context: Context): Boolean {
+ val telecomManager = context.getSystemService(Context.TELECOM_SERVICE) as TelecomManager
+ val account = telecomManager.getPhoneAccount(getPhoneAccountHandle(context))
+ return account?.isEnabled == true
+ }
+
+ /**
+ * Opens system settings where user can enable the PhoneAccount.
+ * Call this if isPhoneAccountEnabled() returns false.
+ */
+ fun openPhoneAccountSettings(context: Context) {
+ val intent = Intent(TelecomManager.ACTION_CHANGE_PHONE_ACCOUNTS)
+ intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
+ context.startActivity(intent)
+ }
+}
+```
+
+
+```java
+/**
+ * Manages PhoneAccount registration with Android's Telecom system.
+ * Must be called once when the app starts (typically in Application.onCreate).
+ */
+public class PhoneAccountManager {
+ private static final String PHONE_ACCOUNT_ID = "cometchat_voip_account";
+ private static PhoneAccountHandle phoneAccountHandle;
+
+ /**
+ * Registers your app as a calling app with Android's Telecom system.
+ * Call this in your Application.onCreate() method.
+ */
+ public static void register(Context context) {
+ TelecomManager telecomManager =
+ (TelecomManager) context.getSystemService(Context.TELECOM_SERVICE);
+
+ // ComponentName points to your ConnectionService implementation
+ ComponentName componentName =
+ new ComponentName(context, CallConnectionService.class);
+
+ // PhoneAccountHandle uniquely identifies your calling account
+ phoneAccountHandle = new PhoneAccountHandle(componentName, PHONE_ACCOUNT_ID);
+
+ // Build the PhoneAccount with required capabilities
+ PhoneAccount phoneAccount = PhoneAccount.builder(phoneAccountHandle, "CometChat Calls")
+ .setCapabilities(
+ // CAPABILITY_CALL_PROVIDER: Can make and receive calls
+ // CAPABILITY_SELF_MANAGED: Manages its own call UI (required for VoIP)
+ PhoneAccount.CAPABILITY_CALL_PROVIDER |
+ PhoneAccount.CAPABILITY_SELF_MANAGED
+ )
+ .build();
+
+ // Register with the system
+ telecomManager.registerPhoneAccount(phoneAccount);
+ }
+
+ /**
+ * Returns the PhoneAccountHandle for use with TelecomManager calls.
+ */
+ public static PhoneAccountHandle getPhoneAccountHandle(Context context) {
+ if (phoneAccountHandle == null) {
+ ComponentName componentName =
+ new ComponentName(context, CallConnectionService.class);
+ phoneAccountHandle = new PhoneAccountHandle(componentName, PHONE_ACCOUNT_ID);
+ }
+ return phoneAccountHandle;
+ }
+
+ /**
+ * Checks if the user has enabled the PhoneAccount in system settings.
+ */
+ public static boolean isPhoneAccountEnabled(Context context) {
+ TelecomManager telecomManager =
+ (TelecomManager) context.getSystemService(Context.TELECOM_SERVICE);
+ PhoneAccount account = telecomManager.getPhoneAccount(getPhoneAccountHandle(context));
+ return account != null && account.isEnabled();
+ }
+
+ /**
+ * Opens system settings where user can enable the PhoneAccount.
+ */
+ public static void openPhoneAccountSettings(Context context) {
+ Intent intent = new Intent(TelecomManager.ACTION_CHANGE_PHONE_ACCOUNTS);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ context.startActivity(intent);
+ }
+}
+```
+
+
+
+### 2.2 Register on App Start
+
+Register the PhoneAccount when your app starts. This should be done in your `Application` class to ensure it's registered before any calls can be received.
+
+
+
+```kotlin
+class MyApplication : Application() {
+ override fun onCreate() {
+ super.onCreate()
+
+ // Initialize CometChat (see Setup guide)
+ // CometChat.init(...)
+
+ // Register PhoneAccount for VoIP calling
+ // This must be done before receiving any calls
+ PhoneAccountManager.register(this)
+ }
+}
+```
+
+
+```java
+public class MyApplication extends Application {
+ @Override
+ public void onCreate() {
+ super.onCreate();
+
+ // Initialize CometChat (see Setup guide)
+ // CometChat.init(...)
+
+ // Register PhoneAccount for VoIP calling
+ // This must be done before receiving any calls
+ PhoneAccountManager.register(this);
+ }
+}
+```
+
+
+
+---
+
+## Step 3: Implement ConnectionService
+
+The `ConnectionService` is the core component that bridges your app with Android's Telecom framework. It creates `Connection` objects that represent individual calls and handle user interactions.
+
+### 3.1 Create CallConnectionService
+
+This service is called by Android when a new incoming or outgoing call needs to be created. It's responsible for creating `Connection` objects that manage the call state.
+
+
+
+
+```kotlin
+/**
+ * ConnectionService implementation for VoIP calling.
+ * Android's Telecom framework calls this service to create Connection objects
+ * for incoming and outgoing calls.
+ */
+class CallConnectionService : ConnectionService() {
+
+ /**
+ * Called by Android when an incoming call is reported via TelecomManager.addNewIncomingCall().
+ * Creates a Connection object that will handle the incoming call.
+ */
+ override fun onCreateIncomingConnection(
+ connectionManagerPhoneAccount: PhoneAccountHandle?,
+ request: ConnectionRequest?
+ ): Connection {
+ // Extract call data from the request extras
+ val extras = request?.extras
+ val callData = extras?.getParcelable(EXTRA_CALL_DATA)
+
+ // Create a new Connection to represent this call
+ val connection = CallConnection(applicationContext, callData)
+
+ // Set initial state: initializing -> ringing
+ // This triggers the system to show the incoming call UI
+ connection.setInitializing()
+ connection.setRinging()
+
+ // Store the connection so we can access it from other components
+ CallConnectionHolder.setConnection(connection)
+
+ return connection
+ }
+
+ /**
+ * Called by Android when an outgoing call is placed via TelecomManager.placeCall().
+ * Creates a Connection object that will handle the outgoing call.
+ */
+ override fun onCreateOutgoingConnection(
+ connectionManagerPhoneAccount: PhoneAccountHandle?,
+ request: ConnectionRequest?
+ ): Connection {
+ val extras = request?.extras
+ val callData = extras?.getParcelable(EXTRA_CALL_DATA)
+
+ val connection = CallConnection(applicationContext, callData)
+
+ // Set initial state: initializing -> dialing
+ // This triggers the system to show the outgoing call UI
+ connection.setInitializing()
+ connection.setDialing()
+
+ CallConnectionHolder.setConnection(connection)
+
+ return connection
+ }
+
+ /**
+ * Called when the system fails to create an incoming connection.
+ * This can happen due to permission issues or system constraints.
+ */
+ override fun onCreateIncomingConnectionFailed(
+ connectionManagerPhoneAccount: PhoneAccountHandle?,
+ request: ConnectionRequest?
+ ) {
+ Log.e(TAG, "Failed to create incoming connection")
+ // Consider showing a fallback notification here
+ }
+
+ /**
+ * Called when the system fails to create an outgoing connection.
+ */
+ override fun onCreateOutgoingConnectionFailed(
+ connectionManagerPhoneAccount: PhoneAccountHandle?,
+ request: ConnectionRequest?
+ ) {
+ Log.e(TAG, "Failed to create outgoing connection")
+ }
+
+ companion object {
+ private const val TAG = "CallConnectionService"
+ const val EXTRA_CALL_DATA = "extra_call_data"
+ }
+}
+```
+
+
+```java
+/**
+ * ConnectionService implementation for VoIP calling.
+ * Android's Telecom framework calls this service to create Connection objects
+ * for incoming and outgoing calls.
+ */
+public class CallConnectionService extends ConnectionService {
+
+ private static final String TAG = "CallConnectionService";
+ public static final String EXTRA_CALL_DATA = "extra_call_data";
+
+ /**
+ * Called by Android when an incoming call is reported via TelecomManager.addNewIncomingCall().
+ * Creates a Connection object that will handle the incoming call.
+ */
+ @Override
+ public Connection onCreateIncomingConnection(
+ PhoneAccountHandle connectionManagerPhoneAccount,
+ ConnectionRequest request) {
+
+ // Extract call data from the request extras
+ Bundle extras = request.getExtras();
+ CallData callData = extras.getParcelable(EXTRA_CALL_DATA);
+
+ // Create a new Connection to represent this call
+ CallConnection connection = new CallConnection(getApplicationContext(), callData);
+
+ // Set initial state: initializing -> ringing
+ // This triggers the system to show the incoming call UI
+ connection.setInitializing();
+ connection.setRinging();
+
+ // Store the connection so we can access it from other components
+ CallConnectionHolder.setConnection(connection);
+
+ return connection;
+ }
+
+ /**
+ * Called by Android when an outgoing call is placed via TelecomManager.placeCall().
+ * Creates a Connection object that will handle the outgoing call.
+ */
+ @Override
+ public Connection onCreateOutgoingConnection(
+ PhoneAccountHandle connectionManagerPhoneAccount,
+ ConnectionRequest request) {
+
+ Bundle extras = request.getExtras();
+ CallData callData = extras.getParcelable(EXTRA_CALL_DATA);
+
+ CallConnection connection = new CallConnection(getApplicationContext(), callData);
+
+ // Set initial state: initializing -> dialing
+ connection.setInitializing();
+ connection.setDialing();
+
+ CallConnectionHolder.setConnection(connection);
+
+ return connection;
+ }
+
+ @Override
+ public void onCreateIncomingConnectionFailed(
+ PhoneAccountHandle connectionManagerPhoneAccount,
+ ConnectionRequest request) {
+ Log.e(TAG, "Failed to create incoming connection");
+ }
+
+ @Override
+ public void onCreateOutgoingConnectionFailed(
+ PhoneAccountHandle connectionManagerPhoneAccount,
+ ConnectionRequest request) {
+ Log.e(TAG, "Failed to create outgoing connection");
+ }
+}
+```
+
+
+
+### 3.2 Create CallConnection
+
+The `Connection` class represents an individual call. It receives callbacks from Android when the user interacts with the call (answer, reject, hold, etc.) and is responsible for updating the call state and communicating with CometChat.
+
+
+
+```kotlin
+/**
+ * Represents an individual VoIP call.
+ * Handles user actions (answer, reject, disconnect) and bridges to CometChat SDK.
+ */
+class CallConnection(
+ private val context: Context,
+ private val callData: CallData?
+) : Connection() {
+
+ init {
+ // PROPERTY_SELF_MANAGED: We manage our own call UI (not using system dialer)
+ connectionProperties = PROPERTY_SELF_MANAGED
+
+ // Set capabilities for this call
+ // CAPABILITY_MUTE: User can mute the call
+ // CAPABILITY_SUPPORT_HOLD/CAPABILITY_HOLD: User can put call on hold
+ connectionCapabilities = CAPABILITY_MUTE or
+ CAPABILITY_SUPPORT_HOLD or
+ CAPABILITY_HOLD
+
+ // Set caller information for the system call UI
+ callData?.let {
+ // Display name shown on incoming call screen
+ setCallerDisplayName(it.callerName, TelecomManager.PRESENTATION_ALLOWED)
+ // Address (used for call log and display)
+ setAddress(
+ Uri.parse("tel:${it.callerUid}"),
+ TelecomManager.PRESENTATION_ALLOWED
+ )
+ }
+
+ // Mark this as a VoIP call for proper audio routing
+ audioModeIsVoip = true
+ }
+
+ /**
+ * Called when user taps "Answer" on the incoming call screen.
+ * Accept the call via CometChat and launch the call activity.
+ */
+ override fun onAnswer() {
+ Log.d(TAG, "Call answered by user")
+
+ // Update connection state to active (call is now connected)
+ setActive()
+
+ callData?.let { data ->
+ // Accept the call via CometChat Chat SDK
+ // This notifies the caller that we've accepted
+ CometChat.acceptCall(data.sessionId, object : CometChat.CallbackListener() {
+ override fun onSuccess(call: Call) {
+ Log.d(TAG, "CometChat call accepted successfully")
+ // Launch the call activity to show the video/audio UI
+ launchCallActivity(data)
+ }
+
+ override fun onError(e: CometChatException) {
+ Log.e(TAG, "Failed to accept call: ${e.message}")
+ // Call failed - disconnect and clean up
+ setDisconnected(DisconnectCause(DisconnectCause.ERROR))
+ destroy()
+ }
+ })
+ }
+ }
+
+ /**
+ * Called when user taps "Decline" on the incoming call screen.
+ * Reject the call via CometChat.
+ */
+ override fun onReject() {
+ Log.d(TAG, "Call rejected by user")
+
+ callData?.let { data ->
+ // Reject the call via CometChat Chat SDK
+ // This notifies the caller that we've declined
+ CometChat.rejectCall(
+ data.sessionId,
+ CometChatConstants.CALL_STATUS_REJECTED,
+ object : CometChat.CallbackListener() {
+ override fun onSuccess(call: Call) {
+ Log.d(TAG, "Call rejected successfully")
+ }
+ override fun onError(e: CometChatException) {
+ Log.e(TAG, "Failed to reject call: ${e.message}")
+ }
+ }
+ )
+ }
+
+ // Update connection state and clean up
+ setDisconnected(DisconnectCause(DisconnectCause.REJECTED))
+ destroy()
+ }
+
+ /**
+ * Called when user ends the call (taps end call button).
+ * Leave the call session and notify the other participant.
+ */
+ override fun onDisconnect() {
+ Log.d(TAG, "Call disconnected")
+
+ // Leave the Calls SDK session if active
+ if (CallSession.getInstance().isSessionActive) {
+ CallSession.getInstance().leaveSession()
+ }
+
+ // End the call via CometChat Chat SDK
+ // This notifies the other participant that the call has ended
+ callData?.let { data ->
+ CometChat.endCall(data.sessionId, object : CometChat.CallbackListener() {
+ override fun onSuccess(call: Call) {
+ Log.d(TAG, "Call ended successfully")
+ }
+ override fun onError(e: CometChatException) {
+ Log.e(TAG, "Failed to end call: ${e.message}")
+ }
+ })
+ }
+
+ setDisconnected(DisconnectCause(DisconnectCause.LOCAL))
+ destroy()
+ }
+
+ /**
+ * Called when user puts the call on hold.
+ */
+ override fun onHold() {
+ setOnHold()
+ // Mute audio when on hold
+ CallSession.getInstance().muteAudio()
+ }
+
+ /**
+ * Called when user takes the call off hold.
+ */
+ override fun onUnhold() {
+ setActive()
+ CallSession.getInstance().unMuteAudio()
+ }
+
+ /**
+ * Launches the CallActivity to show the call UI.
+ */
+ private fun launchCallActivity(callData: CallData) {
+ val intent = Intent(context, CallActivity::class.java).apply {
+ flags = Intent.FLAG_ACTIVITY_NEW_TASK
+ putExtra(CallActivity.EXTRA_SESSION_ID, callData.sessionId)
+ putExtra(CallActivity.EXTRA_CALL_TYPE, callData.callType)
+ putExtra(CallActivity.EXTRA_IS_INCOMING, true)
+ }
+ context.startActivity(intent)
+ }
+
+ /**
+ * Public method to end the call from outside this class.
+ */
+ fun endCall() {
+ onDisconnect()
+ }
+
+ companion object {
+ private const val TAG = "CallConnection"
+ }
+}
+```
+
+
+```java
+/**
+ * Represents an individual VoIP call.
+ * Handles user actions (answer, reject, disconnect) and bridges to CometChat SDK.
+ */
+public class CallConnection extends Connection {
+
+ private static final String TAG = "CallConnection";
+ private final Context context;
+ private final CallData callData;
+
+ public CallConnection(Context context, CallData callData) {
+ this.context = context;
+ this.callData = callData;
+
+ // PROPERTY_SELF_MANAGED: We manage our own call UI
+ setConnectionProperties(PROPERTY_SELF_MANAGED);
+
+ // Set capabilities for this call
+ setConnectionCapabilities(
+ CAPABILITY_MUTE | CAPABILITY_SUPPORT_HOLD | CAPABILITY_HOLD
+ );
+
+ // Set caller information for the system call UI
+ if (callData != null) {
+ setCallerDisplayName(callData.getCallerName(), TelecomManager.PRESENTATION_ALLOWED);
+ setAddress(
+ Uri.parse("tel:" + callData.getCallerUid()),
+ TelecomManager.PRESENTATION_ALLOWED
+ );
+ }
+
+ // Mark this as a VoIP call for proper audio routing
+ setAudioModeIsVoip(true);
+ }
+
+ /**
+ * Called when user taps "Answer" on the incoming call screen.
+ */
+ @Override
+ public void onAnswer() {
+ Log.d(TAG, "Call answered by user");
+ setActive();
+
+ if (callData != null) {
+ // Accept the call via CometChat Chat SDK
+ CometChat.acceptCall(callData.getSessionId(), new CometChat.CallbackListener() {
+ @Override
+ public void onSuccess(Call call) {
+ Log.d(TAG, "CometChat call accepted successfully");
+ launchCallActivity(callData);
+ }
+
+ @Override
+ public void onError(CometChatException e) {
+ Log.e(TAG, "Failed to accept call: " + e.getMessage());
+ setDisconnected(new DisconnectCause(DisconnectCause.ERROR));
+ destroy();
+ }
+ });
+ }
+ }
+
+ /**
+ * Called when user taps "Decline" on the incoming call screen.
+ */
+ @Override
+ public void onReject() {
+ Log.d(TAG, "Call rejected by user");
+
+ if (callData != null) {
+ CometChat.rejectCall(
+ callData.getSessionId(),
+ CometChatConstants.CALL_STATUS_REJECTED,
+ new CometChat.CallbackListener() {
+ @Override
+ public void onSuccess(Call call) {
+ Log.d(TAG, "Call rejected successfully");
+ }
+ @Override
+ public void onError(CometChatException e) {
+ Log.e(TAG, "Failed to reject call: " + e.getMessage());
+ }
+ }
+ );
+ }
+
+ setDisconnected(new DisconnectCause(DisconnectCause.REJECTED));
+ destroy();
+ }
+
+ /**
+ * Called when user ends the call.
+ */
+ @Override
+ public void onDisconnect() {
+ Log.d(TAG, "Call disconnected");
+
+ if (CallSession.getInstance().isSessionActive()) {
+ CallSession.getInstance().leaveSession();
+ }
+
+ if (callData != null) {
+ CometChat.endCall(callData.getSessionId(), new CometChat.CallbackListener() {
+ @Override
+ public void onSuccess(Call call) {
+ Log.d(TAG, "Call ended successfully");
+ }
+ @Override
+ public void onError(CometChatException e) {
+ Log.e(TAG, "Failed to end call: " + e.getMessage());
+ }
+ });
+ }
+
+ setDisconnected(new DisconnectCause(DisconnectCause.LOCAL));
+ destroy();
+ }
+
+ @Override
+ public void onHold() {
+ setOnHold();
+ CallSession.getInstance().muteAudio();
+ }
+
+ @Override
+ public void onUnhold() {
+ setActive();
+ CallSession.getInstance().unMuteAudio();
+ }
+
+ private void launchCallActivity(CallData callData) {
+ Intent intent = new Intent(context, CallActivity.class);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.putExtra(CallActivity.EXTRA_SESSION_ID, callData.getSessionId());
+ intent.putExtra(CallActivity.EXTRA_CALL_TYPE, callData.getCallType());
+ intent.putExtra(CallActivity.EXTRA_IS_INCOMING, true);
+ context.startActivity(intent);
+ }
+
+ public void endCall() {
+ onDisconnect();
+ }
+}
+```
+
+
+
+### 3.3 Create CallConnectionHolder
+
+This singleton holds a reference to the active `Connection` so it can be accessed from other components (like the CallActivity or BroadcastReceiver).
+
+
+
+```kotlin
+/**
+ * Singleton to hold the active CallConnection.
+ * Allows other components to access and control the current call.
+ */
+object CallConnectionHolder {
+ private var connection: CallConnection? = null
+
+ fun setConnection(conn: CallConnection?) {
+ connection = conn
+ }
+
+ fun getConnection(): CallConnection? = connection
+
+ /**
+ * Ends the current call and clears the reference.
+ */
+ fun endCall() {
+ connection?.endCall()
+ connection = null
+ }
+
+ fun hasActiveConnection(): Boolean = connection != null
+}
+```
+
+
+```java
+/**
+ * Singleton to hold the active CallConnection.
+ * Allows other components to access and control the current call.
+ */
+public class CallConnectionHolder {
+ private static CallConnection connection;
+
+ public static void setConnection(CallConnection conn) {
+ connection = conn;
+ }
+
+ public static CallConnection getConnection() {
+ return connection;
+ }
+
+ public static void endCall() {
+ if (connection != null) {
+ connection.endCall();
+ connection = null;
+ }
+ }
+
+ public static boolean hasActiveConnection() {
+ return connection != null;
+ }
+}
+```
+
+
+
+---
+
+## Step 4: Create CallNotificationManager
+
+This class is responsible for showing incoming calls to the user. It first tries to use the system call UI via `TelecomManager`, and falls back to a high-priority notification if that fails.
+
+
+
+
+```kotlin
+/**
+ * Manages showing incoming calls via Android's Telecom system.
+ * Falls back to a high-priority notification if Telecom fails.
+ */
+object CallNotificationManager {
+
+ /**
+ * Shows an incoming call to the user.
+ * Tries to use the system call UI first, falls back to notification.
+ */
+ fun showIncomingCall(context: Context, callData: CallData) {
+ val telecomManager = context.getSystemService(Context.TELECOM_SERVICE) as TelecomManager
+
+ // Prepare extras with call data for the ConnectionService
+ val extras = Bundle().apply {
+ putParcelable(CallConnectionService.EXTRA_CALL_DATA, callData)
+ putParcelable(
+ TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE,
+ PhoneAccountManager.getPhoneAccountHandle(context)
+ )
+ }
+
+ try {
+ // Tell Android there's an incoming call
+ // This triggers onCreateIncomingConnection in our ConnectionService
+ telecomManager.addNewIncomingCall(
+ PhoneAccountManager.getPhoneAccountHandle(context),
+ extras
+ )
+ } catch (e: SecurityException) {
+ // Permission denied - PhoneAccount may not be enabled
+ Log.e(TAG, "Permission denied for incoming call: ${e.message}")
+ showFallbackNotification(context, callData)
+ } catch (e: Exception) {
+ Log.e(TAG, "Failed to show incoming call: ${e.message}")
+ showFallbackNotification(context, callData)
+ }
+ }
+
+ /**
+ * Places an outgoing call via the Telecom system.
+ */
+ fun placeOutgoingCall(context: Context, callData: CallData) {
+ val telecomManager = context.getSystemService(Context.TELECOM_SERVICE) as TelecomManager
+
+ val extras = Bundle().apply {
+ putParcelable(CallConnectionService.EXTRA_CALL_DATA, callData)
+ putParcelable(
+ TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE,
+ PhoneAccountManager.getPhoneAccountHandle(context)
+ )
+ }
+
+ try {
+ telecomManager.placeCall(
+ Uri.parse("tel:${callData.callerUid}"),
+ extras
+ )
+ } catch (e: SecurityException) {
+ Log.e(TAG, "Permission denied for outgoing call: ${e.message}")
+ }
+ }
+
+ /**
+ * Shows a high-priority notification as fallback when Telecom fails.
+ * This notification has full-screen intent to show on lock screen.
+ */
+ private fun showFallbackNotification(context: Context, callData: CallData) {
+ val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE)
+ as NotificationManager
+
+ // Create notification channel (required for Android 8.0+)
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ val channel = NotificationChannel(
+ CHANNEL_ID,
+ "Incoming Calls",
+ NotificationManager.IMPORTANCE_HIGH // High importance for call notifications
+ ).apply {
+ description = "Notifications for incoming calls"
+ setSound(null, null) // We'll play our own ringtone
+ enableVibration(true)
+ }
+ notificationManager.createNotificationChannel(channel)
+ }
+
+ // Create accept action - launches call when tapped
+ val acceptIntent = Intent(context, CallActionReceiver::class.java).apply {
+ action = ACTION_ACCEPT_CALL
+ putExtra(EXTRA_CALL_DATA, callData)
+ }
+ val acceptPendingIntent = PendingIntent.getBroadcast(
+ context, 0, acceptIntent,
+ PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
+ )
+
+ // Create reject action
+ val rejectIntent = Intent(context, CallActionReceiver::class.java).apply {
+ action = ACTION_REJECT_CALL
+ putExtra(EXTRA_CALL_DATA, callData)
+ }
+ val rejectPendingIntent = PendingIntent.getBroadcast(
+ context, 1, rejectIntent,
+ PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
+ )
+
+ // Full screen intent - shows activity on lock screen
+ val fullScreenIntent = Intent(context, IncomingCallActivity::class.java).apply {
+ putExtra(EXTRA_CALL_DATA, callData)
+ flags = Intent.FLAG_ACTIVITY_NEW_TASK
+ }
+ val fullScreenPendingIntent = PendingIntent.getActivity(
+ context, 2, fullScreenIntent,
+ PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
+ )
+
+ // Build the notification
+ val notification = NotificationCompat.Builder(context, CHANNEL_ID)
+ .setSmallIcon(R.drawable.ic_call)
+ .setContentTitle("Incoming ${callData.callType} call")
+ .setContentText("${callData.callerName} is calling...")
+ .setPriority(NotificationCompat.PRIORITY_MAX)
+ .setCategory(NotificationCompat.CATEGORY_CALL) // Marks as call notification
+ .setAutoCancel(true)
+ .setOngoing(true) // Can't be swiped away
+ .setFullScreenIntent(fullScreenPendingIntent, true) // Shows on lock screen
+ .addAction(R.drawable.ic_call_end, "Decline", rejectPendingIntent)
+ .addAction(R.drawable.ic_call_accept, "Accept", acceptPendingIntent)
+ .build()
+
+ notificationManager.notify(NOTIFICATION_ID, notification)
+ }
+
+ /**
+ * Cancels the incoming call notification.
+ * Call this when the call is answered, rejected, or cancelled.
+ */
+ fun cancelNotification(context: Context) {
+ val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE)
+ as NotificationManager
+ notificationManager.cancel(NOTIFICATION_ID)
+ }
+
+ private const val TAG = "CallNotificationManager"
+ private const val CHANNEL_ID = "incoming_calls"
+ private const val NOTIFICATION_ID = 1001
+ const val ACTION_ACCEPT_CALL = "action_accept_call"
+ const val ACTION_REJECT_CALL = "action_reject_call"
+ const val EXTRA_CALL_DATA = "extra_call_data"
+}
+```
+
+
+```java
+/**
+ * Manages showing incoming calls via Android's Telecom system.
+ * Falls back to a high-priority notification if Telecom fails.
+ */
+public class CallNotificationManager {
+
+ private static final String TAG = "CallNotificationManager";
+ private static final String CHANNEL_ID = "incoming_calls";
+ private static final int NOTIFICATION_ID = 1001;
+ public static final String ACTION_ACCEPT_CALL = "action_accept_call";
+ public static final String ACTION_REJECT_CALL = "action_reject_call";
+ public static final String EXTRA_CALL_DATA = "extra_call_data";
+
+ /**
+ * Shows an incoming call to the user.
+ */
+ public static void showIncomingCall(Context context, CallData callData) {
+ TelecomManager telecomManager =
+ (TelecomManager) context.getSystemService(Context.TELECOM_SERVICE);
+
+ Bundle extras = new Bundle();
+ extras.putParcelable(CallConnectionService.EXTRA_CALL_DATA, callData);
+ extras.putParcelable(
+ TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE,
+ PhoneAccountManager.getPhoneAccountHandle(context)
+ );
+
+ try {
+ telecomManager.addNewIncomingCall(
+ PhoneAccountManager.getPhoneAccountHandle(context),
+ extras
+ );
+ } catch (SecurityException e) {
+ Log.e(TAG, "Permission denied: " + e.getMessage());
+ showFallbackNotification(context, callData);
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to show incoming call: " + e.getMessage());
+ showFallbackNotification(context, callData);
+ }
+ }
+
+ /**
+ * Places an outgoing call via the Telecom system.
+ */
+ public static void placeOutgoingCall(Context context, CallData callData) {
+ TelecomManager telecomManager =
+ (TelecomManager) context.getSystemService(Context.TELECOM_SERVICE);
+
+ Bundle extras = new Bundle();
+ extras.putParcelable(CallConnectionService.EXTRA_CALL_DATA, callData);
+ extras.putParcelable(
+ TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE,
+ PhoneAccountManager.getPhoneAccountHandle(context)
+ );
+
+ try {
+ telecomManager.placeCall(
+ Uri.parse("tel:" + callData.getCallerUid()),
+ extras
+ );
+ } catch (SecurityException e) {
+ Log.e(TAG, "Permission denied: " + e.getMessage());
+ }
+ }
+
+ /**
+ * Shows a high-priority notification as fallback.
+ */
+ private static void showFallbackNotification(Context context, CallData callData) {
+ NotificationManager notificationManager =
+ (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
+
+ // Create channel for Android 8.0+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ NotificationChannel channel = new NotificationChannel(
+ CHANNEL_ID, "Incoming Calls", NotificationManager.IMPORTANCE_HIGH
+ );
+ channel.setDescription("Notifications for incoming calls");
+ channel.setSound(null, null);
+ channel.enableVibration(true);
+ notificationManager.createNotificationChannel(channel);
+ }
+
+ // Accept intent
+ Intent acceptIntent = new Intent(context, CallActionReceiver.class);
+ acceptIntent.setAction(ACTION_ACCEPT_CALL);
+ acceptIntent.putExtra(EXTRA_CALL_DATA, callData);
+ PendingIntent acceptPendingIntent = PendingIntent.getBroadcast(
+ context, 0, acceptIntent,
+ PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE
+ );
+
+ // Reject intent
+ Intent rejectIntent = new Intent(context, CallActionReceiver.class);
+ rejectIntent.setAction(ACTION_REJECT_CALL);
+ rejectIntent.putExtra(EXTRA_CALL_DATA, callData);
+ PendingIntent rejectPendingIntent = PendingIntent.getBroadcast(
+ context, 1, rejectIntent,
+ PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE
+ );
+
+ // Full screen intent for lock screen
+ Intent fullScreenIntent = new Intent(context, IncomingCallActivity.class);
+ fullScreenIntent.putExtra(EXTRA_CALL_DATA, callData);
+ fullScreenIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ PendingIntent fullScreenPendingIntent = PendingIntent.getActivity(
+ context, 2, fullScreenIntent,
+ PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE
+ );
+
+ Notification notification = new NotificationCompat.Builder(context, CHANNEL_ID)
+ .setSmallIcon(R.drawable.ic_call)
+ .setContentTitle("Incoming " + callData.getCallType() + " call")
+ .setContentText(callData.getCallerName() + " is calling...")
+ .setPriority(NotificationCompat.PRIORITY_MAX)
+ .setCategory(NotificationCompat.CATEGORY_CALL)
+ .setAutoCancel(true)
+ .setOngoing(true)
+ .setFullScreenIntent(fullScreenPendingIntent, true)
+ .addAction(R.drawable.ic_call_end, "Decline", rejectPendingIntent)
+ .addAction(R.drawable.ic_call_accept, "Accept", acceptPendingIntent)
+ .build();
+
+ notificationManager.notify(NOTIFICATION_ID, notification);
+ }
+
+ public static void cancelNotification(Context context) {
+ NotificationManager notificationManager =
+ (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
+ notificationManager.cancel(NOTIFICATION_ID);
+ }
+}
+```
+
+
+
+---
+
+## Step 5: Create CallActionReceiver
+
+This `BroadcastReceiver` handles the Accept and Decline button taps from the fallback notification.
+
+
+
+```kotlin
+/**
+ * Handles notification button actions (Accept/Decline).
+ * Used when the fallback notification is shown instead of system call UI.
+ */
+class CallActionReceiver : BroadcastReceiver() {
+
+ override fun onReceive(context: Context, intent: Intent) {
+ val callData = intent.getParcelableExtra(
+ CallNotificationManager.EXTRA_CALL_DATA
+ ) ?: return
+
+ when (intent.action) {
+ CallNotificationManager.ACTION_ACCEPT_CALL -> {
+ acceptCall(context, callData)
+ }
+ CallNotificationManager.ACTION_REJECT_CALL -> {
+ rejectCall(context, callData)
+ }
+ }
+
+ // Always cancel the notification after handling
+ CallNotificationManager.cancelNotification(context)
+ }
+
+ private fun acceptCall(context: Context, callData: CallData) {
+ // If we have an active Connection, use it
+ CallConnectionHolder.getConnection()?.onAnswer()
+ ?: run {
+ // No Connection - accept directly via CometChat
+ CometChat.acceptCall(callData.sessionId, object : CometChat.CallbackListener() {
+ override fun onSuccess(call: Call) {
+ // Launch call activity
+ val intent = Intent(context, CallActivity::class.java).apply {
+ flags = Intent.FLAG_ACTIVITY_NEW_TASK
+ putExtra(CallActivity.EXTRA_SESSION_ID, callData.sessionId)
+ putExtra(CallActivity.EXTRA_CALL_TYPE, callData.callType)
+ }
+ context.startActivity(intent)
+ }
+ override fun onError(e: CometChatException) {
+ Log.e(TAG, "Accept failed: ${e.message}")
+ }
+ })
+ }
+ }
+
+ private fun rejectCall(context: Context, callData: CallData) {
+ CallConnectionHolder.getConnection()?.onReject()
+ ?: run {
+ CometChat.rejectCall(
+ callData.sessionId,
+ CometChatConstants.CALL_STATUS_REJECTED,
+ object : CometChat.CallbackListener() {
+ override fun onSuccess(call: Call) {
+ Log.d(TAG, "Call rejected")
+ }
+ override fun onError(e: CometChatException) {
+ Log.e(TAG, "Reject failed: ${e.message}")
+ }
+ }
+ )
+ }
+ }
+
+ companion object {
+ private const val TAG = "CallActionReceiver"
+ }
+}
+```
+
+
+```java
+/**
+ * Handles notification button actions (Accept/Decline).
+ */
+public class CallActionReceiver extends BroadcastReceiver {
+
+ private static final String TAG = "CallActionReceiver";
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ CallData callData = intent.getParcelableExtra(CallNotificationManager.EXTRA_CALL_DATA);
+ if (callData == null) return;
+
+ String action = intent.getAction();
+ if (CallNotificationManager.ACTION_ACCEPT_CALL.equals(action)) {
+ acceptCall(context, callData);
+ } else if (CallNotificationManager.ACTION_REJECT_CALL.equals(action)) {
+ rejectCall(context, callData);
+ }
+
+ CallNotificationManager.cancelNotification(context);
+ }
+
+ private void acceptCall(Context context, CallData callData) {
+ CallConnection connection = CallConnectionHolder.getConnection();
+ if (connection != null) {
+ connection.onAnswer();
+ } else {
+ CometChat.acceptCall(callData.getSessionId(), new CometChat.CallbackListener() {
+ @Override
+ public void onSuccess(Call call) {
+ Intent intent = new Intent(context, CallActivity.class);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.putExtra(CallActivity.EXTRA_SESSION_ID, callData.getSessionId());
+ intent.putExtra(CallActivity.EXTRA_CALL_TYPE, callData.getCallType());
+ context.startActivity(intent);
+ }
+ @Override
+ public void onError(CometChatException e) {
+ Log.e(TAG, "Accept failed: " + e.getMessage());
+ }
+ });
+ }
+ }
+
+ private void rejectCall(Context context, CallData callData) {
+ CallConnection connection = CallConnectionHolder.getConnection();
+ if (connection != null) {
+ connection.onReject();
+ } else {
+ CometChat.rejectCall(
+ callData.getSessionId(),
+ CometChatConstants.CALL_STATUS_REJECTED,
+ new CometChat.CallbackListener() {
+ @Override
+ public void onSuccess(Call call) {
+ Log.d(TAG, "Call rejected");
+ }
+ @Override
+ public void onError(CometChatException e) {
+ Log.e(TAG, "Reject failed: " + e.getMessage());
+ }
+ }
+ );
+ }
+ }
+}
+```
+
+
+
+---
+
+## Step 6: Create CallActivity
+
+The `CallActivity` hosts the actual call UI using CometChat's Calls SDK. It joins the call session and handles the call lifecycle.
+
+
+
+
+```kotlin
+/**
+ * Activity that hosts the call UI.
+ * Joins the CometChat call session and displays the video/audio interface.
+ */
+class CallActivity : AppCompatActivity() {
+
+ private lateinit var callSession: CallSession
+ private var sessionId: String? = null
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_call)
+
+ // Keep screen on during the call
+ window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
+
+ callSession = CallSession.getInstance()
+
+ // Get call parameters from intent
+ sessionId = intent.getStringExtra(EXTRA_SESSION_ID)
+ val callType = intent.getStringExtra(EXTRA_CALL_TYPE) ?: "video"
+
+ // Join the call session
+ sessionId?.let { joinCallSession(it, callType) }
+
+ // Listen for session end events
+ setupSessionStatusListener()
+ }
+
+ /**
+ * Joins the CometChat call session.
+ * This connects to the actual audio/video call.
+ */
+ private fun joinCallSession(sessionId: String, callType: String) {
+ val callContainer = findViewById(R.id.callContainer)
+
+ // Configure session settings
+ // See SessionSettingsBuilder for all options
+ val sessionSettings = CometChatCalls.SessionSettingsBuilder()
+ .setType(if (callType == "video") SessionType.VIDEO else SessionType.AUDIO)
+ .build()
+
+ // Join the session
+ CometChatCalls.joinSession(
+ sessionId = sessionId,
+ sessionSettings = sessionSettings,
+ view = callContainer,
+ context = this,
+ listener = object : CometChatCalls.CallbackListener() {
+ override fun onSuccess(p0: Void?) {
+ Log.d(TAG, "Joined call session successfully")
+ }
+
+ override fun onError(exception: CometChatException) {
+ Log.e(TAG, "Failed to join call: ${exception.message}")
+ endCallAndFinish()
+ }
+ }
+ )
+ }
+
+ /**
+ * Listens for session status changes.
+ * Ends the activity when the call ends.
+ */
+ private fun setupSessionStatusListener() {
+ callSession.addSessionStatusListener(this, object : SessionStatusListener() {
+ override fun onSessionLeft() {
+ runOnUiThread { endCallAndFinish() }
+ }
+
+ override fun onConnectionClosed() {
+ runOnUiThread { endCallAndFinish() }
+ }
+ })
+ }
+
+ /**
+ * Properly ends the call and finishes the activity.
+ */
+ private fun endCallAndFinish() {
+ // End the Connection (updates system call state)
+ CallConnectionHolder.endCall()
+
+ // Notify other participant via CometChat
+ sessionId?.let { sid ->
+ CometChat.endCall(sid, object : CometChat.CallbackListener() {
+ override fun onSuccess(call: Call) {
+ Log.d(TAG, "Call ended successfully")
+ }
+ override fun onError(e: CometChatException) {
+ Log.e(TAG, "End call error: ${e.message}")
+ }
+ })
+ }
+
+ finish()
+ }
+
+ override fun onBackPressed() {
+ // Prevent accidental back press during call
+ // User must use the end call button
+ }
+
+ override fun onDestroy() {
+ super.onDestroy()
+ // Clean up if activity is destroyed while call is active
+ if (callSession.isSessionActive) {
+ callSession.leaveSession()
+ }
+ }
+
+ companion object {
+ private const val TAG = "CallActivity"
+ const val EXTRA_SESSION_ID = "extra_session_id"
+ const val EXTRA_CALL_TYPE = "extra_call_type"
+ const val EXTRA_IS_INCOMING = "extra_is_incoming"
+ }
+}
+```
+
+
+```java
+/**
+ * Activity that hosts the call UI.
+ */
+public class CallActivity extends AppCompatActivity {
+
+ private static final String TAG = "CallActivity";
+ public static final String EXTRA_SESSION_ID = "extra_session_id";
+ public static final String EXTRA_CALL_TYPE = "extra_call_type";
+ public static final String EXTRA_IS_INCOMING = "extra_is_incoming";
+
+ private CallSession callSession;
+ private String sessionId;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_call);
+
+ // Keep screen on during the call
+ getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+
+ callSession = CallSession.getInstance();
+
+ sessionId = getIntent().getStringExtra(EXTRA_SESSION_ID);
+ String callType = getIntent().getStringExtra(EXTRA_CALL_TYPE);
+ if (callType == null) callType = "video";
+
+ if (sessionId != null) {
+ joinCallSession(sessionId, callType);
+ }
+
+ setupSessionStatusListener();
+ }
+
+ private void joinCallSession(String sessionId, String callType) {
+ FrameLayout callContainer = findViewById(R.id.callContainer);
+
+ SessionSettings sessionSettings = new CometChatCalls.SessionSettingsBuilder()
+ .setType(callType.equals("video") ? SessionType.VIDEO : SessionType.AUDIO)
+ .build();
+
+ CometChatCalls.joinSession(
+ sessionId,
+ sessionSettings,
+ callContainer,
+ this,
+ new CometChatCalls.CallbackListener() {
+ @Override
+ public void onSuccess(Void unused) {
+ Log.d(TAG, "Joined call session successfully");
+ }
+
+ @Override
+ public void onError(CometChatException e) {
+ Log.e(TAG, "Failed to join call: " + e.getMessage());
+ endCallAndFinish();
+ }
+ }
+ );
+ }
+
+ private void setupSessionStatusListener() {
+ callSession.addSessionStatusListener(this, new SessionStatusListener() {
+ @Override
+ public void onSessionLeft() {
+ runOnUiThread(() -> endCallAndFinish());
+ }
+
+ @Override
+ public void onConnectionClosed() {
+ runOnUiThread(() -> endCallAndFinish());
+ }
+ });
+ }
+
+ private void endCallAndFinish() {
+ CallConnectionHolder.endCall();
+
+ if (sessionId != null) {
+ CometChat.endCall(sessionId, new CometChat.CallbackListener() {
+ @Override
+ public void onSuccess(Call call) {
+ Log.d(TAG, "Call ended successfully");
+ }
+ @Override
+ public void onError(CometChatException e) {
+ Log.e(TAG, "End call error: " + e.getMessage());
+ }
+ });
+ }
+
+ finish();
+ }
+
+ @Override
+ public void onBackPressed() {
+ // Prevent accidental back press
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ if (callSession.isSessionActive()) {
+ callSession.leaveSession();
+ }
+ }
+}
+```
+
+
+
+
+```xml
+
+
+```
+
+
+---
+
+## Step 7: Configure AndroidManifest
+
+Add all required permissions and component declarations to your `AndroidManifest.xml`:
+
+```xml
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+---
+
+## Step 8: Initiate Outgoing Calls
+
+To make an outgoing VoIP call, use the [CometChat Chat SDK](/sdk/android/calling) to initiate the call, then join the session:
+
+
+
+```kotlin
+/**
+ * Initiates a VoIP call to another user.
+ *
+ * @param receiverId The CometChat UID of the user to call
+ * @param receiverName Display name of the receiver (for UI)
+ * @param callType "audio" or "video"
+ */
+fun initiateVoIPCall(receiverId: String, receiverName: String, callType: String) {
+ // Create a Call object with receiver info
+ val call = Call(receiverId, CometChatConstants.RECEIVER_TYPE_USER, callType)
+
+ // Initiate the call via CometChat Chat SDK
+ // This sends a call notification to the receiver
+ CometChat.initiateCall(call, object : CometChat.CallbackListener() {
+ override fun onSuccess(call: Call) {
+ Log.d(TAG, "Call initiated with sessionId: ${call.sessionId}")
+
+ // Create CallData for the outgoing call
+ val callData = CallData(
+ sessionId = call.sessionId,
+ callerName = receiverName,
+ callerUid = receiverId,
+ callType = callType,
+ callerAvatar = null
+ )
+
+ // Option 1: Use Telecom system (shows system outgoing call UI)
+ // CallNotificationManager.placeOutgoingCall(this@MainActivity, callData)
+
+ // Option 2: Launch CallActivity directly (recommended)
+ val intent = Intent(this@MainActivity, CallActivity::class.java).apply {
+ putExtra(CallActivity.EXTRA_SESSION_ID, call.sessionId)
+ putExtra(CallActivity.EXTRA_CALL_TYPE, callType)
+ putExtra(CallActivity.EXTRA_IS_INCOMING, false)
+ }
+ startActivity(intent)
+ }
+
+ override fun onError(e: CometChatException) {
+ Log.e(TAG, "Call initiation failed: ${e.message}")
+ Toast.makeText(this@MainActivity, "Failed to start call", Toast.LENGTH_SHORT).show()
+ }
+ })
+}
+
+// Usage example:
+// initiateVoIPCall("user123", "John Doe", CometChatConstants.CALL_TYPE_VIDEO)
+```
+
+
+```java
+/**
+ * Initiates a VoIP call to another user.
+ */
+void initiateVoIPCall(String receiverId, String receiverName, String callType) {
+ // Create a Call object with receiver info
+ Call call = new Call(receiverId, CometChatConstants.RECEIVER_TYPE_USER, callType);
+
+ // Initiate the call via CometChat Chat SDK
+ CometChat.initiateCall(call, new CometChat.CallbackListener() {
+ @Override
+ public void onSuccess(Call call) {
+ Log.d(TAG, "Call initiated with sessionId: " + call.getSessionId());
+
+ CallData callData = new CallData(
+ call.getSessionId(),
+ receiverName,
+ receiverId,
+ callType,
+ null
+ );
+
+ // Launch CallActivity directly
+ Intent intent = new Intent(MainActivity.this, CallActivity.class);
+ intent.putExtra(CallActivity.EXTRA_SESSION_ID, call.getSessionId());
+ intent.putExtra(CallActivity.EXTRA_CALL_TYPE, callType);
+ intent.putExtra(CallActivity.EXTRA_IS_INCOMING, false);
+ startActivity(intent);
+ }
+
+ @Override
+ public void onError(CometChatException e) {
+ Log.e(TAG, "Call initiation failed: " + e.getMessage());
+ Toast.makeText(MainActivity.this, "Failed to start call", Toast.LENGTH_SHORT).show();
+ }
+ });
+}
+```
+
+
+
+---
+
+## Complete Flow Diagram
+
+This diagram shows the complete flow for both incoming and outgoing VoIP calls:
+
+```mermaid
+sequenceDiagram
+ participant Caller
+ participant CallerApp
+ participant CometChat
+ participant FCM
+ participant ReceiverApp
+ participant AndroidTelecom
+ participant Receiver
+
+ Note over Caller,Receiver: Outgoing Call Flow
+ Caller->>CallerApp: Tap call button
+ CallerApp->>CometChat: initiateCall()
+ CometChat-->>CallerApp: onSuccess(sessionId)
+ CallerApp->>CallerApp: Launch CallActivity
+ CallerApp->>CometChat: joinSession()
+ CometChat->>FCM: Send push notification
+
+ Note over Caller,Receiver: Incoming Call Flow
+ FCM->>ReceiverApp: onMessageReceived()
+ ReceiverApp->>ReceiverApp: Parse call data
+
+ alt App in Background
+ ReceiverApp->>AndroidTelecom: addNewIncomingCall()
+ AndroidTelecom->>Receiver: Show system call UI
+ Receiver->>AndroidTelecom: Accept/Reject
+ AndroidTelecom->>ReceiverApp: onAnswer()/onReject()
+ else App in Foreground
+ ReceiverApp->>Receiver: Show in-app call UI
+ Receiver->>ReceiverApp: Accept/Reject
+ end
+
+ alt Call Accepted
+ ReceiverApp->>CometChat: acceptCall()
+ CometChat-->>ReceiverApp: onSuccess
+ ReceiverApp->>ReceiverApp: Launch CallActivity
+ ReceiverApp->>CometChat: joinSession()
+ CometChat-->>CallerApp: onOutgoingCallAccepted
+ Note over CallerApp,ReceiverApp: Call Connected
+ else Call Rejected
+ ReceiverApp->>CometChat: rejectCall()
+ CometChat-->>CallerApp: onOutgoingCallRejected
+ end
+```
+
+---
+
+## Troubleshooting
+
+| Issue | Solution |
+|-------|----------|
+| System call UI not showing | Ensure PhoneAccount is registered and enabled. Check Settings > Apps > Phone > Calling accounts |
+| Calls not received in background | Verify [FCM configuration](/notifications/android-push-notifications) and ensure high-priority notifications are enabled |
+| Permission denied errors | Request `MANAGE_OWN_CALLS` permission at runtime on Android 11+ |
+| Call drops immediately | Verify [CometChat authentication](/calls/android/authentication) is valid before joining session |
+| Audio routing issues | Ensure `audioModeIsVoip = true` is set on the Connection |
+| PhoneAccount not enabled | Call `PhoneAccountManager.openPhoneAccountSettings()` to let user enable it |
+
+
+```kotlin
+private val requiredPermissions = arrayOf(
+ Manifest.permission.CAMERA,
+ Manifest.permission.RECORD_AUDIO,
+ Manifest.permission.BLUETOOTH_CONNECT, // Android 12+
+ Manifest.permission.POST_NOTIFICATIONS // Android 13+
+)
+
+private fun checkAndRequestPermissions() {
+ val permissionsToRequest = requiredPermissions.filter {
+ ContextCompat.checkSelfPermission(this, it) != PackageManager.PERMISSION_GRANTED
+ }
+
+ if (permissionsToRequest.isNotEmpty()) {
+ ActivityCompat.requestPermissions(
+ this,
+ permissionsToRequest.toTypedArray(),
+ PERMISSION_REQUEST_CODE
+ )
+ }
+}
+
+override fun onRequestPermissionsResult(
+ requestCode: Int,
+ permissions: Array,
+ grantResults: IntArray
+) {
+ super.onRequestPermissionsResult(requestCode, permissions, grantResults)
+ if (requestCode == PERMISSION_REQUEST_CODE) {
+ val allGranted = grantResults.all { it == PackageManager.PERMISSION_GRANTED }
+ if (!allGranted) {
+ // Show explanation or disable calling features
+ Toast.makeText(this, "Permissions required for calling", Toast.LENGTH_LONG).show()
+ }
+ }
+}
+
+companion object {
+ private const val PERMISSION_REQUEST_CODE = 100
+}
+```
+
+
+## Related Documentation
+
+- [Ringing](/calls/android/ringing) - Basic in-app call signaling
+- [Actions](/calls/android/actions) - Control call functionality
+- [Events](/calls/android/events) - Listen for call state changes
+- [Push Notifications](/notifications/android-push-notifications) - FCM setup
+- [Push Integration](/notifications/push-integration) - Register push tokens with CometChat
diff --git a/calls/api/get-call.mdx b/calls/api/get-call.mdx
new file mode 100644
index 00000000..b6a9f198
--- /dev/null
+++ b/calls/api/get-call.mdx
@@ -0,0 +1,46 @@
+---
+title: "Get Call"
+sidebarTitle: "Get Call"
+openapi: get /calls/{sessionId}
+---
+
+Retrieve detailed information about a specific call using its session ID. This endpoint returns complete call data including all participants, their individual metrics, and recording information.
+
+## When to Use
+
+| Scenario | Description |
+|----------|-------------|
+| Call details page | Display comprehensive information about a completed call |
+| Recording access | Get the recording URL for playback or download |
+| Participant analytics | View individual participant metrics (join time, duration, etc.) |
+| Debugging | Investigate issues with a specific call session |
+
+## Example Request
+
+```bash
+curl -X GET "https://{appId}.call-{region}.cometchat.io/v3/calls/v1.us.31780434a95d45.16923681138d75114d60d1345a22e4cc612263fb26c0b5cf92" \
+ -H "apikey: YOUR_REST_API_KEY"
+```
+
+## Response Details
+
+The response includes:
+
+- **Call metadata**: Type, mode, status, duration, timestamps
+- **Participants array**: Each participant's UID, device ID, join/leave times, and individual audio/video minutes
+- **Recordings array**: Recording IDs, URLs, duration, and timestamps (if `hasRecording` is true)
+
+
+The `sessionId` is returned when a call is initiated via the SDK or can be found in the [List Calls](/calls/api/list-calls) response.
+
+
+## Participant States
+
+Each participant in the response has a `state` field:
+
+| State | Description |
+|-------|-------------|
+| `ongoing` | Participant is currently in the call |
+| `ended` | Participant left the call normally |
+| `unanswered` | Participant didn't answer the call |
+| `rejected` | Participant rejected the call |
diff --git a/calls/api/list-calls.mdx b/calls/api/list-calls.mdx
new file mode 100644
index 00000000..0ba3dc35
--- /dev/null
+++ b/calls/api/list-calls.mdx
@@ -0,0 +1,36 @@
+---
+title: "List Calls"
+sidebarTitle: "List Calls"
+openapi: get /calls
+---
+
+Retrieve a paginated list of all calls in your application. Use query parameters to filter by call type, status, date range, and more.
+
+## Common Use Cases
+
+| Use Case | Query Parameters |
+|----------|------------------|
+| Get all video calls | `type=video` |
+| Get ongoing calls | `status=ongoing` |
+| Get calls with recordings | `hasRecording=true` |
+| Get calls for a specific user | `uid=user123` |
+| Get group calls only | `receiverType=group` |
+| Get calls from a specific date | `startedAt=1692368000` |
+
+## Example Request
+
+```bash
+curl -X GET "https://{appId}.call-{region}.cometchat.io/v3/calls?type=video&status=ended&hasRecording=true" \
+ -H "apikey: YOUR_REST_API_KEY"
+```
+
+## Filtering Tips
+
+- Combine multiple filters to narrow results (e.g., `type=video&status=ended`)
+- Use `startedAt` and `endedAt` for date range queries (Unix timestamps)
+- Filter by `participantsCount` to find calls with specific attendance
+- Use `uid` to get all calls involving a specific user
+
+
+Results are paginated. Check the `meta.pagination` object in the response for total count and page information.
+
diff --git a/calls/api/overview.mdx b/calls/api/overview.mdx
new file mode 100644
index 00000000..d309af3b
--- /dev/null
+++ b/calls/api/overview.mdx
@@ -0,0 +1,239 @@
+---
+title: "Overview"
+sidebarTitle: "Overview"
+---
+
+The Calls API provides programmatic access to call logs and analytics. Use these APIs to retrieve call history, participant details, duration metrics, and recording information for your application.
+
+## Base URL
+
+```
+https://{appId}.call-{region}.cometchat.io/v3
+```
+
+| Variable | Description |
+|----------|-------------|
+| `appId` | Your CometChat App ID |
+| `region` | Your app's region: `us`, `eu`, or `in` |
+
+## Authentication
+
+All API requests require authentication using your REST API Key in the header:
+
+```bash
+curl -X GET "https://{appId}.call-{region}.cometchat.io/v3/calls" \
+ -H "apikey: YOUR_REST_API_KEY"
+```
+
+
+Use the REST API Key from your [CometChat Dashboard](https://app.cometchat.com). This key has full access scope and should only be used server-side.
+
+
+---
+
+## Available Endpoints
+
+| Endpoint | Method | Description |
+|----------|--------|-------------|
+| [/calls](/calls/api/list-calls) | GET | List all calls with filtering options |
+| [/calls/{sessionId}](/calls/api/get-call) | GET | Get details of a specific call |
+
+---
+
+## Use Cases
+
+| Use Case | Endpoint | Description |
+|----------|----------|-------------|
+| Call history | List Calls | Display call logs in your app |
+| Analytics dashboard | List Calls | Aggregate call duration and participant metrics |
+| Call details page | Get Call | Show detailed information for a specific call |
+| Recording access | Get Call | Retrieve recording URLs for playback |
+| Billing reports | List Calls | Calculate audio/video minutes for billing |
+
+---
+
+## Call Object
+
+The call object contains all information about a call session.
+
+| Property | Type | Description |
+|----------|------|-------------|
+| `sessionId` | string | Unique identifier for the call |
+| `type` | string | Call type: `audio` or `video` |
+| `mode` | string | Call mode: `call`, `meet`, or `presenter` |
+| `status` | string | Current status: `initiated`, `ongoing`, `ended`, `unanswered`, `rejected`, `canceled` |
+| `receiverType` | string | Receiver type: `user` or `group` |
+| `initiator` | string | UID of the user who initiated the call |
+| `receiver` | string | UID of the user or GUID of the group receiving the call |
+| `totalParticipants` | integer | Number of participants (multiple devices counted separately) |
+| `totalAudioMinutes` | float | Total audio minutes across all participants |
+| `totalVideoMinutes` | float | Total video minutes across all participants |
+| `totalDurationInMinutes` | float | Total call duration (audio + video minutes) |
+| `totalDuration` | string | Duration in timer format (e.g., "00:05:30") |
+| `hasRecording` | boolean | Whether the call has recordings |
+| `initiatedAt` | integer | Unix timestamp when call was initiated |
+| `startedAt` | integer | Unix timestamp when call started (first participant joined) |
+| `endedAt` | integer | Unix timestamp when call ended |
+| `participants` | array | List of participant objects |
+| `recordings` | array | List of recording objects (if `hasRecording` is true) |
+
+### Call Status Values
+
+| Status | Description |
+|--------|-------------|
+| `initiated` | Call has been initiated but no one has joined yet |
+| `ongoing` | Call is currently in progress |
+| `ended` | Call has ended normally |
+| `unanswered` | Call was not answered within the timeout period |
+| `rejected` | Receiver rejected the call |
+| `canceled` | Caller canceled before receiver answered |
+
+### Call Mode Values
+
+| Mode | Description |
+|------|-------------|
+| `call` | Standard 1-on-1 or group call initiated via SDK |
+| `meet` | Meeting/conference call with a shared session ID |
+| `presenter` | Webinar-style call with presenter and viewers |
+
+---
+
+## Participant Object
+
+Each participant in a call has the following properties:
+
+| Property | Type | Description |
+|----------|------|-------------|
+| `uid` | string | Unique identifier of the user |
+| `deviceId` | string | Unique identifier of the device used to join |
+| `isJoined` | boolean | Whether the user actually joined the call |
+| `state` | string | Participant state: `ongoing`, `ended`, `unanswered`, `rejected` |
+| `joinedAt` | integer | Unix timestamp when participant joined |
+| `leftAt` | integer | Unix timestamp when participant left |
+| `totalAudioMinutes` | float | Audio minutes for this participant |
+| `totalVideoMinutes` | float | Video minutes for this participant |
+| `totalDurationInMinutes` | float | Total duration for this participant |
+
+
+If a user joins from multiple devices, each device is counted as a separate participant entry.
+
+
+---
+
+## Recording Object
+
+When a call has recordings, each recording contains:
+
+| Property | Type | Description |
+|----------|------|-------------|
+| `rid` | string | Unique identifier of the recording |
+| `recording_url` | string | S3 URL to download/stream the recording |
+| `duration` | float | Recording duration in minutes |
+| `startTime` | integer | Unix timestamp when recording started |
+| `endTime` | integer | Unix timestamp when recording ended |
+
+---
+
+## Example Response
+
+```json
+{
+ "data": {
+ "sessionId": "v1.us.31780434a95d45.16923681138d75114d60d1345a22e4cc612263fb26c0b5cf92",
+ "type": "audio",
+ "mode": "call",
+ "status": "ended",
+ "receiverType": "user",
+ "initiator": "superhero8",
+ "receiver": "superhero2",
+ "totalParticipants": 2,
+ "totalAudioMinutes": 0.32,
+ "totalVideoMinutes": 0,
+ "totalDurationInMinutes": 0.32,
+ "totalDuration": "00:00:19",
+ "hasRecording": false,
+ "initiatedAt": 1692368113,
+ "startedAt": 1692368127,
+ "endedAt": 1692368146,
+ "participants": [
+ {
+ "uid": "superhero8",
+ "deviceId": "70ecae89-b71c-4bb3-8220-b7c99ec1658f@rtc.cometchat.com/hsYWb5ul",
+ "isJoined": true,
+ "state": "ended",
+ "joinedAt": 1692368128,
+ "leftAt": 1692368144,
+ "totalAudioMinutes": 0.27,
+ "totalVideoMinutes": 0,
+ "totalDurationInMinutes": 0.27
+ },
+ {
+ "uid": "superhero2",
+ "deviceId": "c9ed493e-8495-428d-b6ee-b32019cc57ce@rtc.cometchat.com/CKT3xgR4",
+ "isJoined": true,
+ "state": "ended",
+ "joinedAt": 1692368132,
+ "leftAt": 1692368146,
+ "totalAudioMinutes": 0.23,
+ "totalVideoMinutes": 0,
+ "totalDurationInMinutes": 0.23
+ }
+ ]
+ }
+}
+```
+
+---
+
+## Pagination
+
+List endpoints return paginated results with metadata:
+
+```json
+{
+ "data": [...],
+ "meta": {
+ "pagination": {
+ "total": 150,
+ "count": 25,
+ "per_page": 25,
+ "current_page": 1,
+ "total_pages": 6
+ }
+ }
+}
+```
+
+| Property | Description |
+|----------|-------------|
+| `total` | Total number of records |
+| `count` | Number of records in current response |
+| `per_page` | Records per page |
+| `current_page` | Current page number |
+| `total_pages` | Total number of pages |
+
+---
+
+## Error Handling
+
+The API returns standard HTTP status codes:
+
+| Status Code | Description |
+|-------------|-------------|
+| `200` | Success |
+| `400` | Bad request - Invalid parameters |
+| `401` | Unauthorized - Invalid or missing API key |
+| `404` | Not found - Call session doesn't exist |
+| `429` | Rate limited - Too many requests |
+| `500` | Server error |
+
+Error responses include a message:
+
+```json
+{
+ "error": {
+ "code": "ERR_SESSION_NOT_FOUND",
+ "message": "The specified session ID does not exist"
+ }
+}
+```
diff --git a/calls/flutter/overview.mdx b/calls/flutter/overview.mdx
new file mode 100644
index 00000000..e37718e1
--- /dev/null
+++ b/calls/flutter/overview.mdx
@@ -0,0 +1,6 @@
+---
+title: "Calls SDK"
+sidebarTitle: "Overview"
+---
+
+Documentation coming soon for Flutter Calls SDK.
diff --git a/calls/ionic/overview.mdx b/calls/ionic/overview.mdx
new file mode 100644
index 00000000..ab0871fd
--- /dev/null
+++ b/calls/ionic/overview.mdx
@@ -0,0 +1,6 @@
+---
+title: "Calls SDK"
+sidebarTitle: "Overview"
+---
+
+Documentation coming soon for Ionic Calls SDK.
diff --git a/calls/ios/actions.mdx b/calls/ios/actions.mdx
new file mode 100644
index 00000000..2b20e2f0
--- /dev/null
+++ b/calls/ios/actions.mdx
@@ -0,0 +1,432 @@
+---
+title: "Actions"
+sidebarTitle: "Actions"
+---
+
+Use call actions to create your own custom controls or trigger call functionality dynamically based on your use case. All actions are called on the `CallSession.shared` singleton instance during an active call session.
+
+## Get CallSession Instance
+
+The `CallSession` is a singleton that manages the active call. All actions are accessed through this instance.
+
+
+
+```swift
+let callSession = CallSession.shared
+```
+
+
+```objectivec
+CallSession *callSession = [CallSession shared];
+```
+
+
+
+
+Always check `isCallSessionActive()` before calling actions to ensure there's an active call.
+
+
+## Actions
+
+### Mute Audio
+
+Mutes your local microphone, stopping audio transmission to other participants.
+
+
+
+```swift
+CallSession.shared.muteAudio()
+```
+
+
+```objectivec
+[[CallSession shared] muteAudio];
+```
+
+
+
+### Unmute Audio
+
+Unmutes your local microphone, resuming audio transmission.
+
+
+
+```swift
+CallSession.shared.unMuteAudio()
+```
+
+
+```objectivec
+[[CallSession shared] unMuteAudio];
+```
+
+
+
+### Set Audio Mode
+
+Changes the audio output device during a call.
+
+
+
+```swift
+CallSession.shared.setAudioMode("SPEAKER")
+CallSession.shared.setAudioMode("EARPIECE")
+CallSession.shared.setAudioMode("BLUETOOTH")
+CallSession.shared.setAudioMode("HEADPHONES")
+```
+
+
+```objectivec
+[[CallSession shared] setAudioMode:@"SPEAKER"];
+[[CallSession shared] setAudioMode:@"EARPIECE"];
+[[CallSession shared] setAudioMode:@"BLUETOOTH"];
+[[CallSession shared] setAudioMode:@"HEADPHONES"];
+```
+
+
+
+
+| Value | Description |
+|-------|-------------|
+| `SPEAKER` | Routes audio through device loudspeaker |
+| `EARPIECE` | Routes audio through phone earpiece |
+| `BLUETOOTH` | Routes audio through connected Bluetooth device |
+| `HEADPHONES` | Routes audio through wired headphones |
+
+
+### Pause Video
+
+Turns off your local camera, stopping video transmission. Other participants see your avatar.
+
+
+
+```swift
+CallSession.shared.pauseVideo()
+```
+
+
+```objectivec
+[[CallSession shared] pauseVideo];
+```
+
+
+
+### Resume Video
+
+Turns on your local camera, resuming video transmission.
+
+
+
+```swift
+CallSession.shared.resumeVideo()
+```
+
+
+```objectivec
+[[CallSession shared] resumeVideo];
+```
+
+
+
+### Switch Camera
+
+Toggles between front and back cameras without interrupting the video stream.
+
+
+
+```swift
+CallSession.shared.switchCamera()
+```
+
+
+```objectivec
+[[CallSession shared] switchCamera];
+```
+
+
+
+### Start Recording
+
+Begins server-side recording of the call. All participants are notified.
+
+
+
+```swift
+CallSession.shared.startRecording()
+```
+
+
+```objectivec
+[[CallSession shared] startRecording];
+```
+
+
+
+
+Recording requires the feature to be enabled for your CometChat app.
+
+
+### Stop Recording
+
+Stops the current recording. The recording is saved and accessible via the dashboard.
+
+
+
+```swift
+CallSession.shared.stopRecording()
+```
+
+
+```objectivec
+[[CallSession shared] stopRecording];
+```
+
+
+
+### Mute Participant
+
+Mutes a specific participant's audio. This is a moderator action.
+
+
+
+```swift
+CallSession.shared.muteParticipant(participantId: participant.pid)
+```
+
+
+```objectivec
+[[CallSession shared] muteParticipantWithParticipantId:participant.pid];
+```
+
+
+
+### Pause Participant Video
+
+Pauses a specific participant's video. This is a moderator action.
+
+
+
+```swift
+CallSession.shared.pauseParticipantVideo(participantId: participant.pid)
+```
+
+
+```objectivec
+[[CallSession shared] pauseParticipantVideoWithParticipantId:participant.pid];
+```
+
+
+
+### Pin Participant
+
+Pins a participant to keep them prominently displayed regardless of who is speaking.
+
+
+
+```swift
+CallSession.shared.pinParticipant()
+```
+
+
+```objectivec
+[[CallSession shared] pinParticipant];
+```
+
+
+
+### Unpin Participant
+
+Removes the pin, returning to automatic speaker highlighting.
+
+
+
+```swift
+CallSession.shared.unPinParticipant()
+```
+
+
+```objectivec
+[[CallSession shared] unPinParticipant];
+```
+
+
+
+### Set Layout
+
+Changes the call layout. Each participant can choose their own layout independently.
+
+
+
+```swift
+CallSession.shared.setLayout("TILE")
+CallSession.shared.setLayout("SPOTLIGHT")
+CallSession.shared.setLayout("SIDEBAR")
+```
+
+
+```objectivec
+[[CallSession shared] setLayout:@"TILE"];
+[[CallSession shared] setLayout:@"SPOTLIGHT"];
+[[CallSession shared] setLayout:@"SIDEBAR"];
+```
+
+
+
+
+| Value | Description |
+|-------|-------------|
+| `TILE` | Grid layout with equally-sized tiles |
+| `SPOTLIGHT` | Large view for active speaker, small tiles for others |
+| `SIDEBAR` | Main speaker with participants in a sidebar |
+
+
+### Enable Picture In Picture Layout
+
+Enables PiP mode, allowing the call to continue in a floating window.
+
+
+
+```swift
+CallSession.shared.enablePictureInPictureLayout()
+```
+
+
+```objectivec
+[[CallSession shared] enablePictureInPictureLayout];
+```
+
+
+
+### Disable Picture In Picture Layout
+
+Disables PiP mode, returning to full-screen call interface.
+
+
+
+```swift
+CallSession.shared.disablePictureInPictureLayout()
+```
+
+
+```objectivec
+[[CallSession shared] disablePictureInPictureLayout];
+```
+
+
+
+### Set Chat Button Unread Count
+
+Updates the badge count on the chat button. Pass 0 to hide the badge.
+
+
+
+```swift
+CallSession.shared.setChatButtonUnreadCount(5)
+```
+
+
+```objectivec
+[[CallSession shared] setChatButtonUnreadCount:5];
+```
+
+
+
+### Is Session Active
+
+Returns `true` if a call session is active, `false` otherwise.
+
+
+
+```swift
+let isActive = CallSession.shared.isCallSessionActive()
+```
+
+
+```objectivec
+BOOL isActive = [[CallSession shared] isCallSessionActive];
+```
+
+
+
+### Leave Session
+
+Ends your participation and disconnects gracefully. The call continues for other participants.
+
+
+
+```swift
+CallSession.shared.leaveSession()
+```
+
+
+```objectivec
+[[CallSession shared] leaveSession];
+```
+
+
+
+### Raise Hand
+
+Shows a hand-raised indicator to get attention from other participants.
+
+
+
+```swift
+CallSession.shared.raiseHand()
+```
+
+
+```objectivec
+[[CallSession shared] raiseHand];
+```
+
+
+
+### Lower Hand
+
+Removes the hand-raised indicator.
+
+
+
+```swift
+CallSession.shared.lowerHand()
+```
+
+
+```objectivec
+[[CallSession shared] lowerHand];
+```
+
+
+
+### Hide Settings Panel
+
+Hides the settings panel if it's currently visible.
+
+
+
+```swift
+CallSession.shared.hideSettingsPanel()
+```
+
+
+```objectivec
+[[CallSession shared] hideSettingsPanel];
+```
+
+
+
+
+| Property | Type | Description |
+|----------|------|-------------|
+| `uid` | `String` | Unique identifier (CometChat user ID) |
+| `name` | `String` | Display name |
+| `avatar` | `String` | URL of avatar image |
+| `pid` | `String` | Participant ID for this call session |
+| `role` | `String` | Role in the call |
+| `audioMuted` | `Bool` | Whether audio is muted |
+| `videoPaused` | `Bool` | Whether video is paused |
+| `isPinned` | `Bool` | Whether pinned in layout |
+| `isPresenting` | `Bool` | Whether screen sharing |
+| `raisedHandTimestamp` | `Int` | Timestamp when hand was raised |
+
diff --git a/calls/ios/audio-modes.mdx b/calls/ios/audio-modes.mdx
new file mode 100644
index 00000000..ab53af7f
--- /dev/null
+++ b/calls/ios/audio-modes.mdx
@@ -0,0 +1,197 @@
+---
+title: "Audio Modes"
+sidebarTitle: "Audio Modes"
+---
+
+Control audio output routing during calls. Switch between speaker, earpiece, Bluetooth, and wired headphones based on user preference or device availability.
+
+## Available Audio Modes
+
+| Mode | Description |
+|------|-------------|
+| `.speaker` | Routes audio through the device loudspeaker |
+| `.earpiece` | Routes audio through the phone earpiece (for private calls) |
+| `.bluetooth` | Routes audio through a connected Bluetooth device |
+| `.headphones` | Routes audio through wired headphones |
+
+## Set Initial Audio Mode
+
+Configure the audio mode when joining a session:
+
+
+
+```swift
+let sessionSettings = CometChatCalls.sessionSettingsBuilder
+ .setAudioMode(.speaker)
+ .build()
+
+CometChatCalls.joinSession(
+ sessionID: sessionId,
+ callSetting: sessionSettings,
+ container: callViewContainer,
+ onSuccess: { message in
+ print("Joined with speaker mode")
+ },
+ onError: { error in
+ print("Failed: \(error?.errorDescription ?? "")")
+ }
+)
+```
+
+
+```objectivec
+SessionSettings *sessionSettings = [[[CometChatCalls sessionSettingsBuilder]
+ setAudioMode:AudioModeTypeSpeaker]
+ build];
+
+[CometChatCalls joinSessionWithSessionID:sessionId
+ callSetting:sessionSettings
+ container:self.callViewContainer
+ onSuccess:^(NSString * message) {
+ NSLog(@"Joined with speaker mode");
+} onError:^(CometChatCallException * error) {
+ NSLog(@"Failed: %@", error.errorDescription);
+}];
+```
+
+
+
+## Change Audio Mode During Call
+
+Switch audio modes dynamically during an active call:
+
+
+
+```swift
+// Switch to speaker
+CallSession.shared.setAudioMode("SPEAKER")
+
+// Switch to earpiece
+CallSession.shared.setAudioMode("EARPIECE")
+
+// Switch to Bluetooth
+CallSession.shared.setAudioMode("BLUETOOTH")
+
+// Switch to wired headphones
+CallSession.shared.setAudioMode("HEADPHONES")
+```
+
+
+```objectivec
+// Switch to speaker
+[[CallSession shared] setAudioMode:@"SPEAKER"];
+
+// Switch to earpiece
+[[CallSession shared] setAudioMode:@"EARPIECE"];
+
+// Switch to Bluetooth
+[[CallSession shared] setAudioMode:@"BLUETOOTH"];
+
+// Switch to wired headphones
+[[CallSession shared] setAudioMode:@"HEADPHONES"];
+```
+
+
+
+## Listen for Audio Mode Changes
+
+Monitor audio mode changes using `MediaEventsListener`:
+
+
+
+```swift
+class CallViewController: UIViewController, MediaEventsListener {
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+ CallSession.shared.addMediaEventsListener(self)
+ }
+
+ deinit {
+ CallSession.shared.removeMediaEventsListener(self)
+ }
+
+ func onAudioModeChanged(audioModeType: AudioModeType) {
+ switch audioModeType {
+ case .speaker:
+ print("Switched to speaker")
+ case .earpiece:
+ print("Switched to earpiece")
+ case .bluetooth:
+ print("Switched to Bluetooth")
+ case .headphones:
+ print("Switched to headphones")
+ default:
+ break
+ }
+ // Update audio mode button icon
+ updateAudioModeIcon(audioModeType)
+ }
+
+ // Other callbacks...
+ func onAudioMuted() {}
+ func onAudioUnMuted() {}
+ func onVideoPaused() {}
+ func onVideoResumed() {}
+ func onRecordingStarted() {}
+ func onRecordingStopped() {}
+ func onScreenShareStarted() {}
+ func onScreenShareStopped() {}
+ func onCameraFacingChanged(cameraFacing: CameraFacing) {}
+}
+```
+
+
+```objectivec
+@interface CallViewController ()
+@end
+
+@implementation CallViewController
+
+- (void)viewDidLoad {
+ [super viewDidLoad];
+ [[CallSession shared] addMediaEventsListener:self];
+}
+
+- (void)dealloc {
+ [[CallSession shared] removeMediaEventsListener:self];
+}
+
+- (void)onAudioModeChangedWithAudioModeType:(AudioModeType)audioModeType {
+ // Update audio mode button icon
+ [self updateAudioModeIcon:audioModeType];
+}
+
+// Other callbacks...
+
+@end
+```
+
+
+
+## Hide Audio Mode Button
+
+To prevent users from changing the audio mode, hide the button in the call UI:
+
+
+
+```swift
+let sessionSettings = CometChatCalls.sessionSettingsBuilder
+ .setAudioMode(.speaker) // Fixed audio mode
+ .hideAudioModeButton(true) // Hide toggle button
+ .build()
+```
+
+
+```objectivec
+SessionSettings *sessionSettings = [[[[CometChatCalls sessionSettingsBuilder]
+ setAudioMode:AudioModeTypeSpeaker]
+ hideAudioModeButton:YES]
+ build];
+```
+
+
+
+
+The SDK automatically detects connected audio devices. If Bluetooth or wired headphones are connected, they become available as audio mode options.
+
diff --git a/calls/ios/authentication.mdx b/calls/ios/authentication.mdx
new file mode 100644
index 00000000..058c8032
--- /dev/null
+++ b/calls/ios/authentication.mdx
@@ -0,0 +1,188 @@
+---
+title: "Authentication"
+sidebarTitle: "Authentication"
+---
+
+Before users can make or receive calls, they must be authenticated with the CometChat Calls SDK. This guide covers the login and logout methods.
+
+
+**Sample Users**
+
+CometChat provides 5 test users: `cometchat-uid-1`, `cometchat-uid-2`, `cometchat-uid-3`, `cometchat-uid-4`, and `cometchat-uid-5`.
+
+
+## Check Login Status
+
+Before calling `login()`, check if a user is already logged in using `getLoggedInUser()`. The SDK maintains the session internally, so you only need to login once per user session.
+
+
+
+```swift
+if let loggedInUser = CometChatCalls.getLoggedInUser() {
+ // User is already logged in
+ print("User already logged in: \(loggedInUser.uid ?? "")")
+} else {
+ // No user logged in, proceed with login
+}
+```
+
+
+```objectivec
+CallsUser *loggedInUser = [CometChatCalls getLoggedInUser];
+
+if (loggedInUser != nil) {
+ // User is already logged in
+ NSLog(@"User already logged in: %@", loggedInUser.uid);
+} else {
+ // No user logged in, proceed with login
+}
+```
+
+
+
+The `getLoggedInUser()` method returns a `CallsUser` object if a user is logged in, or `nil` if no session exists.
+
+## Login with UID and API Key
+
+This method is suitable for development and testing. For production apps, use [Auth Token login](#login-with-auth-token) instead.
+
+
+**Security Notice**
+
+Using the API Key directly in client code is not recommended for production. Use Auth Token authentication for enhanced security.
+
+
+
+
+```swift
+let uid = "cometchat-uid-1" // Replace with your user's UID
+let apiKey = "API_KEY" // Replace with your API Key
+
+if CometChatCalls.getLoggedInUser() == nil {
+ CometChatCalls.login(UID: uid, apiKey: apiKey, onSuccess: { user in
+ print("Login successful: \(user.uid ?? "")")
+ }, onError: { error in
+ print("Login failed: \(error.errorDescription)")
+ })
+} else {
+ // User already logged in
+}
+```
+
+
+```objectivec
+NSString *uid = @"cometchat-uid-1"; // Replace with your user's UID
+NSString *apiKey = @"API_KEY"; // Replace with your API Key
+
+if ([CometChatCalls getLoggedInUser] == nil) {
+ [CometChatCalls loginWithUID:uid
+ apiKey:apiKey
+ onSuccess:^(CallsUser * user) {
+ NSLog(@"Login successful: %@", user.uid);
+ } onError:^(CometChatCallException error) {
+ NSLog(@"Login failed: %@", error.errorDescription);
+ }];
+} else {
+ // User already logged in
+}
+```
+
+
+
+| Parameter | Type | Description |
+|-----------|------|-------------|
+| `UID` | String | The unique identifier of the user to login |
+| `apiKey` | String | Your CometChat API Key |
+| `onSuccess` | Closure | Closure called with `CallsUser` on success |
+| `onError` | Closure | Closure called with error on failure |
+
+## Login with Auth Token
+
+This is the recommended authentication method for production applications. The Auth Token is generated server-side, keeping your API Key secure.
+
+### Auth Token Flow
+
+1. User authenticates with your backend
+2. Your backend calls the [CometChat Create Auth Token API](https://api-explorer.cometchat.com/reference/create-authtoken)
+3. Your backend returns the Auth Token to the client
+4. Client uses the Auth Token to login
+
+
+
+```swift
+let authToken = "AUTH_TOKEN" // Token received from your backend
+
+CometChatCalls.login(authToken: authToken, onSuccess: { user in
+ print("Login successful: \(user.uid ?? "")")
+}, onError: { error in
+ print("Login failed: \(error.errorDescription)")
+})
+```
+
+
+```objectivec
+NSString *authToken = @"AUTH_TOKEN"; // Token received from your backend
+
+[CometChatCalls loginWithAuthToken:authToken
+ onSuccess:^(CallsUser * user) {
+ NSLog(@"Login successful: %@", user.uid);
+} onError:^(CometChatCallException error) {
+ NSLog(@"Login failed: %@", error.errorDescription);
+}];
+```
+
+
+
+| Parameter | Type | Description |
+|-----------|------|-------------|
+| `authToken` | String | Auth Token generated via CometChat API |
+| `onSuccess` | Closure | Closure called with `CallsUser` on success |
+| `onError` | Closure | Closure called with error on failure |
+
+## CallsUser Object
+
+On successful login, the callback returns a `CallsUser` object containing user information:
+
+| Property | Type | Description |
+|----------|------|-------------|
+| `uid` | String? | Unique identifier of the user |
+| `name` | String? | Display name of the user |
+| `avatar` | String? | URL of the user's avatar image |
+| `status` | UserStatus | User's online status |
+
+## Logout
+
+Call `logout()` when the user signs out of your application. This clears the local session and disconnects from CometChat services.
+
+
+
+```swift
+CometChatCalls.logout(onSuccess: { message in
+ print("Logout successful")
+}, onError: { error in
+ print("Logout failed: \(error.errorDescription)")
+})
+```
+
+
+```objectivec
+[CometChatCalls logoutOnSuccess:^(NSString * message) {
+ NSLog(@"Logout successful");
+} onError:^(CometChatCallException error) {
+ NSLog(@"Logout failed: %@", error.errorDescription);
+}];
+```
+
+
+
+## Error Handling
+
+Common authentication errors:
+
+| Error Code | Description |
+|------------|-------------|
+| `ERROR_INVALID_UID` | The provided UID is empty or invalid |
+| `ERROR_UID_WITH_SPACE` | The UID contains spaces (not allowed) |
+| `ERROR_API_KEY_NOT_FOUND` | The API Key is missing or invalid |
+| `ERROR_BLANK_AUTHTOKEN` | The Auth Token is empty |
+| `ERROR_AUTHTOKEN_WITH_SPACE` | The Auth Token contains spaces |
diff --git a/calls/ios/background-handling.mdx b/calls/ios/background-handling.mdx
new file mode 100644
index 00000000..039d209e
--- /dev/null
+++ b/calls/ios/background-handling.mdx
@@ -0,0 +1,67 @@
+---
+title: "Background Handling"
+sidebarTitle: "Background Handling"
+---
+
+Keep calls alive when users navigate away from your app. Background handling ensures the call continues running when users press the home button, switch to another app, or lock their device.
+
+## Overview
+
+When a user leaves your call view controller, iOS may suspend it to free resources. Proper background handling:
+- Keeps the call session active in the background
+- Maintains audio/video streams
+- Allows users to return to the call seamlessly
+
+```mermaid
+flowchart LR
+ subgraph "User in Call"
+ A[Call VC] --> B{User Action}
+ end
+
+ B -->|Stays in app| A
+ B -->|Presses HOME| C[Background Mode]
+ B -->|Opens other app| C
+ B -->|Locks device| C
+
+ C --> D[Call Continues]
+ D -->|Returns to app| A
+```
+
+## When to Use
+
+| Scenario | Solution |
+|----------|----------|
+| User stays in call view | No action needed |
+| User presses HOME during call | **Use Background Modes** |
+| User switches to another app | **Use Background Modes** |
+| User locks device during call | **Use Background Modes** |
+| Receiving calls when app is killed | [VoIP Calling](/calls/ios/voip-calling) handles this |
+
+
+Background Handling is different from [VoIP Calling](/calls/ios/voip-calling). VoIP handles **receiving** calls when the app is not running. Background Handling keeps an **active** call alive when the user leaves the app.
+
+
+---
+
+## Enable Background Modes
+
+In Xcode, add the following background modes to your app:
+
+**1.** Go to your target's "Signing & Capabilities" tab
+
+**2.** Add "Background Modes" capability
+
+**3.** Enable the following modes:
+ - Audio, AirPlay, and Picture in Picture
+ - Voice over IP
+
+
+The Calls SDK automatically handles audio session configuration. You only need to enable the background modes capability.
+
+
+---
+
+## Related Documentation
+
+- [VoIP Calling](/calls/ios/voip-calling) - Receive calls when app is killed
+- [Events](/calls/ios/events) - Session status events
diff --git a/calls/ios/call-layouts.mdx b/calls/ios/call-layouts.mdx
new file mode 100644
index 00000000..d1240938
--- /dev/null
+++ b/calls/ios/call-layouts.mdx
@@ -0,0 +1,185 @@
+---
+title: "Call Layouts"
+sidebarTitle: "Call Layouts"
+---
+
+Choose how participants are displayed during a call. The SDK provides multiple layout options to suit different use cases like team meetings, presentations, or one-on-one calls.
+
+## Available Layouts
+
+| Layout | Description | Best For |
+|--------|-------------|----------|
+| `.tile` | Grid layout with equally-sized tiles for all participants | Team meetings, group discussions |
+| `.spotlight` | Large view for the other participant, small tile for yourself | One-on-one calls, presentations, webinars |
+| `.sidebar` | Main speaker with participants in a sidebar | Interviews, panel discussions |
+
+## Set Initial Layout
+
+Configure the layout when joining a session using `SessionSettingsBuilder`:
+
+
+
+```swift
+let sessionSettings = CometChatCalls.sessionSettingsBuilder
+ .setLayout(.tile)
+ .build()
+
+CometChatCalls.joinSession(
+ sessionID: sessionId,
+ callSetting: sessionSettings,
+ container: callViewContainer,
+ onSuccess: { message in
+ print("Joined with TILE layout")
+ },
+ onError: { error in
+ print("Failed: \(error?.errorDescription ?? "")")
+ }
+)
+```
+
+
+```objectivec
+SessionSettings *sessionSettings = [[[CometChatCalls sessionSettingsBuilder]
+ setLayout:LayoutTypeTile]
+ build];
+
+[CometChatCalls joinSessionWithSessionID:sessionId
+ callSetting:sessionSettings
+ container:self.callViewContainer
+ onSuccess:^(NSString * message) {
+ NSLog(@"Joined with TILE layout");
+} onError:^(CometChatCallException * error) {
+ NSLog(@"Failed: %@", error.errorDescription);
+}];
+```
+
+
+
+## Change Layout During Call
+
+Switch layouts dynamically during an active call using `setLayout()`:
+
+
+
+```swift
+// Switch to Spotlight layout
+CallSession.shared.setLayout("SPOTLIGHT")
+
+// Switch to Tile layout
+CallSession.shared.setLayout("TILE")
+
+// Switch to Sidebar layout
+CallSession.shared.setLayout("SIDEBAR")
+```
+
+
+```objectivec
+// Switch to Spotlight layout
+[[CallSession shared] setLayout:@"SPOTLIGHT"];
+
+// Switch to Tile layout
+[[CallSession shared] setLayout:@"TILE"];
+
+// Switch to Sidebar layout
+[[CallSession shared] setLayout:@"SIDEBAR"];
+```
+
+
+
+
+Each participant can choose their own layout independently. Changing your layout does not affect other participants.
+
+
+## Listen for Layout Changes
+
+Monitor layout changes using `LayoutListener`:
+
+
+
+```swift
+class CallViewController: UIViewController, LayoutListener {
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+ CallSession.shared.addLayoutListener(self)
+ }
+
+ deinit {
+ CallSession.shared.removeLayoutListener(self)
+ }
+
+ func onCallLayoutChanged(layoutType: LayoutType) {
+ switch layoutType {
+ case .tile:
+ print("Switched to Tile layout")
+ case .spotlight:
+ print("Switched to Spotlight layout")
+ case .sidebar:
+ print("Switched to Sidebar layout")
+ default:
+ break
+ }
+ // Update layout toggle button icon
+ updateLayoutIcon(layoutType)
+ }
+
+ func onParticipantListVisible() {}
+ func onParticipantListHidden() {}
+ func onPictureInPictureLayoutEnabled() {}
+ func onPictureInPictureLayoutDisabled() {}
+}
+```
+
+
+```objectivec
+@interface CallViewController ()
+@end
+
+@implementation CallViewController
+
+- (void)viewDidLoad {
+ [super viewDidLoad];
+ [[CallSession shared] addLayoutListener:self];
+}
+
+- (void)dealloc {
+ [[CallSession shared] removeLayoutListener:self];
+}
+
+- (void)onCallLayoutChangedWithLayoutType:(LayoutType)layoutType {
+ // Update layout toggle button icon
+ [self updateLayoutIcon:layoutType];
+}
+
+- (void)onParticipantListVisible {}
+- (void)onParticipantListHidden {}
+- (void)onPictureInPictureLayoutEnabled {}
+- (void)onPictureInPictureLayoutDisabled {}
+
+@end
+```
+
+
+
+## Hide Layout Toggle Button
+
+To prevent users from changing the layout, hide the layout toggle button in the call UI:
+
+
+
+```swift
+let sessionSettings = CometChatCalls.sessionSettingsBuilder
+ .setLayout(.spotlight) // Fixed layout
+ .hideChangeLayoutButton(true) // Hide toggle button
+ .build()
+```
+
+
+```objectivec
+SessionSettings *sessionSettings = [[[[CometChatCalls sessionSettingsBuilder]
+ setLayout:LayoutTypeSpotlight]
+ hideChangeLayoutButton:YES]
+ build];
+```
+
+
diff --git a/calls/ios/call-logs.mdx b/calls/ios/call-logs.mdx
new file mode 100644
index 00000000..ca116502
--- /dev/null
+++ b/calls/ios/call-logs.mdx
@@ -0,0 +1,249 @@
+---
+title: "Call Logs"
+sidebarTitle: "Call Logs"
+---
+
+Retrieve call history for your application. Call logs provide detailed information about past calls including duration, participants, recordings, and status.
+
+## Fetch Call Logs
+
+Use `CallLogsRequest` to fetch call logs with pagination support. The builder pattern allows you to filter results by various criteria.
+
+
+
+```swift
+let callLogRequest = CallLogsRequest.CallLogsRequestBuilder()
+ .setLimit(30)
+ .build()
+
+callLogRequest.fetchNext(onSuccess: { callLogs in
+ for callLog in callLogs {
+ print("Session: \(callLog.sessionID ?? "")")
+ print("Duration: \(callLog.totalDuration ?? "")")
+ print("Status: \(callLog.status ?? "")")
+ }
+}, onError: { error in
+ print("Error: \(error?.errorDescription ?? "")")
+})
+```
+
+
+```objectivec
+CallLogsRequest *callLogRequest = [[[CallLogsRequest CallLogsRequestBuilder]
+ setLimit:30]
+ build];
+
+[callLogRequest fetchNextOnSuccess:^(NSArray * callLogs) {
+ for (CallLog *callLog in callLogs) {
+ NSLog(@"Session: %@", callLog.sessionID);
+ NSLog(@"Duration: %@", callLog.totalDuration);
+ NSLog(@"Status: %@", callLog.status);
+ }
+} onError:^(CometChatCallException * error) {
+ NSLog(@"Error: %@", error.errorDescription);
+}];
+```
+
+
+
+## CallLogsRequestBuilder
+
+Configure the request using the builder methods:
+
+| Method | Type | Description |
+|--------|------|-------------|
+| `setLimit(Int)` | Int | Number of call logs to fetch per request (default: 30, max: 100) |
+| `setSessionType(String)` | String | Filter by call type: `video` or `audio` |
+| `setCallStatus(String)` | String | Filter by call status |
+| `setHasRecording(Bool)` | Bool | Filter calls that have recordings |
+| `setCallCategory(String)` | String | Filter by category: `call` or `meet` |
+| `setCallDirection(String)` | String | Filter by direction: `incoming` or `outgoing` |
+| `setUid(String)` | String | Filter calls with a specific user |
+| `setGuid(String)` | String | Filter calls with a specific group |
+
+### Filter Examples
+
+
+
+```swift
+// Fetch only video calls
+let videoCallsRequest = CallLogsRequest.CallLogsRequestBuilder()
+ .setSessionType("video")
+ .setLimit(20)
+ .build()
+
+// Fetch calls with recordings
+let recordedCallsRequest = CallLogsRequest.CallLogsRequestBuilder()
+ .setHasRecording(true)
+ .build()
+
+// Fetch missed incoming calls
+let missedCallsRequest = CallLogsRequest.CallLogsRequestBuilder()
+ .setCallStatus("missed")
+ .setCallDirection("incoming")
+ .build()
+
+// Fetch calls with a specific user
+let userCallsRequest = CallLogsRequest.CallLogsRequestBuilder()
+ .setUid("user_id")
+ .build()
+```
+
+
+```objectivec
+// Fetch only video calls
+CallLogsRequest *videoCallsRequest = [[[[CallLogsRequest CallLogsRequestBuilder]
+ setSessionType:@"video"]
+ setLimit:20]
+ build];
+
+// Fetch calls with recordings
+CallLogsRequest *recordedCallsRequest = [[[CallLogsRequest CallLogsRequestBuilder]
+ setHasRecording:YES]
+ build];
+
+// Fetch missed incoming calls
+CallLogsRequest *missedCallsRequest = [[[[CallLogsRequest CallLogsRequestBuilder]
+ setCallStatus:@"missed"]
+ setCallDirection:@"incoming"]
+ build];
+
+// Fetch calls with a specific user
+CallLogsRequest *userCallsRequest = [[[CallLogsRequest CallLogsRequestBuilder]
+ setUid:@"user_id"]
+ build];
+```
+
+
+
+## Pagination
+
+Use `fetchNext()` and `fetchPrevious()` for pagination:
+
+
+
+```swift
+// Fetch next page
+callLogRequest.fetchNext(onSuccess: { callLogs in
+ // Handle next page
+}, onError: { error in
+ print("Error: \(error?.errorDescription ?? "")")
+})
+
+// Fetch previous page
+callLogRequest.fetchPrevious(onSuccess: { callLogs in
+ // Handle previous page
+}, onError: { error in
+ print("Error: \(error?.errorDescription ?? "")")
+})
+```
+
+
+```objectivec
+// Fetch next page
+[callLogRequest fetchNextOnSuccess:^(NSArray * callLogs) {
+ // Handle next page
+} onError:^(CometChatCallException * error) {
+ NSLog(@"Error: %@", error.errorDescription);
+}];
+
+// Fetch previous page
+[callLogRequest fetchPreviousOnSuccess:^(NSArray * callLogs) {
+ // Handle previous page
+} onError:^(CometChatCallException * error) {
+ NSLog(@"Error: %@", error.errorDescription);
+}];
+```
+
+
+
+## CallLog Object
+
+Each `CallLog` object contains detailed information about a call:
+
+| Property | Type | Description |
+|----------|------|-------------|
+| `sessionID` | String | Unique identifier for the call session |
+| `initiator` | CallEntity | User who initiated the call |
+| `receiver` | CallEntity | User or group that received the call |
+| `receiverType` | String | `user` or `group` |
+| `type` | String | Call type: `video` or `audio` |
+| `status` | String | Final status of the call |
+| `callCategory` | String | Category: `call` or `meet` |
+| `initiatedAt` | Int | Timestamp when call was initiated |
+| `endedAt` | Int | Timestamp when call ended |
+| `totalDuration` | String | Human-readable duration (e.g., "5:30") |
+| `totalDurationInMinutes` | Double | Duration in minutes |
+| `totalAudioMinutes` | Double | Audio duration in minutes |
+| `totalVideoMinutes` | Double | Video duration in minutes |
+| `totalParticipants` | Int | Number of participants |
+| `hasRecording` | Bool | Whether the call was recorded |
+| `recordings` | [Recording] | List of recording objects |
+
+## Access Recordings
+
+If a call has recordings, access them through the `recordings` property:
+
+
+
+```swift
+callLogRequest.fetchNext(onSuccess: { callLogs in
+ for callLog in callLogs {
+ if callLog.hasRecording {
+ for recording in callLog.recordings ?? [] {
+ print("Recording ID: \(recording.rid ?? "")")
+ print("Recording URL: \(recording.recordingURL ?? "")")
+ print("Duration: \(recording.duration) seconds")
+ }
+ }
+ }
+}, onError: { error in
+ print("Error: \(error?.errorDescription ?? "")")
+})
+```
+
+
+```objectivec
+[callLogRequest fetchNextOnSuccess:^(NSArray * callLogs) {
+ for (CallLog *callLog in callLogs) {
+ if (callLog.hasRecording) {
+ for (Recording *recording in callLog.recordings) {
+ NSLog(@"Recording ID: %@", recording.rid);
+ NSLog(@"Recording URL: %@", recording.recordingURL);
+ NSLog(@"Duration: %f seconds", recording.duration);
+ }
+ }
+ }
+} onError:^(CometChatCallException * error) {
+ NSLog(@"Error: %@", error.errorDescription);
+}];
+```
+
+
+
+
+| Status | Description |
+|--------|-------------|
+| `ongoing` | Call is currently in progress |
+| `busy` | Receiver was busy |
+| `rejected` | Call was rejected |
+| `cancelled` | Call was cancelled by initiator |
+| `ended` | Call ended normally |
+| `missed` | Call was missed |
+| `initiated` | Call was initiated but not answered |
+| `unanswered` | Call was not answered |
+
+
+
+| Category | Description |
+|----------|-------------|
+| `call` | Direct call between users |
+| `meet` | Meeting/conference call |
+
+
+
+| Direction | Description |
+|-----------|-------------|
+| `incoming` | Call received by the user |
+| `outgoing` | Call initiated by the user |
+
diff --git a/calls/ios/custom-control-panel.mdx b/calls/ios/custom-control-panel.mdx
new file mode 100644
index 00000000..856c9d83
--- /dev/null
+++ b/calls/ios/custom-control-panel.mdx
@@ -0,0 +1,454 @@
+---
+title: "Custom Control Panel"
+sidebarTitle: "Custom Control Panel"
+---
+
+Build a fully customized control panel for your call interface by hiding the default controls and implementing your own UI with call actions. This guide walks you through creating a custom control panel with essential call controls.
+
+## Overview
+
+Custom control panels allow you to:
+- Match your app's branding and design language
+- Simplify the interface by showing only relevant controls
+- Add custom functionality and workflows
+- Create unique user experiences
+
+This guide demonstrates building a basic custom control panel with:
+- Mute/Unmute audio button
+- Pause/Resume video button
+- Switch camera button
+- End call button
+
+## Prerequisites
+
+- CometChat Calls SDK installed and initialized
+- Active call session (see [Join Session](/calls/ios/join-session))
+- Familiarity with [Actions](/calls/ios/actions) and [Events](/calls/ios/events)
+
+---
+
+## Step 1: Hide Default Controls
+
+Configure your session settings to hide the default control panel:
+
+
+
+```swift
+let sessionSettings = CometChatCalls.sessionSettingsBuilder
+ .hideControlPanel(true)
+ .build()
+```
+
+
+```objectivec
+SessionSettings *sessionSettings = [[[CometChatCalls sessionSettingsBuilder]
+ hideControlPanel:YES]
+ build];
+```
+
+
+
+
+You can also hide individual buttons while keeping the control panel visible. See [SessionSettingsBuilder](/calls/ios/session-settings) for all options.
+
+
+---
+
+## Step 2: Create Custom Layout
+
+Create a custom view for your controls programmatically or in Interface Builder:
+
+
+
+```swift
+class CallViewController: UIViewController {
+
+ // Call container view
+ private let callContainer = UIView()
+
+ // Custom control panel
+ private let controlPanel = UIStackView()
+ private let btnToggleAudio = UIButton(type: .system)
+ private let btnToggleVideo = UIButton(type: .system)
+ private let btnSwitchCamera = UIButton(type: .system)
+ private let btnEndCall = UIButton(type: .system)
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+ setupUI()
+ setupControlListeners()
+ }
+
+ private func setupUI() {
+ view.backgroundColor = .black
+
+ // Setup call container
+ callContainer.translatesAutoresizingMaskIntoConstraints = false
+ view.addSubview(callContainer)
+
+ // Setup control panel
+ controlPanel.axis = .horizontal
+ controlPanel.distribution = .equalSpacing
+ controlPanel.alignment = .center
+ controlPanel.spacing = 20
+ controlPanel.translatesAutoresizingMaskIntoConstraints = false
+ controlPanel.backgroundColor = UIColor.black.withAlphaComponent(0.8)
+ controlPanel.layoutMargins = UIEdgeInsets(top: 16, left: 32, bottom: 16, right: 32)
+ controlPanel.isLayoutMarginsRelativeArrangement = true
+ view.addSubview(controlPanel)
+
+ // Configure buttons
+ configureButton(btnToggleAudio, imageName: "mic.fill", backgroundColor: .darkGray)
+ configureButton(btnToggleVideo, imageName: "video.fill", backgroundColor: .darkGray)
+ configureButton(btnSwitchCamera, imageName: "camera.rotate.fill", backgroundColor: .darkGray)
+ configureButton(btnEndCall, imageName: "phone.down.fill", backgroundColor: .systemRed)
+
+ // Add buttons to control panel
+ controlPanel.addArrangedSubview(btnToggleAudio)
+ controlPanel.addArrangedSubview(btnToggleVideo)
+ controlPanel.addArrangedSubview(btnSwitchCamera)
+ controlPanel.addArrangedSubview(btnEndCall)
+
+ // Layout constraints
+ NSLayoutConstraint.activate([
+ callContainer.topAnchor.constraint(equalTo: view.topAnchor),
+ callContainer.leadingAnchor.constraint(equalTo: view.leadingAnchor),
+ callContainer.trailingAnchor.constraint(equalTo: view.trailingAnchor),
+ callContainer.bottomAnchor.constraint(equalTo: view.bottomAnchor),
+
+ controlPanel.leadingAnchor.constraint(equalTo: view.leadingAnchor),
+ controlPanel.trailingAnchor.constraint(equalTo: view.trailingAnchor),
+ controlPanel.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor),
+ controlPanel.heightAnchor.constraint(equalToConstant: 80)
+ ])
+ }
+
+ private func configureButton(_ button: UIButton, imageName: String, backgroundColor: UIColor) {
+ button.setImage(UIImage(systemName: imageName), for: .normal)
+ button.tintColor = .white
+ button.backgroundColor = backgroundColor
+ button.layer.cornerRadius = 28
+ button.translatesAutoresizingMaskIntoConstraints = false
+ NSLayoutConstraint.activate([
+ button.widthAnchor.constraint(equalToConstant: 56),
+ button.heightAnchor.constraint(equalToConstant: 56)
+ ])
+ }
+}
+```
+
+
+```objectivec
+@interface CallViewController ()
+@property (nonatomic, strong) UIView *callContainer;
+@property (nonatomic, strong) UIStackView *controlPanel;
+@property (nonatomic, strong) UIButton *btnToggleAudio;
+@property (nonatomic, strong) UIButton *btnToggleVideo;
+@property (nonatomic, strong) UIButton *btnSwitchCamera;
+@property (nonatomic, strong) UIButton *btnEndCall;
+@end
+
+@implementation CallViewController
+
+- (void)viewDidLoad {
+ [super viewDidLoad];
+ [self setupUI];
+ [self setupControlListeners];
+}
+
+- (void)setupUI {
+ self.view.backgroundColor = [UIColor blackColor];
+
+ // Setup call container
+ self.callContainer = [[UIView alloc] init];
+ self.callContainer.translatesAutoresizingMaskIntoConstraints = NO;
+ [self.view addSubview:self.callContainer];
+
+ // Setup control panel
+ self.controlPanel = [[UIStackView alloc] init];
+ self.controlPanel.axis = UILayoutConstraintAxisHorizontal;
+ self.controlPanel.distribution = UIStackViewDistributionEqualSpacing;
+ self.controlPanel.alignment = UIStackViewAlignmentCenter;
+ self.controlPanel.spacing = 20;
+ self.controlPanel.translatesAutoresizingMaskIntoConstraints = NO;
+ self.controlPanel.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0.8];
+ [self.view addSubview:self.controlPanel];
+
+ // Configure and add buttons
+ self.btnToggleAudio = [self createButtonWithImageName:@"mic.fill" backgroundColor:[UIColor darkGrayColor]];
+ self.btnToggleVideo = [self createButtonWithImageName:@"video.fill" backgroundColor:[UIColor darkGrayColor]];
+ self.btnSwitchCamera = [self createButtonWithImageName:@"camera.rotate.fill" backgroundColor:[UIColor darkGrayColor]];
+ self.btnEndCall = [self createButtonWithImageName:@"phone.down.fill" backgroundColor:[UIColor systemRedColor]];
+
+ [self.controlPanel addArrangedSubview:self.btnToggleAudio];
+ [self.controlPanel addArrangedSubview:self.btnToggleVideo];
+ [self.controlPanel addArrangedSubview:self.btnSwitchCamera];
+ [self.controlPanel addArrangedSubview:self.btnEndCall];
+
+ // Layout constraints
+ [NSLayoutConstraint activateConstraints:@[
+ [self.callContainer.topAnchor constraintEqualToAnchor:self.view.topAnchor],
+ [self.callContainer.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor],
+ [self.callContainer.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor],
+ [self.callContainer.bottomAnchor constraintEqualToAnchor:self.view.bottomAnchor],
+
+ [self.controlPanel.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor],
+ [self.controlPanel.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor],
+ [self.controlPanel.bottomAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.bottomAnchor],
+ [self.controlPanel.heightAnchor constraintEqualToConstant:80]
+ ]];
+}
+
+- (UIButton *)createButtonWithImageName:(NSString *)imageName backgroundColor:(UIColor *)backgroundColor {
+ UIButton *button = [UIButton buttonWithType:UIButtonTypeSystem];
+ [button setImage:[UIImage systemImageNamed:imageName] forState:UIControlStateNormal];
+ button.tintColor = [UIColor whiteColor];
+ button.backgroundColor = backgroundColor;
+ button.layer.cornerRadius = 28;
+ button.translatesAutoresizingMaskIntoConstraints = NO;
+ [NSLayoutConstraint activateConstraints:@[
+ [button.widthAnchor constraintEqualToConstant:56],
+ [button.heightAnchor constraintEqualToConstant:56]
+ ]];
+ return button;
+}
+
+@end
+```
+
+
+
+---
+
+## Step 3: Implement Control Actions
+
+Set up button actions and call the appropriate SDK methods:
+
+
+
+```swift
+private var isAudioMuted = false
+private var isVideoPaused = false
+
+private func setupControlListeners() {
+ btnToggleAudio.addTarget(self, action: #selector(toggleAudio), for: .touchUpInside)
+ btnToggleVideo.addTarget(self, action: #selector(toggleVideo), for: .touchUpInside)
+ btnSwitchCamera.addTarget(self, action: #selector(switchCamera), for: .touchUpInside)
+ btnEndCall.addTarget(self, action: #selector(endCall), for: .touchUpInside)
+}
+
+@objc private func toggleAudio() {
+ if isAudioMuted {
+ CallSession.shared.unMuteAudio()
+ } else {
+ CallSession.shared.muteAudio()
+ }
+}
+
+@objc private func toggleVideo() {
+ if isVideoPaused {
+ CallSession.shared.resumeVideo()
+ } else {
+ CallSession.shared.pauseVideo()
+ }
+}
+
+@objc private func switchCamera() {
+ CallSession.shared.switchCamera()
+}
+
+@objc private func endCall() {
+ CallSession.shared.leaveSession()
+ navigationController?.popViewController(animated: true)
+}
+```
+
+
+```objectivec
+@interface CallViewController ()
+@property (nonatomic, assign) BOOL isAudioMuted;
+@property (nonatomic, assign) BOOL isVideoPaused;
+@end
+
+- (void)setupControlListeners {
+ [self.btnToggleAudio addTarget:self action:@selector(toggleAudio) forControlEvents:UIControlEventTouchUpInside];
+ [self.btnToggleVideo addTarget:self action:@selector(toggleVideo) forControlEvents:UIControlEventTouchUpInside];
+ [self.btnSwitchCamera addTarget:self action:@selector(switchCamera) forControlEvents:UIControlEventTouchUpInside];
+ [self.btnEndCall addTarget:self action:@selector(endCall) forControlEvents:UIControlEventTouchUpInside];
+}
+
+- (void)toggleAudio {
+ if (self.isAudioMuted) {
+ [[CallSession shared] unMuteAudio];
+ } else {
+ [[CallSession shared] muteAudio];
+ }
+}
+
+- (void)toggleVideo {
+ if (self.isVideoPaused) {
+ [[CallSession shared] resumeVideo];
+ } else {
+ [[CallSession shared] pauseVideo];
+ }
+}
+
+- (void)switchCamera {
+ [[CallSession shared] switchCamera];
+}
+
+- (void)endCall {
+ [[CallSession shared] leaveSession];
+ [self.navigationController popViewControllerAnimated:YES];
+}
+```
+
+
+
+---
+
+## Step 4: Handle State Updates
+
+Use `MediaEventsListener` to keep your UI synchronized with the actual call state:
+
+
+
+```swift
+extension CallViewController: MediaEventsListener {
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+ // ... other setup
+ CallSession.shared.addMediaEventsListener(self)
+ }
+
+ deinit {
+ CallSession.shared.removeMediaEventsListener(self)
+ }
+
+ func onAudioMuted() {
+ DispatchQueue.main.async {
+ self.isAudioMuted = true
+ self.btnToggleAudio.setImage(UIImage(systemName: "mic.slash.fill"), for: .normal)
+ }
+ }
+
+ func onAudioUnMuted() {
+ DispatchQueue.main.async {
+ self.isAudioMuted = false
+ self.btnToggleAudio.setImage(UIImage(systemName: "mic.fill"), for: .normal)
+ }
+ }
+
+ func onVideoPaused() {
+ DispatchQueue.main.async {
+ self.isVideoPaused = true
+ self.btnToggleVideo.setImage(UIImage(systemName: "video.slash.fill"), for: .normal)
+ }
+ }
+
+ func onVideoResumed() {
+ DispatchQueue.main.async {
+ self.isVideoPaused = false
+ self.btnToggleVideo.setImage(UIImage(systemName: "video.fill"), for: .normal)
+ }
+ }
+
+ // Other MediaEventsListener callbacks
+ func onRecordingStarted() {}
+ func onRecordingStopped() {}
+ func onScreenShareStarted() {}
+ func onScreenShareStopped() {}
+ func onAudioModeChanged(audioModeType: AudioModeType) {}
+ func onCameraFacingChanged(cameraFacing: CameraFacing) {}
+}
+```
+
+
+```objectivec
+@interface CallViewController ()
+@end
+
+- (void)viewDidLoad {
+ [super viewDidLoad];
+ // ... other setup
+ [[CallSession shared] addMediaEventsListener:self];
+}
+
+- (void)dealloc {
+ [[CallSession shared] removeMediaEventsListener:self];
+}
+
+- (void)onAudioMuted {
+ dispatch_async(dispatch_get_main_queue(), ^{
+ self.isAudioMuted = YES;
+ [self.btnToggleAudio setImage:[UIImage systemImageNamed:@"mic.slash.fill"] forState:UIControlStateNormal];
+ });
+}
+
+- (void)onAudioUnMuted {
+ dispatch_async(dispatch_get_main_queue(), ^{
+ self.isAudioMuted = NO;
+ [self.btnToggleAudio setImage:[UIImage systemImageNamed:@"mic.fill"] forState:UIControlStateNormal];
+ });
+}
+
+- (void)onVideoPaused {
+ dispatch_async(dispatch_get_main_queue(), ^{
+ self.isVideoPaused = YES;
+ [self.btnToggleVideo setImage:[UIImage systemImageNamed:@"video.slash.fill"] forState:UIControlStateNormal];
+ });
+}
+
+- (void)onVideoResumed {
+ dispatch_async(dispatch_get_main_queue(), ^{
+ self.isVideoPaused = NO;
+ [self.btnToggleVideo setImage:[UIImage systemImageNamed:@"video.fill"] forState:UIControlStateNormal];
+ });
+}
+```
+
+
+
+Use `SessionStatusListener` to handle session end events:
+
+
+
+```swift
+extension CallViewController: SessionStatusListener {
+
+ func onSessionLeft() {
+ DispatchQueue.main.async {
+ self.navigationController?.popViewController(animated: true)
+ }
+ }
+
+ func onConnectionClosed() {
+ DispatchQueue.main.async {
+ self.navigationController?.popViewController(animated: true)
+ }
+ }
+
+ func onSessionJoined() {}
+ func onSessionTimedOut() {}
+ func onConnectionLost() {}
+ func onConnectionRestored() {}
+}
+```
+
+
+```objectivec
+- (void)onSessionLeft {
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [self.navigationController popViewControllerAnimated:YES];
+ });
+}
+
+- (void)onConnectionClosed {
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [self.navigationController popViewControllerAnimated:YES];
+ });
+}
+```
+
+
diff --git a/calls/ios/custom-participant-list.mdx b/calls/ios/custom-participant-list.mdx
new file mode 100644
index 00000000..bad35682
--- /dev/null
+++ b/calls/ios/custom-participant-list.mdx
@@ -0,0 +1,647 @@
+---
+title: "Custom Participant List"
+sidebarTitle: "Custom Participant List"
+---
+
+Build a custom participant list UI that displays real-time participant information with full control over layout and interactions. This guide demonstrates how to hide the default participant list and create your own using participant events and actions.
+
+## Overview
+
+The SDK provides participant data through events, allowing you to build custom UIs for:
+- Participant roster with search and filtering
+- Custom participant cards with role badges or metadata
+- Moderation dashboards with quick access to controls
+- Attendance tracking and engagement monitoring
+
+## Prerequisites
+
+- CometChat Calls SDK installed and initialized
+- Active call session (see [Join Session](/calls/ios/join-session))
+- Basic understanding of UITableView or UICollectionView
+
+---
+
+## Step 1: Hide Default Participant List
+
+Configure session settings to hide the default participant list button:
+
+
+
+```swift
+let sessionSettings = CometChatCalls.sessionSettingsBuilder
+ .hideParticipantListButton(true)
+ .build()
+```
+
+
+```objectivec
+SessionSettings *sessionSettings = [[[CometChatCalls sessionSettingsBuilder]
+ hideParticipantListButton:YES]
+ build];
+```
+
+
+
+---
+
+## Step 2: Create Participant List Layout
+
+Create a custom view controller for displaying participants:
+
+
+
+```swift
+class ParticipantListViewController: UIViewController {
+
+ private let tableView = UITableView()
+ private let searchBar = UISearchBar()
+ private var participants: [Participant] = []
+ private var filteredParticipants: [Participant] = []
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+ setupUI()
+ setupParticipantListener()
+ }
+
+ private func setupUI() {
+ title = "Participants"
+ view.backgroundColor = .systemBackground
+
+ // Setup search bar
+ searchBar.placeholder = "Search participants..."
+ searchBar.delegate = self
+ searchBar.translatesAutoresizingMaskIntoConstraints = false
+ view.addSubview(searchBar)
+
+ // Setup table view
+ tableView.delegate = self
+ tableView.dataSource = self
+ tableView.register(ParticipantCell.self, forCellReuseIdentifier: "ParticipantCell")
+ tableView.translatesAutoresizingMaskIntoConstraints = false
+ view.addSubview(tableView)
+
+ // Layout constraints
+ NSLayoutConstraint.activate([
+ searchBar.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
+ searchBar.leadingAnchor.constraint(equalTo: view.leadingAnchor),
+ searchBar.trailingAnchor.constraint(equalTo: view.trailingAnchor),
+
+ tableView.topAnchor.constraint(equalTo: searchBar.bottomAnchor),
+ tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
+ tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
+ tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
+ ])
+
+ // Add close button
+ navigationItem.rightBarButtonItem = UIBarButtonItem(
+ barButtonSystemItem: .close,
+ target: self,
+ action: #selector(dismissView)
+ )
+ }
+
+ @objc private func dismissView() {
+ dismiss(animated: true)
+ }
+}
+```
+
+
+```objectivec
+@interface ParticipantListViewController ()
+@property (nonatomic, strong) UITableView *tableView;
+@property (nonatomic, strong) UISearchBar *searchBar;
+@property (nonatomic, strong) NSArray *participants;
+@property (nonatomic, strong) NSArray *filteredParticipants;
+@end
+
+@implementation ParticipantListViewController
+
+- (void)viewDidLoad {
+ [super viewDidLoad];
+ [self setupUI];
+ [self setupParticipantListener];
+}
+
+- (void)setupUI {
+ self.title = @"Participants";
+ self.view.backgroundColor = [UIColor systemBackgroundColor];
+
+ // Setup search bar
+ self.searchBar = [[UISearchBar alloc] init];
+ self.searchBar.placeholder = @"Search participants...";
+ self.searchBar.delegate = self;
+ self.searchBar.translatesAutoresizingMaskIntoConstraints = NO;
+ [self.view addSubview:self.searchBar];
+
+ // Setup table view
+ self.tableView = [[UITableView alloc] init];
+ self.tableView.delegate = self;
+ self.tableView.dataSource = self;
+ [self.tableView registerClass:[ParticipantCell class] forCellReuseIdentifier:@"ParticipantCell"];
+ self.tableView.translatesAutoresizingMaskIntoConstraints = NO;
+ [self.view addSubview:self.tableView];
+
+ // Layout constraints
+ [NSLayoutConstraint activateConstraints:@[
+ [self.searchBar.topAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.topAnchor],
+ [self.searchBar.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor],
+ [self.searchBar.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor],
+
+ [self.tableView.topAnchor constraintEqualToAnchor:self.searchBar.bottomAnchor],
+ [self.tableView.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor],
+ [self.tableView.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor],
+ [self.tableView.bottomAnchor constraintEqualToAnchor:self.view.bottomAnchor]
+ ]];
+
+ // Add close button
+ self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc]
+ initWithBarButtonSystemItem:UIBarButtonSystemItemClose
+ target:self
+ action:@selector(dismissView)];
+}
+
+- (void)dismissView {
+ [self dismissViewControllerAnimated:YES completion:nil];
+}
+
+@end
+```
+
+
+
+---
+
+## Step 3: Create Participant Cell
+
+Build a custom table view cell to display participant information:
+
+
+
+```swift
+class ParticipantCell: UITableViewCell {
+
+ private let avatarImageView = UIImageView()
+ private let nameLabel = UILabel()
+ private let statusLabel = UILabel()
+ private let muteButton = UIButton(type: .system)
+ private let pinButton = UIButton(type: .system)
+
+ var participant: Participant?
+ var onMuteAction: ((Participant) -> Void)?
+ var onPinAction: ((Participant) -> Void)?
+
+ override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
+ super.init(style: style, reuseIdentifier: reuseIdentifier)
+ setupUI()
+ }
+
+ required init?(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+ private func setupUI() {
+ // Avatar
+ avatarImageView.layer.cornerRadius = 20
+ avatarImageView.clipsToBounds = true
+ avatarImageView.backgroundColor = .systemGray4
+ avatarImageView.translatesAutoresizingMaskIntoConstraints = false
+ contentView.addSubview(avatarImageView)
+
+ // Name label
+ nameLabel.font = .systemFont(ofSize: 16, weight: .semibold)
+ nameLabel.translatesAutoresizingMaskIntoConstraints = false
+ contentView.addSubview(nameLabel)
+
+ // Status label
+ statusLabel.font = .systemFont(ofSize: 12)
+ statusLabel.textColor = .secondaryLabel
+ statusLabel.translatesAutoresizingMaskIntoConstraints = false
+ contentView.addSubview(statusLabel)
+
+ // Action buttons
+ muteButton.setImage(UIImage(systemName: "mic.slash"), for: .normal)
+ muteButton.addTarget(self, action: #selector(muteButtonTapped), for: .touchUpInside)
+ muteButton.translatesAutoresizingMaskIntoConstraints = false
+ contentView.addSubview(muteButton)
+
+ pinButton.setImage(UIImage(systemName: "pin"), for: .normal)
+ pinButton.addTarget(self, action: #selector(pinButtonTapped), for: .touchUpInside)
+ pinButton.translatesAutoresizingMaskIntoConstraints = false
+ contentView.addSubview(pinButton)
+
+ // Layout
+ NSLayoutConstraint.activate([
+ avatarImageView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 16),
+ avatarImageView.centerYAnchor.constraint(equalTo: contentView.centerYAnchor),
+ avatarImageView.widthAnchor.constraint(equalToConstant: 40),
+ avatarImageView.heightAnchor.constraint(equalToConstant: 40),
+
+ nameLabel.leadingAnchor.constraint(equalTo: avatarImageView.trailingAnchor, constant: 12),
+ nameLabel.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 12),
+
+ statusLabel.leadingAnchor.constraint(equalTo: nameLabel.leadingAnchor),
+ statusLabel.topAnchor.constraint(equalTo: nameLabel.bottomAnchor, constant: 4),
+ statusLabel.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -12),
+
+ pinButton.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -16),
+ pinButton.centerYAnchor.constraint(equalTo: contentView.centerYAnchor),
+ pinButton.widthAnchor.constraint(equalToConstant: 32),
+
+ muteButton.trailingAnchor.constraint(equalTo: pinButton.leadingAnchor, constant: -8),
+ muteButton.centerYAnchor.constraint(equalTo: contentView.centerYAnchor),
+ muteButton.widthAnchor.constraint(equalToConstant: 32)
+ ])
+ }
+
+ func configure(with participant: Participant) {
+ self.participant = participant
+ nameLabel.text = participant.name
+
+ // Build status text
+ var statusParts: [String] = []
+ if participant.isAudioMuted { statusParts.append("🔇 Muted") }
+ if participant.isVideoPaused { statusParts.append("📹 Video Off") }
+ if participant.isPresenting { statusParts.append("🖥️ Presenting") }
+ if participant.raisedHandTimestamp > 0 { statusParts.append("✋ Hand Raised") }
+ if participant.isPinned { statusParts.append("📌 Pinned") }
+
+ statusLabel.text = statusParts.isEmpty ? "Active" : statusParts.joined(separator: " • ")
+
+ // Update button states
+ muteButton.alpha = participant.isAudioMuted ? 0.5 : 1.0
+ pinButton.tintColor = participant.isPinned ? .systemBlue : .systemGray
+ }
+
+ @objc private func muteButtonTapped() {
+ guard let participant = participant else { return }
+ onMuteAction?(participant)
+ }
+
+ @objc private func pinButtonTapped() {
+ guard let participant = participant else { return }
+ onPinAction?(participant)
+ }
+}
+```
+
+
+```objectivec
+@interface ParticipantCell : UITableViewCell
+@property (nonatomic, strong) Participant *participant;
+@property (nonatomic, copy) void (^onMuteAction)(Participant *);
+@property (nonatomic, copy) void (^onPinAction)(Participant *);
+- (void)configureWithParticipant:(Participant *)participant;
+@end
+
+@implementation ParticipantCell {
+ UIImageView *_avatarImageView;
+ UILabel *_nameLabel;
+ UILabel *_statusLabel;
+ UIButton *_muteButton;
+ UIButton *_pinButton;
+}
+
+- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
+ self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
+ if (self) {
+ [self setupUI];
+ }
+ return self;
+}
+
+- (void)setupUI {
+ // Avatar
+ _avatarImageView = [[UIImageView alloc] init];
+ _avatarImageView.layer.cornerRadius = 20;
+ _avatarImageView.clipsToBounds = YES;
+ _avatarImageView.backgroundColor = [UIColor systemGray4Color];
+ _avatarImageView.translatesAutoresizingMaskIntoConstraints = NO;
+ [self.contentView addSubview:_avatarImageView];
+
+ // Name label
+ _nameLabel = [[UILabel alloc] init];
+ _nameLabel.font = [UIFont systemFontOfSize:16 weight:UIFontWeightSemibold];
+ _nameLabel.translatesAutoresizingMaskIntoConstraints = NO;
+ [self.contentView addSubview:_nameLabel];
+
+ // Status label
+ _statusLabel = [[UILabel alloc] init];
+ _statusLabel.font = [UIFont systemFontOfSize:12];
+ _statusLabel.textColor = [UIColor secondaryLabelColor];
+ _statusLabel.translatesAutoresizingMaskIntoConstraints = NO;
+ [self.contentView addSubview:_statusLabel];
+
+ // Action buttons
+ _muteButton = [UIButton buttonWithType:UIButtonTypeSystem];
+ [_muteButton setImage:[UIImage systemImageNamed:@"mic.slash"] forState:UIControlStateNormal];
+ [_muteButton addTarget:self action:@selector(muteButtonTapped) forControlEvents:UIControlEventTouchUpInside];
+ _muteButton.translatesAutoresizingMaskIntoConstraints = NO;
+ [self.contentView addSubview:_muteButton];
+
+ _pinButton = [UIButton buttonWithType:UIButtonTypeSystem];
+ [_pinButton setImage:[UIImage systemImageNamed:@"pin"] forState:UIControlStateNormal];
+ [_pinButton addTarget:self action:@selector(pinButtonTapped) forControlEvents:UIControlEventTouchUpInside];
+ _pinButton.translatesAutoresizingMaskIntoConstraints = NO;
+ [self.contentView addSubview:_pinButton];
+
+ // Layout constraints
+ [NSLayoutConstraint activateConstraints:@[
+ [_avatarImageView.leadingAnchor constraintEqualToAnchor:self.contentView.leadingAnchor constant:16],
+ [_avatarImageView.centerYAnchor constraintEqualToAnchor:self.contentView.centerYAnchor],
+ [_avatarImageView.widthAnchor constraintEqualToConstant:40],
+ [_avatarImageView.heightAnchor constraintEqualToConstant:40],
+
+ [_nameLabel.leadingAnchor constraintEqualToAnchor:_avatarImageView.trailingAnchor constant:12],
+ [_nameLabel.topAnchor constraintEqualToAnchor:self.contentView.topAnchor constant:12],
+
+ [_statusLabel.leadingAnchor constraintEqualToAnchor:_nameLabel.leadingAnchor],
+ [_statusLabel.topAnchor constraintEqualToAnchor:_nameLabel.bottomAnchor constant:4],
+ [_statusLabel.bottomAnchor constraintEqualToAnchor:self.contentView.bottomAnchor constant:-12],
+
+ [_pinButton.trailingAnchor constraintEqualToAnchor:self.contentView.trailingAnchor constant:-16],
+ [_pinButton.centerYAnchor constraintEqualToAnchor:self.contentView.centerYAnchor],
+ [_pinButton.widthAnchor constraintEqualToConstant:32],
+
+ [_muteButton.trailingAnchor constraintEqualToAnchor:_pinButton.leadingAnchor constant:-8],
+ [_muteButton.centerYAnchor constraintEqualToAnchor:self.contentView.centerYAnchor],
+ [_muteButton.widthAnchor constraintEqualToConstant:32]
+ ]];
+}
+
+- (void)configureWithParticipant:(Participant *)participant {
+ self.participant = participant;
+ _nameLabel.text = participant.name;
+
+ // Build status text
+ NSMutableArray *statusParts = [NSMutableArray array];
+ if (participant.isAudioMuted) [statusParts addObject:@"🔇 Muted"];
+ if (participant.isVideoPaused) [statusParts addObject:@"📹 Video Off"];
+ if (participant.isPresenting) [statusParts addObject:@"🖥️ Presenting"];
+ if (participant.raisedHandTimestamp > 0) [statusParts addObject:@"✋ Hand Raised"];
+ if (participant.isPinned) [statusParts addObject:@"📌 Pinned"];
+
+ _statusLabel.text = statusParts.count == 0 ? @"Active" : [statusParts componentsJoinedByString:@" • "];
+
+ // Update button states
+ _muteButton.alpha = participant.isAudioMuted ? 0.5 : 1.0;
+ _pinButton.tintColor = participant.isPinned ? [UIColor systemBlueColor] : [UIColor systemGrayColor];
+}
+
+- (void)muteButtonTapped {
+ if (self.onMuteAction && self.participant) {
+ self.onMuteAction(self.participant);
+ }
+}
+
+- (void)pinButtonTapped {
+ if (self.onPinAction && self.participant) {
+ self.onPinAction(self.participant);
+ }
+}
+
+@end
+```
+
+
+
+---
+
+## Step 4: Implement Participant Events
+
+Listen for participant updates and handle actions:
+
+
+
+```swift
+extension ParticipantListViewController: ParticipantEventListener {
+
+ private func setupParticipantListener() {
+ CallSession.shared.addParticipantEventListener(self)
+ }
+
+ deinit {
+ CallSession.shared.removeParticipantEventListener(self)
+ }
+
+ func onParticipantListChanged(participants: [Participant]) {
+ DispatchQueue.main.async {
+ self.participants = participants
+ self.filteredParticipants = participants
+ self.title = "Participants (\(participants.count))"
+ self.tableView.reloadData()
+ }
+ }
+
+ func onParticipantJoined(participant: Participant) {
+ print("\(participant.name) joined")
+ }
+
+ func onParticipantLeft(participant: Participant) {
+ print("\(participant.name) left")
+ }
+
+ func onParticipantAudioMuted(participant: Participant) {
+ // Table will update via onParticipantListChanged
+ }
+
+ func onParticipantAudioUnmuted(participant: Participant) {}
+ func onParticipantVideoPaused(participant: Participant) {}
+ func onParticipantVideoResumed(participant: Participant) {}
+ func onParticipantHandRaised(participant: Participant) {}
+ func onParticipantHandLowered(participant: Participant) {}
+}
+```
+
+
+```objectivec
+@interface ParticipantListViewController ()
+@end
+
+- (void)setupParticipantListener {
+ [[CallSession shared] addParticipantEventListener:self];
+}
+
+- (void)dealloc {
+ [[CallSession shared] removeParticipantEventListener:self];
+}
+
+- (void)onParticipantListChangedWithParticipants:(NSArray *)participants {
+ dispatch_async(dispatch_get_main_queue(), ^{
+ self.participants = participants;
+ self.filteredParticipants = participants;
+ self.title = [NSString stringWithFormat:@"Participants (%lu)", (unsigned long)participants.count];
+ [self.tableView reloadData];
+ });
+}
+
+- (void)onParticipantJoinedWithParticipant:(Participant *)participant {
+ NSLog(@"%@ joined", participant.name);
+}
+
+- (void)onParticipantLeftWithParticipant:(Participant *)participant {
+ NSLog(@"%@ left", participant.name);
+}
+```
+
+
+
+---
+
+## Step 5: Implement Table View Data Source
+
+
+
+```swift
+extension ParticipantListViewController: UITableViewDelegate, UITableViewDataSource {
+
+ func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
+ return filteredParticipants.count
+ }
+
+ func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
+ let cell = tableView.dequeueReusableCell(withIdentifier: "ParticipantCell", for: indexPath) as! ParticipantCell
+ let participant = filteredParticipants[indexPath.row]
+
+ cell.configure(with: participant)
+
+ cell.onMuteAction = { [weak self] participant in
+ CallSession.shared.muteParticipant(participant.uid)
+ }
+
+ cell.onPinAction = { [weak self] participant in
+ if participant.isPinned {
+ CallSession.shared.unPinParticipant()
+ } else {
+ CallSession.shared.pinParticipant(participant.uid)
+ }
+ }
+
+ return cell
+ }
+}
+
+extension ParticipantListViewController: UISearchBarDelegate {
+
+ func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
+ if searchText.isEmpty {
+ filteredParticipants = participants
+ } else {
+ filteredParticipants = participants.filter {
+ $0.name.localizedCaseInsensitiveContains(searchText)
+ }
+ }
+ tableView.reloadData()
+ }
+}
+```
+
+
+```objectivec
+- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
+ return self.filteredParticipants.count;
+}
+
+- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
+ ParticipantCell *cell = [tableView dequeueReusableCellWithIdentifier:@"ParticipantCell" forIndexPath:indexPath];
+ Participant *participant = self.filteredParticipants[indexPath.row];
+
+ [cell configureWithParticipant:participant];
+
+ __weak typeof(self) weakSelf = self;
+ cell.onMuteAction = ^(Participant *p) {
+ [[CallSession shared] muteParticipant:p.uid];
+ };
+
+ cell.onPinAction = ^(Participant *p) {
+ if (p.isPinned) {
+ [[CallSession shared] unPinParticipant];
+ } else {
+ [[CallSession shared] pinParticipant:p.uid];
+ }
+ };
+
+ return cell;
+}
+
+- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText {
+ if (searchText.length == 0) {
+ self.filteredParticipants = self.participants;
+ } else {
+ NSPredicate *predicate = [NSPredicate predicateWithFormat:@"name CONTAINS[cd] %@", searchText];
+ self.filteredParticipants = [self.participants filteredArrayUsingPredicate:predicate];
+ }
+ [self.tableView reloadData];
+}
+```
+
+
+
+---
+
+## Step 6: Present Participant List
+
+Show the participant list from your call view controller:
+
+
+
+```swift
+class CallViewController: UIViewController {
+
+ private let participantListButton = UIButton(type: .system)
+
+ private func setupParticipantListButton() {
+ participantListButton.setImage(UIImage(systemName: "person.3"), for: .normal)
+ participantListButton.addTarget(self, action: #selector(showParticipantList), for: .touchUpInside)
+ // Add to your view hierarchy
+ }
+
+ @objc private func showParticipantList() {
+ let participantListVC = ParticipantListViewController()
+ let navController = UINavigationController(rootViewController: participantListVC)
+ navController.modalPresentationStyle = .pageSheet
+
+ if let sheet = navController.sheetPresentationController {
+ sheet.detents = [.medium(), .large()]
+ sheet.prefersGrabberVisible = true
+ }
+
+ present(navController, animated: true)
+ }
+}
+```
+
+
+```objectivec
+- (void)setupParticipantListButton {
+ self.participantListButton = [UIButton buttonWithType:UIButtonTypeSystem];
+ [self.participantListButton setImage:[UIImage systemImageNamed:@"person.3"] forState:UIControlStateNormal];
+ [self.participantListButton addTarget:self action:@selector(showParticipantList) forControlEvents:UIControlEventTouchUpInside];
+ // Add to your view hierarchy
+}
+
+- (void)showParticipantList {
+ ParticipantListViewController *participantListVC = [[ParticipantListViewController alloc] init];
+ UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:participantListVC];
+ navController.modalPresentationStyle = UIModalPresentationPageSheet;
+
+ UISheetPresentationController *sheet = navController.sheetPresentationController;
+ if (sheet) {
+ sheet.detents = @[UISheetPresentationControllerDetent.mediumDetent, UISheetPresentationControllerDetent.largeDetent];
+ sheet.prefersGrabberVisible = YES;
+ }
+
+ [self presentViewController:navController animated:YES completion:nil];
+}
+```
+
+
+
+---
+
+## Related Documentation
+
+- [Participant Management](/calls/ios/participant-management) - Participant actions and events
+- [Events](/calls/ios/events) - All available event listeners
+- [Actions](/calls/ios/actions) - Available call actions
diff --git a/calls/ios/events.mdx b/calls/ios/events.mdx
new file mode 100644
index 00000000..eadd2abb
--- /dev/null
+++ b/calls/ios/events.mdx
@@ -0,0 +1,585 @@
+---
+title: "Events"
+sidebarTitle: "Events"
+---
+
+Handle call session events to build responsive UIs. The SDK provides five event listener protocols to monitor session status, participant activities, media changes, button clicks, and layout changes.
+
+## Get CallSession Instance
+
+The `CallSession` is a singleton that manages the active call. All event listener registrations and session control methods are accessed through this instance.
+
+
+
+```swift
+let callSession = CallSession.shared
+```
+
+
+```objectivec
+CallSession *callSession = [CallSession shared];
+```
+
+
+
+
+Remember to remove listeners when your view controller is deallocated to prevent memory leaks. Use `removeSessionStatusListener`, `removeParticipantEventListener`, etc.
+
+
+---
+
+## Session Events
+
+Monitor the call session lifecycle including join/leave events and connection status.
+
+
+
+```swift
+class CallViewController: UIViewController, SessionStatusListener {
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+ CallSession.shared.addSessionStatusListener(self)
+ }
+
+ deinit {
+ CallSession.shared.removeSessionStatusListener(self)
+ }
+
+ func onSessionJoined() {
+ // Successfully connected to the session
+ }
+
+ func onSessionLeft() {
+ // Left the session
+ navigationController?.popViewController(animated: true)
+ }
+
+ func onSessionTimedOut() {
+ // Session ended due to inactivity
+ navigationController?.popViewController(animated: true)
+ }
+
+ func onConnectionLost() {
+ // Network interrupted, attempting reconnection
+ }
+
+ func onConnectionRestored() {
+ // Connection restored after being lost
+ }
+
+ func onConnectionClosed() {
+ // Connection permanently closed
+ navigationController?.popViewController(animated: true)
+ }
+}
+```
+
+
+```objectivec
+@interface CallViewController ()
+@end
+
+@implementation CallViewController
+
+- (void)viewDidLoad {
+ [super viewDidLoad];
+ [[CallSession shared] addSessionStatusListener:self];
+}
+
+- (void)dealloc {
+ [[CallSession shared] removeSessionStatusListener:self];
+}
+
+- (void)onSessionJoined {
+ // Successfully connected to the session
+}
+
+- (void)onSessionLeft {
+ // Left the session
+ [self.navigationController popViewControllerAnimated:YES];
+}
+
+- (void)onSessionTimedOut {
+ // Session ended due to inactivity
+ [self.navigationController popViewControllerAnimated:YES];
+}
+
+- (void)onConnectionLost {
+ // Network interrupted, attempting reconnection
+}
+
+- (void)onConnectionRestored {
+ // Connection restored after being lost
+}
+
+- (void)onConnectionClosed {
+ // Connection permanently closed
+ [self.navigationController popViewControllerAnimated:YES];
+}
+
+@end
+```
+
+
+
+| Event | Description |
+|-------|-------------|
+| `onSessionJoined()` | Successfully connected and joined the session |
+| `onSessionLeft()` | Left the session via `leaveSession()` or was removed |
+| `onSessionTimedOut()` | Session ended due to inactivity timeout |
+| `onConnectionLost()` | Network interrupted, SDK attempting reconnection |
+| `onConnectionRestored()` | Connection restored after being lost |
+| `onConnectionClosed()` | Connection permanently closed, cannot reconnect |
+
+---
+
+## Participant Events
+
+Monitor participant activities including join/leave, audio/video state, hand raise, screen sharing, and recording.
+
+
+
+```swift
+class CallViewController: UIViewController, ParticipantEventListener {
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+ CallSession.shared.addParticipantEventListener(self)
+ }
+
+ deinit {
+ CallSession.shared.removeParticipantEventListener(self)
+ }
+
+ func onParticipantJoined(participant: Participant) {
+ // A participant joined the call
+ }
+
+ func onParticipantLeft(participant: Participant) {
+ // A participant left the call
+ }
+
+ func onParticipantListChanged(participants: [Participant]) {
+ // Participant list updated
+ }
+
+ func onParticipantAudioMuted(participant: Participant) {}
+ func onParticipantAudioUnmuted(participant: Participant) {}
+ func onParticipantVideoPaused(participant: Participant) {}
+ func onParticipantVideoResumed(participant: Participant) {}
+ func onParticipantHandRaised(participant: Participant) {}
+ func onParticipantHandLowered(participant: Participant) {}
+ func onParticipantStartedScreenShare(participant: Participant) {}
+ func onParticipantStoppedScreenShare(participant: Participant) {}
+ func onParticipantStartedRecording(participant: Participant) {}
+ func onParticipantStoppedRecording(participant: Participant) {}
+ func onDominantSpeakerChanged(participant: Participant) {}
+}
+```
+
+
+```objectivec
+@interface CallViewController ()
+@end
+
+@implementation CallViewController
+
+- (void)viewDidLoad {
+ [super viewDidLoad];
+ [[CallSession shared] addParticipantEventListener:self];
+}
+
+- (void)dealloc {
+ [[CallSession shared] removeParticipantEventListener:self];
+}
+
+- (void)onParticipantJoinedWithParticipant:(Participant *)participant {
+ // A participant joined the call
+}
+
+- (void)onParticipantLeftWithParticipant:(Participant *)participant {
+ // A participant left the call
+}
+
+- (void)onParticipantListChangedWithParticipants:(NSArray *)participants {
+ // Participant list updated
+}
+
+// Other callbacks...
+
+@end
+```
+
+
+
+| Event | Parameter | Description |
+|-------|-----------|-------------|
+| `onParticipantJoined` | `Participant` | A participant connected to the call |
+| `onParticipantLeft` | `Participant` | A participant disconnected from the call |
+| `onParticipantListChanged` | `[Participant]` | Participant list updated |
+| `onParticipantAudioMuted` | `Participant` | A participant muted their microphone |
+| `onParticipantAudioUnmuted` | `Participant` | A participant unmuted their microphone |
+| `onParticipantVideoPaused` | `Participant` | A participant turned off their camera |
+| `onParticipantVideoResumed` | `Participant` | A participant turned on their camera |
+| `onParticipantHandRaised` | `Participant` | A participant raised their hand |
+| `onParticipantHandLowered` | `Participant` | A participant lowered their hand |
+| `onParticipantStartedScreenShare` | `Participant` | A participant started screen sharing |
+| `onParticipantStoppedScreenShare` | `Participant` | A participant stopped screen sharing |
+| `onParticipantStartedRecording` | `Participant` | A participant started recording |
+| `onParticipantStoppedRecording` | `Participant` | A participant stopped recording |
+| `onDominantSpeakerChanged` | `Participant` | The active speaker changed |
+
+---
+
+## Media Events
+
+Monitor your local media state changes including audio/video status, recording, and device changes.
+
+
+
+```swift
+class CallViewController: UIViewController, MediaEventsListener {
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+ CallSession.shared.addMediaEventsListener(self)
+ }
+
+ deinit {
+ CallSession.shared.removeMediaEventsListener(self)
+ }
+
+ func onAudioMuted() {
+ // Your microphone was muted
+ }
+
+ func onAudioUnMuted() {
+ // Your microphone was unmuted
+ }
+
+ func onVideoPaused() {
+ // Your camera was turned off
+ }
+
+ func onVideoResumed() {
+ // Your camera was turned on
+ }
+
+ func onRecordingStarted() {
+ // Call recording started
+ }
+
+ func onRecordingStopped() {
+ // Call recording stopped
+ }
+
+ func onScreenShareStarted() {}
+ func onScreenShareStopped() {}
+
+ func onAudioModeChanged(audioModeType: AudioModeType) {
+ // Audio output device changed
+ }
+
+ func onCameraFacingChanged(cameraFacing: CameraFacing) {
+ // Camera switched between front and back
+ }
+}
+```
+
+
+```objectivec
+@interface CallViewController ()
+@end
+
+@implementation CallViewController
+
+- (void)viewDidLoad {
+ [super viewDidLoad];
+ [[CallSession shared] addMediaEventsListener:self];
+}
+
+- (void)dealloc {
+ [[CallSession shared] removeMediaEventsListener:self];
+}
+
+- (void)onAudioMuted {
+ // Your microphone was muted
+}
+
+- (void)onAudioUnMuted {
+ // Your microphone was unmuted
+}
+
+- (void)onVideoPaused {
+ // Your camera was turned off
+}
+
+- (void)onVideoResumed {
+ // Your camera was turned on
+}
+
+- (void)onRecordingStarted {
+ // Call recording started
+}
+
+- (void)onRecordingStopped {
+ // Call recording stopped
+}
+
+- (void)onAudioModeChangedWithAudioModeType:(AudioModeType)audioModeType {
+ // Audio output device changed
+}
+
+- (void)onCameraFacingChangedWithCameraFacing:(CameraFacing)cameraFacing {
+ // Camera switched between front and back
+}
+
+// Other callbacks...
+
+@end
+```
+
+
+
+| Event | Parameter | Description |
+|-------|-----------|-------------|
+| `onAudioMuted` | - | Your microphone was muted |
+| `onAudioUnMuted` | - | Your microphone was unmuted |
+| `onVideoPaused` | - | Your camera was turned off |
+| `onVideoResumed` | - | Your camera was turned on |
+| `onRecordingStarted` | - | Call recording started |
+| `onRecordingStopped` | - | Call recording stopped |
+| `onScreenShareStarted` | - | You started screen sharing |
+| `onScreenShareStopped` | - | You stopped screen sharing |
+| `onAudioModeChanged` | `AudioModeType` | Audio output device changed |
+| `onCameraFacingChanged` | `CameraFacing` | Camera switched between front and back |
+
+
+
+| Value | Description |
+|-------|-------------|
+| `.speaker` | Audio routed through device loudspeaker |
+| `.earpiece` | Audio routed through phone earpiece |
+| `.bluetooth` | Audio routed through connected Bluetooth device |
+| `.headphones` | Audio routed through wired headphones |
+
+
+
+| Value | Description |
+|-------|-------------|
+| `.FRONT` | Front-facing (selfie) camera is active |
+| `.BACK` | Rear-facing (main) camera is active |
+
+
+
+---
+
+## Button Click Events
+
+Intercept UI button clicks from the default call interface to add custom behavior or analytics.
+
+
+
+```swift
+class CallViewController: UIViewController, ButtonClickListener {
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+ CallSession.shared.addButtonClickListener(self)
+ }
+
+ deinit {
+ CallSession.shared.removeButtonClickListener(self)
+ }
+
+ func onLeaveSessionButtonClicked() {
+ // Leave button tapped
+ }
+
+ func onToggleAudioButtonClicked() {
+ // Mute/unmute button tapped
+ }
+
+ func onToggleVideoButtonClicked() {
+ // Video on/off button tapped
+ }
+
+ func onSwitchCameraButtonClicked() {}
+ func onRaiseHandButtonClicked() {}
+ func onShareInviteButtonClicked() {}
+ func onChangeLayoutButtonClicked() {}
+ func onParticipantListButtonClicked() {}
+ func onChatButtonClicked() {}
+ func onRecordingToggleButtonClicked() {}
+}
+```
+
+
+```objectivec
+@interface CallViewController ()
+@end
+
+@implementation CallViewController
+
+- (void)viewDidLoad {
+ [super viewDidLoad];
+ [[CallSession shared] addButtonClickListener:self];
+}
+
+- (void)dealloc {
+ [[CallSession shared] removeButtonClickListener:self];
+}
+
+- (void)onLeaveSessionButtonClicked {
+ // Leave button tapped
+}
+
+- (void)onToggleAudioButtonClicked {
+ // Mute/unmute button tapped
+}
+
+- (void)onToggleVideoButtonClicked {
+ // Video on/off button tapped
+}
+
+// Other callbacks...
+
+@end
+```
+
+
+
+| Event | Description |
+|-------|-------------|
+| `onLeaveSessionButtonClicked` | Leave/end call button was tapped |
+| `onToggleAudioButtonClicked` | Mute/unmute button was tapped |
+| `onToggleVideoButtonClicked` | Video on/off button was tapped |
+| `onSwitchCameraButtonClicked` | Camera switch button was tapped |
+| `onRaiseHandButtonClicked` | Raise hand button was tapped |
+| `onShareInviteButtonClicked` | Share/invite button was tapped |
+| `onChangeLayoutButtonClicked` | Layout change button was tapped |
+| `onParticipantListButtonClicked` | Participant list button was tapped |
+| `onChatButtonClicked` | In-call chat button was tapped |
+| `onRecordingToggleButtonClicked` | Recording toggle button was tapped |
+
+
+Button click events fire before the SDK's default action executes. Use these to add custom logic alongside default behavior.
+
+
+---
+
+## Layout Events
+
+Monitor layout changes including layout type switches and Picture-in-Picture mode transitions.
+
+
+
+```swift
+class CallViewController: UIViewController, LayoutListener {
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+ CallSession.shared.addLayoutListener(self)
+ }
+
+ deinit {
+ CallSession.shared.removeLayoutListener(self)
+ }
+
+ func onCallLayoutChanged(layoutType: LayoutType) {
+ // Layout changed (TILE, SPOTLIGHT)
+ }
+
+ func onParticipantListVisible() {
+ // Participant list panel opened
+ }
+
+ func onParticipantListHidden() {
+ // Participant list panel closed
+ }
+
+ func onPictureInPictureLayoutEnabled() {
+ // Entered PiP mode
+ }
+
+ func onPictureInPictureLayoutDisabled() {
+ // Exited PiP mode
+ }
+}
+```
+
+
+```objectivec
+@interface CallViewController ()
+@end
+
+@implementation CallViewController
+
+- (void)viewDidLoad {
+ [super viewDidLoad];
+ [[CallSession shared] addLayoutListener:self];
+}
+
+- (void)dealloc {
+ [[CallSession shared] removeLayoutListener:self];
+}
+
+- (void)onCallLayoutChangedWithLayoutType:(LayoutType)layoutType {
+ // Layout changed (TILE, SPOTLIGHT)
+}
+
+- (void)onParticipantListVisible {
+ // Participant list panel opened
+}
+
+- (void)onParticipantListHidden {
+ // Participant list panel closed
+}
+
+- (void)onPictureInPictureLayoutEnabled {
+ // Entered PiP mode
+}
+
+- (void)onPictureInPictureLayoutDisabled {
+ // Exited PiP mode
+}
+
+@end
+```
+
+
+
+| Event | Parameter | Description |
+|-------|-----------|-------------|
+| `onCallLayoutChanged` | `LayoutType` | Call layout changed |
+| `onParticipantListVisible` | - | Participant list panel was opened |
+| `onParticipantListHidden` | - | Participant list panel was closed |
+| `onPictureInPictureLayoutEnabled` | - | Call entered Picture-in-Picture mode |
+| `onPictureInPictureLayoutDisabled` | - | Call exited Picture-in-Picture mode |
+
+
+| Value | Description | Best For |
+|-------|-------------|----------|
+| `.tile` | Grid layout with equally-sized tiles | Group discussions, team meetings |
+| `.spotlight` | Large view for active speaker, small tiles for others | Presentations, one-on-one calls |
+
+
+---
+
+## Clear All Listeners
+
+Remove all registered listeners at once. Useful when cleaning up resources.
+
+
+
+```swift
+CallSession.shared.clearAllListeners()
+```
+
+
+```objectivec
+[[CallSession shared] clearAllListeners];
+```
+
+
diff --git a/calls/ios/idle-timeout.mdx b/calls/ios/idle-timeout.mdx
new file mode 100644
index 00000000..8a4fdee4
--- /dev/null
+++ b/calls/ios/idle-timeout.mdx
@@ -0,0 +1,168 @@
+---
+title: "Idle Timeout"
+sidebarTitle: "Idle Timeout"
+---
+
+Configure automatic session termination when a user is alone in a call. Idle timeout helps manage resources by ending sessions that have no active participants.
+
+## How Idle Timeout Works
+
+When a user is the only participant in a call session, the idle timeout countdown begins. If no other participant joins before the timeout expires, the session automatically ends and the `onSessionTimedOut` callback is triggered.
+
+The timer also restarts when other participants leave and only one user remains in the call.
+
+```mermaid
+flowchart LR
+ A[Alone in call] --> B[Timer starts]
+ B --> C{Participant joins?}
+ C -->|Yes| D[Timer stops]
+ C -->|No, timeout| E[Session ends]
+ D -->|Participant leaves| A
+```
+
+This is useful for:
+- Preventing abandoned call sessions from running indefinitely
+- Managing server resources efficiently
+- Providing a better user experience when the other party doesn't join
+
+## Configure Idle Timeout
+
+Set the idle timeout period using `setIdleTimeoutPeriod()` in `SessionSettingsBuilder`. The value is in seconds.
+
+
+
+```swift
+let sessionSettings = CometChatCalls.sessionSettingsBuilder
+ .setIdleTimeoutPeriod(120) // 2 minutes
+ .setType(.video)
+ .build()
+
+CometChatCalls.joinSession(
+ sessionID: sessionId,
+ callSetting: sessionSettings,
+ container: callViewContainer,
+ onSuccess: { message in
+ print("Joined session")
+ },
+ onError: { error in
+ print("Failed: \(error?.errorDescription ?? "")")
+ }
+)
+```
+
+
+```objectivec
+SessionSettings *sessionSettings = [[[[CometChatCalls sessionSettingsBuilder]
+ setIdleTimeoutPeriod:120]
+ setType:CallTypeVideo]
+ build];
+
+[CometChatCalls joinSessionWithSessionID:sessionId
+ callSetting:sessionSettings
+ container:self.callViewContainer
+ onSuccess:^(NSString * message) {
+ NSLog(@"Joined session");
+} onError:^(CometChatCallException * error) {
+ NSLog(@"Failed: %@", error.errorDescription);
+}];
+```
+
+
+
+| Parameter | Type | Default | Description |
+|-----------|------|---------|-------------|
+| `idleTimeoutPeriod` | Int | 300 | Timeout in seconds when alone in the session |
+
+## Handle Session Timeout
+
+Listen for the `onSessionTimedOut` callback using `SessionStatusListener` to handle when the session ends due to idle timeout:
+
+
+
+```swift
+class CallViewController: UIViewController, SessionStatusListener {
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+ CallSession.shared.addSessionStatusListener(self)
+ }
+
+ deinit {
+ CallSession.shared.removeSessionStatusListener(self)
+ }
+
+ func onSessionTimedOut() {
+ print("Session ended due to idle timeout")
+ // Show message to user
+ showToast("Call ended - no other participants joined")
+ // Navigate away from call screen
+ navigationController?.popViewController(animated: true)
+ }
+
+ func onSessionJoined() {}
+ func onSessionLeft() {}
+ func onConnectionLost() {}
+ func onConnectionRestored() {}
+ func onConnectionClosed() {}
+}
+```
+
+
+```objectivec
+@interface CallViewController ()
+@end
+
+@implementation CallViewController
+
+- (void)viewDidLoad {
+ [super viewDidLoad];
+ [[CallSession shared] addSessionStatusListener:self];
+}
+
+- (void)dealloc {
+ [[CallSession shared] removeSessionStatusListener:self];
+}
+
+- (void)onSessionTimedOut {
+ NSLog(@"Session ended due to idle timeout");
+ // Show message to user
+ [self showToast:@"Call ended - no other participants joined"];
+ // Navigate away from call screen
+ [self.navigationController popViewControllerAnimated:YES];
+}
+
+- (void)onSessionJoined {}
+- (void)onSessionLeft {}
+- (void)onConnectionLost {}
+- (void)onConnectionRestored {}
+- (void)onConnectionClosed {}
+
+@end
+```
+
+
+
+## Disable Idle Timeout
+
+To disable idle timeout and allow sessions to run indefinitely, set a value of `0`:
+
+
+
+```swift
+let sessionSettings = CometChatCalls.sessionSettingsBuilder
+ .setIdleTimeoutPeriod(0) // Disable idle timeout
+ .build()
+```
+
+
+```objectivec
+SessionSettings *sessionSettings = [[[CometChatCalls sessionSettingsBuilder]
+ setIdleTimeoutPeriod:0]
+ build];
+```
+
+
+
+
+Disabling idle timeout may result in sessions running indefinitely if participants don't join or leave properly. Use with caution.
+
diff --git a/calls/ios/in-call-chat.mdx b/calls/ios/in-call-chat.mdx
new file mode 100644
index 00000000..d484552f
--- /dev/null
+++ b/calls/ios/in-call-chat.mdx
@@ -0,0 +1,530 @@
+---
+title: "In-Call Chat"
+sidebarTitle: "In-Call Chat"
+---
+
+Add real-time messaging to your call experience using CometChat UI Kit. This allows participants to send text messages, share files, and communicate via chat while on a call.
+
+## Overview
+
+In-call chat creates a group conversation linked to the call session. When participants tap the chat button, they can:
+- Send and receive text messages
+- Share images, files, and media
+- See message history from the current call
+- Get unread message notifications via badge count
+
+```mermaid
+flowchart LR
+ subgraph "Call Session"
+ A[Call UI] --> B[Chat Button]
+ B --> C[Chat VC]
+ end
+
+ subgraph "CometChat"
+ D[Group] --> E[Messages]
+ end
+
+ C <--> D
+ A -->|Session ID = Group GUID| D
+```
+
+## Prerequisites
+
+- CometChat Calls SDK integrated ([Setup](/calls/ios/setup))
+- CometChat Chat SDK integrated ([Chat SDK](/sdk/ios/overview))
+- CometChat UI Kit integrated ([UI Kit](/ui-kit/ios/overview))
+
+
+The Chat SDK and UI Kit are separate from the Calls SDK. You'll need to add both dependencies to your project.
+
+
+---
+
+## Step 1: Add UI Kit Dependency
+
+Add the CometChat UI Kit to your project via Swift Package Manager or CocoaPods:
+
+**Swift Package Manager:**
+```
+https://github.com/cometchat/cometchat-chat-uikit-ios
+```
+
+**CocoaPods:**
+```ruby
+pod 'CometChatUIKitSwift', '~> 4.0'
+```
+
+---
+
+## Step 2: Enable Chat Button
+
+Configure session settings to show the chat button:
+
+
+
+```swift
+let sessionSettings = CometChatCalls.sessionSettingsBuilder
+ .hideChatButton(false) // Show the chat button
+ .build()
+```
+
+
+```objectivec
+SessionSettings *sessionSettings = [[[CometChatCalls sessionSettingsBuilder]
+ hideChatButton:NO] // Show the chat button
+ build];
+```
+
+
+
+---
+
+## Step 3: Create Chat Group
+
+Create or join a CometChat group using the session ID as the group GUID. This links the chat to the specific call session.
+
+
+
+```swift
+private func setupChatGroup(sessionId: String, meetingName: String) {
+ // Try to get existing group first
+ CometChat.getGroup(GUID: sessionId) { group in
+ if !group.hasJoined {
+ self.joinGroup(guid: sessionId, groupType: group.groupType)
+ } else {
+ print("Already joined group: \(group.name ?? "")")
+ }
+ } onError: { error in
+ if error?.errorCode == "ERR_GUID_NOT_FOUND" {
+ // Group doesn't exist, create it
+ self.createGroup(guid: sessionId, name: meetingName)
+ } else {
+ print("Error getting group: \(error?.errorDescription ?? "")")
+ }
+ }
+}
+
+private func createGroup(guid: String, name: String) {
+ let group = Group(guid: guid, name: name, groupType: .public, password: nil)
+
+ CometChat.createGroup(group: group) { createdGroup in
+ print("Group created: \(createdGroup.name ?? "")")
+ } onError: { error in
+ print("Group creation failed: \(error?.errorDescription ?? "")")
+ }
+}
+
+private func joinGroup(guid: String, groupType: CometChat.groupType) {
+ CometChat.joinGroup(GUID: guid, groupType: groupType, password: nil) { joinedGroup in
+ print("Joined group: \(joinedGroup.name ?? "")")
+ } onError: { error in
+ print("Join group failed: \(error?.errorDescription ?? "")")
+ }
+}
+```
+
+
+```objectivec
+- (void)setupChatGroupWithSessionId:(NSString *)sessionId meetingName:(NSString *)meetingName {
+ [CometChat getGroupWithGUID:sessionId onSuccess:^(Group * group) {
+ if (!group.hasJoined) {
+ [self joinGroupWithGuid:sessionId groupType:group.groupType];
+ } else {
+ NSLog(@"Already joined group: %@", group.name);
+ }
+ } onError:^(CometChatException * error) {
+ if ([error.errorCode isEqualToString:@"ERR_GUID_NOT_FOUND"]) {
+ [self createGroupWithGuid:sessionId name:meetingName];
+ } else {
+ NSLog(@"Error getting group: %@", error.errorDescription);
+ }
+ }];
+}
+
+- (void)createGroupWithGuid:(NSString *)guid name:(NSString *)name {
+ Group *group = [[Group alloc] initWithGuid:guid name:name groupType:GroupTypePublic password:nil];
+
+ [CometChat createGroupWithGroup:group onSuccess:^(Group * createdGroup) {
+ NSLog(@"Group created: %@", createdGroup.name);
+ } onError:^(CometChatException * error) {
+ NSLog(@"Group creation failed: %@", error.errorDescription);
+ }];
+}
+
+- (void)joinGroupWithGuid:(NSString *)guid groupType:(GroupType)groupType {
+ [CometChat joinGroupWithGUID:guid groupType:groupType password:nil onSuccess:^(Group * joinedGroup) {
+ NSLog(@"Joined group: %@", joinedGroup.name);
+ } onError:^(CometChatException * error) {
+ NSLog(@"Join group failed: %@", error.errorDescription);
+ }];
+}
+```
+
+
+
+---
+
+## Step 4: Handle Chat Button Click
+
+Listen for the chat button click and open your chat view controller:
+
+
+
+```swift
+private var unreadMessageCount = 0
+
+private func setupChatButtonListener() {
+ CallSession.shared.addButtonClickListener(self)
+}
+
+extension CallViewController: ButtonClickListener {
+
+ func onChatButtonClicked() {
+ // Reset unread count when opening chat
+ unreadMessageCount = 0
+ CallSession.shared.setChatButtonUnreadCount(0)
+
+ // Open chat view controller
+ let chatVC = ChatViewController()
+ chatVC.sessionId = sessionId
+ chatVC.meetingName = meetingName
+
+ let navController = UINavigationController(rootViewController: chatVC)
+ navController.modalPresentationStyle = .pageSheet
+
+ if let sheet = navController.sheetPresentationController {
+ sheet.detents = [.medium(), .large()]
+ sheet.prefersGrabberVisible = true
+ }
+
+ present(navController, animated: true)
+ }
+}
+```
+
+
+```objectivec
+@interface CallViewController ()
+@property (nonatomic, assign) NSInteger unreadMessageCount;
+@end
+
+- (void)setupChatButtonListener {
+ [[CallSession shared] addButtonClickListener:self];
+}
+
+- (void)onChatButtonClicked {
+ // Reset unread count when opening chat
+ self.unreadMessageCount = 0;
+ [[CallSession shared] setChatButtonUnreadCount:0];
+
+ // Open chat view controller
+ ChatViewController *chatVC = [[ChatViewController alloc] init];
+ chatVC.sessionId = self.sessionId;
+ chatVC.meetingName = self.meetingName;
+
+ UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:chatVC];
+ navController.modalPresentationStyle = UIModalPresentationPageSheet;
+
+ UISheetPresentationController *sheet = navController.sheetPresentationController;
+ if (sheet) {
+ sheet.detents = @[UISheetPresentationControllerDetent.mediumDetent, UISheetPresentationControllerDetent.largeDetent];
+ sheet.prefersGrabberVisible = YES;
+ }
+
+ [self presentViewController:navController animated:YES completion:nil];
+}
+```
+
+
+
+---
+
+## Step 5: Track Unread Messages
+
+Listen for incoming messages and update the badge count on the chat button:
+
+
+
+```swift
+private func setupMessageListener() {
+ CometChat.addMessageListener("CallChatListener", self)
+}
+
+extension CallViewController: CometChatMessageDelegate {
+
+ func onTextMessageReceived(textMessage: TextMessage) {
+ // Check if message is for our call's group
+ if let receiver = textMessage.receiver as? Group,
+ receiver.guid == sessionId {
+ unreadMessageCount += 1
+ CallSession.shared.setChatButtonUnreadCount(unreadMessageCount)
+ }
+ }
+
+ func onMediaMessageReceived(mediaMessage: MediaMessage) {
+ if let receiver = mediaMessage.receiver as? Group,
+ receiver.guid == sessionId {
+ unreadMessageCount += 1
+ CallSession.shared.setChatButtonUnreadCount(unreadMessageCount)
+ }
+ }
+}
+
+deinit {
+ CometChat.removeMessageListener("CallChatListener")
+}
+```
+
+
+```objectivec
+- (void)setupMessageListener {
+ [CometChat addMessageListener:@"CallChatListener" delegate:self];
+}
+
+- (void)onTextMessageReceived:(TextMessage *)textMessage {
+ if ([textMessage.receiver isKindOfClass:[Group class]]) {
+ Group *group = (Group *)textMessage.receiver;
+ if ([group.guid isEqualToString:self.sessionId]) {
+ self.unreadMessageCount++;
+ [[CallSession shared] setChatButtonUnreadCount:self.unreadMessageCount];
+ }
+ }
+}
+
+- (void)onMediaMessageReceived:(MediaMessage *)mediaMessage {
+ if ([mediaMessage.receiver isKindOfClass:[Group class]]) {
+ Group *group = (Group *)mediaMessage.receiver;
+ if ([group.guid isEqualToString:self.sessionId]) {
+ self.unreadMessageCount++;
+ [[CallSession shared] setChatButtonUnreadCount:self.unreadMessageCount];
+ }
+ }
+}
+
+- (void)dealloc {
+ [CometChat removeMessageListener:@"CallChatListener"];
+}
+```
+
+
+
+---
+
+## Step 6: Create Chat View Controller
+
+Create a chat view controller using UI Kit components:
+
+
+
+```swift
+import CometChatUIKitSwift
+
+class ChatViewController: UIViewController {
+
+ var sessionId: String = ""
+ var meetingName: String = ""
+
+ private let messageList = CometChatMessageList()
+ private let messageComposer = CometChatMessageComposer()
+ private let activityIndicator = UIActivityIndicatorView(style: .large)
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+ setupUI()
+ loadGroup()
+ }
+
+ private func setupUI() {
+ view.backgroundColor = .systemBackground
+ title = meetingName
+
+ navigationItem.rightBarButtonItem = UIBarButtonItem(
+ barButtonSystemItem: .close,
+ target: self,
+ action: #selector(dismissView)
+ )
+
+ // Setup message list
+ messageList.translatesAutoresizingMaskIntoConstraints = false
+ view.addSubview(messageList)
+
+ // Setup message composer
+ messageComposer.translatesAutoresizingMaskIntoConstraints = false
+ view.addSubview(messageComposer)
+
+ // Setup activity indicator
+ activityIndicator.translatesAutoresizingMaskIntoConstraints = false
+ activityIndicator.hidesWhenStopped = true
+ view.addSubview(activityIndicator)
+
+ NSLayoutConstraint.activate([
+ messageList.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
+ messageList.leadingAnchor.constraint(equalTo: view.leadingAnchor),
+ messageList.trailingAnchor.constraint(equalTo: view.trailingAnchor),
+ messageList.bottomAnchor.constraint(equalTo: messageComposer.topAnchor),
+
+ messageComposer.leadingAnchor.constraint(equalTo: view.leadingAnchor),
+ messageComposer.trailingAnchor.constraint(equalTo: view.trailingAnchor),
+ messageComposer.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor),
+
+ activityIndicator.centerXAnchor.constraint(equalTo: view.centerXAnchor),
+ activityIndicator.centerYAnchor.constraint(equalTo: view.centerYAnchor)
+ ])
+ }
+
+ @objc private func dismissView() {
+ dismiss(animated: true)
+ }
+
+ private func loadGroup() {
+ activityIndicator.startAnimating()
+
+ CometChat.getGroup(GUID: sessionId) { [weak self] group in
+ guard let self = self else { return }
+
+ if !group.hasJoined {
+ self.joinAndSetGroup(guid: self.sessionId, groupType: group.groupType)
+ } else {
+ self.setGroup(group)
+ }
+ } onError: { [weak self] error in
+ guard let self = self else { return }
+
+ if error?.errorCode == "ERR_GUID_NOT_FOUND" {
+ self.createAndSetGroup()
+ } else {
+ self.activityIndicator.stopAnimating()
+ print("Error: \(error?.errorDescription ?? "")")
+ }
+ }
+ }
+
+ private func createAndSetGroup() {
+ let group = Group(guid: sessionId, name: meetingName, groupType: .public, password: nil)
+
+ CometChat.createGroup(group: group) { [weak self] createdGroup in
+ self?.setGroup(createdGroup)
+ } onError: { [weak self] error in
+ self?.activityIndicator.stopAnimating()
+ }
+ }
+
+ private func joinAndSetGroup(guid: String, groupType: CometChat.groupType) {
+ CometChat.joinGroup(GUID: guid, groupType: groupType, password: nil) { [weak self] joinedGroup in
+ self?.setGroup(joinedGroup)
+ } onError: { [weak self] error in
+ self?.activityIndicator.stopAnimating()
+ }
+ }
+
+ private func setGroup(_ group: Group) {
+ activityIndicator.stopAnimating()
+
+ messageList.set(group: group)
+ messageComposer.set(group: group)
+ }
+}
+```
+
+
+```objectivec
+#import
+
+@interface ChatViewController ()
+@property (nonatomic, strong) CometChatMessageList *messageList;
+@property (nonatomic, strong) CometChatMessageComposer *messageComposer;
+@property (nonatomic, strong) UIActivityIndicatorView *activityIndicator;
+@property (nonatomic, copy) NSString *sessionId;
+@property (nonatomic, copy) NSString *meetingName;
+@end
+
+@implementation ChatViewController
+
+- (void)viewDidLoad {
+ [super viewDidLoad];
+ [self setupUI];
+ [self loadGroup];
+}
+
+- (void)setupUI {
+ self.view.backgroundColor = [UIColor systemBackgroundColor];
+ self.title = self.meetingName;
+
+ self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc]
+ initWithBarButtonSystemItem:UIBarButtonSystemItemClose
+ target:self
+ action:@selector(dismissView)];
+
+ // Setup message list
+ self.messageList = [[CometChatMessageList alloc] init];
+ self.messageList.translatesAutoresizingMaskIntoConstraints = NO;
+ [self.view addSubview:self.messageList];
+
+ // Setup message composer
+ self.messageComposer = [[CometChatMessageComposer alloc] init];
+ self.messageComposer.translatesAutoresizingMaskIntoConstraints = NO;
+ [self.view addSubview:self.messageComposer];
+
+ // Setup activity indicator
+ self.activityIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleLarge];
+ self.activityIndicator.translatesAutoresizingMaskIntoConstraints = NO;
+ self.activityIndicator.hidesWhenStopped = YES;
+ [self.view addSubview:self.activityIndicator];
+
+ [NSLayoutConstraint activateConstraints:@[
+ [self.messageList.topAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.topAnchor],
+ [self.messageList.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor],
+ [self.messageList.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor],
+ [self.messageList.bottomAnchor constraintEqualToAnchor:self.messageComposer.topAnchor],
+
+ [self.messageComposer.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor],
+ [self.messageComposer.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor],
+ [self.messageComposer.bottomAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.bottomAnchor],
+
+ [self.activityIndicator.centerXAnchor constraintEqualToAnchor:self.view.centerXAnchor],
+ [self.activityIndicator.centerYAnchor constraintEqualToAnchor:self.view.centerYAnchor]
+ ]];
+}
+
+- (void)dismissView {
+ [self dismissViewControllerAnimated:YES completion:nil];
+}
+
+- (void)loadGroup {
+ [self.activityIndicator startAnimating];
+
+ __weak typeof(self) weakSelf = self;
+ [CometChat getGroupWithGUID:self.sessionId onSuccess:^(Group * group) {
+ if (!group.hasJoined) {
+ [weakSelf joinAndSetGroupWithGuid:weakSelf.sessionId groupType:group.groupType];
+ } else {
+ [weakSelf setGroup:group];
+ }
+ } onError:^(CometChatException * error) {
+ if ([error.errorCode isEqualToString:@"ERR_GUID_NOT_FOUND"]) {
+ [weakSelf createAndSetGroup];
+ } else {
+ [weakSelf.activityIndicator stopAnimating];
+ }
+ }];
+}
+
+- (void)setGroup:(Group *)group {
+ [self.activityIndicator stopAnimating];
+
+ [self.messageList setWithGroup:group];
+ [self.messageComposer setWithGroup:group];
+}
+
+@end
+```
+
+
+
+---
+
+## Related Documentation
+
+- [UI Kit Overview](/ui-kit/ios/overview) - CometChat UI Kit components
+- [Events](/calls/ios/events) - Button click events
+- [Session Settings](/calls/ios/session-settings) - Configure chat button visibility
diff --git a/calls/ios/join-session.mdx b/calls/ios/join-session.mdx
new file mode 100644
index 00000000..c3c01e73
--- /dev/null
+++ b/calls/ios/join-session.mdx
@@ -0,0 +1,278 @@
+---
+title: "Join Session"
+sidebarTitle: "Join Session"
+---
+
+Join a call session using one of two approaches: the quick start method with a session ID, or the advanced flow with manual token generation for more control.
+
+## Overview
+
+The CometChat Calls SDK provides two ways to join a session:
+
+| Approach | Best For | Complexity |
+|----------|----------|------------|
+| **Join with Session ID** | Most use cases - simple and straightforward | Low - One method call |
+| **Join with Token** | Custom token management, pre-generation, caching | Medium - Two-step process |
+
+
+Both approaches require a container view in your layout and properly configured [SessionSettings](/calls/ios/session-settings).
+
+
+## Container Setup
+
+Create a container view where the call interface will be rendered. This can be done in your storyboard or programmatically:
+
+
+
+```swift
+// Programmatically
+let callViewContainer = UIView()
+callViewContainer.translatesAutoresizingMaskIntoConstraints = false
+view.addSubview(callViewContainer)
+
+NSLayoutConstraint.activate([
+ callViewContainer.topAnchor.constraint(equalTo: view.topAnchor),
+ callViewContainer.bottomAnchor.constraint(equalTo: view.bottomAnchor),
+ callViewContainer.leadingAnchor.constraint(equalTo: view.leadingAnchor),
+ callViewContainer.trailingAnchor.constraint(equalTo: view.trailingAnchor)
+])
+```
+
+
+Add a `UIView` to your view controller and create an `@IBOutlet`:
+
+```swift
+@IBOutlet weak var callViewContainer: UIView!
+```
+
+
+
+The call UI will be dynamically added to this container when you join the session.
+
+## Join with Session ID
+
+The simplest way to join a session. Pass a session ID and the SDK automatically generates the token and joins the call.
+
+
+
+```swift
+let sessionId = "SESSION_ID"
+
+let sessionSettings = CometChatCalls.sessionSettingsBuilder
+ .setDisplayName("John Doe")
+ .setType(.video)
+ .build()
+
+CometChatCalls.joinSession(
+ sessionID: sessionId,
+ callSetting: sessionSettings,
+ container: callViewContainer,
+ onSuccess: { message in
+ print("Joined session successfully")
+ },
+ onError: { error in
+ print("Failed: \(error?.errorDescription ?? "")")
+ }
+)
+```
+
+
+```objectivec
+NSString *sessionId = @"SESSION_ID";
+
+SessionSettings *sessionSettings = [[[[CometChatCalls sessionSettingsBuilder]
+ setDisplayName:@"John Doe"]
+ setType:CallTypeVideo]
+ build];
+
+[CometChatCalls joinSessionWithSessionID:sessionId
+ callSetting:sessionSettings
+ container:self.callViewContainer
+ onSuccess:^(NSString * message) {
+ NSLog(@"Joined session successfully");
+} onError:^(CometChatCallException * error) {
+ NSLog(@"Failed: %@", error.errorDescription);
+}];
+```
+
+
+
+| Parameter | Type | Description |
+|-----------|------|-------------|
+| `sessionID` | String | Unique identifier for the call session |
+| `callSetting` | SessionSettings | Configuration for the session |
+| `container` | UIView | Container view for the call UI |
+| `onSuccess` | Closure | Closure called on successful join |
+| `onError` | Closure | Closure called on failure |
+
+
+All participants joining the same call must use the same session ID.
+
+
+## Join with Token
+
+For scenarios requiring more control over token generation, such as pre-generating tokens, implementing custom caching strategies, or managing token lifecycle separately.
+
+**Step 1: Generate Token**
+
+Generate a call token for the session. Each token is unique to a specific session and user combination.
+
+
+
+```swift
+let sessionId = "SESSION_ID"
+
+CometChatCalls.generateToken(sessionID: sessionId, onSuccess: { token in
+ print("Token generated: \(token ?? "")")
+ // Store or use the token
+}, onError: { error in
+ print("Token generation failed: \(error?.errorDescription ?? "")")
+})
+```
+
+
+```objectivec
+NSString *sessionId = @"SESSION_ID";
+
+[CometChatCalls generateTokenWithSessionID:sessionId
+ onSuccess:^(NSString * token) {
+ NSLog(@"Token generated: %@", token);
+ // Store or use the token
+} onError:^(CometChatCallException * error) {
+ NSLog(@"Token generation failed: %@", error.errorDescription);
+}];
+```
+
+
+
+| Parameter | Type | Description |
+|-----------|------|-------------|
+| `sessionID` | String | Unique identifier for the call session |
+| `onSuccess` | Closure | Closure returning the generated token string |
+| `onError` | Closure | Closure called on failure |
+
+**Step 2: Join with Token**
+
+Use the generated token to join the session. This gives you control over when and how the token is used.
+
+
+
+```swift
+let sessionSettings = CometChatCalls.sessionSettingsBuilder
+ .setDisplayName("John Doe")
+ .setType(.video)
+ .build()
+
+// Use the previously generated token
+CometChatCalls.joinSession(
+ callToken: generatedToken,
+ callSetting: sessionSettings,
+ container: callViewContainer,
+ onSuccess: { message in
+ print("Joined session successfully")
+ },
+ onError: { error in
+ print("Failed: \(error?.errorDescription ?? "")")
+ }
+)
+```
+
+
+```objectivec
+SessionSettings *sessionSettings = [[[[CometChatCalls sessionSettingsBuilder]
+ setDisplayName:@"John Doe"]
+ setType:CallTypeVideo]
+ build];
+
+// Use the previously generated token
+[CometChatCalls joinSessionWithCallToken:generatedToken
+ callSetting:sessionSettings
+ container:self.callViewContainer
+ onSuccess:^(NSString * message) {
+ NSLog(@"Joined session successfully");
+} onError:^(CometChatCallException * error) {
+ NSLog(@"Failed: %@", error.errorDescription);
+}];
+```
+
+
+
+| Parameter | Type | Description |
+|-----------|------|-------------|
+| `callToken` | String | Previously generated token string |
+| `callSetting` | SessionSettings | Configuration for the session |
+| `container` | UIView | Container view for the call UI |
+| `onSuccess` | Closure | Closure called on successful join |
+| `onError` | Closure | Closure called on failure |
+
+**Complete Example**
+
+
+
+```swift
+let sessionId = "SESSION_ID"
+
+// Step 1: Generate token
+CometChatCalls.generateToken(sessionID: sessionId, onSuccess: { [weak self] token in
+ guard let self = self, let token = token else { return }
+
+ // Step 2: Join with token
+ let sessionSettings = CometChatCalls.sessionSettingsBuilder
+ .setDisplayName("John Doe")
+ .setType(.video)
+ .build()
+
+ CometChatCalls.joinSession(
+ callToken: token,
+ callSetting: sessionSettings,
+ container: self.callViewContainer,
+ onSuccess: { message in
+ print("Joined session successfully")
+ },
+ onError: { error in
+ print("Failed to join: \(error?.errorDescription ?? "")")
+ }
+ )
+}, onError: { error in
+ print("Token generation failed: \(error?.errorDescription ?? "")")
+})
+```
+
+
+```objectivec
+NSString *sessionId = @"SESSION_ID";
+
+// Step 1: Generate token
+[CometChatCalls generateTokenWithSessionID:sessionId
+ onSuccess:^(NSString * token) {
+ // Step 2: Join with token
+ SessionSettings *sessionSettings = [[[[CometChatCalls sessionSettingsBuilder]
+ setDisplayName:@"John Doe"]
+ setType:CallTypeVideo]
+ build];
+
+ [CometChatCalls joinSessionWithCallToken:token
+ callSetting:sessionSettings
+ container:self.callViewContainer
+ onSuccess:^(NSString * message) {
+ NSLog(@"Joined session successfully");
+ } onError:^(CometChatCallException * error) {
+ NSLog(@"Failed to join: %@", error.errorDescription);
+ }];
+} onError:^(CometChatCallException * error) {
+ NSLog(@"Token generation failed: %@", error.errorDescription);
+}];
+```
+
+
+
+## Error Handling
+
+Common errors when joining a session:
+
+| Error Code | Description |
+|------------|-------------|
+| `INIT ERROR` | SDK not initialized - call `init()` first |
+| `AUTH TOKEN ERROR` | User not logged in or auth token invalid |
+| `SESSION ID ERROR` | Session ID is null or empty |
+| `API ERROR` | Invalid call token or API error |
diff --git a/calls/ios/overview.mdx b/calls/ios/overview.mdx
new file mode 100644
index 00000000..9436784c
--- /dev/null
+++ b/calls/ios/overview.mdx
@@ -0,0 +1,107 @@
+---
+title: "Calls SDK"
+sidebarTitle: "Overview"
+---
+
+The CometChat Calls SDK enables real-time voice and video calling capabilities in your iOS application. Built on top of WebRTC, it provides a complete calling solution with built-in UI components and extensive customization options.
+
+
+**Faster Integration with UI Kits**
+
+If you're using CometChat UI Kits, voice and video calling can be quickly integrated:
+- Incoming & outgoing call screens
+- Call buttons with one-tap calling
+- Call logs with history
+
+👉 [iOS UI Kit Calling Integration](/ui-kit/ios/calling-integration)
+
+Use this Calls SDK directly only if you need custom call UI or advanced control.
+
+
+## Prerequisites
+
+Before integrating the Calls SDK, ensure you have:
+
+1. **CometChat Account**: [Sign up](https://app.cometchat.com/signup) and create an app to get your App ID, Region, and API Key
+2. **CometChat Users**: Users must exist in CometChat to use calling features. For testing, create users via the [Dashboard](https://app.cometchat.com) or [REST API](/rest-api/chat-apis/users/create-user). Authentication is handled by the Calls SDK - see [Authentication](/calls/ios/authentication)
+3. **iOS Requirements**:
+ - Minimum iOS version: 16.0
+ - Xcode 14.0 or later
+ - Swift 5.0 or later
+4. **Permissions**: Camera and microphone permissions for video/audio calls
+
+## Call Flow
+
+```mermaid
+sequenceDiagram
+ participant App
+ participant CometChatCalls
+ participant CallSession
+
+ App->>CometChatCalls: init()
+ App->>CometChatCalls: login()
+ App->>CometChatCalls: generateToken()
+ App->>CometChatCalls: joinSession()
+ CometChatCalls-->>App: CallSession.shared
+ App->>CallSession: Actions (mute, pause, etc.)
+ CallSession-->>App: Event callbacks
+ App->>CallSession: leaveSession()
+```
+
+## Features
+
+
+
+
+ Incoming and outgoing call notifications with accept/reject functionality
+
+
+
+ Tile and Spotlight view modes for different call scenarios
+
+
+
+ Switch between speaker, earpiece, Bluetooth, and headphones
+
+
+
+ Record call sessions for later playback
+
+
+
+ Retrieve call history and details
+
+
+
+ Mute, pin, and manage call participants
+
+
+
+ View screen shares from other participants
+
+
+
+ Continue calls while using other apps
+
+
+
+ Signal to get attention during calls
+
+
+
+ Automatic session termination when alone in a call
+
+
+
+
+## Architecture
+
+The SDK is organized around these core components:
+
+| Component | Description |
+|-----------|-------------|
+| `CometChatCalls` | Main entry point for SDK initialization, authentication, and session management |
+| `CallAppSettings` | Configuration for SDK initialization (App ID, Region) |
+| `SessionSettings` | Configuration for individual call sessions |
+| `CallSession` | Singleton that manages the active call and provides control methods |
+| `Listeners` | Protocol-based event interfaces for session, participant, media, and UI events |
diff --git a/calls/ios/participant-management.mdx b/calls/ios/participant-management.mdx
new file mode 100644
index 00000000..74c5e8bd
--- /dev/null
+++ b/calls/ios/participant-management.mdx
@@ -0,0 +1,229 @@
+---
+title: "Participant Management"
+sidebarTitle: "Participant Management"
+---
+
+Manage participants during a call with actions like muting, pausing video, and pinning. These features help maintain order in group calls and highlight important speakers.
+
+
+By default, all participants who join a call have moderator access and can perform these actions. Implementing role-based moderation (e.g., restricting actions to hosts only) is the responsibility of the app developer based on their use case.
+
+
+## Mute a Participant
+
+Mute a specific participant's audio. This affects the participant for all users in the call.
+
+
+
+```swift
+CallSession.shared.muteParticipant(participantId: participant.pid)
+```
+
+
+```objectivec
+[[CallSession shared] muteParticipantWithParticipantId:participant.pid];
+```
+
+
+
+## Pause Participant Video
+
+Pause a specific participant's video. This affects the participant for all users in the call.
+
+
+
+```swift
+CallSession.shared.pauseParticipantVideo(participantId: participant.pid)
+```
+
+
+```objectivec
+[[CallSession shared] pauseParticipantVideoWithParticipantId:participant.pid];
+```
+
+
+
+## Pin a Participant
+
+Pin a participant to keep them prominently displayed regardless of who is speaking. Useful for keeping focus on a presenter or important speaker.
+
+
+
+```swift
+// Pin a participant
+CallSession.shared.pinParticipant()
+
+// Unpin (returns to automatic speaker highlighting)
+CallSession.shared.unPinParticipant()
+```
+
+
+```objectivec
+// Pin a participant
+[[CallSession shared] pinParticipant];
+
+// Unpin (returns to automatic speaker highlighting)
+[[CallSession shared] unPinParticipant];
+```
+
+
+
+
+Pinning a participant only affects your local view. Other participants can pin different users independently.
+
+
+## Listen for Participant Events
+
+Monitor participant state changes using `ParticipantEventListener`:
+
+
+
+```swift
+class CallViewController: UIViewController, ParticipantEventListener {
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+ CallSession.shared.addParticipantEventListener(self)
+ }
+
+ deinit {
+ CallSession.shared.removeParticipantEventListener(self)
+ }
+
+ func onParticipantJoined(participant: Participant) {
+ print("\(participant.name ?? "") joined the call")
+ updateParticipantList()
+ }
+
+ func onParticipantLeft(participant: Participant) {
+ print("\(participant.name ?? "") left the call")
+ updateParticipantList()
+ }
+
+ func onParticipantListChanged(participants: [Participant]) {
+ print("Participant count: \(participants.count)")
+ refreshParticipantList(participants)
+ }
+
+ func onParticipantAudioMuted(participant: Participant) {
+ print("\(participant.name ?? "") was muted")
+ updateMuteIndicator(participant, muted: true)
+ }
+
+ func onParticipantAudioUnmuted(participant: Participant) {
+ print("\(participant.name ?? "") was unmuted")
+ updateMuteIndicator(participant, muted: false)
+ }
+
+ func onParticipantVideoPaused(participant: Participant) {
+ print("\(participant.name ?? "") video paused")
+ showParticipantAvatar(participant)
+ }
+
+ func onParticipantVideoResumed(participant: Participant) {
+ print("\(participant.name ?? "") video resumed")
+ showParticipantVideo(participant)
+ }
+
+ func onDominantSpeakerChanged(participant: Participant) {
+ print("\(participant.name ?? "") is now speaking")
+ highlightActiveSpeaker(participant)
+ }
+
+ // Other callbacks...
+ func onParticipantHandRaised(participant: Participant) {}
+ func onParticipantHandLowered(participant: Participant) {}
+ func onParticipantStartedScreenShare(participant: Participant) {}
+ func onParticipantStoppedScreenShare(participant: Participant) {}
+ func onParticipantStartedRecording(participant: Participant) {}
+ func onParticipantStoppedRecording(participant: Participant) {}
+}
+```
+
+
+```objectivec
+@interface CallViewController ()
+@end
+
+@implementation CallViewController
+
+- (void)viewDidLoad {
+ [super viewDidLoad];
+ [[CallSession shared] addParticipantEventListener:self];
+}
+
+- (void)dealloc {
+ [[CallSession shared] removeParticipantEventListener:self];
+}
+
+- (void)onParticipantJoinedWithParticipant:(Participant *)participant {
+ NSLog(@"%@ joined the call", participant.name);
+ [self updateParticipantList];
+}
+
+- (void)onParticipantLeftWithParticipant:(Participant *)participant {
+ NSLog(@"%@ left the call", participant.name);
+ [self updateParticipantList];
+}
+
+- (void)onParticipantListChangedWithParticipants:(NSArray *)participants {
+ NSLog(@"Participant count: %lu", (unsigned long)participants.count);
+ [self refreshParticipantList:participants];
+}
+
+- (void)onParticipantAudioMutedWithParticipant:(Participant *)participant {
+ NSLog(@"%@ was muted", participant.name);
+}
+
+- (void)onParticipantAudioUnmutedWithParticipant:(Participant *)participant {
+ NSLog(@"%@ was unmuted", participant.name);
+}
+
+- (void)onDominantSpeakerChangedWithParticipant:(Participant *)participant {
+ NSLog(@"%@ is now speaking", participant.name);
+}
+
+// Other callbacks...
+
+@end
+```
+
+
+
+## Participant Object
+
+The `Participant` object contains information about each call participant:
+
+| Property | Type | Description |
+|----------|------|-------------|
+| `uid` | String | Unique identifier (CometChat user ID) |
+| `name` | String | Display name |
+| `avatar` | String | URL of avatar image |
+| `pid` | String | Participant ID for this call session |
+| `role` | String | Role in the call |
+| `audioMuted` | Bool | Whether audio is muted |
+| `videoPaused` | Bool | Whether video is paused |
+| `isPinned` | Bool | Whether pinned in layout |
+| `isPresenting` | Bool | Whether screen sharing |
+| `raisedHandTimestamp` | Int | Timestamp when hand was raised (0 if not raised) |
+
+## Hide Participant List Button
+
+To hide the participant list button in the call UI:
+
+
+
+```swift
+let sessionSettings = CometChatCalls.sessionSettingsBuilder
+ .hideParticipantListButton(true)
+ .build()
+```
+
+
+```objectivec
+SessionSettings *sessionSettings = [[[CometChatCalls sessionSettingsBuilder]
+ hideParticipantListButton:YES]
+ build];
+```
+
+
diff --git a/calls/ios/picture-in-picture.mdx b/calls/ios/picture-in-picture.mdx
new file mode 100644
index 00000000..abc68386
--- /dev/null
+++ b/calls/ios/picture-in-picture.mdx
@@ -0,0 +1,174 @@
+---
+title: "Picture-in-Picture"
+sidebarTitle: "Picture-in-Picture"
+---
+
+Enable Picture-in-Picture (PiP) mode to allow users to continue their call in a floating window while using other apps. PiP provides a seamless multitasking experience during calls.
+
+
+Picture-in-Picture implementation is handled at the app level using iOS's PiP APIs. The Calls SDK only adjusts the call UI layout to fit the PiP window - it does not manage the PiP window itself.
+
+
+## How It Works
+
+1. Your app enters PiP mode using iOS's AVPictureInPictureController API
+2. You notify the Calls SDK by calling `enablePictureInPictureLayout()`
+3. The SDK adjusts the call UI to fit the smaller PiP window (hides controls, optimizes layout)
+4. When exiting PiP, call `disablePictureInPictureLayout()` to restore the full UI
+
+## Enable Picture-in-Picture
+
+Enter PiP mode programmatically using the `enablePictureInPictureLayout()` action:
+
+
+
+```swift
+CallSession.shared.enablePictureInPictureLayout()
+```
+
+
+```objectivec
+[[CallSession shared] enablePictureInPictureLayout];
+```
+
+
+
+## Disable Picture-in-Picture
+
+Exit PiP mode and return to the full-screen call interface:
+
+
+
+```swift
+CallSession.shared.disablePictureInPictureLayout()
+```
+
+
+```objectivec
+[[CallSession shared] disablePictureInPictureLayout];
+```
+
+
+
+## Listen for PiP Events
+
+Monitor PiP mode transitions using `LayoutListener` to update your UI accordingly:
+
+
+
+```swift
+class CallViewController: UIViewController, LayoutListener {
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+ CallSession.shared.addLayoutListener(self)
+ }
+
+ deinit {
+ CallSession.shared.removeLayoutListener(self)
+ }
+
+ func onPictureInPictureLayoutEnabled() {
+ print("Entered PiP mode")
+ // Hide custom overlays or controls
+ hideCustomControls()
+ }
+
+ func onPictureInPictureLayoutDisabled() {
+ print("Exited PiP mode")
+ // Show custom overlays or controls
+ showCustomControls()
+ }
+
+ func onCallLayoutChanged(layoutType: LayoutType) {}
+ func onParticipantListVisible() {}
+ func onParticipantListHidden() {}
+}
+```
+
+
+```objectivec
+@interface CallViewController ()
+@end
+
+@implementation CallViewController
+
+- (void)viewDidLoad {
+ [super viewDidLoad];
+ [[CallSession shared] addLayoutListener:self];
+}
+
+- (void)dealloc {
+ [[CallSession shared] removeLayoutListener:self];
+}
+
+- (void)onPictureInPictureLayoutEnabled {
+ NSLog(@"Entered PiP mode");
+ // Hide custom overlays or controls
+ [self hideCustomControls];
+}
+
+- (void)onPictureInPictureLayoutDisabled {
+ NSLog(@"Exited PiP mode");
+ // Show custom overlays or controls
+ [self showCustomControls];
+}
+
+- (void)onCallLayoutChangedWithLayoutType:(LayoutType)layoutType {}
+- (void)onParticipantListVisible {}
+- (void)onParticipantListHidden {}
+
+@end
+```
+
+
+
+## iOS PiP Setup
+
+To enable PiP in your iOS app:
+
+**1. Enable Background Modes**
+
+In your project's **Signing & Capabilities**, add the **Background Modes** capability and enable:
+- Audio, AirPlay, and Picture in Picture
+
+**2. Configure AVAudioSession**
+
+
+
+```swift
+import AVFoundation
+
+func configureAudioSession() {
+ do {
+ try AVAudioSession.sharedInstance().setCategory(
+ .playAndRecord,
+ mode: .videoChat,
+ options: [.allowBluetooth, .defaultToSpeaker]
+ )
+ try AVAudioSession.sharedInstance().setActive(true)
+ } catch {
+ print("Failed to configure audio session: \(error)")
+ }
+}
+```
+
+
+```objectivec
+#import
+
+- (void)configureAudioSession {
+ NSError *error = nil;
+ [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayAndRecord
+ mode:AVAudioSessionModeVideoChat
+ options:AVAudioSessionCategoryOptionAllowBluetooth | AVAudioSessionCategoryOptionDefaultToSpeaker
+ error:&error];
+ [[AVAudioSession sharedInstance] setActive:YES error:&error];
+}
+```
+
+
+
+
+PiP mode is available on iOS 14.0 and later for iPhones, and iOS 9.0 and later for iPads.
+
diff --git a/calls/ios/raise-hand.mdx b/calls/ios/raise-hand.mdx
new file mode 100644
index 00000000..b7c74688
--- /dev/null
+++ b/calls/ios/raise-hand.mdx
@@ -0,0 +1,178 @@
+---
+title: "Raise Hand"
+sidebarTitle: "Raise Hand"
+---
+
+Allow participants to raise their hand to get attention during calls. This feature is useful for large meetings, webinars, or any scenario where participants need to signal they want to speak.
+
+## Raise Hand
+
+Signal that you want to speak or get attention:
+
+
+
+```swift
+CallSession.shared.raiseHand()
+```
+
+
+```objectivec
+[[CallSession shared] raiseHand];
+```
+
+
+
+## Lower Hand
+
+Remove the raised hand indicator:
+
+
+
+```swift
+CallSession.shared.lowerHand()
+```
+
+
+```objectivec
+[[CallSession shared] lowerHand];
+```
+
+
+
+## Listen for Raise Hand Events
+
+Monitor when participants raise or lower their hands using `ParticipantEventListener`:
+
+
+
+```swift
+class CallViewController: UIViewController, ParticipantEventListener {
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+ CallSession.shared.addParticipantEventListener(self)
+ }
+
+ deinit {
+ CallSession.shared.removeParticipantEventListener(self)
+ }
+
+ func onParticipantHandRaised(participant: Participant) {
+ print("\(participant.name ?? "") raised their hand")
+ // Show notification or visual indicator
+ showHandRaisedNotification(participant)
+ }
+
+ func onParticipantHandLowered(participant: Participant) {
+ print("\(participant.name ?? "") lowered their hand")
+ // Remove notification or visual indicator
+ hideHandRaisedIndicator(participant)
+ }
+
+ // Other callbacks...
+ func onParticipantJoined(participant: Participant) {}
+ func onParticipantLeft(participant: Participant) {}
+ func onParticipantListChanged(participants: [Participant]) {}
+ func onParticipantAudioMuted(participant: Participant) {}
+ func onParticipantAudioUnmuted(participant: Participant) {}
+ func onParticipantVideoPaused(participant: Participant) {}
+ func onParticipantVideoResumed(participant: Participant) {}
+ func onParticipantStartedScreenShare(participant: Participant) {}
+ func onParticipantStoppedScreenShare(participant: Participant) {}
+ func onParticipantStartedRecording(participant: Participant) {}
+ func onParticipantStoppedRecording(participant: Participant) {}
+ func onDominantSpeakerChanged(participant: Participant) {}
+}
+```
+
+
+```objectivec
+@interface CallViewController ()
+@end
+
+@implementation CallViewController
+
+- (void)viewDidLoad {
+ [super viewDidLoad];
+ [[CallSession shared] addParticipantEventListener:self];
+}
+
+- (void)dealloc {
+ [[CallSession shared] removeParticipantEventListener:self];
+}
+
+- (void)onParticipantHandRaisedWithParticipant:(Participant *)participant {
+ NSLog(@"%@ raised their hand", participant.name);
+ // Show notification or visual indicator
+ [self showHandRaisedNotification:participant];
+}
+
+- (void)onParticipantHandLoweredWithParticipant:(Participant *)participant {
+ NSLog(@"%@ lowered their hand", participant.name);
+ // Remove notification or visual indicator
+ [self hideHandRaisedIndicator:participant];
+}
+
+// Other callbacks...
+
+@end
+```
+
+
+
+## Check Raised Hand Status
+
+The `Participant` object includes a `raisedHandTimestamp` property to check if a participant has their hand raised:
+
+
+
+```swift
+func onParticipantListChanged(participants: [Participant]) {
+ let raisedHands = participants
+ .filter { $0.raisedHandTimestamp > 0 }
+ .sorted { $0.raisedHandTimestamp < $1.raisedHandTimestamp }
+
+ // Display participants with raised hands in order
+ updateRaisedHandsList(raisedHands)
+}
+```
+
+
+```objectivec
+- (void)onParticipantListChangedWithParticipants:(NSArray *)participants {
+ NSMutableArray *raisedHands = [NSMutableArray array];
+ for (Participant *p in participants) {
+ if (p.raisedHandTimestamp > 0) {
+ [raisedHands addObject:p];
+ }
+ }
+ // Sort by timestamp and display
+ [raisedHands sortUsingComparator:^NSComparisonResult(Participant *a, Participant *b) {
+ return [@(a.raisedHandTimestamp) compare:@(b.raisedHandTimestamp)];
+ }];
+ [self updateRaisedHandsList:raisedHands];
+}
+```
+
+
+
+## Hide Raise Hand Button
+
+To disable the raise hand feature, hide the button in the call UI:
+
+
+
+```swift
+let sessionSettings = CometChatCalls.sessionSettingsBuilder
+ .hideRaiseHandButton(true)
+ .build()
+```
+
+
+```objectivec
+SessionSettings *sessionSettings = [[[CometChatCalls sessionSettingsBuilder]
+ hideRaiseHandButton:YES]
+ build];
+```
+
+
diff --git a/calls/ios/recording.mdx b/calls/ios/recording.mdx
new file mode 100644
index 00000000..70909fe1
--- /dev/null
+++ b/calls/ios/recording.mdx
@@ -0,0 +1,218 @@
+---
+title: "Recording"
+sidebarTitle: "Recording"
+---
+
+Record call sessions for later playback. Recordings are stored server-side and can be accessed through call logs or the CometChat Dashboard.
+
+
+Recording must be enabled for your CometChat app. Contact support or check your Dashboard settings if recording is not available.
+
+
+## Start Recording
+
+Start recording during an active call session:
+
+
+
+```swift
+CallSession.shared.startRecording()
+```
+
+
+```objectivec
+[[CallSession shared] startRecording];
+```
+
+
+
+All participants are notified when recording starts.
+
+## Stop Recording
+
+Stop an active recording:
+
+
+
+```swift
+CallSession.shared.stopRecording()
+```
+
+
+```objectivec
+[[CallSession shared] stopRecording];
+```
+
+
+
+## Auto-Start Recording
+
+Configure calls to automatically start recording when the session begins:
+
+
+
+```swift
+let sessionSettings = CometChatCalls.sessionSettingsBuilder
+ .enableAutoStartRecording(true)
+ .build()
+```
+
+
+```objectivec
+SessionSettings *sessionSettings = [[[CometChatCalls sessionSettingsBuilder]
+ enableAutoStartRecording:YES]
+ build];
+```
+
+
+
+## Hide Recording Button
+
+Hide the recording button from the default call UI:
+
+
+
+```swift
+let sessionSettings = CometChatCalls.sessionSettingsBuilder
+ .hideRecordingButton(true)
+ .build()
+```
+
+
+```objectivec
+SessionSettings *sessionSettings = [[[CometChatCalls sessionSettingsBuilder]
+ hideRecordingButton:YES]
+ build];
+```
+
+
+
+## Listen for Recording Events
+
+Monitor recording state changes using `MediaEventsListener`:
+
+
+
+```swift
+class CallViewController: UIViewController, MediaEventsListener {
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+ CallSession.shared.addMediaEventsListener(self)
+ }
+
+ deinit {
+ CallSession.shared.removeMediaEventsListener(self)
+ }
+
+ func onRecordingStarted() {
+ print("Recording started")
+ showRecordingIndicator()
+ }
+
+ func onRecordingStopped() {
+ print("Recording stopped")
+ hideRecordingIndicator()
+ }
+
+ // Other callbacks...
+ func onAudioMuted() {}
+ func onAudioUnMuted() {}
+ func onVideoPaused() {}
+ func onVideoResumed() {}
+ func onScreenShareStarted() {}
+ func onScreenShareStopped() {}
+ func onAudioModeChanged(audioModeType: AudioModeType) {}
+ func onCameraFacingChanged(cameraFacing: CameraFacing) {}
+}
+```
+
+
+```objectivec
+@interface CallViewController ()
+@end
+
+@implementation CallViewController
+
+- (void)viewDidLoad {
+ [super viewDidLoad];
+ [[CallSession shared] addMediaEventsListener:self];
+}
+
+- (void)dealloc {
+ [[CallSession shared] removeMediaEventsListener:self];
+}
+
+- (void)onRecordingStarted {
+ NSLog(@"Recording started");
+ [self showRecordingIndicator];
+}
+
+- (void)onRecordingStopped {
+ NSLog(@"Recording stopped");
+ [self hideRecordingIndicator];
+}
+
+// Other callbacks...
+
+@end
+```
+
+
+
+## Track Participant Recording
+
+Monitor when other participants start or stop recording using `ParticipantEventListener`:
+
+
+
+```swift
+class CallViewController: UIViewController, ParticipantEventListener {
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+ CallSession.shared.addParticipantEventListener(self)
+ }
+
+ func onParticipantStartedRecording(participant: Participant) {
+ print("\(participant.name ?? "") started recording")
+ }
+
+ func onParticipantStoppedRecording(participant: Participant) {
+ print("\(participant.name ?? "") stopped recording")
+ }
+
+ // Other callbacks...
+}
+```
+
+
+```objectivec
+- (void)onParticipantStartedRecordingWithParticipant:(Participant *)participant {
+ NSLog(@"%@ started recording", participant.name);
+}
+
+- (void)onParticipantStoppedRecordingWithParticipant:(Participant *)participant {
+ NSLog(@"%@ stopped recording", participant.name);
+}
+```
+
+
+
+## Access Recordings
+
+Recordings are available after the call ends. You can access them in two ways:
+
+1. **CometChat Dashboard**: Navigate to **Calls > Call Logs** in your [CometChat Dashboard](https://app.cometchat.com) to view and download recordings.
+
+2. **Programmatically**: Fetch recordings through [Call Logs](/calls/ios/call-logs).
+
+## Recording Object
+
+| Property | Type | Description |
+|----------|------|-------------|
+| `rid` | String | Unique recording identifier |
+| `recordingURL` | String | URL to download/stream the recording |
+| `startTime` | Int | Timestamp when recording started |
+| `endTime` | Int | Timestamp when recording ended |
+| `duration` | Double | Recording duration in seconds |
diff --git a/calls/ios/ringing.mdx b/calls/ios/ringing.mdx
new file mode 100644
index 00000000..d07d1495
--- /dev/null
+++ b/calls/ios/ringing.mdx
@@ -0,0 +1,453 @@
+---
+title: "Ringing"
+sidebarTitle: "Ringing"
+---
+
+Implement incoming and outgoing call notifications with accept/reject functionality. Ringing enables real-time call signaling between users, allowing them to initiate calls and respond to incoming call requests.
+
+
+Ringing functionality requires the CometChat Chat SDK to be integrated alongside the Calls SDK. The Chat SDK handles call signaling (initiating, accepting, rejecting calls), while the Calls SDK manages the actual call session.
+
+
+## How Ringing Works
+
+The ringing flow involves two SDKs working together:
+
+1. **Chat SDK** - Handles call signaling (initiate, accept, reject, cancel)
+2. **Calls SDK** - Manages the actual call session once accepted
+
+```mermaid
+sequenceDiagram
+ participant Caller
+ participant ChatSDK
+ participant Receiver
+ participant CallsSDK
+
+ Caller->>ChatSDK: initiateCall()
+ ChatSDK->>Receiver: onIncomingCallReceived
+ Receiver->>ChatSDK: acceptCall()
+ ChatSDK-->>Caller: onOutgoingCallAccepted
+ Caller->>CallsSDK: joinSession()
+ Receiver->>CallsSDK: joinSession()
+```
+
+## Initiate a Call
+
+Use the Chat SDK to initiate a call to a user or group:
+
+
+
+```swift
+let receiverID = "USER_ID"
+let receiverType: CometChat.ReceiverType = .user
+let callType: CometChat.CallType = .video
+
+let call = Call(receiverId: receiverID, callType: callType, receiverType: receiverType)
+
+CometChat.initiateCall(call: call, onSuccess: { call in
+ print("Call initiated: \(call?.sessionID ?? "")")
+ // Show outgoing call UI
+}, onError: { error in
+ print("Call initiation failed: \(error?.errorDescription ?? "")")
+})
+```
+
+
+```objectivec
+NSString *receiverID = @"USER_ID";
+CometChatReceiverType receiverType = CometChatReceiverTypeUser;
+CometChatCallType callType = CometChatCallTypeVideo;
+
+Call *call = [[Call alloc] initWithReceiverId:receiverID
+ callType:callType
+ receiverType:receiverType];
+
+[CometChat initiateCallWithCall:call
+ onSuccess:^(Call * call) {
+ NSLog(@"Call initiated: %@", call.sessionID);
+ // Show outgoing call UI
+} onError:^(CometChatException * error) {
+ NSLog(@"Call initiation failed: %@", error.errorDescription);
+}];
+```
+
+
+
+| Parameter | Type | Description |
+|-----------|------|-------------|
+| `receiverID` | String | UID of the user or GUID of the group to call |
+| `receiverType` | ReceiverType | `.user` or `.group` |
+| `callType` | CallType | `.video` or `.audio` |
+
+## Listen for Incoming Calls
+
+Register a call listener to receive incoming call notifications:
+
+
+
+```swift
+let listenerID = "UNIQUE_LISTENER_ID"
+
+CometChat.addCallListener(listenerID, self)
+
+// Implement CometChatCallDelegate
+extension CallViewController: CometChatCallDelegate {
+
+ func onIncomingCallReceived(incomingCall: Call?, error: CometChatException?) {
+ guard let call = incomingCall else { return }
+ print("Incoming call from: \(call.callInitiator?.name ?? "")")
+ // Show incoming call UI with accept/reject options
+ }
+
+ func onOutgoingCallAccepted(acceptedCall: Call?, error: CometChatException?) {
+ guard let call = acceptedCall else { return }
+ print("Call accepted, joining session...")
+ joinCallSession(sessionId: call.sessionID ?? "")
+ }
+
+ func onOutgoingCallRejected(rejectedCall: Call?, error: CometChatException?) {
+ print("Call rejected")
+ // Dismiss outgoing call UI
+ }
+
+ func onIncomingCallCancelled(cancelledCall: Call?, error: CometChatException?) {
+ print("Incoming call cancelled")
+ // Dismiss incoming call UI
+ }
+
+ func onCallEndedMessageReceived(endedCall: Call?, error: CometChatException?) {
+ print("Call ended")
+ }
+}
+```
+
+
+```objectivec
+NSString *listenerID = @"UNIQUE_LISTENER_ID";
+
+[CometChat addCallListener:listenerID delegate:self];
+
+// Implement CometChatCallDelegate
+- (void)onIncomingCallReceivedWithIncomingCall:(Call *)incomingCall
+ error:(CometChatException *)error {
+ NSLog(@"Incoming call from: %@", incomingCall.callInitiator.name);
+ // Show incoming call UI with accept/reject options
+}
+
+- (void)onOutgoingCallAcceptedWithAcceptedCall:(Call *)acceptedCall
+ error:(CometChatException *)error {
+ NSLog(@"Call accepted, joining session...");
+ [self joinCallSessionWithSessionId:acceptedCall.sessionID];
+}
+
+- (void)onOutgoingCallRejectedWithRejectedCall:(Call *)rejectedCall
+ error:(CometChatException *)error {
+ NSLog(@"Call rejected");
+ // Dismiss outgoing call UI
+}
+
+- (void)onIncomingCallCancelledWithCancelledCall:(Call *)cancelledCall
+ error:(CometChatException *)error {
+ NSLog(@"Incoming call cancelled");
+ // Dismiss incoming call UI
+}
+
+- (void)onCallEndedMessageReceivedWithEndedCall:(Call *)endedCall
+ error:(CometChatException *)error {
+ NSLog(@"Call ended");
+}
+```
+
+
+
+| Callback | Description |
+|----------|-------------|
+| `onIncomingCallReceived` | A new incoming call is received |
+| `onOutgoingCallAccepted` | The receiver accepted your outgoing call |
+| `onOutgoingCallRejected` | The receiver rejected your outgoing call |
+| `onIncomingCallCancelled` | The caller cancelled the incoming call |
+| `onCallEndedMessageReceived` | The call has ended |
+
+
+Remember to remove the call listener when it's no longer needed to prevent memory leaks:
+```swift
+CometChat.removeCallListener(listenerID)
+```
+
+
+## Accept a Call
+
+When an incoming call is received, accept it using the Chat SDK:
+
+
+
+```swift
+func acceptIncomingCall(sessionId: String) {
+ CometChat.acceptCall(sessionID: sessionId, onSuccess: { call in
+ print("Call accepted")
+ self.joinCallSession(sessionId: call?.sessionID ?? "")
+ }, onError: { error in
+ print("Accept call failed: \(error?.errorDescription ?? "")")
+ })
+}
+```
+
+
+```objectivec
+- (void)acceptIncomingCallWithSessionId:(NSString *)sessionId {
+ [CometChat acceptCallWithSessionID:sessionId
+ onSuccess:^(Call * call) {
+ NSLog(@"Call accepted");
+ [self joinCallSessionWithSessionId:call.sessionID];
+ } onError:^(CometChatException * error) {
+ NSLog(@"Accept call failed: %@", error.errorDescription);
+ }];
+}
+```
+
+
+
+## Reject a Call
+
+Reject an incoming call:
+
+
+
+```swift
+func rejectIncomingCall(sessionId: String) {
+ let status: CometChat.CallStatus = .rejected
+
+ CometChat.rejectCall(sessionID: sessionId, status: status, onSuccess: { call in
+ print("Call rejected")
+ // Dismiss incoming call UI
+ }, onError: { error in
+ print("Reject call failed: \(error?.errorDescription ?? "")")
+ })
+}
+```
+
+
+```objectivec
+- (void)rejectIncomingCallWithSessionId:(NSString *)sessionId {
+ [CometChat rejectCallWithSessionID:sessionId
+ status:CometChatCallStatusRejected
+ onSuccess:^(Call * call) {
+ NSLog(@"Call rejected");
+ // Dismiss incoming call UI
+ } onError:^(CometChatException * error) {
+ NSLog(@"Reject call failed: %@", error.errorDescription);
+ }];
+}
+```
+
+
+
+## Cancel a Call
+
+Cancel an outgoing call before it's answered:
+
+
+
+```swift
+func cancelOutgoingCall(sessionId: String) {
+ let status: CometChat.CallStatus = .cancelled
+
+ CometChat.rejectCall(sessionID: sessionId, status: status, onSuccess: { call in
+ print("Call cancelled")
+ // Dismiss outgoing call UI
+ }, onError: { error in
+ print("Cancel call failed: \(error?.errorDescription ?? "")")
+ })
+}
+```
+
+
+```objectivec
+- (void)cancelOutgoingCallWithSessionId:(NSString *)sessionId {
+ [CometChat rejectCallWithSessionID:sessionId
+ status:CometChatCallStatusCancelled
+ onSuccess:^(Call * call) {
+ NSLog(@"Call cancelled");
+ // Dismiss outgoing call UI
+ } onError:^(CometChatException * error) {
+ NSLog(@"Cancel call failed: %@", error.errorDescription);
+ }];
+}
+```
+
+
+
+## Join the Call Session
+
+After accepting a call (or when your outgoing call is accepted), join the call session using the Calls SDK:
+
+
+
+```swift
+func joinCallSession(sessionId: String) {
+ let sessionSettings = CometChatCalls.sessionSettingsBuilder
+ .setType(.video)
+ .build()
+
+ CometChatCalls.joinSession(
+ sessionID: sessionId,
+ callSetting: sessionSettings,
+ container: callViewContainer,
+ onSuccess: { message in
+ print("Joined call session")
+ },
+ onError: { error in
+ print("Failed to join: \(error?.errorDescription ?? "")")
+ }
+ )
+}
+```
+
+
+```objectivec
+- (void)joinCallSessionWithSessionId:(NSString *)sessionId {
+ SessionSettings *sessionSettings = [[[CometChatCalls sessionSettingsBuilder]
+ setType:CallTypeVideo]
+ build];
+
+ [CometChatCalls joinSessionWithSessionID:sessionId
+ callSetting:sessionSettings
+ container:self.callViewContainer
+ onSuccess:^(NSString * message) {
+ NSLog(@"Joined call session");
+ } onError:^(CometChatCallException * error) {
+ NSLog(@"Failed to join: %@", error.errorDescription);
+ }];
+}
+```
+
+
+
+## End a Call
+
+Properly ending a call requires coordination between both SDKs to ensure all participants are notified and call logs are recorded correctly.
+
+
+Always call `CometChat.endCall()` when ending a call. This notifies the other participant and ensures the call is properly logged. Without this, the other user won't know the call has ended and call logs may be incomplete.
+
+
+```mermaid
+sequenceDiagram
+ participant User
+ participant CallsSDK
+ participant ChatSDK
+ participant OtherParticipant
+
+ User->>CallsSDK: leaveSession()
+ User->>ChatSDK: endCall(sessionId)
+ ChatSDK->>OtherParticipant: onCallEndedMessageReceived
+ OtherParticipant->>CallsSDK: leaveSession()
+```
+
+When using the default call UI, listen for the end call button click using `ButtonClickListener` and call `endCall()`:
+
+
+
+```swift
+class CallViewController: UIViewController, ButtonClickListener {
+
+ var currentSessionId: String = ""
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+ CallSession.shared.addButtonClickListener(self)
+ }
+
+ func onLeaveSessionButtonClicked() {
+ endCall(sessionId: currentSessionId)
+ }
+
+ func endCall(sessionId: String) {
+ // 1. Leave the call session (Calls SDK)
+ CallSession.shared.leaveSession()
+
+ // 2. Notify other participants (Chat SDK)
+ CometChat.endCall(sessionID: sessionId, onSuccess: { call in
+ print("Call ended successfully")
+ self.navigationController?.popViewController(animated: true)
+ }, onError: { error in
+ print("End call failed: \(error?.errorDescription ?? "")")
+ self.navigationController?.popViewController(animated: true)
+ })
+ }
+
+ // Other ButtonClickListener callbacks...
+}
+```
+
+
+```objectivec
+@interface CallViewController ()
+@property (nonatomic, strong) NSString *currentSessionId;
+@end
+
+@implementation CallViewController
+
+- (void)viewDidLoad {
+ [super viewDidLoad];
+ [[CallSession shared] addButtonClickListener:self];
+}
+
+- (void)onLeaveSessionButtonClicked {
+ [self endCallWithSessionId:self.currentSessionId];
+}
+
+- (void)endCallWithSessionId:(NSString *)sessionId {
+ // 1. Leave the call session (Calls SDK)
+ [[CallSession shared] leaveSession];
+
+ // 2. Notify other participants (Chat SDK)
+ [CometChat endCallWithSessionID:sessionId
+ onSuccess:^(Call * call) {
+ NSLog(@"Call ended successfully");
+ [self.navigationController popViewControllerAnimated:YES];
+ } onError:^(CometChatException * error) {
+ NSLog(@"End call failed: %@", error.errorDescription);
+ [self.navigationController popViewControllerAnimated:YES];
+ }];
+}
+
+@end
+```
+
+
+
+The other participant receives `onCallEndedMessageReceived` callback and should leave the session:
+
+
+
+```swift
+func onCallEndedMessageReceived(endedCall: Call?, error: CometChatException?) {
+ CallSession.shared.leaveSession()
+ navigationController?.popViewController(animated: true)
+}
+```
+
+
+```objectivec
+- (void)onCallEndedMessageReceivedWithEndedCall:(Call *)endedCall
+ error:(CometChatException *)error {
+ [[CallSession shared] leaveSession];
+ [self.navigationController popViewControllerAnimated:YES];
+}
+```
+
+
+
+## Call Status Values
+
+| Status | Description |
+|--------|-------------|
+| `initiated` | Call has been initiated but not yet answered |
+| `ongoing` | Call is currently in progress |
+| `busy` | Receiver is busy on another call |
+| `rejected` | Receiver rejected the call |
+| `cancelled` | Caller cancelled before receiver answered |
+| `ended` | Call ended normally |
+| `missed` | Receiver didn't answer in time |
+| `unanswered` | Call was not answered |
diff --git a/calls/ios/screen-sharing.mdx b/calls/ios/screen-sharing.mdx
new file mode 100644
index 00000000..9b777c0d
--- /dev/null
+++ b/calls/ios/screen-sharing.mdx
@@ -0,0 +1,122 @@
+---
+title: "Screen Sharing"
+sidebarTitle: "Screen Sharing"
+---
+
+View screen shares from other participants during a call. The iOS SDK can receive and display screen shares initiated from web clients.
+
+
+The iOS Calls SDK does not support initiating screen sharing. Screen sharing can only be started from web clients. iOS participants can view shared screens.
+
+
+## How It Works
+
+When a web participant starts screen sharing:
+1. The SDK receives the screen share stream
+2. The call layout automatically adjusts to display the shared screen prominently
+3. iOS participants can view the shared content
+
+## Listen for Screen Share Events
+
+Monitor when participants start or stop screen sharing:
+
+
+
+```swift
+class CallViewController: UIViewController, ParticipantEventListener {
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+ CallSession.shared.addParticipantEventListener(self)
+ }
+
+ deinit {
+ CallSession.shared.removeParticipantEventListener(self)
+ }
+
+ func onParticipantStartedScreenShare(participant: Participant) {
+ print("\(participant.name ?? "") started screen sharing")
+ // Layout automatically adjusts to show shared screen
+ }
+
+ func onParticipantStoppedScreenShare(participant: Participant) {
+ print("\(participant.name ?? "") stopped screen sharing")
+ // Layout returns to normal view
+ }
+
+ // Other callbacks...
+ func onParticipantJoined(participant: Participant) {}
+ func onParticipantLeft(participant: Participant) {}
+ func onParticipantListChanged(participants: [Participant]) {}
+ func onParticipantAudioMuted(participant: Participant) {}
+ func onParticipantAudioUnmuted(participant: Participant) {}
+ func onParticipantVideoPaused(participant: Participant) {}
+ func onParticipantVideoResumed(participant: Participant) {}
+ func onParticipantStartedRecording(participant: Participant) {}
+ func onParticipantStoppedRecording(participant: Participant) {}
+ func onParticipantHandRaised(participant: Participant) {}
+ func onParticipantHandLowered(participant: Participant) {}
+ func onDominantSpeakerChanged(participant: Participant) {}
+}
+```
+
+
+```objectivec
+@interface CallViewController ()
+@end
+
+@implementation CallViewController
+
+- (void)viewDidLoad {
+ [super viewDidLoad];
+ [[CallSession shared] addParticipantEventListener:self];
+}
+
+- (void)dealloc {
+ [[CallSession shared] removeParticipantEventListener:self];
+}
+
+- (void)onParticipantStartedScreenShareWithParticipant:(Participant *)participant {
+ NSLog(@"%@ started screen sharing", participant.name);
+ // Layout automatically adjusts to show shared screen
+}
+
+- (void)onParticipantStoppedScreenShareWithParticipant:(Participant *)participant {
+ NSLog(@"%@ stopped screen sharing", participant.name);
+ // Layout returns to normal view
+}
+
+// Other callbacks...
+
+@end
+```
+
+
+
+## Check Screen Share Status
+
+Use the `isPresenting` property on the `Participant` object to check if someone is sharing their screen:
+
+
+
+```swift
+func onParticipantListChanged(participants: [Participant]) {
+ if let presenter = participants.first(where: { $0.isPresenting }) {
+ print("\(presenter.name ?? "") is currently sharing their screen")
+ }
+}
+```
+
+
+```objectivec
+- (void)onParticipantListChangedWithParticipants:(NSArray *)participants {
+ for (Participant *p in participants) {
+ if (p.isPresenting) {
+ NSLog(@"%@ is currently sharing their screen", p.name);
+ break;
+ }
+ }
+}
+```
+
+
diff --git a/calls/ios/session-settings.mdx b/calls/ios/session-settings.mdx
new file mode 100644
index 00000000..a94d8454
--- /dev/null
+++ b/calls/ios/session-settings.mdx
@@ -0,0 +1,640 @@
+---
+title: "SessionSettingsBuilder"
+sidebarTitle: "SessionSettingsBuilder"
+---
+
+The `SessionSettingsBuilder` is a powerful configuration tool that allows you to customize every aspect of your call session before participants join. From controlling the initial audio/video state to customizing the UI layout and hiding specific controls, this builder gives you complete control over the call experience.
+
+Proper session configuration is crucial for creating a seamless user experience tailored to your application's specific needs.
+
+
+These are pre-session configurations that must be set before joining a call. Once configured, pass the `SessionSettings` object to the `joinSession()` method. Settings cannot be changed after the session has started, though many features can be controlled dynamically during the call using call actions.
+
+
+
+
+```swift
+let sessionSettings = CometChatCalls.sessionSettingsBuilder
+ .setTitle("Team Meeting")
+ .setDisplayName("John Doe")
+ .setType(.video)
+ .setLayout(.tile)
+ .startAudioMuted(false)
+ .startVideoPaused(false)
+ .build()
+```
+
+
+```objectivec
+SessionSettings *sessionSettings = [[[[[[[CometChatCalls sessionSettingsBuilder]
+ setTitle:@"Team Meeting"]
+ setDisplayName:@"John Doe"]
+ setType:CallTypeVideo]
+ setLayout:LayoutTypeTile]
+ startAudioMuted:NO]
+ startVideoPaused:NO]
+ build];
+```
+
+
+
+## Session Settings
+
+### Title
+
+**Method:** `setTitle(_ title: String)`
+
+Sets the title that appears in the call header. This helps participants identify the purpose or name of the call session. The title is displayed prominently at the top of the call interface.
+
+
+
+```swift
+.setTitle("Team Meeting")
+```
+
+
+```objectivec
+[builder setTitle:@"Team Meeting"]
+```
+
+
+
+| Parameter | Type | Default |
+|-----------|------|---------|
+| `title` | String | nil |
+
+### Display Name
+
+**Method:** `setDisplayName(_ name: String)`
+
+Sets the display name that will be shown to other participants in the call. This name appears on your video tile and in the participant list, helping others identify you during the session.
+
+
+
+```swift
+.setDisplayName("John Doe")
+```
+
+
+```objectivec
+[builder setDisplayName:@"John Doe"]
+```
+
+
+
+| Parameter | Type | Default |
+|-----------|------|---------|
+| `name` | String | nil |
+
+### Session Type
+
+**Method:** `setType(_ type: CallType)`
+
+Defines the type of call session. Choose `.video` for video calls with camera enabled, or `.audio` for audio-only calls. This setting determines whether video streaming is enabled by default.
+
+
+
+```swift
+.setType(.video)
+```
+
+
+```objectivec
+[builder setType:CallTypeVideo]
+```
+
+
+
+| Parameter | Type | Default |
+|-----------|------|---------|
+| `type` | CallType | .video |
+
+
+| Value | Description |
+|-------|-------------|
+| `.video` | Video call with camera enabled |
+| `.audio` | Audio-only call |
+
+
+### Layout Mode
+
+**Method:** `setLayout(_ layoutType: LayoutType)`
+
+Sets the initial layout mode for displaying participants. `.tile` shows all participants in a grid, `.spotlight` focuses on the active speaker with others in a sidebar, and `.sidebar` displays the main speaker with participants in a side panel.
+
+
+
+```swift
+.setLayout(.tile)
+```
+
+
+```objectivec
+[builder setLayout:LayoutTypeTile]
+```
+
+
+
+| Parameter | Type | Default |
+|-----------|------|---------|
+| `layoutType` | LayoutType | .tile |
+
+
+| Value | Description |
+|-------|-------------|
+| `.tile` | Grid layout showing all participants equally |
+| `.spotlight` | Focus on active speaker with others in sidebar |
+| `.sidebar` | Main speaker with participants in a sidebar |
+
+
+### Idle Timeout Period
+
+**Method:** `setIdleTimeoutPeriod(_ timeoutSeconds: Int)`
+
+Configures the timeout duration in seconds before automatically ending the session when you're the only participant. This prevents sessions from running indefinitely when others have left.
+
+
+
+```swift
+.setIdleTimeoutPeriod(300)
+```
+
+
+```objectivec
+[builder setIdleTimeoutPeriod:300]
+```
+
+
+
+| Parameter | Type | Default |
+|-----------|------|---------|
+| `timeoutSeconds` | Int | 300 |
+
+### Start Audio Muted
+
+**Method:** `startAudioMuted(_ muted: Bool)`
+
+Determines whether the microphone is muted when joining the session. Set to `true` to join with audio muted, requiring users to manually unmute. Useful for large meetings to prevent background noise.
+
+
+
+```swift
+.startAudioMuted(true)
+```
+
+
+```objectivec
+[builder startAudioMuted:YES]
+```
+
+
+
+| Parameter | Type | Default |
+|-----------|------|---------|
+| `muted` | Bool | false |
+
+### Start Video Paused
+
+**Method:** `startVideoPaused(_ muted: Bool)`
+
+Controls whether the camera is turned off when joining the session. Set to `true` to join with video disabled, allowing users to enable it when ready. Helpful for privacy or bandwidth considerations.
+
+
+
+```swift
+.startVideoPaused(true)
+```
+
+
+```objectivec
+[builder startVideoPaused:YES]
+```
+
+
+
+| Parameter | Type | Default |
+|-----------|------|---------|
+| `muted` | Bool | false |
+
+### Audio Mode
+
+**Method:** `setAudioMode(_ audioMode: AudioModeType)`
+
+Sets the initial audio output device for the call. Options include `.speaker` for loudspeaker, `.earpiece` for phone earpiece, `.bluetooth` for connected Bluetooth devices, or `.headphones` for wired headphones.
+
+
+
+```swift
+.setAudioMode(.speaker)
+```
+
+
+```objectivec
+[builder setAudioMode:AudioModeTypeSpeaker]
+```
+
+
+
+| Parameter | Type | Default |
+|-----------|------|---------|
+| `audioMode` | AudioModeType | .speaker |
+
+
+| Value | Description |
+|-------|-------------|
+| `.speaker` | Device loudspeaker |
+| `.earpiece` | Phone earpiece |
+| `.bluetooth` | Connected Bluetooth device |
+| `.headphones` | Wired headphones |
+
+
+### Initial Camera Facing
+
+**Method:** `setInitialCameraFacing(_ initialCameraFacing: CameraFacing)`
+
+Specifies which camera to use when starting the session. Choose `.FRONT` for the front-facing camera (selfie mode) or `.BACK` for the rear camera. Users can switch cameras during the call.
+
+
+
+```swift
+.setInitialCameraFacing(.FRONT)
+```
+
+
+```objectivec
+[builder setInitialCameraFacing:CameraFacingFRONT]
+```
+
+
+
+| Parameter | Type | Default |
+|-----------|------|---------|
+| `initialCameraFacing` | CameraFacing | .FRONT |
+
+
+| Value | Description |
+|-------|-------------|
+| `.FRONT` | Front-facing camera |
+| `.BACK` | Rear camera |
+
+
+### Auto Start Recording
+
+**Method:** `enableAutoStartRecording(_ enabled: Bool)`
+
+Automatically starts recording the session as soon as it begins. When enabled, recording starts without manual intervention, ensuring the entire session is captured from the start.
+
+
+
+```swift
+.enableAutoStartRecording(true)
+```
+
+
+```objectivec
+[builder enableAutoStartRecording:YES]
+```
+
+
+
+| Parameter | Type | Default |
+|-----------|------|---------|
+| `enabled` | Bool | false |
+
+### Hide Control Panel
+
+**Method:** `hideControlPanel(_ hidden: Bool)`
+
+Hides the bottom control bar that contains call action buttons. Set to `true` to remove the control panel entirely, useful for custom UI implementations or view-only modes.
+
+
+
+```swift
+.hideControlPanel(true)
+```
+
+
+```objectivec
+[builder hideControlPanel:YES]
+```
+
+
+
+| Parameter | Type | Default |
+|-----------|------|---------|
+| `hidden` | Bool | false |
+
+### Hide Header Panel
+
+**Method:** `hideHeaderPanel(_ hidden: Bool)`
+
+Hides the top header bar that displays the call title and session information. Set to `true` to maximize the video viewing area or implement a custom header.
+
+
+
+```swift
+.hideHeaderPanel(true)
+```
+
+
+```objectivec
+[builder hideHeaderPanel:YES]
+```
+
+
+
+| Parameter | Type | Default |
+|-----------|------|---------|
+| `hidden` | Bool | false |
+
+### Hide Session Timer
+
+**Method:** `hideSessionTimer(_ hidden: Bool)`
+
+Hides the session duration timer that shows how long the call has been active. Set to `true` to remove the timer display from the interface.
+
+
+
+```swift
+.hideSessionTimer(true)
+```
+
+
+```objectivec
+[builder hideSessionTimer:YES]
+```
+
+
+
+| Parameter | Type | Default |
+|-----------|------|---------|
+| `hidden` | Bool | false |
+
+### Hide Leave Session Button
+
+**Method:** `hideLeaveSessionButton(_ hidden: Bool)`
+
+Hides the button that allows users to leave or end the call. Set to `true` to remove this button, requiring an alternative method to exit the session.
+
+
+
+```swift
+.hideLeaveSessionButton(true)
+```
+
+
+```objectivec
+[builder hideLeaveSessionButton:YES]
+```
+
+
+
+| Parameter | Type | Default |
+|-----------|------|---------|
+| `hidden` | Bool | false |
+
+### Hide Toggle Audio Button
+
+**Method:** `hideToggleAudioButton(_ hidden: Bool)`
+
+Hides the microphone mute/unmute button from the control panel. Set to `true` to remove audio controls, useful when audio control should be managed programmatically.
+
+
+
+```swift
+.hideToggleAudioButton(true)
+```
+
+
+```objectivec
+[builder hideToggleAudioButton:YES]
+```
+
+
+
+| Parameter | Type | Default |
+|-----------|------|---------|
+| `hidden` | Bool | false |
+
+### Hide Toggle Video Button
+
+**Method:** `hideToggleVideoButton(_ hidden: Bool)`
+
+Hides the camera on/off button from the control panel. Set to `true` to remove video controls, useful for audio-only calls or custom video control implementations.
+
+
+
+```swift
+.hideToggleVideoButton(true)
+```
+
+
+```objectivec
+[builder hideToggleVideoButton:YES]
+```
+
+
+
+| Parameter | Type | Default |
+|-----------|------|---------|
+| `hidden` | Bool | false |
+
+### Hide Switch Camera Button
+
+**Method:** `hideSwitchCameraButton(_ enabled: Bool)`
+
+Hides the button that allows switching between front and rear cameras. Set to `true` to remove this control, useful for devices with single cameras or fixed camera requirements.
+
+
+
+```swift
+.hideSwitchCameraButton(true)
+```
+
+
+```objectivec
+[builder hideSwitchCameraButton:YES]
+```
+
+
+
+| Parameter | Type | Default |
+|-----------|------|---------|
+| `enabled` | Bool | false |
+
+### Hide Recording Button
+
+**Method:** `hideRecordingButton(_ enabled: Bool)`
+
+Hides the recording start/stop button from the control panel. Set to `false` to show the recording button, allowing users to manually control session recording.
+
+
+
+```swift
+.hideRecordingButton(false)
+```
+
+
+```objectivec
+[builder hideRecordingButton:NO]
+```
+
+
+
+| Parameter | Type | Default |
+|-----------|------|---------|
+| `enabled` | Bool | true |
+
+### Hide Audio Mode Button
+
+**Method:** `hideAudioModeButton(_ enabled: Bool)`
+
+Hides the button that toggles between speaker, earpiece, and other audio output modes. Set to `true` to remove audio mode controls from the interface.
+
+
+
+```swift
+.hideAudioModeButton(true)
+```
+
+
+```objectivec
+[builder hideAudioModeButton:YES]
+```
+
+
+
+| Parameter | Type | Default |
+|-----------|------|---------|
+| `enabled` | Bool | false |
+
+### Hide Raise Hand Button
+
+**Method:** `hideRaiseHandButton(_ enabled: Bool)`
+
+Hides the raise hand button that participants use to signal they want to speak. Set to `true` to remove this feature from the interface.
+
+
+
+```swift
+.hideRaiseHandButton(true)
+```
+
+
+```objectivec
+[builder hideRaiseHandButton:YES]
+```
+
+
+
+| Parameter | Type | Default |
+|-----------|------|---------|
+| `enabled` | Bool | false |
+
+### Hide Share Invite Button
+
+**Method:** `hideShareInviteButton(_ enabled: Bool)`
+
+Hides the button that allows sharing session invite links with others. Set to `false` to show the invite button, enabling easy participant invitation.
+
+
+
+```swift
+.hideShareInviteButton(false)
+```
+
+
+```objectivec
+[builder hideShareInviteButton:NO]
+```
+
+
+
+| Parameter | Type | Default |
+|-----------|------|---------|
+| `enabled` | Bool | true |
+
+### Hide Participant List Button
+
+**Method:** `hideParticipantListButton(_ hidden: Bool)`
+
+Hides the button that opens the participant list view. Set to `true` to remove access to the participant list, useful for simplified interfaces.
+
+
+
+```swift
+.hideParticipantListButton(true)
+```
+
+
+```objectivec
+[builder hideParticipantListButton:YES]
+```
+
+
+
+| Parameter | Type | Default |
+|-----------|------|---------|
+| `hidden` | Bool | false |
+
+### Hide Change Layout Button
+
+**Method:** `hideChangeLayoutButton(_ hidden: Bool)`
+
+Hides the button that allows switching between different layout modes (tile, spotlight, sidebar). Set to `true` to lock the layout to the initial setting.
+
+
+
+```swift
+.hideChangeLayoutButton(true)
+```
+
+
+```objectivec
+[builder hideChangeLayoutButton:YES]
+```
+
+
+
+| Parameter | Type | Default |
+|-----------|------|---------|
+| `hidden` | Bool | false |
+
+### Hide Chat Button
+
+**Method:** `hideChatButton(_ hidden: Bool)`
+
+Hides the button that opens the in-call chat interface. Set to `false` to show the chat button, enabling text communication during calls.
+
+
+
+```swift
+.hideChatButton(false)
+```
+
+
+```objectivec
+[builder hideChatButton:NO]
+```
+
+
+
+| Parameter | Type | Default |
+|-----------|------|---------|
+| `hidden` | Bool | true |
+
+
+| Enum | Value | Description |
+|------|-------|-------------|
+| `CallType` | `.video` | Video call with camera enabled |
+| | `.audio` | Audio-only call |
+| `LayoutType` | `.tile` | Grid layout showing all participants equally |
+| | `.spotlight` | Focus on active speaker with others in sidebar |
+| | `.sidebar` | Main speaker with participants in a sidebar |
+| `AudioModeType` | `.speaker` | Device loudspeaker |
+| | `.earpiece` | Phone earpiece |
+| | `.bluetooth` | Connected Bluetooth device |
+| | `.headphones` | Wired headphones |
+| `CameraFacing` | `.FRONT` | Front-facing camera |
+| | `.BACK` | Rear camera |
+
diff --git a/calls/ios/setup.mdx b/calls/ios/setup.mdx
new file mode 100644
index 00000000..026a715e
--- /dev/null
+++ b/calls/ios/setup.mdx
@@ -0,0 +1,121 @@
+---
+title: "Setup"
+sidebarTitle: "Setup"
+---
+
+This guide walks you through installing the CometChat Calls SDK and initializing it in your iOS application.
+
+## Add the CometChat Dependency
+
+### Using CocoaPods
+
+Add the CometChat Calls SDK to your `Podfile`:
+
+```ruby
+platform :ios, '16.0'
+use_frameworks!
+
+target 'YourApp' do
+ pod 'CometChatCallsSDK', '~> 5.0.0'
+end
+```
+
+Then run:
+
+```bash
+pod install
+```
+
+### Using Swift Package Manager
+
+1. In Xcode, go to **File > Add Package Dependencies**
+2. Enter the repository URL: `https://github.com/cometchat/cometchat-calls-sdk-ios`
+3. Select the version and add to your target
+
+## Add Permissions
+
+Add the required permissions to your `Info.plist`:
+
+```xml
+NSCameraUsageDescription
+Camera access is required for video calls
+NSMicrophoneUsageDescription
+Microphone access is required for voice and video calls
+```
+
+
+iOS requires you to provide a description for why your app needs camera and microphone access. These descriptions are shown to users when requesting permissions.
+
+
+## Enable Background Modes
+
+For calls to continue when the app is in the background, enable the following background modes in your project's **Signing & Capabilities**:
+
+1. Select your target in Xcode
+2. Go to **Signing & Capabilities**
+3. Click **+ Capability** and add **Background Modes**
+4. Enable:
+ - **Audio, AirPlay, and Picture in Picture**
+ - **Voice over IP** (if using VoIP push notifications)
+
+## Initialize CometChat Calls
+
+The `init()` method initializes the SDK with your app credentials. Call this method once when your application starts, typically in your `AppDelegate` or app entry point.
+
+### CallAppSettings
+
+The `CallAppSettings` class configures the SDK initialization:
+
+| Parameter | Type | Required | Description |
+|-----------|------|----------|-------------|
+| `appId` | String | Yes | Your CometChat App ID |
+| `region` | String | Yes | Your app region (`us` or `eu`) |
+
+
+
+```swift
+import CometChatCallsSDK
+
+let appId = "APP_ID" // Replace with your App ID
+let region = "REGION" // Replace with your Region ("us" or "eu")
+
+let callAppSettings = CallAppSettingsBuilder()
+ .setAppId(appId)
+ .setRegion(region)
+ .build()
+
+CometChatCalls(callsAppSettings: callAppSettings, onSuccess: { message in
+ print("CometChat Calls SDK initialized successfully")
+}, onError: { error in
+ print("CometChat Calls SDK initialization failed: \(error?.errorDescription ?? "")")
+})
+```
+
+
+```objectivec
+@import CometChatCallsSDK;
+
+NSString *appId = @"APP_ID"; // Replace with your App ID
+NSString *region = @"REGION"; // Replace with your Region ("us" or "eu")
+
+CallAppSettings *callAppSettings = [[[CallAppSettingsBuilder alloc] init]
+ setAppId:appId]
+ setRegion:region]
+ build];
+
+[[CometChatCalls alloc] initWithCallsAppSettings:callAppSettings
+ onSuccess:^(NSString * message) {
+ NSLog(@"CometChat Calls SDK initialized successfully");
+ }
+ onError:^(CometChatCallException * error) {
+ NSLog(@"CometChat Calls SDK initialization failed: %@", error.errorDescription);
+ }];
+```
+
+
+
+| Parameter | Description |
+|-----------|-------------|
+| `callsAppSettings` | Configuration object with App ID and Region |
+| `onSuccess` | Closure called on successful initialization |
+| `onError` | Closure called if initialization fails |
diff --git a/calls/ios/share-invite.mdx b/calls/ios/share-invite.mdx
new file mode 100644
index 00000000..b5122804
--- /dev/null
+++ b/calls/ios/share-invite.mdx
@@ -0,0 +1,591 @@
+---
+title: "Share Invite"
+sidebarTitle: "Share Invite"
+---
+
+Enable participants to invite others to join a call by sharing a meeting link. The share invite button opens the system share sheet, allowing users to send the invite via any messaging app, email, or social media.
+
+## Overview
+
+The share invite feature:
+- Generates a shareable meeting link with session ID
+- Opens iOS's native share sheet
+- Works with any app that supports text sharing
+- Can be triggered from the default button or custom UI
+
+```mermaid
+flowchart LR
+ A[Share Button] --> B[Generate Link]
+ B --> C[Share Sheet]
+ C --> D[Messages]
+ C --> E[Mail]
+ C --> F[AirDrop]
+ C --> G[Other Apps]
+```
+
+## Prerequisites
+
+- CometChat Calls SDK integrated ([Setup](/calls/ios/setup))
+- Active call session ([Join Session](/calls/ios/join-session))
+
+---
+
+## Step 1: Enable Share Button
+
+Configure session settings to show the share invite button:
+
+
+
+```swift
+let sessionSettings = CometChatCalls.sessionSettingsBuilder
+ .hideShareInviteButton(false) // Show the share button
+ .build()
+```
+
+
+```objectivec
+SessionSettings *sessionSettings = [[[CometChatCalls sessionSettingsBuilder]
+ hideShareInviteButton:NO] // Show the share button
+ build];
+```
+
+
+
+---
+
+## Step 2: Handle Share Button Click
+
+Listen for the share button click using `ButtonClickListener`:
+
+
+
+```swift
+private func setupShareButtonListener() {
+ CallSession.shared.addButtonClickListener(self)
+}
+
+extension CallViewController: ButtonClickListener {
+
+ func onShareInviteButtonClicked() {
+ shareInviteLink()
+ }
+}
+```
+
+
+```objectivec
+- (void)setupShareButtonListener {
+ [[CallSession shared] addButtonClickListener:self];
+}
+
+- (void)onShareInviteButtonClicked {
+ [self shareInviteLink];
+}
+```
+
+
+
+---
+
+## Step 3: Generate and Share Link
+
+Create the meeting invite URL and open the share sheet:
+
+
+
+```swift
+private func shareInviteLink() {
+ let inviteUrl = generateInviteUrl(sessionId: sessionId, meetingName: meetingName)
+
+ let activityItems: [Any] = [
+ "Join my meeting: \(meetingName)",
+ inviteUrl
+ ]
+
+ let activityVC = UIActivityViewController(
+ activityItems: activityItems,
+ applicationActivities: nil
+ )
+
+ // For iPad
+ if let popover = activityVC.popoverPresentationController {
+ popover.sourceView = view
+ popover.sourceRect = CGRect(x: view.bounds.midX, y: view.bounds.midY, width: 0, height: 0)
+ popover.permittedArrowDirections = []
+ }
+
+ present(activityVC, animated: true)
+}
+
+private func generateInviteUrl(sessionId: String, meetingName: String) -> URL {
+ let encodedName = meetingName.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? meetingName
+
+ // Replace with your app's deep link or web URL
+ let urlString = "https://yourapp.com/join?sessionId=\(sessionId)&name=\(encodedName)"
+ return URL(string: urlString)!
+}
+```
+
+
+```objectivec
+- (void)shareInviteLink {
+ NSURL *inviteUrl = [self generateInviteUrlWithSessionId:self.sessionId meetingName:self.meetingName];
+
+ NSArray *activityItems = @[
+ [NSString stringWithFormat:@"Join my meeting: %@", self.meetingName],
+ inviteUrl
+ ];
+
+ UIActivityViewController *activityVC = [[UIActivityViewController alloc]
+ initWithActivityItems:activityItems
+ applicationActivities:nil];
+
+ // For iPad
+ if (activityVC.popoverPresentationController) {
+ activityVC.popoverPresentationController.sourceView = self.view;
+ activityVC.popoverPresentationController.sourceRect = CGRectMake(
+ CGRectGetMidX(self.view.bounds),
+ CGRectGetMidY(self.view.bounds),
+ 0, 0
+ );
+ activityVC.popoverPresentationController.permittedArrowDirections = 0;
+ }
+
+ [self presentViewController:activityVC animated:YES completion:nil];
+}
+
+- (NSURL *)generateInviteUrlWithSessionId:(NSString *)sessionId meetingName:(NSString *)meetingName {
+ NSString *encodedName = [meetingName stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]];
+
+ // Replace with your app's deep link or web URL
+ NSString *urlString = [NSString stringWithFormat:@"https://yourapp.com/join?sessionId=%@&name=%@", sessionId, encodedName];
+ return [NSURL URLWithString:urlString];
+}
+```
+
+
+
+---
+
+## Custom Share Message
+
+Customize the share message with more details:
+
+
+
+```swift
+private func shareInviteLink() {
+ let inviteUrl = generateInviteUrl(sessionId: sessionId, meetingName: meetingName)
+
+ let shareMessage = """
+ 📞 Join my meeting: \(meetingName)
+
+ Click the link below to join:
+ \(inviteUrl.absoluteString)
+
+ Meeting ID: \(sessionId)
+ """
+
+ let activityItems: [Any] = [shareMessage]
+
+ let activityVC = UIActivityViewController(
+ activityItems: activityItems,
+ applicationActivities: nil
+ )
+
+ // For iPad
+ if let popover = activityVC.popoverPresentationController {
+ popover.sourceView = view
+ popover.sourceRect = CGRect(x: view.bounds.midX, y: view.bounds.midY, width: 0, height: 0)
+ popover.permittedArrowDirections = []
+ }
+
+ present(activityVC, animated: true)
+}
+```
+
+
+```objectivec
+- (void)shareInviteLink {
+ NSURL *inviteUrl = [self generateInviteUrlWithSessionId:self.sessionId meetingName:self.meetingName];
+
+ NSString *shareMessage = [NSString stringWithFormat:
+ @"📞 Join my meeting: %@\n\n"
+ @"Click the link below to join:\n"
+ @"%@\n\n"
+ @"Meeting ID: %@",
+ self.meetingName, inviteUrl.absoluteString, self.sessionId];
+
+ NSArray *activityItems = @[shareMessage];
+
+ UIActivityViewController *activityVC = [[UIActivityViewController alloc]
+ initWithActivityItems:activityItems
+ applicationActivities:nil];
+
+ // For iPad
+ if (activityVC.popoverPresentationController) {
+ activityVC.popoverPresentationController.sourceView = self.view;
+ activityVC.popoverPresentationController.sourceRect = CGRectMake(
+ CGRectGetMidX(self.view.bounds),
+ CGRectGetMidY(self.view.bounds),
+ 0, 0
+ );
+ activityVC.popoverPresentationController.permittedArrowDirections = 0;
+ }
+
+ [self presentViewController:activityVC animated:YES completion:nil];
+}
+```
+
+
+
+---
+
+## Deep Link Handling
+
+To allow users to join directly from the shared link, implement deep link handling in your app.
+
+### Configure Universal Links
+
+Add Associated Domains capability in Xcode and configure your `apple-app-site-association` file on your server.
+
+### Handle Deep Link
+
+
+
+```swift
+class SceneDelegate: UIResponder, UIWindowSceneDelegate {
+
+ func scene(_ scene: UIScene, continue userActivity: NSUserActivity) {
+ guard userActivity.activityType == NSUserActivityTypeBrowsingWeb,
+ let url = userActivity.webpageURL else {
+ return
+ }
+
+ handleDeepLink(url: url)
+ }
+
+ func scene(_ scene: UIScene, openURLContexts URLContexts: Set) {
+ guard let url = URLContexts.first?.url else { return }
+ handleDeepLink(url: url)
+ }
+
+ private func handleDeepLink(url: URL) {
+ guard let components = URLComponents(url: url, resolvingAgainstBaseURL: true),
+ components.path == "/join" else {
+ return
+ }
+
+ let queryItems = components.queryItems ?? []
+ let sessionId = queryItems.first(where: { $0.name == "sessionId" })?.value
+ let meetingName = queryItems.first(where: { $0.name == "name" })?.value
+
+ guard let sessionId = sessionId else { return }
+
+ // Check if user is logged in
+ if CometChat.getLoggedInUser() != nil {
+ joinCall(sessionId: sessionId, meetingName: meetingName ?? "Meeting")
+ } else {
+ // Save params and redirect to login
+ saveJoinParams(sessionId: sessionId, meetingName: meetingName)
+ showLoginScreen()
+ }
+ }
+
+ private func joinCall(sessionId: String, meetingName: String) {
+ let callVC = CallViewController()
+ callVC.sessionId = sessionId
+ callVC.meetingName = meetingName
+ callVC.modalPresentationStyle = .fullScreen
+
+ window?.rootViewController?.present(callVC, animated: true)
+ }
+
+ private func saveJoinParams(sessionId: String, meetingName: String?) {
+ UserDefaults.standard.set(sessionId, forKey: "pendingSessionId")
+ UserDefaults.standard.set(meetingName, forKey: "pendingMeetingName")
+ }
+
+ private func showLoginScreen() {
+ // Navigate to login
+ }
+}
+```
+
+
+```objectivec
+- (void)scene:(UIScene *)scene continueUserActivity:(NSUserActivity *)userActivity {
+ if (![userActivity.activityType isEqualToString:NSUserActivityTypeBrowsingWeb]) {
+ return;
+ }
+
+ NSURL *url = userActivity.webpageURL;
+ if (url) {
+ [self handleDeepLink:url];
+ }
+}
+
+- (void)scene:(UIScene *)scene openURLContexts:(NSSet *)URLContexts {
+ UIOpenURLContext *context = URLContexts.anyObject;
+ if (context) {
+ [self handleDeepLink:context.URL];
+ }
+}
+
+- (void)handleDeepLink:(NSURL *)url {
+ NSURLComponents *components = [NSURLComponents componentsWithURL:url resolvingAgainstBaseURL:YES];
+
+ if (![components.path isEqualToString:@"/join"]) {
+ return;
+ }
+
+ NSString *sessionId = nil;
+ NSString *meetingName = nil;
+
+ for (NSURLQueryItem *item in components.queryItems) {
+ if ([item.name isEqualToString:@"sessionId"]) {
+ sessionId = item.value;
+ } else if ([item.name isEqualToString:@"name"]) {
+ meetingName = item.value;
+ }
+ }
+
+ if (!sessionId) return;
+
+ if ([CometChat getLoggedInUser] != nil) {
+ [self joinCallWithSessionId:sessionId meetingName:meetingName ?: @"Meeting"];
+ } else {
+ [self saveJoinParamsWithSessionId:sessionId meetingName:meetingName];
+ [self showLoginScreen];
+ }
+}
+
+- (void)joinCallWithSessionId:(NSString *)sessionId meetingName:(NSString *)meetingName {
+ CallViewController *callVC = [[CallViewController alloc] init];
+ callVC.sessionId = sessionId;
+ callVC.meetingName = meetingName;
+ callVC.modalPresentationStyle = UIModalPresentationFullScreen;
+
+ [self.window.rootViewController presentViewController:callVC animated:YES completion:nil];
+}
+```
+
+
+
+---
+
+## Custom Share Button
+
+If you want to use a custom share button instead of the default one, hide the default button and implement your own:
+
+
+
+```swift
+// Hide default share button
+let sessionSettings = CometChatCalls.sessionSettingsBuilder
+ .hideShareInviteButton(true)
+ .build()
+
+// Add your custom button
+customShareButton.addTarget(self, action: #selector(shareInviteLink), for: .touchUpInside)
+```
+
+
+```objectivec
+// Hide default share button
+SessionSettings *sessionSettings = [[[CometChatCalls sessionSettingsBuilder]
+ hideShareInviteButton:YES]
+ build];
+
+// Add your custom button
+[customShareButton addTarget:self action:@selector(shareInviteLink) forControlEvents:UIControlEventTouchUpInside];
+```
+
+
+
+---
+
+## Complete Example
+
+
+
+```swift
+class CallViewController: UIViewController {
+
+ var sessionId: String = ""
+ var meetingName: String = ""
+
+ private let callContainer = UIView()
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+
+ setupUI()
+ setupShareButtonListener()
+ joinCall()
+ }
+
+ private func setupUI() {
+ view.backgroundColor = .black
+
+ callContainer.translatesAutoresizingMaskIntoConstraints = false
+ view.addSubview(callContainer)
+
+ NSLayoutConstraint.activate([
+ callContainer.topAnchor.constraint(equalTo: view.topAnchor),
+ callContainer.leadingAnchor.constraint(equalTo: view.leadingAnchor),
+ callContainer.trailingAnchor.constraint(equalTo: view.trailingAnchor),
+ callContainer.bottomAnchor.constraint(equalTo: view.bottomAnchor)
+ ])
+ }
+
+ private func setupShareButtonListener() {
+ CallSession.shared.addButtonClickListener(self)
+ }
+
+ private func joinCall() {
+ let sessionSettings = CometChatCalls.sessionSettingsBuilder
+ .setTitle(meetingName)
+ .hideShareInviteButton(false)
+ .build()
+
+ CometChatCalls.joinSession(
+ sessionId: sessionId,
+ sessionSettings: sessionSettings,
+ view: callContainer
+ ) { session in
+ print("Joined call")
+ } onError: { error in
+ print("Join failed: \(error?.errorDescription ?? "")")
+ }
+ }
+
+ @objc private func shareInviteLink() {
+ let inviteUrl = generateInviteUrl(sessionId: sessionId, meetingName: meetingName)
+
+ let shareMessage = "📞 Join my meeting: \(meetingName)\n\n\(inviteUrl.absoluteString)"
+
+ let activityVC = UIActivityViewController(
+ activityItems: [shareMessage],
+ applicationActivities: nil
+ )
+
+ if let popover = activityVC.popoverPresentationController {
+ popover.sourceView = view
+ popover.sourceRect = CGRect(x: view.bounds.midX, y: view.bounds.midY, width: 0, height: 0)
+ popover.permittedArrowDirections = []
+ }
+
+ present(activityVC, animated: true)
+ }
+
+ private func generateInviteUrl(sessionId: String, meetingName: String) -> URL {
+ let encodedName = meetingName.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? meetingName
+ return URL(string: "https://yourapp.com/join?sessionId=\(sessionId)&name=\(encodedName)")!
+ }
+}
+
+extension CallViewController: ButtonClickListener {
+
+ func onShareInviteButtonClicked() {
+ shareInviteLink()
+ }
+}
+```
+
+
+```objectivec
+@interface CallViewController ()
+@property (nonatomic, strong) UIView *callContainer;
+@property (nonatomic, copy) NSString *sessionId;
+@property (nonatomic, copy) NSString *meetingName;
+@end
+
+@implementation CallViewController
+
+- (void)viewDidLoad {
+ [super viewDidLoad];
+
+ [self setupUI];
+ [self setupShareButtonListener];
+ [self joinCall];
+}
+
+- (void)setupUI {
+ self.view.backgroundColor = [UIColor blackColor];
+
+ self.callContainer = [[UIView alloc] init];
+ self.callContainer.translatesAutoresizingMaskIntoConstraints = NO;
+ [self.view addSubview:self.callContainer];
+
+ [NSLayoutConstraint activateConstraints:@[
+ [self.callContainer.topAnchor constraintEqualToAnchor:self.view.topAnchor],
+ [self.callContainer.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor],
+ [self.callContainer.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor],
+ [self.callContainer.bottomAnchor constraintEqualToAnchor:self.view.bottomAnchor]
+ ]];
+}
+
+- (void)setupShareButtonListener {
+ [[CallSession shared] addButtonClickListener:self];
+}
+
+- (void)joinCall {
+ SessionSettings *sessionSettings = [[[[CometChatCalls sessionSettingsBuilder]
+ setTitle:self.meetingName]
+ hideShareInviteButton:NO]
+ build];
+
+ [CometChatCalls joinSessionWithSessionId:self.sessionId
+ sessionSettings:sessionSettings
+ view:self.callContainer
+ onSuccess:^(CallSession *session) {
+ NSLog(@"Joined call");
+ } onError:^(CometChatException *error) {
+ NSLog(@"Join failed: %@", error.errorDescription);
+ }];
+}
+
+- (void)shareInviteLink {
+ NSURL *inviteUrl = [self generateInviteUrlWithSessionId:self.sessionId meetingName:self.meetingName];
+
+ NSString *shareMessage = [NSString stringWithFormat:@"📞 Join my meeting: %@\n\n%@", self.meetingName, inviteUrl.absoluteString];
+
+ UIActivityViewController *activityVC = [[UIActivityViewController alloc]
+ initWithActivityItems:@[shareMessage]
+ applicationActivities:nil];
+
+ if (activityVC.popoverPresentationController) {
+ activityVC.popoverPresentationController.sourceView = self.view;
+ activityVC.popoverPresentationController.sourceRect = CGRectMake(
+ CGRectGetMidX(self.view.bounds),
+ CGRectGetMidY(self.view.bounds),
+ 0, 0
+ );
+ activityVC.popoverPresentationController.permittedArrowDirections = 0;
+ }
+
+ [self presentViewController:activityVC animated:YES completion:nil];
+}
+
+- (NSURL *)generateInviteUrlWithSessionId:(NSString *)sessionId meetingName:(NSString *)meetingName {
+ NSString *encodedName = [meetingName stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]];
+ return [NSURL URLWithString:[NSString stringWithFormat:@"https://yourapp.com/join?sessionId=%@&name=%@", sessionId, encodedName]];
+}
+
+- (void)onShareInviteButtonClicked {
+ [self shareInviteLink];
+}
+
+@end
+```
+
+
+
+---
+
+## Related Documentation
+
+- [Events](/calls/ios/events) - Button click events
+- [Session Settings](/calls/ios/session-settings) - Configure share button visibility
+- [Join Session](/calls/ios/join-session) - Join a call session
diff --git a/calls/ios/voip-calling.mdx b/calls/ios/voip-calling.mdx
new file mode 100644
index 00000000..24d8eff4
--- /dev/null
+++ b/calls/ios/voip-calling.mdx
@@ -0,0 +1,663 @@
+---
+title: "VoIP Calling"
+sidebarTitle: "VoIP Calling"
+---
+
+Implement native VoIP calling that works when your app is in the background or killed. This guide shows how to integrate Apple's CallKit framework with CometChat to display system call UI, handle calls from the lock screen, and provide a native calling experience.
+
+## Overview
+
+VoIP calling differs from [basic in-app ringing](/calls/ios/ringing) by leveraging Apple's CallKit to:
+- Show incoming calls on lock screen with system UI
+- Handle calls when app is in background or killed
+- Integrate with CarPlay, Bluetooth, and other audio accessories
+- Provide consistent call experience across iOS devices
+
+```mermaid
+flowchart TB
+ subgraph "Incoming Call Flow"
+ A[Push Notification] --> B[PushKit]
+ B --> C{App State?}
+ C -->|Background/Killed| D[CallKit]
+ C -->|Foreground| E[CometChat CallListener]
+ D --> F[System Call UI]
+ E --> G[In-App Call UI]
+ F --> H{User Action}
+ G --> H
+ H -->|Accept| I[CometChat.acceptCall]
+ H -->|Reject| J[CometChat.rejectCall]
+ I --> K[CometChatCalls.joinSession]
+ end
+```
+
+## Prerequisites
+
+Before implementing VoIP calling, ensure you have:
+
+- [CometChat Chat SDK](/sdk/ios/overview) and [Calls SDK](/calls/ios/setup) integrated
+- Apple Push Notification service (APNs) VoIP certificate configured
+- [Push notifications enabled](/notifications/push-integration) in CometChat Dashboard
+- iOS 10.0+ for CallKit support
+
+
+This documentation builds on the [Ringing](/calls/ios/ringing) functionality. Make sure you understand basic call signaling before implementing VoIP.
+
+
+---
+
+## Architecture Overview
+
+The VoIP implementation consists of several components working together:
+
+| Component | Purpose |
+|-----------|---------|
+| `PushKit` | Receives VoIP push notifications when app is in background |
+| `CXProvider` | CallKit provider that manages call state with the system |
+| `CXCallController` | Requests call actions (start, end, hold) from the system |
+| `CallManager` | Your custom class that coordinates between CometChat and CallKit |
+
+---
+
+## Step 1: Configure Push Notifications
+
+VoIP push notifications are essential for receiving incoming calls when your app is not in the foreground.
+
+
+### 1.1 Enable VoIP Capability
+
+In Xcode, add the following capabilities to your app:
+1. Go to your target's "Signing & Capabilities" tab
+2. Add "Push Notifications" capability
+3. Add "Background Modes" capability and enable:
+ - Voice over IP
+ - Background fetch
+ - Remote notifications
+
+### 1.2 Register for VoIP Push
+
+
+
+```swift
+import PushKit
+
+class AppDelegate: UIResponder, UIApplicationDelegate {
+
+ var voipRegistry: PKPushRegistry!
+
+ func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
+
+ // Initialize CometChat
+ // CometChat.init(...)
+
+ // Register for VoIP push notifications
+ registerForVoIPPush()
+
+ return true
+ }
+
+ private func registerForVoIPPush() {
+ voipRegistry = PKPushRegistry(queue: DispatchQueue.main)
+ voipRegistry.delegate = self
+ voipRegistry.desiredPushTypes = [.voIP]
+ }
+}
+```
+
+
+```objectivec
+#import
+
+@interface AppDelegate ()
+@property (nonatomic, strong) PKPushRegistry *voipRegistry;
+@end
+
+@implementation AppDelegate
+
+- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
+
+ // Initialize CometChat
+ // [CometChat init:...]
+
+ // Register for VoIP push notifications
+ [self registerForVoIPPush];
+
+ return YES;
+}
+
+- (void)registerForVoIPPush {
+ self.voipRegistry = [[PKPushRegistry alloc] initWithQueue:dispatch_get_main_queue()];
+ self.voipRegistry.delegate = self;
+ self.voipRegistry.desiredPushTypes = [NSSet setWithObject:PKPushTypeVoIP];
+}
+
+@end
+```
+
+
+
+### 1.3 Handle VoIP Push Registration
+
+
+
+```swift
+extension AppDelegate: PKPushRegistryDelegate {
+
+ func pushRegistry(_ registry: PKPushRegistry, didUpdate pushCredentials: PKPushCredentials, for type: PKPushType) {
+ let token = pushCredentials.token.map { String(format: "%02.2hhx", $0) }.joined()
+
+ // Register VoIP token with CometChat
+ CometChat.registerTokenForPushNotification(token: token) { success in
+ print("VoIP token registered successfully")
+ } onError: { error in
+ print("VoIP token registration failed: \(error?.errorDescription ?? "")")
+ }
+ }
+
+ func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, for type: PKPushType, completion: @escaping () -> Void) {
+
+ guard type == .voIP else {
+ completion()
+ return
+ }
+
+ // Extract call data from payload
+ let data = payload.dictionaryPayload
+ guard let sessionId = data["sessionId"] as? String,
+ let callerName = data["senderName"] as? String,
+ let callerUid = data["senderUid"] as? String else {
+ completion()
+ return
+ }
+
+ let callType = data["callType"] as? String ?? "video"
+ let hasVideo = callType == "video"
+
+ // Report incoming call to CallKit
+ CallManager.shared.reportIncomingCall(
+ sessionId: sessionId,
+ callerName: callerName,
+ callerUid: callerUid,
+ hasVideo: hasVideo
+ ) { error in
+ completion()
+ }
+ }
+}
+```
+
+
+```objectivec
+- (void)pushRegistry:(PKPushRegistry *)registry didUpdatePushCredentials:(PKPushCredentials *)pushCredentials forType:(PKPushType)type {
+ NSMutableString *token = [NSMutableString string];
+ const unsigned char *data = pushCredentials.token.bytes;
+ for (NSUInteger i = 0; i < pushCredentials.token.length; i++) {
+ [token appendFormat:@"%02.2hhx", data[i]];
+ }
+
+ // Register VoIP token with CometChat
+ [CometChat registerTokenForPushNotificationWithToken:token onSuccess:^(NSString * success) {
+ NSLog(@"VoIP token registered successfully");
+ } onError:^(CometChatException * error) {
+ NSLog(@"VoIP token registration failed: %@", error.errorDescription);
+ }];
+}
+
+- (void)pushRegistry:(PKPushRegistry *)registry didReceiveIncomingPushWithPayload:(PKPushPayload *)payload forType:(PKPushType)type withCompletionHandler:(void (^)(void))completion {
+
+ if (![type isEqualToString:PKPushTypeVoIP]) {
+ completion();
+ return;
+ }
+
+ // Extract call data from payload
+ NSDictionary *data = payload.dictionaryPayload;
+ NSString *sessionId = data[@"sessionId"];
+ NSString *callerName = data[@"senderName"];
+ NSString *callerUid = data[@"senderUid"];
+
+ if (!sessionId || !callerName || !callerUid) {
+ completion();
+ return;
+ }
+
+ NSString *callType = data[@"callType"] ?: @"video";
+ BOOL hasVideo = [callType isEqualToString:@"video"];
+
+ // Report incoming call to CallKit
+ [[CallManager shared] reportIncomingCallWithSessionId:sessionId
+ callerName:callerName
+ callerUid:callerUid
+ hasVideo:hasVideo
+ completion:^(NSError *error) {
+ completion();
+ }];
+}
+```
+
+
+
+---
+
+## Step 2: Create CallManager
+
+The CallManager coordinates between CometChat and CallKit:
+
+
+
+```swift
+import CallKit
+
+class CallManager: NSObject {
+
+ static let shared = CallManager()
+
+ private let provider: CXProvider
+ private let callController = CXCallController()
+
+ private var activeCallUUID: UUID?
+ private var activeSessionId: String?
+ private var activeCallerUid: String?
+
+ private override init() {
+ let configuration = CXProviderConfiguration()
+ configuration.supportsVideo = true
+ configuration.maximumCallsPerCallGroup = 1
+ configuration.supportedHandleTypes = [.generic]
+ configuration.iconTemplateImageData = UIImage(named: "CallIcon")?.pngData()
+
+ provider = CXProvider(configuration: configuration)
+
+ super.init()
+ provider.setDelegate(self, queue: nil)
+ }
+
+ func reportIncomingCall(sessionId: String, callerName: String, callerUid: String, hasVideo: Bool, completion: @escaping (Error?) -> Void) {
+ let uuid = UUID()
+ activeCallUUID = uuid
+ activeSessionId = sessionId
+ activeCallerUid = callerUid
+
+ let update = CXCallUpdate()
+ update.remoteHandle = CXHandle(type: .generic, value: callerUid)
+ update.localizedCallerName = callerName
+ update.hasVideo = hasVideo
+ update.supportsGrouping = false
+ update.supportsUngrouping = false
+ update.supportsHolding = true
+ update.supportsDTMF = false
+
+ provider.reportNewIncomingCall(with: uuid, update: update) { error in
+ if let error = error {
+ print("Failed to report incoming call: \(error.localizedDescription)")
+ self.activeCallUUID = nil
+ self.activeSessionId = nil
+ }
+ completion(error)
+ }
+ }
+
+ func startOutgoingCall(sessionId: String, receiverName: String, receiverUid: String, hasVideo: Bool) {
+ let uuid = UUID()
+ activeCallUUID = uuid
+ activeSessionId = sessionId
+
+ let handle = CXHandle(type: .generic, value: receiverUid)
+ let startCallAction = CXStartCallAction(call: uuid, handle: handle)
+ startCallAction.isVideo = hasVideo
+ startCallAction.contactIdentifier = receiverName
+
+ let transaction = CXTransaction(action: startCallAction)
+ callController.request(transaction) { error in
+ if let error = error {
+ print("Failed to start call: \(error.localizedDescription)")
+ }
+ }
+ }
+
+ func endCall() {
+ guard let uuid = activeCallUUID else { return }
+
+ let endCallAction = CXEndCallAction(call: uuid)
+ let transaction = CXTransaction(action: endCallAction)
+
+ callController.request(transaction) { error in
+ if let error = error {
+ print("Failed to end call: \(error.localizedDescription)")
+ }
+ }
+ }
+
+ func reportCallEnded(reason: CXCallEndedReason) {
+ guard let uuid = activeCallUUID else { return }
+ provider.reportCall(with: uuid, endedAt: Date(), reason: reason)
+ activeCallUUID = nil
+ activeSessionId = nil
+ }
+
+ func reportCallConnected() {
+ guard let uuid = activeCallUUID else { return }
+ provider.reportOutgoingCall(with: uuid, connectedAt: Date())
+ }
+}
+```
+
+
+```objectivec
+#import
+
+@interface CallManager : NSObject
+@property (class, nonatomic, readonly) CallManager *shared;
+- (void)reportIncomingCallWithSessionId:(NSString *)sessionId
+ callerName:(NSString *)callerName
+ callerUid:(NSString *)callerUid
+ hasVideo:(BOOL)hasVideo
+ completion:(void (^)(NSError *))completion;
+- (void)startOutgoingCallWithSessionId:(NSString *)sessionId
+ receiverName:(NSString *)receiverName
+ receiverUid:(NSString *)receiverUid
+ hasVideo:(BOOL)hasVideo;
+- (void)endCall;
+- (void)reportCallEndedWithReason:(CXCallEndedReason)reason;
+- (void)reportCallConnected;
+@end
+
+@implementation CallManager {
+ CXProvider *_provider;
+ CXCallController *_callController;
+ NSUUID *_activeCallUUID;
+ NSString *_activeSessionId;
+ NSString *_activeCallerUid;
+}
+
+static CallManager *_sharedInstance = nil;
+
++ (CallManager *)shared {
+ static dispatch_once_t onceToken;
+ dispatch_once(&onceToken, ^{
+ _sharedInstance = [[CallManager alloc] init];
+ });
+ return _sharedInstance;
+}
+
+- (instancetype)init {
+ self = [super init];
+ if (self) {
+ CXProviderConfiguration *configuration = [[CXProviderConfiguration alloc] init];
+ configuration.supportsVideo = YES;
+ configuration.maximumCallsPerCallGroup = 1;
+ configuration.supportedHandleTypes = [NSSet setWithObject:@(CXHandleTypeGeneric)];
+
+ _provider = [[CXProvider alloc] initWithConfiguration:configuration];
+ [_provider setDelegate:self queue:nil];
+ _callController = [[CXCallController alloc] init];
+ }
+ return self;
+}
+
+- (void)reportIncomingCallWithSessionId:(NSString *)sessionId
+ callerName:(NSString *)callerName
+ callerUid:(NSString *)callerUid
+ hasVideo:(BOOL)hasVideo
+ completion:(void (^)(NSError *))completion {
+ NSUUID *uuid = [NSUUID UUID];
+ _activeCallUUID = uuid;
+ _activeSessionId = sessionId;
+ _activeCallerUid = callerUid;
+
+ CXCallUpdate *update = [[CXCallUpdate alloc] init];
+ update.remoteHandle = [[CXHandle alloc] initWithType:CXHandleTypeGeneric value:callerUid];
+ update.localizedCallerName = callerName;
+ update.hasVideo = hasVideo;
+ update.supportsGrouping = NO;
+ update.supportsUngrouping = NO;
+ update.supportsHolding = YES;
+ update.supportsDTMF = NO;
+
+ [_provider reportNewIncomingCallWithUUID:uuid update:update completion:^(NSError * _Nullable error) {
+ if (error) {
+ NSLog(@"Failed to report incoming call: %@", error.localizedDescription);
+ self->_activeCallUUID = nil;
+ self->_activeSessionId = nil;
+ }
+ completion(error);
+ }];
+}
+
+@end
+```
+
+
+
+---
+
+## Step 3: Implement CXProviderDelegate
+
+Handle CallKit callbacks for user actions:
+
+
+
+```swift
+extension CallManager: CXProviderDelegate {
+
+ func providerDidReset(_ provider: CXProvider) {
+ // Clean up any active calls
+ activeCallUUID = nil
+ activeSessionId = nil
+ }
+
+ func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) {
+ // User tapped "Answer" on the call UI
+ guard let sessionId = activeSessionId else {
+ action.fail()
+ return
+ }
+
+ // Accept the call via CometChat
+ CometChat.acceptCall(sessionID: sessionId) { call in
+ action.fulfill()
+
+ // Launch call UI
+ DispatchQueue.main.async {
+ self.launchCallViewController(sessionId: sessionId)
+ }
+ } onError: { error in
+ print("Failed to accept call: \(error?.errorDescription ?? "")")
+ action.fail()
+ }
+ }
+
+ func provider(_ provider: CXProvider, perform action: CXEndCallAction) {
+ // User tapped "Decline" or "End Call"
+ guard let sessionId = activeSessionId else {
+ action.fulfill()
+ return
+ }
+
+ // Reject or end the call via CometChat
+ CometChat.rejectCall(sessionID: sessionId, status: .rejected) { call in
+ action.fulfill()
+ } onError: { error in
+ action.fulfill()
+ }
+
+ activeCallUUID = nil
+ activeSessionId = nil
+ }
+
+ func provider(_ provider: CXProvider, perform action: CXSetMutedCallAction) {
+ if action.isMuted {
+ CallSession.shared.muteAudio()
+ } else {
+ CallSession.shared.unMuteAudio()
+ }
+ action.fulfill()
+ }
+
+ func provider(_ provider: CXProvider, perform action: CXSetHeldCallAction) {
+ // Handle hold/unhold if needed
+ action.fulfill()
+ }
+
+ func provider(_ provider: CXProvider, didActivate audioSession: AVAudioSession) {
+ // Configure audio session for call
+ do {
+ try audioSession.setCategory(.playAndRecord, mode: .voiceChat)
+ try audioSession.setActive(true)
+ } catch {
+ print("Failed to configure audio session: \(error)")
+ }
+ }
+
+ func provider(_ provider: CXProvider, didDeactivate audioSession: AVAudioSession) {
+ // Clean up audio session
+ do {
+ try audioSession.setActive(false)
+ } catch {
+ print("Failed to deactivate audio session: \(error)")
+ }
+ }
+
+ private func launchCallViewController(sessionId: String) {
+ guard let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
+ let rootViewController = windowScene.windows.first?.rootViewController else {
+ return
+ }
+
+ let callVC = CallViewController()
+ callVC.sessionId = sessionId
+ callVC.modalPresentationStyle = .fullScreen
+
+ rootViewController.present(callVC, animated: true)
+ }
+}
+```
+
+
+```objectivec
+- (void)providerDidReset:(CXProvider *)provider {
+ _activeCallUUID = nil;
+ _activeSessionId = nil;
+}
+
+- (void)provider:(CXProvider *)provider performAnswerCallAction:(CXAnswerCallAction *)action {
+ if (!_activeSessionId) {
+ [action fail];
+ return;
+ }
+
+ NSString *sessionId = _activeSessionId;
+
+ [CometChat acceptCallWithSessionID:sessionId onSuccess:^(Call * call) {
+ [action fulfill];
+
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [self launchCallViewControllerWithSessionId:sessionId];
+ });
+ } onError:^(CometChatException * error) {
+ NSLog(@"Failed to accept call: %@", error.errorDescription);
+ [action fail];
+ }];
+}
+
+- (void)provider:(CXProvider *)provider performEndCallAction:(CXEndCallAction *)action {
+ if (!_activeSessionId) {
+ [action fulfill];
+ return;
+ }
+
+ [CometChat rejectCallWithSessionID:_activeSessionId status:CometChatCallStatusRejected onSuccess:^(Call * call) {
+ [action fulfill];
+ } onError:^(CometChatException * error) {
+ [action fulfill];
+ }];
+
+ _activeCallUUID = nil;
+ _activeSessionId = nil;
+}
+
+- (void)provider:(CXProvider *)provider performSetMutedCallAction:(CXSetMutedCallAction *)action {
+ if (action.isMuted) {
+ [[CallSession shared] muteAudio];
+ } else {
+ [[CallSession shared] unMuteAudio];
+ }
+ [action fulfill];
+}
+```
+
+
+
+---
+
+## Step 4: Handle Call End Events
+
+Update CallKit when the call ends from the remote side:
+
+
+
+```swift
+class CallViewController: UIViewController, SessionStatusListener {
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+ CallSession.shared.addSessionStatusListener(self)
+ }
+
+ func onSessionLeft() {
+ CallManager.shared.reportCallEnded(reason: .remoteEnded)
+ dismiss(animated: true)
+ }
+
+ func onConnectionClosed() {
+ CallManager.shared.reportCallEnded(reason: .failed)
+ dismiss(animated: true)
+ }
+
+ func onSessionJoined() {
+ CallManager.shared.reportCallConnected()
+ }
+
+ func onSessionTimedOut() {
+ CallManager.shared.reportCallEnded(reason: .unanswered)
+ }
+
+ func onConnectionLost() {}
+ func onConnectionRestored() {}
+}
+```
+
+
+```objectivec
+- (void)viewDidLoad {
+ [super viewDidLoad];
+ [[CallSession shared] addSessionStatusListener:self];
+}
+
+- (void)onSessionLeft {
+ [[CallManager shared] reportCallEndedWithReason:CXCallEndedReasonRemoteEnded];
+ [self dismissViewControllerAnimated:YES completion:nil];
+}
+
+- (void)onConnectionClosed {
+ [[CallManager shared] reportCallEndedWithReason:CXCallEndedReasonFailed];
+ [self dismissViewControllerAnimated:YES completion:nil];
+}
+
+- (void)onSessionJoined {
+ [[CallManager shared] reportCallConnected];
+}
+
+- (void)onSessionTimedOut {
+ [[CallManager shared] reportCallEndedWithReason:CXCallEndedReasonUnanswered];
+}
+```
+
+
+
+---
+
+## Related Documentation
+
+- [Ringing](/calls/ios/ringing) - Basic call signaling
+- [Background Handling](/calls/ios/background-handling) - Keep calls alive in background
+- [Events](/calls/ios/events) - Session status events
diff --git a/calls/javascript/actions.mdx b/calls/javascript/actions.mdx
new file mode 100644
index 00000000..116f9523
--- /dev/null
+++ b/calls/javascript/actions.mdx
@@ -0,0 +1,362 @@
+---
+title: "Actions"
+sidebarTitle: "Actions"
+---
+
+Use call actions to create your own custom controls or trigger call functionality dynamically based on your use case. All actions are called on the `CometChatCalls` class during an active call session.
+
+## Audio Controls
+
+### Mute Audio
+
+Mutes your local microphone, stopping audio transmission to other participants.
+
+```javascript
+CometChatCalls.muteAudio();
+```
+
+### Unmute Audio
+
+Unmutes your local microphone, resuming audio transmission.
+
+```javascript
+CometChatCalls.unMuteAudio();
+```
+
+## Video Controls
+
+### Pause Video
+
+Turns off your local camera, stopping video transmission. Other participants see your avatar.
+
+```javascript
+CometChatCalls.pauseVideo();
+```
+
+### Resume Video
+
+Turns on your local camera, resuming video transmission.
+
+```javascript
+CometChatCalls.resumeVideo();
+```
+
+### Switch Camera
+
+Toggles between front and back cameras (on supported devices).
+
+```javascript
+CometChatCalls.switchCamera();
+```
+
+## Screen Sharing
+
+### Start Screen Sharing
+
+Begins sharing your screen with other participants. The browser will prompt the user to select which screen, window, or tab to share.
+
+```javascript
+CometChatCalls.startScreenSharing();
+```
+
+### Stop Screen Sharing
+
+Stops the current screen share.
+
+```javascript
+CometChatCalls.stopScreenSharing();
+```
+
+## Recording
+
+### Start Recording
+
+Begins server-side recording of the call. All participants are notified.
+
+```javascript
+CometChatCalls.startRecording();
+```
+
+
+Recording requires the feature to be enabled for your CometChat app.
+
+
+### Stop Recording
+
+Stops the current recording. The recording is saved and accessible via the dashboard.
+
+```javascript
+CometChatCalls.stopRecording();
+```
+
+## Participant Management
+
+### Mute Participant
+
+Mutes a specific participant's audio. This is a moderator action.
+
+```javascript
+CometChatCalls.muteParticipant(participantId);
+```
+
+| Parameter | Type | Description |
+|-----------|------|-------------|
+| `participantId` | String | The participant's unique identifier |
+
+### Pause Participant Video
+
+Pauses a specific participant's video. This is a moderator action.
+
+```javascript
+CometChatCalls.pauseParticipantVideo(participantId);
+```
+
+| Parameter | Type | Description |
+|-----------|------|-------------|
+| `participantId` | String | The participant's unique identifier |
+
+### Pin Participant
+
+Pins a participant to keep them prominently displayed regardless of who is speaking.
+
+```javascript
+CometChatCalls.pinParticipant(participantId, type);
+```
+
+| Parameter | Type | Description |
+|-----------|------|-------------|
+| `participantId` | String | The participant's unique identifier |
+| `type` | String | The participant type |
+
+### Unpin Participant
+
+Removes the pin, returning to automatic speaker highlighting.
+
+```javascript
+CometChatCalls.unpinParticipant();
+```
+
+## Layout
+
+### Set Layout
+
+Changes the call layout. Each participant can choose their own layout independently.
+
+```javascript
+CometChatCalls.setLayout("TILE");
+CometChatCalls.setLayout("SIDEBAR");
+CometChatCalls.setLayout("SPOTLIGHT");
+```
+
+| Value | Description |
+|-------|-------------|
+| `TILE` | Grid layout with equally-sized tiles |
+| `SIDEBAR` | Main speaker with participants in a sidebar |
+| `SPOTLIGHT` | Large view for active speaker, small tiles for others |
+
+## Raise Hand
+
+### Raise Hand
+
+Shows a hand-raised indicator to get attention from other participants.
+
+```javascript
+CometChatCalls.raiseHand();
+```
+
+### Lower Hand
+
+Removes the hand-raised indicator.
+
+```javascript
+CometChatCalls.lowerHand();
+```
+
+## Session Control
+
+### Leave Session
+
+Ends your participation and disconnects gracefully. The call continues for other participants.
+
+```javascript
+CometChatCalls.leaveSession();
+```
+
+## UI Controls
+
+### Show Settings Dialog
+
+Opens the settings dialog for audio/video device selection.
+
+```javascript
+CometChatCalls.showSettingsDialog();
+```
+
+### Hide Settings Dialog
+
+Closes the settings dialog.
+
+```javascript
+CometChatCalls.hideSettingsDialog();
+```
+
+### Show Virtual Background Dialog
+
+Opens the virtual background settings dialog.
+
+```javascript
+CometChatCalls.showVirtualBackgroundDialog();
+```
+
+### Hide Virtual Background Dialog
+
+Closes the virtual background dialog.
+
+```javascript
+CometChatCalls.hideVirtualBackgroundDialog();
+```
+
+### Set Chat Button Unread Count
+
+Updates the badge count on the chat button. Pass 0 to hide the badge.
+
+```javascript
+CometChatCalls.setChatButtonUnreadCount(5);
+```
+
+| Parameter | Type | Description |
+|-----------|------|-------------|
+| `count` | Number | The unread message count to display |
+
+## Virtual Background
+
+### Clear Virtual Background
+
+Removes any applied virtual background.
+
+```javascript
+CometChatCalls.clearVirtualBackground();
+```
+
+### Set Virtual Background Blur Level
+
+Applies a blur effect to the background with the specified intensity.
+
+```javascript
+CometChatCalls.setVirtualBackgroundBlurLevel(10);
+```
+
+| Parameter | Type | Description |
+|-----------|------|-------------|
+| `blurLevel` | Number | The blur intensity level |
+
+### Set Virtual Background Image
+
+Sets a custom image as the virtual background.
+
+```javascript
+CometChatCalls.setVirtualBackgroundImage("https://example.com/background.jpg");
+```
+
+| Parameter | Type | Description |
+|-----------|------|-------------|
+| `imageUrl` | String | URL of the background image |
+
+## Device Management
+
+### Get Audio Input Devices
+
+Returns a list of available audio input devices (microphones).
+
+```javascript
+const audioInputDevices = CometChatCalls.getAudioInputDevices();
+console.log(audioInputDevices);
+```
+
+### Get Audio Output Devices
+
+Returns a list of available audio output devices (speakers).
+
+```javascript
+const audioOutputDevices = CometChatCalls.getAudioOutputDevices();
+console.log(audioOutputDevices);
+```
+
+### Get Video Input Devices
+
+Returns a list of available video input devices (cameras).
+
+```javascript
+const videoInputDevices = CometChatCalls.getVideoInputDevices();
+console.log(videoInputDevices);
+```
+
+### Get Current Audio Input Device
+
+Returns the currently selected audio input device.
+
+```javascript
+const currentAudioInput = CometChatCalls.getCurrentAudioInputDevice();
+console.log(currentAudioInput);
+```
+
+### Get Current Audio Output Device
+
+Returns the currently selected audio output device.
+
+```javascript
+const currentAudioOutput = CometChatCalls.getCurrentAudioOutputDevice();
+console.log(currentAudioOutput);
+```
+
+### Get Current Video Input Device
+
+Returns the currently selected video input device.
+
+```javascript
+const currentVideoInput = CometChatCalls.getCurrentVideoInputDevice();
+console.log(currentVideoInput);
+```
+
+### Set Audio Input Device
+
+Sets the audio input device by device ID.
+
+```javascript
+CometChatCalls.setAudioInputDevice(deviceId);
+```
+
+### Set Audio Output Device
+
+Sets the audio output device by device ID.
+
+```javascript
+CometChatCalls.setAudioOutputDevice(deviceId);
+```
+
+### Set Video Input Device
+
+Sets the video input device by device ID.
+
+```javascript
+CometChatCalls.setVideoInputDevice(deviceId);
+```
+
+## Picture-in-Picture
+
+### Enable Picture-in-Picture Layout
+
+Enables Picture-in-Picture mode for the call.
+
+```javascript
+CometChatCalls.enablePictureInPictureLayout();
+```
+
+### Disable Picture-in-Picture Layout
+
+Disables Picture-in-Picture mode.
+
+```javascript
+CometChatCalls.disablePictureInPictureLayout();
+```
+
diff --git a/calls/javascript/angular-integration.mdx b/calls/javascript/angular-integration.mdx
new file mode 100644
index 00000000..28b83405
--- /dev/null
+++ b/calls/javascript/angular-integration.mdx
@@ -0,0 +1,602 @@
+---
+title: "Angular Integration"
+sidebarTitle: "Angular"
+---
+
+This guide walks you through integrating the CometChat Calls SDK into an Angular application. By the end, you'll have a working video call implementation with proper service architecture and lifecycle management.
+
+## Prerequisites
+
+Before you begin, ensure you have:
+- A CometChat account with an app created ([Sign up](https://app.cometchat.com/signup))
+- Your App ID, Region, and API Key from the CometChat Dashboard
+- An Angular 14+ project
+- Node.js 16+ installed
+
+## Step 1: Install the SDK
+
+Install the CometChat Calls SDK package:
+
+```bash
+npm install @cometchat/calls-sdk-javascript
+```
+
+## Step 2: Create the Calls Service
+
+Create a service to manage SDK initialization, authentication, and call operations:
+
+```typescript
+// src/app/services/cometchat-calls.service.ts
+import { Injectable } from "@angular/core";
+import { CometChatCalls } from "@cometchat/calls-sdk-javascript";
+import { BehaviorSubject, Observable } from "rxjs";
+
+interface User {
+ uid: string;
+ name: string;
+ avatar?: string;
+}
+
+@Injectable({
+ providedIn: "root",
+})
+export class CometChatCallsService {
+ private readonly APP_ID = "YOUR_APP_ID"; // Replace with your App ID
+ private readonly REGION = "YOUR_REGION"; // Replace with your Region
+ private readonly API_KEY = "YOUR_API_KEY"; // Replace with your API Key
+
+ // Observable state
+ private isReadySubject = new BehaviorSubject(false);
+ private userSubject = new BehaviorSubject(null);
+ private errorSubject = new BehaviorSubject(null);
+
+ isReady$: Observable = this.isReadySubject.asObservable();
+ user$: Observable = this.userSubject.asObservable();
+ error$: Observable = this.errorSubject.asObservable();
+
+ /**
+ * Initialize the SDK and login the user.
+ * Call this once when your app starts.
+ */
+ async initAndLogin(uid: string): Promise {
+ if (this.isReadySubject.value) return;
+
+ try {
+ // Step 1: Initialize the SDK
+ const initResult = await CometChatCalls.init({
+ appId: this.APP_ID,
+ region: this.REGION,
+ });
+
+ if (!initResult.success) {
+ throw new Error("SDK initialization failed");
+ }
+
+ // Step 2: Check if already logged in
+ let loggedInUser = CometChatCalls.getLoggedInUser();
+
+ // Step 3: Login if not already logged in
+ if (!loggedInUser) {
+ loggedInUser = await CometChatCalls.login(uid, this.API_KEY);
+ }
+
+ this.userSubject.next(loggedInUser as User);
+ this.isReadySubject.next(true);
+ this.errorSubject.next(null);
+ } catch (err: any) {
+ console.error("CometChat Calls setup failed:", err);
+ this.errorSubject.next(err.message || "Setup failed");
+ }
+ }
+
+ /**
+ * Logout the current user.
+ */
+ async logout(): Promise {
+ try {
+ await CometChatCalls.logout();
+ this.userSubject.next(null);
+ this.isReadySubject.next(false);
+ } catch (err) {
+ console.error("Logout failed:", err);
+ }
+ }
+
+ /**
+ * Generate a call token for a session.
+ */
+ async generateToken(sessionId: string): Promise<{ token: string }> {
+ return CometChatCalls.generateToken(sessionId);
+ }
+
+ /**
+ * Join a call session.
+ */
+ async joinSession(
+ token: string,
+ settings: any,
+ container: HTMLElement
+ ): Promise {
+ return CometChatCalls.joinSession(token, settings, container);
+ }
+
+ /**
+ * Leave the current call session.
+ */
+ leaveSession(): void {
+ CometChatCalls.leaveSession();
+ }
+
+ /**
+ * Add an event listener.
+ * Returns an unsubscribe function.
+ */
+ addEventListener(event: string, callback: Function): () => void {
+ return CometChatCalls.addEventListener(event as any, callback as any);
+ }
+
+ // Audio controls
+ muteAudio(): void {
+ CometChatCalls.muteAudio();
+ }
+
+ unMuteAudio(): void {
+ CometChatCalls.unMuteAudio();
+ }
+
+ // Video controls
+ pauseVideo(): void {
+ CometChatCalls.pauseVideo();
+ }
+
+ resumeVideo(): void {
+ CometChatCalls.resumeVideo();
+ }
+}
+```
+
+## Step 3: Initialize in App Component
+
+Initialize the SDK when your app starts:
+
+```typescript
+// src/app/app.component.ts
+import { Component, OnInit } from "@angular/core";
+import { CometChatCallsService } from "./services/cometchat-calls.service";
+import { Observable } from "rxjs";
+
+@Component({
+ selector: "app-root",
+ template: `
+
+ Error: {{ error }}
+
+
+ Loading...
+
+
+ `,
+})
+export class AppComponent implements OnInit {
+ isReady$: Observable;
+ error$: Observable;
+
+ constructor(private callsService: CometChatCallsService) {
+ this.isReady$ = this.callsService.isReady$;
+ this.error$ = this.callsService.error$;
+ }
+
+ ngOnInit(): void {
+ // In a real app, get this from your authentication system
+ const currentUserId = "cometchat-uid-1";
+ this.callsService.initAndLogin(currentUserId);
+ }
+}
+```
+
+## Step 4: Create the Call Component
+
+Build a call component with proper lifecycle management:
+
+```typescript
+// src/app/components/call-screen/call-screen.component.ts
+import {
+ Component,
+ Input,
+ Output,
+ EventEmitter,
+ OnInit,
+ OnDestroy,
+ ViewChild,
+ ElementRef,
+} from "@angular/core";
+import { CometChatCallsService } from "../../services/cometchat-calls.service";
+
+@Component({
+ selector: "app-call-screen",
+ template: `
+
+
+
+
+
+
+ Joining call...
+
+
+
+
+
Error: {{ callError }}
+
Go Back
+
+
+
+
+
+ {{ isMuted ? "Unmute" : "Mute" }}
+
+
+ {{ isVideoOff ? "Start Video" : "Stop Video" }}
+
+
+ Leave Call
+
+
+
+ `,
+ styles: [
+ `
+ .call-screen {
+ display: flex;
+ flex-direction: column;
+ height: 100vh;
+ position: relative;
+ }
+ .call-container {
+ flex: 1;
+ background-color: #1a1a1a;
+ min-height: 400px;
+ }
+ .call-controls {
+ display: flex;
+ justify-content: center;
+ gap: 12px;
+ padding: 16px;
+ background-color: #2a2a2a;
+ }
+ .call-controls button {
+ padding: 12px 24px;
+ border: none;
+ border-radius: 8px;
+ font-size: 14px;
+ cursor: pointer;
+ }
+ .call-controls button.active {
+ background-color: #28a745;
+ color: white;
+ }
+ .call-controls button.muted {
+ background-color: #dc3545;
+ color: white;
+ }
+ .call-controls .leave-btn {
+ background-color: #dc3545;
+ color: white;
+ }
+ .call-overlay {
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ background: rgba(0, 0, 0, 0.8);
+ color: white;
+ padding: 20px;
+ border-radius: 8px;
+ text-align: center;
+ }
+ `,
+ ],
+})
+export class CallScreenComponent implements OnInit, OnDestroy {
+ @Input() sessionId!: string;
+ @Output() callEnded = new EventEmitter();
+ @ViewChild("callContainer", { static: true }) callContainer!: ElementRef;
+
+ isJoined = false;
+ isJoining = false;
+ isMuted = false;
+ isVideoOff = false;
+ callError: string | null = null;
+
+ private unsubscribers: (() => void)[] = [];
+
+ constructor(private callsService: CometChatCallsService) {}
+
+ ngOnInit(): void {
+ this.joinCall();
+ }
+
+ ngOnDestroy(): void {
+ this.cleanup();
+ }
+
+ /**
+ * Join the call session.
+ */
+ private async joinCall(): Promise {
+ this.isJoining = true;
+ this.callError = null;
+
+ try {
+ // Register event listeners before joining
+ this.unsubscribers = [
+ this.callsService.addEventListener("onSessionJoined", () => {
+ this.isJoined = true;
+ this.isJoining = false;
+ }),
+ this.callsService.addEventListener("onSessionLeft", () => {
+ this.isJoined = false;
+ this.callEnded.emit();
+ }),
+ this.callsService.addEventListener("onAudioMuted", () => {
+ this.isMuted = true;
+ }),
+ this.callsService.addEventListener("onAudioUnMuted", () => {
+ this.isMuted = false;
+ }),
+ this.callsService.addEventListener("onVideoPaused", () => {
+ this.isVideoOff = true;
+ }),
+ this.callsService.addEventListener("onVideoResumed", () => {
+ this.isVideoOff = false;
+ }),
+ ];
+
+ // Generate a call token for this session
+ const tokenResult = await this.callsService.generateToken(this.sessionId);
+
+ // Join the call session
+ const joinResult = await this.callsService.joinSession(
+ tokenResult.token,
+ {
+ sessionType: "VIDEO",
+ layout: "TILE",
+ startAudioMuted: false,
+ startVideoPaused: false,
+ },
+ this.callContainer.nativeElement
+ );
+
+ if (joinResult.error) {
+ throw new Error(joinResult.error.message);
+ }
+ } catch (err: any) {
+ console.error("Failed to join call:", err);
+ this.callError = err.message || "Failed to join call";
+ this.isJoining = false;
+ }
+ }
+
+ /**
+ * Toggle microphone mute state.
+ */
+ toggleAudio(): void {
+ if (this.isMuted) {
+ this.callsService.unMuteAudio();
+ } else {
+ this.callsService.muteAudio();
+ }
+ }
+
+ /**
+ * Toggle camera on/off state.
+ */
+ toggleVideo(): void {
+ if (this.isVideoOff) {
+ this.callsService.resumeVideo();
+ } else {
+ this.callsService.pauseVideo();
+ }
+ }
+
+ /**
+ * Leave the current call session.
+ */
+ leaveCall(): void {
+ this.callsService.leaveSession();
+ }
+
+ /**
+ * Cleanup event listeners.
+ */
+ private cleanup(): void {
+ this.unsubscribers.forEach((unsub) => unsub());
+ this.unsubscribers = [];
+ this.callsService.leaveSession();
+ }
+}
+```
+
+## Step 5: Create the Call Page
+
+Create a page component that manages the call flow:
+
+```typescript
+// src/app/pages/call-page/call-page.component.ts
+import { Component } from "@angular/core";
+import { CometChatCallsService } from "../../services/cometchat-calls.service";
+import { Observable } from "rxjs";
+
+@Component({
+ selector: "app-call-page",
+ template: `
+
+
+
+
CometChat Video Calls
+
Logged in as: {{ (user$ | async)?.name || (user$ | async)?.uid }}
+
+
+
+
+ Join Call
+
+
+
+
+ Share the same Session ID with others to join the same call.
+
+
+
+
+
+
+ `,
+ styles: [
+ `
+ .call-page {
+ min-height: 100vh;
+ }
+ .pre-call {
+ max-width: 400px;
+ margin: 0 auto;
+ padding: 40px 20px;
+ text-align: center;
+ }
+ .join-form {
+ margin-top: 30px;
+ }
+ .join-form input {
+ width: 100%;
+ padding: 12px;
+ margin-bottom: 12px;
+ border: 1px solid #ddd;
+ border-radius: 8px;
+ font-size: 16px;
+ box-sizing: border-box;
+ }
+ .join-form button {
+ width: 100%;
+ padding: 12px;
+ background-color: #6851d6;
+ color: white;
+ border: none;
+ border-radius: 8px;
+ font-size: 16px;
+ cursor: pointer;
+ }
+ .join-form button:disabled {
+ background-color: #ccc;
+ cursor: not-allowed;
+ }
+ .hint {
+ margin-top: 20px;
+ color: #666;
+ font-size: 14px;
+ }
+ `,
+ ],
+})
+export class CallPageComponent {
+ user$: Observable;
+ sessionId = "";
+ isInCall = false;
+
+ constructor(private callsService: CometChatCallsService) {
+ this.user$ = this.callsService.user$;
+ }
+
+ startCall(): void {
+ if (this.sessionId) {
+ this.isInCall = true;
+ }
+ }
+
+ endCall(): void {
+ this.isInCall = false;
+ }
+}
+```
+
+## Step 6: Module Configuration
+
+Add the components to your module:
+
+```typescript
+// src/app/app.module.ts
+import { NgModule } from "@angular/core";
+import { BrowserModule } from "@angular/platform-browser";
+import { FormsModule } from "@angular/forms";
+import { RouterModule } from "@angular/router";
+
+import { AppComponent } from "./app.component";
+import { CallScreenComponent } from "./components/call-screen/call-screen.component";
+import { CallPageComponent } from "./pages/call-page/call-page.component";
+
+@NgModule({
+ declarations: [AppComponent, CallScreenComponent, CallPageComponent],
+ imports: [
+ BrowserModule,
+ FormsModule,
+ RouterModule.forRoot([
+ { path: "", component: CallPageComponent },
+ ]),
+ ],
+ providers: [],
+ bootstrap: [AppComponent],
+})
+export class AppModule {}
+```
+
+## Standalone Components (Angular 14+)
+
+For standalone components without NgModules:
+
+```typescript
+// src/app/components/call-screen/call-screen.component.ts
+import { Component, Input, Output, EventEmitter, OnInit, OnDestroy, ViewChild, ElementRef } from "@angular/core";
+import { CommonModule } from "@angular/common";
+import { CometChatCalls } from "@cometchat/calls-sdk-javascript";
+
+@Component({
+ selector: "app-call-screen",
+ standalone: true,
+ imports: [CommonModule],
+ template: `...`, // Same template as above
+})
+export class CallScreenComponent implements OnInit, OnDestroy {
+ // Same implementation, but import CometChatCalls directly
+ // instead of using the service
+}
+```
+
+## Related Documentation
+
+For more detailed information on specific topics covered in this guide, refer to the main documentation:
+
+- [Setup](/calls/javascript/setup) - Detailed SDK installation and initialization
+- [Authentication](/calls/javascript/authentication) - Login methods and user management
+- [Session Settings](/calls/javascript/session-settings) - All available call configuration options
+- [Join Session](/calls/javascript/join-session) - Session joining and token generation
+- [Events](/calls/javascript/events) - Complete list of event listeners
+- [Actions](/calls/javascript/actions) - All available call control methods
+- [Call Layouts](/calls/javascript/call-layouts) - Layout options and customization
+- [Participant Management](/calls/javascript/participant-management) - Managing call participants
+
diff --git a/calls/javascript/authentication.mdx b/calls/javascript/authentication.mdx
new file mode 100644
index 00000000..71dc99b3
--- /dev/null
+++ b/calls/javascript/authentication.mdx
@@ -0,0 +1,153 @@
+---
+title: "Authentication"
+sidebarTitle: "Authentication"
+---
+
+Before users can make or receive calls, they must be authenticated with the CometChat Calls SDK. This guide covers the login and logout methods.
+
+
+**Sample Users**
+
+CometChat provides 5 test users: `cometchat-uid-1`, `cometchat-uid-2`, `cometchat-uid-3`, `cometchat-uid-4`, and `cometchat-uid-5`.
+
+
+## Check Login Status
+
+Before calling `login()`, check if a user is already logged in using `getLoggedInUser()`. The SDK maintains the session internally, so you only need to login once per user session.
+
+```javascript
+const loggedInUser = CometChatCalls.getLoggedInUser();
+
+if (loggedInUser) {
+ // User is already logged in
+ console.log("User already logged in:", loggedInUser.uid);
+} else {
+ // No user logged in, proceed with login
+}
+```
+
+The `getLoggedInUser()` method returns a user object if a user is logged in, or `null` if no session exists.
+
+## Login with UID and API Key
+
+This method is suitable for development and testing. For production apps, use [Auth Token login](#login-with-auth-token) instead.
+
+
+**Security Notice**
+
+Using the API Key directly in client code is not recommended for production. Use Auth Token authentication for enhanced security.
+
+
+```javascript
+const uid = "cometchat-uid-1"; // Replace with your user's UID
+const apiKey = "API_KEY"; // Replace with your API Key
+
+if (!CometChatCalls.getLoggedInUser()) {
+ try {
+ const user = await CometChatCalls.login(uid, apiKey);
+ console.log("Login successful:", user.uid);
+ } catch (error) {
+ console.error("Login failed:", error.errorDescription);
+ }
+} else {
+ // User already logged in
+}
+```
+
+| Parameter | Type | Description |
+|-----------|------|-------------|
+| `uid` | String | The unique identifier of the user to login |
+| `apiKey` | String | Your CometChat API Key |
+
+## Login with Auth Token
+
+This is the recommended authentication method for production applications. The Auth Token is generated server-side, keeping your API Key secure.
+
+### Auth Token Flow
+
+1. User authenticates with your backend
+2. Your backend calls the [CometChat Create Auth Token API](https://api-explorer.cometchat.com/reference/create-authtoken)
+3. Your backend returns the Auth Token to the client
+4. Client uses the Auth Token to login
+
+```javascript
+const authToken = "AUTH_TOKEN"; // Token received from your backend
+
+try {
+ const user = await CometChatCalls.loginWithAuthToken(authToken);
+ console.log("Login successful:", user.uid);
+} catch (error) {
+ console.error("Login failed:", error.errorDescription);
+}
+```
+
+| Parameter | Type | Description |
+|-----------|------|-------------|
+| `authToken` | String | Auth Token generated via CometChat API |
+
+## User Object
+
+On successful login, the method returns a user object containing user information:
+
+| Property | Type | Description |
+|----------|------|-------------|
+| `uid` | String | Unique identifier of the user |
+| `name` | String | Display name of the user |
+| `avatar` | String | URL of the user's avatar image |
+| `status` | String | User's online status |
+| `authToken` | String | The user's auth token |
+
+## Check User Login Status
+
+You can verify if a user is currently logged in:
+
+```javascript
+const isLoggedIn = CometChatCalls.isUserLoggedIn();
+
+if (isLoggedIn) {
+ // User is logged in
+} else {
+ // User is not logged in
+}
+```
+
+## Get User Auth Token
+
+Retrieve the auth token of the currently logged-in user:
+
+```javascript
+const authToken = CometChatCalls.getUserAuthToken();
+
+if (authToken) {
+ console.log("User auth token:", authToken);
+}
+```
+
+## Logout
+
+Call `logout()` when the user signs out of your application. This clears the local session and disconnects from CometChat services.
+
+```javascript
+try {
+ const message = await CometChatCalls.logout();
+ console.log("Logout successful:", message);
+} catch (error) {
+ console.error("Logout failed:", error.errorDescription);
+}
+```
+
+## Error Handling
+
+Common authentication errors:
+
+| Error Code | Description |
+|------------|-------------|
+| `ERROR_INVALID_UID` | The provided UID is empty or invalid |
+| `ERROR_UID_WITH_SPACE` | The UID contains spaces (not allowed) |
+| `ERROR_API_KEY_NOT_FOUND` | The API Key is missing or invalid |
+| `ERROR_BLANK_AUTHTOKEN` | The Auth Token is empty |
+| `ERROR_AUTHTOKEN_WITH_SPACE` | The Auth Token contains spaces (not allowed) |
+| `ERROR_LOGIN_IN_PROGRESS` | A login operation is already in progress |
+| `ERROR_SDK_NOT_INITIALIZED` | SDK not initialized - call `init()` first |
+| `ERROR_NO_USER_LOGGED_IN` | No user is currently logged in (for logout) |
+
diff --git a/calls/javascript/call-layouts.mdx b/calls/javascript/call-layouts.mdx
new file mode 100644
index 00000000..aa9b6c8a
--- /dev/null
+++ b/calls/javascript/call-layouts.mdx
@@ -0,0 +1,76 @@
+---
+title: "Call Layouts"
+sidebarTitle: "Call Layouts"
+---
+
+The CometChat Calls SDK provides three layout modes to display participants during a call. Each participant can independently choose their preferred layout without affecting others.
+
+## Available Layouts
+
+| Layout | Description | Best For |
+|--------|-------------|----------|
+| `TILE` | Grid layout with equally-sized tiles | Group discussions, team meetings |
+| `SIDEBAR` | Main speaker with participants in a sidebar | Presentations, webinars |
+| `SPOTLIGHT` | Large view for active speaker, small tiles for others | One-on-one calls, focused discussions |
+
+## Set Initial Layout
+
+Configure the initial layout when joining a session using the `layout` property in session settings:
+
+```javascript
+const callSettings = {
+ layout: "TILE", // or "SIDEBAR" or "SPOTLIGHT"
+ // ... other settings
+};
+
+await CometChatCalls.joinSession(callToken, callSettings, container);
+```
+
+## Change Layout During Call
+
+Change the layout dynamically during an active call:
+
+```javascript
+// Switch to tile layout
+CometChatCalls.setLayout("TILE");
+
+// Switch to sidebar layout
+CometChatCalls.setLayout("SIDEBAR");
+
+// Switch to spotlight layout
+CometChatCalls.setLayout("SPOTLIGHT");
+```
+
+## Listen for Layout Changes
+
+Monitor when the layout changes:
+
+```javascript
+CometChatCalls.addEventListener("onCallLayoutChanged", (layout) => {
+ console.log("Layout changed to:", layout);
+});
+```
+
+## Hide Layout Controls
+
+To prevent users from changing the layout, hide the layout change button:
+
+```javascript
+const callSettings = {
+ hideChangeLayoutButton: true,
+ // ... other settings
+};
+```
+
+## Layout Constants
+
+Access layout constants through the SDK:
+
+```javascript
+const layouts = CometChatCalls.constants.LAYOUT;
+
+console.log(layouts.TILE); // "TILE"
+console.log(layouts.SIDEBAR); // "SIDEBAR"
+console.log(layouts.SPOTLIGHT); // "SPOTLIGHT"
+```
+
diff --git a/calls/javascript/call-logs.mdx b/calls/javascript/call-logs.mdx
new file mode 100644
index 00000000..d656356d
--- /dev/null
+++ b/calls/javascript/call-logs.mdx
@@ -0,0 +1,51 @@
+---
+title: "Call Logs"
+sidebarTitle: "Call Logs"
+---
+
+Retrieve call history and details using the CometChat REST API. Call logs provide information about past calls including participants, duration, and recording URLs.
+
+## Retrieving Call Logs
+
+Call logs are accessed through the CometChat REST API rather than the client SDK. Use the following endpoints:
+
+### List Calls
+
+Retrieve a list of calls for your app:
+
+```
+GET https://{{appId}}.api-{{region}}.cometchat.io/v3/calls
+```
+
+See the [List Calls API](/calls/api/list-calls) documentation for full details on parameters and response format.
+
+### Get Call Details
+
+Retrieve details for a specific call:
+
+```
+GET https://{{appId}}.api-{{region}}.cometchat.io/v3/calls/{{sessionId}}
+```
+
+See the [Get Call API](/calls/api/get-call) documentation for full details.
+
+## Call Log Properties
+
+| Property | Type | Description |
+|----------|------|-------------|
+| `sessionId` | String | Unique identifier for the call session |
+| `initiator` | Object | User who initiated the call |
+| `receiver` | Object | User or group that received the call |
+| `type` | String | Call type: `audio` or `video` |
+| `status` | String | Call status: `initiated`, `ongoing`, `ended`, etc. |
+| `startedAt` | Number | Unix timestamp when the call started |
+| `endedAt` | Number | Unix timestamp when the call ended |
+| `duration` | Number | Call duration in seconds |
+| `participants` | Array | List of participants who joined |
+| `recordingUrl` | String | URL to the call recording (if recorded) |
+
+## Related Documentation
+
+- [List Calls API](/calls/api/list-calls)
+- [Get Call API](/calls/api/get-call)
+
diff --git a/calls/javascript/custom-control-panel.mdx b/calls/javascript/custom-control-panel.mdx
new file mode 100644
index 00000000..0bdae429
--- /dev/null
+++ b/calls/javascript/custom-control-panel.mdx
@@ -0,0 +1,210 @@
+---
+title: "Custom Control Panel"
+sidebarTitle: "Custom Control Panel"
+---
+
+Build a custom control panel by hiding the default UI and using the SDK's action methods to control call functionality.
+
+## Hide Default Control Panel
+
+Hide the built-in control panel when joining a session:
+
+```javascript
+const callSettings = {
+ hideControlPanel: true,
+ // ... other settings
+};
+
+await CometChatCalls.joinSession(callToken, callSettings, container);
+```
+
+## Build Custom Controls
+
+With the control panel hidden, use the SDK's action methods to build your own UI:
+
+### Audio Controls
+
+```javascript
+// Mute/unmute microphone
+function toggleAudio(isMuted) {
+ if (isMuted) {
+ CometChatCalls.unMuteAudio();
+ } else {
+ CometChatCalls.muteAudio();
+ }
+}
+```
+
+### Video Controls
+
+```javascript
+// Toggle camera
+function toggleVideo(isPaused) {
+ if (isPaused) {
+ CometChatCalls.resumeVideo();
+ } else {
+ CometChatCalls.pauseVideo();
+ }
+}
+```
+
+### Screen Sharing
+
+```javascript
+// Toggle screen share
+function toggleScreenShare(isSharing) {
+ if (isSharing) {
+ CometChatCalls.stopScreenSharing();
+ } else {
+ CometChatCalls.startScreenSharing();
+ }
+}
+```
+
+### Leave Session
+
+```javascript
+function leaveCall() {
+ CometChatCalls.leaveSession();
+}
+```
+
+## Track State with Events
+
+Listen to events to keep your custom UI in sync:
+
+```javascript
+let isAudioMuted = false;
+let isVideoPaused = false;
+let isScreenSharing = false;
+
+CometChatCalls.addEventListener("onAudioMuted", () => {
+ isAudioMuted = true;
+ updateUI();
+});
+
+CometChatCalls.addEventListener("onAudioUnMuted", () => {
+ isAudioMuted = false;
+ updateUI();
+});
+
+CometChatCalls.addEventListener("onVideoPaused", () => {
+ isVideoPaused = true;
+ updateUI();
+});
+
+CometChatCalls.addEventListener("onVideoResumed", () => {
+ isVideoPaused = false;
+ updateUI();
+});
+
+CometChatCalls.addEventListener("onScreenShareStarted", () => {
+ isScreenSharing = true;
+ updateUI();
+});
+
+CometChatCalls.addEventListener("onScreenShareStopped", () => {
+ isScreenSharing = false;
+ updateUI();
+});
+```
+
+## Hide Individual Buttons
+
+Instead of hiding the entire control panel, you can hide specific buttons:
+
+```javascript
+const callSettings = {
+ hideControlPanel: false,
+ hideLeaveSessionButton: false,
+ hideToggleAudioButton: false,
+ hideToggleVideoButton: false,
+ hideScreenSharingButton: true,
+ hideRecordingButton: true,
+ hideRaiseHandButton: true,
+ hideShareInviteButton: true,
+ hideChangeLayoutButton: true,
+ hideParticipantListButton: true,
+ hideChatButton: true,
+ hideVirtualBackgroundButton: true,
+ // ... other settings
+};
+```
+
+## Complete Example
+
+```html
+
+
+
+ Mute
+ Stop Video
+ Share Screen
+ Leave
+
+
+
+```
+
+## Available Actions
+
+All these methods can be used to build custom controls:
+
+| Action | Method |
+|--------|--------|
+| Mute audio | `CometChatCalls.muteAudio()` |
+| Unmute audio | `CometChatCalls.unMuteAudio()` |
+| Pause video | `CometChatCalls.pauseVideo()` |
+| Resume video | `CometChatCalls.resumeVideo()` |
+| Start screen share | `CometChatCalls.startScreenSharing()` |
+| Stop screen share | `CometChatCalls.stopScreenSharing()` |
+| Start recording | `CometChatCalls.startRecording()` |
+| Stop recording | `CometChatCalls.stopRecording()` |
+| Raise hand | `CometChatCalls.raiseHand()` |
+| Lower hand | `CometChatCalls.lowerHand()` |
+| Change layout | `CometChatCalls.setLayout(layout)` |
+| Leave session | `CometChatCalls.leaveSession()` |
+
+See [Actions](/calls/javascript/actions) for the complete list of available methods.
+
diff --git a/calls/javascript/device-management.mdx b/calls/javascript/device-management.mdx
new file mode 100644
index 00000000..d6e3bd0b
--- /dev/null
+++ b/calls/javascript/device-management.mdx
@@ -0,0 +1,151 @@
+---
+title: "Device Management"
+sidebarTitle: "Device Management"
+---
+
+Manage audio and video devices during calls, including selecting microphones, speakers, and cameras.
+
+## Get Available Devices
+
+### Audio Input Devices (Microphones)
+
+Get a list of available microphones:
+
+```javascript
+const audioInputDevices = CometChatCalls.getAudioInputDevices();
+console.log(audioInputDevices);
+```
+
+### Audio Output Devices (Speakers)
+
+Get a list of available speakers:
+
+```javascript
+const audioOutputDevices = CometChatCalls.getAudioOutputDevices();
+console.log(audioOutputDevices);
+```
+
+### Video Input Devices (Cameras)
+
+Get a list of available cameras:
+
+```javascript
+const videoInputDevices = CometChatCalls.getVideoInputDevices();
+console.log(videoInputDevices);
+```
+
+## Get Current Device
+
+### Current Audio Input Device
+
+Get the currently selected microphone:
+
+```javascript
+const currentMic = CometChatCalls.getCurrentAudioInputDevice();
+console.log("Current microphone:", currentMic);
+```
+
+### Current Audio Output Device
+
+Get the currently selected speaker:
+
+```javascript
+const currentSpeaker = CometChatCalls.getCurrentAudioOutputDevice();
+console.log("Current speaker:", currentSpeaker);
+```
+
+### Current Video Input Device
+
+Get the currently selected camera:
+
+```javascript
+const currentCamera = CometChatCalls.getCurrentVideoInputDevice();
+console.log("Current camera:", currentCamera);
+```
+
+## Change Device
+
+### Set Audio Input Device
+
+Switch to a different microphone:
+
+```javascript
+CometChatCalls.setAudioInputDevice(deviceId);
+```
+
+### Set Audio Output Device
+
+Switch to a different speaker:
+
+```javascript
+CometChatCalls.setAudioOutputDevice(deviceId);
+```
+
+### Set Video Input Device
+
+Switch to a different camera:
+
+```javascript
+CometChatCalls.setVideoInputDevice(deviceId);
+```
+
+## Device Change Events
+
+### Device Selection Changed
+
+Monitor when the selected device changes:
+
+```javascript
+CometChatCalls.addEventListener("onAudioInputDeviceChanged", (device) => {
+ console.log("Microphone changed:", device);
+});
+
+CometChatCalls.addEventListener("onAudioOutputDeviceChanged", (device) => {
+ console.log("Speaker changed:", device);
+});
+
+CometChatCalls.addEventListener("onVideoInputDeviceChanged", (device) => {
+ console.log("Camera changed:", device);
+});
+```
+
+### Available Devices Changed
+
+Monitor when devices are connected or disconnected:
+
+```javascript
+CometChatCalls.addEventListener("onAudioInputDevicesChanged", (devices) => {
+ console.log("Available microphones updated:", devices);
+});
+
+CometChatCalls.addEventListener("onAudioOutputDevicesChanged", (devices) => {
+ console.log("Available speakers updated:", devices);
+});
+
+CometChatCalls.addEventListener("onVideoInputDevicesChanged", (devices) => {
+ console.log("Available cameras updated:", devices);
+});
+```
+
+## Settings Dialog
+
+Open the built-in settings dialog for device selection:
+
+```javascript
+// Show settings dialog
+CometChatCalls.showSettingsDialog();
+
+// Hide settings dialog
+CometChatCalls.hideSettingsDialog();
+```
+
+## Device Object
+
+Each device object contains:
+
+| Property | Type | Description |
+|----------|------|-------------|
+| `deviceId` | String | Unique identifier for the device |
+| `label` | String | Human-readable device name |
+| `kind` | String | Device type (`audioinput`, `audiooutput`, `videoinput`) |
+
diff --git a/calls/javascript/events.mdx b/calls/javascript/events.mdx
new file mode 100644
index 00000000..ab3c45af
--- /dev/null
+++ b/calls/javascript/events.mdx
@@ -0,0 +1,530 @@
+---
+title: "Events"
+sidebarTitle: "Events"
+---
+
+Handle call session events to build responsive UIs. The SDK provides event listeners to monitor session status, participant activities, media changes, button clicks, and layout changes.
+
+## Adding Event Listeners
+
+Use the `addEventListener()` method to register event listeners. The method returns an unsubscribe function that you should call to remove the listener when no longer needed.
+
+```javascript
+const unsubscribe = CometChatCalls.addEventListener("eventName", (data) => {
+ // Handle the event
+});
+
+// Later, to remove the listener:
+unsubscribe();
+```
+
+---
+
+## Session Events
+
+Monitor the call session lifecycle including join/leave events and connection status.
+
+### Session Joined
+
+Fired when you successfully connect to the session.
+
+```javascript
+CometChatCalls.addEventListener("onSessionJoined", () => {
+ console.log("Successfully joined the session");
+});
+```
+
+### Session Left
+
+Fired when you leave the session.
+
+```javascript
+CometChatCalls.addEventListener("onSessionLeft", () => {
+ console.log("Left the session");
+});
+```
+
+### Session Timed Out
+
+Fired when the session ends due to inactivity timeout.
+
+```javascript
+CometChatCalls.addEventListener("onSessionTimedOut", () => {
+ console.log("Session timed out");
+});
+```
+
+### Connection Lost
+
+Fired when the network connection is interrupted.
+
+```javascript
+CometChatCalls.addEventListener("onConnectionLost", () => {
+ console.log("Connection lost, attempting to reconnect...");
+});
+```
+
+### Connection Restored
+
+Fired when the connection is restored after being lost.
+
+```javascript
+CometChatCalls.addEventListener("onConnectionRestored", () => {
+ console.log("Connection restored");
+});
+```
+
+### Connection Closed
+
+Fired when the connection is permanently closed.
+
+```javascript
+CometChatCalls.addEventListener("onConnectionClosed", () => {
+ console.log("Connection closed");
+});
+```
+
+---
+
+## Participant Events
+
+Monitor participant activities including join/leave, audio/video state, hand raise, screen sharing, and recording.
+
+### Participant Joined
+
+Fired when a participant joins the call.
+
+```javascript
+CometChatCalls.addEventListener("onParticipantJoined", (participant) => {
+ console.log("Participant joined:", participant.name);
+});
+```
+
+### Participant Left
+
+Fired when a participant leaves the call.
+
+```javascript
+CometChatCalls.addEventListener("onParticipantLeft", (participant) => {
+ console.log("Participant left:", participant.name);
+});
+```
+
+### Participant List Changed
+
+Fired when the participant list is updated.
+
+```javascript
+CometChatCalls.addEventListener("onParticipantListChanged", (participants) => {
+ console.log("Participants:", participants.length);
+});
+```
+
+### Participant Audio Muted
+
+Fired when a participant mutes their microphone.
+
+```javascript
+CometChatCalls.addEventListener("onParticipantAudioMuted", (participant) => {
+ console.log("Participant muted:", participant.name);
+});
+```
+
+### Participant Audio Unmuted
+
+Fired when a participant unmutes their microphone.
+
+```javascript
+CometChatCalls.addEventListener("onParticipantAudioUnmuted", (participant) => {
+ console.log("Participant unmuted:", participant.name);
+});
+```
+
+### Participant Video Paused
+
+Fired when a participant turns off their camera.
+
+```javascript
+CometChatCalls.addEventListener("onParticipantVideoPaused", (participant) => {
+ console.log("Participant video paused:", participant.name);
+});
+```
+
+### Participant Video Resumed
+
+Fired when a participant turns on their camera.
+
+```javascript
+CometChatCalls.addEventListener("onParticipantVideoResumed", (participant) => {
+ console.log("Participant video resumed:", participant.name);
+});
+```
+
+### Participant Hand Raised
+
+Fired when a participant raises their hand.
+
+```javascript
+CometChatCalls.addEventListener("onParticipantHandRaised", (participant) => {
+ console.log("Participant raised hand:", participant.name);
+});
+```
+
+### Participant Hand Lowered
+
+Fired when a participant lowers their hand.
+
+```javascript
+CometChatCalls.addEventListener("onParticipantHandLowered", (participant) => {
+ console.log("Participant lowered hand:", participant.name);
+});
+```
+
+### Participant Started Screen Share
+
+Fired when a participant starts screen sharing.
+
+```javascript
+CometChatCalls.addEventListener("onParticipantStartedScreenShare", (participant) => {
+ console.log("Participant started screen share:", participant.name);
+});
+```
+
+### Participant Stopped Screen Share
+
+Fired when a participant stops screen sharing.
+
+```javascript
+CometChatCalls.addEventListener("onParticipantStoppedScreenShare", (participant) => {
+ console.log("Participant stopped screen share:", participant.name);
+});
+```
+
+### Participant Started Recording
+
+Fired when a participant starts recording.
+
+```javascript
+CometChatCalls.addEventListener("onParticipantStartedRecording", (participant) => {
+ console.log("Participant started recording:", participant.name);
+});
+```
+
+### Participant Stopped Recording
+
+Fired when a participant stops recording.
+
+```javascript
+CometChatCalls.addEventListener("onParticipantStoppedRecording", (participant) => {
+ console.log("Participant stopped recording:", participant.name);
+});
+```
+
+### Dominant Speaker Changed
+
+Fired when the active speaker changes.
+
+```javascript
+CometChatCalls.addEventListener("onDominantSpeakerChanged", (participant) => {
+ console.log("Dominant speaker:", participant.name);
+});
+```
+
+---
+
+## Media Events
+
+Monitor your local media state changes including audio/video status, recording, and device changes.
+
+### Audio Muted
+
+Fired when your microphone is muted.
+
+```javascript
+CometChatCalls.addEventListener("onAudioMuted", () => {
+ console.log("Your audio is muted");
+});
+```
+
+### Audio Unmuted
+
+Fired when your microphone is unmuted.
+
+```javascript
+CometChatCalls.addEventListener("onAudioUnMuted", () => {
+ console.log("Your audio is unmuted");
+});
+```
+
+### Video Paused
+
+Fired when your camera is turned off.
+
+```javascript
+CometChatCalls.addEventListener("onVideoPaused", () => {
+ console.log("Your video is paused");
+});
+```
+
+### Video Resumed
+
+Fired when your camera is turned on.
+
+```javascript
+CometChatCalls.addEventListener("onVideoResumed", () => {
+ console.log("Your video is resumed");
+});
+```
+
+### Recording Started
+
+Fired when call recording starts.
+
+```javascript
+CometChatCalls.addEventListener("onRecordingStarted", () => {
+ console.log("Recording started");
+});
+```
+
+### Recording Stopped
+
+Fired when call recording stops.
+
+```javascript
+CometChatCalls.addEventListener("onRecordingStopped", () => {
+ console.log("Recording stopped");
+});
+```
+
+### Screen Share Started
+
+Fired when you start screen sharing.
+
+```javascript
+CometChatCalls.addEventListener("onScreenShareStarted", () => {
+ console.log("Screen sharing started");
+});
+```
+
+### Screen Share Stopped
+
+Fired when you stop screen sharing.
+
+```javascript
+CometChatCalls.addEventListener("onScreenShareStopped", () => {
+ console.log("Screen sharing stopped");
+});
+```
+
+### Audio Input Device Changed
+
+Fired when the audio input device changes.
+
+```javascript
+CometChatCalls.addEventListener("onAudioInputDeviceChanged", (device) => {
+ console.log("Audio input device changed:", device);
+});
+```
+
+### Audio Output Device Changed
+
+Fired when the audio output device changes.
+
+```javascript
+CometChatCalls.addEventListener("onAudioOutputDeviceChanged", (device) => {
+ console.log("Audio output device changed:", device);
+});
+```
+
+### Video Input Device Changed
+
+Fired when the video input device changes.
+
+```javascript
+CometChatCalls.addEventListener("onVideoInputDeviceChanged", (device) => {
+ console.log("Video input device changed:", device);
+});
+```
+
+### Audio Input Devices Changed
+
+Fired when the list of available audio input devices changes.
+
+```javascript
+CometChatCalls.addEventListener("onAudioInputDevicesChanged", (devices) => {
+ console.log("Audio input devices updated:", devices);
+});
+```
+
+### Audio Output Devices Changed
+
+Fired when the list of available audio output devices changes.
+
+```javascript
+CometChatCalls.addEventListener("onAudioOutputDevicesChanged", (devices) => {
+ console.log("Audio output devices updated:", devices);
+});
+```
+
+### Video Input Devices Changed
+
+Fired when the list of available video input devices changes.
+
+```javascript
+CometChatCalls.addEventListener("onVideoInputDevicesChanged", (devices) => {
+ console.log("Video input devices updated:", devices);
+});
+```
+
+---
+
+## Button Click Events
+
+Intercept UI button clicks from the default call interface to add custom behavior or analytics.
+
+### Leave Session Button Clicked
+
+Fired when the leave button is clicked.
+
+```javascript
+CometChatCalls.addEventListener("onLeaveSessionButtonClicked", () => {
+ console.log("Leave button clicked");
+});
+```
+
+### Toggle Audio Button Clicked
+
+Fired when the mute/unmute button is clicked.
+
+```javascript
+CometChatCalls.addEventListener("onToggleAudioButtonClicked", () => {
+ console.log("Audio toggle button clicked");
+});
+```
+
+### Toggle Video Button Clicked
+
+Fired when the video on/off button is clicked.
+
+```javascript
+CometChatCalls.addEventListener("onToggleVideoButtonClicked", () => {
+ console.log("Video toggle button clicked");
+});
+```
+
+### Raise Hand Button Clicked
+
+Fired when the raise hand button is clicked.
+
+```javascript
+CometChatCalls.addEventListener("onRaiseHandButtonClicked", () => {
+ console.log("Raise hand button clicked");
+});
+```
+
+### Share Invite Button Clicked
+
+Fired when the share/invite button is clicked.
+
+```javascript
+CometChatCalls.addEventListener("onShareInviteButtonClicked", () => {
+ console.log("Share invite button clicked");
+});
+```
+
+### Change Layout Button Clicked
+
+Fired when the layout change button is clicked.
+
+```javascript
+CometChatCalls.addEventListener("onChangeLayoutButtonClicked", () => {
+ console.log("Change layout button clicked");
+});
+```
+
+### Participant List Button Clicked
+
+Fired when the participant list button is clicked.
+
+```javascript
+CometChatCalls.addEventListener("onParticipantListButtonClicked", () => {
+ console.log("Participant list button clicked");
+});
+```
+
+### Chat Button Clicked
+
+Fired when the in-call chat button is clicked.
+
+```javascript
+CometChatCalls.addEventListener("onChatButtonClicked", () => {
+ console.log("Chat button clicked");
+});
+```
+
+### Recording Toggle Button Clicked
+
+Fired when the recording toggle button is clicked.
+
+```javascript
+CometChatCalls.addEventListener("onRecordingToggleButtonClicked", () => {
+ console.log("Recording toggle button clicked");
+});
+```
+
+### Screen Share Button Clicked
+
+Fired when the screen share button is clicked.
+
+```javascript
+CometChatCalls.addEventListener("onScreenShareButtonClicked", () => {
+ console.log("Screen share button clicked");
+});
+```
+
+---
+
+## Layout Events
+
+Monitor layout changes including layout type switches and participant list visibility.
+
+### Call Layout Changed
+
+Fired when the call layout changes.
+
+```javascript
+CometChatCalls.addEventListener("onCallLayoutChanged", (layout) => {
+ console.log("Layout changed to:", layout);
+});
+```
+
+### Participant List Visible
+
+Fired when the participant list panel is opened.
+
+```javascript
+CometChatCalls.addEventListener("onParticipantListVisible", () => {
+ console.log("Participant list opened");
+});
+```
+
+### Participant List Hidden
+
+Fired when the participant list panel is closed.
+
+```javascript
+CometChatCalls.addEventListener("onParticipantListHidden", () => {
+ console.log("Participant list closed");
+});
+```
+
+---
+
+## Participant Object Reference
+
+| Property | Type | Description |
+|----------|------|-------------|
+| `uid` | String | Unique identifier (CometChat user ID) |
+| `name` | String | Display name |
+| `avatar` | String | URL of avatar image |
+
diff --git a/calls/javascript/idle-timeout.mdx b/calls/javascript/idle-timeout.mdx
new file mode 100644
index 00000000..b6216142
--- /dev/null
+++ b/calls/javascript/idle-timeout.mdx
@@ -0,0 +1,59 @@
+---
+title: "Idle Timeout"
+sidebarTitle: "Idle Timeout"
+---
+
+The idle timeout feature automatically ends a call session when you're the only participant remaining. This prevents sessions from running indefinitely and consuming resources.
+
+## How It Works
+
+1. When all other participants leave, a countdown timer starts
+2. After the initial timeout period, a prompt is shown to the user
+3. If the user doesn't respond, the session ends after the second timeout period
+
+## Configure Timeout Periods
+
+Set the timeout periods when joining a session:
+
+```javascript
+const callSettings = {
+ idleTimeoutPeriodBeforePrompt: 60000, // 60 seconds before showing prompt
+ idleTimeoutPeriodAfterPrompt: 120000, // 120 seconds after prompt before ending
+ // ... other settings
+};
+
+await CometChatCalls.joinSession(callToken, callSettings, container);
+```
+
+| Property | Type | Default | Description |
+|----------|------|---------|-------------|
+| `idleTimeoutPeriodBeforePrompt` | Number | `60000` | Time in milliseconds before showing the timeout prompt |
+| `idleTimeoutPeriodAfterPrompt` | Number | `120000` | Time in milliseconds after the prompt before ending the session |
+
+## Session Timeout Event
+
+Listen for when the session times out:
+
+```javascript
+CometChatCalls.addEventListener("onSessionTimedOut", () => {
+ console.log("Session ended due to inactivity");
+ // Navigate away or show a message
+});
+```
+
+## Disable Idle Timeout
+
+To effectively disable the idle timeout, set very long timeout periods:
+
+```javascript
+const callSettings = {
+ idleTimeoutPeriodBeforePrompt: 86400000, // 24 hours
+ idleTimeoutPeriodAfterPrompt: 86400000, // 24 hours
+ // ... other settings
+};
+```
+
+
+The minimum value for `idleTimeoutPeriodAfterPrompt` is 60 seconds (60000 milliseconds).
+
+
diff --git a/calls/javascript/in-call-chat.mdx b/calls/javascript/in-call-chat.mdx
new file mode 100644
index 00000000..62e06d18
--- /dev/null
+++ b/calls/javascript/in-call-chat.mdx
@@ -0,0 +1,74 @@
+---
+title: "In-Call Chat"
+sidebarTitle: "In-Call Chat"
+---
+
+Enable text messaging during calls by integrating the in-call chat feature. The SDK provides a chat button in the control panel and events to help you build a custom chat experience.
+
+## Chat Button
+
+### Show Chat Button
+
+By default, the chat button is hidden. To show it:
+
+```javascript
+const callSettings = {
+ hideChatButton: false,
+ // ... other settings
+};
+```
+
+### Listen for Chat Button Clicks
+
+Handle chat button clicks to open your chat interface:
+
+```javascript
+CometChatCalls.addEventListener("onChatButtonClicked", () => {
+ console.log("Chat button clicked");
+ // Open your chat UI
+});
+```
+
+## Unread Message Badge
+
+Update the badge count on the chat button to show unread messages:
+
+```javascript
+// Set unread count
+CometChatCalls.setChatButtonUnreadCount(5);
+
+// Clear the badge
+CometChatCalls.setChatButtonUnreadCount(0);
+```
+
+| Parameter | Type | Description |
+|-----------|------|-------------|
+| `count` | Number | The unread message count to display |
+
+## Building In-Call Chat
+
+The Calls SDK provides the UI hooks for in-call chat, but the actual messaging functionality should be implemented using the CometChat Chat SDK or your own messaging solution.
+
+### Integration with CometChat Chat SDK
+
+If you're using the CometChat Chat SDK, you can:
+
+1. Create a group for the call session
+2. Use the group ID as the session ID
+3. Send and receive messages through the Chat SDK
+4. Update the unread badge count based on incoming messages
+
+```javascript
+// Example: Update badge when new message arrives
+CometChat.addMessageListener("call-chat-listener", {
+ onTextMessageReceived: (message) => {
+ if (message.getReceiverType() === "group" &&
+ message.getReceiverId() === sessionId) {
+ // Increment unread count
+ unreadCount++;
+ CometChatCalls.setChatButtonUnreadCount(unreadCount);
+ }
+ }
+});
+```
+
diff --git a/calls/javascript/ionic-integration.mdx b/calls/javascript/ionic-integration.mdx
new file mode 100644
index 00000000..77de909c
--- /dev/null
+++ b/calls/javascript/ionic-integration.mdx
@@ -0,0 +1,1329 @@
+---
+title: "Ionic Integration"
+sidebarTitle: "Ionic"
+---
+
+This guide walks you through integrating the CometChat Calls SDK into an Ionic application. By the end, you'll have a working video call implementation with proper authentication and lifecycle handling. This guide covers Ionic with Angular, React, and Vue.
+
+
+For native mobile features like CallKit, VoIP push notifications, and background handling, consider using the native [iOS](/calls/ios/overview) or [Android](/calls/android/overview) SDKs.
+
+
+## Prerequisites
+
+Before you begin, ensure you have:
+- A CometChat account with an app created ([Sign up](https://app.cometchat.com/signup))
+- Your App ID, Region, and API Key from the CometChat Dashboard
+- An Ionic project (Angular, React, or Vue)
+- Node.js 16+ installed
+- Ionic CLI installed (`npm install -g @ionic/cli`)
+
+## Step 1: Install the SDK
+
+Install the CometChat Calls SDK package:
+
+```bash
+npm install @cometchat/calls-sdk-javascript
+```
+
+## Ionic Angular
+
+### Step 2: Create the Service
+
+Create a service that handles SDK initialization, authentication, and call operations. The service waits for the Ionic platform to be ready before initializing:
+
+```typescript
+// src/app/services/cometchat-calls.service.ts
+import { Injectable } from "@angular/core";
+import { CometChatCalls } from "@cometchat/calls-sdk-javascript";
+import { Platform } from "@ionic/angular";
+import { BehaviorSubject } from "rxjs";
+
+interface User {
+ uid: string;
+ name: string;
+ avatar?: string;
+}
+
+@Injectable({
+ providedIn: "root",
+})
+export class CometChatCallsService {
+ private initialized = false;
+ private _isReady$ = new BehaviorSubject(false);
+ private _user$ = new BehaviorSubject(null);
+ private _error$ = new BehaviorSubject(null);
+
+ isReady$ = this._isReady$.asObservable();
+ user$ = this._user$.asObservable();
+ error$ = this._error$.asObservable();
+
+ // Replace with your CometChat credentials
+ private readonly APP_ID = "YOUR_APP_ID";
+ private readonly REGION = "YOUR_REGION";
+ private readonly API_KEY = "YOUR_API_KEY";
+
+ constructor(private platform: Platform) {}
+
+ async initAndLogin(uid: string): Promise {
+ try {
+ // Wait for Ionic platform to be ready
+ await this.platform.ready();
+
+ if (this.initialized) {
+ return true;
+ }
+
+ // Step 1: Initialize the SDK
+ const initResult = await CometChatCalls.init({
+ appId: this.APP_ID,
+ region: this.REGION,
+ });
+
+ if (!initResult.success) {
+ throw new Error("SDK initialization failed");
+ }
+
+ // Step 2: Check if already logged in
+ let loggedInUser = CometChatCalls.getLoggedInUser();
+
+ // Step 3: Login if not already logged in
+ if (!loggedInUser) {
+ loggedInUser = await CometChatCalls.login(uid, this.API_KEY);
+ }
+
+ this.initialized = true;
+ this._user$.next(loggedInUser);
+ this._isReady$.next(true);
+ return true;
+ } catch (err: any) {
+ console.error("CometChat Calls setup failed:", err);
+ this._error$.next(err.message || "Setup failed");
+ return false;
+ }
+ }
+
+ getLoggedInUser(): User | null {
+ return this._user$.value;
+ }
+
+ async generateToken(sessionId: string) {
+ return CometChatCalls.generateToken(sessionId);
+ }
+
+ async joinSession(token: string, settings: any, container: HTMLElement) {
+ return CometChatCalls.joinSession(token, settings, container);
+ }
+
+ leaveSession() {
+ CometChatCalls.leaveSession();
+ }
+
+ muteAudio() {
+ CometChatCalls.muteAudio();
+ }
+
+ unMuteAudio() {
+ CometChatCalls.unMuteAudio();
+ }
+
+ pauseVideo() {
+ CometChatCalls.pauseVideo();
+ }
+
+ resumeVideo() {
+ CometChatCalls.resumeVideo();
+ }
+
+ addEventListener(event: string, callback: Function) {
+ return CometChatCalls.addEventListener(event as any, callback as any);
+ }
+}
+```
+
+
+### Step 3: Initialize in App Component
+
+Initialize the SDK and login when the app starts:
+
+```typescript
+// src/app/app.component.ts
+import { Component, OnInit } from "@angular/core";
+import { CometChatCallsService } from "./services/cometchat-calls.service";
+
+@Component({
+ selector: "app-root",
+ templateUrl: "app.component.html",
+})
+export class AppComponent implements OnInit {
+ constructor(private callsService: CometChatCallsService) {}
+
+ ngOnInit() {
+ // In a real app, get this from your authentication system
+ const currentUserId = "cometchat-uid-1";
+ this.callsService.initAndLogin(currentUserId);
+ }
+}
+```
+
+### Step 4: Create the Call Page
+
+Create a call page component that handles joining sessions, media controls, and cleanup:
+
+```typescript
+// src/app/pages/call/call.page.ts
+import { Component, OnInit, OnDestroy, ViewChild, ElementRef } from "@angular/core";
+import { ActivatedRoute } from "@angular/router";
+import { NavController } from "@ionic/angular";
+import { CometChatCallsService } from "../../services/cometchat-calls.service";
+import { Subscription } from "rxjs";
+
+@Component({
+ selector: "app-call",
+ templateUrl: "./call.page.html",
+ styleUrls: ["./call.page.scss"],
+})
+export class CallPage implements OnInit, OnDestroy {
+ @ViewChild("callContainer", { static: true }) callContainer!: ElementRef;
+
+ sessionId: string = "";
+ isReady = false;
+ isJoined = false;
+ isJoining = false;
+ isMuted = false;
+ isVideoOff = false;
+ error: string | null = null;
+
+ private unsubscribers: Function[] = [];
+ private subscriptions: Subscription[] = [];
+
+ constructor(
+ private route: ActivatedRoute,
+ private navCtrl: NavController,
+ private callsService: CometChatCallsService
+ ) {}
+
+ ngOnInit() {
+ this.sessionId = this.route.snapshot.paramMap.get("sessionId") || "";
+
+ // Subscribe to ready state
+ this.subscriptions.push(
+ this.callsService.isReady$.subscribe((ready) => {
+ this.isReady = ready;
+ if (ready && this.sessionId) {
+ this.joinCall();
+ }
+ }),
+ this.callsService.error$.subscribe((err) => {
+ this.error = err;
+ })
+ );
+ }
+
+ ngOnDestroy() {
+ this.cleanup();
+ this.subscriptions.forEach((sub) => sub.unsubscribe());
+ }
+
+ private async joinCall() {
+ if (!this.callContainer?.nativeElement) return;
+
+ this.isJoining = true;
+ this.error = null;
+
+ try {
+ // Register event listeners before joining
+ this.unsubscribers = [
+ this.callsService.addEventListener("onSessionJoined", () => {
+ this.isJoined = true;
+ this.isJoining = false;
+ }),
+ this.callsService.addEventListener("onSessionLeft", () => {
+ this.isJoined = false;
+ this.navCtrl.back();
+ }),
+ this.callsService.addEventListener("onAudioMuted", () => {
+ this.isMuted = true;
+ }),
+ this.callsService.addEventListener("onAudioUnMuted", () => {
+ this.isMuted = false;
+ }),
+ this.callsService.addEventListener("onVideoPaused", () => {
+ this.isVideoOff = true;
+ }),
+ this.callsService.addEventListener("onVideoResumed", () => {
+ this.isVideoOff = false;
+ }),
+ ];
+
+ // Generate a call token for this session
+ const tokenResult = await this.callsService.generateToken(this.sessionId);
+
+ // Join the call session
+ await this.callsService.joinSession(
+ tokenResult.token,
+ {
+ sessionType: "VIDEO",
+ layout: "TILE",
+ startAudioMuted: false,
+ startVideoPaused: false,
+ },
+ this.callContainer.nativeElement
+ );
+ } catch (err: any) {
+ console.error("Failed to join call:", err);
+ this.error = err.message || "Failed to join call";
+ this.isJoining = false;
+ }
+ }
+
+ toggleAudio() {
+ this.isMuted ? this.callsService.unMuteAudio() : this.callsService.muteAudio();
+ }
+
+ toggleVideo() {
+ this.isVideoOff ? this.callsService.resumeVideo() : this.callsService.pauseVideo();
+ }
+
+ leaveCall() {
+ this.callsService.leaveSession();
+ }
+
+ private cleanup() {
+ this.unsubscribers.forEach((unsub) => unsub());
+ this.unsubscribers = [];
+ this.callsService.leaveSession();
+ }
+}
+```
+
+
+### Step 5: Create the Call Page Template
+
+Create the HTML template for the call page with video container and controls:
+
+```html
+
+
+
+
+
+
+
+
+
{{ error }}
+
Retry
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+```scss
+/* src/app/pages/call/call.page.scss */
+.call-container {
+ width: 100%;
+ height: calc(100% - 80px);
+ background-color: #1a1a1a;
+}
+
+.call-controls {
+ display: flex;
+ justify-content: center;
+ gap: 16px;
+ padding: 16px;
+ background-color: #f5f5f5;
+}
+
+.loading-container,
+.error-container,
+.joining-overlay {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ height: 100%;
+ gap: 16px;
+}
+
+.joining-overlay {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background-color: rgba(0, 0, 0, 0.7);
+ color: white;
+ z-index: 100;
+}
+```
+
+### Step 6: Create the Home Page
+
+Create a home page where users can enter a session ID and join a call:
+
+```typescript
+// src/app/pages/home/home.page.ts
+import { Component } from "@angular/core";
+import { Router } from "@angular/router";
+import { CometChatCallsService } from "../../services/cometchat-calls.service";
+
+@Component({
+ selector: "app-home",
+ templateUrl: "./home.page.html",
+})
+export class HomePage {
+ sessionId = "";
+ isReady$ = this.callsService.isReady$;
+ user$ = this.callsService.user$;
+ error$ = this.callsService.error$;
+
+ constructor(
+ private router: Router,
+ private callsService: CometChatCallsService
+ ) {}
+
+ joinCall() {
+ if (this.sessionId) {
+ this.router.navigate(["/call", this.sessionId]);
+ }
+ }
+}
+```
+
+```html
+
+
+
+ CometChat Calls
+
+
+
+
+
+ {{ error }}
+
+
+
+
+
+
+ Logged in as: {{ user.name || user.uid }}
+
+
+
+ Session ID
+
+
+
+
+ Join Call
+
+
+
+```
+
+
+## Ionic React
+
+### Step 2: Create the Provider
+
+Create a context provider that handles SDK initialization and authentication:
+
+```tsx
+// src/providers/CometChatCallsProvider.tsx
+import { createContext, useContext, useEffect, useState, ReactNode } from "react";
+import { CometChatCalls } from "@cometchat/calls-sdk-javascript";
+import { isPlatform } from "@ionic/react";
+
+interface User {
+ uid: string;
+ name: string;
+ avatar?: string;
+}
+
+interface CometChatCallsContextType {
+ isReady: boolean;
+ user: User | null;
+ error: string | null;
+}
+
+const CometChatCallsContext = createContext({
+ isReady: false,
+ user: null,
+ error: null,
+});
+
+// Replace with your CometChat credentials
+const APP_ID = "YOUR_APP_ID";
+const REGION = "YOUR_REGION";
+const API_KEY = "YOUR_API_KEY";
+
+interface ProviderProps {
+ children: ReactNode;
+ uid: string;
+}
+
+export function CometChatCallsProvider({ children, uid }: ProviderProps) {
+ const [isReady, setIsReady] = useState(false);
+ const [user, setUser] = useState(null);
+ const [error, setError] = useState(null);
+
+ useEffect(() => {
+ async function initAndLogin() {
+ try {
+ // Step 1: Initialize the SDK
+ const initResult = await CometChatCalls.init({
+ appId: APP_ID,
+ region: REGION,
+ });
+
+ if (!initResult.success) {
+ throw new Error("SDK initialization failed");
+ }
+
+ // Step 2: Check if already logged in
+ let loggedInUser = CometChatCalls.getLoggedInUser();
+
+ // Step 3: Login if not already logged in
+ if (!loggedInUser) {
+ loggedInUser = await CometChatCalls.login(uid, API_KEY);
+ }
+
+ setUser(loggedInUser);
+ setIsReady(true);
+ } catch (err: any) {
+ console.error("CometChat Calls setup failed:", err);
+ setError(err.message || "Setup failed");
+ }
+ }
+
+ if (uid) {
+ initAndLogin();
+ }
+ }, [uid]);
+
+ return (
+
+ {children}
+
+ );
+}
+
+export function useCometChatCalls(): CometChatCallsContextType {
+ return useContext(CometChatCallsContext);
+}
+```
+
+### Step 3: Wrap Your App
+
+Add the provider to your app's root component:
+
+```tsx
+// src/App.tsx
+import { IonApp, IonRouterOutlet, setupIonicReact } from "@ionic/react";
+import { IonReactRouter } from "@ionic/react-router";
+import { Route } from "react-router-dom";
+import { CometChatCallsProvider } from "./providers/CometChatCallsProvider";
+import HomePage from "./pages/Home";
+import CallPage from "./pages/Call";
+
+setupIonicReact();
+
+const App: React.FC = () => {
+ // In a real app, get this from your authentication system
+ const currentUserId = "cometchat-uid-1";
+
+ return (
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default App;
+```
+
+### Step 4: Create the Call Page
+
+Create a call page that handles joining sessions, media controls, and cleanup:
+
+```tsx
+// src/pages/Call.tsx
+import { useEffect, useRef, useState } from "react";
+import {
+ IonContent,
+ IonPage,
+ IonButton,
+ IonIcon,
+ IonSpinner,
+ useIonRouter
+} from "@ionic/react";
+import { mic, micOff, videocam, videocamOff, call } from "ionicons/icons";
+import { useParams } from "react-router-dom";
+import { CometChatCalls } from "@cometchat/calls-sdk-javascript";
+import { useCometChatCalls } from "../providers/CometChatCallsProvider";
+
+const CallPage: React.FC = () => {
+ const { sessionId } = useParams<{ sessionId: string }>();
+ const { isReady, error: initError } = useCometChatCalls();
+ const router = useIonRouter();
+ const containerRef = useRef(null);
+
+ // Call state
+ const [isJoined, setIsJoined] = useState(false);
+ const [isJoining, setIsJoining] = useState(false);
+ const [isMuted, setIsMuted] = useState(false);
+ const [isVideoOff, setIsVideoOff] = useState(false);
+ const [error, setError] = useState(null);
+
+ const unsubscribersRef = useRef([]);
+
+ useEffect(() => {
+ if (!isReady || !containerRef.current || !sessionId) return;
+
+ async function joinCall() {
+ setIsJoining(true);
+ setError(null);
+
+ try {
+ // Register event listeners before joining
+ unsubscribersRef.current = [
+ CometChatCalls.addEventListener("onSessionJoined", () => {
+ setIsJoined(true);
+ setIsJoining(false);
+ }),
+ CometChatCalls.addEventListener("onSessionLeft", () => {
+ setIsJoined(false);
+ router.goBack();
+ }),
+ CometChatCalls.addEventListener("onAudioMuted", () => setIsMuted(true)),
+ CometChatCalls.addEventListener("onAudioUnMuted", () => setIsMuted(false)),
+ CometChatCalls.addEventListener("onVideoPaused", () => setIsVideoOff(true)),
+ CometChatCalls.addEventListener("onVideoResumed", () => setIsVideoOff(false)),
+ ];
+
+ // Generate a call token for this session
+ const tokenResult = await CometChatCalls.generateToken(sessionId);
+
+ // Join the call session
+ await CometChatCalls.joinSession(
+ tokenResult.token,
+ {
+ sessionType: "VIDEO",
+ layout: "TILE",
+ startAudioMuted: false,
+ startVideoPaused: false,
+ },
+ containerRef.current!
+ );
+ } catch (err: any) {
+ console.error("Failed to join call:", err);
+ setError(err.message || "Failed to join call");
+ setIsJoining(false);
+ }
+ }
+
+ joinCall();
+
+ return () => {
+ unsubscribersRef.current.forEach((unsub) => unsub());
+ unsubscribersRef.current = [];
+ CometChatCalls.leaveSession();
+ };
+ }, [isReady, sessionId, router]);
+
+ // Control handlers
+ const toggleAudio = () => {
+ isMuted ? CometChatCalls.unMuteAudio() : CometChatCalls.muteAudio();
+ };
+
+ const toggleVideo = () => {
+ isVideoOff ? CometChatCalls.resumeVideo() : CometChatCalls.pauseVideo();
+ };
+
+ const leaveCall = () => {
+ CometChatCalls.leaveSession();
+ };
+
+ // Loading state
+ if (!isReady) {
+ return (
+
+
+
+ Initializing...
+
+
+ );
+ }
+
+ // Error state
+ if (error || initError) {
+ return (
+
+
+
+ Error: {error || initError}
+
+ window.location.reload()}>Retry
+
+
+ );
+ }
+
+ return (
+
+
+ {/* Video container - SDK renders the call UI here */}
+
+
+ {/* Joining overlay */}
+ {isJoining && (
+
+ )}
+
+ {/* Call controls */}
+ {isJoined && (
+
+
+
+
+
+
+
+
+
+
+
+ )}
+
+
+ );
+};
+
+export default CallPage;
+```
+
+
+### Step 5: Create the Home Page
+
+Create a home page where users can enter a session ID and join a call:
+
+```tsx
+// src/pages/Home.tsx
+import { useState } from "react";
+import {
+ IonContent,
+ IonPage,
+ IonHeader,
+ IonToolbar,
+ IonTitle,
+ IonItem,
+ IonLabel,
+ IonInput,
+ IonButton,
+ IonSpinner,
+ IonText,
+ useIonRouter
+} from "@ionic/react";
+import { useCometChatCalls } from "../providers/CometChatCallsProvider";
+
+const HomePage: React.FC = () => {
+ const { isReady, user, error } = useCometChatCalls();
+ const router = useIonRouter();
+ const [sessionId, setSessionId] = useState("");
+
+ const joinCall = () => {
+ if (sessionId) {
+ router.push(`/call/${sessionId}`);
+ }
+ };
+
+ return (
+
+
+
+ CometChat Calls
+
+
+
+ {error && (
+
+ {error}
+
+ )}
+
+ {!isReady ? (
+
+ ) : (
+ <>
+ Logged in as: {user?.name || user?.uid}
+
+
+ Session ID
+ setSessionId(e.detail.value || "")}
+ placeholder="Enter Session ID"
+ />
+
+
+
+ Join Call
+
+ >
+ )}
+
+
+ );
+};
+
+export default HomePage;
+```
+
+## Ionic Vue
+
+### Step 2: Create the Composable
+
+Create a composable that handles SDK initialization and authentication:
+
+```typescript
+// src/composables/useCometChatCalls.ts
+import { ref, readonly } from "vue";
+import { CometChatCalls } from "@cometchat/calls-sdk-javascript";
+
+interface User {
+ uid: string;
+ name: string;
+ avatar?: string;
+}
+
+// Replace with your CometChat credentials
+const APP_ID = "YOUR_APP_ID";
+const REGION = "YOUR_REGION";
+const API_KEY = "YOUR_API_KEY";
+
+// Shared state across all components
+const isReady = ref(false);
+const user = ref(null);
+const error = ref(null);
+const initialized = ref(false);
+
+export function useCometChatCalls() {
+ async function initAndLogin(uid: string): Promise {
+ if (initialized.value) {
+ return isReady.value;
+ }
+
+ try {
+ // Step 1: Initialize the SDK
+ const initResult = await CometChatCalls.init({
+ appId: APP_ID,
+ region: REGION,
+ });
+
+ if (!initResult.success) {
+ throw new Error("SDK initialization failed");
+ }
+
+ // Step 2: Check if already logged in
+ let loggedInUser = CometChatCalls.getLoggedInUser();
+
+ // Step 3: Login if not already logged in
+ if (!loggedInUser) {
+ loggedInUser = await CometChatCalls.login(uid, API_KEY);
+ }
+
+ user.value = loggedInUser;
+ isReady.value = true;
+ initialized.value = true;
+ return true;
+ } catch (err: any) {
+ console.error("CometChat Calls setup failed:", err);
+ error.value = err.message || "Setup failed";
+ return false;
+ }
+ }
+
+ return {
+ isReady: readonly(isReady),
+ user: readonly(user),
+ error: readonly(error),
+ initAndLogin,
+ };
+}
+```
+
+### Step 3: Initialize in App Component
+
+Initialize the SDK and login when the app starts:
+
+```vue
+
+
+
+
+
+
+
+
+```
+
+### Step 4: Create the Call Page
+
+Create a call page that handles joining sessions, media controls, and cleanup:
+
+```vue
+
+
+
+
+
+
+
+
+
+ {{ callError }}
+ Retry
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+
+### Step 5: Create the Home Page
+
+Create a home page where users can enter a session ID and join a call:
+
+```vue
+
+
+
+
+
+ CometChat Calls
+
+
+
+
+
+ {{ error }}
+
+
+
+
+
+ Logged in as: {{ user?.name || user?.uid }}
+
+
+ Session ID
+
+
+
+
+ Join Call
+
+
+
+
+
+
+
+```
+
+### Step 6: Configure Routes
+
+Set up the router with the home and call pages:
+
+```typescript
+// src/router/index.ts
+import { createRouter, createWebHistory } from "@ionic/vue-router";
+import { RouteRecordRaw } from "vue-router";
+import HomePage from "../views/HomePage.vue";
+import CallPage from "../views/CallPage.vue";
+
+const routes: Array = [
+ {
+ path: "/",
+ component: HomePage,
+ },
+ {
+ path: "/call/:sessionId",
+ component: CallPage,
+ },
+];
+
+const router = createRouter({
+ history: createWebHistory(import.meta.env.BASE_URL),
+ routes,
+});
+
+export default router;
+```
+
+## Related Documentation
+
+For more detailed information on specific topics covered in this guide, refer to the main documentation:
+
+- [Setup](/calls/javascript/setup) - Detailed SDK installation and initialization
+- [Authentication](/calls/javascript/authentication) - Login methods and user management
+- [Session Settings](/calls/javascript/session-settings) - All available call configuration options
+- [Join Session](/calls/javascript/join-session) - Session joining and token generation
+- [Events](/calls/javascript/events) - Complete list of event listeners
+- [Actions](/calls/javascript/actions) - All available call control methods
+- [Call Layouts](/calls/javascript/call-layouts) - Layout options and customization
+- [Participant Management](/calls/javascript/participant-management) - Managing call participants
diff --git a/calls/javascript/join-session.mdx b/calls/javascript/join-session.mdx
new file mode 100644
index 00000000..b4e88361
--- /dev/null
+++ b/calls/javascript/join-session.mdx
@@ -0,0 +1,146 @@
+---
+title: "Join Session"
+sidebarTitle: "Join Session"
+---
+
+Join a call session using one of two approaches: the quick start method with a session ID, or the advanced flow with manual token generation for more control.
+
+## Overview
+
+The CometChat Calls SDK provides two ways to join a session:
+
+| Approach | Best For | Complexity |
+|----------|----------|------------|
+| **Join with Token** | Most use cases - recommended approach | Low - Two-step process |
+| **Generate Token Separately** | Custom token management, pre-generation, caching | Medium - Separate token handling |
+
+
+Both approaches require a container element in your HTML where the call interface will be rendered.
+
+
+## Container Setup
+
+Add a container element to your HTML where the call interface will be rendered:
+
+```html
+
+```
+
+The call UI will be dynamically rendered inside this container when you join the session.
+
+## Generate Token
+
+Generate a call token for the session. Each token is unique to a specific session and user combination.
+
+```javascript
+const sessionId = "SESSION_ID";
+
+try {
+ const result = await CometChatCalls.generateToken(sessionId);
+ console.log("Token generated:", result.token);
+ // Use the token to join the session
+} catch (error) {
+ console.error("Token generation failed:", error.errorDescription);
+}
+```
+
+| Parameter | Type | Description |
+|-----------|------|-------------|
+| `sessionId` | String | Unique identifier for the call session |
+
+The method returns an object with:
+
+| Property | Type | Description |
+|----------|------|-------------|
+| `token` | String | The generated call token |
+
+
+The `generateToken()` method uses the auth token of the currently logged-in user. Ensure a user is logged in before calling this method.
+
+
+## Join Session
+
+Use the generated token to join the session along with your call settings and container element.
+
+```javascript
+const container = document.getElementById("call-container");
+
+const callSettings = {
+ sessionType: "VIDEO",
+ layout: "TILE",
+ startAudioMuted: false,
+ startVideoPaused: false,
+};
+
+const result = await CometChatCalls.joinSession(callToken, callSettings, container);
+
+if (result.error) {
+ console.error("Failed to join session:", result.error);
+} else {
+ console.log("Joined session successfully");
+}
+```
+
+| Parameter | Type | Description |
+|-----------|------|-------------|
+| `callToken` | String | Previously generated call token |
+| `callSettings` | Object | Configuration for the session (see [Session Settings](/calls/javascript/session-settings)) |
+| `container` | HTMLElement | Container element for the call UI |
+
+The method returns an object with:
+
+| Property | Type | Description |
+|----------|------|-------------|
+| `data` | undefined | Undefined on success |
+| `error` | Object \| null | Error details if join failed |
+
+## Complete Example
+
+```javascript
+const sessionId = "SESSION_ID";
+const container = document.getElementById("call-container");
+
+// Step 1: Generate token
+try {
+ const tokenResult = await CometChatCalls.generateToken(sessionId);
+
+ // Step 2: Configure session settings
+ const callSettings = {
+ sessionType: "VIDEO",
+ layout: "TILE",
+ startAudioMuted: false,
+ startVideoPaused: false,
+ };
+
+ // Step 3: Join session
+ const joinResult = await CometChatCalls.joinSession(
+ tokenResult.token,
+ callSettings,
+ container
+ );
+
+ if (joinResult.error) {
+ console.error("Failed to join session:", joinResult.error);
+ } else {
+ console.log("Joined session successfully");
+ }
+} catch (error) {
+ console.error("Error:", error);
+}
+```
+
+
+All participants joining the same call must use the same session ID.
+
+
+## Error Handling
+
+Common errors when joining a session:
+
+| Error Code | Description |
+|------------|-------------|
+| `ERROR_SDK_NOT_INITIALIZED` | SDK not initialized - call `init()` first |
+| `ERROR_AUTH_TOKEN_MISSING` | User not logged in or auth token invalid |
+| `ERROR_SESSION_ID_MISSING` | Session ID is null or empty |
+| `VALIDATION_ERROR` | Invalid call settings |
+
diff --git a/calls/javascript/nextjs-integration.mdx b/calls/javascript/nextjs-integration.mdx
new file mode 100644
index 00000000..d59b3721
--- /dev/null
+++ b/calls/javascript/nextjs-integration.mdx
@@ -0,0 +1,557 @@
+---
+title: "Next.js Integration"
+sidebarTitle: "Next.js"
+---
+
+This guide walks you through integrating the CometChat Calls SDK into a Next.js application. By the end, you'll have a working video call implementation with proper server-side rendering handling and authentication.
+
+## Prerequisites
+
+Before you begin, ensure you have:
+- A CometChat account with an app created ([Sign up](https://app.cometchat.com/signup))
+- Your App ID, Region, and API Key from the CometChat Dashboard
+- A Next.js project (App Router or Pages Router)
+- Node.js 16+ installed
+
+## Important: Client-Side Only
+
+The CometChat Calls SDK uses browser APIs (WebRTC, DOM) that are not available during server-side rendering. You must ensure the SDK only loads and runs on the client side. This guide shows you how to handle this properly with both the App Router and Pages Router.
+
+## Step 1: Install the SDK
+
+Install the CometChat Calls SDK package:
+
+```bash
+npm install @cometchat/calls-sdk-javascript
+```
+
+## Step 2: Configure Environment Variables
+
+Add your CometChat credentials to `.env.local`. The `NEXT_PUBLIC_` prefix makes these variables available in the browser:
+
+```env
+NEXT_PUBLIC_COMETCHAT_APP_ID=your_app_id
+NEXT_PUBLIC_COMETCHAT_REGION=us
+NEXT_PUBLIC_COMETCHAT_API_KEY=your_api_key
+```
+
+
+Never expose your API Key in production client-side code. Use a backend service to generate auth tokens securely. The API Key approach shown here is for development and testing only.
+
+
+## Step 3: Create the Provider
+
+Create a context provider that handles SDK initialization and user authentication. The `"use client"` directive ensures this component only runs in the browser.
+
+```tsx
+// providers/CometChatCallsProvider.tsx
+"use client";
+
+import { createContext, useContext, useEffect, useState, ReactNode } from "react";
+
+interface User {
+ uid: string;
+ name: string;
+ avatar?: string;
+}
+
+interface CometChatCallsContextType {
+ isReady: boolean;
+ user: User | null;
+ error: string | null;
+ CometChatCalls: any;
+}
+
+const CometChatCallsContext = createContext({
+ isReady: false,
+ user: null,
+ error: null,
+ CometChatCalls: null,
+});
+
+interface ProviderProps {
+ children: ReactNode;
+ uid: string;
+}
+
+export function CometChatCallsProvider({ children, uid }: ProviderProps) {
+ const [isReady, setIsReady] = useState(false);
+ const [user, setUser] = useState(null);
+ const [error, setError] = useState(null);
+ const [CometChatCalls, setCometChatCalls] = useState(null);
+
+ useEffect(() => {
+ async function initAndLogin() {
+ try {
+ // Dynamic import ensures the SDK only loads on the client
+ const { CometChatCalls: SDK } = await import("@cometchat/calls-sdk-javascript");
+
+ // Step 1: Initialize the SDK
+ const initResult = await SDK.init({
+ appId: process.env.NEXT_PUBLIC_COMETCHAT_APP_ID!,
+ region: process.env.NEXT_PUBLIC_COMETCHAT_REGION!,
+ });
+
+ if (!initResult.success) {
+ throw new Error("SDK initialization failed");
+ }
+
+ // Step 2: Check if already logged in
+ let loggedInUser = SDK.getLoggedInUser();
+
+ // Step 3: Login if not already logged in
+ if (!loggedInUser) {
+ loggedInUser = await SDK.login(
+ uid,
+ process.env.NEXT_PUBLIC_COMETCHAT_API_KEY!
+ );
+ }
+
+ setCometChatCalls(SDK);
+ setUser(loggedInUser);
+ setIsReady(true);
+ } catch (err: any) {
+ console.error("CometChat Calls setup failed:", err);
+ setError(err.message || "Setup failed");
+ }
+ }
+
+ if (uid) {
+ initAndLogin();
+ }
+ }, [uid]);
+
+ return (
+
+ {children}
+
+ );
+}
+
+export function useCometChatCalls(): CometChatCallsContextType {
+ return useContext(CometChatCallsContext);
+}
+```
+
+## Step 4: Add Provider to Layout (App Router)
+
+Wrap your application with the provider. Since the provider is a client component, you can still use it in a server component layout:
+
+```tsx
+// app/layout.tsx
+import { CometChatCallsProvider } from "@/providers/CometChatCallsProvider";
+
+export default function RootLayout({ children }: { children: React.ReactNode }) {
+ // In a real app, get this from your authentication system
+ const currentUserId = "cometchat-uid-1";
+
+ return (
+
+
+
+ {children}
+
+
+
+ );
+}
+```
+
+## Step 5: Create the Call Component
+
+Build a client-side call component that handles joining sessions, media controls, and cleanup. The component uses the SDK instance from the context provider:
+
+```tsx
+// components/CallScreen.tsx
+"use client";
+
+import { useEffect, useRef, useState } from "react";
+import { useCometChatCalls } from "@/providers/CometChatCallsProvider";
+
+interface CallScreenProps {
+ sessionId: string;
+ onCallEnd?: () => void;
+}
+
+export default function CallScreen({ sessionId, onCallEnd }: CallScreenProps) {
+ const { isReady, CometChatCalls } = useCometChatCalls();
+ const containerRef = useRef(null);
+
+ // Call state
+ const [isJoined, setIsJoined] = useState(false);
+ const [isJoining, setIsJoining] = useState(false);
+ const [isMuted, setIsMuted] = useState(false);
+ const [isVideoOff, setIsVideoOff] = useState(false);
+ const [error, setError] = useState(null);
+
+ // Store unsubscribe functions for cleanup
+ const unsubscribersRef = useRef([]);
+
+ useEffect(() => {
+ // Don't proceed if SDK isn't ready or container isn't mounted
+ if (!isReady || !CometChatCalls || !containerRef.current || !sessionId) return;
+
+ async function joinCall() {
+ setIsJoining(true);
+ setError(null);
+
+ try {
+ // Register event listeners before joining
+ unsubscribersRef.current = [
+ CometChatCalls.addEventListener("onSessionJoined", () => {
+ setIsJoined(true);
+ setIsJoining(false);
+ }),
+ CometChatCalls.addEventListener("onSessionLeft", () => {
+ setIsJoined(false);
+ onCallEnd?.();
+ }),
+ CometChatCalls.addEventListener("onAudioMuted", () => setIsMuted(true)),
+ CometChatCalls.addEventListener("onAudioUnMuted", () => setIsMuted(false)),
+ CometChatCalls.addEventListener("onVideoPaused", () => setIsVideoOff(true)),
+ CometChatCalls.addEventListener("onVideoResumed", () => setIsVideoOff(false)),
+ ];
+
+ // Generate a call token for this session
+ const tokenResult = await CometChatCalls.generateToken(sessionId);
+
+ // Join the call session
+ const joinResult = await CometChatCalls.joinSession(
+ tokenResult.token,
+ {
+ sessionType: "VIDEO",
+ layout: "TILE",
+ startAudioMuted: false,
+ startVideoPaused: false,
+ },
+ containerRef.current
+ );
+
+ if (joinResult.error) {
+ throw new Error(joinResult.error.message);
+ }
+ } catch (err: any) {
+ console.error("Failed to join call:", err);
+ setError(err.message || "Failed to join call");
+ setIsJoining(false);
+ }
+ }
+
+ joinCall();
+
+ // Cleanup when component unmounts
+ return () => {
+ unsubscribersRef.current.forEach((unsub) => unsub());
+ unsubscribersRef.current = [];
+ CometChatCalls?.leaveSession();
+ };
+ }, [isReady, CometChatCalls, sessionId, onCallEnd]);
+
+ // Control handlers
+ const toggleAudio = () => {
+ isMuted ? CometChatCalls.unMuteAudio() : CometChatCalls.muteAudio();
+ };
+
+ const toggleVideo = () => {
+ isVideoOff ? CometChatCalls.resumeVideo() : CometChatCalls.pauseVideo();
+ };
+
+ const leaveCall = () => {
+ CometChatCalls.leaveSession();
+ };
+
+ // Loading state
+ if (!isReady) {
+ return Initializing...
;
+ }
+
+ // Error state
+ if (error) {
+ return (
+
+
Error: {error}
+
window.location.reload()}
+ className="px-4 py-2 bg-blue-500 text-white rounded"
+ >
+ Retry
+
+
+ );
+ }
+
+ return (
+
+ {/* Video container - SDK renders the call UI here */}
+
+
+ {/* Loading overlay */}
+ {isJoining && (
+
Joining call...
+ )}
+
+ {/* Call controls */}
+ {isJoined && (
+
+
+ {isMuted ? "Unmute" : "Mute"}
+
+
+ {isVideoOff ? "Start Video" : "Stop Video"}
+
+
+ Leave Call
+
+
+ )}
+
+ );
+}
+```
+
+## Step 6: Create the Call Page
+
+Create a page that uses the call component. This example shows a simple interface where users can enter a session ID and join a call:
+
+```tsx
+// app/page.tsx
+"use client";
+
+import { useState } from "react";
+import { useCometChatCalls } from "@/providers/CometChatCallsProvider";
+import CallScreen from "@/components/CallScreen";
+
+export default function HomePage() {
+ const { isReady, user, error } = useCometChatCalls();
+ const [sessionId, setSessionId] = useState("");
+ const [isInCall, setIsInCall] = useState(false);
+
+ if (error) {
+ return (
+
+ );
+ }
+
+ if (!isReady) {
+ return (
+
+ );
+ }
+
+ if (isInCall) {
+ return (
+ setIsInCall(false)}
+ />
+ );
+ }
+
+ return (
+
+
CometChat Calls
+
Logged in as: {user?.name || user?.uid}
+
+
+ setSessionId(e.target.value)}
+ className="w-full p-3 border rounded-lg"
+ />
+ setIsInCall(true)}
+ disabled={!sessionId}
+ className="w-full p-3 bg-purple-600 text-white rounded-lg disabled:opacity-50"
+ >
+ Join Call
+
+
+
+ );
+}
+```
+
+## Dynamic Route for Call Sessions
+
+For a cleaner URL structure, create a dynamic route that accepts the session ID as a parameter:
+
+```tsx
+// app/call/[sessionId]/page.tsx
+"use client";
+
+import { useParams, useRouter } from "next/navigation";
+import CallScreen from "@/components/CallScreen";
+
+export default function CallPage() {
+ const params = useParams();
+ const router = useRouter();
+ const sessionId = params.sessionId as string;
+
+ return (
+ router.push("/")}
+ />
+ );
+}
+```
+
+## Pages Router Setup
+
+If you're using the Pages Router instead of the App Router, use dynamic imports to prevent server-side rendering of the SDK:
+
+```tsx
+// pages/_app.tsx
+import type { AppProps } from "next/app";
+import dynamic from "next/dynamic";
+
+const CometChatCallsProvider = dynamic(
+ () => import("@/providers/CometChatCallsProvider").then(mod => mod.CometChatCallsProvider),
+ { ssr: false }
+);
+
+export default function App({ Component, pageProps }: AppProps) {
+ const currentUserId = "cometchat-uid-1";
+
+ return (
+
+
+
+ );
+}
+```
+
+```tsx
+// pages/call/[sessionId].tsx
+import dynamic from "next/dynamic";
+import { useRouter } from "next/router";
+
+const CallScreen = dynamic(() => import("@/components/CallScreen"), {
+ ssr: false,
+ loading: () => Loading call...
,
+});
+
+export default function CallPage() {
+ const router = useRouter();
+ const { sessionId } = router.query;
+
+ if (!sessionId) {
+ return Loading...
;
+ }
+
+ return (
+ router.push("/")}
+ />
+ );
+}
+```
+
+## Custom Hook (Optional)
+
+For more complex applications, extract call logic into a reusable hook:
+
+```tsx
+// hooks/useCall.ts
+"use client";
+
+import { useState, useCallback, useRef, useEffect } from "react";
+import { useCometChatCalls } from "@/providers/CometChatCallsProvider";
+
+export function useCall() {
+ const { CometChatCalls, isReady } = useCometChatCalls();
+ const [isInCall, setIsInCall] = useState(false);
+ const [isMuted, setIsMuted] = useState(false);
+ const [isVideoOff, setIsVideoOff] = useState(false);
+ const [participants, setParticipants] = useState([]);
+ const unsubscribersRef = useRef([]);
+
+ const joinCall = useCallback(async (sessionId: string, container: HTMLElement, settings = {}) => {
+ if (!CometChatCalls) return;
+
+ // Setup listeners
+ unsubscribersRef.current = [
+ CometChatCalls.addEventListener("onAudioMuted", () => setIsMuted(true)),
+ CometChatCalls.addEventListener("onAudioUnMuted", () => setIsMuted(false)),
+ CometChatCalls.addEventListener("onVideoPaused", () => setIsVideoOff(true)),
+ CometChatCalls.addEventListener("onVideoResumed", () => setIsVideoOff(false)),
+ CometChatCalls.addEventListener("onParticipantListChanged", setParticipants),
+ CometChatCalls.addEventListener("onSessionLeft", () => setIsInCall(false)),
+ ];
+
+ const tokenResult = await CometChatCalls.generateToken(sessionId);
+ await CometChatCalls.joinSession(
+ tokenResult.token,
+ { sessionType: "VIDEO", layout: "TILE", ...settings },
+ container
+ );
+ setIsInCall(true);
+ }, [CometChatCalls]);
+
+ const leaveCall = useCallback(() => {
+ CometChatCalls?.leaveSession();
+ unsubscribersRef.current.forEach((unsub) => unsub());
+ unsubscribersRef.current = [];
+ setIsInCall(false);
+ }, [CometChatCalls]);
+
+ const toggleAudio = useCallback(() => {
+ isMuted ? CometChatCalls?.unMuteAudio() : CometChatCalls?.muteAudio();
+ }, [CometChatCalls, isMuted]);
+
+ const toggleVideo = useCallback(() => {
+ isVideoOff ? CometChatCalls?.resumeVideo() : CometChatCalls?.pauseVideo();
+ }, [CometChatCalls, isVideoOff]);
+
+ // Cleanup on unmount
+ useEffect(() => {
+ return () => {
+ unsubscribersRef.current.forEach((unsub) => unsub());
+ };
+ }, []);
+
+ return {
+ isReady,
+ isInCall,
+ isMuted,
+ isVideoOff,
+ participants,
+ joinCall,
+ leaveCall,
+ toggleAudio,
+ toggleVideo,
+ };
+}
+```
+
+## Related Documentation
+
+For more detailed information on specific topics covered in this guide, refer to the main documentation:
+
+- [Setup](/calls/javascript/setup) - Detailed SDK installation and initialization
+- [Authentication](/calls/javascript/authentication) - Login methods and user management
+- [Session Settings](/calls/javascript/session-settings) - All available call configuration options
+- [Join Session](/calls/javascript/join-session) - Session joining and token generation
+- [Events](/calls/javascript/events) - Complete list of event listeners
+- [Actions](/calls/javascript/actions) - All available call control methods
+- [Call Layouts](/calls/javascript/call-layouts) - Layout options and customization
+- [Participant Management](/calls/javascript/participant-management) - Managing call participants
diff --git a/calls/javascript/overview.mdx b/calls/javascript/overview.mdx
new file mode 100644
index 00000000..7c381e4b
--- /dev/null
+++ b/calls/javascript/overview.mdx
@@ -0,0 +1,155 @@
+---
+title: "Calls SDK"
+sidebarTitle: "Overview"
+---
+
+The CometChat Calls SDK enables real-time voice and video calling capabilities in your web application. Built on top of WebRTC, it provides a complete calling solution with built-in UI components and extensive customization options.
+
+
+**Faster Integration with UI Kits**
+
+If you're using CometChat UI Kits, voice and video calling can be quickly integrated:
+- Incoming & outgoing call screens
+- Call buttons with one-tap calling
+- Call logs with history
+
+👉 [React UI Kit Calling Integration](/ui-kit/react/calling-integration)
+
+Use this Calls SDK directly only if you need custom call UI or advanced control.
+
+
+## Prerequisites
+
+Before integrating the Calls SDK, ensure you have:
+
+1. **CometChat Account**: [Sign up](https://app.cometchat.com/signup) and create an app to get your App ID, Region, and API Key
+2. **CometChat Users**: Users must exist in CometChat to use calling features. For testing, create users via the [Dashboard](https://app.cometchat.com) or [REST API](/rest-api/chat-apis/users/create-user). Authentication is handled by the Calls SDK - see [Authentication](/calls/javascript/authentication)
+3. **Browser Requirements**: See [Browser Compatibility](#browser-compatibility) below
+4. **Permissions**: Camera and microphone permissions for video/audio calls
+
+## Browser Compatibility
+
+The Calls SDK requires a modern browser with WebRTC support:
+
+| Browser | Minimum Version | Notes |
+|---------|-----------------|-------|
+| Chrome | 72+ | Full support |
+| Firefox | 68+ | Full support |
+| Safari | 12.1+ | Full support |
+| Edge | 79+ | Chromium-based |
+| Opera | 60+ | Full support |
+| Samsung Internet | 12+ | Full support |
+
+### Requirements
+
+- **HTTPS**: Required for camera/microphone access in production. Localhost is exempt during development.
+- **WebRTC**: The browser must support WebRTC APIs (`getUserMedia`, `RTCPeerConnection`)
+- **JavaScript**: ES6+ support required
+
+### Mobile Browsers
+
+| Browser | Support |
+|---------|---------|
+| Chrome for Android | ✅ Full support |
+| Safari for iOS | ✅ iOS 12.1+ |
+| Firefox for Android | ✅ Full support |
+| Samsung Internet | ✅ Full support |
+
+
+For native mobile apps, consider using the [iOS](/calls/ios/overview) or [Android](/calls/android/overview) SDKs for better performance and native features like CallKit/VoIP.
+
+
+## Framework Integrations
+
+Get started quickly with framework-specific guides that include complete setup, authentication, and working call implementations:
+
+
+
+
+ Context provider pattern with hooks
+
+
+
+ Composables and reactive state management
+
+
+
+ Service-based architecture with RxJS
+
+
+
+ SSR handling with App Router and Pages Router
+
+
+
+ Cross-platform with Angular, React, and Vue
+
+
+
+
+## Call Flow
+
+```mermaid
+sequenceDiagram
+ participant App
+ participant CometChatCalls
+
+ App->>CometChatCalls: init()
+ App->>CometChatCalls: login()
+ App->>CometChatCalls: generateToken()
+ App->>CometChatCalls: joinSession()
+ CometChatCalls-->>App: Session joined
+ App->>CometChatCalls: Actions (mute, pause, etc.)
+ CometChatCalls-->>App: Event callbacks
+ App->>CometChatCalls: leaveSession()
+```
+
+## Features
+
+
+
+
+ Tile, Sidebar, and Spotlight view modes for different call scenarios
+
+
+
+ Record call sessions for later playback
+
+
+
+ Retrieve call history and details
+
+
+
+ Mute, pin, and manage call participants
+
+
+
+ Share your screen with other participants
+
+
+
+ Apply blur or custom image backgrounds
+
+
+
+ Signal to get attention during calls
+
+
+
+ Automatic session termination when alone in a call
+
+
+
+
+## Architecture
+
+The SDK is organized around these core components:
+
+| Component | Description |
+|-----------|-------------|
+| `CometChatCalls` | Main entry point for SDK initialization, authentication, session management, and call actions |
+| `CallAppSettings` | Configuration object for SDK initialization (App ID, Region) |
+| `CallSettings` | Configuration object for individual call sessions |
+| `addEventListener` | Method to register event listeners for session, participant, media, and UI events |
+
diff --git a/calls/javascript/participant-management.mdx b/calls/javascript/participant-management.mdx
new file mode 100644
index 00000000..105430f9
--- /dev/null
+++ b/calls/javascript/participant-management.mdx
@@ -0,0 +1,170 @@
+---
+title: "Participant Management"
+sidebarTitle: "Participant Management"
+---
+
+Manage call participants including muting, pinning, and monitoring their status during a call.
+
+## Participant Actions
+
+### Mute Participant
+
+Mute a specific participant's audio. This is typically a moderator action.
+
+```javascript
+CometChatCalls.muteParticipant(participantId);
+```
+
+| Parameter | Type | Description |
+|-----------|------|-------------|
+| `participantId` | String | The participant's unique identifier |
+
+### Pause Participant Video
+
+Pause a specific participant's video. This is typically a moderator action.
+
+```javascript
+CometChatCalls.pauseParticipantVideo(participantId);
+```
+
+| Parameter | Type | Description |
+|-----------|------|-------------|
+| `participantId` | String | The participant's unique identifier |
+
+### Pin Participant
+
+Pin a participant to keep them prominently displayed regardless of who is speaking.
+
+```javascript
+CometChatCalls.pinParticipant(participantId, type);
+```
+
+| Parameter | Type | Description |
+|-----------|------|-------------|
+| `participantId` | String | The participant's unique identifier |
+| `type` | String | The participant type |
+
+### Unpin Participant
+
+Remove the pin, returning to automatic speaker highlighting.
+
+```javascript
+CometChatCalls.unpinParticipant();
+```
+
+## Participant Events
+
+### Participant Joined
+
+Fired when a participant joins the call:
+
+```javascript
+CometChatCalls.addEventListener("onParticipantJoined", (participant) => {
+ console.log(`${participant.name} joined the call`);
+});
+```
+
+### Participant Left
+
+Fired when a participant leaves the call:
+
+```javascript
+CometChatCalls.addEventListener("onParticipantLeft", (participant) => {
+ console.log(`${participant.name} left the call`);
+});
+```
+
+### Participant List Changed
+
+Fired when the participant list is updated:
+
+```javascript
+CometChatCalls.addEventListener("onParticipantListChanged", (participants) => {
+ console.log(`Total participants: ${participants.length}`);
+ participants.forEach(p => console.log(p.name));
+});
+```
+
+### Participant Audio State
+
+Monitor when participants mute or unmute:
+
+```javascript
+CometChatCalls.addEventListener("onParticipantAudioMuted", (participant) => {
+ console.log(`${participant.name} muted their audio`);
+});
+
+CometChatCalls.addEventListener("onParticipantAudioUnmuted", (participant) => {
+ console.log(`${participant.name} unmuted their audio`);
+});
+```
+
+### Participant Video State
+
+Monitor when participants turn their camera on or off:
+
+```javascript
+CometChatCalls.addEventListener("onParticipantVideoPaused", (participant) => {
+ console.log(`${participant.name} turned off their camera`);
+});
+
+CometChatCalls.addEventListener("onParticipantVideoResumed", (participant) => {
+ console.log(`${participant.name} turned on their camera`);
+});
+```
+
+### Dominant Speaker Changed
+
+Fired when the active speaker changes:
+
+```javascript
+CometChatCalls.addEventListener("onDominantSpeakerChanged", (participant) => {
+ console.log(`Active speaker: ${participant.name}`);
+});
+```
+
+## Participant List UI
+
+### Show/Hide Participant List Button
+
+Control visibility of the participant list button:
+
+```javascript
+const callSettings = {
+ hideParticipantListButton: false, // Show the button
+ // ... other settings
+};
+```
+
+### Listen for Participant List Events
+
+Monitor when the participant list panel is opened or closed:
+
+```javascript
+CometChatCalls.addEventListener("onParticipantListVisible", () => {
+ console.log("Participant list opened");
+});
+
+CometChatCalls.addEventListener("onParticipantListHidden", () => {
+ console.log("Participant list closed");
+});
+```
+
+### Listen for Participant List Button Clicks
+
+Intercept participant list button clicks:
+
+```javascript
+CometChatCalls.addEventListener("onParticipantListButtonClicked", () => {
+ console.log("Participant list button clicked");
+});
+```
+
+## Participant Object
+
+| Property | Type | Description |
+|----------|------|-------------|
+| `uid` | String | Unique identifier (CometChat user ID) |
+| `name` | String | Display name |
+| `avatar` | String | URL of avatar image |
+
diff --git a/calls/javascript/permissions-handling.mdx b/calls/javascript/permissions-handling.mdx
new file mode 100644
index 00000000..b7b43fb4
--- /dev/null
+++ b/calls/javascript/permissions-handling.mdx
@@ -0,0 +1,319 @@
+---
+title: "Permissions Handling"
+sidebarTitle: "Permissions Handling"
+---
+
+Handle camera and microphone permissions gracefully to provide a good user experience when joining calls.
+
+## Permission Requirements
+
+The Calls SDK requires:
+- **Microphone**: Required for audio calls
+- **Camera**: Required for video calls (optional for audio-only)
+
+## Check Permissions Before Joining
+
+Check if permissions are granted before attempting to join a call:
+
+```javascript
+async function checkMediaPermissions() {
+ try {
+ const stream = await navigator.mediaDevices.getUserMedia({
+ video: true,
+ audio: true
+ });
+
+ // Stop the tracks immediately - we just needed to check permissions
+ stream.getTracks().forEach(track => track.stop());
+
+ return { video: true, audio: true };
+ } catch (error) {
+ if (error.name === "NotAllowedError") {
+ return { video: false, audio: false, denied: true };
+ }
+ if (error.name === "NotFoundError") {
+ return { video: false, audio: false, notFound: true };
+ }
+ throw error;
+ }
+}
+```
+
+## Query Permission Status
+
+Use the Permissions API to check status without prompting:
+
+```javascript
+async function getPermissionStatus() {
+ const permissions = {};
+
+ try {
+ const camera = await navigator.permissions.query({ name: "camera" });
+ permissions.camera = camera.state; // "granted", "denied", or "prompt"
+
+ const microphone = await navigator.permissions.query({ name: "microphone" });
+ permissions.microphone = microphone.state;
+ } catch (error) {
+ // Permissions API not supported
+ permissions.supported = false;
+ }
+
+ return permissions;
+}
+
+// Usage
+const status = await getPermissionStatus();
+if (status.camera === "denied" || status.microphone === "denied") {
+ showPermissionDeniedMessage();
+}
+```
+
+## Handle Permission Denial
+
+Show helpful messages when permissions are denied:
+
+```javascript
+async function joinCallWithPermissionCheck(sessionId, container) {
+ const permissions = await checkMediaPermissions();
+
+ if (permissions.denied) {
+ showError(
+ "Camera and microphone access denied. " +
+ "Please enable permissions in your browser settings and refresh the page."
+ );
+ return;
+ }
+
+ if (permissions.notFound) {
+ showError(
+ "No camera or microphone found. " +
+ "Please connect a device and try again."
+ );
+ return;
+ }
+
+ // Permissions granted, proceed with joining
+ const tokenResult = await CometChatCalls.generateToken(sessionId);
+ await CometChatCalls.joinSession(tokenResult.token, { sessionType: "VIDEO" }, container);
+}
+```
+
+## Permission Request UI
+
+Create a pre-call permission check screen:
+
+```javascript
+function PermissionCheck({ onPermissionsGranted, onPermissionsDenied }) {
+ const [status, setStatus] = useState("checking");
+
+ useEffect(() => {
+ checkPermissions();
+ }, []);
+
+ async function checkPermissions() {
+ const result = await checkMediaPermissions();
+
+ if (result.video && result.audio) {
+ setStatus("granted");
+ onPermissionsGranted();
+ } else if (result.denied) {
+ setStatus("denied");
+ onPermissionsDenied();
+ } else {
+ setStatus("prompt");
+ }
+ }
+
+ async function requestPermissions() {
+ setStatus("requesting");
+ const result = await checkMediaPermissions();
+
+ if (result.video && result.audio) {
+ setStatus("granted");
+ onPermissionsGranted();
+ } else {
+ setStatus("denied");
+ onPermissionsDenied();
+ }
+ }
+
+ if (status === "checking") {
+ return Checking permissions...
;
+ }
+
+ if (status === "prompt") {
+ return (
+
+
We need access to your camera and microphone for the call.
+
Allow Access
+
+ );
+ }
+
+ if (status === "denied") {
+ return (
+
+
Camera and microphone access was denied.
+
To join the call, please:
+
+ Click the camera icon in your browser's address bar
+ Select "Allow" for camera and microphone
+ Refresh this page
+
+
+ );
+ }
+
+ return null;
+}
+```
+
+## Listen for Permission Changes
+
+Monitor permission changes in real-time:
+
+```javascript
+async function watchPermissions(onChange) {
+ try {
+ const camera = await navigator.permissions.query({ name: "camera" });
+ const microphone = await navigator.permissions.query({ name: "microphone" });
+
+ camera.addEventListener("change", () => {
+ onChange({ camera: camera.state, microphone: microphone.state });
+ });
+
+ microphone.addEventListener("change", () => {
+ onChange({ camera: camera.state, microphone: microphone.state });
+ });
+ } catch (error) {
+ console.log("Permission watching not supported");
+ }
+}
+
+// Usage
+watchPermissions((status) => {
+ if (status.camera === "denied" || status.microphone === "denied") {
+ // Handle permission revocation during call
+ showWarning("Permissions were revoked. Some features may not work.");
+ }
+});
+```
+
+## HTTPS Requirement
+
+Camera and microphone access requires a secure context:
+
+```javascript
+function isSecureContext() {
+ // Localhost is considered secure for development
+ if (window.location.hostname === "localhost" ||
+ window.location.hostname === "127.0.0.1") {
+ return true;
+ }
+
+ return window.location.protocol === "https:";
+}
+
+if (!isSecureContext()) {
+ showError("Video calls require HTTPS. Please access this page via HTTPS.");
+}
+```
+
+## Browser-Specific Instructions
+
+Provide browser-specific help for enabling permissions:
+
+```javascript
+function getBrowserPermissionInstructions() {
+ const ua = navigator.userAgent;
+
+ if (ua.includes("Chrome")) {
+ return {
+ browser: "Chrome",
+ steps: [
+ "Click the lock/camera icon in the address bar",
+ "Set Camera and Microphone to 'Allow'",
+ "Refresh the page"
+ ]
+ };
+ }
+
+ if (ua.includes("Firefox")) {
+ return {
+ browser: "Firefox",
+ steps: [
+ "Click the permissions icon (camera/lock) in the address bar",
+ "Remove the block for camera and microphone",
+ "Refresh the page"
+ ]
+ };
+ }
+
+ if (ua.includes("Safari")) {
+ return {
+ browser: "Safari",
+ steps: [
+ "Go to Safari > Settings > Websites",
+ "Select Camera and Microphone",
+ "Set this website to 'Allow'",
+ "Refresh the page"
+ ]
+ };
+ }
+
+ return {
+ browser: "your browser",
+ steps: [
+ "Check your browser settings for camera and microphone permissions",
+ "Allow access for this website",
+ "Refresh the page"
+ ]
+ };
+}
+```
+
+## Audio-Only Fallback
+
+If camera permission is denied but microphone is available, offer audio-only mode:
+
+```javascript
+async function joinWithFallback(sessionId, container) {
+ let hasVideo = false;
+ let hasAudio = false;
+
+ // Check audio
+ try {
+ const audioStream = await navigator.mediaDevices.getUserMedia({ audio: true });
+ audioStream.getTracks().forEach(track => track.stop());
+ hasAudio = true;
+ } catch (e) {
+ console.log("No audio permission");
+ }
+
+ // Check video
+ try {
+ const videoStream = await navigator.mediaDevices.getUserMedia({ video: true });
+ videoStream.getTracks().forEach(track => track.stop());
+ hasVideo = true;
+ } catch (e) {
+ console.log("No video permission");
+ }
+
+ if (!hasAudio) {
+ showError("Microphone access is required to join the call.");
+ return;
+ }
+
+ const tokenResult = await CometChatCalls.generateToken(sessionId);
+
+ await CometChatCalls.joinSession(tokenResult.token, {
+ sessionType: hasVideo ? "VIDEO" : "VOICE",
+ startVideoPaused: !hasVideo,
+ }, container);
+
+ if (!hasVideo) {
+ showInfo("Joined in audio-only mode. Enable camera permission for video.");
+ }
+}
+```
+
diff --git a/calls/javascript/picture-in-picture.mdx b/calls/javascript/picture-in-picture.mdx
new file mode 100644
index 00000000..1648a701
--- /dev/null
+++ b/calls/javascript/picture-in-picture.mdx
@@ -0,0 +1,114 @@
+---
+title: "Picture-in-Picture"
+sidebarTitle: "Picture-in-Picture"
+---
+
+Picture-in-Picture (PiP) allows the call video to continue playing in a floating window while users interact with other content on the page or other browser tabs.
+
+## Enable Picture-in-Picture
+
+Enable PiP mode during a call:
+
+```javascript
+CometChatCalls.enablePictureInPictureLayout();
+```
+
+## Disable Picture-in-Picture
+
+Return to the normal call view:
+
+```javascript
+CometChatCalls.disablePictureInPictureLayout();
+```
+
+## Browser Support
+
+Picture-in-Picture support varies by browser:
+
+| Browser | Support | Notes |
+|---------|---------|-------|
+| Chrome | ✅ Full support | Chrome 70+ |
+| Edge | ✅ Full support | Chromium-based |
+| Safari | ✅ Full support | Safari 13.1+ |
+| Firefox | ⚠️ Limited | Behind flag in some versions |
+| Opera | ✅ Full support | Opera 57+ |
+
+## Check PiP Support
+
+Before enabling PiP, check if the browser supports it:
+
+```javascript
+function isPiPSupported() {
+ return document.pictureInPictureEnabled || false;
+}
+
+if (isPiPSupported()) {
+ CometChatCalls.enablePictureInPictureLayout();
+} else {
+ console.log("Picture-in-Picture not supported in this browser");
+}
+```
+
+## PiP Events
+
+The SDK doesn't provide specific PiP events, but you can listen to the browser's native PiP events on the video element if needed:
+
+```javascript
+// If you have access to the video element
+videoElement.addEventListener("enterpictureinpicture", () => {
+ console.log("Entered PiP mode");
+});
+
+videoElement.addEventListener("leavepictureinpicture", () => {
+ console.log("Left PiP mode");
+});
+```
+
+## User-Initiated PiP
+
+Browsers typically require PiP to be triggered by a user gesture (click, tap). Wrap the enable call in a button handler:
+
+```javascript
+document.getElementById("pip-btn").addEventListener("click", () => {
+ CometChatCalls.enablePictureInPictureLayout();
+});
+```
+
+## Auto-PiP on Tab Switch
+
+Some browsers support automatic PiP when switching tabs. This is a browser-level feature and may require user permission.
+
+```javascript
+// Check if auto-PiP is available (Chrome)
+if ("documentPictureInPicture" in window) {
+ // Document PiP API available
+}
+```
+
+## Styling Considerations
+
+When PiP is active:
+- The main call container may appear empty or show a placeholder
+- Consider showing a message indicating the call is in PiP mode
+- Provide a button to exit PiP and return to the full view
+
+```javascript
+let isPiPActive = false;
+
+function togglePiP() {
+ if (isPiPActive) {
+ CometChatCalls.disablePictureInPictureLayout();
+ isPiPActive = false;
+ } else {
+ CometChatCalls.enablePictureInPictureLayout();
+ isPiPActive = true;
+ }
+ updateUI();
+}
+
+function updateUI() {
+ const placeholder = document.getElementById("pip-placeholder");
+ placeholder.style.display = isPiPActive ? "block" : "none";
+}
+```
+
diff --git a/calls/javascript/raise-hand.mdx b/calls/javascript/raise-hand.mdx
new file mode 100644
index 00000000..b012761d
--- /dev/null
+++ b/calls/javascript/raise-hand.mdx
@@ -0,0 +1,69 @@
+---
+title: "Raise Hand"
+sidebarTitle: "Raise Hand"
+---
+
+The raise hand feature allows participants to signal that they want to speak or get attention during a call without interrupting the current speaker.
+
+## Raise Hand
+
+Show a hand-raised indicator to other participants:
+
+```javascript
+CometChatCalls.raiseHand();
+```
+
+## Lower Hand
+
+Remove the hand-raised indicator:
+
+```javascript
+CometChatCalls.lowerHand();
+```
+
+## Raise Hand Events
+
+### Participant Hand Raised
+
+Fired when a participant raises their hand:
+
+```javascript
+CometChatCalls.addEventListener("onParticipantHandRaised", (participant) => {
+ console.log(`${participant.name} raised their hand`);
+ // Show notification or update UI
+});
+```
+
+### Participant Hand Lowered
+
+Fired when a participant lowers their hand:
+
+```javascript
+CometChatCalls.addEventListener("onParticipantHandLowered", (participant) => {
+ console.log(`${participant.name} lowered their hand`);
+});
+```
+
+## Raise Hand Button
+
+### Hide Raise Hand Button
+
+To hide the raise hand button from the control panel:
+
+```javascript
+const callSettings = {
+ hideRaiseHandButton: true,
+ // ... other settings
+};
+```
+
+### Listen for Button Clicks
+
+Intercept raise hand button clicks for custom behavior:
+
+```javascript
+CometChatCalls.addEventListener("onRaiseHandButtonClicked", () => {
+ console.log("Raise hand button clicked");
+});
+```
+
diff --git a/calls/javascript/react-integration.mdx b/calls/javascript/react-integration.mdx
new file mode 100644
index 00000000..132a1435
--- /dev/null
+++ b/calls/javascript/react-integration.mdx
@@ -0,0 +1,453 @@
+---
+title: "React Integration"
+sidebarTitle: "React"
+---
+
+This guide walks you through integrating the CometChat Calls SDK into a React application. By the end, you'll have a working video call implementation with proper state management and lifecycle handling.
+
+## Prerequisites
+
+Before you begin, ensure you have:
+- A CometChat account with an app created ([Sign up](https://app.cometchat.com/signup))
+- Your App ID, Region, and API Key from the CometChat Dashboard
+- A React project (Create React App, Vite, or similar)
+- Node.js 16+ installed
+
+## Step 1: Install the SDK
+
+Install the CometChat Calls SDK package:
+
+```bash
+npm install @cometchat/calls-sdk-javascript
+```
+
+## Step 2: Initialize and Login
+
+Create a provider component to handle SDK initialization and authentication. This ensures the SDK is ready before any call components render.
+
+```jsx
+// src/providers/CometChatCallsProvider.jsx
+import { createContext, useContext, useEffect, useState } from "react";
+import { CometChatCalls } from "@cometchat/calls-sdk-javascript";
+
+const CometChatCallsContext = createContext({
+ isReady: false,
+ user: null,
+ error: null,
+});
+
+const APP_ID = "YOUR_APP_ID"; // Replace with your App ID
+const REGION = "YOUR_REGION"; // Replace with your Region (us, eu, in)
+const API_KEY = "YOUR_API_KEY"; // Replace with your API Key
+
+export function CometChatCallsProvider({ children, uid }) {
+ const [isReady, setIsReady] = useState(false);
+ const [user, setUser] = useState(null);
+ const [error, setError] = useState(null);
+
+ useEffect(() => {
+ async function initAndLogin() {
+ try {
+ // Step 1: Initialize the SDK
+ const initResult = await CometChatCalls.init({
+ appId: APP_ID,
+ region: REGION,
+ });
+
+ if (!initResult.success) {
+ throw new Error("SDK initialization failed");
+ }
+
+ // Step 2: Check if already logged in
+ let loggedInUser = CometChatCalls.getLoggedInUser();
+
+ // Step 3: Login if not already logged in
+ if (!loggedInUser) {
+ loggedInUser = await CometChatCalls.login(uid, API_KEY);
+ }
+
+ setUser(loggedInUser);
+ setIsReady(true);
+ } catch (err) {
+ console.error("CometChat Calls setup failed:", err);
+ setError(err.message || "Setup failed");
+ }
+ }
+
+ if (uid) {
+ initAndLogin();
+ }
+ }, [uid]);
+
+ return (
+
+ {children}
+
+ );
+}
+
+export function useCometChatCalls() {
+ return useContext(CometChatCallsContext);
+}
+```
+
+## Step 3: Wrap Your App
+
+Add the provider to your app's root component:
+
+```jsx
+// src/App.jsx
+import { CometChatCallsProvider } from "./providers/CometChatCallsProvider";
+import CallPage from "./pages/CallPage";
+
+function App() {
+ // In a real app, get this from your authentication system
+ const currentUserId = "cometchat-uid-1";
+
+ return (
+
+
+
+ );
+}
+
+export default App;
+```
+
+## Step 4: Create the Call Component
+
+Build a call component that handles joining, controls, and cleanup:
+
+```jsx
+// src/components/CallScreen.jsx
+import { useEffect, useRef, useState } from "react";
+import { CometChatCalls } from "@cometchat/calls-sdk-javascript";
+import { useCometChatCalls } from "../providers/CometChatCallsProvider";
+
+export default function CallScreen({ sessionId, onCallEnd }) {
+ const { isReady } = useCometChatCalls();
+ const containerRef = useRef(null);
+
+ // Call state
+ const [isJoined, setIsJoined] = useState(false);
+ const [isJoining, setIsJoining] = useState(false);
+ const [isMuted, setIsMuted] = useState(false);
+ const [isVideoOff, setIsVideoOff] = useState(false);
+ const [error, setError] = useState(null);
+
+ // Store unsubscribe functions for cleanup
+ const unsubscribersRef = useRef([]);
+
+ useEffect(() => {
+ // Don't proceed if SDK isn't ready or container isn't mounted
+ if (!isReady || !containerRef.current || !sessionId) return;
+
+ async function joinCall() {
+ setIsJoining(true);
+ setError(null);
+
+ try {
+ // Register event listeners before joining
+ unsubscribersRef.current = [
+ CometChatCalls.addEventListener("onSessionJoined", () => {
+ setIsJoined(true);
+ setIsJoining(false);
+ }),
+ CometChatCalls.addEventListener("onSessionLeft", () => {
+ setIsJoined(false);
+ onCallEnd?.();
+ }),
+ CometChatCalls.addEventListener("onAudioMuted", () => setIsMuted(true)),
+ CometChatCalls.addEventListener("onAudioUnMuted", () => setIsMuted(false)),
+ CometChatCalls.addEventListener("onVideoPaused", () => setIsVideoOff(true)),
+ CometChatCalls.addEventListener("onVideoResumed", () => setIsVideoOff(false)),
+ ];
+
+ // Generate a call token for this session
+ const tokenResult = await CometChatCalls.generateToken(sessionId);
+
+ // Join the call session
+ const joinResult = await CometChatCalls.joinSession(
+ tokenResult.token,
+ {
+ sessionType: "VIDEO",
+ layout: "TILE",
+ startAudioMuted: false,
+ startVideoPaused: false,
+ },
+ containerRef.current
+ );
+
+ if (joinResult.error) {
+ throw new Error(joinResult.error.message);
+ }
+ } catch (err) {
+ console.error("Failed to join call:", err);
+ setError(err.message || "Failed to join call");
+ setIsJoining(false);
+ }
+ }
+
+ joinCall();
+
+ // Cleanup when component unmounts
+ return () => {
+ unsubscribersRef.current.forEach((unsub) => unsub());
+ unsubscribersRef.current = [];
+ CometChatCalls.leaveSession();
+ };
+ }, [isReady, sessionId, onCallEnd]);
+
+ // Control handlers
+ const toggleAudio = () => {
+ isMuted ? CometChatCalls.unMuteAudio() : CometChatCalls.muteAudio();
+ };
+
+ const toggleVideo = () => {
+ isVideoOff ? CometChatCalls.resumeVideo() : CometChatCalls.pauseVideo();
+ };
+
+ const leaveCall = () => {
+ CometChatCalls.leaveSession();
+ };
+
+ // Loading state
+ if (!isReady) {
+ return Initializing...
;
+ }
+
+ // Error state
+ if (error) {
+ return (
+
+
Error: {error}
+
window.location.reload()}>Retry
+
+ );
+ }
+
+ return (
+
+ {/* Video container - SDK renders the call UI here */}
+
+
+ {/* Loading overlay */}
+ {isJoining && (
+
Joining call...
+ )}
+
+ {/* Call controls */}
+ {isJoined && (
+
+
+ {isMuted ? "Unmute" : "Mute"}
+
+
+ {isVideoOff ? "Start Video" : "Stop Video"}
+
+
+ Leave Call
+
+
+ )}
+
+ );
+}
+```
+
+## Step 5: Use the Call Component
+
+Create a page that uses the call component:
+
+```jsx
+// src/pages/CallPage.jsx
+import { useState } from "react";
+import { useCometChatCalls } from "../providers/CometChatCallsProvider";
+import CallScreen from "../components/CallScreen";
+
+export default function CallPage() {
+ const { isReady, user, error } = useCometChatCalls();
+ const [sessionId, setSessionId] = useState("");
+ const [isInCall, setIsInCall] = useState(false);
+
+ if (error) {
+ return Error: {error}
;
+ }
+
+ if (!isReady) {
+ return Loading...
;
+ }
+
+ if (isInCall) {
+ return (
+ setIsInCall(false)}
+ />
+ );
+ }
+
+ return (
+
+
CometChat Calls
+
Logged in as: {user?.name || user?.uid}
+
+
+ setSessionId(e.target.value)}
+ style={{ width: "100%", padding: "12px", marginBottom: "10px" }}
+ />
+ setIsInCall(true)}
+ disabled={!sessionId}
+ style={{ width: "100%", padding: "12px", backgroundColor: "#6851D6", color: "white", border: "none", borderRadius: "8px" }}
+ >
+ Join Call
+
+
+
+ );
+}
+```
+
+## Custom Hook (Optional)
+
+For more complex applications, extract call logic into a reusable hook:
+
+```jsx
+// src/hooks/useCall.js
+import { useState, useCallback, useRef, useEffect } from "react";
+import { CometChatCalls } from "@cometchat/calls-sdk-javascript";
+
+export function useCall() {
+ const [isInCall, setIsInCall] = useState(false);
+ const [isMuted, setIsMuted] = useState(false);
+ const [isVideoOff, setIsVideoOff] = useState(false);
+ const [participants, setParticipants] = useState([]);
+ const unsubscribersRef = useRef([]);
+
+ const joinCall = useCallback(async (sessionId, container, settings = {}) => {
+ // Setup listeners
+ unsubscribersRef.current = [
+ CometChatCalls.addEventListener("onAudioMuted", () => setIsMuted(true)),
+ CometChatCalls.addEventListener("onAudioUnMuted", () => setIsMuted(false)),
+ CometChatCalls.addEventListener("onVideoPaused", () => setIsVideoOff(true)),
+ CometChatCalls.addEventListener("onVideoResumed", () => setIsVideoOff(false)),
+ CometChatCalls.addEventListener("onParticipantListChanged", setParticipants),
+ CometChatCalls.addEventListener("onSessionLeft", () => setIsInCall(false)),
+ ];
+
+ const tokenResult = await CometChatCalls.generateToken(sessionId);
+ await CometChatCalls.joinSession(
+ tokenResult.token,
+ { sessionType: "VIDEO", layout: "TILE", ...settings },
+ container
+ );
+ setIsInCall(true);
+ }, []);
+
+ const leaveCall = useCallback(() => {
+ CometChatCalls.leaveSession();
+ unsubscribersRef.current.forEach((unsub) => unsub());
+ unsubscribersRef.current = [];
+ setIsInCall(false);
+ }, []);
+
+ const toggleAudio = useCallback(() => {
+ isMuted ? CometChatCalls.unMuteAudio() : CometChatCalls.muteAudio();
+ }, [isMuted]);
+
+ const toggleVideo = useCallback(() => {
+ isVideoOff ? CometChatCalls.resumeVideo() : CometChatCalls.pauseVideo();
+ }, [isVideoOff]);
+
+ // Cleanup on unmount
+ useEffect(() => {
+ return () => {
+ unsubscribersRef.current.forEach((unsub) => unsub());
+ };
+ }, []);
+
+ return {
+ isInCall,
+ isMuted,
+ isVideoOff,
+ participants,
+ joinCall,
+ leaveCall,
+ toggleAudio,
+ toggleVideo,
+ };
+}
+```
+
+## TypeScript Support
+
+The SDK includes TypeScript definitions. Here's a typed version of the provider:
+
+```tsx
+// src/providers/CometChatCallsProvider.tsx
+import { createContext, useContext, useEffect, useState, ReactNode } from "react";
+import { CometChatCalls } from "@cometchat/calls-sdk-javascript";
+
+interface User {
+ uid: string;
+ name: string;
+ avatar?: string;
+}
+
+interface CometChatCallsContextType {
+ isReady: boolean;
+ user: User | null;
+ error: string | null;
+}
+
+const CometChatCallsContext = createContext({
+ isReady: false,
+ user: null,
+ error: null,
+});
+
+interface ProviderProps {
+ children: ReactNode;
+ uid: string;
+}
+
+export function CometChatCallsProvider({ children, uid }: ProviderProps) {
+ // ... same implementation as above
+}
+
+export function useCometChatCalls(): CometChatCallsContextType {
+ return useContext(CometChatCallsContext);
+}
+```
+
+## Related Documentation
+
+For more detailed information on specific topics covered in this guide, refer to the main documentation:
+
+- [Setup](/calls/javascript/setup) - Detailed SDK installation and initialization
+- [Authentication](/calls/javascript/authentication) - Login methods and user management
+- [Session Settings](/calls/javascript/session-settings) - All available call configuration options
+- [Join Session](/calls/javascript/join-session) - Session joining and token generation
+- [Events](/calls/javascript/events) - Complete list of event listeners
+- [Actions](/calls/javascript/actions) - All available call control methods
+- [Call Layouts](/calls/javascript/call-layouts) - Layout options and customization
+- [Participant Management](/calls/javascript/participant-management) - Managing call participants
+
diff --git a/calls/javascript/recording.mdx b/calls/javascript/recording.mdx
new file mode 100644
index 00000000..ada4a3d4
--- /dev/null
+++ b/calls/javascript/recording.mdx
@@ -0,0 +1,108 @@
+---
+title: "Recording"
+sidebarTitle: "Recording"
+---
+
+Record call sessions for later playback, compliance, or training purposes. Recordings are stored server-side and can be accessed through the CometChat dashboard.
+
+
+Recording must be enabled for your CometChat app. Contact support if you need to enable this feature.
+
+
+## Auto-Start Recording
+
+Configure recording to start automatically when the session begins:
+
+```javascript
+const callSettings = {
+ autoStartRecording: true,
+ // ... other settings
+};
+
+await CometChatCalls.joinSession(callToken, callSettings, container);
+```
+
+## Manual Recording Control
+
+### Start Recording
+
+Begin recording during an active call:
+
+```javascript
+CometChatCalls.startRecording();
+```
+
+### Stop Recording
+
+Stop the current recording:
+
+```javascript
+CometChatCalls.stopRecording();
+```
+
+## Recording Events
+
+### Recording Started
+
+Fired when recording begins:
+
+```javascript
+CometChatCalls.addEventListener("onRecordingStarted", () => {
+ console.log("Recording started");
+ // Update UI to show recording indicator
+});
+```
+
+### Recording Stopped
+
+Fired when recording ends:
+
+```javascript
+CometChatCalls.addEventListener("onRecordingStopped", () => {
+ console.log("Recording stopped");
+ // Update UI to hide recording indicator
+});
+```
+
+### Participant Recording Events
+
+Monitor when other participants start or stop recording:
+
+```javascript
+CometChatCalls.addEventListener("onParticipantStartedRecording", (participant) => {
+ console.log(`${participant.name} started recording`);
+});
+
+CometChatCalls.addEventListener("onParticipantStoppedRecording", (participant) => {
+ console.log(`${participant.name} stopped recording`);
+});
+```
+
+## Recording Button Visibility
+
+By default, the recording button is hidden. To show it:
+
+```javascript
+const callSettings = {
+ hideRecordingButton: false,
+ // ... other settings
+};
+```
+
+## Listen for Recording Button Clicks
+
+Intercept recording button clicks for custom behavior:
+
+```javascript
+CometChatCalls.addEventListener("onRecordingToggleButtonClicked", () => {
+ console.log("Recording button clicked");
+ // Add custom logic like confirmation dialogs
+});
+```
+
+## Accessing Recordings
+
+Recordings are available through:
+- The CometChat Dashboard under your app's call logs
+- The [Call Logs API](/calls/api/list-calls) to retrieve call details including recording URLs
+
diff --git a/calls/javascript/ringing.mdx b/calls/javascript/ringing.mdx
new file mode 100644
index 00000000..ad54370b
--- /dev/null
+++ b/calls/javascript/ringing.mdx
@@ -0,0 +1,282 @@
+---
+title: "Ringing"
+sidebarTitle: "Ringing"
+---
+
+Implement incoming and outgoing call notifications by integrating the Calls SDK with the CometChat Chat SDK for call signaling.
+
+
+The Calls SDK handles the actual call session. For call signaling (ringing, accept, reject), use the CometChat Chat SDK's calling features.
+
+
+## Overview
+
+Call flow with ringing:
+
+1. **Initiator** sends a call request via Chat SDK
+2. **Receiver** gets notified of incoming call
+3. **Receiver** accepts or rejects the call
+4. **Both parties** join the call session using Calls SDK
+
+## Prerequisites
+
+Install both SDKs:
+
+```bash
+npm install @cometchat/chat-sdk-javascript @cometchat/calls-sdk-javascript
+```
+
+## Initialize Both SDKs
+
+```javascript
+import { CometChat } from "@cometchat/chat-sdk-javascript";
+import { CometChatCalls } from "@cometchat/calls-sdk-javascript";
+
+const appId = "APP_ID";
+const region = "REGION";
+
+// Initialize Chat SDK
+await CometChat.init(appId, new CometChat.AppSettingsBuilder()
+ .subscribePresenceForAllUsers()
+ .setRegion(region)
+ .build()
+);
+
+// Initialize Calls SDK
+await CometChatCalls.init({ appId, region });
+
+// Login to both
+await CometChat.login(uid, apiKey);
+await CometChatCalls.login(uid, apiKey);
+```
+
+## Initiate a Call
+
+Start an outgoing call:
+
+```javascript
+async function initiateCall(receiverUid, callType = "video") {
+ const call = new CometChat.Call(
+ receiverUid,
+ callType === "video" ? CometChat.CALL_TYPE.VIDEO : CometChat.CALL_TYPE.AUDIO,
+ CometChat.RECEIVER_TYPE.USER
+ );
+
+ try {
+ const outgoingCall = await CometChat.initiateCall(call);
+ console.log("Call initiated:", outgoingCall);
+
+ // Show outgoing call UI
+ showOutgoingCallScreen(outgoingCall);
+
+ return outgoingCall;
+ } catch (error) {
+ console.error("Call initiation failed:", error);
+ throw error;
+ }
+}
+```
+
+## Listen for Incoming Calls
+
+Register a call listener to receive incoming calls:
+
+```javascript
+const callListenerId = "CALL_LISTENER_ID";
+
+CometChat.addCallListener(
+ callListenerId,
+ new CometChat.CallListener({
+ onIncomingCallReceived: (incomingCall) => {
+ console.log("Incoming call:", incomingCall);
+ showIncomingCallScreen(incomingCall);
+ },
+ onOutgoingCallAccepted: (acceptedCall) => {
+ console.log("Call accepted:", acceptedCall);
+ startCallSession(acceptedCall.getSessionId());
+ },
+ onOutgoingCallRejected: (rejectedCall) => {
+ console.log("Call rejected:", rejectedCall);
+ hideOutgoingCallScreen();
+ },
+ onIncomingCallCancelled: (cancelledCall) => {
+ console.log("Call cancelled:", cancelledCall);
+ hideIncomingCallScreen();
+ },
+ onCallEndedMessageReceived: (endedCall) => {
+ console.log("Call ended:", endedCall);
+ },
+ })
+);
+```
+
+## Accept an Incoming Call
+
+```javascript
+async function acceptCall(incomingCall) {
+ try {
+ const acceptedCall = await CometChat.acceptCall(incomingCall.getSessionId());
+ console.log("Call accepted:", acceptedCall);
+
+ // Start the call session
+ startCallSession(acceptedCall.getSessionId());
+ } catch (error) {
+ console.error("Accept call failed:", error);
+ }
+}
+```
+
+## Reject an Incoming Call
+
+```javascript
+async function rejectCall(incomingCall, reason = CometChat.CALL_STATUS.REJECTED) {
+ try {
+ await CometChat.rejectCall(incomingCall.getSessionId(), reason);
+ console.log("Call rejected");
+ hideIncomingCallScreen();
+ } catch (error) {
+ console.error("Reject call failed:", error);
+ }
+}
+```
+
+## Cancel an Outgoing Call
+
+```javascript
+async function cancelCall(outgoingCall) {
+ try {
+ await CometChat.rejectCall(outgoingCall.getSessionId(), CometChat.CALL_STATUS.CANCELLED);
+ console.log("Call cancelled");
+ hideOutgoingCallScreen();
+ } catch (error) {
+ console.error("Cancel call failed:", error);
+ }
+}
+```
+
+## Start the Call Session
+
+Once the call is accepted, join the session using the Calls SDK:
+
+```javascript
+async function startCallSession(sessionId) {
+ const container = document.getElementById("call-container");
+
+ try {
+ // Generate token
+ const tokenResult = await CometChatCalls.generateToken(sessionId);
+
+ // Join session
+ await CometChatCalls.joinSession(
+ tokenResult.token,
+ {
+ sessionType: "VIDEO",
+ layout: "TILE",
+ },
+ container
+ );
+
+ // Listen for session end
+ CometChatCalls.addEventListener("onSessionLeft", () => {
+ endCall(sessionId);
+ });
+ } catch (error) {
+ console.error("Failed to start call session:", error);
+ }
+}
+```
+
+## End the Call
+
+```javascript
+async function endCall(sessionId) {
+ try {
+ // Leave the Calls SDK session
+ CometChatCalls.leaveSession();
+
+ // End the call in Chat SDK
+ await CometChat.endCall(sessionId);
+
+ console.log("Call ended");
+ } catch (error) {
+ console.error("End call failed:", error);
+ }
+}
+```
+
+## Incoming Call UI Example
+
+```javascript
+function showIncomingCallScreen(call) {
+ const caller = call.getCallInitiator();
+
+ const html = `
+
+
+
+
${caller.getName()}
+
Incoming ${call.getType()} call...
+
+ Decline
+ Accept
+
+
+
+ `;
+
+ document.body.insertAdjacentHTML("beforeend", html);
+ window.currentCall = call;
+
+ // Play ringtone
+ playRingtone();
+}
+
+function hideIncomingCallScreen() {
+ document.getElementById("incoming-call")?.remove();
+ stopRingtone();
+}
+```
+
+## Outgoing Call UI Example
+
+```javascript
+function showOutgoingCallScreen(call) {
+ const receiver = call.getCallReceiver();
+
+ const html = `
+
+
+
+
${receiver.getName()}
+
Calling...
+
Cancel
+
+
+ `;
+
+ document.body.insertAdjacentHTML("beforeend", html);
+ window.currentCall = call;
+
+ // Play ringback tone
+ playRingbackTone();
+}
+
+function hideOutgoingCallScreen() {
+ document.getElementById("outgoing-call")?.remove();
+ stopRingbackTone();
+}
+```
+
+## Cleanup
+
+Remove the call listener when no longer needed:
+
+```javascript
+CometChat.removeCallListener(callListenerId);
+```
+
+## Related Documentation
+
+- [CometChat Chat SDK - Calling](/sdk/javascript/calling-overview)
+- [Join Session](/calls/javascript/join-session)
+
diff --git a/calls/javascript/screen-sharing.mdx b/calls/javascript/screen-sharing.mdx
new file mode 100644
index 00000000..d3c70c38
--- /dev/null
+++ b/calls/javascript/screen-sharing.mdx
@@ -0,0 +1,96 @@
+---
+title: "Screen Sharing"
+sidebarTitle: "Screen Sharing"
+---
+
+Share your screen with other participants during a call. The browser will prompt users to select which screen, window, or browser tab to share.
+
+## Start Screen Sharing
+
+Begin sharing your screen:
+
+```javascript
+CometChatCalls.startScreenSharing();
+```
+
+The browser will display a dialog allowing the user to choose:
+- Entire screen
+- Application window
+- Browser tab
+
+## Stop Screen Sharing
+
+Stop the current screen share:
+
+```javascript
+CometChatCalls.stopScreenSharing();
+```
+
+## Screen Sharing Events
+
+### Local Screen Share Events
+
+Monitor your own screen sharing state:
+
+```javascript
+CometChatCalls.addEventListener("onScreenShareStarted", () => {
+ console.log("You started screen sharing");
+});
+
+CometChatCalls.addEventListener("onScreenShareStopped", () => {
+ console.log("You stopped screen sharing");
+});
+```
+
+### Participant Screen Share Events
+
+Monitor when other participants start or stop screen sharing:
+
+```javascript
+CometChatCalls.addEventListener("onParticipantStartedScreenShare", (participant) => {
+ console.log(`${participant.name} started screen sharing`);
+});
+
+CometChatCalls.addEventListener("onParticipantStoppedScreenShare", (participant) => {
+ console.log(`${participant.name} stopped screen sharing`);
+});
+```
+
+## Screen Sharing Button
+
+### Hide Screen Sharing Button
+
+To hide the screen sharing button from the control panel:
+
+```javascript
+const callSettings = {
+ hideScreenSharingButton: true,
+ // ... other settings
+};
+```
+
+### Listen for Button Clicks
+
+Intercept screen share button clicks for custom behavior:
+
+```javascript
+CometChatCalls.addEventListener("onScreenShareButtonClicked", () => {
+ console.log("Screen share button clicked");
+});
+```
+
+## Browser Support
+
+Screen sharing requires browser support for the `getDisplayMedia` API:
+
+| Browser | Support |
+|---------|---------|
+| Chrome | ✅ Full support |
+| Firefox | ✅ Full support |
+| Safari | ✅ Safari 13+ |
+| Edge | ✅ Full support |
+
+
+Screen sharing requires HTTPS in production. Localhost is exempt during development.
+
+
diff --git a/calls/javascript/session-settings.mdx b/calls/javascript/session-settings.mdx
new file mode 100644
index 00000000..d2948b67
--- /dev/null
+++ b/calls/javascript/session-settings.mdx
@@ -0,0 +1,293 @@
+---
+title: "Session Settings"
+sidebarTitle: "Session Settings"
+---
+
+The session settings object allows you to customize every aspect of your call session before participants join. From controlling the initial audio/video state to customizing the UI layout and hiding specific controls, these settings give you complete control over the call experience.
+
+
+These are pre-session configurations that must be set before joining a call. Once configured, pass the settings object to the `joinSession()` method. Settings cannot be changed after the session has started, though many features can be controlled dynamically during the call using call actions.
+
+
+```javascript
+const callSettings = {
+ sessionType: "VIDEO",
+ layout: "TILE",
+ startAudioMuted: false,
+ startVideoPaused: false,
+ hideControlPanel: false,
+ hideLeaveSessionButton: false,
+ hideToggleAudioButton: false,
+ hideToggleVideoButton: false,
+};
+```
+
+## Session Settings
+
+### Session Type
+
+**Property:** `sessionType`
+
+Defines the type of call session. Choose `VIDEO` for video calls with camera enabled, or `VOICE` for audio-only calls.
+
+```javascript
+sessionType: "VIDEO"
+```
+
+| Value | Description |
+|-------|-------------|
+| `VIDEO` | Video call with camera enabled |
+| `VOICE` | Audio-only call |
+
+**Default:** `VIDEO`
+
+### Layout Mode
+
+**Property:** `layout`
+
+Sets the initial layout mode for displaying participants.
+
+```javascript
+layout: "TILE"
+```
+
+| Value | Description |
+|-------|-------------|
+| `TILE` | Grid layout showing all participants equally |
+| `SIDEBAR` | Main speaker with participants in a sidebar |
+| `SPOTLIGHT` | Focus on active speaker with others in sidebar |
+
+**Default:** `TILE`
+
+### Start Audio Muted
+
+**Property:** `startAudioMuted`
+
+Determines whether the microphone is muted when joining the session.
+
+```javascript
+startAudioMuted: true
+```
+
+**Default:** `false`
+
+### Start Video Paused
+
+**Property:** `startVideoPaused`
+
+Controls whether the camera is turned off when joining the session.
+
+```javascript
+startVideoPaused: true
+```
+
+**Default:** `false`
+
+### Auto Start Recording
+
+**Property:** `autoStartRecording`
+
+Automatically starts recording the session as soon as it begins.
+
+```javascript
+autoStartRecording: true
+```
+
+**Default:** `false`
+
+### Idle Timeout Period Before Prompt
+
+**Property:** `idleTimeoutPeriodBeforePrompt`
+
+Time in milliseconds before showing the idle timeout prompt when you're the only participant.
+
+```javascript
+idleTimeoutPeriodBeforePrompt: 60000 // 60 seconds
+```
+
+**Default:** `60000` (60 seconds)
+
+### Idle Timeout Period After Prompt
+
+**Property:** `idleTimeoutPeriodAfterPrompt`
+
+Time in milliseconds after the prompt before automatically ending the session.
+
+```javascript
+idleTimeoutPeriodAfterPrompt: 120000 // 120 seconds
+```
+
+**Default:** `120000` (120 seconds)
+
+## UI Visibility Settings
+
+### Hide Control Panel
+
+**Property:** `hideControlPanel`
+
+Hides the bottom control bar that contains call action buttons.
+
+```javascript
+hideControlPanel: true
+```
+
+**Default:** `false`
+
+### Hide Leave Session Button
+
+**Property:** `hideLeaveSessionButton`
+
+Hides the button that allows users to leave or end the call.
+
+```javascript
+hideLeaveSessionButton: true
+```
+
+**Default:** `false`
+
+### Hide Toggle Audio Button
+
+**Property:** `hideToggleAudioButton`
+
+Hides the microphone mute/unmute button from the control panel.
+
+```javascript
+hideToggleAudioButton: true
+```
+
+**Default:** `false`
+
+### Hide Toggle Video Button
+
+**Property:** `hideToggleVideoButton`
+
+Hides the camera on/off button from the control panel.
+
+```javascript
+hideToggleVideoButton: true
+```
+
+**Default:** `false`
+
+### Hide Recording Button
+
+**Property:** `hideRecordingButton`
+
+Hides the recording start/stop button from the control panel.
+
+```javascript
+hideRecordingButton: false
+```
+
+**Default:** `true`
+
+### Hide Screen Sharing Button
+
+**Property:** `hideScreenSharingButton`
+
+Hides the screen sharing button from the control panel.
+
+```javascript
+hideScreenSharingButton: true
+```
+
+**Default:** `false`
+
+### Hide Change Layout Button
+
+**Property:** `hideChangeLayoutButton`
+
+Hides the button that allows switching between different layout modes.
+
+```javascript
+hideChangeLayoutButton: true
+```
+
+**Default:** `false`
+
+### Hide Switch Layout Button
+
+**Property:** `hideSwitchLayoutButton`
+
+Hides the layout switch button.
+
+```javascript
+hideSwitchLayoutButton: true
+```
+
+**Default:** `false`
+
+### Hide Virtual Background Button
+
+**Property:** `hideVirtualBackgroundButton`
+
+Hides the virtual background settings button.
+
+```javascript
+hideVirtualBackgroundButton: true
+```
+
+**Default:** `false`
+
+### Hide Network Indicator
+
+**Property:** `hideNetworkIndicator`
+
+Hides the network quality indicator.
+
+```javascript
+hideNetworkIndicator: true
+```
+
+**Default:** `false`
+
+## Complete Example
+
+```javascript
+const callSettings = {
+ // Session configuration
+ sessionType: "VIDEO",
+ layout: "TILE",
+ startAudioMuted: false,
+ startVideoPaused: false,
+ autoStartRecording: false,
+
+ // Timeout settings
+ idleTimeoutPeriodBeforePrompt: 60000,
+ idleTimeoutPeriodAfterPrompt: 120000,
+
+ // UI visibility
+ hideControlPanel: false,
+ hideLeaveSessionButton: false,
+ hideToggleAudioButton: false,
+ hideToggleVideoButton: false,
+ hideRecordingButton: true,
+ hideScreenSharingButton: false,
+ hideChangeLayoutButton: false,
+ hideVirtualBackgroundButton: false,
+ hideNetworkIndicator: false,
+};
+```
+
+
+| Property | Type | Default | Description |
+|----------|------|---------|-------------|
+| `sessionType` | String | `VIDEO` | Call type: `VIDEO` or `VOICE` |
+| `layout` | String | `TILE` | Layout: `TILE`, `SIDEBAR`, or `SPOTLIGHT` |
+| `startAudioMuted` | Boolean | `false` | Start with microphone muted |
+| `startVideoPaused` | Boolean | `false` | Start with camera off |
+| `autoStartRecording` | Boolean | `false` | Auto-start recording |
+| `idleTimeoutPeriodBeforePrompt` | Number | `60000` | Idle timeout before prompt (ms) |
+| `idleTimeoutPeriodAfterPrompt` | Number | `120000` | Idle timeout after prompt (ms) |
+| `hideControlPanel` | Boolean | `false` | Hide control panel |
+| `hideLeaveSessionButton` | Boolean | `false` | Hide leave button |
+| `hideToggleAudioButton` | Boolean | `false` | Hide audio toggle |
+| `hideToggleVideoButton` | Boolean | `false` | Hide video toggle |
+| `hideRecordingButton` | Boolean | `true` | Hide recording button |
+| `hideScreenSharingButton` | Boolean | `false` | Hide screen share button |
+| `hideChangeLayoutButton` | Boolean | `false` | Hide layout change button |
+| `hideSwitchLayoutButton` | Boolean | `false` | Hide layout switch button |
+| `hideVirtualBackgroundButton` | Boolean | `false` | Hide virtual background button |
+| `hideNetworkIndicator` | Boolean | `false` | Hide network indicator |
+
+
diff --git a/calls/javascript/setup.mdx b/calls/javascript/setup.mdx
new file mode 100644
index 00000000..3a81c0a4
--- /dev/null
+++ b/calls/javascript/setup.mdx
@@ -0,0 +1,106 @@
+---
+title: "Setup"
+sidebarTitle: "Setup"
+---
+
+This guide walks you through installing the CometChat Calls SDK and initializing it in your web application.
+
+## Install the SDK
+
+Install the CometChat Calls SDK using npm or yarn:
+
+
+
+```bash
+npm install @cometchat/calls-sdk-javascript
+```
+
+
+```bash
+yarn add @cometchat/calls-sdk-javascript
+```
+
+
+
+## Import the SDK
+
+Import the `CometChatCalls` class in your JavaScript or TypeScript file:
+
+```javascript
+import { CometChatCalls } from "@cometchat/calls-sdk-javascript";
+```
+
+## Initialize CometChat Calls
+
+The `init()` method initializes the SDK with your app credentials. Call this method once when your application starts.
+
+### CallAppSettings
+
+The initialization requires a configuration object with the following properties:
+
+| Parameter | Type | Required | Description |
+|-----------|------|----------|-------------|
+| `appId` | String | Yes | Your CometChat App ID |
+| `region` | String | Yes | Your app region (`us`, `eu`, or `in`) |
+
+```javascript
+const appId = "APP_ID"; // Replace with your App ID
+const region = "REGION"; // Replace with your Region ("us", "eu", or "in")
+
+const callAppSettings = {
+ appId: appId,
+ region: region,
+};
+
+const result = await CometChatCalls.init(callAppSettings);
+
+if (result.success) {
+ console.log("CometChat Calls SDK initialized successfully");
+} else {
+ console.error("CometChat Calls SDK initialization failed:", result.error);
+}
+```
+
+The `init()` method returns an object with:
+
+| Property | Type | Description |
+|----------|------|-------------|
+| `success` | Boolean | `true` if initialization succeeded |
+| `error` | Object \| null | Error details if initialization failed |
+
+## Browser Requirements
+
+The Calls SDK requires a modern browser with WebRTC support:
+
+| Browser | Minimum Version |
+|---------|-----------------|
+| Chrome | 72+ |
+| Firefox | 68+ |
+| Safari | 12.1+ |
+| Edge | 79+ |
+
+
+HTTPS is required for camera and microphone access in production. Localhost is exempt from this requirement during development.
+
+
+## Permissions
+
+The browser will prompt users for camera and microphone permissions when joining a call. Ensure your application handles permission denials gracefully.
+
+```javascript
+// Check if permissions are granted
+async function checkMediaPermissions() {
+ try {
+ const stream = await navigator.mediaDevices.getUserMedia({
+ video: true,
+ audio: true
+ });
+ stream.getTracks().forEach(track => track.stop());
+ return true;
+ } catch (error) {
+ console.error("Media permissions denied:", error);
+ return false;
+ }
+}
+```
+
diff --git a/calls/javascript/share-invite.mdx b/calls/javascript/share-invite.mdx
new file mode 100644
index 00000000..7865d62a
--- /dev/null
+++ b/calls/javascript/share-invite.mdx
@@ -0,0 +1,93 @@
+---
+title: "Share Invite"
+sidebarTitle: "Share Invite"
+---
+
+Allow participants to share call invitations with others using the share invite feature.
+
+## Share Invite Button
+
+### Show Share Invite Button
+
+By default, the share invite button is hidden. To show it:
+
+```javascript
+const callSettings = {
+ hideShareInviteButton: false,
+ // ... other settings
+};
+```
+
+### Listen for Share Invite Button Clicks
+
+Handle share invite button clicks to implement your sharing logic:
+
+```javascript
+CometChatCalls.addEventListener("onShareInviteButtonClicked", () => {
+ console.log("Share invite button clicked");
+ // Implement your sharing logic
+ shareCallInvite();
+});
+```
+
+## Implementing Share Functionality
+
+When the share invite button is clicked, you can implement various sharing methods:
+
+### Web Share API
+
+Use the native Web Share API for mobile-friendly sharing:
+
+```javascript
+async function shareCallInvite() {
+ const shareData = {
+ title: "Join my call",
+ text: "Click the link to join my video call",
+ url: `https://yourapp.com/call/${sessionId}`
+ };
+
+ if (navigator.share) {
+ try {
+ await navigator.share(shareData);
+ console.log("Shared successfully");
+ } catch (error) {
+ console.log("Share cancelled or failed");
+ }
+ } else {
+ // Fallback for browsers that don't support Web Share API
+ copyToClipboard(shareData.url);
+ }
+}
+```
+
+### Copy to Clipboard
+
+Provide a simple copy-to-clipboard option:
+
+```javascript
+function copyToClipboard(text) {
+ navigator.clipboard.writeText(text).then(() => {
+ console.log("Link copied to clipboard");
+ // Show a toast notification
+ }).catch(err => {
+ console.error("Failed to copy:", err);
+ });
+}
+```
+
+### Custom Share Dialog
+
+Create a custom share dialog with multiple options:
+
+```javascript
+function showShareDialog() {
+ const callLink = `https://yourapp.com/call/${sessionId}`;
+
+ // Show your custom dialog with options like:
+ // - Copy link
+ // - Share via email
+ // - Share via SMS
+ // - Share to social media
+}
+```
+
diff --git a/calls/javascript/virtual-background.mdx b/calls/javascript/virtual-background.mdx
new file mode 100644
index 00000000..bae8b1da
--- /dev/null
+++ b/calls/javascript/virtual-background.mdx
@@ -0,0 +1,81 @@
+---
+title: "Virtual Background"
+sidebarTitle: "Virtual Background"
+---
+
+Apply virtual backgrounds to your video feed during calls. You can blur your background or replace it with a custom image.
+
+## Background Blur
+
+Apply a blur effect to your background:
+
+```javascript
+CometChatCalls.setVirtualBackgroundBlurLevel(10);
+```
+
+| Parameter | Type | Description |
+|-----------|------|-------------|
+| `blurLevel` | Number | The blur intensity level (higher = more blur) |
+
+## Custom Background Image
+
+Set a custom image as your virtual background:
+
+```javascript
+CometChatCalls.setVirtualBackgroundImage("https://example.com/background.jpg");
+```
+
+| Parameter | Type | Description |
+|-----------|------|-------------|
+| `imageUrl` | String | URL of the background image |
+
+
+The image URL must be accessible from the user's browser. Consider hosting images on a CDN for best performance.
+
+
+## Clear Virtual Background
+
+Remove any applied virtual background:
+
+```javascript
+CometChatCalls.clearVirtualBackground();
+```
+
+## Virtual Background Dialog
+
+### Show Dialog
+
+Open the built-in virtual background settings dialog:
+
+```javascript
+CometChatCalls.showVirtualBackgroundDialog();
+```
+
+### Hide Dialog
+
+Close the virtual background dialog:
+
+```javascript
+CometChatCalls.hideVirtualBackgroundDialog();
+```
+
+## Hide Virtual Background Button
+
+To hide the virtual background button from the control panel:
+
+```javascript
+const callSettings = {
+ hideVirtualBackgroundButton: true,
+ // ... other settings
+};
+```
+
+## Browser Requirements
+
+Virtual backgrounds use machine learning for background segmentation. Performance may vary based on:
+- Device processing power
+- Browser version
+- Camera resolution
+
+For best results, use a modern browser on a device with adequate processing power.
+
diff --git a/calls/javascript/vue-integration.mdx b/calls/javascript/vue-integration.mdx
new file mode 100644
index 00000000..2cd5a425
--- /dev/null
+++ b/calls/javascript/vue-integration.mdx
@@ -0,0 +1,579 @@
+---
+title: "Vue Integration"
+sidebarTitle: "Vue"
+---
+
+This guide walks you through integrating the CometChat Calls SDK into a Vue.js application. By the end, you'll have a working video call implementation with proper state management and lifecycle handling.
+
+## Prerequisites
+
+Before you begin, ensure you have:
+- A CometChat account with an app created ([Sign up](https://app.cometchat.com/signup))
+- Your App ID, Region, and API Key from the CometChat Dashboard
+- A Vue 3 project (Vite, Vue CLI, or Nuxt)
+- Node.js 16+ installed
+
+## Step 1: Install the SDK
+
+Install the CometChat Calls SDK package:
+
+```bash
+npm install @cometchat/calls-sdk-javascript
+```
+
+## Step 2: Create a Composable for SDK Management
+
+Create a composable that handles initialization, login, and provides reactive state:
+
+```javascript
+// src/composables/useCometChatCalls.js
+import { ref, readonly } from "vue";
+import { CometChatCalls } from "@cometchat/calls-sdk-javascript";
+
+const APP_ID = "YOUR_APP_ID"; // Replace with your App ID
+const REGION = "YOUR_REGION"; // Replace with your Region (us, eu, in)
+const API_KEY = "YOUR_API_KEY"; // Replace with your API Key
+
+// Shared state across all components
+const isReady = ref(false);
+const user = ref(null);
+const error = ref(null);
+const isInitializing = ref(false);
+
+export function useCometChatCalls() {
+ /**
+ * Initialize the SDK and login the user.
+ * Call this once when your app starts or when the user authenticates.
+ */
+ async function initAndLogin(uid) {
+ if (isInitializing.value || isReady.value) return;
+
+ isInitializing.value = true;
+ error.value = null;
+
+ try {
+ // Step 1: Initialize the SDK
+ const initResult = await CometChatCalls.init({
+ appId: APP_ID,
+ region: REGION,
+ });
+
+ if (!initResult.success) {
+ throw new Error("SDK initialization failed");
+ }
+
+ // Step 2: Check if already logged in
+ let loggedInUser = CometChatCalls.getLoggedInUser();
+
+ // Step 3: Login if not already logged in
+ if (!loggedInUser) {
+ loggedInUser = await CometChatCalls.login(uid, API_KEY);
+ }
+
+ user.value = loggedInUser;
+ isReady.value = true;
+ } catch (err) {
+ console.error("CometChat Calls setup failed:", err);
+ error.value = err.message || "Setup failed";
+ } finally {
+ isInitializing.value = false;
+ }
+ }
+
+ /**
+ * Logout the current user and reset state.
+ */
+ async function logout() {
+ try {
+ await CometChatCalls.logout();
+ user.value = null;
+ isReady.value = false;
+ } catch (err) {
+ console.error("Logout failed:", err);
+ }
+ }
+
+ return {
+ // State (readonly to prevent external mutations)
+ isReady: readonly(isReady),
+ user: readonly(user),
+ error: readonly(error),
+ isInitializing: readonly(isInitializing),
+
+ // Methods
+ initAndLogin,
+ logout,
+ };
+}
+```
+
+## Step 3: Initialize in App.vue
+
+Initialize the SDK when your app mounts:
+
+```vue
+
+
+
+
+ Error: {{ error }}
+
+
+ Loading...
+
+
+
+
+
+
+```
+
+## Step 4: Create the Call Component
+
+Build a call component with proper lifecycle management:
+
+```vue
+
+
+
+
+
+
+
+
+ Joining call...
+
+
+
+
+
Error: {{ callError }}
+
Go Back
+
+
+
+
+
+ {{ isMuted ? "Unmute" : "Mute" }}
+
+
+ {{ isVideoOff ? "Start Video" : "Stop Video" }}
+
+
+ Leave Call
+
+
+
+
+
+
+
+
+```
+
+## Step 5: Create the Call Page
+
+Create a page that manages the call flow:
+
+```vue
+
+
+
+
+
+
CometChat Video Calls
+
Logged in as: {{ user?.name || user?.uid }}
+
+
+
+
+ Join Call
+
+
+
+
+ Share the same Session ID with others to join the same call.
+
+
+
+
+
+
+
+
+
+
+
+```
+
+## Call Composable (Optional)
+
+For reusable call logic across multiple components:
+
+```javascript
+// src/composables/useCall.js
+import { ref, onUnmounted } from "vue";
+import { CometChatCalls } from "@cometchat/calls-sdk-javascript";
+
+export function useCall() {
+ const isInCall = ref(false);
+ const isMuted = ref(false);
+ const isVideoOff = ref(false);
+ const participants = ref([]);
+ const unsubscribers = ref([]);
+
+ async function joinCall(sessionId, container, settings = {}) {
+ unsubscribers.value = [
+ CometChatCalls.addEventListener("onAudioMuted", () => { isMuted.value = true; }),
+ CometChatCalls.addEventListener("onAudioUnMuted", () => { isMuted.value = false; }),
+ CometChatCalls.addEventListener("onVideoPaused", () => { isVideoOff.value = true; }),
+ CometChatCalls.addEventListener("onVideoResumed", () => { isVideoOff.value = false; }),
+ CometChatCalls.addEventListener("onParticipantListChanged", (list) => { participants.value = list; }),
+ CometChatCalls.addEventListener("onSessionLeft", () => { isInCall.value = false; }),
+ ];
+
+ const tokenResult = await CometChatCalls.generateToken(sessionId);
+ await CometChatCalls.joinSession(
+ tokenResult.token,
+ { sessionType: "VIDEO", layout: "TILE", ...settings },
+ container
+ );
+ isInCall.value = true;
+ }
+
+ function leaveCall() {
+ CometChatCalls.leaveSession();
+ cleanup();
+ }
+
+ function toggleAudio() {
+ isMuted.value ? CometChatCalls.unMuteAudio() : CometChatCalls.muteAudio();
+ }
+
+ function toggleVideo() {
+ isVideoOff.value ? CometChatCalls.resumeVideo() : CometChatCalls.pauseVideo();
+ }
+
+ function cleanup() {
+ unsubscribers.value.forEach((unsub) => unsub());
+ unsubscribers.value = [];
+ isInCall.value = false;
+ }
+
+ onUnmounted(() => {
+ cleanup();
+ });
+
+ return {
+ isInCall,
+ isMuted,
+ isVideoOff,
+ participants,
+ joinCall,
+ leaveCall,
+ toggleAudio,
+ toggleVideo,
+ };
+}
+```
+
+## Vue 2 Support
+
+For Vue 2 projects using the Options API, see the [Vue 2 migration guide](https://v3-migration.vuejs.org/) or use the `@vue/composition-api` package to use the Composition API in Vue 2.
+
+## Related Documentation
+
+For more detailed information on specific topics covered in this guide, refer to the main documentation:
+
+- [Setup](/calls/javascript/setup) - Detailed SDK installation and initialization
+- [Authentication](/calls/javascript/authentication) - Login methods and user management
+- [Session Settings](/calls/javascript/session-settings) - All available call configuration options
+- [Join Session](/calls/javascript/join-session) - Session joining and token generation
+- [Events](/calls/javascript/events) - Complete list of event listeners
+- [Actions](/calls/javascript/actions) - All available call control methods
+- [Call Layouts](/calls/javascript/call-layouts) - Layout options and customization
+- [Participant Management](/calls/javascript/participant-management) - Managing call participants
+
diff --git a/calls/platform/compatibility.mdx b/calls/platform/compatibility.mdx
new file mode 100644
index 00000000..64b75573
--- /dev/null
+++ b/calls/platform/compatibility.mdx
@@ -0,0 +1,162 @@
+---
+title: "Platform Compatibility"
+sidebarTitle: "Compatibility"
+description: "Feature availability, browser support, and platform requirements for CometChat Calls SDK."
+---
+
+import { Callout, CardGroup, Card } from 'mintlify';
+
+This page provides detailed information about feature availability across platforms, browser support, and minimum version requirements.
+
+## Platform Feature Comparison
+
+Not all features are available on every platform. Use this table to understand feature availability:
+
+| Feature | JavaScript | React Native | iOS | Android | Flutter |
+|---------|:----------:|:------------:|:---:|:-------:|:-------:|
+| **Core Features** | | | | | |
+| Voice Calls | ✓ | ✓ | ✓ | ✓ | ✓ |
+| Video Calls | ✓ | ✓ | ✓ | ✓ | ✓ |
+| Group Calls | ✓ | ✓ | ✓ | ✓ | ✓ |
+| Screen Sharing | ✓ | ✓ | ✓ | ✓ | ✓ |
+| Recording | ✓ | ✓ | ✓ | ✓ | ✓ |
+| Call Layouts | ✓ | ✓ | ✓ | ✓ | ✓ |
+| **Call Management** | | | | | |
+| Ringing | ✓ | ✓ | ✓ | ✓ | ✓ |
+| Call Logs | ✓ | ✓ | ✓ | ✓ | ✓ |
+| Participant Management | ✓ | ✓ | ✓ | ✓ | ✓ |
+| In-Call Chat | ✓ | ✓ | ✓ | ✓ | ✓ |
+| **Advanced Features** | | | | | |
+| Virtual Background | ✓ | ✗ | ✗ | ✗ | ✗ |
+| Picture-in-Picture | ✓ | ✗ | ✗ | ✓ | ✗ |
+| Raise Hand | ✓ | ✓ | ✓ | ✓ | ✓ |
+| Audio Modes | ✗ | ✓ | ✓ | ✓ | ✓ |
+| **Customization** | | | | | |
+| Custom Control Panel | ✓ | ✓ | ✓ | ✓ | ✓ |
+| Idle Timeout | ✓ | ✓ | ✓ | ✓ | ✓ |
+| Device Management | ✓ | ✗ | ✗ | ✗ | ✗ |
+| **Platform-Specific** | | | | | |
+| VoIP / CallKit | ✗ | ✓ | ✓ | ✓ | ✗ |
+| Background Handling | ✗ | ✓ | ✓ | ✓ | ✗ |
+
+✓ = Available, ✗ = Not Available
+
+## Browser Support
+
+### Desktop Browsers
+
+| Browser | Minimum Version | Notes |
+|---------|-----------------|-------|
+| Chrome | 72+ | Recommended |
+| Firefox | 68+ | Full support |
+| Safari | 12.1+ | macOS 10.14.4+ |
+| Edge | 79+ | Chromium-based |
+| Opera | 60+ | Chromium-based |
+
+### Mobile Browsers
+
+| Browser | Minimum Version |
+|---------|-----------------|
+| Chrome for Android | 72+ |
+| Firefox for Android | 68+ |
+| Samsung Internet | 12+ |
+| Edge for Android | 79+ |
+| Safari for iOS | 12.1+ |
+| Chrome for iOS | 72+ |
+| Edge for iOS | 79+ |
+
+## Native Mobile Platforms
+
+### iOS
+
+| Requirement | Value |
+|-------------|-------|
+| Minimum Version | iOS 16.0+ |
+| Architecture | arm64 |
+| Package Manager | CocoaPods, SPM |
+| Platform Capabilities | CallKit, VoIP Push, Background Audio |
+
+### Android
+
+| Requirement | Value |
+|-------------|-------|
+| Minimum API | API 24 (Android 7.0+) |
+| Architecture | arm64-v8a, armeabi-v7a |
+| Package Manager | Gradle |
+| Platform Capabilities | PiP Mode, Foreground Service, ConnectionService |
+
+## Cross-Platform Frameworks
+
+### React Native
+
+| Requirement | Value |
+|-------------|-------|
+| Minimum Version | 0.70+ |
+| Platforms | iOS, Android |
+| Package Manager | npm, yarn |
+| Expo Support | Bare workflow |
+
+### Flutter
+
+| Requirement | Value |
+|-------------|-------|
+| Minimum Flutter | 3.0+ |
+| Minimum Dart | 2.17+ |
+| Platforms | iOS, Android |
+| Package Manager | pub |
+
+## Network Requirements
+
+### Ports & Protocols
+
+| Protocol | Port | Purpose |
+|----------|------|---------|
+| HTTPS | 443 | API calls, signaling |
+| UDP | 10000-20000 | Media streaming (preferred) |
+| TCP | 443 | Media fallback (TURN) |
+
+### Bandwidth Recommendations
+
+| Call Type | Bandwidth |
+|-----------|-----------|
+| Voice Call | 100 - 300 kbps |
+| Video Call (SD) | 500 kbps - 1 Mbps |
+| Video Call (HD) | 1.5 - 3 Mbps |
+| Screen Sharing | 1 - 2 Mbps |
+
+
+**HTTPS Required**: Camera and microphone access requires HTTPS in production environments. Localhost (`http://localhost` or `http://127.0.0.1`) is exempt during development.
+
+
+## Permissions Required
+
+### Web
+- Camera access
+- Microphone access
+- Screen capture (for screen sharing)
+- Notifications (optional)
+
+### iOS
+```
+NSCameraUsageDescription
+NSMicrophoneUsageDescription
+```
+
+### Android
+```xml
+
+
+
+
+```
+
+## Next Steps
+
+
+
+ Explore all available features
+
+
+ Set up user authentication
+
+
diff --git a/calls/platform/features.mdx b/calls/platform/features.mdx
new file mode 100644
index 00000000..3267146f
--- /dev/null
+++ b/calls/platform/features.mdx
@@ -0,0 +1,402 @@
+---
+title: "Key Features"
+sidebarTitle: "Key Features"
+description: "Comprehensive guide to all CometChat Calls SDK features and capabilities."
+---
+
+import { Callout, CardGroup, Card } from 'mintlify';
+
+The Calls SDK includes a comprehensive set of features for building professional calling experiences. Each feature is designed to work seamlessly across platforms while providing the flexibility to customize for your specific use case.
+
+## Core Calling Features
+
+### Voice Calls
+
+High-quality audio calls powered by WebRTC with enterprise-grade infrastructure designed for reliability and clarity.
+
+**How It Works:**
+Voice calls use WebRTC's audio processing pipeline with automatic codec selection (Opus preferred) for optimal quality. The SDK handles all the complexity of establishing peer connections, managing ICE candidates, and maintaining stable audio streams.
+
+**Technical Capabilities:**
+- Adaptive bitrate encoding that adjusts to network conditions
+- Automatic noise cancellation and echo reduction
+- Automatic gain control for consistent volume levels
+- Low latency audio streaming optimized for real-time communication
+- Automatic reconnection on network interruptions
+
+**Call Types Supported:**
+- 1-to-1 private voice calls
+- Group voice calls with multiple participants
+- Audio-only mode in video calls
+
+**Use Cases:** Customer support calls, team meetings, social audio rooms, telehealth consultations
+
+---
+
+### Video Calls
+
+HD video calling with adaptive quality optimization that delivers smooth video across varying network conditions.
+
+**How It Works:**
+Video calls leverage WebRTC's video processing with VP8/VP9/H.264 codec support. The SDK automatically selects the best codec based on browser/device capabilities and negotiates optimal resolution and framerate.
+
+**Technical Capabilities:**
+- HD video support with adaptive bitrate
+- Automatic quality adjustment based on network conditions
+- Front/back camera switching on mobile devices
+- Camera on/off toggle during calls
+- Video preview before joining calls
+
+**Use Cases:** Video consultations, remote interviews, virtual events, family video calls, remote team collaboration
+
+---
+
+### Group Calls
+
+Multi-participant calling with intelligent layout management and active speaker detection.
+
+**How It Works:**
+Group calls use a Selective Forwarding Unit (SFU) architecture where media streams are routed through CometChat's servers. This enables efficient bandwidth usage as each participant only uploads one stream while receiving multiple streams.
+
+**Participant Features:**
+- Support for multiple simultaneous video/audio participants
+- Dynamic participant join/leave handling
+- Participant list with real-time status (muted, video off)
+- Active speaker detection and highlighting
+
+**Use Cases:** Team meetings, webinars, virtual classrooms, group therapy sessions, online workshops
+
+---
+
+### Call Layouts
+
+Three view modes that automatically adapt to participant count and call context.
+
+**Available Layouts:**
+
+| Layout | Description | Best For |
+|--------|-------------|----------|
+| **Tile** | Grid layout with equally-sized tiles | Group discussions, team meetings |
+| **Sidebar** | Main speaker with participants in a sidebar | Presentations, webinars |
+| **Spotlight** | Large view for active speaker, small tiles for others | One-on-one calls, focused discussions |
+
+**Features:**
+- Set initial layout when joining a session
+- Switch layouts dynamically during calls
+- Each participant can independently choose their preferred layout
+- Automatic layout optimization based on participant count
+
+---
+
+### Screen Sharing
+
+Share your screen with other participants during a call.
+
+**How It Works:**
+Screen sharing uses the browser's `getDisplayMedia` API (web) or native screen capture APIs (mobile). The shared content is encoded as a video stream and transmitted to other participants.
+
+**Sharing Options (Web):**
+- Entire screen
+- Application window
+- Browser tab
+
+**Features:**
+- Start/stop screen sharing programmatically
+- Screen share events for local and remote participants
+- Configurable screen share button visibility
+- Button click interception for custom behavior
+
+**Use Cases:** Software demos, slide presentations, collaborative document editing, remote tech support, code reviews
+
+---
+
+### Recording
+
+Server-side call recording with cloud storage for later playback and compliance.
+
+**How It Works:**
+Recording happens on CometChat's media servers, not on participant devices. This ensures consistent quality regardless of individual network conditions and eliminates the risk of lost recordings due to client crashes.
+
+
+Recording must be enabled for your CometChat app. Contact support if you need to enable this feature.
+
+
+**Features:**
+- Auto-start recording when session begins (configurable)
+- Manual start/stop recording during calls
+- Recording status indicators for all participants
+- Recording events (started, stopped)
+- Access recordings via CometChat Dashboard or REST API
+
+**Recording Controls:**
+- `startRecording()` - Begin recording
+- `stopRecording()` - End recording
+- `autoStartRecording` setting for automatic recording
+
+**Use Cases:** Meeting archives, compliance documentation, training content creation, interview recordings
+
+---
+
+### Virtual Background
+
+Replace or blur your real background with AI-powered segmentation for privacy and professionalism.
+
+**How It Works:**
+Virtual backgrounds use machine learning models to segment the person from the background in real-time. The segmentation mask is applied to each video frame, replacing the background with blur or a custom image.
+
+**Background Options:**
+- **Blur**: Gaussian blur applied to background (adjustable intensity via `setVirtualBackgroundBlurLevel`)
+- **Image**: Custom image as background (via `setVirtualBackgroundImage`)
+- **Clear**: Remove virtual background (via `clearVirtualBackground`)
+
+**Features:**
+- Built-in virtual background settings dialog
+- Configurable button visibility
+- Real-time background replacement
+
+**Platform Availability:** JavaScript (Web) only
+
+**Browser Requirements:** Modern browser with adequate processing power for best results
+
+**Use Cases:** Work from home privacy, professional video calls, hiding messy rooms, branded backgrounds
+
+---
+
+### Audio Modes
+
+Switch between different audio output routes on mobile devices.
+
+**Available Modes:**
+
+| Mode | Description |
+|------|-------------|
+| `SPEAKER` | Routes audio through the device loudspeaker |
+| `EARPIECE` | Routes audio through the phone earpiece (for private calls) |
+| `BLUETOOTH` | Routes audio through a connected Bluetooth device |
+| `HEADPHONES` | Routes audio through wired headphones |
+
+**Features:**
+- Set initial audio mode when joining
+- Change audio mode dynamically during calls
+- Audio mode change events via `MediaEventsListener`
+- Automatic detection of connected audio devices
+- Configurable audio mode button visibility
+
+**Platform Availability:** iOS, Android, React Native, Flutter (mobile platforms only)
+
+**Use Cases:** Switching to speaker during long calls, using Bluetooth in car, private calls with earpiece
+
+---
+
+### Raise Hand
+
+Virtual hand-raising feature for orderly communication in group calls without verbal interruption.
+
+**How It Works:**
+Participants can raise their hand to signal they want to speak. Other participants see a visual indicator, enabling orderly communication.
+
+**Features:**
+- `raiseHand()` - Show hand-raised indicator
+- `lowerHand()` - Remove hand-raised indicator
+- Events for when participants raise/lower hands
+- Configurable raise hand button visibility
+
+**Use Cases:** Q&A sessions, classroom discussions, moderated panels, orderly group discussions
+
+---
+
+### Idle Timeout
+
+Automatic session termination when a participant is alone in a call, preventing abandoned sessions.
+
+**How It Works:**
+When all other participants leave, a countdown timer starts. After the timeout period, a prompt is shown. If no response, the session ends automatically.
+
+**Configuration:**
+- `idleTimeoutPeriodBeforePrompt` - Time before showing the timeout prompt (default: 60 seconds)
+- `idleTimeoutPeriodAfterPrompt` - Time after prompt before ending session (default: 120 seconds)
+
+**Features:**
+- Configurable timeout periods
+- Session timeout event (`onSessionTimedOut`)
+- Can be effectively disabled by setting very long timeout periods
+
+**Use Cases:** Preventing forgotten calls, resource management, automatic cleanup
+
+---
+
+## Advanced Features
+
+### Custom Control Panel
+
+Build a fully customized control panel by hiding default controls and implementing your own UI.
+
+**How It Works:**
+Configure session settings to hide the default control panel (`hideControlPanel: true`), then implement your own buttons that call SDK actions.
+
+**Customization Options:**
+- Hide entire control panel
+- Hide individual buttons (mute, video, screen share, recording, etc.)
+- Intercept button clicks for custom behavior
+- Build completely custom UI with SDK actions
+
+**Available Actions:**
+- `muteAudio()` / `unMuteAudio()`
+- `pauseVideo()` / `resumeVideo()`
+- `switchCamera()`
+- `startScreenSharing()` / `stopScreenSharing()`
+- `startRecording()` / `stopRecording()`
+- `leaveSession()`
+
+**Use Cases:** Branded call experience, simplified UI for specific use cases, adding app-specific actions
+
+---
+
+### Picture-in-Picture
+
+Continue video calls in a floating window while using other content.
+
+**How It Works:**
+Picture-in-Picture uses the browser's Picture-in-Picture API (web) or native PiP frameworks (Android) to display the call video in a floating overlay window.
+
+**Features:**
+- `enablePictureInPictureLayout()` - Enable PiP mode
+- `disablePictureInPictureLayout()` - Return to normal view
+- Floating video window that stays on top
+- Works when switching tabs or apps
+
+**Browser Support (Web):**
+- Chrome 70+
+- Safari 13.1+
+- Edge (Chromium-based)
+- Opera 57+
+
+**Platform Availability:** JavaScript (Web) and Android only
+
+**Use Cases:** Multitasking during calls, reference documents while on call, monitoring calls while working
+
+---
+
+### Device Management
+
+Select and switch between available cameras and microphones during calls.
+
+**How It Works:**
+The SDK enumerates available media devices and allows users to select their preferred input/output devices. Changes take effect immediately without interrupting the call.
+
+**Features:**
+- `getAudioInputDevices()` - List available microphones
+- `getAudioOutputDevices()` - List available speakers
+- `getVideoInputDevices()` - List available cameras
+- `setAudioInputDevice(deviceId)` - Switch microphone
+- `setAudioOutputDevice(deviceId)` - Switch speaker
+- `setVideoInputDevice(deviceId)` - Switch camera
+- Device change events when devices are connected/disconnected
+- Built-in settings dialog (`showSettingsDialog()`)
+
+**Platform Availability:** JavaScript (Web) only
+
+**Use Cases:** Switching to external webcam, using USB microphone, selecting Bluetooth headset
+
+---
+
+### VoIP Calling
+
+Native VoIP integration with system-level call handling for a phone-like calling experience.
+
+**How It Works:**
+VoIP calling integrates with the operating system's native call infrastructure (CallKit on iOS, ConnectionService on Android) to provide system-level call UI and lock screen controls.
+
+**iOS (CallKit) Features:**
+- Native iOS incoming call UI (full screen)
+- Lock screen call controls
+- Call appears in Phone app's Recents
+- CarPlay integration
+- Do Not Disturb respect
+
+**Android Features:**
+- System notification with call controls
+- Foreground service for reliable call handling
+- Lock screen call controls
+
+**Requirements:**
+- VoIP push certificate (iOS)
+- Push notifications configured
+- Platform-specific implementation (CallManager, PushKit delegate)
+
+**Platform Availability:** iOS, Android, React Native
+
+**Use Cases:** Phone-like calling experience, reliable incoming calls, car integration
+
+---
+
+### Background Handling
+
+Keep calls alive when users navigate away from your app on mobile devices.
+
+**How It Works:**
+Background handling uses platform-specific APIs to keep the call active when the user switches to another app or locks their device. On Android, this is achieved through `CometChatOngoingCallService` - a foreground service.
+
+**Android Features:**
+- `CometChatOngoingCallService.launch()` - Start foreground service
+- `CometChatOngoingCallService.abort()` - Stop service
+- Ongoing notification in status bar
+- Tap notification to return to call
+- Customizable notification appearance
+
+**iOS Features:**
+- Audio continues in background (requires Background Modes capability)
+- CallKit integration for lock screen controls
+- Background task handling
+
+**Required Permissions (Android):**
+```xml
+
+
+```
+
+**Platform Availability:** iOS, Android, React Native
+
+**Use Cases:** Multitasking during calls, checking messages mid-call, hands-free calling
+
+---
+
+### In-Call Chat
+
+Enable text messaging during calls by integrating the chat button with your messaging solution.
+
+**How It Works:**
+The SDK provides a chat button in the control panel and events to help you build a custom chat experience. The actual messaging functionality should be implemented using the CometChat Chat SDK or your own messaging solution.
+
+**Features:**
+- Chat button visibility control (`hideChatButton`)
+- Chat button click events (`onChatButtonClicked`)
+- Unread message badge (`setChatButtonUnreadCount`)
+
+**Integration:**
+The Calls SDK provides UI hooks, but messaging is handled separately:
+1. Show/hide chat button in call UI
+2. Listen for chat button clicks
+3. Open your chat interface (using CometChat Chat SDK or custom)
+4. Update unread badge count
+
+**Platform Availability:** All platforms
+
+**Use Cases:** Sharing meeting links, posting questions, sharing code snippets, silent communication
+
+---
+
+
+**Platform Availability:** Not all features are available on every platform. See the [Compatibility](/calls/platform/compatibility) page for a detailed feature comparison matrix.
+
+
+## Next Steps
+
+
+
+ Check platform and browser support
+
+
+ Set up user authentication
+
+
diff --git a/calls/platform/overview.mdx b/calls/platform/overview.mdx
new file mode 100644
index 00000000..83781f23
--- /dev/null
+++ b/calls/platform/overview.mdx
@@ -0,0 +1,72 @@
+---
+title: "What is CometChat Calls?"
+sidebarTitle: "Overview"
+description: "Complete voice and video calling solution built on WebRTC with drop-in UI components."
+---
+
+import { CardGroup, Card, Callout } from 'mintlify';
+
+CometChat Calls SDK provides a complete voice and video calling solution built on WebRTC. It delivers enterprise-grade real-time communication with pre-built UI components, allowing you to add calling functionality to your app in minutes rather than months.
+
+## Why CometChat Calls?
+
+Building real-time voice and video calling from scratch requires expertise in WebRTC, media servers, network optimization, and cross-platform development. CometChat handles all this complexity so you can focus on your core product.
+
+| Benefit | Description |
+|---------|-------------|
+| **Built-in UI** | Pre-built call screens, controls, and participant views—no UI work required |
+| **WebRTC Powered** | Enterprise-grade media infrastructure handles all the complexity |
+| **Cross-Platform** | Web, iOS, Android, React Native, and Flutter with consistent APIs |
+| **Scalable Infrastructure** | Global media servers with automatic scaling and low latency |
+
+## How It Works
+
+1. **Initialize** — Set up the SDK with your App ID and Region
+2. **Authenticate** — Log in users with their CometChat auth token
+3. **Join Call** — Generate a session token and join the call
+4. **Ready** — The SDK renders a complete call UI automatically
+
+
+**Already using CometChat UI Kits?** Voice and video calling is already integrated with ready-to-use components. Use the Calls SDK directly only if you need custom call UI or advanced control.
+
+
+## Use Cases
+
+CometChat Calls powers real-time communication across industries:
+
+| Industry | Use Cases |
+|----------|-----------|
+| **Healthcare** | Telehealth consultations, patient check-ins, specialist referrals |
+| **Education** | Virtual classrooms, tutoring sessions, parent-teacher meetings |
+| **Marketplace** | Buyer-seller negotiations, customer support, expert consultations |
+| **Social** | Video dating, group hangouts, live streaming |
+| **Enterprise** | Team meetings, remote interviews, client calls |
+| **Gaming** | Voice chat, team coordination, live commentary |
+
+## What's Included
+
+
+
+ 1-to-1 and group calls with HD audio/video, adaptive bitrate, and noise cancellation
+
+
+ Mute, screen share, recording, layouts, participant management, and more
+
+
+ Complete call screens with controls, participant grid, and responsive layouts
+
+
+ Native SDKs for JavaScript, iOS, Android, React Native, and Flutter
+
+
+
+## Next Steps
+
+
+
+ Explore all available features in detail
+
+
+ Check platform and browser support
+
+
diff --git a/calls/platform/user-sync.mdx b/calls/platform/user-sync.mdx
new file mode 100644
index 00000000..13e0fd63
--- /dev/null
+++ b/calls/platform/user-sync.mdx
@@ -0,0 +1,262 @@
+---
+title: "User Sync"
+sidebarTitle: "User Sync"
+description: "How to sync users with CometChat for voice and video calling."
+---
+
+import { CardGroup, Card, Steps, Callout } from 'mintlify';
+
+Users must exist in CometChat before they can make or receive calls. This page explains how to create and authenticate users for the Calls SDK.
+
+## Why User Sync?
+
+CometChat needs to know about your users to:
+
+- **Route calls** — Connect callers to the right recipients
+- **Identify participants** — Display names and avatars in calls
+- **Track history** — Associate call logs with specific users
+- **Manage permissions** — Control who can call whom
+
+## Creating Users
+
+Choose the method that best fits your workflow:
+
+
+
+ Add users manually via the CometChat Dashboard. Ideal for quick testing or small teams.
+
+
+ Create users programmatically via SDK methods. Perfect for auto-provisioning during sign-up.
+
+
+ Create users using the REST API. Best for batch imports or backend workflows.
+
+
+
+## User Requirements
+
+### Required Fields
+
+| Field | Type | Description |
+|:------|:-----|:------------|
+| `uid` | String | Unique user identifier. Alphanumeric, max 100 characters. Must be unique across your app. |
+
+### Optional Fields
+
+| Field | Type | Description |
+|:------|:-----|:------------|
+| `name` | String | Display name shown in calls and participant lists |
+| `avatar` | String | URL to profile image |
+| `metadata` | Object | Custom JSON data for your application |
+| `role` | String | User role (default, admin, etc.) |
+| `tags` | Array | Tags for categorization and filtering |
+
+### Example User Object
+
+```json
+{
+ "uid": "user_123",
+ "name": "John Doe",
+ "avatar": "https://example.com/avatars/john.png",
+ "metadata": {
+ "department": "Engineering",
+ "location": "San Francisco"
+ }
+}
+```
+
+## Authentication Flow
+
+
+
+ Create the user in CometChat when they sign up for your app. This only needs to happen once per user.
+
+ **Server-side (recommended):**
+ ```bash
+ curl -X POST "https://api-{region}.cometchat.io/v3/users" \
+ -H "apiKey: YOUR_API_KEY" \
+ -H "Content-Type: application/json" \
+ -d '{"uid": "user_123", "name": "John Doe"}'
+ ```
+
+
+
+ Generate an authentication token for the user on your server. This token is used to log in the user on the client.
+
+ **Server-side:**
+ ```bash
+ curl -X POST "https://api-{region}.cometchat.io/v3/users/{uid}/auth_tokens" \
+ -H "apiKey: YOUR_API_KEY" \
+ -H "Content-Type: application/json"
+ ```
+
+ **Response:**
+ ```json
+ {
+ "data": {
+ "uid": "user_123",
+ "authToken": "user_123_abc123xyz..."
+ }
+ }
+ ```
+
+
+
+ Use the auth token to log in the user via the SDK. This establishes a session for making calls.
+
+ **JavaScript:**
+ ```javascript
+ CometChatCalls.login(authToken).then(
+ user => console.log("Login successful:", user),
+ error => console.log("Login failed:", error)
+ );
+ ```
+
+
+
+ Once logged in, the user can initiate and receive calls. The SDK handles all session management automatically.
+
+
+
+## Authentication Methods
+
+### Auth Token (Recommended for Production)
+
+Generate tokens server-side using the REST API. This is the secure method for production apps.
+
+**Pros:**
+- Secure — API key never exposed to clients
+- Controlled — You decide when tokens are issued
+- Auditable — Token generation can be logged
+
+**Flow:**
+1. User logs into your app
+2. Your server generates a CometChat auth token
+3. Token is sent to the client
+4. Client uses token to log into CometChat
+
+### Auth Key (Development Only)
+
+Use the Auth Key directly in client code for quick testing. **Never use in production.**
+
+**JavaScript:**
+```javascript
+CometChatCalls.login(uid, authKey).then(
+ user => console.log("Login successful:", user),
+ error => console.log("Login failed:", error)
+);
+```
+
+
+**Security Warning**: Never expose your Auth Key in production client code. Auth Keys have full access to create users and perform admin operations. Always use auth tokens generated server-side for production apps.
+
+
+## Sync Strategies
+
+### Just-in-Time Provisioning
+
+Create CometChat users when they first need calling functionality:
+
+```javascript
+// When user initiates their first call
+async function ensureUserExists(user) {
+ try {
+ // Try to get auth token (user exists)
+ return await getAuthToken(user.id);
+ } catch (error) {
+ // User doesn't exist, create them first
+ await createCometChatUser(user);
+ return await getAuthToken(user.id);
+ }
+}
+```
+
+**Best for:** Apps where not all users need calling
+
+### Batch Import
+
+Import existing users via REST API when integrating CometChat:
+
+```bash
+# Create multiple users
+curl -X POST "https://api-{region}.cometchat.io/v3/users" \
+ -H "apiKey: YOUR_API_KEY" \
+ -H "Content-Type: application/json" \
+ -d '[
+ {"uid": "user_1", "name": "Alice"},
+ {"uid": "user_2", "name": "Bob"},
+ {"uid": "user_3", "name": "Charlie"}
+ ]'
+```
+
+**Best for:** Migrating existing user bases
+
+### Webhook Sync
+
+Use webhooks to keep CometChat users in sync with your database:
+
+1. User signs up → Create CometChat user
+2. User updates profile → Update CometChat user
+3. User deletes account → Delete CometChat user
+
+**Best for:** Apps with frequent user changes
+
+## User Lifecycle
+
+### Creating Users
+
+Users can be created via:
+- REST API (server-side)
+- SDK `createUser` method
+- Dashboard (manual)
+
+### Updating Users
+
+Update user details when they change in your app:
+
+```bash
+curl -X PUT "https://api-{region}.cometchat.io/v3/users/{uid}" \
+ -H "apiKey: YOUR_API_KEY" \
+ -H "Content-Type: application/json" \
+ -d '{"name": "John Smith", "avatar": "https://new-avatar.png"}'
+```
+
+### Deleting Users
+
+Remove users when they leave your platform:
+
+```bash
+curl -X DELETE "https://api-{region}.cometchat.io/v3/users/{uid}" \
+ -H "apiKey: YOUR_API_KEY"
+```
+
+
+**Soft Delete**: By default, deleted users are soft-deleted and can be restored. Use `permanent=true` query parameter for permanent deletion.
+
+
+## Best Practices
+
+### Do
+
+- Generate auth tokens server-side
+- Use consistent UIDs across your app
+- Keep user data in sync
+- Handle token expiration gracefully
+
+### Don't
+
+- Expose API keys in client code
+- Use Auth Key in production
+- Create duplicate users
+- Store auth tokens long-term
+
+## Next Steps
+
+
+
+ Check platform and browser support
+
+
+ Full API documentation for user management
+
+
diff --git a/calls/react-native/actions.mdx b/calls/react-native/actions.mdx
new file mode 100644
index 00000000..dd0a2477
--- /dev/null
+++ b/calls/react-native/actions.mdx
@@ -0,0 +1,329 @@
+---
+title: "Actions"
+sidebarTitle: "Actions"
+---
+
+Control the active call session programmatically using the static methods on `CometChatCalls`. These methods allow you to manage audio, video, screen sharing, and other call features.
+
+## Audio Controls
+
+### Mute Audio
+
+Mute the local user's microphone:
+
+```tsx
+CometChatCalls.muteAudio();
+```
+
+### Unmute Audio
+
+Unmute the local user's microphone:
+
+```tsx
+CometChatCalls.unMuteAudio();
+```
+
+## Video Controls
+
+### Pause Video
+
+Stop sending the local user's video:
+
+```tsx
+CometChatCalls.pauseVideo();
+```
+
+### Resume Video
+
+Resume sending the local user's video:
+
+```tsx
+CometChatCalls.resumeVideo();
+```
+
+### Switch Camera
+
+Toggle between front and rear cameras:
+
+```tsx
+CometChatCalls.switchCamera();
+```
+
+## Session Control
+
+### Leave Session
+
+End the call and leave the session:
+
+```tsx
+CometChatCalls.leaveSession();
+```
+
+
+The `leaveSession()` method ends the call for the local user. Other participants will remain in the call unless they also leave.
+
+
+## Screen Sharing
+
+### Start Screen Sharing
+
+Begin sharing your screen with other participants:
+
+```tsx
+CometChatCalls.startScreenSharing();
+```
+
+### Stop Screen Sharing
+
+Stop sharing your screen:
+
+```tsx
+CometChatCalls.stopScreenSharing();
+```
+
+
+Screen sharing requires additional platform-specific configuration. See [Screen Sharing](/calls/react-native/screen-sharing) for setup instructions.
+
+
+## Raise Hand
+
+### Raise Hand
+
+Signal to other participants that you want attention:
+
+```tsx
+CometChatCalls.raiseHand();
+```
+
+### Lower Hand
+
+Lower your raised hand:
+
+```tsx
+CometChatCalls.lowerHand();
+```
+
+## Layout Control
+
+### Set Layout
+
+Change the call layout programmatically:
+
+```tsx
+// Available layouts: 'TILE', 'SIDEBAR', 'SPOTLIGHT'
+CometChatCalls.setLayout('TILE');
+CometChatCalls.setLayout('SIDEBAR');
+CometChatCalls.setLayout('SPOTLIGHT');
+```
+
+| Layout | Description |
+|--------|-------------|
+| `TILE` | Grid layout showing all participants equally |
+| `SIDEBAR` | Main video with participants in a sidebar |
+| `SPOTLIGHT` | Focus on one participant with others minimized |
+
+## Picture-in-Picture
+
+### Enable Picture-in-Picture
+
+Enable PiP mode to continue the call in a floating window:
+
+```tsx
+CometChatCalls.enablePictureInPictureLayout();
+```
+
+### Disable Picture-in-Picture
+
+Exit PiP mode and return to full-screen:
+
+```tsx
+CometChatCalls.disablePictureInPictureLayout();
+```
+
+## Participant Management
+
+### Pin Participant
+
+Pin a participant to the main view:
+
+```tsx
+const participantId = 'participant_pid';
+const type = 'human'; // 'human' or 'screen-share'
+
+CometChatCalls.pinParticipant(participantId, type);
+```
+
+| Parameter | Type | Description |
+|-----------|------|-------------|
+| `participantId` | string | The participant's ID |
+| `type` | string | `'human'` for user video, `'screen-share'` for screen share |
+
+### Unpin Participant
+
+Remove the pinned participant:
+
+```tsx
+CometChatCalls.unpinParticipant();
+```
+
+### Mute Participant
+
+Mute another participant's audio (requires moderator permissions):
+
+```tsx
+const participantId = 'participant_pid';
+CometChatCalls.muteParticipant(participantId);
+```
+
+### Pause Participant Video
+
+Pause another participant's video (requires moderator permissions):
+
+```tsx
+const participantId = 'participant_pid';
+CometChatCalls.pauseParticipantVideo(participantId);
+```
+
+## Chat Integration
+
+### Set Chat Button Unread Count
+
+Update the unread message count on the chat button:
+
+```tsx
+CometChatCalls.setChatButtonUnreadCount(5);
+```
+
+## Complete Example
+
+```tsx
+import React, { useState } from 'react';
+import { View, TouchableOpacity, Text, StyleSheet } from 'react-native';
+import { CometChatCalls } from '@cometchat/calls-sdk-react-native';
+
+function CallControls() {
+ const [isAudioMuted, setIsAudioMuted] = useState(false);
+ const [isVideoMuted, setIsVideoMuted] = useState(false);
+ const [isHandRaised, setIsHandRaised] = useState(false);
+ const [currentLayout, setCurrentLayout] = useState<'TILE' | 'SIDEBAR' | 'SPOTLIGHT'>('TILE');
+
+ const toggleAudio = () => {
+ if (isAudioMuted) {
+ CometChatCalls.unMuteAudio();
+ } else {
+ CometChatCalls.muteAudio();
+ }
+ setIsAudioMuted(!isAudioMuted);
+ };
+
+ const toggleVideo = () => {
+ if (isVideoMuted) {
+ CometChatCalls.resumeVideo();
+ } else {
+ CometChatCalls.pauseVideo();
+ }
+ setIsVideoMuted(!isVideoMuted);
+ };
+
+ const toggleHand = () => {
+ if (isHandRaised) {
+ CometChatCalls.lowerHand();
+ } else {
+ CometChatCalls.raiseHand();
+ }
+ setIsHandRaised(!isHandRaised);
+ };
+
+ const cycleLayout = () => {
+ const layouts: Array<'TILE' | 'SIDEBAR' | 'SPOTLIGHT'> = ['TILE', 'SIDEBAR', 'SPOTLIGHT'];
+ const currentIndex = layouts.indexOf(currentLayout);
+ const nextLayout = layouts[(currentIndex + 1) % layouts.length];
+ CometChatCalls.setLayout(nextLayout);
+ setCurrentLayout(nextLayout);
+ };
+
+ const handleSwitchCamera = () => {
+ CometChatCalls.switchCamera();
+ };
+
+ const handleEndCall = () => {
+ CometChatCalls.leaveSession();
+ };
+
+ return (
+
+
+
+ {isAudioMuted ? 'Unmute' : 'Mute'}
+
+
+
+
+
+ {isVideoMuted ? 'Show Video' : 'Hide Video'}
+
+
+
+
+ Switch Camera
+
+
+
+
+ {isHandRaised ? 'Lower Hand' : 'Raise Hand'}
+
+
+
+
+ Layout: {currentLayout}
+
+
+
+ End Call
+
+
+ );
+}
+
+const styles = StyleSheet.create({
+ container: {
+ flexDirection: 'row',
+ flexWrap: 'wrap',
+ justifyContent: 'center',
+ padding: 16,
+ gap: 8,
+ },
+ button: {
+ backgroundColor: '#333',
+ paddingHorizontal: 16,
+ paddingVertical: 12,
+ borderRadius: 8,
+ },
+ endButton: {
+ backgroundColor: '#ff4444',
+ },
+ buttonText: {
+ color: '#fff',
+ fontSize: 14,
+ fontWeight: '600',
+ },
+});
+
+export default CallControls;
+```
+
+## Legacy Methods
+
+These methods are deprecated but still available for backward compatibility:
+
+| Deprecated Method | Replacement |
+|-------------------|-------------|
+| `startScreenShare()` | `startScreenSharing()` |
+| `stopScreenShare()` | `stopScreenSharing()` |
+| `endSession()` | `leaveSession()` |
+
+## Related Documentation
+
+- [Events](/calls/react-native/events) - Listen for call events
+- [Participant Management](/calls/react-native/participant-management) - Manage participants
+- [Call Layouts](/calls/react-native/call-layouts) - Layout options
diff --git a/calls/react-native/audio-modes.mdx b/calls/react-native/audio-modes.mdx
new file mode 100644
index 00000000..b385f35b
--- /dev/null
+++ b/calls/react-native/audio-modes.mdx
@@ -0,0 +1,293 @@
+---
+title: "Audio Modes"
+sidebarTitle: "Audio Modes"
+---
+
+The CometChat Calls SDK supports multiple audio output modes on mobile devices. Users can switch between speaker, earpiece, Bluetooth, and wired headphones.
+
+## Available Audio Modes
+
+| Mode | Constant | Description |
+|------|----------|-------------|
+| Speaker | `CometChatCalls.AUDIO_MODE.SPEAKER` | Phone speaker (loudspeaker) |
+| Earpiece | `CometChatCalls.AUDIO_MODE.EARPIECE` | Phone earpiece (for private calls) |
+| Bluetooth | `CometChatCalls.AUDIO_MODE.BLUETOOTH` | Connected Bluetooth device |
+| Headphones | `CometChatCalls.AUDIO_MODE.HEADPHONES` | Wired headphones |
+
+## Set Default Audio Mode
+
+Configure the default audio mode when creating call settings:
+
+```tsx
+import { CometChatCalls } from '@cometchat/calls-sdk-react-native';
+
+const callSettings = new CometChatCalls.CallSettingsBuilder()
+ .setDefaultAudioMode(CometChatCalls.AUDIO_MODE.SPEAKER)
+ .build();
+```
+
+## Show Audio Mode Button
+
+Enable the audio mode button in the call UI:
+
+```tsx
+const callSettings = new CometChatCalls.CallSettingsBuilder()
+ .showAudioModeButton(true)
+ .build();
+```
+
+## Listen for Audio Mode Changes
+
+Subscribe to audio mode change events:
+
+```tsx
+CometChatCalls.addEventListener('onAudioModeChanged', (mode) => {
+ console.log('Audio mode changed to:', mode);
+ // mode: 'SPEAKER' | 'EARPIECE' | 'BLUETOOTH' | 'HEADPHONES'
+});
+```
+
+## Using OngoingCallListener
+
+Handle audio mode updates through the call settings listener:
+
+```tsx
+const callListener = new CometChatCalls.OngoingCallListener({
+ onAudioModesUpdated: (audioModes) => {
+ console.log('Available audio modes:', audioModes);
+ // audioModes is an array of available modes
+ },
+});
+
+const callSettings = new CometChatCalls.CallSettingsBuilder()
+ .setCallEventListener(callListener)
+ .build();
+```
+
+## Audio Mode Object
+
+When receiving audio mode updates, each mode object contains:
+
+| Property | Type | Description |
+|----------|------|-------------|
+| `type` | string | Mode type (`SPEAKER`, `EARPIECE`, `BLUETOOTH`, `HEADPHONES`) |
+| `selected` | boolean | Whether this mode is currently active |
+
+## Complete Example
+
+```tsx
+import React, { useState, useEffect } from 'react';
+import { View, TouchableOpacity, Text, StyleSheet, Modal, FlatList } from 'react-native';
+import { CometChatCalls } from '@cometchat/calls-sdk-react-native';
+
+type AudioMode = 'SPEAKER' | 'EARPIECE' | 'BLUETOOTH' | 'HEADPHONES';
+
+interface AudioModeOption {
+ type: AudioMode;
+ label: string;
+ icon: string;
+}
+
+const audioModeOptions: AudioModeOption[] = [
+ { type: 'SPEAKER', label: 'Speaker', icon: '🔊' },
+ { type: 'EARPIECE', label: 'Earpiece', icon: '📱' },
+ { type: 'BLUETOOTH', label: 'Bluetooth', icon: '🎧' },
+ { type: 'HEADPHONES', label: 'Headphones', icon: '🎵' },
+];
+
+function AudioModeSelector() {
+ const [currentMode, setCurrentMode] = useState('SPEAKER');
+ const [availableModes, setAvailableModes] = useState(['SPEAKER', 'EARPIECE']);
+ const [modalVisible, setModalVisible] = useState(false);
+
+ useEffect(() => {
+ const unsubscribe = CometChatCalls.addEventListener(
+ 'onAudioModeChanged',
+ (mode: AudioMode) => {
+ setCurrentMode(mode);
+ }
+ );
+
+ return () => unsubscribe();
+ }, []);
+
+ const selectMode = (mode: AudioMode) => {
+ // The SDK handles audio mode switching through the UI
+ // This is for display purposes
+ setCurrentMode(mode);
+ setModalVisible(false);
+ };
+
+ const getCurrentModeOption = () => {
+ return audioModeOptions.find((opt) => opt.type === currentMode) || audioModeOptions[0];
+ };
+
+ const renderModeOption = ({ item }: { item: AudioModeOption }) => {
+ const isAvailable = availableModes.includes(item.type);
+ const isSelected = currentMode === item.type;
+
+ return (
+ isAvailable && selectMode(item.type)}
+ disabled={!isAvailable}
+ >
+ {item.icon}
+
+ {item.label}
+
+ {isSelected && ✓ }
+
+ );
+ };
+
+ return (
+
+ setModalVisible(true)}
+ >
+ {getCurrentModeOption().icon}
+ {getCurrentModeOption().label}
+
+
+ setModalVisible(false)}
+ >
+
+
+ Audio Output
+ item.type}
+ renderItem={renderModeOption}
+ />
+ setModalVisible(false)}
+ >
+ Close
+
+
+
+
+
+ );
+}
+
+const styles = StyleSheet.create({
+ button: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ backgroundColor: '#333',
+ paddingHorizontal: 16,
+ paddingVertical: 12,
+ borderRadius: 8,
+ gap: 8,
+ },
+ buttonIcon: {
+ fontSize: 18,
+ },
+ buttonText: {
+ color: '#fff',
+ fontSize: 14,
+ fontWeight: '600',
+ },
+ modalOverlay: {
+ flex: 1,
+ backgroundColor: 'rgba(0, 0, 0, 0.5)',
+ justifyContent: 'flex-end',
+ },
+ modalContent: {
+ backgroundColor: '#1a1a1a',
+ borderTopLeftRadius: 20,
+ borderTopRightRadius: 20,
+ padding: 20,
+ },
+ modalTitle: {
+ color: '#fff',
+ fontSize: 18,
+ fontWeight: '600',
+ marginBottom: 16,
+ textAlign: 'center',
+ },
+ modeOption: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ padding: 16,
+ borderRadius: 8,
+ marginBottom: 8,
+ backgroundColor: '#333',
+ },
+ selectedOption: {
+ backgroundColor: '#6851D6',
+ },
+ disabledOption: {
+ opacity: 0.5,
+ },
+ modeIcon: {
+ fontSize: 24,
+ marginRight: 12,
+ },
+ modeLabel: {
+ flex: 1,
+ color: '#fff',
+ fontSize: 16,
+ },
+ selectedLabel: {
+ fontWeight: '600',
+ },
+ disabledLabel: {
+ color: '#666',
+ },
+ checkmark: {
+ color: '#fff',
+ fontSize: 18,
+ },
+ closeButton: {
+ marginTop: 16,
+ padding: 16,
+ alignItems: 'center',
+ },
+ closeButtonText: {
+ color: '#6851D6',
+ fontSize: 16,
+ fontWeight: '600',
+ },
+});
+
+export default AudioModeSelector;
+```
+
+## Platform Considerations
+
+### iOS
+
+- Audio mode switching is handled automatically by iOS based on connected devices
+- Bluetooth devices appear when connected
+- Headphones are detected when plugged in
+
+### Android
+
+- Requires `MODIFY_AUDIO_SETTINGS` permission
+- Bluetooth requires `BLUETOOTH` and `BLUETOOTH_CONNECT` permissions
+- Audio routing may vary by device manufacturer
+
+## Related Documentation
+
+- [Session Settings](/calls/react-native/session-settings) - Configure default audio mode
+- [Events](/calls/react-native/events) - Audio mode events
+- [Actions](/calls/react-native/actions) - Audio control methods
diff --git a/calls/react-native/authentication.mdx b/calls/react-native/authentication.mdx
new file mode 100644
index 00000000..c5f66cf0
--- /dev/null
+++ b/calls/react-native/authentication.mdx
@@ -0,0 +1,194 @@
+---
+title: "Authentication"
+sidebarTitle: "Authentication"
+---
+
+This guide covers initializing the CometChat Calls SDK and authenticating users in your React Native application.
+
+## Initialize CometChat Calls
+
+The `init()` method initializes the SDK with your app credentials. Call this method once when your application starts, typically in your app's entry point or a dedicated initialization module.
+
+### CallAppSettingsBuilder
+
+The `CallAppSettingsBuilder` class configures the SDK initialization:
+
+| Parameter | Type | Required | Description |
+|-----------|------|----------|-------------|
+| `appId` | string | Yes | Your CometChat App ID |
+| `region` | string | Yes | Your app region (`us`, `eu`, or `in`) |
+
+```tsx
+import { CometChatCalls } from '@cometchat/calls-sdk-react-native';
+
+const appId = 'APP_ID'; // Replace with your App ID
+const region = 'REGION'; // Replace with your Region ("us", "eu", or "in")
+
+const callAppSettings = new CometChatCalls.CallAppSettingsBuilder()
+ .setAppId(appId)
+ .setRegion(region)
+ .build();
+
+const initResult = await CometChatCalls.init(callAppSettings);
+
+if (initResult.success) {
+ console.log('CometChat Calls SDK initialized successfully');
+} else {
+ console.error('CometChat Calls SDK initialization failed:', initResult.error);
+}
+```
+
+## Login
+
+After initialization, authenticate the user using one of the login methods.
+
+### Login with UID and Auth Key
+
+Use this method during development or when you manage authentication on the client side:
+
+```tsx
+const uid = 'USER_UID';
+const authKey = 'AUTH_KEY';
+
+try {
+ const user = await CometChatCalls.login(uid, authKey);
+ console.log('Login successful:', user);
+} catch (error) {
+ console.error('Login failed:', error);
+}
+```
+
+| Parameter | Type | Required | Description |
+|-----------|------|----------|-------------|
+| `uid` | string | Yes | The unique identifier of the user |
+| `authKey` | string | Yes | Your CometChat Auth Key |
+
+
+Never expose your Auth Key in production apps. Use Auth Tokens generated from your backend server instead.
+
+
+### Login with Auth Token
+
+For production apps, generate an Auth Token on your backend server and use it to authenticate:
+
+```tsx
+const authToken = 'USER_AUTH_TOKEN'; // Token from your backend
+
+try {
+ const user = await CometChatCalls.loginWithAuthToken(authToken);
+ console.log('Login successful:', user);
+} catch (error) {
+ console.error('Login failed:', error);
+}
+```
+
+| Parameter | Type | Required | Description |
+|-----------|------|----------|-------------|
+| `authToken` | string | Yes | Auth token generated from your backend |
+
+## Logout
+
+Log out the current user when they sign out of your app:
+
+```tsx
+try {
+ const message = await CometChatCalls.logout();
+ console.log('Logout successful:', message);
+} catch (error) {
+ console.error('Logout failed:', error);
+}
+```
+
+## Check Login Status
+
+Verify if a user is currently logged in:
+
+```tsx
+const isLoggedIn = CometChatCalls.isUserLoggedIn();
+
+if (isLoggedIn) {
+ console.log('User is logged in');
+} else {
+ console.log('No user logged in');
+}
+```
+
+## Get Logged In User
+
+Retrieve the currently logged-in user's details:
+
+```tsx
+const user = CometChatCalls.getLoggedInUser();
+
+if (user) {
+ console.log('Current user:', user.uid, user.name);
+} else {
+ console.log('No user logged in');
+}
+```
+
+## Get User Auth Token
+
+Retrieve the auth token of the currently logged-in user:
+
+```tsx
+const authToken = CometChatCalls.getUserAuthToken();
+
+if (authToken) {
+ console.log('User auth token:', authToken);
+}
+```
+
+## Login Listeners
+
+Monitor login state changes by adding a login listener:
+
+```tsx
+const listenerId = 'unique_listener_id';
+
+CometChatCalls.addLoginListener(listenerId, {
+ onLoginSuccess: (user) => {
+ console.log('User logged in:', user);
+ },
+ onLoginFailure: (error) => {
+ console.error('Login failed:', error);
+ },
+ onLogoutSuccess: () => {
+ console.log('User logged out');
+ },
+ onLogoutFailure: (error) => {
+ console.error('Logout failed:', error);
+ },
+});
+
+// Remove listener when no longer needed
+CometChatCalls.removeLoginListener(listenerId);
+```
+
+## Error Handling
+
+The SDK throws structured errors with the following properties:
+
+| Property | Type | Description |
+|----------|------|-------------|
+| `errorCode` | string | A unique error code |
+| `errorDescription` | string | Human-readable error description |
+
+Common error codes:
+
+| Error Code | Description |
+|------------|-------------|
+| `ERROR_SDK_NOT_INITIALIZED` | SDK not initialized. Call `init()` first |
+| `ERROR_LOGIN_IN_PROGRESS` | A login operation is already in progress |
+| `ERROR_INVALID_UID` | UID is empty or invalid |
+| `ERROR_UID_WITH_SPACE` | UID contains spaces |
+| `ERROR_API_KEY_NOT_FOUND` | Auth Key is empty |
+| `ERROR_BLANK_AUTHTOKEN` | Auth token is empty |
+| `ERROR_AUTHTOKEN_WITH_SPACE` | Auth token contains spaces |
+| `ERROR_NO_USER_LOGGED_IN` | No user is currently logged in |
+
+## Related Documentation
+
+- [Setup](/calls/react-native/setup) - Install and configure the SDK
+- [Session Settings](/calls/react-native/session-settings) - Configure call settings
+- [Join Session](/calls/react-native/join-session) - Start your first call
diff --git a/calls/react-native/background-handling.mdx b/calls/react-native/background-handling.mdx
new file mode 100644
index 00000000..f0b84327
--- /dev/null
+++ b/calls/react-native/background-handling.mdx
@@ -0,0 +1,292 @@
+---
+title: "Background Handling"
+sidebarTitle: "Background Handling"
+---
+
+Keep calls active when your app goes to the background. This requires platform-specific configuration to maintain audio/video streams and handle system interruptions.
+
+## iOS Configuration
+
+### Enable Background Modes
+
+1. Open your project in Xcode
+2. Select your target and go to **Signing & Capabilities**
+3. Add **Background Modes** capability
+4. Enable:
+ - **Audio, AirPlay, and Picture in Picture**
+ - **Voice over IP** (for VoIP push notifications)
+
+### Configure Audio Session
+
+The SDK automatically configures the audio session, but you can customize it in your native code:
+
+```swift
+// ios/AppDelegate.swift
+import AVFoundation
+
+func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
+ // Configure audio session for calls
+ do {
+ try AVAudioSession.sharedInstance().setCategory(
+ .playAndRecord,
+ mode: .voiceChat,
+ options: [.allowBluetooth, .allowBluetoothA2DP, .defaultToSpeaker]
+ )
+ try AVAudioSession.sharedInstance().setActive(true)
+ } catch {
+ print("Failed to configure audio session: \(error)")
+ }
+
+ return true
+}
+```
+
+## Android Configuration
+
+### Add Permissions
+
+Add to your `AndroidManifest.xml`:
+
+```xml
+
+
+
+```
+
+### Configure Foreground Service
+
+For Android 10+, calls require a foreground service to continue in the background:
+
+```xml
+
+```
+
+### Keep Screen Awake
+
+The SDK automatically manages wake locks during calls. No additional configuration is needed.
+
+## Handle App State Changes
+
+Monitor app state to handle background transitions:
+
+```tsx
+import { useEffect, useRef } from 'react';
+import { AppState, AppStateStatus } from 'react-native';
+import { CometChatCalls } from '@cometchat/calls-sdk-react-native';
+
+function useBackgroundHandling() {
+ const appState = useRef(AppState.currentState);
+
+ useEffect(() => {
+ const subscription = AppState.addEventListener(
+ 'change',
+ (nextAppState: AppStateStatus) => {
+ if (
+ appState.current.match(/active/) &&
+ nextAppState === 'background'
+ ) {
+ console.log('App going to background');
+ // Optionally enable PiP
+ CometChatCalls.enablePictureInPictureLayout();
+ } else if (
+ appState.current === 'background' &&
+ nextAppState === 'active'
+ ) {
+ console.log('App coming to foreground');
+ // Optionally disable PiP
+ CometChatCalls.disablePictureInPictureLayout();
+ }
+ appState.current = nextAppState;
+ }
+ );
+
+ return () => {
+ subscription.remove();
+ };
+ }, []);
+}
+
+export default useBackgroundHandling;
+```
+
+## Handle Audio Interruptions
+
+Handle system audio interruptions (phone calls, alarms, etc.):
+
+```tsx
+import { useEffect } from 'react';
+import { NativeEventEmitter, NativeModules, Platform } from 'react-native';
+
+function useAudioInterruptions() {
+ useEffect(() => {
+ if (Platform.OS === 'ios') {
+ // iOS handles audio interruptions automatically
+ // The SDK will pause/resume as needed
+ return;
+ }
+
+ // Android: Listen for audio focus changes
+ // This is typically handled by the SDK automatically
+ }, []);
+}
+
+export default useAudioInterruptions;
+```
+
+## Connection Events
+
+Listen for connection state changes:
+
+```tsx
+import { CometChatCalls } from '@cometchat/calls-sdk-react-native';
+
+// Connection lost (e.g., network issues)
+CometChatCalls.addEventListener('onConnectionLost', () => {
+ console.log('Connection lost - attempting to reconnect');
+});
+
+// Connection restored
+CometChatCalls.addEventListener('onConnectionRestored', () => {
+ console.log('Connection restored');
+});
+
+// Connection closed
+CometChatCalls.addEventListener('onConnectionClosed', () => {
+ console.log('Connection closed');
+});
+```
+
+## Complete Example
+
+```tsx
+import React, { useEffect, useRef, useCallback } from 'react';
+import { View, StyleSheet, AppState, AppStateStatus } from 'react-native';
+import { CometChatCalls } from '@cometchat/calls-sdk-react-native';
+
+interface CallScreenProps {
+ callToken: string;
+ callSettings: any;
+ onCallEnd: () => void;
+}
+
+function CallScreen({ callToken, callSettings, onCallEnd }: CallScreenProps) {
+ const appState = useRef(AppState.currentState);
+ const isInCall = useRef(true);
+
+ // Handle app state changes
+ useEffect(() => {
+ const subscription = AppState.addEventListener(
+ 'change',
+ (nextAppState: AppStateStatus) => {
+ if (!isInCall.current) return;
+
+ if (
+ appState.current.match(/active/) &&
+ nextAppState === 'background'
+ ) {
+ // Going to background - enable PiP for video calls
+ CometChatCalls.enablePictureInPictureLayout();
+ } else if (
+ appState.current === 'background' &&
+ nextAppState === 'active'
+ ) {
+ // Coming to foreground - disable PiP
+ CometChatCalls.disablePictureInPictureLayout();
+ }
+ appState.current = nextAppState;
+ }
+ );
+
+ return () => {
+ subscription.remove();
+ };
+ }, []);
+
+ // Handle connection events
+ useEffect(() => {
+ const unsubscribeLost = CometChatCalls.addEventListener(
+ 'onConnectionLost',
+ () => {
+ console.log('Connection lost');
+ // Show reconnecting UI
+ }
+ );
+
+ const unsubscribeRestored = CometChatCalls.addEventListener(
+ 'onConnectionRestored',
+ () => {
+ console.log('Connection restored');
+ // Hide reconnecting UI
+ }
+ );
+
+ const unsubscribeClosed = CometChatCalls.addEventListener(
+ 'onConnectionClosed',
+ () => {
+ console.log('Connection closed');
+ isInCall.current = false;
+ onCallEnd();
+ }
+ );
+
+ return () => {
+ unsubscribeLost();
+ unsubscribeRestored();
+ unsubscribeClosed();
+ };
+ }, [onCallEnd]);
+
+ // Cleanup on unmount
+ useEffect(() => {
+ return () => {
+ isInCall.current = false;
+ };
+ }, []);
+
+ return (
+
+
+
+ );
+}
+
+const styles = StyleSheet.create({
+ container: {
+ flex: 1,
+ backgroundColor: '#000',
+ },
+});
+
+export default CallScreen;
+```
+
+## Platform Behavior
+
+### iOS
+
+| Scenario | Behavior |
+|----------|----------|
+| App backgrounded | Audio continues, video pauses |
+| Phone call received | Call audio is interrupted |
+| Phone call ended | Call audio resumes |
+| Screen locked | Audio continues |
+
+### Android
+
+| Scenario | Behavior |
+|----------|----------|
+| App backgrounded | Audio continues with foreground service |
+| Phone call received | Call audio may be interrupted |
+| Screen off | Audio continues with wake lock |
+
+## Related Documentation
+
+- [Picture-in-Picture](/calls/react-native/picture-in-picture) - Continue calls in floating window
+- [VoIP Calling](/calls/react-native/voip-calling) - Receive calls when app is closed
+- [Events](/calls/react-native/events) - Connection events
diff --git a/calls/react-native/call-layouts.mdx b/calls/react-native/call-layouts.mdx
new file mode 100644
index 00000000..248f78f7
--- /dev/null
+++ b/calls/react-native/call-layouts.mdx
@@ -0,0 +1,213 @@
+---
+title: "Call Layouts"
+sidebarTitle: "Call Layouts"
+---
+
+The CometChat Calls SDK provides three layout modes for displaying participants during a call. You can set the initial layout in call settings or change it programmatically during the call.
+
+## Available Layouts
+
+### Tile Layout
+
+The default grid layout that displays all participants equally sized in a grid pattern.
+
+```tsx
+CometChatCalls.setLayout('TILE');
+```
+
+### Sidebar Layout
+
+Displays one main participant with other participants in a sidebar. Useful when focusing on a presenter or active speaker.
+
+```tsx
+CometChatCalls.setLayout('SIDEBAR');
+```
+
+### Spotlight Layout
+
+Focuses on a single participant with minimal UI for others. Ideal for presentations or when one participant needs full attention.
+
+```tsx
+CometChatCalls.setLayout('SPOTLIGHT');
+```
+
+## Set Initial Layout
+
+Configure the initial layout when creating call settings:
+
+```tsx
+import { CometChatCalls } from '@cometchat/calls-sdk-react-native';
+
+const callSettings = new CometChatCalls.CallSettingsBuilder()
+ .setMode(CometChatCalls.CALL_MODE.DEFAULT) // or SPOTLIGHT
+ .build();
+```
+
+| Mode | Description |
+|------|-------------|
+| `CometChatCalls.CALL_MODE.DEFAULT` | Starts with Tile layout |
+| `CometChatCalls.CALL_MODE.SPOTLIGHT` | Starts with Spotlight layout |
+
+## Change Layout During Call
+
+Change the layout programmatically at any time during the call:
+
+```tsx
+// Switch to Tile layout
+CometChatCalls.setLayout('TILE');
+
+// Switch to Sidebar layout
+CometChatCalls.setLayout('SIDEBAR');
+
+// Switch to Spotlight layout
+CometChatCalls.setLayout('SPOTLIGHT');
+```
+
+## Listen for Layout Changes
+
+Subscribe to layout change events:
+
+```tsx
+const unsubscribe = CometChatCalls.addEventListener('onCallLayoutChanged', (layout) => {
+ console.log('Layout changed to:', layout);
+ // Update UI state if needed
+});
+
+// Cleanup
+unsubscribe();
+```
+
+## Spotlight Mode Options
+
+When using Spotlight layout, you can configure video tile interactions:
+
+```tsx
+const callSettings = new CometChatCalls.CallSettingsBuilder()
+ .setMode(CometChatCalls.CALL_MODE.SPOTLIGHT)
+ .enableVideoTileClick(true) // Allow clicking tiles to spotlight
+ .enableVideoTileDrag(true) // Allow dragging tiles
+ .build();
+```
+
+| Option | Default | Description |
+|--------|---------|-------------|
+| `enableVideoTileClick` | `true` | Click a participant tile to spotlight them |
+| `enableVideoTileDrag` | `true` | Drag tiles to reposition in Spotlight mode |
+
+## Pin Participant
+
+Pin a specific participant to the main view in Sidebar or Spotlight layouts:
+
+```tsx
+// Pin a participant
+const participantId = 'participant_pid';
+CometChatCalls.pinParticipant(participantId, 'human');
+
+// Pin a screen share
+CometChatCalls.pinParticipant(participantId, 'screen-share');
+
+// Unpin
+CometChatCalls.unpinParticipant();
+```
+
+## Automatic Layout Changes
+
+The SDK automatically switches to Sidebar layout when:
+- A participant starts screen sharing
+- A participant is pinned
+
+When screen sharing stops or the participant is unpinned, the layout returns to the previous state if it was programmatically set.
+
+## Complete Example
+
+```tsx
+import React, { useState, useEffect } from 'react';
+import { View, TouchableOpacity, Text, StyleSheet } from 'react-native';
+import { CometChatCalls } from '@cometchat/calls-sdk-react-native';
+
+type Layout = 'TILE' | 'SIDEBAR' | 'SPOTLIGHT';
+
+function LayoutControls() {
+ const [currentLayout, setCurrentLayout] = useState('TILE');
+
+ useEffect(() => {
+ const unsubscribe = CometChatCalls.addEventListener(
+ 'onCallLayoutChanged',
+ (layout: Layout) => {
+ setCurrentLayout(layout);
+ }
+ );
+
+ return () => unsubscribe();
+ }, []);
+
+ const layouts: Layout[] = ['TILE', 'SIDEBAR', 'SPOTLIGHT'];
+
+ return (
+
+ Layout: {currentLayout}
+
+ {layouts.map((layout) => (
+ CometChatCalls.setLayout(layout)}
+ >
+
+ {layout}
+
+
+ ))}
+
+
+ );
+}
+
+const styles = StyleSheet.create({
+ container: {
+ padding: 16,
+ },
+ label: {
+ color: '#fff',
+ fontSize: 14,
+ marginBottom: 8,
+ },
+ buttons: {
+ flexDirection: 'row',
+ gap: 8,
+ },
+ button: {
+ backgroundColor: '#333',
+ paddingHorizontal: 16,
+ paddingVertical: 8,
+ borderRadius: 8,
+ },
+ activeButton: {
+ backgroundColor: '#6851D6',
+ },
+ buttonText: {
+ color: '#999',
+ fontSize: 12,
+ fontWeight: '600',
+ },
+ activeButtonText: {
+ color: '#fff',
+ },
+});
+
+export default LayoutControls;
+```
+
+## Related Documentation
+
+- [Actions](/calls/react-native/actions) - Control the call programmatically
+- [Participant Management](/calls/react-native/participant-management) - Pin and manage participants
+- [Screen Sharing](/calls/react-native/screen-sharing) - Share your screen
diff --git a/calls/react-native/call-logs.mdx b/calls/react-native/call-logs.mdx
new file mode 100644
index 00000000..3693f70e
--- /dev/null
+++ b/calls/react-native/call-logs.mdx
@@ -0,0 +1,302 @@
+---
+title: "Call Logs"
+sidebarTitle: "Call Logs"
+---
+
+Call logs provide a history of calls made through your application. You can retrieve call logs using the CometChat Chat SDK's call log APIs.
+
+
+Call logs are managed by the CometChat Chat SDK, not the Calls SDK. Ensure you have the Chat SDK integrated to access call history.
+
+
+## Prerequisites
+
+To use call logs, you need:
+1. CometChat Chat SDK integrated (`@cometchat/chat-sdk-react-native`)
+2. User authenticated with the Chat SDK
+
+## Retrieve Call Logs
+
+Use the `CallLogRequestBuilder` from the Chat SDK to fetch call logs:
+
+```tsx
+import { CometChat } from '@cometchat/chat-sdk-react-native';
+
+async function fetchCallLogs() {
+ const callLogRequest = new CometChat.CallLogRequestBuilder()
+ .setLimit(30)
+ .setCallStatus('all') // 'all', 'initiated', 'ongoing', 'ended', 'cancelled', 'rejected', 'busy', 'unanswered'
+ .build();
+
+ try {
+ const callLogs = await callLogRequest.fetchNext();
+ console.log('Call logs:', callLogs);
+ return callLogs;
+ } catch (error) {
+ console.error('Error fetching call logs:', error);
+ throw error;
+ }
+}
+```
+
+## CallLogRequestBuilder Options
+
+| Method | Type | Description |
+|--------|------|-------------|
+| `setLimit(limit)` | number | Maximum number of logs to fetch (default: 30, max: 100) |
+| `setCallStatus(status)` | string | Filter by call status |
+| `setCallType(type)` | string | Filter by call type (`audio` or `video`) |
+| `setCallCategory(category)` | string | Filter by category (`call` for direct calls) |
+| `setAuthToken(token)` | string | Auth token for the request |
+
+### Call Status Values
+
+| Status | Description |
+|--------|-------------|
+| `all` | All call logs |
+| `initiated` | Calls that were initiated |
+| `ongoing` | Currently active calls |
+| `ended` | Completed calls |
+| `cancelled` | Calls cancelled by the initiator |
+| `rejected` | Calls rejected by the receiver |
+| `busy` | Calls where receiver was busy |
+| `unanswered` | Calls that weren't answered |
+
+## Call Log Object
+
+Each call log contains:
+
+| Property | Type | Description |
+|----------|------|-------------|
+| `sessionId` | string | Unique session identifier |
+| `initiator` | User | User who initiated the call |
+| `receiver` | User/Group | Call recipient |
+| `callStatus` | string | Final status of the call |
+| `callType` | string | `audio` or `video` |
+| `initiatedAt` | number | Timestamp when call was initiated |
+| `joinedAt` | number | Timestamp when call was joined |
+| `endedAt` | number | Timestamp when call ended |
+| `duration` | number | Call duration in seconds |
+| `participants` | array | List of participants |
+| `recordings` | array | List of recordings (if any) |
+
+## Filter by User
+
+Get call logs for a specific user:
+
+```tsx
+const callLogRequest = new CometChat.CallLogRequestBuilder()
+ .setLimit(30)
+ .setUid('user_uid')
+ .build();
+```
+
+## Filter by Group
+
+Get call logs for a specific group:
+
+```tsx
+const callLogRequest = new CometChat.CallLogRequestBuilder()
+ .setLimit(30)
+ .setGuid('group_guid')
+ .build();
+```
+
+## Pagination
+
+Fetch call logs in pages:
+
+```tsx
+import { CometChat } from '@cometchat/chat-sdk-react-native';
+
+class CallLogManager {
+ private callLogRequest: CometChat.CallLogRequest;
+
+ constructor() {
+ this.callLogRequest = new CometChat.CallLogRequestBuilder()
+ .setLimit(30)
+ .build();
+ }
+
+ async fetchNextPage() {
+ try {
+ const callLogs = await this.callLogRequest.fetchNext();
+ return callLogs;
+ } catch (error) {
+ console.error('Error fetching call logs:', error);
+ throw error;
+ }
+ }
+}
+
+// Usage
+const manager = new CallLogManager();
+
+// First page
+const page1 = await manager.fetchNextPage();
+
+// Next page
+const page2 = await manager.fetchNextPage();
+```
+
+## Complete Example
+
+```tsx
+import React, { useState, useEffect, useCallback } from 'react';
+import { View, FlatList, Text, StyleSheet, ActivityIndicator } from 'react-native';
+import { CometChat } from '@cometchat/chat-sdk-react-native';
+
+interface CallLog {
+ sessionId: string;
+ initiator: { uid: string; name: string };
+ receiver: { uid: string; name: string };
+ callStatus: string;
+ callType: string;
+ initiatedAt: number;
+ duration: number;
+}
+
+function CallLogsScreen() {
+ const [callLogs, setCallLogs] = useState([]);
+ const [loading, setLoading] = useState(true);
+ const [loadingMore, setLoadingMore] = useState(false);
+ const [callLogRequest, setCallLogRequest] = useState(null);
+
+ useEffect(() => {
+ const request = new CometChat.CallLogRequestBuilder()
+ .setLimit(30)
+ .build();
+ setCallLogRequest(request);
+ fetchCallLogs(request);
+ }, []);
+
+ const fetchCallLogs = async (request: CometChat.CallLogRequest) => {
+ try {
+ const logs = await request.fetchNext();
+ setCallLogs(logs);
+ } catch (error) {
+ console.error('Error fetching call logs:', error);
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ const loadMore = useCallback(async () => {
+ if (!callLogRequest || loadingMore) return;
+
+ setLoadingMore(true);
+ try {
+ const moreLogs = await callLogRequest.fetchNext();
+ setCallLogs((prev) => [...prev, ...moreLogs]);
+ } catch (error) {
+ console.error('Error loading more:', error);
+ } finally {
+ setLoadingMore(false);
+ }
+ }, [callLogRequest, loadingMore]);
+
+ const formatDuration = (seconds: number) => {
+ const mins = Math.floor(seconds / 60);
+ const secs = seconds % 60;
+ return `${mins}:${secs.toString().padStart(2, '0')}`;
+ };
+
+ const formatDate = (timestamp: number) => {
+ return new Date(timestamp * 1000).toLocaleString();
+ };
+
+ const renderCallLog = ({ item }: { item: CallLog }) => (
+
+
+
+ {item.initiator.name} → {item.receiver.name}
+
+
+ {item.callType} • {item.callStatus}
+
+ {formatDate(item.initiatedAt)}
+
+
+ {item.duration > 0 ? formatDuration(item.duration) : '-'}
+
+
+ );
+
+ if (loading) {
+ return (
+
+
+
+ );
+ }
+
+ return (
+ item.sessionId}
+ renderItem={renderCallLog}
+ onEndReached={loadMore}
+ onEndReachedThreshold={0.5}
+ ListFooterComponent={
+ loadingMore ? : null
+ }
+ ListEmptyComponent={
+
+ No call logs
+
+ }
+ />
+ );
+}
+
+const styles = StyleSheet.create({
+ centered: {
+ flex: 1,
+ justifyContent: 'center',
+ alignItems: 'center',
+ },
+ callLogItem: {
+ flexDirection: 'row',
+ justifyContent: 'space-between',
+ alignItems: 'center',
+ padding: 16,
+ borderBottomWidth: 1,
+ borderBottomColor: '#eee',
+ },
+ callInfo: {
+ flex: 1,
+ },
+ participantName: {
+ fontSize: 16,
+ fontWeight: '600',
+ },
+ callDetails: {
+ fontSize: 14,
+ color: '#666',
+ marginTop: 4,
+ },
+ callTime: {
+ fontSize: 12,
+ color: '#999',
+ marginTop: 4,
+ },
+ duration: {
+ fontSize: 14,
+ color: '#333',
+ },
+ footer: {
+ padding: 16,
+ },
+ emptyText: {
+ fontSize: 16,
+ color: '#999',
+ },
+});
+
+export default CallLogsScreen;
+```
+
+## Related Documentation
+
+- [Recording](/calls/react-native/recording) - Access call recordings
+- [Ringing](/calls/react-native/ringing) - Implement call notifications
diff --git a/calls/react-native/custom-control-panel.mdx b/calls/react-native/custom-control-panel.mdx
new file mode 100644
index 00000000..578b0df8
--- /dev/null
+++ b/calls/react-native/custom-control-panel.mdx
@@ -0,0 +1,323 @@
+---
+title: "Custom Control Panel"
+sidebarTitle: "Custom Control Panel"
+---
+
+Build a custom control panel to replace or extend the default call controls. Hide the default layout and implement your own UI using the SDK's action methods.
+
+## Hide Default Layout
+
+Disable the default control panel to use your own:
+
+```tsx
+import { CometChatCalls } from '@cometchat/calls-sdk-react-native';
+
+const callSettings = new CometChatCalls.CallSettingsBuilder()
+ .enableDefaultLayout(false)
+ .build();
+```
+
+## Available Actions
+
+Use these methods to control the call from your custom UI:
+
+| Action | Method | Description |
+|--------|--------|-------------|
+| Mute Audio | `CometChatCalls.muteAudio()` | Mute local microphone |
+| Unmute Audio | `CometChatCalls.unMuteAudio()` | Unmute local microphone |
+| Pause Video | `CometChatCalls.pauseVideo()` | Stop sending video |
+| Resume Video | `CometChatCalls.resumeVideo()` | Resume sending video |
+| Switch Camera | `CometChatCalls.switchCamera()` | Toggle front/rear camera |
+| Leave Session | `CometChatCalls.leaveSession()` | End the call |
+| Raise Hand | `CometChatCalls.raiseHand()` | Raise hand |
+| Lower Hand | `CometChatCalls.lowerHand()` | Lower hand |
+| Set Layout | `CometChatCalls.setLayout(layout)` | Change call layout |
+| Start Recording | `CometChatCalls.startRecording()` | Start recording |
+| Stop Recording | `CometChatCalls.stopRecording()` | Stop recording |
+| Start Screen Share | `CometChatCalls.startScreenSharing()` | Share screen |
+| Stop Screen Share | `CometChatCalls.stopScreenSharing()` | Stop sharing |
+| Enable PiP | `CometChatCalls.enablePictureInPictureLayout()` | Enter PiP mode |
+| Disable PiP | `CometChatCalls.disablePictureInPictureLayout()` | Exit PiP mode |
+
+## Complete Example
+
+```tsx
+import React, { useState, useEffect } from 'react';
+import { View, TouchableOpacity, Text, StyleSheet, SafeAreaView } from 'react-native';
+import { CometChatCalls } from '@cometchat/calls-sdk-react-native';
+
+interface CustomControlPanelProps {
+ isAudioOnly?: boolean;
+}
+
+function CustomControlPanel({ isAudioOnly = false }: CustomControlPanelProps) {
+ const [isAudioMuted, setIsAudioMuted] = useState(false);
+ const [isVideoMuted, setIsVideoMuted] = useState(false);
+ const [isHandRaised, setIsHandRaised] = useState(false);
+ const [isRecording, setIsRecording] = useState(false);
+ const [currentLayout, setCurrentLayout] = useState<'TILE' | 'SIDEBAR' | 'SPOTLIGHT'>('TILE');
+
+ useEffect(() => {
+ // Listen for media events
+ const unsubscribeAudioMuted = CometChatCalls.addEventListener(
+ 'onAudioMuted',
+ () => setIsAudioMuted(true)
+ );
+ const unsubscribeAudioUnmuted = CometChatCalls.addEventListener(
+ 'onAudioUnMuted',
+ () => setIsAudioMuted(false)
+ );
+ const unsubscribeVideoPaused = CometChatCalls.addEventListener(
+ 'onVideoPaused',
+ () => setIsVideoMuted(true)
+ );
+ const unsubscribeVideoResumed = CometChatCalls.addEventListener(
+ 'onVideoResumed',
+ () => setIsVideoMuted(false)
+ );
+ const unsubscribeRecordingStarted = CometChatCalls.addEventListener(
+ 'onRecordingStarted',
+ () => setIsRecording(true)
+ );
+ const unsubscribeRecordingStopped = CometChatCalls.addEventListener(
+ 'onRecordingStopped',
+ () => setIsRecording(false)
+ );
+ const unsubscribeLayoutChanged = CometChatCalls.addEventListener(
+ 'onCallLayoutChanged',
+ (layout: 'TILE' | 'SIDEBAR' | 'SPOTLIGHT') => setCurrentLayout(layout)
+ );
+
+ return () => {
+ unsubscribeAudioMuted();
+ unsubscribeAudioUnmuted();
+ unsubscribeVideoPaused();
+ unsubscribeVideoResumed();
+ unsubscribeRecordingStarted();
+ unsubscribeRecordingStopped();
+ unsubscribeLayoutChanged();
+ };
+ }, []);
+
+ const toggleAudio = () => {
+ if (isAudioMuted) {
+ CometChatCalls.unMuteAudio();
+ } else {
+ CometChatCalls.muteAudio();
+ }
+ };
+
+ const toggleVideo = () => {
+ if (isVideoMuted) {
+ CometChatCalls.resumeVideo();
+ } else {
+ CometChatCalls.pauseVideo();
+ }
+ };
+
+ const toggleHand = () => {
+ if (isHandRaised) {
+ CometChatCalls.lowerHand();
+ setIsHandRaised(false);
+ } else {
+ CometChatCalls.raiseHand();
+ setIsHandRaised(true);
+ }
+ };
+
+ const toggleRecording = () => {
+ if (isRecording) {
+ CometChatCalls.stopRecording();
+ } else {
+ CometChatCalls.startRecording();
+ }
+ };
+
+ const cycleLayout = () => {
+ const layouts: Array<'TILE' | 'SIDEBAR' | 'SPOTLIGHT'> = ['TILE', 'SIDEBAR', 'SPOTLIGHT'];
+ const currentIndex = layouts.indexOf(currentLayout);
+ const nextLayout = layouts[(currentIndex + 1) % layouts.length];
+ CometChatCalls.setLayout(nextLayout);
+ };
+
+ const handleEndCall = () => {
+ CometChatCalls.leaveSession();
+ };
+
+ return (
+
+
+ {/* Audio Toggle */}
+
+ {isAudioMuted ? '🔇' : '🎤'}
+
+ {isAudioMuted ? 'Unmute' : 'Mute'}
+
+
+
+ {/* Video Toggle (only for video calls) */}
+ {!isAudioOnly && (
+
+ {isVideoMuted ? '📷' : '🎥'}
+
+ {isVideoMuted ? 'Show' : 'Hide'}
+
+
+ )}
+
+ {/* Switch Camera (only for video calls) */}
+ {!isAudioOnly && (
+ CometChatCalls.switchCamera()}
+ >
+ 🔄
+ Flip
+
+ )}
+
+ {/* Raise Hand */}
+
+ {isHandRaised ? '✋' : '🤚'}
+
+ {isHandRaised ? 'Lower' : 'Raise'}
+
+
+
+
+
+ {/* Layout Toggle */}
+
+ 📐
+ {currentLayout}
+
+
+ {/* Recording */}
+
+ {isRecording ? '⏹️' : '⏺️'}
+
+ {isRecording ? 'Stop' : 'Record'}
+
+
+
+ {/* End Call */}
+
+ 📞
+ End
+
+
+
+ );
+}
+
+const styles = StyleSheet.create({
+ container: {
+ position: 'absolute',
+ bottom: 0,
+ left: 0,
+ right: 0,
+ backgroundColor: 'rgba(0, 0, 0, 0.8)',
+ paddingVertical: 16,
+ paddingHorizontal: 8,
+ },
+ controlRow: {
+ flexDirection: 'row',
+ justifyContent: 'center',
+ marginBottom: 12,
+ gap: 12,
+ },
+ controlButton: {
+ alignItems: 'center',
+ justifyContent: 'center',
+ backgroundColor: '#333',
+ width: 64,
+ height: 64,
+ borderRadius: 32,
+ },
+ activeButton: {
+ backgroundColor: '#6851D6',
+ },
+ handRaisedButton: {
+ backgroundColor: '#f59e0b',
+ },
+ recordingButton: {
+ backgroundColor: '#ef4444',
+ },
+ endCallButton: {
+ backgroundColor: '#dc2626',
+ },
+ buttonIcon: {
+ fontSize: 24,
+ },
+ buttonLabel: {
+ color: '#fff',
+ fontSize: 10,
+ marginTop: 4,
+ },
+});
+
+export default CustomControlPanel;
+```
+
+## Using with Call Component
+
+Combine the custom control panel with the call component:
+
+```tsx
+import React from 'react';
+import { View, StyleSheet } from 'react-native';
+import { CometChatCalls } from '@cometchat/calls-sdk-react-native';
+import CustomControlPanel from './CustomControlPanel';
+
+interface CallScreenProps {
+ callToken: string;
+ isAudioOnly?: boolean;
+}
+
+function CallScreen({ callToken, isAudioOnly = false }: CallScreenProps) {
+ const callSettings = new CometChatCalls.CallSettingsBuilder()
+ .enableDefaultLayout(false) // Hide default controls
+ .setIsAudioOnlyCall(isAudioOnly)
+ .build();
+
+ return (
+
+
+
+
+ );
+}
+
+const styles = StyleSheet.create({
+ container: {
+ flex: 1,
+ backgroundColor: '#000',
+ },
+});
+
+export default CallScreen;
+```
+
+## Related Documentation
+
+- [Actions](/calls/react-native/actions) - All available call actions
+- [Events](/calls/react-native/events) - Listen for call events
+- [Session Settings](/calls/react-native/session-settings) - Configure call settings
diff --git a/calls/react-native/custom-participant-list.mdx b/calls/react-native/custom-participant-list.mdx
new file mode 100644
index 00000000..28c46d27
--- /dev/null
+++ b/calls/react-native/custom-participant-list.mdx
@@ -0,0 +1,513 @@
+---
+title: "Custom Participant List"
+sidebarTitle: "Custom Participant List"
+---
+
+Build a custom participant list UI to display and manage call participants. Listen for participant events and use the SDK's management methods.
+
+## Get Participants
+
+Listen for participant list changes:
+
+```tsx
+import { CometChatCalls } from '@cometchat/calls-sdk-react-native';
+
+CometChatCalls.addEventListener('onParticipantListChanged', (participants) => {
+ console.log('Participants:', participants);
+});
+```
+
+## Participant Object
+
+Each participant contains:
+
+| Property | Type | Description |
+|----------|------|-------------|
+| `pid` | string | Participant ID (unique per session) |
+| `uid` | string | User ID |
+| `name` | string | Display name |
+| `avatar` | string | Avatar URL (optional) |
+
+## Participant Events
+
+Subscribe to individual participant events:
+
+```tsx
+// Join/Leave
+CometChatCalls.addEventListener('onParticipantJoined', (participant) => {
+ console.log(`${participant.name} joined`);
+});
+
+CometChatCalls.addEventListener('onParticipantLeft', (participant) => {
+ console.log(`${participant.name} left`);
+});
+
+// Audio state
+CometChatCalls.addEventListener('onParticipantAudioMuted', (participant) => {
+ console.log(`${participant.name} muted`);
+});
+
+CometChatCalls.addEventListener('onParticipantAudioUnmuted', (participant) => {
+ console.log(`${participant.name} unmuted`);
+});
+
+// Video state
+CometChatCalls.addEventListener('onParticipantVideoPaused', (participant) => {
+ console.log(`${participant.name} video paused`);
+});
+
+CometChatCalls.addEventListener('onParticipantVideoResumed', (participant) => {
+ console.log(`${participant.name} video resumed`);
+});
+
+// Hand raised
+CometChatCalls.addEventListener('onParticipantHandRaised', (participant) => {
+ console.log(`${participant.name} raised hand`);
+});
+
+CometChatCalls.addEventListener('onParticipantHandLowered', (participant) => {
+ console.log(`${participant.name} lowered hand`);
+});
+
+// Screen sharing
+CometChatCalls.addEventListener('onParticipantStartedScreenShare', (participant) => {
+ console.log(`${participant.name} started screen share`);
+});
+
+CometChatCalls.addEventListener('onParticipantStoppedScreenShare', (participant) => {
+ console.log(`${participant.name} stopped screen share`);
+});
+
+// Dominant speaker
+CometChatCalls.addEventListener('onDominantSpeakerChanged', (participant) => {
+ console.log(`Dominant speaker: ${participant.name}`);
+});
+```
+
+## Participant Actions
+
+Manage participants using these methods:
+
+```tsx
+// Pin participant
+CometChatCalls.pinParticipant(participantId, 'human');
+
+// Unpin
+CometChatCalls.unpinParticipant();
+
+// Mute participant (requires moderator)
+CometChatCalls.muteParticipant(participantId);
+
+// Pause participant video (requires moderator)
+CometChatCalls.pauseParticipantVideo(participantId);
+```
+
+## Complete Example
+
+```tsx
+import React, { useState, useEffect } from 'react';
+import {
+ View,
+ FlatList,
+ Text,
+ TouchableOpacity,
+ StyleSheet,
+ Image,
+ Modal,
+} from 'react-native';
+import { CometChatCalls } from '@cometchat/calls-sdk-react-native';
+
+interface Participant {
+ pid: string;
+ uid: string;
+ name: string;
+ avatar?: string;
+}
+
+interface ParticipantState extends Participant {
+ isAudioMuted: boolean;
+ isVideoMuted: boolean;
+ isHandRaised: boolean;
+ isScreenSharing: boolean;
+ isDominantSpeaker: boolean;
+}
+
+function CustomParticipantList() {
+ const [participants, setParticipants] = useState>(
+ new Map()
+ );
+ const [pinnedId, setPinnedId] = useState(null);
+ const [isVisible, setIsVisible] = useState(false);
+ const [dominantSpeakerId, setDominantSpeakerId] = useState(null);
+
+ useEffect(() => {
+ const unsubscribers: Array<() => void> = [];
+
+ // Participant list changes
+ unsubscribers.push(
+ CometChatCalls.addEventListener(
+ 'onParticipantListChanged',
+ (list: Participant[]) => {
+ setParticipants((prev) => {
+ const newMap = new Map(prev);
+ // Add new participants
+ list.forEach((p) => {
+ if (!newMap.has(p.pid)) {
+ newMap.set(p.pid, {
+ ...p,
+ isAudioMuted: false,
+ isVideoMuted: false,
+ isHandRaised: false,
+ isScreenSharing: false,
+ isDominantSpeaker: false,
+ });
+ }
+ });
+ // Remove participants who left
+ const currentPids = new Set(list.map((p) => p.pid));
+ newMap.forEach((_, pid) => {
+ if (!currentPids.has(pid)) {
+ newMap.delete(pid);
+ }
+ });
+ return newMap;
+ });
+ }
+ )
+ );
+
+ // Audio state
+ unsubscribers.push(
+ CometChatCalls.addEventListener(
+ 'onParticipantAudioMuted',
+ (p: Participant) => {
+ updateParticipant(p.pid, { isAudioMuted: true });
+ }
+ )
+ );
+
+ unsubscribers.push(
+ CometChatCalls.addEventListener(
+ 'onParticipantAudioUnmuted',
+ (p: Participant) => {
+ updateParticipant(p.pid, { isAudioMuted: false });
+ }
+ )
+ );
+
+ // Video state
+ unsubscribers.push(
+ CometChatCalls.addEventListener(
+ 'onParticipantVideoPaused',
+ (p: Participant) => {
+ updateParticipant(p.pid, { isVideoMuted: true });
+ }
+ )
+ );
+
+ unsubscribers.push(
+ CometChatCalls.addEventListener(
+ 'onParticipantVideoResumed',
+ (p: Participant) => {
+ updateParticipant(p.pid, { isVideoMuted: false });
+ }
+ )
+ );
+
+ // Hand raised
+ unsubscribers.push(
+ CometChatCalls.addEventListener(
+ 'onParticipantHandRaised',
+ (p: Participant) => {
+ updateParticipant(p.pid, { isHandRaised: true });
+ }
+ )
+ );
+
+ unsubscribers.push(
+ CometChatCalls.addEventListener(
+ 'onParticipantHandLowered',
+ (p: Participant) => {
+ updateParticipant(p.pid, { isHandRaised: false });
+ }
+ )
+ );
+
+ // Screen sharing
+ unsubscribers.push(
+ CometChatCalls.addEventListener(
+ 'onParticipantStartedScreenShare',
+ (p: Participant) => {
+ updateParticipant(p.pid, { isScreenSharing: true });
+ }
+ )
+ );
+
+ unsubscribers.push(
+ CometChatCalls.addEventListener(
+ 'onParticipantStoppedScreenShare',
+ (p: Participant) => {
+ updateParticipant(p.pid, { isScreenSharing: false });
+ }
+ )
+ );
+
+ // Dominant speaker
+ unsubscribers.push(
+ CometChatCalls.addEventListener(
+ 'onDominantSpeakerChanged',
+ (p: Participant) => {
+ setDominantSpeakerId(p.pid);
+ }
+ )
+ );
+
+ return () => {
+ unsubscribers.forEach((unsub) => unsub());
+ };
+ }, []);
+
+ const updateParticipant = (pid: string, updates: Partial) => {
+ setParticipants((prev) => {
+ const newMap = new Map(prev);
+ const participant = newMap.get(pid);
+ if (participant) {
+ newMap.set(pid, { ...participant, ...updates });
+ }
+ return newMap;
+ });
+ };
+
+ const handlePin = (participant: ParticipantState) => {
+ if (pinnedId === participant.pid) {
+ CometChatCalls.unpinParticipant();
+ setPinnedId(null);
+ } else {
+ CometChatCalls.pinParticipant(participant.pid, 'human');
+ setPinnedId(participant.pid);
+ }
+ };
+
+ const handleMute = (participant: ParticipantState) => {
+ CometChatCalls.muteParticipant(participant.pid);
+ };
+
+ const renderParticipant = ({ item }: { item: ParticipantState }) => (
+
+
+ {item.avatar ? (
+
+ ) : (
+
+
+ {item.name.charAt(0).toUpperCase()}
+
+
+ )}
+ {dominantSpeakerId === item.pid && (
+
+ )}
+
+
+
+ {item.name}
+
+ {item.isAudioMuted && 🔇 }
+ {item.isVideoMuted && 📷 }
+ {item.isHandRaised && ✋ }
+ {item.isScreenSharing && 🖥️ }
+ {pinnedId === item.pid && 📌 }
+
+
+
+
+ handlePin(item)}
+ >
+
+ {pinnedId === item.pid ? 'Unpin' : 'Pin'}
+
+
+ handleMute(item)}
+ >
+ Mute
+
+
+
+ );
+
+ const participantList = Array.from(participants.values());
+
+ return (
+ <>
+ setIsVisible(true)}
+ >
+
+ 👥 {participantList.length}
+
+
+
+ setIsVisible(false)}
+ >
+
+
+
+
+ Participants ({participantList.length})
+
+ setIsVisible(false)}>
+ ✕
+
+
+ item.pid}
+ renderItem={renderParticipant}
+ ListEmptyComponent={
+ No participants
+ }
+ />
+
+
+
+ >
+ );
+}
+
+const styles = StyleSheet.create({
+ toggleButton: {
+ position: 'absolute',
+ top: 60,
+ right: 16,
+ backgroundColor: 'rgba(0, 0, 0, 0.6)',
+ paddingHorizontal: 16,
+ paddingVertical: 8,
+ borderRadius: 20,
+ },
+ toggleButtonText: {
+ color: '#fff',
+ fontSize: 16,
+ },
+ modalOverlay: {
+ flex: 1,
+ backgroundColor: 'rgba(0, 0, 0, 0.5)',
+ justifyContent: 'flex-end',
+ },
+ modalContent: {
+ backgroundColor: '#1a1a1a',
+ borderTopLeftRadius: 20,
+ borderTopRightRadius: 20,
+ maxHeight: '70%',
+ },
+ modalHeader: {
+ flexDirection: 'row',
+ justifyContent: 'space-between',
+ alignItems: 'center',
+ padding: 16,
+ borderBottomWidth: 1,
+ borderBottomColor: '#333',
+ },
+ modalTitle: {
+ color: '#fff',
+ fontSize: 18,
+ fontWeight: '600',
+ },
+ closeButton: {
+ color: '#fff',
+ fontSize: 20,
+ padding: 4,
+ },
+ participantItem: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ padding: 12,
+ borderBottomWidth: 1,
+ borderBottomColor: '#333',
+ },
+ avatarContainer: {
+ position: 'relative',
+ },
+ avatar: {
+ width: 44,
+ height: 44,
+ borderRadius: 22,
+ },
+ avatarPlaceholder: {
+ width: 44,
+ height: 44,
+ borderRadius: 22,
+ backgroundColor: '#6851D6',
+ justifyContent: 'center',
+ alignItems: 'center',
+ },
+ avatarText: {
+ color: '#fff',
+ fontSize: 18,
+ fontWeight: '600',
+ },
+ speakingIndicator: {
+ position: 'absolute',
+ bottom: 0,
+ right: 0,
+ width: 14,
+ height: 14,
+ borderRadius: 7,
+ backgroundColor: '#22c55e',
+ borderWidth: 2,
+ borderColor: '#1a1a1a',
+ },
+ participantInfo: {
+ flex: 1,
+ marginLeft: 12,
+ },
+ participantName: {
+ color: '#fff',
+ fontSize: 16,
+ fontWeight: '500',
+ },
+ statusIcons: {
+ flexDirection: 'row',
+ marginTop: 4,
+ gap: 4,
+ },
+ statusIcon: {
+ fontSize: 14,
+ },
+ actions: {
+ flexDirection: 'row',
+ gap: 8,
+ },
+ actionButton: {
+ backgroundColor: '#333',
+ paddingHorizontal: 12,
+ paddingVertical: 6,
+ borderRadius: 4,
+ },
+ actionText: {
+ color: '#fff',
+ fontSize: 12,
+ },
+ emptyText: {
+ color: '#666',
+ textAlign: 'center',
+ padding: 20,
+ },
+});
+
+export default CustomParticipantList;
+```
+
+## Related Documentation
+
+- [Participant Management](/calls/react-native/participant-management) - Manage participants
+- [Events](/calls/react-native/events) - All participant events
+- [Custom Control Panel](/calls/react-native/custom-control-panel) - Build custom controls
diff --git a/calls/react-native/events.mdx b/calls/react-native/events.mdx
new file mode 100644
index 00000000..c219d4db
--- /dev/null
+++ b/calls/react-native/events.mdx
@@ -0,0 +1,336 @@
+---
+title: "Events"
+sidebarTitle: "Events"
+---
+
+The CometChat Calls SDK provides two ways to listen for call events: the `OngoingCallListener` class for legacy-style callbacks, and the `addEventListener` method for modern event subscriptions.
+
+## OngoingCallListener
+
+The `OngoingCallListener` class provides callbacks for call events. Pass it to `CallSettingsBuilder.setCallEventListener()`:
+
+```tsx
+import { CometChatCalls } from '@cometchat/calls-sdk-react-native';
+
+const callListener = new CometChatCalls.OngoingCallListener({
+ onUserJoined: (user) => {
+ console.log('User joined:', user);
+ },
+ onUserLeft: (user) => {
+ console.log('User left:', user);
+ },
+ onUserListUpdated: (userList) => {
+ console.log('User list updated:', userList);
+ },
+ onCallEnded: () => {
+ console.log('Call ended');
+ },
+ onCallEndButtonPressed: () => {
+ console.log('End call button pressed');
+ },
+ onSessionTimeout: () => {
+ console.log('Session timed out');
+ },
+ onError: (error) => {
+ console.error('Call error:', error);
+ },
+ onAudioModesUpdated: (audioModes) => {
+ console.log('Audio modes updated:', audioModes);
+ },
+ onRecordingStarted: (data) => {
+ console.log('Recording started:', data);
+ },
+ onRecordingStopped: (data) => {
+ console.log('Recording stopped:', data);
+ },
+ onUserMuted: (user) => {
+ console.log('User muted:', user);
+ },
+ onCallSwitchedToVideo: (data) => {
+ console.log('Call switched to video:', data);
+ },
+});
+
+const callSettings = new CometChatCalls.CallSettingsBuilder()
+ .setCallEventListener(callListener)
+ .build();
+```
+
+### OngoingCallListener Events
+
+| Event | Payload | Description |
+|-------|---------|-------------|
+| `onUserJoined` | User object | A user joined the call |
+| `onUserLeft` | User object | A user left the call |
+| `onUserListUpdated` | User array | The participant list changed |
+| `onCallEnded` | - | The call has ended |
+| `onCallEndButtonPressed` | - | The end call button was pressed |
+| `onSessionTimeout` | - | The session timed out due to inactivity |
+| `onError` | Error object | An error occurred |
+| `onAudioModesUpdated` | Audio modes array | Available audio modes changed |
+| `onRecordingStarted` | Recording data | Recording has started |
+| `onRecordingStopped` | Recording data | Recording has stopped |
+| `onUserMuted` | User object | A user was muted |
+| `onCallSwitchedToVideo` | Session data | An audio call was switched to video |
+
+## addEventListener
+
+For more granular control, use `CometChatCalls.addEventListener()` to subscribe to specific events:
+
+```tsx
+const unsubscribe = CometChatCalls.addEventListener('onParticipantJoined', (participant) => {
+ console.log('Participant joined:', participant);
+});
+
+// Later, unsubscribe when no longer needed
+unsubscribe();
+```
+
+### Session Status Events
+
+| Event | Payload | Description |
+|-------|---------|-------------|
+| `onSessionJoined` | - | Successfully joined the session |
+| `onSessionLeft` | - | Left the session |
+| `onConnectionLost` | - | Connection to the session was lost |
+| `onConnectionRestored` | - | Connection was restored |
+| `onConnectionClosed` | - | Connection was closed |
+| `onSessionTimedOut` | - | Session timed out due to inactivity |
+
+```tsx
+CometChatCalls.addEventListener('onSessionJoined', () => {
+ console.log('Successfully joined the session');
+});
+
+CometChatCalls.addEventListener('onSessionLeft', () => {
+ console.log('Left the session');
+});
+
+CometChatCalls.addEventListener('onConnectionLost', () => {
+ console.log('Connection lost - attempting to reconnect');
+});
+
+CometChatCalls.addEventListener('onConnectionRestored', () => {
+ console.log('Connection restored');
+});
+
+CometChatCalls.addEventListener('onSessionTimedOut', () => {
+ console.log('Session timed out');
+});
+```
+
+### Media Events
+
+| Event | Payload | Description |
+|-------|---------|-------------|
+| `onAudioMuted` | - | Local audio was muted |
+| `onAudioUnMuted` | - | Local audio was unmuted |
+| `onVideoPaused` | - | Local video was paused |
+| `onVideoResumed` | - | Local video was resumed |
+| `onRecordingStarted` | - | Recording started |
+| `onRecordingStopped` | - | Recording stopped |
+| `onScreenShareStarted` | - | Screen sharing started |
+| `onScreenShareStopped` | - | Screen sharing stopped |
+| `onAudioModeChanged` | Audio mode | Audio output mode changed |
+| `onCameraFacingChanged` | Camera facing | Camera switched (front/rear) |
+
+```tsx
+CometChatCalls.addEventListener('onAudioMuted', () => {
+ console.log('Audio muted');
+});
+
+CometChatCalls.addEventListener('onAudioUnMuted', () => {
+ console.log('Audio unmuted');
+});
+
+CometChatCalls.addEventListener('onVideoPaused', () => {
+ console.log('Video paused');
+});
+
+CometChatCalls.addEventListener('onVideoResumed', () => {
+ console.log('Video resumed');
+});
+
+CometChatCalls.addEventListener('onAudioModeChanged', (mode) => {
+ console.log('Audio mode changed to:', mode);
+});
+
+CometChatCalls.addEventListener('onCameraFacingChanged', (facing) => {
+ console.log('Camera facing:', facing); // 'FRONT' or 'REAR'
+});
+```
+
+### Participant Events
+
+| Event | Payload | Description |
+|-------|---------|-------------|
+| `onParticipantJoined` | Participant | A participant joined |
+| `onParticipantLeft` | Participant | A participant left |
+| `onParticipantAudioMuted` | Participant | A participant muted their audio |
+| `onParticipantAudioUnmuted` | Participant | A participant unmuted their audio |
+| `onParticipantVideoPaused` | Participant | A participant paused their video |
+| `onParticipantVideoResumed` | Participant | A participant resumed their video |
+| `onParticipantHandRaised` | Participant | A participant raised their hand |
+| `onParticipantHandLowered` | Participant | A participant lowered their hand |
+| `onParticipantStartedScreenShare` | Participant | A participant started screen sharing |
+| `onParticipantStoppedScreenShare` | Participant | A participant stopped screen sharing |
+| `onParticipantStartedRecording` | Participant | A participant started recording |
+| `onParticipantStoppedRecording` | Participant | A participant stopped recording |
+| `onDominantSpeakerChanged` | Participant | The dominant speaker changed |
+| `onParticipantListChanged` | Participant array | The participant list changed |
+
+```tsx
+CometChatCalls.addEventListener('onParticipantJoined', (participant) => {
+ console.log('Participant joined:', participant.name);
+});
+
+CometChatCalls.addEventListener('onParticipantLeft', (participant) => {
+ console.log('Participant left:', participant.name);
+});
+
+CometChatCalls.addEventListener('onParticipantListChanged', (participants) => {
+ console.log('Total participants:', participants.length);
+});
+
+CometChatCalls.addEventListener('onParticipantHandRaised', (participant) => {
+ console.log('Hand raised by:', participant.name);
+});
+
+CometChatCalls.addEventListener('onDominantSpeakerChanged', (participant) => {
+ console.log('Dominant speaker:', participant.name);
+});
+```
+
+### Button Click Events
+
+| Event | Payload | Description |
+|-------|---------|-------------|
+| `onLeaveSessionButtonClicked` | - | Leave session button clicked |
+| `onRaiseHandButtonClicked` | - | Raise hand button clicked |
+| `onShareInviteButtonClicked` | - | Share invite button clicked |
+| `onChangeLayoutButtonClicked` | - | Change layout button clicked |
+| `onParticipantListButtonClicked` | - | Participant list button clicked |
+| `onToggleAudioButtonClicked` | - | Toggle audio button clicked |
+| `onToggleVideoButtonClicked` | - | Toggle video button clicked |
+| `onRecordingToggleButtonClicked` | - | Recording toggle button clicked |
+| `onScreenShareButtonClicked` | - | Screen share button clicked |
+| `onChatButtonClicked` | - | Chat button clicked |
+| `onSwitchCameraButtonClicked` | - | Switch camera button clicked |
+
+```tsx
+CometChatCalls.addEventListener('onLeaveSessionButtonClicked', () => {
+ console.log('Leave button clicked');
+ // Handle leave confirmation
+});
+
+CometChatCalls.addEventListener('onChatButtonClicked', () => {
+ console.log('Chat button clicked');
+ // Open chat interface
+});
+```
+
+### Layout Events
+
+| Event | Payload | Description |
+|-------|---------|-------------|
+| `onCallLayoutChanged` | Layout | The call layout changed |
+| `onParticipantListVisible` | - | Participant list became visible |
+| `onParticipantListHidden` | - | Participant list was hidden |
+| `onPictureInPictureLayoutEnabled` | - | PiP mode was enabled |
+| `onPictureInPictureLayoutDisabled` | - | PiP mode was disabled |
+
+```tsx
+CometChatCalls.addEventListener('onCallLayoutChanged', (layout) => {
+ console.log('Layout changed to:', layout); // 'TILE', 'SIDEBAR', or 'SPOTLIGHT'
+});
+
+CometChatCalls.addEventListener('onPictureInPictureLayoutEnabled', () => {
+ console.log('PiP enabled');
+});
+```
+
+## Participant Object
+
+The participant object contains:
+
+| Property | Type | Description |
+|----------|------|-------------|
+| `pid` | string | Participant ID |
+| `uid` | string | User ID |
+| `name` | string | Display name |
+| `avatar` | string | Avatar URL (optional) |
+
+## Complete Example
+
+```tsx
+import React, { useEffect, useRef } from 'react';
+import { CometChatCalls } from '@cometchat/calls-sdk-react-native';
+
+function useCallEvents(onCallEnd: () => void) {
+ const unsubscribersRef = useRef void>>([]);
+
+ useEffect(() => {
+ // Session events
+ unsubscribersRef.current.push(
+ CometChatCalls.addEventListener('onSessionJoined', () => {
+ console.log('Joined session');
+ })
+ );
+
+ unsubscribersRef.current.push(
+ CometChatCalls.addEventListener('onSessionLeft', () => {
+ console.log('Left session');
+ onCallEnd();
+ })
+ );
+
+ unsubscribersRef.current.push(
+ CometChatCalls.addEventListener('onSessionTimedOut', () => {
+ console.log('Session timed out');
+ onCallEnd();
+ })
+ );
+
+ // Participant events
+ unsubscribersRef.current.push(
+ CometChatCalls.addEventListener('onParticipantJoined', (participant) => {
+ console.log(`${participant.name} joined`);
+ })
+ );
+
+ unsubscribersRef.current.push(
+ CometChatCalls.addEventListener('onParticipantLeft', (participant) => {
+ console.log(`${participant.name} left`);
+ })
+ );
+
+ // Media events
+ unsubscribersRef.current.push(
+ CometChatCalls.addEventListener('onAudioModeChanged', (mode) => {
+ console.log('Audio mode:', mode);
+ })
+ );
+
+ // Layout events
+ unsubscribersRef.current.push(
+ CometChatCalls.addEventListener('onCallLayoutChanged', (layout) => {
+ console.log('Layout:', layout);
+ })
+ );
+
+ // Cleanup
+ return () => {
+ unsubscribersRef.current.forEach((unsubscribe) => unsubscribe());
+ unsubscribersRef.current = [];
+ };
+ }, [onCallEnd]);
+}
+
+export default useCallEvents;
+```
+
+## Related Documentation
+
+- [Actions](/calls/react-native/actions) - Control the call programmatically
+- [Session Settings](/calls/react-native/session-settings) - Configure event listeners
+- [Participant Management](/calls/react-native/participant-management) - Manage participants
diff --git a/calls/react-native/idle-timeout.mdx b/calls/react-native/idle-timeout.mdx
new file mode 100644
index 00000000..b8b24f83
--- /dev/null
+++ b/calls/react-native/idle-timeout.mdx
@@ -0,0 +1,152 @@
+---
+title: "Idle Timeout"
+sidebarTitle: "Idle Timeout"
+---
+
+The idle timeout feature automatically ends a call session when a participant is alone for a specified period. This prevents abandoned calls from running indefinitely.
+
+## Configure Idle Timeout
+
+Set the idle timeout period when creating call settings:
+
+```tsx
+import { CometChatCalls } from '@cometchat/calls-sdk-react-native';
+
+const callSettings = new CometChatCalls.CallSettingsBuilder()
+ .setIdleTimeoutPeriod(180) // 180 seconds (3 minutes)
+ .build();
+```
+
+| Parameter | Type | Default | Description |
+|-----------|------|---------|-------------|
+| `idleTimeoutPeriod` | number | `180` | Seconds before auto-ending when alone |
+
+## How It Works
+
+1. When all other participants leave the call, the idle timeout timer starts
+2. A prompt appears 60 seconds before the timeout, allowing the user to extend the session
+3. If the user doesn't respond, the session ends automatically
+4. If another participant joins, the timer is cancelled
+
+## Listen for Timeout Events
+
+### Session Timeout Event
+
+Listen for when the session times out:
+
+```tsx
+CometChatCalls.addEventListener('onSessionTimedOut', () => {
+ console.log('Session timed out due to inactivity');
+ // Navigate away from call screen
+});
+```
+
+### Using OngoingCallListener
+
+```tsx
+const callListener = new CometChatCalls.OngoingCallListener({
+ onSessionTimeout: () => {
+ console.log('Session timed out');
+ // Handle timeout
+ },
+ onCallEnded: () => {
+ console.log('Call ended');
+ },
+});
+
+const callSettings = new CometChatCalls.CallSettingsBuilder()
+ .setIdleTimeoutPeriod(180)
+ .setCallEventListener(callListener)
+ .build();
+```
+
+## Disable Idle Timeout
+
+Set a very high value to effectively disable the timeout:
+
+```tsx
+const callSettings = new CometChatCalls.CallSettingsBuilder()
+ .setIdleTimeoutPeriod(86400) // 24 hours
+ .build();
+```
+
+## Complete Example
+
+```tsx
+import React, { useEffect, useCallback } from 'react';
+import { Alert } from 'react-native';
+import { CometChatCalls } from '@cometchat/calls-sdk-react-native';
+
+interface CallScreenProps {
+ sessionId: string;
+ onCallEnd: () => void;
+}
+
+function CallScreen({ sessionId, onCallEnd }: CallScreenProps) {
+ const handleSessionTimeout = useCallback(() => {
+ Alert.alert(
+ 'Session Ended',
+ 'The call has ended due to inactivity.',
+ [
+ {
+ text: 'OK',
+ onPress: onCallEnd,
+ },
+ ]
+ );
+ }, [onCallEnd]);
+
+ useEffect(() => {
+ const unsubscribe = CometChatCalls.addEventListener(
+ 'onSessionTimedOut',
+ handleSessionTimeout
+ );
+
+ return () => unsubscribe();
+ }, [handleSessionTimeout]);
+
+ useEffect(() => {
+ async function initializeCall() {
+ try {
+ const { token } = await CometChatCalls.generateToken(sessionId);
+
+ const listener = new CometChatCalls.OngoingCallListener({
+ onSessionTimeout: handleSessionTimeout,
+ onCallEnded: onCallEnd,
+ });
+
+ const settings = new CometChatCalls.CallSettingsBuilder()
+ .setIdleTimeoutPeriod(180) // 3 minutes
+ .setCallEventListener(listener)
+ .build();
+
+ // Render call component with token and settings
+ } catch (error) {
+ console.error('Failed to initialize call:', error);
+ }
+ }
+
+ initializeCall();
+ }, [sessionId, handleSessionTimeout, onCallEnd]);
+
+ // ... render call UI
+ return null;
+}
+
+export default CallScreen;
+```
+
+## Timeout Behavior
+
+| Scenario | Behavior |
+|----------|----------|
+| User is alone | Timer starts counting down |
+| Another participant joins | Timer is cancelled |
+| User extends session | Timer resets |
+| Timer expires | Session ends, `onSessionTimedOut` fires |
+
+## Related Documentation
+
+- [Session Settings](/calls/react-native/session-settings) - Configure call settings
+- [Events](/calls/react-native/events) - Handle timeout events
+- [Join Session](/calls/react-native/join-session) - Start a call session
diff --git a/calls/react-native/in-call-chat.mdx b/calls/react-native/in-call-chat.mdx
new file mode 100644
index 00000000..472fb98d
--- /dev/null
+++ b/calls/react-native/in-call-chat.mdx
@@ -0,0 +1,485 @@
+---
+title: "In-Call Chat"
+sidebarTitle: "In-Call Chat"
+---
+
+Enable messaging during calls by integrating the CometChat Chat SDK. Users can send and receive text messages while on a call.
+
+
+In-call chat requires the CometChat Chat SDK (`@cometchat/chat-sdk-react-native`) for messaging functionality.
+
+
+## Prerequisites
+
+1. CometChat Chat SDK integrated
+2. User authenticated with the Chat SDK
+3. Active call session
+
+## Update Chat Button Badge
+
+Update the unread message count on the chat button:
+
+```tsx
+import { CometChatCalls } from '@cometchat/calls-sdk-react-native';
+
+// Set unread count
+CometChatCalls.setChatButtonUnreadCount(5);
+
+// Clear unread count
+CometChatCalls.setChatButtonUnreadCount(0);
+```
+
+## Listen for Chat Button Click
+
+Handle when the user clicks the chat button:
+
+```tsx
+CometChatCalls.addEventListener('onChatButtonClicked', () => {
+ console.log('Chat button clicked');
+ // Open chat interface
+});
+```
+
+## Send Messages
+
+Use the Chat SDK to send messages during a call:
+
+```tsx
+import { CometChat } from '@cometchat/chat-sdk-react-native';
+
+async function sendMessage(receiverId: string, text: string, receiverType: string) {
+ const message = new CometChat.TextMessage(
+ receiverId,
+ text,
+ receiverType
+ );
+
+ try {
+ const sentMessage = await CometChat.sendMessage(message);
+ console.log('Message sent:', sentMessage);
+ return sentMessage;
+ } catch (error) {
+ console.error('Error sending message:', error);
+ throw error;
+ }
+}
+
+// Send to user
+sendMessage('user_uid', 'Hello!', CometChat.RECEIVER_TYPE.USER);
+
+// Send to group
+sendMessage('group_guid', 'Hello everyone!', CometChat.RECEIVER_TYPE.GROUP);
+```
+
+## Receive Messages
+
+Listen for incoming messages:
+
+```tsx
+import { CometChat } from '@cometchat/chat-sdk-react-native';
+
+const listenerId = 'in_call_chat_listener';
+
+CometChat.addMessageListener(
+ listenerId,
+ new CometChat.MessageListener({
+ onTextMessageReceived: (message) => {
+ console.log('Text message received:', message);
+ // Update UI and badge count
+ },
+ onMediaMessageReceived: (message) => {
+ console.log('Media message received:', message);
+ },
+ })
+);
+
+// Remove listener when done
+CometChat.removeMessageListener(listenerId);
+```
+
+## Complete Example
+
+```tsx
+import React, { useState, useEffect, useRef } from 'react';
+import {
+ View,
+ FlatList,
+ TextInput,
+ TouchableOpacity,
+ Text,
+ StyleSheet,
+ Modal,
+ KeyboardAvoidingView,
+ Platform,
+} from 'react-native';
+import { CometChat } from '@cometchat/chat-sdk-react-native';
+import { CometChatCalls } from '@cometchat/calls-sdk-react-native';
+
+interface Message {
+ id: string;
+ text: string;
+ sender: {
+ uid: string;
+ name: string;
+ };
+ sentAt: number;
+ isOwn: boolean;
+}
+
+interface InCallChatProps {
+ receiverId: string;
+ receiverType: string;
+}
+
+function InCallChat({ receiverId, receiverType }: InCallChatProps) {
+ const [isVisible, setIsVisible] = useState(false);
+ const [messages, setMessages] = useState([]);
+ const [inputText, setInputText] = useState('');
+ const [unreadCount, setUnreadCount] = useState(0);
+ const flatListRef = useRef(null);
+ const currentUserId = useRef('');
+
+ useEffect(() => {
+ // Get current user
+ const user = CometChat.getLoggedinUser();
+ if (user) {
+ currentUserId.current = user.getUid();
+ }
+
+ // Listen for chat button click
+ const unsubscribeChatButton = CometChatCalls.addEventListener(
+ 'onChatButtonClicked',
+ () => {
+ setIsVisible(true);
+ setUnreadCount(0);
+ CometChatCalls.setChatButtonUnreadCount(0);
+ }
+ );
+
+ // Listen for messages
+ const listenerId = 'in_call_chat';
+ CometChat.addMessageListener(
+ listenerId,
+ new CometChat.MessageListener({
+ onTextMessageReceived: (message: any) => {
+ const newMessage: Message = {
+ id: message.getId().toString(),
+ text: message.getText(),
+ sender: {
+ uid: message.getSender().getUid(),
+ name: message.getSender().getName(),
+ },
+ sentAt: message.getSentAt(),
+ isOwn: message.getSender().getUid() === currentUserId.current,
+ };
+
+ setMessages((prev) => [...prev, newMessage]);
+
+ if (!isVisible) {
+ setUnreadCount((prev) => {
+ const newCount = prev + 1;
+ CometChatCalls.setChatButtonUnreadCount(newCount);
+ return newCount;
+ });
+ }
+ },
+ })
+ );
+
+ // Fetch previous messages
+ fetchMessages();
+
+ return () => {
+ unsubscribeChatButton();
+ CometChat.removeMessageListener(listenerId);
+ };
+ }, []);
+
+ const fetchMessages = async () => {
+ try {
+ const messagesRequest = new CometChat.MessagesRequestBuilder()
+ .setUID(receiverId)
+ .setLimit(50)
+ .build();
+
+ const fetchedMessages = await messagesRequest.fetchPrevious();
+
+ const formattedMessages: Message[] = fetchedMessages
+ .filter((msg: any) => msg.getType() === 'text')
+ .map((msg: any) => ({
+ id: msg.getId().toString(),
+ text: msg.getText(),
+ sender: {
+ uid: msg.getSender().getUid(),
+ name: msg.getSender().getName(),
+ },
+ sentAt: msg.getSentAt(),
+ isOwn: msg.getSender().getUid() === currentUserId.current,
+ }));
+
+ setMessages(formattedMessages);
+ } catch (error) {
+ console.error('Error fetching messages:', error);
+ }
+ };
+
+ const sendMessage = async () => {
+ if (!inputText.trim()) return;
+
+ const text = inputText.trim();
+ setInputText('');
+
+ try {
+ const message = new CometChat.TextMessage(
+ receiverId,
+ text,
+ receiverType
+ );
+
+ const sentMessage: any = await CometChat.sendMessage(message);
+
+ const newMessage: Message = {
+ id: sentMessage.getId().toString(),
+ text: sentMessage.getText(),
+ sender: {
+ uid: sentMessage.getSender().getUid(),
+ name: sentMessage.getSender().getName(),
+ },
+ sentAt: sentMessage.getSentAt(),
+ isOwn: true,
+ };
+
+ setMessages((prev) => [...prev, newMessage]);
+
+ // Scroll to bottom
+ setTimeout(() => {
+ flatListRef.current?.scrollToEnd();
+ }, 100);
+ } catch (error) {
+ console.error('Error sending message:', error);
+ }
+ };
+
+ const renderMessage = ({ item }: { item: Message }) => (
+
+ {!item.isOwn && (
+ {item.sender.name}
+ )}
+ {item.text}
+
+ {new Date(item.sentAt * 1000).toLocaleTimeString([], {
+ hour: '2-digit',
+ minute: '2-digit',
+ })}
+
+
+ );
+
+ return (
+ <>
+ {/* Chat toggle button with badge */}
+ {
+ setIsVisible(true);
+ setUnreadCount(0);
+ CometChatCalls.setChatButtonUnreadCount(0);
+ }}
+ >
+ 💬
+ {unreadCount > 0 && (
+
+
+ {unreadCount > 99 ? '99+' : unreadCount}
+
+
+ )}
+
+
+ {/* Chat modal */}
+ setIsVisible(false)}
+ >
+
+
+
+ Chat
+ setIsVisible(false)}>
+ ✕
+
+
+
+ item.id}
+ renderItem={renderMessage}
+ contentContainerStyle={styles.messagesList}
+ onContentSizeChange={() => flatListRef.current?.scrollToEnd()}
+ />
+
+
+
+
+ Send
+
+
+
+
+
+ >
+ );
+}
+
+const styles = StyleSheet.create({
+ chatButton: {
+ position: 'absolute',
+ top: 60,
+ left: 16,
+ backgroundColor: 'rgba(0, 0, 0, 0.6)',
+ width: 48,
+ height: 48,
+ borderRadius: 24,
+ justifyContent: 'center',
+ alignItems: 'center',
+ },
+ chatButtonText: {
+ fontSize: 24,
+ },
+ badge: {
+ position: 'absolute',
+ top: -4,
+ right: -4,
+ backgroundColor: '#ef4444',
+ minWidth: 20,
+ height: 20,
+ borderRadius: 10,
+ justifyContent: 'center',
+ alignItems: 'center',
+ paddingHorizontal: 4,
+ },
+ badgeText: {
+ color: '#fff',
+ fontSize: 12,
+ fontWeight: '600',
+ },
+ modalContainer: {
+ flex: 1,
+ backgroundColor: 'rgba(0, 0, 0, 0.5)',
+ justifyContent: 'flex-end',
+ },
+ chatContainer: {
+ backgroundColor: '#1a1a1a',
+ borderTopLeftRadius: 20,
+ borderTopRightRadius: 20,
+ height: '60%',
+ },
+ header: {
+ flexDirection: 'row',
+ justifyContent: 'space-between',
+ alignItems: 'center',
+ padding: 16,
+ borderBottomWidth: 1,
+ borderBottomColor: '#333',
+ },
+ headerTitle: {
+ color: '#fff',
+ fontSize: 18,
+ fontWeight: '600',
+ },
+ closeButton: {
+ color: '#fff',
+ fontSize: 20,
+ padding: 4,
+ },
+ messagesList: {
+ padding: 16,
+ },
+ messageContainer: {
+ maxWidth: '80%',
+ padding: 12,
+ borderRadius: 16,
+ marginBottom: 8,
+ },
+ ownMessage: {
+ alignSelf: 'flex-end',
+ backgroundColor: '#6851D6',
+ },
+ otherMessage: {
+ alignSelf: 'flex-start',
+ backgroundColor: '#333',
+ },
+ senderName: {
+ color: '#999',
+ fontSize: 12,
+ marginBottom: 4,
+ },
+ messageText: {
+ color: '#fff',
+ fontSize: 16,
+ },
+ messageTime: {
+ color: 'rgba(255, 255, 255, 0.6)',
+ fontSize: 10,
+ marginTop: 4,
+ alignSelf: 'flex-end',
+ },
+ inputContainer: {
+ flexDirection: 'row',
+ padding: 12,
+ borderTopWidth: 1,
+ borderTopColor: '#333',
+ alignItems: 'flex-end',
+ },
+ input: {
+ flex: 1,
+ backgroundColor: '#333',
+ borderRadius: 20,
+ paddingHorizontal: 16,
+ paddingVertical: 10,
+ color: '#fff',
+ maxHeight: 100,
+ },
+ sendButton: {
+ marginLeft: 8,
+ backgroundColor: '#6851D6',
+ paddingHorizontal: 16,
+ paddingVertical: 10,
+ borderRadius: 20,
+ },
+ sendButtonText: {
+ color: '#fff',
+ fontWeight: '600',
+ },
+});
+
+export default InCallChat;
+```
+
+## Related Documentation
+
+- [Events](/calls/react-native/events) - Chat button events
+- [Custom Control Panel](/calls/react-native/custom-control-panel) - Build custom controls
+- [Ringing](/calls/react-native/ringing) - Call signaling with Chat SDK
diff --git a/calls/react-native/join-session.mdx b/calls/react-native/join-session.mdx
new file mode 100644
index 00000000..c5afbaae
--- /dev/null
+++ b/calls/react-native/join-session.mdx
@@ -0,0 +1,349 @@
+---
+title: "Join Session"
+sidebarTitle: "Join Session"
+---
+
+This guide covers generating call tokens and joining call sessions using the CometChat Calls SDK.
+
+## Generate Token
+
+Before joining a call, you need to generate a call token. The token authenticates the user for the specific call session.
+
+```tsx
+import { CometChatCalls } from '@cometchat/calls-sdk-react-native';
+
+const sessionId = 'UNIQUE_SESSION_ID';
+
+try {
+ const { token } = await CometChatCalls.generateToken(sessionId);
+ console.log('Call token generated:', token);
+} catch (error) {
+ console.error('Token generation failed:', error);
+}
+```
+
+| Parameter | Type | Required | Description |
+|-----------|------|----------|-------------|
+| `sessionId` | string | Yes | Unique identifier for the call session |
+| `authToken` | string | No | User's auth token (uses logged-in user's token if not provided) |
+
+
+The `sessionId` should be unique for each call. You can use a UUID, a combination of user IDs, or any unique string that identifies the call session.
+
+
+## Join Session with Component
+
+The `CometChatCalls.Component` renders the call UI. Pass the generated token and call settings to join the session.
+
+```tsx
+import React, { useState, useEffect } from 'react';
+import { View, StyleSheet } from 'react-native';
+import { CometChatCalls } from '@cometchat/calls-sdk-react-native';
+
+interface CallScreenProps {
+ sessionId: string;
+ isAudioOnly?: boolean;
+}
+
+function CallScreen({ sessionId, isAudioOnly = false }: CallScreenProps) {
+ const [callToken, setCallToken] = useState(null);
+ const [callSettings, setCallSettings] = useState(null);
+
+ useEffect(() => {
+ async function initializeCall() {
+ try {
+ // Generate call token
+ const { token } = await CometChatCalls.generateToken(sessionId);
+ setCallToken(token);
+
+ // Create call settings
+ const listener = new CometChatCalls.OngoingCallListener({
+ onUserJoined: (user) => console.log('User joined:', user.name),
+ onUserLeft: (user) => console.log('User left:', user.name),
+ onCallEnded: () => console.log('Call ended'),
+ onError: (error) => console.error('Error:', error),
+ });
+
+ const settings = new CometChatCalls.CallSettingsBuilder()
+ .enableDefaultLayout(true)
+ .setIsAudioOnlyCall(isAudioOnly)
+ .setMode(CometChatCalls.CALL_MODE.DEFAULT)
+ .showEndCallButton(true)
+ .showMuteAudioButton(true)
+ .showPauseVideoButton(!isAudioOnly)
+ .setCallEventListener(listener)
+ .build();
+
+ setCallSettings(settings);
+ } catch (error) {
+ console.error('Failed to initialize call:', error);
+ }
+ }
+
+ initializeCall();
+ }, [sessionId, isAudioOnly]);
+
+ if (!callToken || !callSettings) {
+ return null; // Or show a loading indicator
+ }
+
+ return (
+
+
+
+ );
+}
+
+const styles = StyleSheet.create({
+ container: {
+ flex: 1,
+ },
+});
+
+export default CallScreen;
+```
+
+## Component Props
+
+| Prop | Type | Required | Description |
+|------|------|----------|-------------|
+| `callToken` | string | Yes | Token generated from `generateToken()` |
+| `callSettings` | CallSettings | No | Configuration object from `CallSettingsBuilder` |
+
+## Session ID Strategies
+
+Choose a session ID strategy based on your use case:
+
+### 1:1 Calls
+
+For direct calls between two users, create a deterministic session ID:
+
+```tsx
+function getDirectCallSessionId(userId1: string, userId2: string): string {
+ // Sort IDs to ensure same session ID regardless of who initiates
+ const sortedIds = [userId1, userId2].sort();
+ return `direct_${sortedIds[0]}_${sortedIds[1]}`;
+}
+
+const sessionId = getDirectCallSessionId('alice', 'bob');
+// Result: "direct_alice_bob"
+```
+
+### Group Calls
+
+For group calls, use the group ID or a unique identifier:
+
+```tsx
+function getGroupCallSessionId(groupId: string): string {
+ return `group_${groupId}`;
+}
+
+const sessionId = getGroupCallSessionId('team-standup');
+// Result: "group_team-standup"
+```
+
+### Unique Sessions
+
+For one-time calls, generate a unique ID:
+
+```tsx
+function generateUniqueSessionId(): string {
+ return `call_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
+}
+
+const sessionId = generateUniqueSessionId();
+// Result: "call_1704067200000_abc123xyz"
+```
+
+## Complete Example
+
+```tsx
+import React, { useState, useEffect, useCallback } from 'react';
+import { View, StyleSheet, ActivityIndicator, Text } from 'react-native';
+import { CometChatCalls } from '@cometchat/calls-sdk-react-native';
+
+interface CallScreenProps {
+ sessionId: string;
+ isAudioOnly?: boolean;
+ onCallEnd?: () => void;
+}
+
+function CallScreen({ sessionId, isAudioOnly = false, onCallEnd }: CallScreenProps) {
+ const [callToken, setCallToken] = useState(null);
+ const [callSettings, setCallSettings] = useState(null);
+ const [error, setError] = useState(null);
+ const [isLoading, setIsLoading] = useState(true);
+
+ const handleCallEnd = useCallback(() => {
+ console.log('Call ended');
+ onCallEnd?.();
+ }, [onCallEnd]);
+
+ useEffect(() => {
+ async function initializeCall() {
+ try {
+ setIsLoading(true);
+ setError(null);
+
+ // Generate call token
+ const { token } = await CometChatCalls.generateToken(sessionId);
+ setCallToken(token);
+
+ // Create call listener
+ const listener = new CometChatCalls.OngoingCallListener({
+ onUserJoined: (user) => {
+ console.log('User joined:', user.name);
+ },
+ onUserLeft: (user) => {
+ console.log('User left:', user.name);
+ },
+ onUserListUpdated: (userList) => {
+ console.log('Participants:', userList.length);
+ },
+ onCallEnded: handleCallEnd,
+ onCallEndButtonPressed: () => {
+ console.log('End button pressed');
+ CometChatCalls.leaveSession();
+ },
+ onSessionTimeout: () => {
+ console.log('Session timed out');
+ handleCallEnd();
+ },
+ onError: (err) => {
+ console.error('Call error:', err);
+ setError(err.errorDescription);
+ },
+ onRecordingStarted: () => {
+ console.log('Recording started');
+ },
+ onRecordingStopped: () => {
+ console.log('Recording stopped');
+ },
+ });
+
+ // Create call settings
+ const settings = new CometChatCalls.CallSettingsBuilder()
+ .enableDefaultLayout(true)
+ .setIsAudioOnlyCall(isAudioOnly)
+ .setMode(CometChatCalls.CALL_MODE.DEFAULT)
+ .showEndCallButton(true)
+ .showMuteAudioButton(true)
+ .showPauseVideoButton(!isAudioOnly)
+ .showSwitchCameraButton(!isAudioOnly)
+ .showAudioModeButton(true)
+ .startWithAudioMuted(false)
+ .startWithVideoMuted(false)
+ .setDefaultAudioMode(CometChatCalls.AUDIO_MODE.SPEAKER)
+ .showRecordingButton(true)
+ .setIdleTimeoutPeriod(180)
+ .setCallEventListener(listener)
+ .build();
+
+ setCallSettings(settings);
+ } catch (err: any) {
+ console.error('Failed to initialize call:', err);
+ setError(err.errorDescription || 'Failed to initialize call');
+ } finally {
+ setIsLoading(false);
+ }
+ }
+
+ initializeCall();
+ }, [sessionId, isAudioOnly, handleCallEnd]);
+
+ if (isLoading) {
+ return (
+
+
+ Joining call...
+
+ );
+ }
+
+ if (error) {
+ return (
+
+ {error}
+
+ );
+ }
+
+ if (!callToken || !callSettings) {
+ return null;
+ }
+
+ return (
+
+
+
+ );
+}
+
+const styles = StyleSheet.create({
+ container: {
+ flex: 1,
+ backgroundColor: '#000',
+ },
+ centered: {
+ flex: 1,
+ justifyContent: 'center',
+ alignItems: 'center',
+ backgroundColor: '#000',
+ },
+ loadingText: {
+ color: '#fff',
+ marginTop: 16,
+ fontSize: 16,
+ },
+ errorText: {
+ color: '#ff4444',
+ fontSize: 16,
+ textAlign: 'center',
+ paddingHorizontal: 20,
+ },
+});
+
+export default CallScreen;
+```
+
+## Error Handling
+
+Common errors when joining a session:
+
+| Error Code | Description |
+|------------|-------------|
+| `ERROR_SESSION_ID_MISSING` | Session ID is empty or not provided |
+| `ERROR_AUTH_TOKEN_MISSING` | User is not logged in or auth token is missing |
+| `ERROR_SDK_NOT_INITIALIZED` | SDK not initialized. Call `init()` first |
+
+```tsx
+try {
+ const { token } = await CometChatCalls.generateToken(sessionId);
+} catch (error: any) {
+ switch (error.errorCode) {
+ case 'ERROR_SESSION_ID_MISSING':
+ console.error('Please provide a valid session ID');
+ break;
+ case 'ERROR_AUTH_TOKEN_MISSING':
+ console.error('Please login before generating a token');
+ break;
+ case 'ERROR_SDK_NOT_INITIALIZED':
+ console.error('Please initialize the SDK first');
+ break;
+ default:
+ console.error('Error:', error.errorDescription);
+ }
+}
+```
+
+## Related Documentation
+
+- [Session Settings](/calls/react-native/session-settings) - Configure call settings
+- [Actions](/calls/react-native/actions) - Control the call programmatically
+- [Events](/calls/react-native/events) - Handle call events
diff --git a/calls/react-native/overview.mdx b/calls/react-native/overview.mdx
new file mode 100644
index 00000000..eb018fe0
--- /dev/null
+++ b/calls/react-native/overview.mdx
@@ -0,0 +1,113 @@
+---
+title: "Calls SDK"
+sidebarTitle: "Overview"
+---
+
+The CometChat Calls SDK enables real-time voice and video calling capabilities in your React Native application. Built on top of WebRTC, it provides a complete calling solution with built-in UI components and extensive customization options.
+
+
+**Faster Integration with UI Kits**
+
+If you're using CometChat UI Kits, voice and video calling can be quickly integrated:
+- Incoming & outgoing call screens
+- Call buttons with one-tap calling
+- Call logs with history
+
+👉 [React Native UI Kit Calling Integration](/ui-kit/react-native/calling-integration)
+
+Use this Calls SDK directly only if you need custom call UI or advanced control.
+
+
+## Prerequisites
+
+Before integrating the Calls SDK, ensure you have:
+
+1. **CometChat Account**: [Sign up](https://app.cometchat.com/signup) and create an app to get your App ID, Region, and API Key
+2. **CometChat Users**: Users must exist in CometChat to use calling features. For testing, create users via the [Dashboard](https://app.cometchat.com) or [REST API](/rest-api/chat-apis/users/create-user). Authentication is handled by the Calls SDK - see [Authentication](/calls/react-native/authentication)
+3. **React Native Requirements**:
+ - React Native 0.71 or later
+ - Node.js 18 or later
+ - iOS: Minimum iOS 13.0, Xcode 14.0+
+ - Android: Minimum SDK API Level 24 (Android 7.0)
+4. **Permissions**: Camera and microphone permissions for video/audio calls
+
+## Call Flow
+
+```mermaid
+sequenceDiagram
+ participant App
+ participant CometChatCalls
+ participant Component
+
+ App->>CometChatCalls: init()
+ App->>CometChatCalls: login()
+ App->>CometChatCalls: generateToken()
+ App->>Component: Render with callToken
+ Component-->>App: Event callbacks
+ App->>CometChatCalls: Actions (mute, pause, etc.)
+ App->>CometChatCalls: leaveSession()
+```
+
+## Features
+
+
+
+
+ Incoming and outgoing call notifications with accept/reject functionality
+
+
+
+ Tile, Sidebar, and Spotlight view modes for different call scenarios
+
+
+
+ Switch between speaker, earpiece, Bluetooth, and headphones
+
+
+
+ Record call sessions for later playback
+
+
+
+ Retrieve call history and details
+
+
+
+ Mute, pin, and manage call participants
+
+
+
+ View screen shares from other participants
+
+
+
+ Continue calls while using other apps
+
+
+
+ Signal to get attention during calls
+
+
+
+ Automatic session termination when alone in a call
+
+
+
+
+## Architecture
+
+The SDK is organized around these core components:
+
+| Component | Description |
+|-----------|-------------|
+| `CometChatCalls` | Main entry point for SDK initialization, authentication, and session management |
+| `CallAppSettingsBuilder` | Configuration builder for SDK initialization (App ID, Region) |
+| `CallSettingsBuilder` | Configuration builder for individual call sessions |
+| `CometChatCalls.Component` | React component that renders the call UI |
+| `OngoingCallListener` | Event listener class for call events |
+
+## Related Documentation
+
+- [Setup](/calls/react-native/setup) - Install and configure the SDK
+- [Authentication](/calls/react-native/authentication) - Initialize and authenticate users
+- [Join Session](/calls/react-native/join-session) - Start and join calls
diff --git a/calls/react-native/participant-management.mdx b/calls/react-native/participant-management.mdx
new file mode 100644
index 00000000..43966933
--- /dev/null
+++ b/calls/react-native/participant-management.mdx
@@ -0,0 +1,349 @@
+---
+title: "Participant Management"
+sidebarTitle: "Participant Management"
+---
+
+Manage call participants by muting, pinning, and monitoring their status during a call.
+
+## Get Participant List
+
+Listen for participant list changes:
+
+```tsx
+import { CometChatCalls } from '@cometchat/calls-sdk-react-native';
+
+CometChatCalls.addEventListener('onParticipantListChanged', (participants) => {
+ console.log('Participants:', participants);
+ participants.forEach((participant) => {
+ console.log(`- ${participant.name} (${participant.uid})`);
+ });
+});
+```
+
+## Participant Object
+
+Each participant contains:
+
+| Property | Type | Description |
+|----------|------|-------------|
+| `pid` | string | Participant ID (unique per session) |
+| `uid` | string | User ID |
+| `name` | string | Display name |
+| `avatar` | string | Avatar URL (optional) |
+
+## Pin Participant
+
+Pin a participant to the main view in Sidebar or Spotlight layouts:
+
+```tsx
+// Pin a participant's video
+CometChatCalls.pinParticipant(participantId, 'human');
+
+// Pin a participant's screen share
+CometChatCalls.pinParticipant(participantId, 'screen-share');
+```
+
+| Parameter | Type | Description |
+|-----------|------|-------------|
+| `participantId` | string | The participant's `pid` |
+| `type` | string | `'human'` for video, `'screen-share'` for screen share |
+
+## Unpin Participant
+
+Remove the pinned participant:
+
+```tsx
+CometChatCalls.unpinParticipant();
+```
+
+## Mute Participant
+
+Mute another participant's audio (requires moderator permissions):
+
+```tsx
+CometChatCalls.muteParticipant(participantId);
+```
+
+## Pause Participant Video
+
+Pause another participant's video (requires moderator permissions):
+
+```tsx
+CometChatCalls.pauseParticipantVideo(participantId);
+```
+
+## Participant Events
+
+### Join and Leave
+
+```tsx
+CometChatCalls.addEventListener('onParticipantJoined', (participant) => {
+ console.log(`${participant.name} joined the call`);
+});
+
+CometChatCalls.addEventListener('onParticipantLeft', (participant) => {
+ console.log(`${participant.name} left the call`);
+});
+```
+
+### Audio State
+
+```tsx
+CometChatCalls.addEventListener('onParticipantAudioMuted', (participant) => {
+ console.log(`${participant.name} muted their audio`);
+});
+
+CometChatCalls.addEventListener('onParticipantAudioUnmuted', (participant) => {
+ console.log(`${participant.name} unmuted their audio`);
+});
+```
+
+### Video State
+
+```tsx
+CometChatCalls.addEventListener('onParticipantVideoPaused', (participant) => {
+ console.log(`${participant.name} paused their video`);
+});
+
+CometChatCalls.addEventListener('onParticipantVideoResumed', (participant) => {
+ console.log(`${participant.name} resumed their video`);
+});
+```
+
+### Hand Raised
+
+```tsx
+CometChatCalls.addEventListener('onParticipantHandRaised', (participant) => {
+ console.log(`${participant.name} raised their hand`);
+});
+
+CometChatCalls.addEventListener('onParticipantHandLowered', (participant) => {
+ console.log(`${participant.name} lowered their hand`);
+});
+```
+
+### Screen Sharing
+
+```tsx
+CometChatCalls.addEventListener('onParticipantStartedScreenShare', (participant) => {
+ console.log(`${participant.name} started screen sharing`);
+});
+
+CometChatCalls.addEventListener('onParticipantStoppedScreenShare', (participant) => {
+ console.log(`${participant.name} stopped screen sharing`);
+});
+```
+
+### Dominant Speaker
+
+```tsx
+CometChatCalls.addEventListener('onDominantSpeakerChanged', (participant) => {
+ console.log(`Dominant speaker: ${participant.name}`);
+});
+```
+
+## Using OngoingCallListener
+
+Handle participant events through the call settings listener:
+
+```tsx
+const callListener = new CometChatCalls.OngoingCallListener({
+ onUserJoined: (user) => {
+ console.log('User joined:', user.name);
+ },
+ onUserLeft: (user) => {
+ console.log('User left:', user.name);
+ },
+ onUserListUpdated: (userList) => {
+ console.log('Participant count:', userList.length);
+ },
+ onUserMuted: (user) => {
+ console.log('User muted:', user.name);
+ },
+});
+
+const callSettings = new CometChatCalls.CallSettingsBuilder()
+ .setCallEventListener(callListener)
+ .build();
+```
+
+## Complete Example
+
+```tsx
+import React, { useState, useEffect } from 'react';
+import { View, FlatList, Text, TouchableOpacity, StyleSheet } from 'react-native';
+import { CometChatCalls } from '@cometchat/calls-sdk-react-native';
+
+interface Participant {
+ pid: string;
+ uid: string;
+ name: string;
+ avatar?: string;
+}
+
+function ParticipantList() {
+ const [participants, setParticipants] = useState([]);
+ const [pinnedId, setPinnedId] = useState(null);
+
+ useEffect(() => {
+ const unsubscribeList = CometChatCalls.addEventListener(
+ 'onParticipantListChanged',
+ (list: Participant[]) => {
+ setParticipants(list);
+ }
+ );
+
+ const unsubscribeJoin = CometChatCalls.addEventListener(
+ 'onParticipantJoined',
+ (participant: Participant) => {
+ console.log(`${participant.name} joined`);
+ }
+ );
+
+ const unsubscribeLeave = CometChatCalls.addEventListener(
+ 'onParticipantLeft',
+ (participant: Participant) => {
+ console.log(`${participant.name} left`);
+ // Unpin if the pinned participant left
+ if (pinnedId === participant.pid) {
+ setPinnedId(null);
+ }
+ }
+ );
+
+ return () => {
+ unsubscribeList();
+ unsubscribeJoin();
+ unsubscribeLeave();
+ };
+ }, [pinnedId]);
+
+ const handlePin = (participant: Participant) => {
+ if (pinnedId === participant.pid) {
+ CometChatCalls.unpinParticipant();
+ setPinnedId(null);
+ } else {
+ CometChatCalls.pinParticipant(participant.pid, 'human');
+ setPinnedId(participant.pid);
+ }
+ };
+
+ const handleMute = (participant: Participant) => {
+ CometChatCalls.muteParticipant(participant.pid);
+ };
+
+ const renderParticipant = ({ item }: { item: Participant }) => (
+
+
+
+ {item.name.charAt(0).toUpperCase()}
+
+
+ {item.name}
+
+ handlePin(item)}
+ >
+
+ {pinnedId === item.pid ? 'Unpin' : 'Pin'}
+
+
+ handleMute(item)}
+ >
+ Mute
+
+
+
+ );
+
+ return (
+
+
+ Participants ({participants.length})
+
+ item.pid}
+ renderItem={renderParticipant}
+ ListEmptyComponent={
+ No other participants
+ }
+ />
+
+ );
+}
+
+const styles = StyleSheet.create({
+ container: {
+ flex: 1,
+ backgroundColor: '#1a1a1a',
+ },
+ header: {
+ color: '#fff',
+ fontSize: 18,
+ fontWeight: '600',
+ padding: 16,
+ borderBottomWidth: 1,
+ borderBottomColor: '#333',
+ },
+ participantItem: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ padding: 12,
+ borderBottomWidth: 1,
+ borderBottomColor: '#333',
+ },
+ avatar: {
+ width: 40,
+ height: 40,
+ borderRadius: 20,
+ backgroundColor: '#6851D6',
+ justifyContent: 'center',
+ alignItems: 'center',
+ },
+ avatarText: {
+ color: '#fff',
+ fontSize: 16,
+ fontWeight: '600',
+ },
+ name: {
+ flex: 1,
+ color: '#fff',
+ fontSize: 16,
+ marginLeft: 12,
+ },
+ actions: {
+ flexDirection: 'row',
+ gap: 8,
+ },
+ actionButton: {
+ backgroundColor: '#333',
+ paddingHorizontal: 12,
+ paddingVertical: 6,
+ borderRadius: 4,
+ },
+ activeButton: {
+ backgroundColor: '#6851D6',
+ },
+ actionText: {
+ color: '#fff',
+ fontSize: 12,
+ },
+ emptyText: {
+ color: '#666',
+ textAlign: 'center',
+ padding: 20,
+ },
+});
+
+export default ParticipantList;
+```
+
+## Related Documentation
+
+- [Events](/calls/react-native/events) - All participant events
+- [Call Layouts](/calls/react-native/call-layouts) - Layout options for participants
+- [Custom Participant List](/calls/react-native/custom-participant-list) - Build custom UI
diff --git a/calls/react-native/picture-in-picture.mdx b/calls/react-native/picture-in-picture.mdx
new file mode 100644
index 00000000..82fdb979
--- /dev/null
+++ b/calls/react-native/picture-in-picture.mdx
@@ -0,0 +1,239 @@
+---
+title: "Picture-in-Picture"
+sidebarTitle: "Picture-in-Picture"
+---
+
+Picture-in-Picture (PiP) mode allows users to continue their call in a floating window while using other apps. This feature requires platform-specific configuration.
+
+## Enable Picture-in-Picture
+
+Enable PiP mode programmatically:
+
+```tsx
+import { CometChatCalls } from '@cometchat/calls-sdk-react-native';
+
+CometChatCalls.enablePictureInPictureLayout();
+```
+
+## Disable Picture-in-Picture
+
+Exit PiP mode and return to full-screen:
+
+```tsx
+CometChatCalls.disablePictureInPictureLayout();
+```
+
+## Listen for PiP Events
+
+```tsx
+CometChatCalls.addEventListener('onPictureInPictureLayoutEnabled', () => {
+ console.log('PiP mode enabled');
+});
+
+CometChatCalls.addEventListener('onPictureInPictureLayoutDisabled', () => {
+ console.log('PiP mode disabled');
+});
+```
+
+## iOS Configuration
+
+### Enable Background Modes
+
+1. Open your project in Xcode
+2. Select your target and go to **Signing & Capabilities**
+3. Add **Background Modes** capability
+4. Enable **Audio, AirPlay, and Picture in Picture**
+
+### Info.plist
+
+No additional Info.plist entries are required for PiP on iOS.
+
+### Automatic PiP
+
+iOS automatically enters PiP mode when:
+- The user presses the home button during a video call
+- The user swipes up to go to the app switcher
+
+## Android Configuration
+
+### Enable PiP Support
+
+Add PiP support to your main activity in `AndroidManifest.xml`:
+
+```xml
+
+
+
+```
+
+### Minimum SDK
+
+PiP requires Android 8.0 (API level 26) or higher. Add to your `build.gradle`:
+
+```groovy
+android {
+ defaultConfig {
+ minSdkVersion 26
+ }
+}
+```
+
+### Handle PiP Mode Changes
+
+In your React Native activity, handle PiP mode changes:
+
+```java
+// MainActivity.java
+@Override
+public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode) {
+ super.onPictureInPictureModeChanged(isInPictureInPictureMode);
+ // Notify React Native about PiP state change
+}
+```
+
+## Complete Example
+
+```tsx
+import React, { useState, useEffect } from 'react';
+import { View, TouchableOpacity, Text, StyleSheet, AppState, Platform } from 'react-native';
+import { CometChatCalls } from '@cometchat/calls-sdk-react-native';
+
+function PictureInPictureControls() {
+ const [isPiPEnabled, setIsPiPEnabled] = useState(false);
+ const [appState, setAppState] = useState(AppState.currentState);
+
+ useEffect(() => {
+ const unsubscribeEnabled = CometChatCalls.addEventListener(
+ 'onPictureInPictureLayoutEnabled',
+ () => {
+ setIsPiPEnabled(true);
+ }
+ );
+
+ const unsubscribeDisabled = CometChatCalls.addEventListener(
+ 'onPictureInPictureLayoutDisabled',
+ () => {
+ setIsPiPEnabled(false);
+ }
+ );
+
+ // Listen for app state changes
+ const subscription = AppState.addEventListener('change', (nextAppState) => {
+ if (appState.match(/active/) && nextAppState === 'background') {
+ // App going to background - enable PiP
+ CometChatCalls.enablePictureInPictureLayout();
+ }
+ setAppState(nextAppState);
+ });
+
+ return () => {
+ unsubscribeEnabled();
+ unsubscribeDisabled();
+ subscription.remove();
+ };
+ }, [appState]);
+
+ const togglePiP = () => {
+ if (isPiPEnabled) {
+ CometChatCalls.disablePictureInPictureLayout();
+ } else {
+ CometChatCalls.enablePictureInPictureLayout();
+ }
+ };
+
+ return (
+
+
+ {isPiPEnabled ? 'Exit PiP' : 'Enter PiP'}
+
+
+ );
+}
+
+const styles = StyleSheet.create({
+ button: {
+ backgroundColor: '#333',
+ paddingHorizontal: 16,
+ paddingVertical: 12,
+ borderRadius: 8,
+ },
+ activeButton: {
+ backgroundColor: '#6851D6',
+ },
+ buttonText: {
+ color: '#fff',
+ fontSize: 14,
+ fontWeight: '600',
+ },
+});
+
+export default PictureInPictureControls;
+```
+
+## Auto-Enable PiP on Background
+
+Automatically enable PiP when the app goes to background:
+
+```tsx
+import { useEffect, useRef } from 'react';
+import { AppState, AppStateStatus } from 'react-native';
+import { CometChatCalls } from '@cometchat/calls-sdk-react-native';
+
+function useAutoPiP(enabled: boolean = true) {
+ const appState = useRef(AppState.currentState);
+
+ useEffect(() => {
+ if (!enabled) return;
+
+ const subscription = AppState.addEventListener(
+ 'change',
+ (nextAppState: AppStateStatus) => {
+ if (
+ appState.current.match(/active/) &&
+ nextAppState === 'background'
+ ) {
+ CometChatCalls.enablePictureInPictureLayout();
+ } else if (
+ appState.current === 'background' &&
+ nextAppState === 'active'
+ ) {
+ CometChatCalls.disablePictureInPictureLayout();
+ }
+ appState.current = nextAppState;
+ }
+ );
+
+ return () => {
+ subscription.remove();
+ };
+ }, [enabled]);
+}
+
+export default useAutoPiP;
+```
+
+## Platform Considerations
+
+### iOS
+
+- PiP is available on iOS 14.0 and later
+- Works automatically when the app enters background
+- User can resize and move the PiP window
+
+### Android
+
+- PiP requires Android 8.0 (API level 26) or higher
+- Must be explicitly enabled in the manifest
+- PiP window has fixed aspect ratio
+
+## Related Documentation
+
+- [Background Handling](/calls/react-native/background-handling) - Keep calls active in background
+- [Actions](/calls/react-native/actions) - Control the call programmatically
+- [Events](/calls/react-native/events) - PiP events
diff --git a/calls/react-native/raise-hand.mdx b/calls/react-native/raise-hand.mdx
new file mode 100644
index 00000000..8aa46aa1
--- /dev/null
+++ b/calls/react-native/raise-hand.mdx
@@ -0,0 +1,213 @@
+---
+title: "Raise Hand"
+sidebarTitle: "Raise Hand"
+---
+
+The raise hand feature allows participants to signal that they want attention during a call, useful for Q&A sessions or large meetings.
+
+## Raise Hand
+
+Signal to other participants that you want attention:
+
+```tsx
+import { CometChatCalls } from '@cometchat/calls-sdk-react-native';
+
+CometChatCalls.raiseHand();
+```
+
+## Lower Hand
+
+Lower your raised hand:
+
+```tsx
+CometChatCalls.lowerHand();
+```
+
+## Listen for Hand Raised Events
+
+### Local Events
+
+Listen for when the raise hand button is clicked:
+
+```tsx
+CometChatCalls.addEventListener('onRaiseHandButtonClicked', () => {
+ console.log('Raise hand button clicked');
+});
+```
+
+### Participant Events
+
+Listen for when other participants raise or lower their hands:
+
+```tsx
+CometChatCalls.addEventListener('onParticipantHandRaised', (participant) => {
+ console.log(`${participant.name} raised their hand`);
+});
+
+CometChatCalls.addEventListener('onParticipantHandLowered', (participant) => {
+ console.log(`${participant.name} lowered their hand`);
+});
+```
+
+## Complete Example
+
+```tsx
+import React, { useState, useEffect } from 'react';
+import { View, TouchableOpacity, Text, StyleSheet, FlatList } from 'react-native';
+import { CometChatCalls } from '@cometchat/calls-sdk-react-native';
+
+interface Participant {
+ pid: string;
+ name: string;
+}
+
+function RaiseHandFeature() {
+ const [isHandRaised, setIsHandRaised] = useState(false);
+ const [raisedHands, setRaisedHands] = useState([]);
+
+ useEffect(() => {
+ const unsubscribeRaised = CometChatCalls.addEventListener(
+ 'onParticipantHandRaised',
+ (participant: Participant) => {
+ setRaisedHands((prev) => {
+ if (prev.find((p) => p.pid === participant.pid)) {
+ return prev;
+ }
+ return [...prev, participant];
+ });
+ }
+ );
+
+ const unsubscribeLowered = CometChatCalls.addEventListener(
+ 'onParticipantHandLowered',
+ (participant: Participant) => {
+ setRaisedHands((prev) =>
+ prev.filter((p) => p.pid !== participant.pid)
+ );
+ }
+ );
+
+ const unsubscribeLeft = CometChatCalls.addEventListener(
+ 'onParticipantLeft',
+ (participant: Participant) => {
+ setRaisedHands((prev) =>
+ prev.filter((p) => p.pid !== participant.pid)
+ );
+ }
+ );
+
+ return () => {
+ unsubscribeRaised();
+ unsubscribeLowered();
+ unsubscribeLeft();
+ };
+ }, []);
+
+ const toggleHand = () => {
+ if (isHandRaised) {
+ CometChatCalls.lowerHand();
+ setIsHandRaised(false);
+ } else {
+ CometChatCalls.raiseHand();
+ setIsHandRaised(true);
+ }
+ };
+
+ const renderRaisedHand = ({ item }: { item: Participant }) => (
+
+ ✋
+ {item.name}
+
+ );
+
+ return (
+
+
+ {isHandRaised ? '✋' : '🤚'}
+
+ {isHandRaised ? 'Lower Hand' : 'Raise Hand'}
+
+
+
+ {raisedHands.length > 0 && (
+
+
+ Raised Hands ({raisedHands.length})
+
+ item.pid}
+ renderItem={renderRaisedHand}
+ horizontal
+ showsHorizontalScrollIndicator={false}
+ />
+
+ )}
+
+ );
+}
+
+const styles = StyleSheet.create({
+ container: {
+ padding: 16,
+ },
+ button: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ justifyContent: 'center',
+ backgroundColor: '#333',
+ paddingHorizontal: 20,
+ paddingVertical: 12,
+ borderRadius: 8,
+ gap: 8,
+ },
+ activeButton: {
+ backgroundColor: '#f59e0b',
+ },
+ buttonIcon: {
+ fontSize: 20,
+ },
+ buttonText: {
+ color: '#fff',
+ fontSize: 14,
+ fontWeight: '600',
+ },
+ raisedHandsList: {
+ marginTop: 16,
+ },
+ listTitle: {
+ color: '#fff',
+ fontSize: 14,
+ fontWeight: '600',
+ marginBottom: 8,
+ },
+ raisedHandItem: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ backgroundColor: '#333',
+ paddingHorizontal: 12,
+ paddingVertical: 8,
+ borderRadius: 20,
+ marginRight: 8,
+ gap: 6,
+ },
+ handIcon: {
+ fontSize: 16,
+ },
+ participantName: {
+ color: '#fff',
+ fontSize: 14,
+ },
+});
+
+export default RaiseHandFeature;
+```
+
+## Related Documentation
+
+- [Events](/calls/react-native/events) - All raise hand events
+- [Participant Management](/calls/react-native/participant-management) - Manage participants
+- [Actions](/calls/react-native/actions) - Available call actions
diff --git a/calls/react-native/recording.mdx b/calls/react-native/recording.mdx
new file mode 100644
index 00000000..4dfab3e8
--- /dev/null
+++ b/calls/react-native/recording.mdx
@@ -0,0 +1,242 @@
+---
+title: "Recording"
+sidebarTitle: "Recording"
+---
+
+The CometChat Calls SDK supports recording call sessions. Recordings can be started manually or automatically when the call begins.
+
+## Enable Recording Button
+
+Show the recording button in the call UI:
+
+```tsx
+import { CometChatCalls } from '@cometchat/calls-sdk-react-native';
+
+const callSettings = new CometChatCalls.CallSettingsBuilder()
+ .showRecordingButton(true)
+ .build();
+```
+
+## Auto-Start Recording
+
+Start recording automatically when the call begins:
+
+```tsx
+const callSettings = new CometChatCalls.CallSettingsBuilder()
+ .showRecordingButton(true)
+ .startRecordingOnCallStart(true)
+ .build();
+```
+
+## Start Recording Programmatically
+
+Start recording during an active call:
+
+```tsx
+CometChatCalls.startRecording();
+```
+
+## Stop Recording
+
+Stop an active recording:
+
+```tsx
+CometChatCalls.stopRecording();
+```
+
+## Recording Events
+
+Listen for recording state changes:
+
+### Using OngoingCallListener
+
+```tsx
+const callListener = new CometChatCalls.OngoingCallListener({
+ onRecordingStarted: (data) => {
+ console.log('Recording started:', data);
+ },
+ onRecordingStopped: (data) => {
+ console.log('Recording stopped:', data);
+ },
+});
+
+const callSettings = new CometChatCalls.CallSettingsBuilder()
+ .setCallEventListener(callListener)
+ .build();
+```
+
+### Using addEventListener
+
+```tsx
+const unsubscribeStart = CometChatCalls.addEventListener(
+ 'onRecordingStarted',
+ () => {
+ console.log('Recording started');
+ }
+);
+
+const unsubscribeStop = CometChatCalls.addEventListener(
+ 'onRecordingStopped',
+ () => {
+ console.log('Recording stopped');
+ }
+);
+
+// Cleanup
+unsubscribeStart();
+unsubscribeStop();
+```
+
+### Participant Recording Events
+
+Listen when other participants start or stop recording:
+
+```tsx
+CometChatCalls.addEventListener('onParticipantStartedRecording', (participant) => {
+ console.log(`${participant.name} started recording`);
+});
+
+CometChatCalls.addEventListener('onParticipantStoppedRecording', (participant) => {
+ console.log(`${participant.name} stopped recording`);
+});
+```
+
+## Recording Button Click Event
+
+Listen for when the recording button is clicked:
+
+```tsx
+CometChatCalls.addEventListener('onRecordingToggleButtonClicked', () => {
+ console.log('Recording button clicked');
+});
+```
+
+## Access Recordings
+
+Recordings are available through the call logs after the call ends. Use the Chat SDK to retrieve recordings:
+
+```tsx
+import { CometChat } from '@cometchat/chat-sdk-react-native';
+
+async function getCallRecordings(sessionId: string) {
+ const callLogRequest = new CometChat.CallLogRequestBuilder()
+ .setLimit(1)
+ .build();
+
+ try {
+ const callLogs = await callLogRequest.fetchNext();
+ const callLog = callLogs.find((log) => log.sessionId === sessionId);
+
+ if (callLog && callLog.recordings) {
+ console.log('Recordings:', callLog.recordings);
+ return callLog.recordings;
+ }
+ return [];
+ } catch (error) {
+ console.error('Error fetching recordings:', error);
+ throw error;
+ }
+}
+```
+
+## Recording Object
+
+Each recording contains:
+
+| Property | Type | Description |
+|----------|------|-------------|
+| `rid` | string | Recording ID |
+| `recordingUrl` | string | URL to download the recording |
+| `startTime` | number | Recording start timestamp |
+| `endTime` | number | Recording end timestamp |
+| `duration` | number | Recording duration in seconds |
+
+## Complete Example
+
+```tsx
+import React, { useState, useEffect } from 'react';
+import { View, TouchableOpacity, Text, StyleSheet } from 'react-native';
+import { CometChatCalls } from '@cometchat/calls-sdk-react-native';
+
+function RecordingControls() {
+ const [isRecording, setIsRecording] = useState(false);
+
+ useEffect(() => {
+ const unsubscribeStart = CometChatCalls.addEventListener(
+ 'onRecordingStarted',
+ () => {
+ setIsRecording(true);
+ }
+ );
+
+ const unsubscribeStop = CometChatCalls.addEventListener(
+ 'onRecordingStopped',
+ () => {
+ setIsRecording(false);
+ }
+ );
+
+ return () => {
+ unsubscribeStart();
+ unsubscribeStop();
+ };
+ }, []);
+
+ const toggleRecording = () => {
+ if (isRecording) {
+ CometChatCalls.stopRecording();
+ } else {
+ CometChatCalls.startRecording();
+ }
+ };
+
+ return (
+
+
+
+ {isRecording ? 'Stop Recording' : 'Start Recording'}
+
+
+ );
+}
+
+const styles = StyleSheet.create({
+ button: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ backgroundColor: '#333',
+ paddingHorizontal: 16,
+ paddingVertical: 12,
+ borderRadius: 8,
+ gap: 8,
+ },
+ recordingButton: {
+ backgroundColor: '#ff4444',
+ },
+ indicator: {
+ width: 12,
+ height: 12,
+ borderRadius: 6,
+ backgroundColor: '#666',
+ },
+ recordingIndicator: {
+ backgroundColor: '#fff',
+ },
+ buttonText: {
+ color: '#fff',
+ fontSize: 14,
+ fontWeight: '600',
+ },
+});
+
+export default RecordingControls;
+```
+
+## Related Documentation
+
+- [Call Logs](/calls/react-native/call-logs) - Access call history and recordings
+- [Session Settings](/calls/react-native/session-settings) - Configure recording options
+- [Events](/calls/react-native/events) - Handle recording events
diff --git a/calls/react-native/ringing.mdx b/calls/react-native/ringing.mdx
new file mode 100644
index 00000000..5f83dea0
--- /dev/null
+++ b/calls/react-native/ringing.mdx
@@ -0,0 +1,394 @@
+---
+title: "Ringing"
+sidebarTitle: "Ringing"
+---
+
+Implement incoming and outgoing call notifications using the CometChat Chat SDK's calling features. The Calls SDK handles the actual call session, while the Chat SDK manages call signaling.
+
+
+Ringing functionality requires the CometChat Chat SDK (`@cometchat/chat-sdk-react-native`) for call signaling. The Calls SDK is used for the actual call session.
+
+
+## Prerequisites
+
+1. CometChat Chat SDK integrated
+2. CometChat Calls SDK integrated
+3. Push notifications configured (for background calls)
+
+## Initiate a Call
+
+Use the Chat SDK to initiate a call:
+
+```tsx
+import { CometChat } from '@cometchat/chat-sdk-react-native';
+
+async function initiateCall(receiverId: string, callType: string, receiverType: string) {
+ const call = new CometChat.Call(receiverId, callType, receiverType);
+
+ try {
+ const outgoingCall = await CometChat.initiateCall(call);
+ console.log('Call initiated:', outgoingCall);
+ return outgoingCall;
+ } catch (error) {
+ console.error('Error initiating call:', error);
+ throw error;
+ }
+}
+
+// Initiate a video call to a user
+initiateCall('user_uid', CometChat.CALL_TYPE.VIDEO, CometChat.RECEIVER_TYPE.USER);
+
+// Initiate an audio call to a group
+initiateCall('group_guid', CometChat.CALL_TYPE.AUDIO, CometChat.RECEIVER_TYPE.GROUP);
+```
+
+## Listen for Incoming Calls
+
+Register a call listener to receive incoming calls:
+
+```tsx
+import { CometChat } from '@cometchat/chat-sdk-react-native';
+
+const listenerId = 'unique_listener_id';
+
+CometChat.addCallListener(
+ listenerId,
+ new CometChat.CallListener({
+ onIncomingCallReceived: (call) => {
+ console.log('Incoming call:', call);
+ // Show incoming call UI
+ },
+ onOutgoingCallAccepted: (call) => {
+ console.log('Call accepted:', call);
+ // Start the call session
+ },
+ onOutgoingCallRejected: (call) => {
+ console.log('Call rejected:', call);
+ // Handle rejection
+ },
+ onIncomingCallCancelled: (call) => {
+ console.log('Call cancelled:', call);
+ // Dismiss incoming call UI
+ },
+ onCallEndedMessageReceived: (call) => {
+ console.log('Call ended:', call);
+ // Handle call end
+ },
+ })
+);
+
+// Remove listener when done
+CometChat.removeCallListener(listenerId);
+```
+
+## Accept a Call
+
+Accept an incoming call:
+
+```tsx
+async function acceptCall(sessionId: string) {
+ try {
+ const call = await CometChat.acceptCall(sessionId);
+ console.log('Call accepted:', call);
+ // Start the call session with Calls SDK
+ return call;
+ } catch (error) {
+ console.error('Error accepting call:', error);
+ throw error;
+ }
+}
+```
+
+## Reject a Call
+
+Reject an incoming call:
+
+```tsx
+async function rejectCall(sessionId: string, status: string) {
+ try {
+ const call = await CometChat.rejectCall(sessionId, status);
+ console.log('Call rejected:', call);
+ return call;
+ } catch (error) {
+ console.error('Error rejecting call:', error);
+ throw error;
+ }
+}
+
+// Reject with status
+rejectCall(sessionId, CometChat.CALL_STATUS.REJECTED);
+
+// Mark as busy
+rejectCall(sessionId, CometChat.CALL_STATUS.BUSY);
+```
+
+## End a Call
+
+End an ongoing call:
+
+```tsx
+async function endCall(sessionId: string) {
+ try {
+ // End the call session
+ CometChatCalls.leaveSession();
+
+ // Notify other participants via Chat SDK
+ const call = await CometChat.endCall(sessionId);
+ console.log('Call ended:', call);
+ return call;
+ } catch (error) {
+ console.error('Error ending call:', error);
+ throw error;
+ }
+}
+```
+
+## Start Call Session After Accept
+
+After accepting a call, start the Calls SDK session:
+
+```tsx
+import { CometChatCalls } from '@cometchat/calls-sdk-react-native';
+
+async function startCallSession(call: any) {
+ const sessionId = call.getSessionId();
+
+ try {
+ // Generate token for the session
+ const { token } = await CometChatCalls.generateToken(sessionId);
+
+ // Create call settings
+ const isAudioOnly = call.getType() === CometChat.CALL_TYPE.AUDIO;
+
+ const callSettings = new CometChatCalls.CallSettingsBuilder()
+ .setIsAudioOnlyCall(isAudioOnly)
+ .setCallEventListener(new CometChatCalls.OngoingCallListener({
+ onCallEnded: () => {
+ console.log('Call ended');
+ },
+ }))
+ .build();
+
+ return { token, callSettings };
+ } catch (error) {
+ console.error('Error starting call session:', error);
+ throw error;
+ }
+}
+```
+
+## Complete Example
+
+```tsx
+import React, { useState, useEffect, useCallback } from 'react';
+import { View, Text, TouchableOpacity, StyleSheet, Modal } from 'react-native';
+import { CometChat } from '@cometchat/chat-sdk-react-native';
+import { CometChatCalls } from '@cometchat/calls-sdk-react-native';
+
+interface Call {
+ getSessionId: () => string;
+ getType: () => string;
+ getSender: () => { getName: () => string };
+}
+
+function CallManager({ children }: { children: React.ReactNode }) {
+ const [incomingCall, setIncomingCall] = useState(null);
+ const [activeCall, setActiveCall] = useState<{
+ token: string;
+ settings: any;
+ } | null>(null);
+
+ useEffect(() => {
+ const listenerId = 'call_manager_listener';
+
+ CometChat.addCallListener(
+ listenerId,
+ new CometChat.CallListener({
+ onIncomingCallReceived: (call: Call) => {
+ setIncomingCall(call);
+ },
+ onOutgoingCallAccepted: async (call: Call) => {
+ await startSession(call);
+ },
+ onOutgoingCallRejected: () => {
+ setActiveCall(null);
+ },
+ onIncomingCallCancelled: () => {
+ setIncomingCall(null);
+ },
+ onCallEndedMessageReceived: () => {
+ setActiveCall(null);
+ setIncomingCall(null);
+ },
+ })
+ );
+
+ return () => {
+ CometChat.removeCallListener(listenerId);
+ };
+ }, []);
+
+ const startSession = async (call: Call) => {
+ try {
+ const sessionId = call.getSessionId();
+ const { token } = await CometChatCalls.generateToken(sessionId);
+
+ const isAudioOnly = call.getType() === CometChat.CALL_TYPE.AUDIO;
+
+ const settings = new CometChatCalls.CallSettingsBuilder()
+ .setIsAudioOnlyCall(isAudioOnly)
+ .setCallEventListener(new CometChatCalls.OngoingCallListener({
+ onCallEnded: () => {
+ setActiveCall(null);
+ },
+ }))
+ .build();
+
+ setActiveCall({ token, settings });
+ setIncomingCall(null);
+ } catch (error) {
+ console.error('Error starting session:', error);
+ }
+ };
+
+ const handleAccept = async () => {
+ if (!incomingCall) return;
+
+ try {
+ const call = await CometChat.acceptCall(incomingCall.getSessionId());
+ await startSession(call);
+ } catch (error) {
+ console.error('Error accepting call:', error);
+ }
+ };
+
+ const handleReject = async () => {
+ if (!incomingCall) return;
+
+ try {
+ await CometChat.rejectCall(
+ incomingCall.getSessionId(),
+ CometChat.CALL_STATUS.REJECTED
+ );
+ setIncomingCall(null);
+ } catch (error) {
+ console.error('Error rejecting call:', error);
+ }
+ };
+
+ const handleEndCall = async () => {
+ if (!activeCall) return;
+
+ CometChatCalls.leaveSession();
+ setActiveCall(null);
+ };
+
+ return (
+
+ {children}
+
+ {/* Incoming Call Modal */}
+
+
+
+
+ {incomingCall?.getSender().getName()}
+
+
+ Incoming {incomingCall?.getType()} call
+
+
+
+ Decline
+
+
+ Accept
+
+
+
+
+
+
+ {/* Active Call */}
+ {activeCall && (
+
+
+
+
+
+ )}
+
+ );
+}
+
+const styles = StyleSheet.create({
+ container: {
+ flex: 1,
+ },
+ modalOverlay: {
+ flex: 1,
+ backgroundColor: 'rgba(0, 0, 0, 0.8)',
+ justifyContent: 'center',
+ alignItems: 'center',
+ },
+ incomingCallCard: {
+ backgroundColor: '#1a1a1a',
+ borderRadius: 20,
+ padding: 30,
+ alignItems: 'center',
+ width: '80%',
+ },
+ callerName: {
+ color: '#fff',
+ fontSize: 24,
+ fontWeight: '600',
+ marginBottom: 8,
+ },
+ callType: {
+ color: '#999',
+ fontSize: 16,
+ marginBottom: 30,
+ },
+ callActions: {
+ flexDirection: 'row',
+ gap: 20,
+ },
+ callButton: {
+ paddingHorizontal: 30,
+ paddingVertical: 15,
+ borderRadius: 30,
+ },
+ rejectButton: {
+ backgroundColor: '#ff4444',
+ },
+ acceptButton: {
+ backgroundColor: '#22c55e',
+ },
+ buttonText: {
+ color: '#fff',
+ fontSize: 16,
+ fontWeight: '600',
+ },
+ callContainer: {
+ flex: 1,
+ backgroundColor: '#000',
+ },
+});
+
+export default CallManager;
+```
+
+## Related Documentation
+
+- [VoIP Calling](/calls/react-native/voip-calling) - Push notifications for calls
+- [Join Session](/calls/react-native/join-session) - Start call sessions
+- [Background Handling](/calls/react-native/background-handling) - Handle background calls
diff --git a/calls/react-native/screen-sharing.mdx b/calls/react-native/screen-sharing.mdx
new file mode 100644
index 00000000..2b48c17b
--- /dev/null
+++ b/calls/react-native/screen-sharing.mdx
@@ -0,0 +1,252 @@
+---
+title: "Screen Sharing"
+sidebarTitle: "Screen Sharing"
+---
+
+The CometChat Calls SDK supports viewing screen shares from other participants. Screen sharing initiation on React Native requires platform-specific configuration.
+
+## View Screen Shares
+
+When a participant shares their screen, the SDK automatically displays it. Listen for screen share events:
+
+```tsx
+import { CometChatCalls } from '@cometchat/calls-sdk-react-native';
+
+CometChatCalls.addEventListener('onParticipantStartedScreenShare', (participant) => {
+ console.log(`${participant.name} started screen sharing`);
+});
+
+CometChatCalls.addEventListener('onParticipantStoppedScreenShare', (participant) => {
+ console.log(`${participant.name} stopped screen sharing`);
+});
+```
+
+## Start Screen Sharing
+
+Start sharing your screen:
+
+```tsx
+CometChatCalls.startScreenSharing();
+```
+
+## Stop Screen Sharing
+
+Stop sharing your screen:
+
+```tsx
+CometChatCalls.stopScreenSharing();
+```
+
+## Screen Share Events
+
+### Local Screen Share
+
+```tsx
+CometChatCalls.addEventListener('onScreenShareStarted', () => {
+ console.log('Screen sharing started');
+});
+
+CometChatCalls.addEventListener('onScreenShareStopped', () => {
+ console.log('Screen sharing stopped');
+});
+```
+
+### Button Click Event
+
+```tsx
+CometChatCalls.addEventListener('onScreenShareButtonClicked', () => {
+ console.log('Screen share button clicked');
+});
+```
+
+## iOS Configuration
+
+### Enable Broadcast Upload Extension
+
+To enable screen sharing on iOS, you need to add a Broadcast Upload Extension:
+
+1. In Xcode, go to **File > New > Target**
+2. Select **Broadcast Upload Extension**
+3. Name it (e.g., `ScreenShareExtension`)
+4. Set the same Team and Bundle Identifier prefix as your main app
+
+### Configure App Groups
+
+1. Select your main app target
+2. Go to **Signing & Capabilities**
+3. Add **App Groups** capability
+4. Create a new group (e.g., `group.com.yourcompany.yourapp`)
+5. Repeat for the Broadcast Upload Extension target
+
+### Update Info.plist
+
+Add to your extension's `Info.plist`:
+
+```xml
+NSExtension
+
+ NSExtensionPointIdentifier
+ com.apple.broadcast-services-upload
+ NSExtensionPrincipalClass
+ SampleHandler
+ RPBroadcastProcessMode
+ RPBroadcastProcessModeSampleBuffer
+
+```
+
+## Android Configuration
+
+### Add Permissions
+
+Add to your `AndroidManifest.xml`:
+
+```xml
+
+
+```
+
+### Configure Foreground Service
+
+For Android 10+, screen sharing requires a foreground service. Add to your `AndroidManifest.xml`:
+
+```xml
+
+```
+
+## Pin Screen Share
+
+Pin a participant's screen share to the main view:
+
+```tsx
+CometChatCalls.pinParticipant(participantId, 'screen-share');
+```
+
+## Layout Behavior
+
+When screen sharing starts:
+- The SDK automatically switches to Sidebar layout
+- The screen share is displayed in the main view
+- Other participants appear in the sidebar
+
+When screen sharing stops:
+- The layout returns to the previous state (if it was programmatically set)
+
+## Complete Example
+
+```tsx
+import React, { useState, useEffect } from 'react';
+import { View, TouchableOpacity, Text, StyleSheet, Alert } from 'react-native';
+import { CometChatCalls } from '@cometchat/calls-sdk-react-native';
+
+function ScreenShareControls() {
+ const [isSharing, setIsSharing] = useState(false);
+ const [remoteSharer, setRemoteSharer] = useState(null);
+
+ useEffect(() => {
+ const unsubscribeStart = CometChatCalls.addEventListener(
+ 'onScreenShareStarted',
+ () => {
+ setIsSharing(true);
+ }
+ );
+
+ const unsubscribeStop = CometChatCalls.addEventListener(
+ 'onScreenShareStopped',
+ () => {
+ setIsSharing(false);
+ }
+ );
+
+ const unsubscribeRemoteStart = CometChatCalls.addEventListener(
+ 'onParticipantStartedScreenShare',
+ (participant) => {
+ setRemoteSharer(participant.name);
+ }
+ );
+
+ const unsubscribeRemoteStop = CometChatCalls.addEventListener(
+ 'onParticipantStoppedScreenShare',
+ () => {
+ setRemoteSharer(null);
+ }
+ );
+
+ return () => {
+ unsubscribeStart();
+ unsubscribeStop();
+ unsubscribeRemoteStart();
+ unsubscribeRemoteStop();
+ };
+ }, []);
+
+ const toggleScreenShare = () => {
+ if (isSharing) {
+ CometChatCalls.stopScreenSharing();
+ } else {
+ if (remoteSharer) {
+ Alert.alert(
+ 'Screen Share Active',
+ `${remoteSharer} is currently sharing their screen.`
+ );
+ return;
+ }
+ CometChatCalls.startScreenSharing();
+ }
+ };
+
+ return (
+
+ {remoteSharer && (
+
+ {remoteSharer} is sharing their screen
+
+ )}
+
+
+ {isSharing ? 'Stop Sharing' : 'Share Screen'}
+
+
+
+ );
+}
+
+const styles = StyleSheet.create({
+ container: {
+ padding: 16,
+ alignItems: 'center',
+ },
+ sharingText: {
+ color: '#6851D6',
+ fontSize: 14,
+ marginBottom: 8,
+ },
+ button: {
+ backgroundColor: '#333',
+ paddingHorizontal: 20,
+ paddingVertical: 12,
+ borderRadius: 8,
+ },
+ activeButton: {
+ backgroundColor: '#6851D6',
+ },
+ buttonText: {
+ color: '#fff',
+ fontSize: 14,
+ fontWeight: '600',
+ },
+});
+
+export default ScreenShareControls;
+```
+
+## Related Documentation
+
+- [Call Layouts](/calls/react-native/call-layouts) - Layout behavior during screen share
+- [Participant Management](/calls/react-native/participant-management) - Pin screen shares
+- [Events](/calls/react-native/events) - Screen share events
diff --git a/calls/react-native/session-settings.mdx b/calls/react-native/session-settings.mdx
new file mode 100644
index 00000000..4102e94f
--- /dev/null
+++ b/calls/react-native/session-settings.mdx
@@ -0,0 +1,261 @@
+---
+title: "Session Settings"
+sidebarTitle: "Session Settings"
+---
+
+Configure call sessions using the `CallSettingsBuilder` class. This allows you to customize the call UI, behavior, and event handling.
+
+## CallSettingsBuilder
+
+The `CallSettingsBuilder` provides a fluent API to configure call sessions:
+
+```tsx
+import { CometChatCalls } from '@cometchat/calls-sdk-react-native';
+
+const callSettings = new CometChatCalls.CallSettingsBuilder()
+ .enableDefaultLayout(true)
+ .setIsAudioOnlyCall(false)
+ .setMode(CometChatCalls.CALL_MODE.DEFAULT)
+ .showEndCallButton(true)
+ .showMuteAudioButton(true)
+ .showPauseVideoButton(true)
+ .showSwitchCameraButton(true)
+ .showAudioModeButton(true)
+ .startWithAudioMuted(false)
+ .startWithVideoMuted(false)
+ .setDefaultAudioMode(CometChatCalls.AUDIO_MODE.SPEAKER)
+ .showRecordingButton(false)
+ .startRecordingOnCallStart(false)
+ .setIdleTimeoutPeriod(180)
+ .enableVideoTileClick(true)
+ .enableVideoTileDrag(true)
+ .setCallEventListener(callListener)
+ .build();
+```
+
+## Configuration Options
+
+### Call Type
+
+| Method | Type | Default | Description |
+|--------|------|---------|-------------|
+| `setIsAudioOnlyCall(isAudioOnly)` | boolean | `false` | Set to `true` for audio-only calls |
+
+```tsx
+// Audio-only call
+const audioCallSettings = new CometChatCalls.CallSettingsBuilder()
+ .setIsAudioOnlyCall(true)
+ .build();
+
+// Video call (default)
+const videoCallSettings = new CometChatCalls.CallSettingsBuilder()
+ .setIsAudioOnlyCall(false)
+ .build();
+```
+
+### Layout Mode
+
+| Method | Type | Default | Description |
+|--------|------|---------|-------------|
+| `setMode(mode)` | string | `DEFAULT` | Call layout mode |
+
+Available modes:
+
+| Mode | Description |
+|------|-------------|
+| `CometChatCalls.CALL_MODE.DEFAULT` | Grid layout with all participants visible |
+| `CometChatCalls.CALL_MODE.SPOTLIGHT` | Focus on one participant with others in sidebar |
+
+```tsx
+const callSettings = new CometChatCalls.CallSettingsBuilder()
+ .setMode(CometChatCalls.CALL_MODE.SPOTLIGHT)
+ .build();
+```
+
+### UI Controls
+
+| Method | Type | Default | Description |
+|--------|------|---------|-------------|
+| `enableDefaultLayout(enabled)` | boolean | `true` | Show/hide the default button layout |
+| `showEndCallButton(show)` | boolean | `true` | Show/hide the end call button |
+| `showMuteAudioButton(show)` | boolean | `true` | Show/hide the mute audio button |
+| `showPauseVideoButton(show)` | boolean | `true` | Show/hide the pause video button |
+| `showSwitchCameraButton(show)` | boolean | `true` | Show/hide the switch camera button |
+| `showAudioModeButton(show)` | boolean | `true` | Show/hide the audio mode button |
+| `showRecordingButton(show)` | boolean | `false` | Show/hide the recording button |
+
+```tsx
+const callSettings = new CometChatCalls.CallSettingsBuilder()
+ .enableDefaultLayout(true)
+ .showEndCallButton(true)
+ .showMuteAudioButton(true)
+ .showPauseVideoButton(true)
+ .showSwitchCameraButton(true)
+ .showAudioModeButton(true)
+ .showRecordingButton(true)
+ .build();
+```
+
+### Initial State
+
+| Method | Type | Default | Description |
+|--------|------|---------|-------------|
+| `startWithAudioMuted(muted)` | boolean | `false` | Start call with audio muted |
+| `startWithVideoMuted(muted)` | boolean | `false` | Start call with video paused |
+| `setDefaultAudioMode(mode)` | string | - | Set the default audio output mode |
+
+Available audio modes:
+
+| Mode | Description |
+|------|-------------|
+| `CometChatCalls.AUDIO_MODE.SPEAKER` | Phone speaker |
+| `CometChatCalls.AUDIO_MODE.EARPIECE` | Phone earpiece |
+| `CometChatCalls.AUDIO_MODE.BLUETOOTH` | Bluetooth device |
+| `CometChatCalls.AUDIO_MODE.HEADPHONES` | Wired headphones |
+
+```tsx
+const callSettings = new CometChatCalls.CallSettingsBuilder()
+ .startWithAudioMuted(true)
+ .startWithVideoMuted(false)
+ .setDefaultAudioMode(CometChatCalls.AUDIO_MODE.SPEAKER)
+ .build();
+```
+
+### Recording
+
+| Method | Type | Default | Description |
+|--------|------|---------|-------------|
+| `showRecordingButton(show)` | boolean | `false` | Show/hide the recording button |
+| `startRecordingOnCallStart(start)` | boolean | `false` | Auto-start recording when call begins |
+
+```tsx
+const callSettings = new CometChatCalls.CallSettingsBuilder()
+ .showRecordingButton(true)
+ .startRecordingOnCallStart(true)
+ .build();
+```
+
+### Idle Timeout
+
+| Method | Type | Default | Description |
+|--------|------|---------|-------------|
+| `setIdleTimeoutPeriod(seconds)` | number | `180` | Seconds before auto-ending when alone |
+
+```tsx
+const callSettings = new CometChatCalls.CallSettingsBuilder()
+ .setIdleTimeoutPeriod(300) // 5 minutes
+ .build();
+```
+
+### Video Tile Interaction
+
+| Method | Type | Default | Description |
+|--------|------|---------|-------------|
+| `enableVideoTileClick(enabled)` | boolean | `true` | Enable clicking video tiles in Spotlight mode |
+| `enableVideoTileDrag(enabled)` | boolean | `true` | Enable dragging video tiles in Spotlight mode |
+
+```tsx
+const callSettings = new CometChatCalls.CallSettingsBuilder()
+ .enableVideoTileClick(true)
+ .enableVideoTileDrag(true)
+ .build();
+```
+
+## Event Listener
+
+Set up an event listener to handle call events:
+
+```tsx
+const callListener = new CometChatCalls.OngoingCallListener({
+ onUserJoined: (user) => {
+ console.log('User joined:', user);
+ },
+ onUserLeft: (user) => {
+ console.log('User left:', user);
+ },
+ onUserListUpdated: (userList) => {
+ console.log('User list updated:', userList);
+ },
+ onCallEnded: () => {
+ console.log('Call ended');
+ },
+ onCallEndButtonPressed: () => {
+ console.log('End call button pressed');
+ },
+ onError: (error) => {
+ console.error('Call error:', error);
+ },
+ onAudioModesUpdated: (audioModes) => {
+ console.log('Audio modes updated:', audioModes);
+ },
+ onRecordingStarted: (data) => {
+ console.log('Recording started:', data);
+ },
+ onRecordingStopped: (data) => {
+ console.log('Recording stopped:', data);
+ },
+ onUserMuted: (user) => {
+ console.log('User muted:', user);
+ },
+ onSessionTimeout: () => {
+ console.log('Session timed out');
+ },
+});
+
+const callSettings = new CometChatCalls.CallSettingsBuilder()
+ .setCallEventListener(callListener)
+ .build();
+```
+
+## Complete Example
+
+```tsx
+import { CometChatCalls } from '@cometchat/calls-sdk-react-native';
+
+function createCallSettings(isAudioOnly: boolean = false) {
+ const callListener = new CometChatCalls.OngoingCallListener({
+ onUserJoined: (user) => {
+ console.log('User joined:', user.name);
+ },
+ onUserLeft: (user) => {
+ console.log('User left:', user.name);
+ },
+ onCallEnded: () => {
+ console.log('Call ended');
+ // Navigate back or update UI
+ },
+ onError: (error) => {
+ console.error('Call error:', error.errorDescription);
+ },
+ });
+
+ return new CometChatCalls.CallSettingsBuilder()
+ .enableDefaultLayout(true)
+ .setIsAudioOnlyCall(isAudioOnly)
+ .setMode(CometChatCalls.CALL_MODE.DEFAULT)
+ .showEndCallButton(true)
+ .showMuteAudioButton(true)
+ .showPauseVideoButton(!isAudioOnly)
+ .showSwitchCameraButton(!isAudioOnly)
+ .showAudioModeButton(true)
+ .startWithAudioMuted(false)
+ .startWithVideoMuted(false)
+ .setDefaultAudioMode(CometChatCalls.AUDIO_MODE.SPEAKER)
+ .showRecordingButton(true)
+ .setIdleTimeoutPeriod(180)
+ .setCallEventListener(callListener)
+ .build();
+}
+
+// Create video call settings
+const videoCallSettings = createCallSettings(false);
+
+// Create audio call settings
+const audioCallSettings = createCallSettings(true);
+```
+
+## Related Documentation
+
+- [Join Session](/calls/react-native/join-session) - Start a call with these settings
+- [Events](/calls/react-native/events) - Detailed event documentation
+- [Actions](/calls/react-native/actions) - Control the call programmatically
diff --git a/calls/react-native/setup.mdx b/calls/react-native/setup.mdx
new file mode 100644
index 00000000..4924117d
--- /dev/null
+++ b/calls/react-native/setup.mdx
@@ -0,0 +1,143 @@
+---
+title: "Setup"
+sidebarTitle: "Setup"
+---
+
+This guide walks you through installing the CometChat Calls SDK and configuring it in your React Native application.
+
+## Add the CometChat Dependency
+
+### Using npm
+
+```bash
+npm install @cometchat/calls-sdk-react-native
+```
+
+### Using Yarn
+
+```bash
+yarn add @cometchat/calls-sdk-react-native
+```
+
+## iOS Configuration
+
+### Install CocoaPods Dependencies
+
+Navigate to your iOS directory and install the pods:
+
+```bash
+cd ios
+pod install
+cd ..
+```
+
+### Add Permissions
+
+Add the required permissions to your `ios/YourApp/Info.plist`:
+
+```xml
+NSCameraUsageDescription
+Camera access is required for video calls
+NSMicrophoneUsageDescription
+Microphone access is required for voice and video calls
+```
+
+### Enable Background Modes
+
+For calls to continue when the app is in the background:
+
+1. Open your project in Xcode
+2. Select your target and go to **Signing & Capabilities**
+3. Click **+ Capability** and add **Background Modes**
+4. Enable:
+ - **Audio, AirPlay, and Picture in Picture**
+ - **Voice over IP** (if using VoIP push notifications)
+
+## Android Configuration
+
+### Add Repository
+
+Add the CometChat repository to your **project level** `android/build.gradle`:
+
+```groovy
+allprojects {
+ repositories {
+ google()
+ mavenCentral()
+ maven {
+ url "https://dl.cloudsmith.io/public/cometchat/cometchat/maven/"
+ }
+ }
+}
+```
+
+### Configure Java Version
+
+Add Java 8 compatibility to your **app level** `android/app/build.gradle`:
+
+```groovy
+android {
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+}
+```
+
+### Add Permissions
+
+Add the required permissions to your `android/app/src/main/AndroidManifest.xml`:
+
+```xml
+
+
+
+
+
+
+
+```
+
+
+For Android 6.0 (API level 23) and above, you must request camera and microphone permissions at runtime before starting a call.
+
+
+### Request Runtime Permissions
+
+Use a library like `react-native-permissions` or implement native permission requests:
+
+```tsx
+import { PermissionsAndroid, Platform } from 'react-native';
+
+async function requestCallPermissions(): Promise {
+ if (Platform.OS === 'android') {
+ const granted = await PermissionsAndroid.requestMultiple([
+ PermissionsAndroid.PERMISSIONS.CAMERA,
+ PermissionsAndroid.PERMISSIONS.RECORD_AUDIO,
+ ]);
+
+ return (
+ granted['android.permission.CAMERA'] === PermissionsAndroid.RESULTS.GRANTED &&
+ granted['android.permission.RECORD_AUDIO'] === PermissionsAndroid.RESULTS.GRANTED
+ );
+ }
+ return true;
+}
+```
+
+## Verify Installation
+
+After installation, rebuild your app:
+
+```bash
+# iOS
+npx react-native run-ios
+
+# Android
+npx react-native run-android
+```
+
+## Related Documentation
+
+- [Authentication](/calls/react-native/authentication) - Initialize the SDK and authenticate users
+- [Join Session](/calls/react-native/join-session) - Start your first call
diff --git a/calls/react-native/share-invite.mdx b/calls/react-native/share-invite.mdx
new file mode 100644
index 00000000..c1f04d6c
--- /dev/null
+++ b/calls/react-native/share-invite.mdx
@@ -0,0 +1,360 @@
+---
+title: "Share Invite"
+sidebarTitle: "Share Invite"
+---
+
+Allow users to share call invite links with others. When the share invite button is clicked, you can generate and share a link that others can use to join the call.
+
+## Listen for Share Invite Button Click
+
+Handle when the user clicks the share invite button:
+
+```tsx
+import { CometChatCalls } from '@cometchat/calls-sdk-react-native';
+
+CometChatCalls.addEventListener('onShareInviteButtonClicked', () => {
+ console.log('Share invite button clicked');
+ // Generate and share invite link
+});
+```
+
+## Generate Invite Link
+
+Create an invite link that others can use to join the call:
+
+```tsx
+function generateInviteLink(sessionId: string): string {
+ // Use your app's deep link scheme
+ const baseUrl = 'https://yourapp.com/call';
+ return `${baseUrl}?sessionId=${encodeURIComponent(sessionId)}`;
+}
+```
+
+## Share Using React Native Share
+
+Use the built-in Share API to share the invite:
+
+```tsx
+import { Share } from 'react-native';
+
+async function shareInvite(sessionId: string) {
+ const inviteLink = generateInviteLink(sessionId);
+
+ try {
+ const result = await Share.share({
+ message: `Join my call: ${inviteLink}`,
+ title: 'Join Call',
+ url: inviteLink, // iOS only
+ });
+
+ if (result.action === Share.sharedAction) {
+ if (result.activityType) {
+ console.log('Shared with activity type:', result.activityType);
+ } else {
+ console.log('Shared successfully');
+ }
+ } else if (result.action === Share.dismissedAction) {
+ console.log('Share dismissed');
+ }
+ } catch (error) {
+ console.error('Error sharing:', error);
+ }
+}
+```
+
+## Handle Deep Links
+
+Configure your app to handle incoming deep links:
+
+### iOS Configuration
+
+Add URL scheme to `ios/YourApp/Info.plist`:
+
+```xml
+CFBundleURLTypes
+
+
+ CFBundleURLSchemes
+
+ yourapp
+
+
+
+```
+
+### Android Configuration
+
+Add intent filter to `android/app/src/main/AndroidManifest.xml`:
+
+```xml
+
+
+
+
+
+
+
+
+
+```
+
+### Handle Incoming Links
+
+```tsx
+import { useEffect } from 'react';
+import { Linking } from 'react-native';
+
+function useDeepLinks(onJoinCall: (sessionId: string) => void) {
+ useEffect(() => {
+ // Handle initial URL (app opened via link)
+ Linking.getInitialURL().then((url) => {
+ if (url) {
+ handleDeepLink(url);
+ }
+ });
+
+ // Handle URL when app is already open
+ const subscription = Linking.addEventListener('url', (event) => {
+ handleDeepLink(event.url);
+ });
+
+ return () => {
+ subscription.remove();
+ };
+ }, []);
+
+ const handleDeepLink = (url: string) => {
+ try {
+ const parsedUrl = new URL(url);
+ const sessionId = parsedUrl.searchParams.get('sessionId');
+
+ if (sessionId) {
+ onJoinCall(sessionId);
+ }
+ } catch (error) {
+ console.error('Error parsing deep link:', error);
+ }
+ };
+}
+
+export default useDeepLinks;
+```
+
+## Complete Example
+
+```tsx
+import React, { useState, useEffect, useCallback } from 'react';
+import { View, TouchableOpacity, Text, StyleSheet, Share, Alert } from 'react-native';
+import Clipboard from '@react-native-clipboard/clipboard';
+import { CometChatCalls } from '@cometchat/calls-sdk-react-native';
+
+interface ShareInviteProps {
+ sessionId: string;
+}
+
+function ShareInvite({ sessionId }: ShareInviteProps) {
+ const [showOptions, setShowOptions] = useState(false);
+
+ useEffect(() => {
+ const unsubscribe = CometChatCalls.addEventListener(
+ 'onShareInviteButtonClicked',
+ () => {
+ setShowOptions(true);
+ }
+ );
+
+ return () => unsubscribe();
+ }, []);
+
+ const generateInviteLink = useCallback(() => {
+ return `https://yourapp.com/call?sessionId=${encodeURIComponent(sessionId)}`;
+ }, [sessionId]);
+
+ const handleShare = async () => {
+ const inviteLink = generateInviteLink();
+
+ try {
+ await Share.share({
+ message: `Join my video call!\n\n${inviteLink}`,
+ title: 'Join Call',
+ });
+ } catch (error) {
+ console.error('Error sharing:', error);
+ }
+
+ setShowOptions(false);
+ };
+
+ const handleCopyLink = () => {
+ const inviteLink = generateInviteLink();
+ Clipboard.setString(inviteLink);
+
+ Alert.alert('Link Copied', 'The invite link has been copied to your clipboard.');
+ setShowOptions(false);
+ };
+
+ if (!showOptions) {
+ return (
+ setShowOptions(true)}
+ >
+ 🔗 Share
+
+ );
+ }
+
+ return (
+
+
+ Share Invite
+
+
+ 📤
+ Share via...
+
+
+
+ 📋
+ Copy Link
+
+
+ setShowOptions(false)}
+ >
+ Cancel
+
+
+
+ );
+}
+
+const styles = StyleSheet.create({
+ shareButton: {
+ backgroundColor: '#333',
+ paddingHorizontal: 16,
+ paddingVertical: 12,
+ borderRadius: 8,
+ },
+ shareButtonText: {
+ color: '#fff',
+ fontSize: 14,
+ fontWeight: '600',
+ },
+ optionsContainer: {
+ position: 'absolute',
+ top: 0,
+ left: 0,
+ right: 0,
+ bottom: 0,
+ backgroundColor: 'rgba(0, 0, 0, 0.5)',
+ justifyContent: 'center',
+ alignItems: 'center',
+ },
+ optionsCard: {
+ backgroundColor: '#1a1a1a',
+ borderRadius: 16,
+ padding: 20,
+ width: '80%',
+ maxWidth: 300,
+ },
+ optionsTitle: {
+ color: '#fff',
+ fontSize: 18,
+ fontWeight: '600',
+ textAlign: 'center',
+ marginBottom: 20,
+ },
+ optionButton: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ backgroundColor: '#333',
+ padding: 16,
+ borderRadius: 8,
+ marginBottom: 12,
+ },
+ optionIcon: {
+ fontSize: 20,
+ marginRight: 12,
+ },
+ optionText: {
+ color: '#fff',
+ fontSize: 16,
+ },
+ cancelButton: {
+ padding: 16,
+ alignItems: 'center',
+ },
+ cancelText: {
+ color: '#6851D6',
+ fontSize: 16,
+ fontWeight: '600',
+ },
+});
+
+export default ShareInvite;
+```
+
+## Universal Links (iOS)
+
+For a better user experience, configure Universal Links:
+
+1. Create an `apple-app-site-association` file on your server:
+
+```json
+{
+ "applinks": {
+ "apps": [],
+ "details": [
+ {
+ "appID": "TEAM_ID.com.yourcompany.yourapp",
+ "paths": ["/call/*"]
+ }
+ ]
+ }
+}
+```
+
+2. Host it at `https://yourapp.com/.well-known/apple-app-site-association`
+
+3. Add Associated Domains capability in Xcode:
+ - `applinks:yourapp.com`
+
+## App Links (Android)
+
+Configure App Links for Android:
+
+1. Create a `assetlinks.json` file:
+
+```json
+[{
+ "relation": ["delegate_permission/common.handle_all_urls"],
+ "target": {
+ "namespace": "android_app",
+ "package_name": "com.yourcompany.yourapp",
+ "sha256_cert_fingerprints": ["YOUR_SHA256_FINGERPRINT"]
+ }
+}]
+```
+
+2. Host it at `https://yourapp.com/.well-known/assetlinks.json`
+
+3. Update your intent filter with `autoVerify`:
+
+```xml
+
+
+
+
+
+
+```
+
+## Related Documentation
+
+- [Join Session](/calls/react-native/join-session) - Join calls via invite links
+- [Events](/calls/react-native/events) - Share invite button events
+- [Ringing](/calls/react-native/ringing) - Call notifications
diff --git a/calls/react-native/voip-calling.mdx b/calls/react-native/voip-calling.mdx
new file mode 100644
index 00000000..3fe65c95
--- /dev/null
+++ b/calls/react-native/voip-calling.mdx
@@ -0,0 +1,291 @@
+---
+title: "VoIP Calling"
+sidebarTitle: "VoIP Calling"
+---
+
+Implement VoIP push notifications to receive incoming calls even when your app is in the background or terminated. This requires platform-specific configuration for iOS CallKit and Android ConnectionService.
+
+## iOS VoIP Configuration
+
+### Enable VoIP Push Notifications
+
+1. In Xcode, select your target
+2. Go to **Signing & Capabilities**
+3. Add **Push Notifications** capability
+4. Add **Background Modes** capability
+5. Enable **Voice over IP**
+
+### Create VoIP Certificate
+
+1. Go to [Apple Developer Portal](https://developer.apple.com)
+2. Navigate to **Certificates, Identifiers & Profiles**
+3. Create a new **VoIP Services Certificate**
+4. Download and install the certificate
+5. Export the `.p12` file for your server
+
+### Configure CometChat Dashboard
+
+1. Go to your CometChat Dashboard
+2. Navigate to **Notifications > Push Notifications**
+3. Upload your VoIP certificate (`.p12` file)
+4. Configure the certificate password
+
+### Implement CallKit
+
+Create a native module to handle CallKit:
+
+```swift
+// ios/CallKitManager.swift
+import CallKit
+import PushKit
+
+@objc(CallKitManager)
+class CallKitManager: NSObject, CXProviderDelegate, PKPushRegistryDelegate {
+
+ static let shared = CallKitManager()
+
+ private let provider: CXProvider
+ private let callController = CXCallController()
+ private var voipRegistry: PKPushRegistry?
+
+ override init() {
+ let config = CXProviderConfiguration()
+ config.supportsVideo = true
+ config.maximumCallsPerCallGroup = 1
+ config.supportedHandleTypes = [.generic]
+
+ provider = CXProvider(configuration: config)
+ super.init()
+ provider.setDelegate(self, queue: nil)
+ }
+
+ @objc func registerForVoIPPushes() {
+ voipRegistry = PKPushRegistry(queue: .main)
+ voipRegistry?.delegate = self
+ voipRegistry?.desiredPushTypes = [.voIP]
+ }
+
+ // PKPushRegistryDelegate
+ func pushRegistry(_ registry: PKPushRegistry, didUpdate pushCredentials: PKPushCredentials, for type: PKPushType) {
+ let token = pushCredentials.token.map { String(format: "%02x", $0) }.joined()
+ // Send token to CometChat
+ NotificationCenter.default.post(
+ name: NSNotification.Name("VoIPTokenReceived"),
+ object: nil,
+ userInfo: ["token": token]
+ )
+ }
+
+ func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, for type: PKPushType, completion: @escaping () -> Void) {
+ guard type == .voIP else { return }
+
+ let callerId = payload.dictionaryPayload["callerId"] as? String ?? "Unknown"
+ let callerName = payload.dictionaryPayload["callerName"] as? String ?? "Unknown"
+ let sessionId = payload.dictionaryPayload["sessionId"] as? String ?? ""
+ let hasVideo = payload.dictionaryPayload["hasVideo"] as? Bool ?? false
+
+ reportIncomingCall(
+ uuid: UUID(),
+ handle: callerId,
+ callerName: callerName,
+ hasVideo: hasVideo
+ ) { error in
+ completion()
+ }
+ }
+
+ func reportIncomingCall(uuid: UUID, handle: String, callerName: String, hasVideo: Bool, completion: @escaping (Error?) -> Void) {
+ let update = CXCallUpdate()
+ update.remoteHandle = CXHandle(type: .generic, value: handle)
+ update.localizedCallerName = callerName
+ update.hasVideo = hasVideo
+
+ provider.reportNewIncomingCall(with: uuid, update: update) { error in
+ completion(error)
+ }
+ }
+
+ // CXProviderDelegate
+ func providerDidReset(_ provider: CXProvider) {}
+
+ func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) {
+ // Notify React Native to accept the call
+ NotificationCenter.default.post(
+ name: NSNotification.Name("CallKitAnswerCall"),
+ object: nil,
+ userInfo: ["callUUID": action.callUUID.uuidString]
+ )
+ action.fulfill()
+ }
+
+ func provider(_ provider: CXProvider, perform action: CXEndCallAction) {
+ // Notify React Native to end the call
+ NotificationCenter.default.post(
+ name: NSNotification.Name("CallKitEndCall"),
+ object: nil,
+ userInfo: ["callUUID": action.callUUID.uuidString]
+ )
+ action.fulfill()
+ }
+}
+```
+
+## Android VoIP Configuration
+
+### Add Firebase Cloud Messaging
+
+1. Add Firebase to your Android project
+2. Add the FCM dependency to `android/app/build.gradle`:
+
+```groovy
+dependencies {
+ implementation 'com.google.firebase:firebase-messaging:23.0.0'
+}
+```
+
+### Configure CometChat Dashboard
+
+1. Go to your CometChat Dashboard
+2. Navigate to **Notifications > Push Notifications**
+3. Upload your Firebase Server Key
+
+### Implement ConnectionService
+
+Create a ConnectionService for incoming calls:
+
+```java
+// android/app/src/main/java/com/yourapp/CallConnectionService.java
+package com.yourapp;
+
+import android.telecom.Connection;
+import android.telecom.ConnectionRequest;
+import android.telecom.ConnectionService;
+import android.telecom.PhoneAccountHandle;
+import android.telecom.TelecomManager;
+
+public class CallConnectionService extends ConnectionService {
+
+ @Override
+ public Connection onCreateIncomingConnection(
+ PhoneAccountHandle connectionManagerPhoneAccount,
+ ConnectionRequest request) {
+
+ CallConnection connection = new CallConnection();
+ connection.setConnectionProperties(Connection.PROPERTY_SELF_MANAGED);
+ connection.setCallerDisplayName(
+ request.getExtras().getString("callerName"),
+ TelecomManager.PRESENTATION_ALLOWED
+ );
+ connection.setRinging();
+
+ return connection;
+ }
+
+ @Override
+ public Connection onCreateOutgoingConnection(
+ PhoneAccountHandle connectionManagerPhoneAccount,
+ ConnectionRequest request) {
+
+ CallConnection connection = new CallConnection();
+ connection.setConnectionProperties(Connection.PROPERTY_SELF_MANAGED);
+ connection.setDialing();
+
+ return connection;
+ }
+}
+```
+
+### Register ConnectionService
+
+Add to `AndroidManifest.xml`:
+
+```xml
+
+
+
+
+
+```
+
+### Add Permissions
+
+```xml
+
+
+```
+
+## Register Push Token
+
+Register the VoIP/FCM token with CometChat:
+
+```tsx
+import { CometChat } from '@cometchat/chat-sdk-react-native';
+
+async function registerPushToken(token: string, platform: 'ios' | 'android') {
+ try {
+ if (platform === 'ios') {
+ await CometChat.registerTokenForPushNotification(token, {
+ voip: true,
+ });
+ } else {
+ await CometChat.registerTokenForPushNotification(token);
+ }
+ console.log('Push token registered');
+ } catch (error) {
+ console.error('Error registering push token:', error);
+ }
+}
+```
+
+## Handle Incoming VoIP Push
+
+```tsx
+import { useEffect } from 'react';
+import { NativeEventEmitter, NativeModules, Platform } from 'react-native';
+import { CometChat } from '@cometchat/chat-sdk-react-native';
+import { CometChatCalls } from '@cometchat/calls-sdk-react-native';
+
+function useVoIPPush() {
+ useEffect(() => {
+ if (Platform.OS === 'ios') {
+ const eventEmitter = new NativeEventEmitter(NativeModules.CallKitManager);
+
+ const answerSubscription = eventEmitter.addListener(
+ 'CallKitAnswerCall',
+ async (data) => {
+ // Accept the call via Chat SDK
+ const sessionId = data.sessionId;
+ await CometChat.acceptCall(sessionId);
+
+ // Start the call session
+ const { token } = await CometChatCalls.generateToken(sessionId);
+ // Navigate to call screen with token
+ }
+ );
+
+ const endSubscription = eventEmitter.addListener(
+ 'CallKitEndCall',
+ async (data) => {
+ CometChatCalls.leaveSession();
+ }
+ );
+
+ return () => {
+ answerSubscription.remove();
+ endSubscription.remove();
+ };
+ }
+ }, []);
+}
+
+export default useVoIPPush;
+```
+
+## Related Documentation
+
+- [Ringing](/calls/react-native/ringing) - Implement call notifications
+- [Background Handling](/calls/react-native/background-handling) - Keep calls active in background
+- [Setup](/calls/react-native/setup) - Initial SDK setup
diff --git a/docs.json b/docs.json
index 8f9b6b5d..36dccdbe 100644
--- a/docs.json
+++ b/docs.json
@@ -42,10 +42,10 @@
]
},
{
- "product": "Chat & Calling",
+ "product": "Chat & Messaging",
"tabs": [
{
- "tab": "Chat & Calling",
+ "tab": "Chat",
"pages": [
"chat-call"
]
@@ -416,7 +416,8 @@
"ui-kit/react/astro-one-to-one-chat",
"ui-kit/react/astro-tab-based-chat"
]
- }
+ },
+ "ui-kit/react/calling-integration"
]
},
{
@@ -843,7 +844,13 @@
"group": " ",
"pages": [
"ui-kit/react-native/overview",
- "ui-kit/react-native/getting-started",
+ {
+ "group": "Getting Started",
+ "pages": [
+ "ui-kit/react-native/getting-started",
+ "ui-kit/react-native/calling-integration"
+ ]
+ },
{
"group": "Features",
"pages": [
@@ -1149,7 +1156,8 @@
"ui-kit/ios/getting-started",
"ui-kit/ios/ios-conversation",
"ui-kit/ios/ios-one-to-one-chat",
- "ui-kit/ios/ios-tab-based-chat"
+ "ui-kit/ios/ios-tab-based-chat",
+ "ui-kit/ios/calling-integration"
]
},
{
@@ -1468,7 +1476,8 @@
"ui-kit/android/getting-started",
"ui-kit/android/android-conversation",
"ui-kit/android/android-one-to-one-chat",
- "ui-kit/android/android-tab-based-chat"
+ "ui-kit/android/android-tab-based-chat",
+ "ui-kit/android/calling-integration"
]
},
{
@@ -1787,7 +1796,8 @@
"ui-kit/flutter/getting-started",
"ui-kit/flutter/flutter-conversation",
"ui-kit/flutter/flutter-one-to-one-chat",
- "ui-kit/flutter/flutter-tab-based-chat"
+ "ui-kit/flutter/flutter-tab-based-chat",
+ "ui-kit/flutter/calling-integration"
]
},
{
@@ -4813,6 +4823,278 @@
}
]
},
+ {
+ "product": "Voice & Video Calling",
+ "tabs": [
+ {
+ "tab": "Calling",
+ "pages": [
+ "calls"
+ ]
+ },
+ {
+ "tab": "Platform",
+ "pages": [
+ "calls/platform/overview",
+ "calls/platform/features",
+ "calls/platform/compatibility",
+ "calls/platform/user-sync"
+ ]
+ },
+ {
+ "tab": "SDK",
+ "tab-id": "calls-sdk",
+ "dropdowns": [
+ {
+ "dropdown": "JavaScript",
+ "icon": "/images/icons/js.svg",
+ "pages": [
+ {
+ "group": "Overview",
+ "pages": [
+ "calls/javascript/overview"
+ ]
+ },
+ {
+ "group": "Integrations",
+ "pages": [
+ "calls/javascript/react-integration",
+ "calls/javascript/vue-integration",
+ "calls/javascript/angular-integration",
+ "calls/javascript/nextjs-integration",
+ "calls/javascript/ionic-integration"
+ ]
+ },
+ {
+ "group": "Getting Started",
+ "pages": [
+ "calls/javascript/setup",
+ "calls/javascript/authentication"
+ ]
+ },
+ {
+ "group": "Call Session",
+ "pages": [
+ "calls/javascript/session-settings",
+ "calls/javascript/join-session",
+ "calls/javascript/actions",
+ "calls/javascript/events"
+ ]
+ },
+ {
+ "group": "Features",
+ "pages": [
+ "calls/javascript/ringing",
+ "calls/javascript/call-layouts",
+ "calls/javascript/recording",
+ "calls/javascript/call-logs",
+ "calls/javascript/participant-management",
+ "calls/javascript/screen-sharing",
+ "calls/javascript/virtual-background",
+ "calls/javascript/picture-in-picture",
+ "calls/javascript/raise-hand",
+ "calls/javascript/idle-timeout"
+ ]
+ },
+ {
+ "group": "Advanced",
+ "pages": [
+ "calls/javascript/custom-control-panel",
+ "calls/javascript/device-management",
+ "calls/javascript/permissions-handling",
+ "calls/javascript/in-call-chat",
+ "calls/javascript/share-invite"
+ ]
+ }
+ ]
+ },
+ {
+ "dropdown": "React Native",
+ "icon": "/images/icons/react.svg",
+ "pages": [
+ {
+ "group": "Overview",
+ "pages": [
+ "calls/react-native/overview"
+ ]
+ },
+ {
+ "group": "Getting Started",
+ "pages": [
+ "calls/react-native/setup",
+ "calls/react-native/authentication",
+ "calls/react-native/session-settings"
+ ]
+ },
+ {
+ "group": "Call Session",
+ "pages": [
+ "calls/react-native/join-session",
+ "calls/react-native/actions",
+ "calls/react-native/events"
+ ]
+ },
+ {
+ "group": "Features",
+ "pages": [
+ "calls/react-native/call-layouts",
+ "calls/react-native/call-logs",
+ "calls/react-native/recording",
+ "calls/react-native/participant-management",
+ "calls/react-native/screen-sharing",
+ "calls/react-native/audio-modes",
+ "calls/react-native/raise-hand",
+ "calls/react-native/idle-timeout",
+ "calls/react-native/ringing"
+ ]
+ },
+ {
+ "group": "Advanced",
+ "pages": [
+ "calls/react-native/picture-in-picture",
+ "calls/react-native/voip-calling",
+ "calls/react-native/background-handling",
+ "calls/react-native/custom-control-panel",
+ "calls/react-native/custom-participant-list",
+ "calls/react-native/in-call-chat",
+ "calls/react-native/share-invite"
+ ]
+ }
+ ]
+ },
+ {
+ "dropdown": "iOS",
+ "icon": "/images/icons/swift.svg",
+ "pages": [
+ {
+ "group": "Overview",
+ "pages": [
+ "calls/ios/overview"
+ ]
+ },
+ {
+ "group": "Getting Started",
+ "pages": [
+ "calls/ios/setup",
+ "calls/ios/authentication"
+ ]
+ },
+ {
+ "group": "Call Session",
+ "pages": [
+ "calls/ios/session-settings",
+ "calls/ios/join-session",
+ "calls/ios/actions",
+ "calls/ios/events"
+ ]
+ },
+ {
+ "group": "Features",
+ "pages": [
+ "calls/ios/ringing",
+ "calls/ios/call-layouts",
+ "calls/ios/audio-modes",
+ "calls/ios/recording",
+ "calls/ios/call-logs",
+ "calls/ios/participant-management",
+ "calls/ios/screen-sharing",
+ "calls/ios/picture-in-picture",
+ "calls/ios/raise-hand",
+ "calls/ios/idle-timeout"
+ ]
+ },
+ {
+ "group": "Advanced",
+ "pages": [
+ "calls/ios/custom-control-panel",
+ "calls/ios/custom-participant-list",
+ "calls/ios/voip-calling",
+ "calls/ios/background-handling",
+ "calls/ios/in-call-chat",
+ "calls/ios/share-invite"
+ ]
+ }
+ ]
+ },
+ {
+ "dropdown": "Android",
+ "icon": "/images/icons/android.svg",
+ "pages": [
+ {
+ "group": "Overview",
+ "pages": [
+ "calls/android/overview"
+ ]
+ },
+ {
+ "group": "Getting Started",
+ "pages": [
+ "calls/android/setup",
+ "calls/android/authentication"
+ ]
+ },
+ {
+ "group": "Call Session",
+ "pages": [
+ "calls/android/session-settings",
+ "calls/android/join-session",
+ "calls/android/actions",
+ "calls/android/events"
+ ]
+ },
+ {
+ "group": "Features",
+ "pages": [
+ "calls/android/ringing",
+ "calls/android/call-layouts",
+ "calls/android/audio-modes",
+ "calls/android/recording",
+ "calls/android/call-logs",
+ "calls/android/participant-management",
+ "calls/android/screen-sharing",
+ "calls/android/picture-in-picture",
+ "calls/android/raise-hand",
+ "calls/android/idle-timeout"
+ ]
+ },
+ {
+ "group": "Advanced",
+ "pages": [
+ "calls/android/custom-control-panel",
+ "calls/android/custom-participant-list",
+ "calls/android/voip-calling",
+ "calls/android/background-handling",
+ "calls/android/in-call-chat",
+ "calls/android/share-invite"
+ ]
+ }
+ ]
+ },
+ {
+ "dropdown": "Flutter",
+ "icon": "/images/icons/flutter.svg",
+ "pages": [
+ "calls/flutter/overview"
+ ]
+ }
+ ]
+ },
+ {
+ "tab": "API",
+ "tab-id": "calls-api",
+ "pages": [
+ {
+ "group": "Calls",
+ "pages": [
+ "calls/api/overview",
+ "calls/api/list-calls",
+ "calls/api/get-call"
+ ]
+ }
+ ]
+ }
+ ]
+ },
{
"product": "AI Agents",
"tabs": [
diff --git a/images/voice-video-banner.png b/images/voice-video-banner.png
index 2e316044..dcb18ae1 100644
Binary files a/images/voice-video-banner.png and b/images/voice-video-banner.png differ
diff --git a/sdk/android/calls/session-settings.mdx b/sdk/android/calls/session-settings.mdx
new file mode 100644
index 00000000..e69de29b
diff --git a/ui-kit/android/call-features.mdx b/ui-kit/android/call-features.mdx
index a5deb296..988f6c7e 100644
--- a/ui-kit/android/call-features.mdx
+++ b/ui-kit/android/call-features.mdx
@@ -6,99 +6,9 @@ title: "Call"
CometChat's Calls feature is an advanced functionality that allows you to seamlessly integrate one-on-one as well as group audio and video calling capabilities into your application. This document provides a technical overview of these features, as implemented in the Android UI Kit.
-## Integration
-
-First, make sure that you've correctly integrated the UI Kit library into your project. If you haven't done this yet or are facing difficulties, refer to our [Getting Started](/ui-kit/android/getting-started) guide. This guide will walk you through a step-by-step process of integrating our UI Kit into your Android project.
-
-Once you've successfully integrated the UI Kit, the next step is to add the CometChat Calls SDK to your project. This is necessary to enable the calling features in the UI Kit. Here's how you do it:
-
-Add the following dependency to your build.gradle file:
-
-```javascript
-dependencies {
- implementation 'com.cometchat:calls-sdk-android:4.+.+'
-}
-```
-
-After adding this dependency, the Android UI Kit will automatically detect it and activate the calling features. Now, your application supports both audio and video calling. You will see [CallButtons](/ui-kit/android/call-buttons) component rendered in [MessageHeader](/ui-kit/android/message-header) Component.
-
-
-
-
-
-To start receive calls globally in your app you will need to add `CallListener`, This needs to be added before the you initialize CometChat UI kit, We sudgest you making a custom Application class and adding call listener.
-
-
-
-```java
-public class BaseApplication extends Application {
-
- private static String LISTENER_ID = BaseApplication.class.getSimpleName()+System.currentTimeMillis();
-
- @Override
- public void onCreate() {
- super.onCreate();
- CometChat.addCallListener(LISTENER_ID, new CometChat.CallListener() {
- @Override
- public void onIncomingCallReceived(Call call) {
- CometChatCallActivity.launchIncomingCallScreen(BaseApplication.this, call, null); //pass null or IncomingCallConfiguration if need to configure CometChatIncomingCall component
- }
-
- @Override
- public void onOutgoingCallAccepted(Call call) {
-
- }
-
- @Override
- public void onOutgoingCallRejected(Call call) {
-
- }
-
- @Override
- public void onIncomingCallCancelled(Call call) {
-
- }
- });
- }
-}
-```
-
-
-
-
-```kotlin
-class BaseApplication : Application() {
- companion object {
- private val LISTENER_ID = "${BaseApplication::class.java.simpleName}${System.currentTimeMillis()}"
- }
-
- override fun onCreate() {
- super.onCreate()
- CometChat.addCallListener(LISTENER_ID, object : CometChat.CallListener {
- override fun onIncomingCallReceived(call: Call) {
- CometChatCallActivity.launchIncomingCallScreen(this@BaseApplication, call, null)
- // Pass null or IncomingCallConfiguration if need to configure CometChatIncomingCall component
- }
-
- override fun onOutgoingCallAccepted(call: Call) {
- // To be implemented
- }
-
- override fun onOutgoingCallRejected(call: Call) {
- // To be implemented
- }
-
- override fun onIncomingCallCancelled(call: Call) {
- // To be implemented
- }
- })
- }
-}
-```
-
-
-
-
+
+If you haven't set up calling yet, follow the [Calling Integration](/ui-kit/android/calling-integration) guide first.
+
## Features
diff --git a/ui-kit/android/calling-integration.mdx b/ui-kit/android/calling-integration.mdx
new file mode 100644
index 00000000..078b0eac
--- /dev/null
+++ b/ui-kit/android/calling-integration.mdx
@@ -0,0 +1,102 @@
+---
+title: "Calling Integration"
+description: "Add voice and video calling to your Android UI Kit application"
+---
+
+## Overview
+
+This guide walks you through adding voice and video calling capabilities to your Android application using the CometChat UI Kit.
+
+
+Make sure you've completed the [Getting Started](/ui-kit/android/getting-started) guide before proceeding.
+
+
+## Add the Calls SDK
+
+Add the CometChat Calls SDK dependency to your `build.gradle` file:
+
+```groovy
+dependencies {
+ implementation 'com.cometchat:calls-sdk-android:4.+.+'
+}
+```
+
+After adding this dependency, the Android UI Kit will automatically detect it and activate the calling features. You will see [CallButtons](/ui-kit/android/call-buttons) component rendered in the [MessageHeader](/ui-kit/android/message-header) component.
+
+
+
+
+
+## Set Up Call Listener
+
+To receive incoming calls globally in your app, add a `CallListener` before initializing the CometChat UI Kit. We recommend creating a custom Application class:
+
+
+
+```kotlin
+class BaseApplication : Application() {
+ companion object {
+ private val LISTENER_ID = "${BaseApplication::class.java.simpleName}${System.currentTimeMillis()}"
+ }
+
+ override fun onCreate() {
+ super.onCreate()
+ CometChat.addCallListener(LISTENER_ID, object : CometChat.CallListener {
+ override fun onIncomingCallReceived(call: Call) {
+ CometChatCallActivity.launchIncomingCallScreen(this@BaseApplication, call, null)
+ // Pass null or IncomingCallConfiguration if need to configure CometChatIncomingCall component
+ }
+
+ override fun onOutgoingCallAccepted(call: Call) {
+ // Handle accepted outgoing call
+ }
+
+ override fun onOutgoingCallRejected(call: Call) {
+ // Handle rejected outgoing call
+ }
+
+ override fun onIncomingCallCancelled(call: Call) {
+ // Handle cancelled incoming call
+ }
+ })
+ }
+}
+```
+
+
+
+```java
+public class BaseApplication extends Application {
+
+ private static String LISTENER_ID = BaseApplication.class.getSimpleName() + System.currentTimeMillis();
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ CometChat.addCallListener(LISTENER_ID, new CometChat.CallListener() {
+ @Override
+ public void onIncomingCallReceived(Call call) {
+ CometChatCallActivity.launchIncomingCallScreen(BaseApplication.this, call, null);
+ // Pass null or IncomingCallConfiguration if need to configure CometChatIncomingCall component
+ }
+
+ @Override
+ public void onOutgoingCallAccepted(Call call) {
+ // Handle accepted outgoing call
+ }
+
+ @Override
+ public void onOutgoingCallRejected(Call call) {
+ // Handle rejected outgoing call
+ }
+
+ @Override
+ public void onIncomingCallCancelled(Call call) {
+ // Handle cancelled incoming call
+ }
+ });
+ }
+}
+```
+
+
diff --git a/ui-kit/flutter/call-features.mdx b/ui-kit/flutter/call-features.mdx
index 12559e90..1344b875 100644
--- a/ui-kit/flutter/call-features.mdx
+++ b/ui-kit/flutter/call-features.mdx
@@ -6,199 +6,9 @@ title: "Call"
CometChat's Calls feature is an advanced functionality that allows you to seamlessly integrate one-on-one as well as group audio and video calling capabilities into your application. This document provides a technical overview of these features, as implemented in the Flutter UI Kit.
-## Integration
-
-First, make sure that you've correctly integrated the UI Kit library into your project. If you haven't done this yet or are facing difficulties, refer to our [Getting Started](/ui-kit/flutter/getting-started) guide. This guide will walk you through a step-by-step process of integrating our UI Kit into your Flutter project.
-
-Once you've successfully integrated the UI Kit, the next step is to add the CometChat Calls SDK to your project. This is necessary to enable the calling features in the UI Kit. Here's how you do it:
-
-Step 1
-
-### Add Dependency
-
-Add the following dependency to your `pubspec.yaml` file:
-
-
-
-```dart
-dependencies:
- cometchat_calls_uikit: ^5.0.12
-```
-
-
-
-
-
-***
-
-Step 2
-
-### Update build.gradle
-
-If your Flutter project's minimum Android SDK version (minSdkVersion) is below API level 24, you should update it to at least 24. To achieve this, navigate to the `android/app/build.gradle` file and modify the `minSdkVersion` property within the `defaultConfig` section.
-
-
-
-```gradle
-defaultConfig {
- minSdkVersion 24
- targetSdkVersion flutter.targetSdkVersion
- versionCode flutterVersionCode.toInteger()
- versionName flutterVersionName
-}
-```
-
-
-
-
-
-
-
-If you want to use the Flutter UI Kit or enable calling support within it, you'll need to:
-
-1. Set the `minSdkVersion` to 24 in your `android/app/build.gradle` file.
-
-
-
-***
-
-Step 3
-
-### Update iOS Podfile
-
-In your Podfile located at `ios/Podfile`, update the minimum iOS version that your project supports to 12.
-
-
-
-```xml
-platform :ios, '12.0'
-```
-
-
-
-
-
-***
-
-Step 4
-
-### Modify UIKitSettings
-
-To activate the calling features, you'll need to modify the UIKitSettings using `callingExtension` and pass the key in the widget.
-
-Example
-
-
-
-```dart
-UIKitSettings uiKitSettings = (UIKitSettingsBuilder()
- ..subscriptionType = CometChatSubscriptionType.allUsers
- ..autoEstablishSocketConnection = true
- ..region = "REGION"//Replace with your App Region
- ..appId = "APP_ID" //Replace with your App ID
- ..authKey = "AUTH_KEY" //Replace with your app Auth Key
- ..extensions = CometChatUIKitChatExtensions.getDefaultExtensions() //Replace this with empty array you want to disable all extensions
- ..callingExtension = CometChatCallingExtension() //Added this to Enable calling feature in the UI Kit
-).build();
-
-CometChatUIKit.init(uiKitSettings: uiKitSettings,onSuccess: (successMessage) {
- // TODO("Not yet implemented")
-}, onError: (e) {
- // TODO("Not yet implemented")
-});
-```
-
-
-
-
-
-To allow launching of Incoming Call screen from any widget whenever a call is received provide set key: CallNavigationContext.navigatorKey in the top most widget of your project (the widget that appears first of your app launch).
-
-
-
-```dart
-CometChatUIKit.login(uid, onSuccess: (s) {
- Navigator.push(context, MaterialPageRoute(builder: (context) => CometChatUsersWithMessages(key: CallNavigationContext.navigatorKey)));
-}, onError: (e) {
- // TODO("Not yet implemented")
-});
-```
-
-
-
-
-
-After adding this dependency, the Flutter UI Kit will automatically detect it and activate the calling features. Now, your application supports both audio and video calling. You will see [CallButtons](/ui-kit/flutter/call-buttons) widget rendered in [MessageHeader](/ui-kit/flutter/message-header) Widget.
-
-
-
-
-
-### Listeners
-
-For every top-level widget you wish to receive the call events in, you need to register the CallListener listener using the `addCallListener()` method.
-
-
-
-```dart
-import 'package:cometchat/cometchat_sdk.dart';
-
-class _YourClassNameState extends State with CallListener {
- @override
- void initState() {
- super.initState();
- CometChat.addCallListener(listenerId, this); //Add listener
- }
-
- @override
- void dispose() {
- super.dispose();
- CometChat.removeCallListener(listenerId); //Remove listener
- }
-
- @override
- void onIncomingCallCancelled(Call call) {
- super.onIncomingCallCancelled(call);
- debugPrint("onIncomingCallCancelled");
- }
-
- @override
- void onIncomingCallReceived(Call call) {
- super.onIncomingCallReceived(call);
- debugPrint("onIncomingCallReceived");
- }
-
- @override
- void onOutgoingCallAccepted(Call call) {
- super.onOutgoingCallAccepted(call);
- debugPrint("onOutgoingCallAccepted");
- }
-
- @override
- void onOutgoingCallRejected(Call call) {
- super.onOutgoingCallRejected(call);
- debugPrint("onOutgoingCallRejected");
- }
-
- @override
- void onCallEndedMessageReceived(Call call) {
- super.onCallEndedMessageReceived(call);
- debugPrint("onCallEndedMessageReceived");
- }
-
- @override
- Widget build(BuildContext context) {
- // TODO: implement build
- throw UnimplementedError();
- }
-}
-```
-
-
-
-
-
-***
+
+If you haven't set up calling yet, follow the [Calling Integration](/ui-kit/flutter/calling-integration) guide first.
+
## Features
diff --git a/ui-kit/flutter/calling-integration.mdx b/ui-kit/flutter/calling-integration.mdx
new file mode 100644
index 00000000..5c39b698
--- /dev/null
+++ b/ui-kit/flutter/calling-integration.mdx
@@ -0,0 +1,145 @@
+---
+title: "Calling Integration"
+description: "Add voice and video calling to your Flutter UI Kit application"
+---
+
+## Overview
+
+This guide walks you through adding voice and video calling capabilities to your Flutter application using the CometChat UI Kit.
+
+
+Make sure you've completed the [Getting Started](/ui-kit/flutter/getting-started) guide before proceeding.
+
+
+## Step 1: Add Dependency
+
+Add the following dependency to your `pubspec.yaml` file:
+
+```yaml
+dependencies:
+ cometchat_calls_uikit: ^5.0.12
+```
+
+## Step 2: Update Android build.gradle
+
+If your Flutter project's minimum Android SDK version is below API level 24, update it in `android/app/build.gradle`:
+
+```groovy
+defaultConfig {
+ minSdkVersion 24
+ targetSdkVersion flutter.targetSdkVersion
+ versionCode flutterVersionCode.toInteger()
+ versionName flutterVersionName
+}
+```
+
+## Step 3: Update iOS Podfile
+
+In `ios/Podfile`, update the minimum iOS version to 12:
+
+```ruby
+platform :ios, '12.0'
+```
+
+## Step 4: Enable Calling in UIKitSettings
+
+Modify the UIKitSettings to activate calling features using `callingExtension`:
+
+```dart
+UIKitSettings uiKitSettings = (UIKitSettingsBuilder()
+ ..subscriptionType = CometChatSubscriptionType.allUsers
+ ..autoEstablishSocketConnection = true
+ ..region = "REGION" // Replace with your App Region
+ ..appId = "APP_ID" // Replace with your App ID
+ ..authKey = "AUTH_KEY" // Replace with your Auth Key
+ ..extensions = CometChatUIKitChatExtensions.getDefaultExtensions()
+ ..callingExtension = CometChatCallingExtension() // Enable calling
+).build();
+
+CometChatUIKit.init(uiKitSettings: uiKitSettings, onSuccess: (successMessage) {
+ // Success
+}, onError: (e) {
+ // Error
+});
+```
+
+## Step 5: Set Up Incoming Call Navigation
+
+To allow launching the Incoming Call screen from any widget, provide the `CallNavigationContext.navigatorKey` in your top-level widget:
+
+```dart
+CometChatUIKit.login(uid, onSuccess: (s) {
+ Navigator.push(context, MaterialPageRoute(
+ builder: (context) => CometChatUsersWithMessages(
+ key: CallNavigationContext.navigatorKey
+ )
+ ));
+}, onError: (e) {
+ // Error
+});
+```
+
+## Verify Integration
+
+After adding the dependency, the Flutter UI Kit will automatically detect it and activate calling features. You will see [CallButtons](/ui-kit/flutter/call-buttons) rendered in the [MessageHeader](/ui-kit/flutter/message-header) widget.
+
+
+
+
+
+## Set Up Call Listeners
+
+For every top-level widget where you want to receive call events, register the CallListener:
+
+```dart
+import 'package:cometchat/cometchat_sdk.dart';
+
+class _YourClassNameState extends State with CallListener {
+ @override
+ void initState() {
+ super.initState();
+ CometChat.addCallListener(listenerId, this);
+ }
+
+ @override
+ void dispose() {
+ super.dispose();
+ CometChat.removeCallListener(listenerId);
+ }
+
+ @override
+ void onIncomingCallReceived(Call call) {
+ super.onIncomingCallReceived(call);
+ debugPrint("onIncomingCallReceived");
+ }
+
+ @override
+ void onOutgoingCallAccepted(Call call) {
+ super.onOutgoingCallAccepted(call);
+ debugPrint("onOutgoingCallAccepted");
+ }
+
+ @override
+ void onOutgoingCallRejected(Call call) {
+ super.onOutgoingCallRejected(call);
+ debugPrint("onOutgoingCallRejected");
+ }
+
+ @override
+ void onIncomingCallCancelled(Call call) {
+ super.onIncomingCallCancelled(call);
+ debugPrint("onIncomingCallCancelled");
+ }
+
+ @override
+ void onCallEndedMessageReceived(Call call) {
+ super.onCallEndedMessageReceived(call);
+ debugPrint("onCallEndedMessageReceived");
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ // Your widget build
+ }
+}
+```
diff --git a/ui-kit/ios/call-features.mdx b/ui-kit/ios/call-features.mdx
index 7506c32f..9930149b 100644
--- a/ui-kit/ios/call-features.mdx
+++ b/ui-kit/ios/call-features.mdx
@@ -6,96 +6,10 @@ title: "Call"
CometChat's Calls feature is an advanced functionality that allows you to seamlessly integrate one-on-one as well as group audio and video calling capabilities into your application. This document provides a technical overview of these features, as implemented in the iOS UI Kit.
-## Integration
-
-First, make sure that you've correctly integrated the UI Kit library into your project. If you haven't done this yet or are facing difficulties, refer to our [Getting Started](/ui-kit/ios/getting-started) guide. This guide will walk you through a step-by-step process of integrating our UI Kit into your iOS project.
-
-Once you've successfully integrated the UI Kit, the next step is to add the CometChat Calls SDK to your project. This is necessary to enable the calling features in the UI Kit. Here's how you do it:
-
-**1. CocoaPods**
-
-We recommend using CocoaPods, as they are the most advanced way of managing iOS project dependencies. Open a terminal window, move to your project directory, and then create a Podfile by running the following command.
-
-
-
-1. You can install CometChatCallsSDK for iOS through Swift Package Manager or Cocoapods
-
-2. CometChatCallsSDK supports iOS 13 and aboveSwift 5.0+
-
-3. CometChatCallsSDK supports Swift 5.0+
-
-
-
-```ruby Swift
-$ pod init
-```
-
-Add the following lines to the Podfile.
-
-```ruby Swift
-platform :ios, '11.0'
-use_frameworks!
-
-target 'YourApp' do
- pod 'CometChatUIKitSwift', '4.3.15'
- pod 'CometChatCallsSDK', '4.0.6'
-end
-```
-
-And then install the CometChatCallsSDK framework through CocoaPods.
-
-```ruby Swift
-$ pod install
-```
-
-If you're facing any issues while installing pods then use the below command.
-
-```ruby Swift
-$ pod install --repo-update
-```
-
-Always get the latest version of CometChatCallsSDK by command.
-
-```ruby Swift
-$ pod update CometChatCallsSDK
-```
-
-
-
-Always ensure to open the XCFramework file after adding the dependencies.
-
-
-
-***
-
-**2. Swift Package Manager.**
-
-Add the Call SDK dependency :
-
-You can install **Calling SDK for iOS** through **Swift Package Manager.**
-
-1. Go to your Swift Package Manager's File tab and select Add Packages
-
-2. Add `CometChatCallsSDK` into your `Package Repository` as below:
-
-```sh Bash
-https://github.com/cometchat/calls-sdk-ios.git
-```
-
-3. To add the package, select Version Rules, enter Up to Exact Version, **4.0.5** and click Next.
-
-
-Before Adding the Call SDK dependency to your project's dependencies please make sure that you have completed the Chat UI Kit Integration.
-
+If you haven't set up calling yet, follow the [Calling Integration](/ui-kit/ios/calling-integration) guide first.
-After adding this dependency, the iOS UI Kit will automatically detect it and activate the calling features. Now, your application supports both audio and video calling. You will see [CallButtons](/ui-kit/ios/call-buttons) component rendered in [MessageHeader](/ui-kit/ios/message-header) Component.
-
-
-
-
-
## Features
### Incoming Call
diff --git a/ui-kit/ios/calling-integration.mdx b/ui-kit/ios/calling-integration.mdx
new file mode 100644
index 00000000..c373888e
--- /dev/null
+++ b/ui-kit/ios/calling-integration.mdx
@@ -0,0 +1,86 @@
+---
+title: "Calling Integration"
+description: "Add voice and video calling to your iOS UI Kit application"
+---
+
+## Overview
+
+This guide walks you through adding voice and video calling capabilities to your iOS application using the CometChat UI Kit.
+
+
+Make sure you've completed the [Getting Started](/ui-kit/ios/getting-started) guide before proceeding.
+
+
+## Add the Calls SDK
+
+You can install the CometChat Calls SDK using either CocoaPods or Swift Package Manager.
+
+
+- CometChatCallsSDK supports iOS 13 and above
+- CometChatCallsSDK supports Swift 5.0+
+
+
+### Option 1: CocoaPods
+
+Open a terminal window, navigate to your project directory, and create a Podfile:
+
+```ruby
+$ pod init
+```
+
+Add the following lines to the Podfile:
+
+```ruby
+platform :ios, '13.0'
+use_frameworks!
+
+target 'YourApp' do
+ pod 'CometChatUIKitSwift', '4.3.15'
+ pod 'CometChatCallsSDK', '4.0.6'
+end
+```
+
+Install the dependencies:
+
+```ruby
+$ pod install
+```
+
+If you encounter issues, try:
+
+```ruby
+$ pod install --repo-update
+```
+
+To update to the latest version:
+
+```ruby
+$ pod update CometChatCallsSDK
+```
+
+
+Always open the `.xcworkspace` file after adding the dependencies.
+
+
+### Option 2: Swift Package Manager
+
+1. Go to File → Add Packages in Xcode
+2. Add the CometChat Calls SDK repository:
+
+```
+https://github.com/cometchat/calls-sdk-ios.git
+```
+
+3. Select Version Rules, enter the version (e.g., 4.0.5), and click Next
+
+
+Make sure you have completed the Chat UI Kit integration before adding the Calls SDK.
+
+
+## Verify Integration
+
+After adding the dependency, the iOS UI Kit will automatically detect it and activate calling features. You will see [CallButtons](/ui-kit/ios/call-buttons) rendered in the [MessageHeader](/ui-kit/ios/message-header) component.
+
+
+
+
diff --git a/ui-kit/react-native/call-features.mdx b/ui-kit/react-native/call-features.mdx
index 7d3fef3d..ea0dafe7 100644
--- a/ui-kit/react-native/call-features.mdx
+++ b/ui-kit/react-native/call-features.mdx
@@ -2,221 +2,13 @@
title: "Call"
---
-CometChat’s Calls feature offers advanced functionality for seamlessly integrating one-on-one and group audio/video calling into your application. This document provides a technical overview of how these features are implemented using the React Native UI Kit.
+## Overview
-## Integration
+CometChat's Calls feature offers advanced functionality for seamlessly integrating one-on-one and group audio/video calling into your application. This document provides a technical overview of how these features are implemented using the React Native UI Kit.
-First, make sure that you've correctly integrated the UI Kit library into your project. If you haven't done this yet or are facing difficulties, refer to our [Getting Started](/ui-kit/react-native/getting-started) guide. This guide will walk you through a step-by-step process of integrating our UI Kit into your React Native project.
-
-Once you've successfully integrated the UI Kit, the next step is to add the CometChat Calls SDK to your project. This is necessary to enable the calling features in the UI Kit. Here's how you do it:
-
-
-
-```sh
-npm install @cometchat/calls-sdk-react-native
-```
-
-
-
-
-
-Once the dependency is added, the React Native UI Kit will automatically detect it and enable calling features. Your application will now support both audio and video calls and the [CallButtons](/ui-kit/react-native/call-buttons) component will appear within the [MessageHeader](/ui-kit/react-native/message-header) component.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-## Add necessary permissions
-
-### Android:
-
-Go to AndroidManifest.xml located in the react-native/app/src/main of your React Native project and add the following permissions:
-
-
-
-```xml
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-```
-
-
-
-
-
-### iOS:
-
-Open Info.plist located in the ios/\{appname} of your React Native project and add the following entries:
-
-
-
-```xml
-
-
-
-
- NSCameraUsageDescription
- Access camera
- NSMicrophoneUsageDescription
- Access Microphone
-
-
-```
-
-
-
-
-
-## Setup minimum Android and iOS versions
-
-For Android go to app-level build.gradle and change minSdkVersion to 24 and the targetSdkVersion and compileSdkVersion to 33.
-
-
-
-```gradle
-android {
- namespace "com.org.name_of_your_app"
-
- compileSdkVersion 33
-
- defaultConfig {
-
- minSdkVersion 24
-
- targetSdkVersion 33
-
- }
-
-}
-```
-
-
-
-
-
-For iOS you can open xcworkspace in XCode modify the IPHONEOS\_DEPLOYMENT\_TARGET to 12.0
-
-or you can specify it in the post\_install hook of the Podfile
-
-
-
-```ruby
-post_install do |installer|
- installer.pods_project.targets.each do |target|
- flutter_additional_ios_build_settings(target)
- target.build_configurations.each do |build_configuration|
-
- build_configuration.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '12.0'
-
- build_configuration.build_settings['EXCLUDED_ARCHS[sdk=iphonesimulator*]'] = 'arm64 i386'
- build_configuration.build_settings['ENABLE_BITCODE'] = 'NO'
- end
- end
-end
-```
-
-
-
-
-
-## Add the Call Listeners
-
-In addition to CallButtons, the Calls UI Kit offers fully functional UI components for handling Incoming, Outgoing, and Ongoing calls. To receive call events in your desired component or screen, you must register a call listener using the addCallListener() method. The onIncomingCallReceived() event is triggered whenever an incoming call is received.
-
-
-
-```tsx
-import React, { useEffect, useRef, useState } from "react";
-import { SafeAreaView } from "react-native";
-import { CometChat } from "@cometchat/chat-sdk-react-native";
-import { CometChatIncomingCall } from "@cometchat/chat-uikit-react-native";
-
-// Track whether the user is logged in
-const [loggedIn, setLoggedIn] = useState(false);
-// Track if there is an incoming call to display
-const [callReceived, setCallReceived] = useState(false);
-// Store the incoming call object for use in the UI
-const incomingCall = useRef(
- null
-);
-// Unique ID for registering and removing the call listener
-var listenerID: string = "UNIQUE_LISTENER_ID";
-
-const App = (): React.ReactElement => {
- useEffect(() => {
- // Register the call listener when the component mounts or when login state changes
- CometChat.addCallListener(
- listenerID,
- new CometChat.CallListener({
- // Fired when an incoming call is received
- onIncomingCallReceived: (call: CometChat.Call) => {
- // Store the incoming call and update state.
- incomingCall.current = call;
- // Trigger UI to show incoming call screen
- setCallReceived(true);
- },
- // Fired when an outgoing call is rejected by the recipient
- onOutgoingCallRejected: () => {
- // Clear the call state if outgoing call is rejected.
- incomingCall.current = null; // Clear the call object
- setCallReceived(false); // Hide any call UI
- },
- onIncomingCallCancelled: () => {
- // Clear the call state if the incoming call is cancelled.
- incomingCall.current = null;
- setCallReceived(false);
- },
- })
- );
-
- // Cleanup: remove the call listener when the component unmounts or before re-running
- return () => {
- CometChat.removeCallListener(listenerID);
- };
- }, [loggedIn]); // Re-run effect if the login state changes
-
- return (
-
- {/* Render the incoming call UI when logged in and a call has been received */}
- {loggedIn && callReceived && incomingCall.current ? (
- {
- // Handle call decline by clearing the incoming call state.
- incomingCall.current = null; // Clear the call object
- setCallReceived(false); // Hide the incoming call UI
- }}
- />
- ) : null}
-
- );
-};
-```
-
-
-
-
+
+If you haven't set up calling yet, follow the [Calling Integration](/ui-kit/react-native/calling-integration) guide first.
+
## Features
diff --git a/ui-kit/react-native/calling-integration.mdx b/ui-kit/react-native/calling-integration.mdx
new file mode 100644
index 00000000..ea9703eb
--- /dev/null
+++ b/ui-kit/react-native/calling-integration.mdx
@@ -0,0 +1,157 @@
+---
+title: "Calling Integration"
+description: "Add voice and video calling to your React Native UI Kit application"
+---
+
+## Overview
+
+This guide walks you through adding voice and video calling capabilities to your React Native application using the CometChat UI Kit.
+
+
+Make sure you've completed the [Getting Started](/ui-kit/react-native/getting-started) guide before proceeding.
+
+
+## Add the Calls SDK
+
+Install the CometChat Calls SDK:
+
+```bash
+npm install @cometchat/calls-sdk-react-native
+```
+
+Once added, the React Native UI Kit will automatically detect it and enable calling features. The [CallButtons](/ui-kit/react-native/call-buttons) component will appear within the [MessageHeader](/ui-kit/react-native/message-header) component.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+## Add Permissions
+
+### Android
+
+Add the following permissions to `android/app/src/main/AndroidManifest.xml`:
+
+```xml
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+### iOS
+
+Add the following entries to `ios/{appname}/Info.plist`:
+
+```xml
+NSCameraUsageDescription
+Access camera
+NSMicrophoneUsageDescription
+Access Microphone
+```
+
+## Set Up Minimum Versions
+
+### Android
+
+In `android/app/build.gradle`, set the SDK versions:
+
+```groovy
+android {
+ compileSdkVersion 33
+
+ defaultConfig {
+ minSdkVersion 24
+ targetSdkVersion 33
+ }
+}
+```
+
+### iOS
+
+In Xcode, set `IPHONEOS_DEPLOYMENT_TARGET` to 12.0, or add to your Podfile:
+
+```ruby
+post_install do |installer|
+ installer.pods_project.targets.each do |target|
+ target.build_configurations.each do |build_configuration|
+ build_configuration.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '12.0'
+ build_configuration.build_settings['EXCLUDED_ARCHS[sdk=iphonesimulator*]'] = 'arm64 i386'
+ build_configuration.build_settings['ENABLE_BITCODE'] = 'NO'
+ end
+ end
+end
+```
+
+## Set Up Call Listeners
+
+Register a call listener to receive call events in your component:
+
+```tsx
+import React, { useEffect, useRef, useState } from "react";
+import { SafeAreaView } from "react-native";
+import { CometChat } from "@cometchat/chat-sdk-react-native";
+import { CometChatIncomingCall } from "@cometchat/chat-uikit-react-native";
+
+const App = (): React.ReactElement => {
+ const [loggedIn, setLoggedIn] = useState(false);
+ const [callReceived, setCallReceived] = useState(false);
+ const incomingCall = useRef(null);
+ const listenerID = "UNIQUE_LISTENER_ID";
+
+ useEffect(() => {
+ CometChat.addCallListener(
+ listenerID,
+ new CometChat.CallListener({
+ onIncomingCallReceived: (call: CometChat.Call) => {
+ incomingCall.current = call;
+ setCallReceived(true);
+ },
+ onOutgoingCallRejected: () => {
+ incomingCall.current = null;
+ setCallReceived(false);
+ },
+ onIncomingCallCancelled: () => {
+ incomingCall.current = null;
+ setCallReceived(false);
+ },
+ })
+ );
+
+ return () => {
+ CometChat.removeCallListener(listenerID);
+ };
+ }, [loggedIn]);
+
+ return (
+
+ {loggedIn && callReceived && incomingCall.current ? (
+ {
+ incomingCall.current = null;
+ setCallReceived(false);
+ }}
+ />
+ ) : null}
+
+ );
+};
+```
diff --git a/ui-kit/react/call-features.mdx b/ui-kit/react/call-features.mdx
index f9d61886..92de7d97 100644
--- a/ui-kit/react/call-features.mdx
+++ b/ui-kit/react/call-features.mdx
@@ -6,23 +6,9 @@ title: "Call"
CometChat's Calls feature is an advanced functionality that allows you to seamlessly integrate one-on-one as well as group audio and video calling capabilities into your application. This document provides a technical overview of these features, as implemented in the React UI Kit.
-## Integration
-
-First, make sure that you've correctly integrated the UI Kit library into your project. If you haven't done this yet or are facing difficulties, refer to our [Getting Started](/ui-kit/react/integration) guide. This guide will walk you through a step-by-step process of integrating our UI Kit into your React project.
-
-Once you've successfully integrated the UI Kit, the next step is to add the CometChat Calls SDK to your project. This is necessary to enable the calling features in the UI Kit. Here's how you do it:
-
-Add the SDK files to your project's dependencies or libraries:
-
-```java
-npm install @cometchat/calls-sdk-javascript
-```
-
-After adding this dependency, the React UI Kit will automatically detect it and activate the calling features. Now, your application supports both audio and video calling. You will see [CallButtons](/ui-kit/react/call-buttons) component rendered in [MessageHeader](/ui-kit/react/message-header) Component.
-
-
-
-
+
+If you haven't set up calling yet, follow the [Calling Integration](/ui-kit/react/calling-integration) guide first.
+
## Features
diff --git a/ui-kit/react/calling-integration.mdx b/ui-kit/react/calling-integration.mdx
new file mode 100644
index 00000000..3d0365d8
--- /dev/null
+++ b/ui-kit/react/calling-integration.mdx
@@ -0,0 +1,28 @@
+---
+title: "Calling Integration"
+description: "Add voice and video calling to your React UI Kit application"
+---
+
+## Overview
+
+This guide walks you through adding voice and video calling capabilities to your React application using the CometChat UI Kit.
+
+
+Make sure you've completed the [Getting Started](/ui-kit/react/react-js-integration) guide before proceeding.
+
+
+## Add the Calls SDK
+
+Install the CometChat Calls SDK:
+
+```bash
+npm install @cometchat/calls-sdk-javascript
+```
+
+## Verify Integration
+
+After adding the dependency, the React UI Kit will automatically detect it and activate calling features. You will see [CallButtons](/ui-kit/react/call-buttons) rendered in the [MessageHeader](/ui-kit/react/message-header) component.
+
+
+
+