Live Streaming
  • iOS
  • Android : Java
  • Web
  • Flutter
  • React Native
  • Electron
  • Unity3D
  • Windows
  • macOS
  • Linux
  • Overview
  • Live Streaming vs. Interactive Live Streaming
  • Develop your app
    • Live Streaming
      • Quick start
      • Enhance basic livestream
      • CDN
      • Play live streams
    • Interactive Live Streaming
  • Best practices
    • Implement co-hosting
    • Implement a live audio room
    • Implement PK battles
  • Upgrade the livestream
    • Advanced features
      • Enhance the livestream
        • Share the screen
        • Improve your appearance in the livestream
        • Beautify & Change the voice
        • Output the livestream in H.265
        • Watermark the live/Take snapshots
        • Config video codec
        • Visualize the sound level
      • Message signaling
        • Convey extra information using SEI
        • Broadcast real-time messages to a room
        • Quotas and limits
      • Ensure livestream quality
        • Test network and devices in advance
        • Check the room connection status
        • Monitor streaming quality
        • Configure bandwidth management
      • Play media files
        • Play media files
        • Play sound effects
      • Record video media data
      • Join multiple rooms
      • Publish multiple live streams
      • Low-latency live streaming
      • Use the bit mask
      • Common audio config
      • Playing streams via URL
      • Mix the live streams
    • Distincitve features
      • Set the voice hearing range
      • Single stream transcoding
      • Low-light enhancement
      • Customize the video and audio
  • Upgrade using Add-on
  • Resources & Reference
    • SDK
    • Sample code
    • API reference
      • Client APIs
      • Server APIs
    • Debugging
      • Error codes
    • FAQs
    • Key concepts
  • Documentation
  • Live Streaming
  • Best practices
  • Implement co-hosting

Implement co-hosting

Last updated:2024-01-15 17:38

This doc will introduce how to implement the co-hosting feature in the live streaming scenario.

Prerequisites

Before you begin, make sure you complete the following:

  • Complete SDK integration by referring to Quick Start doc.
  • Download the demo that comes with this doc.
  • Activate the In-app Chat service.
    /Pics/InappChat/ActivateZIMinConsole2.png

Preview the effect

You can achieve the following effect with the demo provided in this doc:

Homepage Live stream page Receive co-hosting request Start co-hosting

Understand the tech

1. What is roomrequest?

The process of co-hosting implemented based on roomrequest, roomrequest is a protocol or message to manage communication and connections in networks. ZEGOCLOUD packages all roomrequest capabilities into a SDK, providing you with a readily available real-time roomrequest API.

2. How to send & receive roomrequest messages through the ZIM SDK interface

The ZIM SDK provides rich functionality for sending and receiving messages, see Send & Receive messages (roomrequest). And here, you will need to use the customizable roomrequest message: ZIMCommandMessage

Complete demo code for this section can be found at ZIMService.java.

(1) Send RoomRequest (ZIMCommandMessage) in the room by calling sendMessage with the following:

zim.sendMessage(commandMessage, mRoomID, ZIMConversationType.ROOM, config, new ZIMMessageSentCallback() {
    // ...
    @Override
    public void onMessageSent(ZIMMessage message, ZIMError errorInfo) {
        // ...
    }
});

(2) After sending, other users in the room will receive the RoomRequest from the onReceiveRoomMessage callback. You can listen to this callback by following below:

zim.setEventHandler(new ZIMEventHandler() {
    @Override
    public void onReceiveRoomMessage(ZIM zim, ArrayList<ZIMMessage> messageList, String fromRoomID) {
        super.onReceiveRoomMessage(zim, messageList, fromRoomID);

        // ...
    }
});

3. How to customize business RoomRequest

Complete demo code for this section can be found at ZIMService.java and RoomRequest.java.

JSON RoomRequest encoding

Since a simple String itself is difficult to express complex information, RoomRequest can be encapsulated in JSON format, making it more convenient for you to organize the protocol content of the RoomRequest.

