Talk to us
Talk to us
menu

Implementing PK Battles in Live Streams

Implementing PK Battles in Live Streams

Live streaming, as a typical scene of entertainment and social interaction going global, has been developing for many years and is facing increasingly serious problems of product homogenisation. How can social applications find new breakthroughs in the market, increase business revenue and establish their own differentiating advantages? Adding more ways to play in live streaming scenarios is one of the important methods.

PK gameplay, a key tool to boost the atmosphere in live streams, is highly popular among various interaction methods. It drives host popularity, fosters fan engagement, and allows hosts to redirect followers to each other. Adding PK enhances content diversity, enriches user-host interaction, and makes live streams more entertaining. This article will show you how to quickly implement this feature in Android live streams using ZEGOCLOUD.

What are PK Battles?

PK (Player Knockout) battles are competitive live streaming sessions where two hosts go head-to-head in real time, usually judged by viewer interactions like gifts, likes, or votes. It’s popular on platforms like TikTok, Bigo Live, and Twitch.

How PK Battles in Live Streaming Work

In a PK battle, two streamers broadcast live at the same time and compete head-to-head in real time. Their video streams are combined into a single split-screen layout, allowing viewers to watch both hosts simultaneously. Audiences can support their favorite host by sending virtual gifts, messages, or likes—actions that contribute to each host’s score. At the end of the countdown, the platform tallies the results and announces a winner, often triggering fun consequences or rewards.

From a technical perspective, implementing PK battles involves real-time stream publishing, mixing, interaction handling, and result synchronization. Using ZEGOCLOUD’s live streaming SDK, developers can easily merge two live streams into one interface, synchronize audience data across both streams, and integrate real-time messaging and gift tally APIs. The SDK also supports smooth audio and video delivery with ultra-low latency, ensuring a seamless experience for both hosts and viewers. By using stream mixing, event callbacks, and in-stream overlays, platforms can create engaging, competitive PK experiences with minimal development effort.

The Benefits of PK Battles in Live Streaming

PK battles do more than just entertain—they play a strategic role in driving engagement, monetization, and platform growth. Here are the key benefits:

  • Boosted Viewer Engagement
    The competitive nature of PK battles keeps audiences actively involved through votes, gifts, and real-time comments, extending watch time and interaction.
  • Increased Revenue
    Viewers often support their favorite host by sending virtual gifts during battles, making PK sessions one of the top revenue-generating formats in live streaming.
  • Enhanced Host Visibility
    PK battles allow streamers to collaborate and cross-promote each other, attracting new followers and expanding their fan base.
  • Stronger Community Dynamics
    Fans tend to rally behind their favorite streamer, creating a team-like atmosphere that fosters loyalty and repeat participation.
  • Gamified User Experience
    The countdown timer, win/loss outcome, and post-battle reactions introduce game-like mechanics that keep the content fresh and addictive.

Implementing Profitable PK Battles in Live Streams

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.
  • Please contact technical support to activate the Stream Mixing service.
  • Activate the In-app Chat service

Preview the effect

You can achieve the following effect with the demo provided in this doc:https://www.zegocloud.com/docs/live-streaming/implement-pk-battles?platform=android&language=java

Send PK battle invitations

To implement PK battle invitations, you can follow a similar approach as used in call invitations, leveraging the signaling feature provided by the ZEGOCLOUD In-app Chat SDK (ZIM SDK).

The ZIM SDK enables sending, accepting, rejecting, and canceling invitations through its call invitation capability. By customizing the extendedData field in ZIMCallInviteConfig, you can define the type of invitation—such as PK battles, room invites, video calls, or voice calls—and implement different business logic accordingly.

How It Works

You can encode your business data into a JSON object and attach it to the extendedData field when sending the invitation. For example:

{
    "room_id": "Room10001",
    "user_name": "Alice",
    "type": "start_pkbattles" // or "video_call" , "voice_call"
}

Once received, the invitee can parse the type field and determine the corresponding business logic (e.g., start a PK battle or join a room).

PK Invitation Process: Example Flow

Scenario: Alice invites Bob to a PK battle

  • Alice composes a JSON invitation containing her roomID, userName, and type = "start_pkbattles".
  • She sends the invite using callInvite(), attaching the JSON via extendedData.
  • Bob receives the invite, parses the data, and confirms it’s a PK battle request.
  • Upon acceptance, Bob responds with his own roomID and userName, allowing the platform to connect both streams.
  • Both parties can now enter the PK session.

