Skip to main content

Broadcast streaming over multiple channels

Some special scenarios require streaming over two or more separate channels. Depending on the scenario, the app joins both channels as host, both channels as audience, or one channel as host and the other as audience. For example, consider the case of an online singing contest, where hosts of different channels interact with each other. The audience receives streams from two channels while the contestants broadcast to both channels.

Agora Video SDK multi-channel streaming allows you to join multiple channels at the same time and broadcast or receive audio and video over them. This page shows you how to implement two different multi-channel methods into your app using Video SDK. Choose the method that best fits your particular scenario.

Understand the tech

Agora Video SDK provides the following approaches to implementing multi-channel streaming:

  • Channel media relay

    In channel media relay, one channel is used as the source channel from which audio and video is relayed to a second channel called the destination channel. The audience on the destination channel subscribes to the relayed media stream. A host can relay media stream to a maximum of four destination channels. Relaying media streams provides the following benefits:

    • All hosts in the channels can see and hear each other.
    • The audience in the channels can see and hear all hosts.
  • Join multiple channels

    To join multiple channels, a host first sets up a primary channel and starts an event. The host then joins a second channel and publishes to the new channel. The audience joins either channel and subscribes to the host channel. The two channels are independent and users on one channels don't see users on the other channel. You can extend this functionality to join as many channels as required.

The following figure shows the workflow you need to implement to add multi-channel streaming to your app:

stream over multiple channels

Prerequisites

To follow this procedure you must have implemented the SDK quickstart for Broadcast Streaming project.

Project setup

In order to create the environment necessary to implement Agora multi-channel streaming into your app, do the following:

Implement multi-channel streaming

This section shows you how to implement the following methods of multi-channel streaming:

Choose the method that best suits your scenario and follow the step by step procedure.

Channel media relay

In this example, you use a single Button to start and stop channel media relay.

Implement the user interface

To enable your users to start and stop relaying to another channel, add a Button to the user interface. In /app/res/layout/activity_main.xml, add the following code before </RelativeLayout>:


_9
<Button
_9
android:id="@+id/ChannelRelayButton"
_9
android:layout_width="wrap_content"
_9
android:layout_height="wrap_content"
_9
android:layout_below="@+id/LeaveButton"
_9
android:layout_alignEnd="@id/LeaveButton"
_9
android:layout_alignStart="@id/JoinButton"
_9
android:onClick="channelRelay"
_9
android:text="Start Channel Media Relay" />

You see an error in your IDE. This is because this layout refers to a method that you create later.

Handle the system logic

In your project, import the relevant libraries and declare the required variables.

  1. Add the required libraries

    To implement media relay and access the UI elements, import the corresponding Agora and Android libraries. In /app/java/com.example.<projectname>/MainActivity, add the following to the list of import statements:


    _4
    import io.agora.rtc2.video.ChannelMediaInfo;
    _4
    import io.agora.rtc2.video.ChannelMediaRelayConfiguration;
    _4
    _4
    import android.widget.Button;

  2. Declare the variables you need

    To store source and destination channel settings and manage channel relay, in /app/java/com.example.<projectname>/MainActivity, add the following variable declarations to the MainActivity class:


    _6
    private String destChannelName = "<name of the destination channel>";
    _6
    private String destChannelToken = "<access token for the destination channel>";
    _6
    private int destUid = 100; // User ID that the user uses in the destination channel.
    _6
    private String sourceChannelToken = "<access token for the source channel>"; // Generate with the channelName and uid = 0.
    _6
    private boolean mediaRelaying = false;
    _6
    private Button channelMediaButton;

  3. Access the channel relay button

    In the onCreate method of the MainActivity class, add the following lines after setupVideoSDKEngine();:


    _1
    channelMediaButton = findViewById(R.id.ChannelRelayButton);

Implement channel media relay