Taking the simplest JSON RoomRequest as an example: {"action_type": 10000}, in such a JSON RoomRequest, you can use the action_type field to express different RoomRequest types, such as:

  • Sending a request: {"action_type": RoomRequestAction.ACTION_REQUEST}
  • Canceling a request: {"action_type": RoomRequestAction.ACTION_CANCEL}
  • Accepting a request: {"action_type": RoomRequestAction.ACTION_ACCEPT}
  • Rejecting a request: {"action_type": RoomRequestAction.ACTION_REJECT}

In addition, you can also extend other common fields for RoomRequest, such as senderID , receiverID,extended_data :

public class RoomRequest {

    // ...
    public String toString() {
        JSONObject jsonObject = new JSONObject();
        try {
            jsonObject.put("action_type", actionType);
            jsonObject.put("sender_id", sender);
            jsonObject.put("receiver_id", receiver);
            jsonObject.put("extended_data", extendedData);
            jsonObject.put("request_id", requestID);
        } catch (JSONException e) {
            throw new RuntimeException(e);
        }
        return jsonObject.toString();
    }

    // ...
}

public @interface RoomRequestAction {
    int ACTION_REQUEST = 0;
    int ACTION_ACCEPT = 1;
    int ACTION_REJECT = 2;
    int ACTION_CANCEL = 3;
}

JSON RoomRequest decoding

And users who receive RoomRequest can decode the JSON RoomRequest and know and process specific business logic based on the fields in it, such as:

zim.setEventHandler(new ZIMEventHandler() {
    @Override
    public void onReceiveRoomMessage(ZIM zim, ArrayList<ZIMMessage> messageList, String fromRoomID) {
        super.onReceiveRoomMessage(zim, messageList, fromRoomID);

        for (ZIMMessage zimMessage : messageList) {
            if (zimMessage instanceof ZIMCommandMessage) {
                ZIMCommandMessage commandMessage = (ZIMCommandMessage) zimMessage;
                String message = new String(commandMessage.message, StandardCharsets.UTF_8);
                try {
                    JSONObject jsonObject = new JSONObject(message);
                    if (jsonObject.has("action_type")) {
                        jsonObject.put("message_id", String.valueOf(commandMessage.getMessageID()));
                        if (currentUser != null) {
                            onReceiveRoomRequestMessage(jsonObject);
                        }
                    } else {
                        // ...
                    }
                } catch (JSONException e) {
                     // ...
                }
            }
        }
    }
    // ...
)

// ...
private void onReceiveRoomRequestMessage(JSONObject jsonObject) throws JSONException {
    String sender = jsonObject.getString("sender_id");
    String receiver = jsonObject.getString("receiver_id");
    int actionType = jsonObject.getInt("action_type");
    String extendedData = jsonObject.getString("extended_data");
    String messageID = jsonObject.getString("message_id");
    if (currentUser.userID.equals(receiver)) {
        if (actionType == RoomRequestAction.ACTION_REQUEST) {
            // ...
        } else if (actionType == RoomRequestAction.ACTION_ACCEPT) {
            // ...
        } else if (actionType == RoomRequestAction.ACTION_CANCEL) {
            // ...
        } else if (actionType == RoomRequestAction.ACTION_REJECT) {
            // ...
        }
    }
}

Further extending RoomRequest

Based on this pattern, when you need to do any protocol extensions in your business, you only need to extend the extended_data field of the RoomRequest to easily implement new business logic, such as:

  • Muting audience: After receiving the corresponding RoomRequest, the UI blocks the user from sending live bullet messages.
  • Sending virtual gifts: After receiving the RoomRequest, show the gift special effects.
  • Removing audience: After receiving the RoomRequest, prompt the audience that they have been removed and exit the room.

Friendly reminder: After reading the following text and further understanding the implementation of co-hosting RoomRequest, you will be able to easily extend your live streaming business RoomRequest.

The demo in this document is a pure client API + ZEGOCLOUD solution. If you have your own business server and want to do more logical extensions, you can use our Server API to pass RoomRequest and combine your server's room business logic to increase the reliability of your app.


sequenceDiagram
    participant Alice
    participant appServer as App Server
    participant server as ZEGOCLOUD Server
    participant sdk as SDK
    participant Bob

    Alice  ->> appServer  : Send co-hosting request
    appServer   ->> appServer  : Process your own business logic
    appServer   ->> server: Alice's co-hosting request
    server ->> sdk  : Alice's co-hosting request
    sdk   ->> Bob   : Alice's co-hosting request


    Note over Alice, Bob: ...

Implementation

Integrate and start to use the ZIM SDK

If you have not used the ZIM SDK before, you can read the following section:

1. Import the ZIM SDK

To import the ZIM SDK, do the following:

  1. Set up repositories.

    • If your Android Gradle Plugin is v7.1.0 or later: go to the root directory of your project, open the settings.gradle file, and add the following line to the dependencyResolutionManagement:

      ...
      dependencyResolutionManagement {
          repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
          repositories {
              maven { url 'https://storage.zego.im/maven' }
              mavenCentral()
              google()
          }
      }

      If you can not find the above fields in settings.gradle, it's probably because your Android Gradle Plugin version is lower than v7.1.0.

      For more details, see Android Gradle Plugin Release Note v7.1.0.

    • If your Android Gradle Plugin is earlier than 7.1.0: go to the root directory of your project, open the build.gradle file, and add the following line to the allprojects:

      ...
      allprojects {
          repositories {
              maven { url 'https://storage.zego.im/maven' }
              mavenCentral()
              google()
          }
      }
  2. Declare dependencies:

    Go to the app directory, open the build.gradle file, and add the following line to the dependencies. (x.y.z is the SDK version number, to obtain the latest version number, see Release Notes.

    ...
    dependencies {
        ...
        implementation 'im.zego:zim:x.y.z'
    }
2. Create and manage SDK instances

After successful integration, you can use the Zim SDK like this:

import im.zego.zim.ZIM

Creating a ZIM instance is the very first step, an instance corresponds to a user logging in to the system as a client.

ZIMAppConfig appConfig = new ZIMAppConfig();
appConfig.appID = yourAppID;
appConfig.appSign = yourAppSign;
ZIM.create(appConfig, application);

Later on, we will provide you with detailed instructions on how to use the ZIM SDK to develop the call invitation feature.

Manage multiple SDKs more easily

In most cases, you need to use multiple SDKs together. For example, in the live streaming scenario described in this doc, you need to use the zim sdk to implement the co-hosting feature, and then use the zego_express_engine sdk to implement the live streaming feature.

If your app has direct calls to SDKs everywhere, it can make the code difficult to manage and troubleshoot. To make your app code more organized, we recommend the following way to manage these SDKs:

1. Create a wrapper layer for each SDK so that you can reuse the code to the greatest extent possible.

Create a ZIMService class for the zim sdk, which manages the interaction with the SDK and stores the necessary data. Please refer to the complete code in ZIMService.java.

public class ZIMService {

    // ...

    public void initSDK(Application application, long appID, String appSign) {
        zimProxy.create(application, appID, appSign);
        // ...
    }
}

class ZIMProxy {

    private SimpleZIMEventHandler zimEventHandler;

    public void create(Application application, long appID, String appSign) {
        ZIMAppConfig zimAppConfig = new ZIMAppConfig();
        zimAppConfig.appID = appID;
        zimAppConfig.appSign = appSign;
        ZIM.create(zimAppConfig, application);

        zimEventHandler = new SimpleZIMEventHandler();
        if (getZIM() != null) {
            ZIM.getInstance().setEventHandler(zimEventHandler);
        }
    }

}

Similarly, create an ExpressService class for the zego_express_engine sdk, which manages the interaction with the SDK and stores the necessary data. Please refer to the complete code in ExpressService.java.

public class ExpressService {

    // ...
    public void initSDK(Application application, long appID, String appSign, ZegoScenario scenario) {
        ZegoEngineConfig config = new ZegoEngineConfig();
        config.advancedConfig.put("notify_remote_device_unknown_status", "true");
        config.advancedConfig.put("notify_remote_device_init_status", "true");
        ZegoExpressEngine.setEngineConfig(config);
        engineProxy.createEngine(application, appID, appSign, scenario);
        // ...
    }
}

class ExpressEngineProxy {

    private SimpleExpressEventHandler expressEventHandler;

    public void createEngine(Application application, long appID, String appSign, ZegoScenario scenario) {
        ZegoEngineProfile profile = new ZegoEngineProfile();
        profile.appID = appID;
        profile.appSign = appSign;
        profile.scenario = scenario;
        profile.application = application;
        expressEventHandler = new SimpleExpressEventHandler();
        ZegoExpressEngine.createEngine(profile, expressEventHandler);
    }
}

With the service, you can add methods to the service whenever you need to use any SDK interface.

E.g., easily add the connectUser method to the ZIMService when you need to implement login:

public class ZIMService {
    // ...
    public void connectUser(String userID, String userName, ZIMLoggedInCallback callback) {
        ZIMUserInfo zimUserInfo = new ZIMUserInfo();
        zimUserInfo.userID = userID;
        zimUserInfo.userName = userName;
        zim.login(zimUserInfo, new ZIMLoggedInCallback() {
            @Override
            public void onLoggedIn(ZIMError errorInfo) {
                // ...
            }
        });
    }
}
2. After completing the service encapsulation, you can further simplify the code by creating a ZEGOSDKManager to manage these services.

As shown below. Please refer to the complete code in ZEGOSDKManager.java.

public class ZEGOSDKManager {
    public ExpressService expressService = new ExpressService();
    public ZIMService zimService = new ZIMService();

    private static final class Holder {
        private static final ZEGOSDKManager INSTANCE = new ZEGOSDKManager();
    }

    public static ZEGOSDKManager getInstance() {
        return Holder.INSTANCE;
    }

    public void initSDK(Application application, long appID, String appSign,ZegoScenario scenario) {
        expressService.initSDK(application, appID, appSign,scenario);
        zimService.initSDK(application, appID, appSign);
    }
}

In this way, you have implemented a singleton class that manages the SDK services you need. From now on, you can get an instance of this class anywhere in your project and use it to execute SDK-related logic, such as:

  • When the app starts up: call ZEGOSDKManager.getInstance().initSDK(application,appID,appSign);
  • When login : call ZEGOSDKManager.getInstance().connectUser(userID,userName,callback);

Later, we will introduce how to add call invitation feature based on this.

Later, we will introduce how to add co-hosting feature based on this.

Send & Cancel a co-hosting request

The implementation of sending and canceling co-hosting requests is similar, with only the type of RoomRequest being different. Here, sending will be used as an example to explain the implementation of the demo.

In the Demo, a request co-host button has been placed in the lower right corner of the LivePage as seen from the audience perspective. When the button is clicked, the following actions will be executed.

  1. Encode the JSON RoomRequest, where the action_type is defined as RoomRequestAction.ACTION_REQUEST in the demo.
  1. Call sendRoomRequest to send the RoomRequest. (sendRoomRequest simplifies the sendMessage interface of ZIM SDK.)
  • If the method call is successful: the applying status of the local end (i.e. the audience) will be switched to applying for co-hosting, and the Request Co-host button will switch to Cancel CoHost.
  • If the method call fails: an error message will be prompted. In actual app development, you should use a more user-friendly UI to prompt the failure of the co-hosting application.
@Override
protected void afterClick() {
    super.afterClick();
    // ...
    RoomRequestExtendedData extendedData = new RoomRequestExtendedData();
    extendedData.roomRequestType = RoomRequestType.REQUEST_COHOST;
    ZEGOSDKManager.getInstance().zimService.sendRoomRequest(hostUser.userID, jsonObject.toString(),
        new RoomRequestCallback() {
            @Override
            public void onRoomRequestSend(int errorCode, String requestID) {
                if (errorCode == 0) {
                    mRoomRequestID = requestID;
                }
            }
        });
// ... 
}

 public void sendRoomRequest(String receiverID, String extendedData, RoomRequestCallback callback) {
        if (zimProxy.getZIM() == null || currentRoom == null || currentUser == null) {
            return;
        }
        RoomRequest roomRequest = new RoomRequest();
        roomRequest.receiver = receiverID;
        roomRequest.sender = currentUser.userID;
        roomRequest.extendedData = extendedData;
        roomRequest.actionType = RoomRequestAction.ACTION_REQUEST;

        byte[] bytes = roomRequest.toString().getBytes(StandardCharsets.UTF_8);
        ZIMCommandMessage commandMessage = new ZIMCommandMessage(bytes);
        zimProxy.sendMessage(commandMessage, currentRoom.roomID, ZIMConversationType.ROOM, new ZIMMessageSendConfig(),
            new ZIMMessageSentCallback() {
                @Override
                public void onMessageAttached(ZIMMessage message) {

                }

                @Override
                public void onMessageSent(ZIMMessage message, ZIMError errorInfo) {
                    if (errorInfo.code == ZIMErrorCode.SUCCESS) {
                        roomRequest.requestID = String.valueOf(message.getMessageID());
                        roomRequestMap.put(roomRequest.requestID, roomRequest);
                    }
                  // ...
                }
            });
    }

public void updateUI() {
    ZEGOSDKUser localUser = ZEGOSDKManager.getInstance().expressService.getCurrentUser();
    ZIMService zimService = ZEGOSDKManager.getInstance().zimService;
    if (ZEGOLiveStreamingManager.getInstance().isCoHost(localUser.userID)) {
        coHostUI();
    } else if (ZEGOLiveStreamingManager.getInstance().isAudience(localUser.userID)) {
        RoomRequest roomRequest = zimService.getRoomRequestByRequestID(mRoomRequestID);
        if (roomRequest == null) {
            audienceUI();
        } else {
            requestCoHostUI();
        }
    }
}
  1. Afterwards, the local end (audience end) will wait for the response from the host.
  • If the host rejects the co-host request: the applying status of the local end will be switched to not applying.
  • If the host accepts the co-host request: the co-hosting will start (see the co-hosting section for details on starting and ending co-hosting).

Accept & Reject the co-hosting request

  1. In the demo, when the host receives a co-hosting request RoomRequest, a red dot will appear on the button in the user list, and the host can choose to accept or reject the user's co-hosting request after clicking on the user list.
  2. After the host responds, a RoomRequest of acceptance or rejection will be sent. The related logic of sending RoomRequest will not be further described here.

The relevant code snippet is as follows, and the complete code can be found in RoomRequestButton.java and RoomRequestListAdapter

Code snippet
  1. The code related to displaying the red dot on the user list when a request is received is as follows:
// ...
@Override
public void onInComingRoomRequestReceived(RoomRequest request) {
    checkRedPoint();
}

private void showRedPoint() {
    redPoint.setVisibility(View.VISIBLE);
}

private void hideRedPoint() {
    redPoint.setVisibility(View.GONE);
}

public void checkRedPoint() {
    ZEGOSDKUser localUser = ZEGOSDKManager.getInstance().expressService.getCurrentUser();
    if (ZEGOLiveStreamingManager.getInstance().isHost(localUser.userID)) {
        List<RoomRequest> myReceivedRoomRequests = ZEGOSDKManager.getInstance().zimService.getMyReceivedRoomRequests();
        boolean showRedPoint = false;
        for (RoomRequest roomRequest : myReceivedRoomRequests) {
            String extendedData = roomRequest.extendedData;
            RoomRequestExtendedData data = RoomRequestExtendedData.parse(extendedData);
            if (data != null && data.roomRequestType == roomRequestType) {
                showRedPoint = true;
                break;
            }
        }
        if (showRedPoint) {
            showRedPoint();
        } else {
            hideRedPoint();
        }
    }
}
  1. In the room request list, host can choose to click accept or reject.
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
    // ...

    agree.setOnClickListener(v -> {
        ZEGOSDKManager.getInstance().zimService.acceptRoomRequest(roomRequest.requestID, new RoomRequestCallback() {
            @Override
            public void onRoomRequestSend(int errorCode, String requestID) {

            }
        });
    });

    disagree.setOnClickListener(v -> {
        ZEGOSDKManager.getInstance().zimService.rejectRoomRequest(roomRequest.requestID, new RoomRequestCallback() {
            @Override
            public void onRoomRequestSend(int errorCode, String requestID) {

            }
        });
    });
}

Start & End co-hosting

The logic after starting co-hosting is the same as Implementation. If you are not familiar with how to publish and play streams and render them, refer to Implementation.

When the audience receives the RoomRequest that the host agrees to co-host, they can become a co-host and start co-host live streaming by calling related methods of zego_express_engine for previewing and publishing streams.

Key code

Complete code can be found in LiveStreamingActivity.java and ExpressService.java.

public class LiveStreamingActivity extends AppCompatActivity {
    // ...
    @Override
    public void onOutgoingRoomRequestAccepted(RoomRequest request) {
        RoomRequestExtendedData data = RoomRequestExtendedData.parse(extendedData);
        if (data != null && data.roomRequestType == RoomRequestType.REQUEST_COHOST) {
            ExpressService expressService = ZEGOSDKManager.getInstance().expressService;
            ZEGOSDKUser currentUser = expressService.getCurrentUser();
            if (ZEGOLiveStreamingManager.getInstance().isAudience(currentUser.userID)) {
                List<String> permissions = Arrays.asList(permission.CAMERA, permission.RECORD_AUDIO);
                requestPermissionIfNeeded(permissions, new RequestCallback() {

                    @Override
                    public void onResult(boolean allGranted, @NonNull List<String> grantedList,
                        @NonNull List<String> deniedList) {
                        ZEGOLiveStreamingManager.getInstance().startCoHost();
                    }
                });
            }
        }
    }
    // ...
}

End co-hosting

After the audience ends co-hosting, they need to call relevant methods of zego_express_engine to stop previewing and publishing streams. The complete code can be found in the CoHostButton.java. And the key code is as follows:

public void endCoHost() {
    removeCoHost(ZEGOSDKManager.getInstance().expressService.getCurrentUser());
    ZEGOSDKManager.getInstance().expressService.openMicrophone(false);
    ZEGOSDKManager.getInstance().expressService.openCamera(false);
    ZEGOSDKManager.getInstance().expressService.stopPreview();
    ZEGOSDKManager.getInstance().expressService.stopPublishingStream();
}

Resolution And Pricing Attention!

Please pay close attention to the relationship between video resolution and price when implementing video call, live streaming, and other video scenarios.

When playing multiple video streams in the same room, the billing will be based on the sum of the resolutions, and different resolutions will correspond to different billing tiers.

The video streams that are included in the calculation of the final resolution are as follows:

  1. Live streaming video view (such as host view, co-host view, PKBattle view, etc.)
  2. Video call's video view for each person
  3. Screen sharing view
  4. Resolution of the cloud recording service
  5. Resolution of the Live stream creation

Before your app goes live, please make sure you have reviewed all configurations and confirmed the billing tiers for your business scenario to avoid unnecessary losses. For more details, please refer to Pricing.

Conclusion

Congratulations! Hereby you have completed the development of the co-hosting feature.

If you have any suggestions or comments, feel free to share them with us via Discord. We value your feedback.

Page Directory