Example Code: Send PK Battle Invitation (Advanced Mode)

/** Send call invitation to users - Advanced mode */
// Send call invitation
List<String> invitees;  // List of invitees
invitees.add("421234");       // ID of the invitee
ZIMCallInviteConfig config = new ZIMCallInviteConfig(); 
config.timeout = 200; // Timeout for invitation in seconds, range 1-600
// mode represents the call invitation mode, ADVANCED represents setting to advanced mode.
config.mode = ADVANCED;

zim.callInvite(invitees, config, new ZIMCallInvitationSentCallback() {
    @Override
    public void onCallInvitationSent(String callID, ZIMCallInvitationSentInfo info, ZIMError errorInfo) {
        // The callID here is generated by the SDK internally to uniquely identify 
        a call invitation after the user initiates a call; 
        // later, when the initiator cancels the call, or the invitee accepts/rejects the call, this callID will be used.
    }
 });

Important Notes

  • When sending a PK invitation, include your own roomID and userName in the JSON.
  • When accepting the invitation, return your own roomID and userName so the initiator can establish the PK connection.
  • In advanced mode, use callEnd() or callQuit() to end or exit the PK session manually.
  • You can continue inviting others using callingInvite() during the PK session.

Stream publishing & playing for PK

Before the PK battle starts, each host simply publishes their own stream, and the audience can play that stream individually. However, once the PK battle begins, the logic for publishing and playing streams changes to support real-time interaction and better viewer experience.

What is Room and Stream?

  • Room: A virtual space provided by ZEGOCLOUD that allows users to join, interact, and exchange real-time audio, video, and messages.
  • Stream Publishing: The process of capturing and uploading audio/video data to the ZEGOCLOUD cloud in real time.
  • Stream Playing: The process of pulling and playing audio/video streams from ZEGOCLOUD’s cloud service.

What is Stream-Mixing?

Stream mixing is the process of combining multiple live streams into a single output stream. It’s a critical part of enabling smooth PK battle viewing.

Why Stream Mixing Is Essential in PK Battles

When two hosts go live in a PK battle, using stream mixing provides significant technical and experiential advantages:

  • Synchronization: Ensures that both hosts’ audio and video are aligned for the viewer.
  • Performance Optimization: Viewers only need to play one mixed stream, saving bandwidth and reducing CPU/GPU load.
  • Device Compatibility: Avoids overheating and lag issues on lower-end devices.
  • Streamlined Experience: Easier management and playback of PK sessions.

Streaming Logic During PK Battles

  • Each host publishes their own stream as usual.
  • Each host plays the stream of the other host to enable two-way interaction.
  • The audience temporarily mutes the individual streams of hosts to conserve bandwidth.
  • Stream mixing is triggered—either on the client or server side—to merge both hosts’ streams into one.
  • The audience plays the mixed stream, watching the full PK battle interaction in a unified view.

Start PK battle – Host logic

When the host is ready to start the PK battle, the following operations need to be performed:

Play the single stream of the each host

Generally, the stream ID is related to the room ID and user ID. For example, in the accompanying demo of this doc, the stream ID rule is "${roomID}_${userID}_main_host". Therefore, you can concatenate each host’s stream ID using this rule and then call startPlayingStream to play the stream of the opponent. The information of both sides can be obtained from the callback of the invitation interface and the extendedData passed between both sides.

If your PK battle is scheduled and matched by the server and the start PK signal is sent down by the server, you need to include the userID, roomID, userName, and other information of the opponent host when sending the PK start notification to the host on the server side.

Start the stream mixing task

Stream mixing can be initiated by the client or the server, and you can choose accordingly:

  • Initiating stream mixing on the client side has a simpler architecture but requires managing client-side issues like complex networks and potential app exits.
  • In contrast, server-side stream mixing is less affected by client abnormalities, but it involves more complex client-server interactions and requires stronger server-side development skills.

The accompanying demo of this doc uses the “Manual stream mixing initiated by the host client” approach.

Initiating stream mixing from the client

In a PK battle, stream mixing is initiated by each host to combine both video streams into one unified layout for the audience. Below are several important parameters and best practices to keep in mind when setting up client-side stream mixing:

1. Stream Mixing Layout

During a PK session, the audience always sees their room’s host on the left side of the mixed video layout.

To maintain this consistency:

  • Each host must initiate their own stream mixing task.
  • When configuring the layout, the local video stream should be placed on the left, and the opponent’s stream on the right.
  • Layout parameters (position, width, height) must be adjusted accordingly.

For more layout configuration options, refer to the Mix the Live Streams Documentation.

2. Stream Mixing Resolution

Assume the default single-stream resolution is 540×960 (width × height).

When combining two streams side by side:

  • The mixed stream resolution should be 1080×960, i.e., 540 * 2 width and 960 height.
  • To reduce bandwidth or device load, you can lower the resolution proportionally (while keeping the aspect ratio), such as:
    • 540×480 → reduce each stream by half
    • 810×720 → as used in the ZEGOCLOUD demo
3. Task ID and Mixed Stream ID

For easier management:

  • Use a consistent naming convention for both the stream mixing task ID and the output stream ID.
  • A common practice is to use ${roomID}__mix as both IDs.
  • This approach simplifies tracking and controlling mixing tasks (e.g., stopping or updating them).
4. Example Code of Mixing Parameters

Here is an example code snippet with the complete stream mixing parameters:


public static final int MIX_VIDEO_WIDTH = 720;
public static final int MIX_VIDEO_HEIGHT = 810;
public static final int MIX_VIDEO_BITRATE = 1500;
public static final int MIX_VIDEO_FPS = 15;

private void updatePKMixTask(IZegoMixerStartCallback callback) {
    if (pkBattleInfo != null) {
        List<String> pkUserStreamList = new ArrayList<>();
        for (PKUser pkUser : pkBattleInfo.pkUserList) {
            if (pkUser.getCallUserState() == ZIMCallUserState.ACCEPTED) {
                pkUserStreamList.add(pkUser.getPKUserStream());
            }
        }

        ZegoMixerVideoConfig videoConfig = new ZegoMixerVideoConfig();
        videoConfig.width = MIX_VIDEO_WIDTH;
        videoConfig.height = MIX_VIDEO_HEIGHT;
        videoConfig.bitrate = MIX_VIDEO_BITRATE;
        videoConfig.fps = MIX_VIDEO_FPS;

        MixLayoutProvider mixLayoutProvider = ZEGOLiveStreamingManager.getInstance().getMixLayoutProvider();
        ArrayList<ZegoMixerInput> mixVideoInputs;
        if (mixLayoutProvider == null) {
            mixVideoInputs = getMixVideoInputs(pkUserStreamList, videoConfig);
        } else {
            mixVideoInputs = mixLayoutProvider.getMixVideoInputs(pkUserStreamList, videoConfig);
        }

        if (task == null) {
            String mixStreamID = ZEGOSDKManager.getInstance().expressService.getCurrentRoomID() + "_mix";

            task = new ZegoMixerTask(mixStreamID);
            task.videoConfig = videoConfig;

            task.setInputList(mixVideoInputs);

            ZegoMixerOutput mixerOutput = new ZegoMixerOutput(mixStreamID);
            ArrayList<ZegoMixerOutput> mixerOutputList = new ArrayList<>();
            mixerOutputList.add(mixerOutput);
            task.setOutputList(mixerOutputList);

            task.enableSoundLevel(true);
        } else {
            task.inputList = mixVideoInputs;
        }

        ZEGOSDKManager.getInstance().expressService.startMixerTask(task, new IZegoMixerStartCallback() {
            @Override
            public void onMixerStartResult(int errorCode, JSONObject data) {
                // 1005026 non_exists_stream_list
                if (errorCode == 0) {
                    updatePKRoomAttributes();
                }
                if (callback != null) {
                    callback.onMixerStartResult(errorCode, data);
                }

            }
        });
    }
}

In the client-initiated stream mixing approach, it is important to check the error code returned when calling the stream mixing interface at this step. If the error code is not 0, it means that the stream mixing has failed. In this case, appropriate actions should be taken on the client-side, such as retrying the stream mixing task, to ensure the normal progress of the PK battle.

customize the mix steam layout

If you want to set the layout for mixing the streams, you can customize the layout by using the setInputList method of ZegoMixerTask. Here we show some simple setting rules.