To enable app users to relay channel media to a destination channel, take the following steps:

  1. Start or stop channel media relay

    When a user presses the button, the app starts relaying media from the source channel to the destination channel. If channel media relay is already running, the app stops it. To integrate this workflow, add the following method to the MainActivity class.


    _18
    public void channelRelay(View view) {
    _18
    _18
    if (mediaRelaying) {
    _18
    agoraEngine.stopChannelMediaRelay();
    _18
    } else {
    _18
    // Configure the source channel information.
    _18
    ChannelMediaInfo srcChannelInfo = new ChannelMediaInfo(channelName, sourceChannelToken, uid);
    _18
    ChannelMediaRelayConfiguration mediaRelayConfiguration = new ChannelMediaRelayConfiguration();
    _18
    mediaRelayConfiguration.setSrcChannelInfo(srcChannelInfo);
    _18
    _18
    // Configure the destination channel information.
    _18
    ChannelMediaInfo destChannelInfo = new ChannelMediaInfo(destChannelName, destChannelToken, destUid);
    _18
    mediaRelayConfiguration.setDestChannelInfo(destChannelName, destChannelInfo);
    _18
    _18
    // Start relaying media streams across channels
    _18
    agoraEngine.startOrUpdateChannelMediaRelay(mediaRelayConfiguration);
    _18
    }
    _18
    }

  2. Monitor the channel media relay state

    To receive the state change notifications sent during media relay, you add a method to the IRtcEngineEventHandler. Your app responds to connection and failure events in the onChannelMediaRelayStateChanged event handler. In the MainActivity class, add the following method after private final IRtcEngineEventHandler mRtcEventHandler = new IRtcEngineEventHandler() {:


    _24
    @Override
    _24
    public void onChannelMediaRelayStateChanged(int state, int code) {
    _24
    // This example shows toast messages when the relay state changes,
    _24
    // a production level app needs to handle state change properly.
    _24
    switch (state) {
    _24
    case 1: // RELAY_STATE_CONNECTING:
    _24
    showMessage("Channel media relay connecting.");
    _24
    runOnUiThread(()-> channelMediaButton.setText("Connecting..."));
    _24
    break;
    _24
    case 2: // RELAY_STATE_RUNNING:
    _24
    mediaRelaying = true;
    _24
    showMessage("Channel media relay running.");
    _24
    runOnUiThread(()-> channelMediaButton.setText("Stop Channel Media Relay"));
    _24
    break;
    _24
    case 3: // RELAY_STATE_FAILURE:
    _24
    mediaRelaying = false;
    _24
    if (code == 2) {
    _24
    showMessage("No server response. Make sure that co-hosting token authentication has been enabled for your project.");
    _24
    } else {
    _24
    showMessage("Channel media relay failure. Error code: " + code);
    _24
    }
    _24
    runOnUiThread(()-> channelMediaButton.setText("Start Channel Media Relay"));
    _24
    }
    _24
    }

  3. Monitor channel media relay events

    To receive notifications of important channel relay events such as network disconnection, reconnection, and users joining channels, you add the onChannelMediaRelayEvent method to the IRtcEngineEventHandler. In the MainActivity class, add the following method after private final IRtcEngineEventHandler mRtcEventHandler = new IRtcEngineEventHandler() {:


    _13
    @Override
    _13
    public void onChannelMediaRelayEvent(int code) {
    _13
    switch (code) {
    _13
    case 0: // RELAY_EVENT_NETWORK_DISCONNECTED
    _13
    showMessage("User disconnected from the server due to a poor network connection.");
    _13
    break;
    _13
    case 1: // RELAY_EVENT_NETWORK_CONNECTED
    _13
    showMessage("Network reconnected");
    _13
    break;
    _13
    case 2: // RELAY_EVENT_PACKET_JOINED_SRC_CHANNEL
    _13
    showMessage("User joined the source channel");
    _13
    }
    _13
    }

Join multiple channels

The alternate approach to multi-channel live streaming is joining multiple channels. In this section, you learn how to implement joining a second channel in your app.

Implement the user interface

In this example, you use a single Button to join and leave a second channel. To add the Button, in /app/res/layout/activity_main.xml, add the following code before </RelativeLayout>:


_9
<Button
_9
android:id="@+id/secondChannelButton"
_9
android:layout_width="wrap_content"
_9
android:layout_height="wrap_content"
_9
android:layout_below="@+id/LeaveButton"
_9
android:layout_alignEnd="@id/LeaveButton"
_9
android:layout_alignStart="@id/JoinButton"
_9
android:onClick="joinSecondChannel"
_9
android:text="Join second channel" />

Handle the system logic

In your project, import the relevant libraries and declare the required variables.

  1. Add the required libraries

    To connect to multiple channels and access the UI elements, import the corresponding Agora and Android libraries. In /app/java/com.example.<projectname>/MainActivity, add the following to the list of import statements:


    _4
    import io.agora.rtc2.RtcConnection;
    _4
    import io.agora.rtc2.RtcEngineEx;
    _4
    _4
    import android.widget.Button;

  2. Declare the variables you need

    To join and manage a second channel, in /app/java/com.example.<projectname>/MainActivity, add the following declarations to the MainActivity class:


    _6
    private Button secondChannelButton;
    _6
    private RtcConnection rtcSecondConnection;
    _6
    private String secondChannelName = "<name of the second channel>";
    _6
    private int secondChannelUid = 100; // Uid for the second channel
    _6
    private String secondChannelToken = "<access token for the second channel>";
    _6
    private boolean isSecondChannelJoined = false; // Track connection state of the second channel

  3. Enable the user to join another channel

    1. In the onCreate method of the MainActivity class, add the following lines after setupVideoSDKEngine();:


      _2
      secondChannelButton = findViewById(R.id.secondChannelButton);
      _2
      secondChannelButton.setEnabled(false);

    2. Enable this button when a user joins the initial channel. Add the following line to the joinChannel method after isJoined = true;:


      _1
      secondChannelButton.setEnabled(true);

Implement joining multiple channels

To add multi-channel functionality to your app, take the following steps:

  1. Use the multi-channel enabled RtcEngineEx interface

    In the SDK quickstart code, the Agora Engine instance was declared as RtcEngine. To implement multi-channel functionality, you use the multi-channel interface of the Agora Engine RtcEngineEx. In order to switch to the multi-user interface, do the following:

    1. In the MainActivity class, replace the declaration private RtcEngine agoraEngine; with:


      _1
      private RtcEngineEx agoraEngine;

    2. In the setupVideoSDKEngine method, replace the line agoraEngine = RtcEngine.create(config); with the following:


      _1
      agoraEngine = (RtcEngineEx) RtcEngine.create(config);

  2. Join a second channel

    When a user presses the button, the app joins a second channel. If the app is already connected to a second channel, it leaves the channel. To do this, add the following method to the MainActivity class.


    _27
    public void joinSecondChannel(View view) {
    _27
    _27
    if (isSecondChannelJoined) {
    _27
    agoraEngine.leaveChannelEx(rtcSecondConnection);
    _27
    _27
    } else {
    _27
    ChannelMediaOptions mediaOptions = new ChannelMediaOptions();
    _27
    _27
    if (audienceRole.isChecked()) { // Audience Role
    _27
    mediaOptions.autoSubscribeAudio = true;
    _27
    mediaOptions.autoSubscribeVideo = true;
    _27
    mediaOptions.clientRoleType = Constants.CLIENT_ROLE_AUDIENCE;
    _27
    } else { // Host Role
    _27
    mediaOptions.publishCameraTrack = true;
    _27
    mediaOptions.publishMicrophoneTrack = true;
    _27
    mediaOptions.channelProfile = Constants.CHANNEL_PROFILE_LIVE_BROADCASTING;
    _27
    mediaOptions.clientRoleType = Constants.CLIENT_ROLE_BROADCASTER;
    _27
    }
    _27
    _27
    rtcSecondConnection = new RtcConnection();
    _27
    rtcSecondConnection.channelId = secondChannelName;
    _27
    rtcSecondConnection.localUid = secondChannelUid;
    _27
    _27
    agoraEngine.joinChannelEx(secondChannelToken, rtcSecondConnection, mediaOptions, secondChannelEventHandler);
    _27
    isSecondChannelJoined = true;
    _27
    }
    _27
    }

  3. Receive callbacks from the second channel

    The IRtcEngineEventHandler implements channel callbacks. For the second channel, you create another instance of IRtcEngineEventHandler and register it with the Agora Engine when you join the channel using joinChannelEx. To create an instance named secondChannelEventHandler, add the following code to the MainActivity class:


    _40
    // Callbacks for the second channel
    _40
    private final IRtcEngineEventHandler secondChannelEventHandler = new IRtcEngineEventHandler() {
    _40
    @Override
    _40
    public void onJoinChannelSuccess(String channel, int uid, int elapsed) {
    _40
    showMessage(String.format("onJoinChannelSuccess channel %s uid %d", secondChannelName, uid));
    _40
    runOnUiThread(() -> {
    _40
    secondChannelButton.setText("Leave Second Channel");
    _40
    });
    _40
    }
    _40
    _40
    public void onLeaveChannel(RtcStats stats) {
    _40
    isSecondChannelJoined = false;
    _40
    showMessage("Left the channel " + secondChannelName);
    _40
    runOnUiThread(() -> {
    _40
    secondChannelButton.setText("Join Second Channel");
    _40
    });
    _40
    }
    _40
    _40
    @Override
    _40
    public void onUserJoined(int uid, int elapsed) {
    _40
    showMessage(String.format("user %d joined!", uid));
    _40
    _40
    // Create surfaceView for remote video
    _40
    remoteSurfaceView = new SurfaceView(getBaseContext());
    _40
    remoteSurfaceView.setZOrderMediaOverlay(true);
    _40
    _40
    FrameLayout container = findViewById(R.id.remote_video_view_container);
    _40
    _40
    // Add surfaceView to the container
    _40
    runOnUiThread(() -> {
    _40
    if (container.getChildCount() > 0) container.removeAllViews();
    _40
    container.addView(remoteSurfaceView);
    _40
    });
    _40
    _40
    // Setup remote video to render
    _40
    agoraEngine.setupRemoteVideoEx(new VideoCanvas(remoteSurfaceView,
    _40
    VideoCanvas.RENDER_MODE_HIDDEN, uid), rtcSecondConnection);
    _40
    remoteSurfaceView.setVisibility(View.VISIBLE);
    _40
    }
    _40
    };

  4. Update the setupRemoteVideo method

    In this example, you display two remote videos from two different channels. To display a remote video in either frame, update the setupRemoteVideo method by doing the following:

    1. Replace the setupRemoteVideo(int uid) method with the following:


      _7
      private void setupRemoteVideo(int uid, FrameLayout container) {
      _7
      remoteSurfaceView = new SurfaceView(getBaseContext());
      _7
      remoteSurfaceView.setZOrderMediaOverlay(true);
      _7
      container.addView(remoteSurfaceView);
      _7
      agoraEngine.setupRemoteVideo(new VideoCanvas(remoteSurfaceView, VideoCanvas.RENDER_MODE_FIT, uid));
      _7
      remoteSurfaceView.setVisibility(View.VISIBLE);
      _7
      }

    2. To update the setupRemoteVideo method call, in onUserJoined under mRtcEventHandler, replace runOnUiThread(() -> setupRemoteVideo(uid)); with the following:


      _1
      runOnUiThread(() -> setupRemoteVideo(uid,findViewById(R.id.local_video_view_container)));

    3. For null safety, in onUserOffline under mRtcEventHandler, replace runOnUiThread(() -> remoteSurfaceView.setVisibility(View.GONE)); with the following:


      _3
      runOnUiThread(() -> {
      _3
      if (remoteSurfaceView != null) remoteSurfaceView.setVisibility(View.GONE);
      _3
      });

Test your implementation

To ensure that you have implemented multi-channel streaming into your app, follow the relevant testing procedure:

Test channel media Relay

  1. Make sure co-host token authentication is enabled for your project in Agora Console.

  2. In Android Studio, open app/java/com.example.<projectname>/MainActivity and update appId, channelName and destChannelName.

  3. Generate a temporary token in Agora Console using appId and channelName. Use it to update token in MainActivity. Use the same values to generate another token and update sourceChannelToken.

  4. Generate a third token in Agora Console using appId and destChannelName. Use it to update destChannelToken in MainActivity.

  5. In your browser, navigate to the Agora web demo and update App ID, Channel, and Token with the values for your temporary token, then click Join.

  6. Repeat the previous step on a second device, but this time use appId, destChannelName, and destChannelToken to Join the channel.

  7. Connect an Android device to your development device.

  8. In Android Studio, click Run app. A moment later, you see the project installed on your device.

    If this is the first time you run your app, grant camera and microphone permissions.

  9. Select Host and press Join. You see the video from the web browser demo app connected to channelName in the top frame of your app.

  10. Press Start Channel Media Relay. You see the video from the web browser demo app connected to channelName relayed to the web browser demo app connected to destChannelName.

  11. Press Stop Channel Media Relay. The media relaying is stopped.

Test joining multiple channels

  1. In Android Studio, open app/java/com.example.<projectname>/MainActivity and update appId, channelName and secondChannelName.

  2. Generate a temporary token in Agora Console using appId and channelName. Use it to update token in MainActivity.

  3. Generate a second token in Agora Console using appId and secondChannelName. Use it to update secondChannelToken in MainActivity.

  4. In your browser, navigate to the Agora web demo and update App ID, Channel, and Token with the values for your temporary token, then click Join.

  5. Repeat the previous step on a second device, but this time use appId, secondChannelName, and secondChannelToken to Join the channel.

  6. Connect an Android device to your development device.

  7. In Android Studio, click Run app. A moment later, you see the project installed on your device.

    If this is the first time you run your app, grant camera and microphone permissions.

  8. Select Audience

    1. Press Join. You see the video from the web browser demo app connected to channelName in the top frame of your app.
    2. Press Join Second Channel. You see the video from the web browser demo app connected to secondChannelName in the bottom frame of your app.
    3. Press Leave Second Channel and then Leave to exit both channels.
  9. Select Host

    1. Press Join. You see the local video in the top frame of your app. The web browser demo app connected to channelName shows the video from your app.
    2. Press Join Second Channel. You see the video from the web browser demo app connected to secondChannelName in the bottom frame of your app.
    3. Press Leave Second Channel to exit both channels.

Reference

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

  • Ensure that each RtcConnection object has a unique user ID that is not 0.
  • You can configure the publishing and subscribing options for the RtcConnection object in joinChannelEx.
  • Each RtcConnection can publish multiple audio streams and a video stream simultaneously.
  • When calls to startOrUpdateChannelMediaRelay are too frequent, they are rejected and RELAY_EVENT_PACKET_UPDATE_DEST_CHANNEL_REFUSED(8) is returned.