# 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)

    
  