For example, if you have two people, you can set the layout to have each person occupying half of the screen. You can set it like this:

private ArrayList<ZegoMixerInput> getMixVideoInputs(List<String> streamList, ZegoMixerVideoConfig videoConfig) {
    ArrayList<ZegoMixerInput> inputList = new ArrayList<>();
    if (streamList.size() == 2) {
        for (int i = 0; i < streamList.size(); i++) {
            int left = (videoConfig.width / streamList.size()) * i;
            int top = 0;
            int right = (videoConfig.width / streamList.size()) * (i + 1);
            int bottom = videoConfig.height;
            ZegoMixerInput input = new ZegoMixerInput(streamList.get(i), ZegoMixerInputContentType.VIDEO,
                new Rect(left, top, right, bottom));
            input.renderMode = ZegoMixRenderMode.FILL;
            inputList.add(input);
        }
    } else {
        //...
    }

    return inputList;
}

If you have more than two people, you can set up the layout as you wish. You can set it up like this:

private ArrayList<ZegoMixerInput> getMixVideoInputs(List<String> streamList, ZegoMixerVideoConfig videoConfig) {
    ArrayList<ZegoMixerInput> inputList = new ArrayList<>();
    //...
    if (streamList.size() == 2) {
        for (int i = 0; i < streamList.size(); i++) {
            int left = (videoConfig.width / streamList.size()) * i;
            int top = 0;
            int right = (videoConfig.width / streamList.size()) * (i + 1);
            int bottom = videoConfig.height;
            ZegoMixerInput input = new ZegoMixerInput(streamList.get(i), ZegoMixerInputContentType.VIDEO,
                new Rect(left, top, right, bottom));
            input.renderMode = ZegoMixRenderMode.FILL;
            inputList.add(input);
        }
    } else if (streamList.size() == 3) {
        for (int i = 0; i < streamList.size(); i++) {
            int left, top, right, bottom;
            if (i == 0) {
                left = 0;
                top = 0;
                right = videoConfig.width / 2;
                bottom = videoConfig.height;
            } else if (i == 1) {
                left = videoConfig.width / 2;
                top = 0;
                right = left + videoConfig.width / 2;
                bottom = top + videoConfig.height / 2;
            } else {
                left = videoConfig.width / 2;
                top = videoConfig.height / 2;
                right = left + videoConfig.width / 2;
                bottom = top + videoConfig.height / 2;
            }
            ZegoMixerInput input = new ZegoMixerInput(streamList.get(i), ZegoMixerInputContentType.VIDEO,
                new Rect(left, top, right, bottom));
            input.renderMode = ZegoMixRenderMode.FILL;
            inputList.add(input);
        }
    } else if (streamList.size() == 4 || streamList.size() == 6) {
        int row = 2;
        int maxCellCount = streamList.size() % 2 == 0 ? streamList.size() : (streamList.size() + 1);
        int column = maxCellCount / row;
        int cellWidth = videoConfig.width / column;
        int cellHeight = videoConfig.height / row;
        int left, top, right, bottom;
        for (int i = 0; i < streamList.size(); i++) {
            left = cellWidth * (i % column);
            top = cellHeight * (i < column ? 0 : 1);
            right = left + cellWidth;
            bottom = top + cellHeight;
            ZegoMixerInput input = new ZegoMixerInput(streamList.get(i), ZegoMixerInputContentType.VIDEO,
                new Rect(left, top, right, bottom));
            input.renderMode = ZegoMixRenderMode.FILL;
            inputList.add(input);
        }
    } else if (streamList.size() == 5) {
        for (int i = 0; i < streamList.size(); i++) {
            int left, top, right, bottom;
            if (i == 0) {
                left = 0;
                top = 0;
                right = videoConfig.width / 2;
                bottom = videoConfig.height / 2;
            } else if (i == 1) {
                left = videoConfig.width / 2;
                top = 0;
                right = left + videoConfig.width / 2;
                bottom = top + videoConfig.height / 2;
            } else if (i == 2) {
                left = 0;
                top = videoConfig.height / 2;
                right = left + videoConfig.width / 3;
                bottom = top + videoConfig.height / 2;
            } else if (i == 3) {
                left = videoConfig.width / 3;
                top = videoConfig.height / 2;
                right = left + videoConfig.width / 3;
                bottom = top + videoConfig.height / 2;
            } else {
                left = (videoConfig.width / 3) * 2;
                top = videoConfig.height / 2;
                right = left + videoConfig.width / 3;
                bottom = top + videoConfig.height / 2;
            }
            ZegoMixerInput input = new ZegoMixerInput(streamList.get(i), ZegoMixerInputContentType.VIDEO,
                new Rect(left, top, right, bottom));
            input.renderMode = ZegoMixRenderMode.FILL;
            inputList.add(input);
        }
    } else {
        int row = 3;
        int column = 3;
        int cellWidth = videoConfig.width / column;
        int cellHeight = videoConfig.height / row;
        int left, top, right, bottom;
        for (int i = 0; i < streamList.size(); i++) {
            left = cellWidth * (i % column);
            top = cellHeight * (i < column ? 0 : 1);
            right = left + cellWidth;
            bottom = top + cellHeight;
            ZegoMixerInput input = new ZegoMixerInput(streamList.get(i), ZegoMixerInputContentType.VIDEO,
                new Rect(left, top, right, bottom));
            input.renderMode = ZegoMixRenderMode.FILL;
            inputList.add(input);
        }
    }

    return inputList;
}

