PK 对战是两个主播之间的友好竞争,展示给观众的是主播之间的互动。 本文档介绍如何在使用实时音视频产品(ZEGO Express SDK)实现基本直播功能的基础上,结合即时通讯产品(ZIM SDK)实现 PK 对战功能。
通常有两种玩法:
本文档将介绍如何在直播场景中实现 PK 对战。
在开始之前,请确保您已完成以下步骤:
您可以使用本文档提供的 示例代码 实现以下效果:
主页 | 直播页面 | 接收 PK 对战请求 | PK 对战 |
---|---|---|---|
以下是本文档内容的结构和概述:
如果您计划实现“由业务服务器协调的 PK 对战”,请忽略本节。
此外,如果您的服务器没有信令通道向客户端发送通知,我们建议使用 ZIM 服务端 API 的Command message (signaling message)
实现,详情请参考 ZIM 服务端 API-发送房间消息。
此处使用类似呼叫邀请的方法,实现 PK 对战邀请:
基于 ZIM SDK 提供的 呼叫邀请 功能。该功能提供了呼叫邀请的能力,允许您发送、取消、接受和拒绝邀请,您可以实现 PK 对战邀请、房间邀请。您可以使用 ZIMCallInviteConfig 提供的extendedData
字段,该字段允许您自定义此邀请的类型,从而实现不同的功能。
例如,您可以将业务协议编码为 JSON 并附加到extendedData
中:
{
"room_id": "Room10001",
"user_name": "Alice",
"type": "start_pkbattles" // 或者 "video_call" , "voice_call"
}
这样,接收方在收到邀请后,可以根据type
字段判断并执行不同的业务逻辑。
基于此实现呼叫邀请的过程如下(以“Alice 邀请 Bob 进行 PK 对战, Bob 接受并连接 PK 对战”为例):
sequenceDiagram participant Alice participant sdk1 as ZIM SDK participant server as ZEGO Server participant sdk2 as ZIM SDK participant Bob Alice ->> sdk1 : 发送 PK 对战请求 sdk1 ->> server: Alice 的 PK 对战请求 server ->> sdk2 : Alice 的 PK 对战请求 sdk2 ->> Bob : Alice 的 PK 对战请求 Bob ->> Bob : Bob 同意与 Alice 进行 PK 对战 Bob ->> sdk2 : 发送同意 PK 对战的信令 sdk2 ->> server: Bob 的同意 PK 对战的信令 server ->> sdk1 : Bob 的同意 PK 对战的信令 sdk1 ->> Alice : Bob 的同意 PK 对战的信令 Note over Alice, Bob: 开始 PK 对战
有关这些接口的具体用法,请参考 呼叫邀请。
在实现 PK 对战邀请时,需要注意在邀请接口的 extendedData
字段中,需要传递双方的信息:
A 在发起 PK 对战邀请时,除了上述的 type
之外,还需要将自己的 roomID
和 userName
传递给对方,以便对方知道开始 PK 对战的相关信息。
B 在接受 PK 对战邀请时,同样需要在 type
之外,传递自己的 roomID
和 userName
。
在第一次邀请时,请调用 callInvite
并将其设置为高级模式。在此模式下,您需要使用 callEnd
或 callQuit
结束或退出 PK 对战,并且您可以使用 callingInvite
继续邀请其他人加入 PK 对战。
/** 向用户发送呼叫邀请 - 高级模式 */
// 发送呼叫邀请
List<String> invitees; // 邀请对象列表
invitees.add("421234"); // 邀请对象的 ID
ZIMCallInviteConfig config = new ZIMCallInviteConfig();
config.timeout = 200; // 邀请的超时时间,单位为秒,范围为 1-600
// mode 表示呼叫邀请模式,ADVANCED 表示设置为高级模式。
config.mode = ADVANCED;
zim.callInvite(invitees, config, new ZIMCallInvitationSentCallback() {
@Override
public void onCallInvitationSent(String callID, ZIMCallInvitationSentInfo info, ZIMError errorInfo) {
// 这里的 callID 是由 SDK 内部生成的,用于在用户发起呼叫后,唯一标识呼叫邀请;
// 后续,当发起者取消呼叫或被邀请者接受或拒绝呼叫时,将使用该 callID。
}
});
开始之前,请确保您熟悉以下概念:
什么是房间和流?
PK 解决方案需要使用 混流:混流指将多个流合并为一个流,这样观众只需播放该流,即可观看多个主播的画面。使用混流的必要性和重要优势如下:
在 PK 开始之前,每个主播都会推流,观众可以直接拉主播的流。然而,在 PK 开始后,拉流的方法将发生变化:
以上是 PK 场景中推流和拉流的基本框架。在接下来的章节中,我们将根据主播和观众的逻辑提供详细操作流程。
当主播准备开始 PK 对战时,需要执行以下操作:
通常,流 ID 与房间 ID 和用户 ID 相关。例如,在本文档的附带示例代码中,流 ID 规则为 "${roomID}_${userID}_main_host"
。因此,您可以使用此规则,连接每个主播的流 ID,然后调用 startPlayingStream
来拉对方的流。双方的信息可以从邀请接口的回调和双方之间传递的 extendedData
中获取。
如果您的 PK 对战是由服务端安排和匹配的,并且服务端发送了开始 PK 信号,您需要在向主播发送 PK 开始通知时,包含对手主播的 userID
、roomID
、userName
等信息。
混流可以由客户端或服务端发起,您可以根据需要选择:
本文档的附带示例代码使用了 “由主播客户端手动发起混流” 的方法。
混流参数相关注意事项:
混流布局:通常,在 PK 对战中,每个观众在界面左侧看到自己房间的画面。因此,在 PK 开始后,需要发起混流任务,即每个房间中的每个主播都需要发起一个混流任务。在混合流时,每个主播需要将自己的视频放在流混流布局的左侧。layout
参数可以参考下方代码,或者您可以参考 混流 文档,了解更多关于混流布局的详细信息。
混流分辨率:以主播的默认 540p 分辨率为例,每个单一流的分辨率为 width=540, height=960
。将两个流并排组合后,混合流的分辨率应为 width=540*2, height=960
。如果要降低混合流的分辨率,可以保持此宽高比,并减小混流分辨率,例如,使用 540*480
分辨率和 width=540*2/2, height=960/2
。请注意,如果需要降低流混流分辨率,还需要相应调整 layout
参数。我们的示例代码使用了混合流分辨率为 width=810
和 height=720
。
混流任务 ID 和流 ID:通常,每个流混流任务只有一个输出流,PK 场景也是如此。因此,可以将混流任务 ID 和流 ID 使用相同的 ID,例如 '${roomID}__mix'
,这样可以更容易地管理未来的流混流任务。
以下是一个完整的混流参数示例代码片段:
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) {
//getInstance 为示例代码封装方法 https://github.com/ZEGOCLOUD/zegocloud_sdk_demo_android/blob/38cc405f591e42412cfb0542bfe4682b9a311b76/best_practice/app/src/main/java/com/zegocloud/demo/bestpractice/internal/sdk/ZEGOSDKManager.java#L37
//getCurrentRoomID 为示例代码封装方法 https://github.com/ZEGOCLOUD/zegocloud_sdk_demo_android/blob/38cc405f591e42412cfb0542bfe4682b9a311b76/best_practice/app/src/main/java/com/zegocloud/demo/bestpractice/internal/sdk/express/ExpressService.java#L791
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);
}
}
});
}
}
在客户端发起的流混流方法中,重要的是要检查调用混流接口时返回的错误码。如果错误码不为 0,则表示流混流失败。在这种情况下,客户端应采取适当的操作,例如重试混流任务,以确保 PK 对战的正常进行。
如果您想设置混流的布局,可以使用 ZegoMixerTask
的 setInputList 方法自定义布局。此处我们展示一些简单的设置规则。
例如,如果有两个人,您可以将布局设置为每个人占据屏幕的一半。您可以按如下方式进行设置:
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;
}
如果有超过两个人,您可以根据需要设置布局。
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;
}
因此,示例代码已实现了混流的默认布局,当 PK 中有 2 个人时,是并排布局。当有超过 2 个人时,屏幕将被分为两行或三行。
如果您需要更复杂的自定义布局,请参考 混流布局文档,了解混流布局的方式,并在示例代码中使用 ZEGOLiveStreamingManager.setMixLayoutProvider 来修改布局:
ZEGOLiveStreamingManager.getInstance().setMixLayoutProvider(new MixLayoutProvider() {
@Override
public ArrayList<ZegoMixerInput> getMixVideoInputs(List<String> streamList,
ZegoMixerVideoConfig videoConfig) {
ArrayList<ZegoMixerInput> inputList = new ArrayList<>();
// ... 您的逻辑
return inputList;
}
});
如果您想要从服务端管理混流,需要在 PK 对战开始时,在服务端启动这两个混流任务。有关从客户端发起时设置混流参数的说明,请参考上述说明。
有关如何从服务端管理混流任务的详细信息,请参考服务端 API:
当 PK 对战开始时,需要通知观众 PK 对战已经开始。观众在收到通知后,可以处理观看 PK 的逻辑。我们建议使用 ZIM SDK 的 房间属性 功能通知观众。
此功能允许应用客户端在房间中设置和同步自定义房间属性。房间属性以键值对的方式存储在 ZEGO 服务器上,ZEGO 服务器处理写冲突调解和其他问题,以确保数据一致性。
同时,应用客户端对房间属性的修改会通过 ZEGO 服务器实时同步到房间中的所有其他观众。
每个房间最多允许设置 20 个属性,key
的长度限制为 16 字节,value
的长度限制为 1024 字节。
当 PK 开始时,主播需要调用 setRoomAttributes 来设置房间的附加属性,表示房间已进入 PK 状态。建议在房间的附加属性中包含以下信息:
host_user_id
:该房间的主播的 userID。request_id
:该 PK 的 requestID。pk_users
:参与 PK 的主播。在设置房间属性时,为了防止在主播异常退出房间时删除房间的附加属性,导致无法恢复 PK 对战,请确保将 isDeleteAfterOwnerLeft
参数设置为 false
。
生成房间属性的示例代码片段:
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
ublic void onRoomAttributesOperated(String roomID, ArrayList<String> errorKeys, ZIMError errorInfo) {
}
});
}
设置完成后,观众将收到 onRoomAttributesUpdated 回调。
在收到 onRoomAttributesUpdated 回调后,如果观众发现有与 PK 对战相关的新添加字段,可以开始处理观看 PK 的逻辑。
在本文档的附带示例代码中,混流的流 ID 规则为 ${currentRoomID}__mix
,建议您也设计并使用与房间 ID 相关的这种规则。
拉普通流和混流的方法是相同的。观众可以调用 startPlayingStream
来开始播放混流。
需要处理以下两个细节:
一旦观众成功拉混流,就可以开始观看 PK 对战。由于混流已经包含了两个主播的音视频,观众不需要再渲染主播的单流。
因此,观众可以使用 mutePlayStreamAudio 和 mutePlayStreamVideo 来暂停播放主播的单流的音频和视频。这样可以进一步降低成本,避免观众端不必要的流量消耗和性能损失。
不建议此时使用 stopPlayingStream 来停止拉主播的单流。如果这样做,观众在 PK 结束后需要重新拉流,而流切换的速度比使用 mute
要慢得多,这会导致用户体验较差。
在示例代码中,主播可以手动点击结束按钮来终止 PK 对战。
当主播点击结束按钮时,还需要通知另一个主播 PK 已经结束,可以通过 ZEGOLivesSreamingManager
的 endPKBattle 方法来实现。当另一个主播收到此通知时,也需要处理结束 PK 的逻辑。
quitPKBattle 和 endPKBattle 的区别在于,前者只允许玩家退出 PK,而后者会让所有人停止 PK。结束 PK 对战需要执行以下操作:
主播:
观众:
当观众收到 onRoomAttributesUpdated 回调,表示与 PK 相关的属性已被删除时,可以开始处理以下逻辑:
通过利用定期发送 SEI 消息,可以将其视为客户端之间的“心跳”,并可以用于检测 PK 对战中的异常情况。当一段时间内,未收到主播的 SEI 消息时,可以认为主播的直播流出现异常。逻辑如下:
当检测到主播异常时,将在主播的视频画面上出现“主播重新连接”的提示。详情请参考 PKBattleView.java
public void onTimeOut(boolean timeout) {
if (timeout) {
connectTipsView.setVisibility(VISIBLE);
} else {
connectTipsView.setVisibility(GONE);
}
}
此外,您还需要定义一个最大超时时间。例如,在示例代码中,如果一个 PK 主播的 SEI 消息超过 60 秒没有收到,所有参与 PK 的用户将把该异常主播从 PK 中移除。
//addLiveStreamingListener 为示例代码封装方法 https://github.com/ZEGOCLOUD/zegocloud_sdk_demo_android/blob/38cc405f591e42412cfb0542bfe4682b9a311b76/best_practice/app/src/main/java/com/zegocloud/demo/bestpractice/internal/ZEGOLiveStreamingManager.java#L218
ZEGOLiveStreamingManager.getInstance().addLiveStreamingListener(new LiveStreamingListener() {
// ...
@Override
//onPKUserConnecting 为示例代码封装方法 https://github.com/ZEGOCLOUD/zegocloud_sdk_demo_android/blob/38cc405f591e42412cfb0542bfe4682b9a311b76/best_practice/app/src/main/java/com/zegocloud/demo/bestpractice/components/cohost/LiveStreamingView.java#L295
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();
}
}
}
});
如果您想向随机主播发起 PK 挑战,您可能需要使用服务端进行匹配。例如,客户端可以向服务端发送请求,服务端将响应目标主播的用户 ID。客户端收到该用户 ID 后,可以使用该用户 ID 调用 startPKBattle
函数,向目标主播发送自动 PK 请求。在使用此接口时,接收到 PK 请求的主播将默认自动同意开始 PK。
根据具体的流媒体场景,可以使用不同的方法来获取主播的设备状态。
场景 1:在 PK 过程中,主播如何获取彼此的设备状态?
在这种实时流媒体互动场景中,主播可以使用 onRemoteCameraStateUpdate 和 onRemoteMicStateUpdate 来获取对方的摄像头和麦克风状态。
需要注意的是,需要在调用 createEngine 后调用 setEngineConfig 来启用此功能。以下是示例代码:
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);
场景 2:观众获取主播的设备状态
在播放直播
或混流
的流时,建议使用 SEI(Supplemental Enhancement Information)方案来获取主播的设备状态。这包括 PK 对战场景中的以下两种情况:
在这种情况下,观众无法接收到“场景 1”中提到的回调。因此,当主播推流时,他们需要使用 sendSEI 更新自己的设备状态,而拉流端将通过回调 onPlayerRecvSEI 接收。
如需要发送 SEI,您需要创建一个定时器来定期发送 SEI 消息。建议每 200ms 发送一次。在定时器中,定期发送以下信息以同步设备状态:
{
'type': 0, // 设备状态
'senderID': myUserID, // 由于将多个流的 SEI 混合到混流中,需要在 SEI 中添加 senderID 标识符。
'cam': false, // true: 打开,false: 关闭
'mic': true, // true: 打开,false: 关闭
}
您可以参考示例代码中的 相关代码 来具体实现此部分。
在实现视频通话、直播等视频场景时,请注意视频分辨率与价格之间的关系。
在同一房间内播放多个视频流时,将基于分辨率总和进行计费,并且不同分辨率将对应不同的计费档次。
计算最终分辨率时,包括以下视频流:
在您的应用上线前,请确保您已检查所有配置并确认适用于您业务场景的计费档次,以避免不必要的损失。更多详情请参见 定价。
在大多数情况下,PK 对战期间不支持共同主持功能。因此,如果您已经实现了共同主持功能,需要考虑以下事项: 一旦房间进入 PK 对战状态,所有客户端角色都需要临时禁用与共同主持人相关的所有功能。
以下是一些建议:
通常,可以使用 音量变化与音频频谱 来渲染音量级别。
然而,当观众端拉混流时,方法略有不同:
enableSoundLevel
设置为 true
,以便混流包含输入流的音量级别信息。soundLevelID
,以便拉流端可以确定其属于哪个音量级别数据。soundLevelID
来确定其属于哪个音量级别。在 PK 期间,可能需要暂时静音另一个主播的音频。静音后,房间中的主播和观众都无法听到另一个主播的音频。
contentType
设置为 ZegoMixerInputContentTypeVideoOnly
(无需调用StopMixerTask
,直接调用startMixerTask
)。在客户端成功发起混流后,客户端可以向业务服务端报告房间已进入 PK 状态。
您可以使用 git diff 查看这些更改,并按照我们的文档逐项检查更改并将其添加到您的项目中。
首先,您需要在 build.gradle 中升级 SDK 版本,请使用以下或更高版本的 SDK。
implementation 'im.zego:zim:2.12.0'
implementation 'im.zego:express-video:3.10.0'
示例代码的 internal
部分已经完全升级以适应多人 PK 功能。这部分发生了重大变化。我们建议您直接删除旧的 internal
文件夹,并将新的 internal
复制到您的项目中。
internal
进行任何修改。internal
中的代码,在将新的 internal
复制到您的项目后,您需要将您对旧的 internal
进行的更改应用到新的 internal
中,这是一个必要的步骤。components
和 activity
,以及 res
中的布局文件,也已经升级以适应多人 PK,这部分的更改相对较小:完成升级后,请对新旧功能进行全面测试。如果在测试过程中遇到任何疑似的错误,请以相同的方式直接测试我们的示例代码,以确定是示例代码的问题还是其他原因引入的问题。
联系我们
文档反馈