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
, andtype = "start_pkbattles"
. - She sends the invite using
callInvite()
, attaching the JSON viaextendedData
. - Bob receives the invite, parses the data, and confirms it’s a PK battle request.
- Upon acceptance, Bob responds with his own
roomID
anduserName
, 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
anduserName
in the JSON. - When accepting the invitation, return your own
roomID
anduserName
so the initiator can establish the PK connection. - In advanced mode, use
callEnd()
orcallQuit()
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 and960
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 half810×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:
- Start stream mixing: Start stream mixing, Callback on stream mixing started
- Stop stream mixing: Stop stream mixing, Callback on stream mixing stopped
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.
- 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:
host_user_id
: The userID of the host of this room.request_id
: The requestID of this PK.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.
- 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:
- 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.
- 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.
- 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:
- Stop playing the single stream of the other host.
- End the mixed stream task.
- Remove the PK-related attributes from the room attribute.
- 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:
- Call stopPlayingStream to stop playing the mixed stream and return to the state of a single host’s live stream.
- Since the audience muted the single stream of the host when the PK started, you need to call mutePlayStreamAudio and mutePlayStreamVideo again to unmute.
- 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:
- Start a 2-second timer after the PK begins.
- Record the last time each host sent an SEI message.
- 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.
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 ofLive Streaming
andInteractive 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:
- In PK mode, the audience plays the mixed stream.
- 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!