So the demo has implemented the default layout for mixing streams: when there are 2 people in a PK, it is a side-by-side layout. When there are more than 2 people, the screen will be divided into two or three rows.

If you need a more complex custom layout, please refer to this complete document on mixing layouts to understand the way of mixing layouts and use the ZEGOLiveStreamingManager.setMixLayoutProvider() in the Demo to modify the layout:

ZEGOLiveStreamingManager.getInstance().setMixLayoutProvider(new MixLayoutProvider() {
    @Override
    public ArrayList<ZegoMixerInput> getMixVideoInputs(List<String> streamList,
        ZegoMixerVideoConfig videoConfig) {
        ArrayList<ZegoMixerInput> inputList = new ArrayList<>();
        // ... your logic
        return inputList;
    }
});

Initiating stream mixing from the server

If you want to manage stream mixing from the server, you need to start these two stream mixing tasks on the server side when the PK battle begins. Refer to the above instructions for setting the stream mixing parameters when initiating from the client.

For details on how to manage stream mixing tasks from the server side, refer to the server-side API:

Note:

  • In the server-initiated stream mixing approach, the server needs to use Callback on logged out room to monitor the client status of the host. If it detects an abnormal exit, it promptly stops the stream mixing task and notifies the host to end the PK battle.
  • If your server does not have a signaling channel to send notifications to the client, we recommend using the Command message (signaling message) in the Send in-room messages server-side API of ZIM to achieve this.
  1. Notifying the audience of the start of PK battle

When the PK battle starts, it is necessary to notify the audience that the PK battle has begun. After receiving the notification, the audience can handle the logic of watching the PK. How to notify the audience? We recommend using the room attribute feature of the ZIM SDK to achieve this.

If you haven’t used this feature before, you can click here to see an introduction

When the PK starts, the host needs to call setRoomAttributes to set the additional attributes of the room, indicating that the room has entered the PK state. It is recommended to include the following information in the room’s additional attributes:

  1. host_user_id: The userID of the host of this room.
  2. request_id: The requestID of this PK.
  3. pk_users: Participating anchors in PK.

When setting the room attributes, make sure to set the isDeleteAfterOwnerLeft parameter to false. This is to prevent the room’s additional attributes from being deleted when the host exits the room abnormally, which would cause the PK battle to be unable to be resumed.

