用户端在 MediaSideInfo(媒体次要信息) 中获取到主播端发送的题目信息。题目信息会按照预先约定的 JSON 格式组织。
如果开发者使用即构提供的主播端推流,请务必按照特定的 JSON 格式解析数据。
如果开发者自行实现主播端推流,内部约定主播端与观众端的通信格式即可。
获取 MediaSideInfo 的开发步骤如下所示。
ZEGO SDK 提供了相关接口,用于设置回调,接收媒体次要信息。
/**
* 设置回调,接收媒体次要信息
*/
mZegoLiveRoom.setZegoMediaSideCallback(new IZegoMediaSideCallback() {
@Override
public void onRecvMediaSideInfo(String streamID, ByteBuffer byteBuffer, int dataLen) {
}
});
其中,当用户端收到媒体次要信息时,SDK 会调用之。因此,对题目的处理,均在 onRecvMediaSideInfo
中进行。
媒体次要信息会作为 onRecvMediaSideInfo
的入参传递进去,开发者需要在该函数内,根据业务要求,处理媒体次要信息。
主播向观众发送答案的 JSON 数据格式为:
{
"seq": 1,
"type": "question",
"data": {
"id": "0ca175b9c0f726a831d895e269332461",
"index": 1,
"activity_id": "123456",
"title": "下面哪个省的面积最大? ",
"options": [
{
"answer": "A",
"option": "河北"
},
{
"answer": "B",
"option": "山东"
},
{
"answer": "C",
"option": "湖南"
}
]
}
}
JSON 字段含义为:
参数名 | 类型 | 说明 |
---|---|---|
seq | Int | 协议序列号,一轮活动内递增 |
type | String | 消息类型;可用类型为:question(题目),answer(答案),sum(用户列表汇总) |
id | String | 题目 id;题目的唯一标识,由客户保证唯一性;最大 64 bytes(包括字符串结束符), 不能包含字符 ',' |
index | Int | 题目序号;第 n 题序号为 n |
activity_id | String | 活动 id,答题场次(活动)的唯一标识;由客户保证唯一性;一个答题场次有多个题目; 最大 64 bytes(包括字符串结束符),不能包含字符 ',' |
title | String | 题目标题;可与 index 配合展示为题目描述 |
options | Array | 题目选项列表 |
answer | String | 某一选项的序号 |
option | String | 某一选项的描述 |
演示 Demo 中处理媒体次要信息的示例代码如下:
/**
* 设置回调,接收媒体次要信息
*/
mZegoLiveRoom.setZegoMediaSideCallback(new IZegoMediaSideCallback() {
@Override
public void onRecvMediaSideInfo(String streamID, ByteBuffer byteBuffer, int dataLen) {
try {
if (dataLen == 0) {
AppLogger.getInstance().writeLog(this.getClass(), "onRecvMediaSideInfo data is empty");
return;
}
//转换成JSONObject格式
JSONObject jsonObject = ZegoCommon.getInstance().getJsonObjectFrom(byteBuffer, dataLen);
if (jsonObject != null && jsonObject.isNull("type")) {
int seq;
seq = jsonObject.getInt("seq");
//seq比较去重
if (seq <= mediaSeq) {
AppLogger.getInstance().writeLog(this.getClass(), "onRecvMediaSideInfo repeat seq %d, discard mediaSeq %d", seq, mediaSeq);
return;
}
mediaSeq = seq;
}
JSONObject jsonObjectData = jsonObject.getJSONObject("data");
/**
* 显示答题
*/
if (!jsonObject.isNull("type") && "question".equals(jsonObject.getString("type"))) {
//转换成map格式
Map<String, Object> map = ZegoCommon.getInstance().getMapFromJsonToMapQuestion(jsonObjectData);
//当前线程是子线程,需要用handler在主线程控制
handler.sendMessage(handler.obtainMessage(ANSWER_DIALOG, map));
/**
* 答案处理
*/
} else if (!jsonObject.isNull("type") && "answer".equals(jsonObject.getString("type"))) {
//转换成map格式
Map<String, Object> map = ZegoCommon.getInstance().getMapFromJsonToMapAnswer(jsonObjectData);
//当前线程是子线程,需要用handler在主线程控制
handler.sendMessage(handler.obtainMessage(ATATISTICS_ANSWER, map));
}
} catch (org.json.JSONException e) {
AppLogger.getInstance().writeLog(this.getClass(), "json data is conversion exception");
e.printStackTrace();
}
}
});
关于媒体次要信息功能的详细说明,可参考文档:视频进阶-媒体次要信息
开发者从媒体次要信息中,解析出题目,并正确展示给用户后,用户可以开始作答。SDK 提供了统一的接口,用于用户答题。
//发送答题信息
mZegoLiveRoom.relay(ZegoRelay.RelayTypeDati, relayDate, new IZegoRelayCallback() {
@Override
public void onRelay(int errorCode, String roomID, String relayResult) {
}
});
调用该接口答题,需要注意以下事项:
RelayTypeDati
请注意,1 中指定的 JSON 格式如下。开发者必须按照此格式传递字符串,格式不对会导致后台解析错误,答题出错。
{
"activity_id": "",
"question_id": "",
"answer": "",
"user_data": ""
}
参数说明为:
参数名 | 类型 | 说明 |
---|---|---|
activity_id | String | 活动 id,答题场次(活动)的唯一标识;由客户保证唯一性;一个答题场次有多个题目; 最大 64 bytes(包括字符串结束符),不能包含字符 ',';观众端从主播端获取活动 id |
question_id | String | 题目 id;题目的唯一标识,由客户保证唯一性;最大 64 bytes(包括字符串结束符), 不能包含字符 ',';观众端从主播端获取题目 id |
answer | String | 观众答题,要与主播下发题目中的 answer 字段值(例如 "A")保持一致 |
user_data | String | 用户自定义数据。开发者上传 user_data 后可调用拉取流水信息接口获取。该数据可以 用于进行消息一致性、完整性校验。 |
Demo 中示例代码如下:
@SuppressLint("HandlerLeak")
Handler retryHandler = new Handler() {
@Override
public void handleMessage(final Message msg) {
final String relayDate = (String) msg.obj;
mZegoLiveRoom.relay(ZegoRelay.RelayTypeDati, relayDate, new IZegoRelayCallback() {
@Override
public void onRelay(int errorCode, String roomID, String relayResult) {
int msgWhat = msg.what;
AppLogger.getInstance().writeLog(this.getClass(), "onRelay errorCode:%d roomID:%s relayResult:%s relayDate:%s msgWhat:%s", errorCode, roomID, relayResult, relayDate, msgWhat);
//如果发送失败,则1秒重复发送一次
if (errorCode != 0 && msgWhat < 5) {
msgWhat = msgWhat + 1;
retryHandler.sendMessageDelayed(retryHandler.obtainMessage(msgWhat, relayDate), 1000);
}
}
});
}
};
观众端仍然在媒体次要信息中接收每道题答案、统计结果。
对媒体次要信息的获取同第一小节一致,处理逻辑视业务逻辑而定。
主播向观众发送答案的 JSON 数据格式为:
{
"seq": 2,
"type": "answer",
"data": {
"id": "0ca175b9c0f726a831d895e269332461",
"correct_answer": "B",
"activity_id": "123456",
"answer_stat": [
{
"answer": "A",
"user_count": 2000
},
{
"answer": "B",
"user_count": 1300
},
{
"answer": "C",
"user_count": 420
}
]
}
}
参数说明为:
参数名 | 类型 | 说明 |
---|---|---|
seq | Int | 协议序列号,一轮活动内递增 |
type | String | 消息类型;可用类型为:question(题目),answer(答案),sum(用户列表汇总) |
id | String | 题目 id;题目的唯一标识,由客户保证唯一性;最大 64 bytes(包括字符串结束符), 不能包含字符 ',' |
correct_answer | String | 某道题目的正确答案 |
activity_id | String | 活动 id,答题场次(活动)的唯一标识;由客户保证唯一性;一个答题场次有多个题目; 最大 64 bytes(包括字符串结束符),不能包含字符 ',' |
answer_stat | Array | 结果统计列表 |
answer | String | 某一选项的序号 |
user_count | String | 选择了该选项的用户总人数 |
每轮活动结束后,观众端或需要拉取最终胜利的用户列表,该信息仍然在媒体次要信息中获取。
对媒体次要信息的获取同第一小节一致,处理逻辑视业务逻辑而定。
主播向观众发送用户列表的 JSON 数据格式为:
{
"seq": 3,
"type": "sum",
"data": {
"room_id": "roomid123",
"activity_id": "123456",
"user_list": [
{
"id_name": "555",
"nick_name": "lzp"
},
{
"id_name": "666",
"nick_name": "hhh"
}
]
}
}
参数说明为:
参数名 | 类型 | 说明 |
---|---|---|
seq | Int | 协议序列号,一轮活动内递增 |
type | String | 消息类型;可用类型为:question(题目),answer(答案),sum(用户列表汇总) |
room_id | String | 房间 id;房间的唯一标识 |
activity_id | String | 活动 id,答题场次(活动)的唯一标识;由客户保证唯一性;一个答题场次有多个题目; 最大 64 bytes(包括字符串结束符),不能包含字符 ',' |
user_list | Array | 最终胜利的用户列表 |
id_name | String | 用户 id,用户的唯一标识 |
nick_name | String | 用户昵称 |
直播答题房间内人数数量可能非常多,房间消息会出现高并发使用场景。
开发者需要调用 ZEGO SDK 提供的、用于发送/接受大房间消息的接口,来实现房间内消息处理。
如果开发者仍然使用普通房间消息收发接口(sendRoomMessage
onRecvRoomMessage
),Zego 后台服务可能会丢弃较多消息,影响业务方的正常功能。
开发者可调用如下 API 发送大房间消息。
/**
房间发送不可靠信道的消息
@param type 消息类型,可以自定义
@param category 消息分类,可以自定义、
@param msg 消息内容
@param IZegoBigRoomMessageCallback 消息发送结果,回调 server 下发的 messageId
@return true 成功,false 失败
@discussion 用于高并发的场景,消息可能被丢弃,当高并发达到极限时会根据策略丢弃部分消息
*/
mZegoLiveRoom.sendBigRoomMessage(ZegoIM.MessageType.Text, ZegoIM.MessageCategory.Chat, msg, new IZegoBigRoomMessageCallback() {
@Override
public void onSendBigRoomMessage(int errorCode, String roomID, String messageID) {
}
});
Demo 代码示例如下:
/**
* 发送房间不可靠消息
* @param msg 消息内容
*/
private void sendBigRoomMsg(String msg) {
AppLogger.getInstance().writeLog(this.getClass(), "sendBigRoomMsg msg %s", msg);
BigMessage bigMessage = new BigMessage();
bigMessage.setContent(msg);
bigMessage.setFromUserName(android.os.Build.MODEL);
roomAdapter.addMsgToString(bigMessage);
mZegoLiveRoom.sendBigRoomMessage(ZegoIM.MessageType.Text, ZegoIM.MessageCategory.Chat, msg, new IZegoBigRoomMessageCallback() {
@Override
public void onSendBigRoomMessage(int errorCode, String roomID, String messageID) {
AppLogger.getInstance().writeLog(this.getClass(), "sendBigRoomMsg errorCode %d roomID %s messageID %s", errorCode, roomID, messageID);
}
});
}
正常接收消息前,需要先设置代理对象,调用如下 API 进行:
mZegoLiveRoom.setZegoIMCallback(new IZegoIMCallback() {
@Override
public void onUserUpdate(ZegoUserState[] zegoUserStates, int i) {
}
@Override
public void onRecvRoomMessage(String s, ZegoRoomMessage[] zegoRoomMessages) {
}
@Override
public void onRecvConversationMessage(String s, String s1, ZegoConversationMessage zegoConversationMessage) {
}
@Override
public void onUpdateOnlineCount(String roomId, int onlineCount) {
}
//不可靠消息回调
@Override
public void onRecvBigRoomMessage(String roomID, ZegoBigRoomMessage[] zegoBigRoomMessages) {
}
});
代理对象设置成功后,当同一房间内的其他用户发送大房间消息时,可在如下回调中接收消息:
/**
收到房间的不可靠消息广播
@param roomID 房间 Id
@param zegoBigRoomMessages 消息列表,包括消息内容,消息分类,消息类型,发送者等信息
@discussion 调用 setZegoIMCallback 设置代理对象成功后,调用 ZegoLiveRoom.sendBigRoomMessage 发送消息,会触发此通知
*/
@Override
public void onRecvBigRoomMessage(String roomID, ZegoBigRoomMessage[] zegoBigRoomMessages) {
}
Demo 代码示例如下:
@Override
public void onRecvBigRoomMessage(String roomID, ZegoBigRoomMessage[] zegoBigRoomMessages) {
if (roomID == null && !roomID.equals(room_id)) {
AppLogger.getInstance().writeLog(this.getClass(), "receive big room message, but roomId mismatch, abandon roomId:%s", roomID);
return;
}
if (zegoBigRoomMessages.length == 0) {
AppLogger.getInstance().writeLog(this.getClass(), "receive big room message, but messageList is 0 zegoBigRoomMessages:%d", zegoBigRoomMessages.length);
return;
}
AppLogger.getInstance().writeLog(this.getClass(), "onRecvBigRoomMessage Im receive roomID: %s", roomID);
for (int i = 0; i < zegoBigRoomMessages.length; i++) {
BigMessage bigMessage = new BigMessage();
bigMessage.setContent(zegoBigRoomMessages[i].content);
bigMessage.setFromUserName(zegoBigRoomMessages[i].fromUserName);
AppLogger.getInstance().writeLog(this.getClass(), "onRecvBigRoomMessage Im receive content: %s userName: %s", zegoBigRoomMessages[i].content, zegoBigRoomMessages[i].fromUserName);
roomAdapter.addMsgToString(bigMessage);
}
}
ZEGO SDK 提供如下 API,向开发者提供房间内实时在线用户数。该回调目前默认为 30s 回调一次,开发者可联系即构技术支持,自定义回调频率。
/**
收到在线人数更新
@param roomId 房间 Id
@param onlineCount 在线人数。默认为 30s 回调一次,开发者可联系即构技术支持,自定义回调频率。
*/
public void onUpdateOnlineCount(String roomId, int onlineCount) {
}
请注意,通过该回调接收在线人数更新前,需要调用
setZegoIMCallback
设置 IM 代理对象,具体设置步骤同 5.2 节中setZegoIMCallback
一致。
Demo 代码示例如下:
public void onUpdateOnlineCount(String roomId, int onlineCount) {
if (roomId != null && roomId.equals(room_id)) {
binding.currentQueueCount.setText(String.valueOf(onlineCount));
}
}
联系我们
文档反馈