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

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

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 [#understand-the-tech-4]

    Video 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 [#prerequisites-4]

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

    ## Implementation [#implementation-4]

    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 [#reference-4]

    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 [#api-reference-4]

    * [`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)

    
  