Example code snippet for generating room attributes:

    private void updatePKRoomAttributes() {
        HashMap<String, String> hashMap = new HashMap<>();

        if (ZEGOLiveStreamingManager.getInstance().getHostUser() != null) {
            hashMap.put("host_user_id", ZEGOLiveStreamingManager.getInstance().getHostUser().userID);
        }

        hashMap.put("request_id", pkBattleInfo.requestID);

        List<PKUser> acceptedUsers = new ArrayList<>();
        for (PKUser pkUser : pkBattleInfo.pkUserList) {
            if (pkUser.hasAccepted()) {
                acceptedUsers.add(pkUser);
            }
        }
        for (PKUser pkUser : acceptedUsers) {
            for (ZegoMixerInput zegoMixerInput : task.inputList) {
                if (Objects.equals(pkUser.getPKUserStream(), zegoMixerInput.streamID)) {
                    pkUser.rect = zegoMixerInput.layout;
                }
            }
        }
        hashMap.put("pk_users", acceptedUsers.toString());

        ZIMRoomAttributesSetConfig config = new ZIMRoomAttributesSetConfig();
        config.isDeleteAfterOwnerLeft = false;
        ZEGOSDKManager.getInstance().zimService.setRoomAttributes(hashMap, config,
            new ZIMRoomAttributesOperatedCallback() {
                @Override
                public void onRoomAttributesOperated(String roomID, ArrayList<String> errorKeys, ZIMError errorInfo) {

                }
            });
    }

After the settings are completed, the audience will receive the onRoomAttributesUpdated callback. Now let’s explain the logic on the audience side.

Start PK battle – Audience logic

After receiving the onRoomAttributesUpdated callback, if the audience finds that there are newly added fields related to PK battle, they can start processing the logic of watching PK.

  1. Audience needs to play the mixed stream according to the stream ID rule

In the accompanying demo of this doc, the stream ID rule for mixing streams is ${currentRoomID}__mix. It is recommended that you also design and use this kind of rule related to the room ID.

The method for playing normal streams and mixed streams is the same. The audience can call startPlayingStream to start playing the mixed stream.

There are two details that need to be handled:

  1. It is necessary to listen for the onPlayerStateUpdate callback to determine whether the stream playing is successful. If playing the mixed stream fails, the user needs to be prompted with a loading message and corresponding retry logic should be implemented.
  2. Since it takes some time to generate the mixed stream, the audience may not be able to play the mixed stream immediately. In order to optimize the user experience and avoid black screens, after playing the mixed stream, it is necessary to listen for the onPlayerRecvVideoFirstFrame and onPlayerRecvAudioFirstFrame callbacks. After receiving either of these callbacks, the mixed stream can be rendered. This can avoid black screens.
  3. During PK, the audience needs to mute the single stream of the host

Once the audience successfully plays the mixed stream, they can start watching the PK battle. Since the mixed stream already contains the audio and video of both hosts, the audience doesn’t need to render the single stream of the host during the PK.

Therefore, the audience can use mutePlayStreamAudio and mutePlayStreamVideo to temporarily stop playing the audio and video of the host’s single stream. This can further reduce costs and avoid unnecessary traffic consumption and performance loss on the audience’s devices.

It is not recommended to use stopPlayingStream to stop playing the single stream of the host at this time. If this is done, the audience will need to re-play the stream after the PK ends, and the speed of stream switching will be much slower compared to using mute. This will result in a poor user experience.

End PK battle & Quit PK battle

In the demo, the host can manually click the end button to terminate the PK battle.

When the host clicks the end button, they also need to notify the other host that the PK has ended. This can be achieved by endPKBattle method in ZEGOLivesSreamingManager.When the other host receives this notification, they also need to handle the logic for ending the PK. The difference between quitPKBattle and endPKBattle is that the former only allows the player to quit the PK, while the latter will make everyone stop PK. The following operations need to be performed to end the PK battle:

Host:

  1. Stop playing the single stream of the other host.
  2. End the mixed stream task.
  3. Remove the PK-related attributes from the room attribute.
  4. Adjust the UI to return to the state of a single host’s live stream.

Audience:

When the audience receives the callback onRoomAttributesUpdated indicating that the PK-related attributes have been removed, they can start handling the following logic:

  1. Call stopPlayingStream to stop playing the mixed stream and return to the state of a single host’s live stream.
  2. Since the audience muted the single stream of the host when the PK started, you need to call mutePlayStreamAudio and mutePlayStreamVideo again to unmute.
  3. Adjust the UI to return to the state of a single host’s live stream.

Detect abnormal situations in a PK battle

By leveraging the periodic sending of SEI messages, it can be treated as a “heartbeat” To detect abnormalities during a PK, use a heartbeat mechanism. When SEI messages from the host aren’t received for a set period, it indicates a potential issue.

Logic:

  1. Start a 2-second timer after the PK begins.
  2. Record the last time each host sent an SEI message.
  3. On each timer trigger, check if the time since the last SEI message exceeds a threshold (e.g., 5 seconds). If so, the host’s livestream is deemed abnormal.

