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
- 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)
- Familiarity with Actions and Events
Step 1: Hide Default Controls
Configure your session settings to hide the default control panel:- Kotlin
- Java
Report incorrect code
Copy
Ask AI
val sessionSettings = CometChatCalls.SessionSettingsBuilder()
.hideControlPanel(true)
.build()
Report incorrect code
Copy
Ask AI
SessionSettings sessionSettings = new CometChatCalls.SessionSettingsBuilder()
.hideControlPanel(true)
.build();
You can also hide individual buttons while keeping the control panel visible. See SessionSettingsBuilder for all options.
Step 2: Create Custom Layout
Create an XML layout for your custom controls:activity_call.xml
activity_call.xml
Report incorrect code
Copy
Ask AI
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- Call View Container -->
<FrameLayout
android:id="@+id/callContainer"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<!-- Custom Control Panel -->
<LinearLayout
android:id="@+id/customControlPanel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:orientation="horizontal"
android:gravity="center"
android:padding="16dp"
android:background="#CC000000">
<!-- Mute/Unmute Button -->
<ImageButton
android:id="@+id/btnToggleAudio"
android:layout_width="56dp"
android:layout_height="56dp"
android:layout_margin="8dp"
android:src="@drawable/ic_mic_on"
android:background="@drawable/control_button_background"
android:contentDescription="Toggle Audio" />
<!-- Pause/Resume Video Button -->
<ImageButton
android:id="@+id/btnToggleVideo"
android:layout_width="56dp"
android:layout_height="56dp"
android:layout_margin="8dp"
android:src="@drawable/ic_video_on"
android:background="@drawable/control_button_background"
android:contentDescription="Toggle Video" />
<!-- Switch Camera Button -->
<ImageButton
android:id="@+id/btnSwitchCamera"
android:layout_width="56dp"
android:layout_height="56dp"
android:layout_margin="8dp"
android:src="@drawable/ic_switch_camera"
android:background="@drawable/control_button_background"
android:contentDescription="Switch Camera" />
<!-- End Call Button -->
<ImageButton
android:id="@+id/btnEndCall"
android:layout_width="56dp"
android:layout_height="56dp"
android:layout_margin="8dp"
android:src="@drawable/ic_call_end"
android:background="@drawable/end_call_button_background"
android:contentDescription="End Call" />
</LinearLayout>
</RelativeLayout>
Button Background Drawables
Button Background Drawables
control_button_background.xml:end_call_button_background.xml:
Report incorrect code
Copy
Ask AI
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="#4D4D4D" />
</shape>
Report incorrect code
Copy
Ask AI
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="#FF3B30" />
</shape>
Step 3: Implement Control Actions
Set up button click listeners and call the appropriate actions:- Kotlin
- Java
Report incorrect code
Copy
Ask AI
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()
}
}
}
Report incorrect code
Copy
Ask AI
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
UseMediaEventsListener to keep your UI synchronized with the actual call state. The listener is lifecycle-aware and automatically removed when the Activity is destroyed.
- Kotlin
- Java
Report incorrect code
Copy
Ask AI
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)
}
}
})
}
Report incorrect code
Copy
Ask AI
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);
});
}
});
}
SessionStatusListener to handle session end events:
- Kotlin
- Java
Report incorrect code
Copy
Ask AI
private fun setupSessionStatusListener() {
callSession.addSessionStatusListener(this, object : SessionStatusListener() {
override fun onSessionLeft() {
runOnUiThread { finish() }
}
override fun onConnectionClosed() {
runOnUiThread { finish() }
}
})
}
Report incorrect code
Copy
Ask AI
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
- Java
Report incorrect code
Copy
Ask AI
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<FrameLayout>(R.id.callContainer)
CometChatCalls.joinSession(
sessionId = "SESSION_ID",
sessionSettings = sessionSettings,
view = callContainer,
context = this,
listener = object : CometChatCalls.CallbackListener<Void>() {
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"
}
}
Report incorrect code
Copy
Ask AI
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<Void>() {
@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());
}
});
}
}