Join multiple channels
Agora Video SDK enables you to simultaneously join multiple channels. This capability allows you to receive and publish audio and video streams across multiple channels concurrently.
Understand the tech
Video SDK's multi-channel functionality is based on two key components:
- 
RtcConnectionThe RtcConnectionobject identifies a connection. It contains the following information:- Channel name
- User ID of the local user
 You create multiple RtcConnectionobjects, each with a different channel name and user ID. EachRtcConnectioninstance can independently publish multiple audio streams and a single video stream.
- 
RtcEngineExThe class contains methods tailored for interacting with a designated RtcConnectionobject.To join multiple channels, you call joinChannelExmethod in theRtcEngineExclass multiple times, using a differentRtcConnectioninstance each time.
When joining multiple channels:
- Ensure that the user ID for each RtcConnectionobject is unique and nonzero.
- Configure publishing and subscribing options for the RtcConnectionobject injoinChannelEx.
- Pass the IRtcEngineEventHandlerobject to theeventHandlerparameter when calling thejoinChannelExmethod to receive multiple channel-related event notifications.
Prerequisites
Ensure that you have implemented the SDK quickstart in your project.
Implementation
This section explains how to join a second channel as a host after you have already joined the first channel.
- Declare variables for RtcEngineExandRtcConnectionobjects.
- Java
- Kotlin
private RtcEngineEx engine;private RtcConnection rtcConnection2 = new RtcConnection();private lateinit var engine: RtcEngineExprivate val rtcConnection2 = RtcConnection()- Initialize the engine instance.
- Java
- Kotlin
engine = (RtcEngineEx) RtcEngine.create(config);engine = RtcEngine.create(config) as RtcEngineEx- Join the channel using a random user ID.
- Java
- Kotlin
private boolean joinSecondChannel() {     ChannelMediaOptions option = new ChannelMediaOptions();     mediaOptions.autoSubscribeAudio = true;     mediaOptions.autoSubscribeVideo = true;     rtcConnection2.channelId = "channel-2";     rtcConnection2.localUid = new Random().nextInt(512)+512;     int ret = engine.joinChannelEx("your token", rtcConnection2, mediaOptions,          iRtcEngineEventHandler2);     return (ret == 0); }private fun joinSecondChannel(): Boolean {    val mediaOptions = ChannelMediaOptions().apply {        autoSubscribeAudio = true        autoSubscribeVideo = true    }    rtcConnection2.channelId = "channel-2"    rtcConnection2.localUid = (Random().nextInt(512) + 512)    val ret = engine.joinChannelEx("your token", rtcConnection2, mediaOptions, iRtcEngineEventHandler2)    return ret == 0}- Listen for events in rtcConnection2and set up the remote video in theonUserJoinedcallback.
- Java
- Kotlin
private final IRtcEngineEventHandler iRtcEngineEventHandler2 = new IRtcEngineEventHandler() {    @Override    public void onJoinChannelSuccess(String channel, int uid, int elapsed) {        Log.i(TAG, String.format("channel2 onJoinChannelSuccess channel %s uid %d", channel2, uid));        showLongToast(String.format("onJoinChannelSuccess channel %s uid %d", channel2, uid));    }    @Override    public void onUserJoined(int uid, int elapsed) {        Log.i(TAG, "channel2 onUserJoined->" + uid);        showLongToast(String.format("user %d joined!", uid));        Context context = getContext();        if (context == null) {            return;        }        handler.post(() ->        {            // Display the remote video stream            SurfaceView surfaceView = null;            if (fl_remote2.getChildCount() > 0) {                fl_remote2.removeAllViews();            }            // Create the rendering view through RtcEngine            surfaceView = new SurfaceView(context);            surfaceView.setZOrderMediaOverlay(true);            // Add the view to the remote container            fl_remote2.addView(surfaceView, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));            // Set the remote view            engine.setupRemoteVideoEx(new VideoCanvas(surfaceView, RENDER_MODE_FIT, uid), rtcConnection2);        });    }};private val iRtcEngineEventHandler2 = object : IRtcEngineEventHandler() {    override fun onJoinChannelSuccess(channel: String?, uid: Int, elapsed: Int) {        Log.i(TAG, String.format("channel2 onJoinChannelSuccess channel %s uid %d", channel2, uid))        showLongToast(String.format("onJoinChannelSuccess channel %s uid %d", channel2, uid))    }    override fun onUserJoined(uid: Int, elapsed: Int) {        Log.i(TAG, "channel2 onUserJoined->$uid")        showLongToast("user $uid joined!")        val context = context        if (context == null) {            return        }        handler.post {            // Display the remote video stream            var surfaceView: SurfaceView? = null            if (fl_remote2.childCount > 0) {                fl_remote2.removeAllViews()            }            // Create the rendering view through RtcEngine            surfaceView = SurfaceView(context)            surfaceView?.zOrderMediaOverlay = true            // Add the view to the remote container            fl_remote2.addView(surfaceView, FrameLayout.LayoutParams(                ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT))            // Set the remote view            engine.setupRemoteVideoEx(VideoCanvas(surfaceView, RENDER_MODE_FIT, uid), rtcConnection2)        }    }}Reference
This section contains content that completes the information on this page, or points you to documentation that explains other aspects to this product.
Sample project
Agora provides the JoinMultipleChannels open-source sample project for your reference. Download the project or view the source code for a more detailed example.