If an anomaly is detected, display a “host reconnecting” prompt on the host’s video screen.

See PKBattleView.java

public void onTimeOut(boolean timeout) {
    if (timeout) {
        connectTipsView.setVisibility(VISIBLE);
    } else {
        connectTipsView.setVisibility(GONE);
    }
}

In addition, you also need to define a maximum timeout period. For example, in the Demo, if a PK host has no SEI for more than 60 seconds, all users participating in the PK will remove that exceptional host from the PK.

ZEGOLiveStreamingManager.getInstance().addLiveStreamingListener(new LiveStreamingListener() {
    // ...
    @Override
    public void onPKUserConnecting(String userID, long duration) {
        if (duration >= 60_000) {
            ZEGOSDKUser currentUser = ZEGOSDKManager.getInstance().expressService.getCurrentUser();
            if (!Objects.equals(currentUser.userID, userID)) {
                ZEGOLiveStreamingManager.getInstance().removeUserFromPKBattle(userID);
            } else {
                ZEGOLiveStreamingManager.getInstance().quitPKBattle();
            }
        }
    }
});

Achieve server-side matching process

If you want to challenge a random host to PK, you may need to use the server for matchmaking. For example, the client can send a request to the server, and the server will respond with the user ID of the target host. After receiving this user ID, the client can call the startPKBattle function to send an automatic PK request to the target host using this user ID. When using this interface, the host receiving the PK request will automatically agree to start the PK by default.

Get the device status of the host process

Depending on the specific streaming scenario, different methods are used to obtain the device status.

Scenario 1: During PK, how do hosts obtain the device status of each other?

In this scenario, where hosts are streaming and interacting with each other in real-time, you can use onRemoteCameraStateUpdate and onRemoteMicStateUpdate to obtain the camera and microphone status of the other host.

It is important to note that this feature needs to be enabled by calling setEngineConfig after calling createEngine. Here is an example of the code:

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

If your audience is using Interactive Live Streaming to play the stream, you can also use this method. You can further understand the concepts of Live Streaming and Interactive Live Streaming here: Live Streaming vs. Interactive Live Streaming

Scenario 2: Audience obtaining the device status of the host

When playing the stream for Live Streaming or Mixed Stream, it is recommended to use the SEI (Supplemental Enhancement Information) solution to obtain the device status of the host. This includes the following two cases in the PK battle scenario:

  1. In PK mode, the audience plays the mixed stream.
  2. In non-PK mode, the audience plays the host’s single stream.

In this case, the audience cannot receive the callbacks mentioned in “Scenario 1”. Therefore, when the host is publishing the stream, they need to update their device status using sendSEI, and the playing side will receive the callback onPlayerRecvSEI.

What is SEI?

To send SEI, you need to create a timer to periodically send SEI messages. It is recommended to send them every 200ms. In the timer, regularly send the following information to synchronize device status:

{
  'type': 0, // device_state
  'senderID': myUserID, // Due to the mixing of SEI from multiple streams into the mixed stream, you need to add a senderID identifier in the SEI.
  'cam': false, // true:on, false:off
  'mic': true, // true:on, false:off
}

You can refer to the relevant code in the demo for the specific implementation of this part.

Conclusion

Implementing PK battles in the live streaming scenario is a powerful way to enhance user retention and platform revenue.

In addition to the PK game, ZEGOCLOUD addresses the three main pain points of long initial load times, blurred images and lag in live streaming applications, providing a smooth live streaming solution. By integrating interactive live streaming services, it achieves instant loading, seamless ultra-high definition video quality and a one-click video quality enhancement solution. This solution intelligently adapts to the user’s network conditions, phone performance and viewing context to deliver the best video quality. Sign up today and enjoy 10,000 minutes free of charge.

Let’s Build APP Together

Start building with real-time video, voice & chat SDK for apps today!

Talk to us

Take your apps to the next level with our voice, video and chat APIs

Free Trial
  • 10,000 minutes for free
  • 4,000+ corporate clients
  • 3 Billion daily call minutes

Stay updated with us by signing up for our newsletter!

Don't miss out on important news and updates from ZEGOCLOUD!

* You may unsubscribe at any time using the unsubscribe link in the digest email. See our privacy policy for more information.