# Join multiple channels (/en/realtime-media/rtc/build/join-and-manage-channels/join-multiple-channels/unity)

> For AI agents: see the complete documentation index at [llms.txt](/llms.txt).

Agora RTC 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

RTC SDK's multi-channel functionality is based on two key components:

* `RtcConnection`

  The `RtcConnection` object identifies a connection. It contains the following information:

  * Channel name
  * User ID of the local user

  You create multiple `RtcConnection` objects, each with a different channel name and user ID. Each `RtcConnection` instance can independently publish multiple audio streams and a single video stream.

* `IRtcEngineEx`

  The class contains methods tailored for interacting with a designated `RtcConnection` object.

  To join multiple channels, you call `JoinChannelEx` method in the `IRtcEngineEx` class multiple times, using a different `RtcConnection` instance each time.

When joining multiple channels:

* Ensure that the user ID for each `RtcConnection` object is unique and nonzero.

* Configure publishing and subscribing options for the `RtcConnection` object in `JoinChannelEx`.

* Use the `connection` parameter in each callback to identify the corresponding channel.

## Prerequisites

Ensure that you have implemented the [SDK quickstart](/en/realtime-media/rtc/get-started-sdk) in your project.

## Implementation

This section explains how to join a second channel as a host after you have already joined the first channel.

1. Join the second channel

   Call `JoinChannelEx` to join the second channel:

   ```csharp
   ChannelMediaOptions options2 = new ChannelMediaOptions();
   // Automatically subscribe to remote audio
   options2.autoSubscribeAudio.SetValue(true);
   // Automatically subscribe to remote video
   options2.autoSubscribeVideo.SetValue(true);
   // Publish audio from the microphone
   options2.publishMicrophoneTrack.SetValue(true);
   // Publish video from the camera
   options2.publishCameraTrack.SetValue(true);
   // Set the user role to broadcaster
   options2.clientRoleType.SetValue(CLIENT_ROLE_TYPE.CLIENT_ROLE_BROADCASTER);
   // Join the second channel with a unique user ID
   ret = RtcEngine.JoinChannelEx(_token, new RtcConnection(_channelName, UID2), options2);
   Debug.Log("JoinChannelEx returns: " + ret);
   ```

2. Set up remote user video

   Listen for the `OnUserJoined` callback and call `SetForUser` to set up video rendering for the remote user:

   ```csharp
   public override void OnUserJoined(RtcConnection connection, uint uid, int elapsed)
   {
     Debug.Log(string.Format("OnUserJoined uid: {0}, elapsed: {1}", uid, elapsed));
     // Create a view for the remote user
     var videoSurface = MakeImageSurface(uid.ToString());
     // Set up video rendering
     videoSurface.SetForUser(uid, connection.channelId, VIDEO_SOURCE_TYPE.VIDEO_SOURCE_REMOTE);
     // Callback when the Texture size changes
     videoSurface.OnTextureSizeModify += (int width, int height) =>
     {
       var transform = videoSurface.GetComponent<RectTransform>();
       if (transform)
       {
         // If rendering in a RawImage, set the RawImage size
         transform.sizeDelta = new Vector2(width / 2, height / 2);
         transform.localScale = Vector3.one;
       }
       else
       {
         // If rendering in a MeshRenderer, set the MeshRenderer size
         float scale = (float)height / (float)width;
         videoSurface.transform.localScale = new Vector3(-1, 1, scale);
       }
       Debug.Log("OnTextureSizeModify: " + width + " " + height);
     };
   }

   private static VideoSurface MakeImageSurface(string goName)
   {
     GameObject go = new GameObject { name = goName };
     // Add RawImage for rendering
     go.AddComponent<RawImage>();
     // Make the object draggable
     go.AddComponent<UIElementDrag>();

     var canvas = GameObject.Find("VideoCanvas");
     if (canvas != null)
     {
       go.transform.parent = canvas.transform;
       Debug.Log("Video view added");
     }
     else
     {
       Debug.Log("Canvas is null, video view not added");
     }

     // Set transform properties
     go.transform.Rotate(0f, 0f, 180f);
     go.transform.localPosition = Vector3.zero;
     go.transform.localScale = new Vector3(2f, 3f, 1f);

     // Add VideoSurface component
     var videoSurface = go.AddComponent<VideoSurface>();
     return videoSurface;
   }
   ```

## Reference

This section contains content that completes the information on this page, or points you to documentation that explains other aspects to this product.

### API reference

* [`IRtcEngineEx`](https://api-ref.agora.io/en/video-sdk/unity/4.x/API/class_irtcengineex.html#class_irtcengineex)
* [`RtcConnection`](https://api-ref.agora.io/en/video-sdk/unity/4.x/API/class_rtcconnection.html)
* [`JoinChannelEx`](https://api-ref.agora.io/en/video-sdk/unity/4.x/API/class_irtcengineex.html#api_irtcengineex_joinchannelex)
* [`VideoSurface`](https://api-ref.agora.io/en/video-sdk/unity/4.x/API/class_videosurface.html#class_videosurface)
* [`OnUserJoined`](https://api-ref.agora.io/en/video-sdk/unity/4.x/API/class_irtcengineeventhandler.html#callback_irtcengineeventhandler_onuserjoined)

    
  
