最近刚用完互动直播sdk 做完直播,简单记录下。
官网:https://cloud.tencent.com/product/ilvb
随心博 demo:https://cloud.tencent.com/document/product/268/7658
直播接口:https://cloud.tencent.com/document/product/268/7685
直播API: https://zhaoyang21cn.github.io/iLiveSDK_Help/android_help/
一.旁路推流
首先先弄懂“旁路”的概念
凡是通过url观看的直播都是旁路直播,旁路直播要进行旁路推流,否则旁路直播无法观看直播。
向在app内我们是通过joinRoom() 加入的房间,所以不属于旁路直播。
二.主播方
大概流程是这个样子,根据自己需求进行变更
1.向服务器请求房间号。
2.根据房间号,创建房间。
public void createRoom() {
ILVLiveRoomOption hostOption = new ILVLiveRoomOption(”主播ID“)
.roomDisconnectListener(this)
.autoCamera(true)
.videoMode(ILiveConstants.VIDEOMODE_BSUPPORT)
.controlRole(CurLiveInfo.getCurRole())
//角色是主播 一定不能设置错 主播走核心机房 观众走边缘机房
// .controlRole("LiveMaster")
.videoRecvMode(AVRoomMulti.VIDEO_RECV_MODE_SEMI_AUTO_RECV_CAMERA_VIDEO);
int ret = ILVLiveManager.getInstance().createRoom(“房间ID”, hostOption, new ILiveCallBack() {
@Override
public void onSuccess(Object data) {
if (getView()!=null)
getView().enterRoomComplete(MySelfInfo.getInstance().getIdStatus(), true);
ILiveLog.d(TAG, "ILVB-SXB|startEnterRoom->create room sucess");
}
@Override
public void onError(String module, int errCode, String errMsg) {
if (getView()!=null)
getView().errRoomFail();
ILiveLog.d(TAG, "ILVB-SXB|createRoom->create room failed:" + module + "|" + errCode + "|" + errMsg);
}
});
}
4.创建房间成功后有旁路直播开启旁路推流。
public void pushStream() {
/**
* 旁路直播 退出房间时必须退出推流。否则会占用后台channel。
*/
ILivePushOption option = new ILivePushOption();
option.encode(ILivePushOption.Encode.HLS);
// option.setRecordFileType(ILivePushOption.RecordFileType.RECORD_HLS_FLV_MP4);
option.setRecordFileType(ILivePushOption.RecordFileType.RECORD_HLS_FLV_MP4);
// option.channelName("跟ios协商");
//推流格式 到时候跟ios统一
pushStream(option);
}
/**
* 开启推流
*
* @param option
*/
public void pushStream(ILivePushOption option) {
if (!isPushed) {
ILiveRoomManager.getInstance().startPushStream(option, new ILiveCallBack<ILivePushRes>() {
@Override
public void onSuccess(ILivePushRes data) {
// List<ILivePushUrl> liveUrls = data.getUrls();
Log.i("pushStream", "推流成功!!!!!!!");
}
@Override
public void onError(String module, int errCode, String errMsg) {
Log.i("pushStream", "推流失败!!!!!!!");
}
});
}
}
5.退出房间前:在房间发送自定义消息告诉观众主播要退出房间,方便观众端切换主播已离开界面,并停止推流。
发送自定义消息退出房间
public void sendExitRoomMSG() {
ILVCustomCmd cmd = new ILVCustomCmd();
cmd.setCmd(Constans.AVIMCMD_EXITLIVE);
cmd.setParam(sendExitRoomJson());
cmd.setType(ILVText.ILVTextType.eGroupMsg);
ILVLiveManager.getInstance().sendCustomCmd(cmd, new ILiveCallBack<TIMMessage>() {
@Override
public void onSuccess(TIMMessage data) {
if (isPushed) {
stopStream();
}
callExitRoom();
}
@Override
public void onError(String module, int errCode, String errMsg) {
}
});
}
停止推流
/**
* 退出直播的时候关闭推流
*/
public void stopStream() {
ILiveRoomManager.getInstance().stopPushStream(streamChannelID, new ILiveCallBack() {
@Override
public void onSuccess(Object data) {
SxbLog.e(TAG, "stopPush->success");
isPushed = false;
}
@Override
public void onError(String module, int errCode, String errMsg) {
SxbLog.e(TAG, "stopPush->failed:" + module + "|" + errCode + "|" + errMsg);
}
});
}
6.退出房间。
private void callExitRoom() {
ILiveSDK.getInstance().getAvVideoCtrl().setLocalVideoPreProcessCallback(null);
quitLiveRoom();
}
三.观众方
大概流程是这个样子,根据自己需求进行变更。
1.向服务器请求房间号。
2.根据房间号加入房间。
//加入房间
public void joinRoom(int roomId) {
ILVLiveManager.getInstance().quitRoom(null);
this.rommId = roomId + "";
ILVLiveRoomOption memberOption = new ILVLiveRoomOption(ConfigUtils.getUid())
.autoCamera(false)
.roomDisconnectListener(this)
.videoMode(ILiveConstants.VIDEOMODE_BSUPPORT)
.controlRole("Guest")
.authBits(AVRoomMulti.AUTH_BITS_JOIN_ROOM | AVRoomMulti.AUTH_BITS_RECV_AUDIO | AVRoomMulti.AUTH_BITS_RECV_CAMERA_VIDEO | AVRoomMulti.AUTH_BITS_RECV_SCREEN_VIDEO)
.videoRecvMode(AVRoomMulti.VIDEO_RECV_MODE_SEMI_AUTO_RECV_CAMERA_VIDEO)
.autoMic(false);
int ret = ILVLiveManager.getInstance().joinRoom(roomId, memberOption, new ILiveCallBack() {
@Override
public void onSuccess(Object data) {
ILiveLog.d(TAG, "ILVB-Suixinbo|startEnterRoom->join room sucess");
if (null != mGuestLiveView)
mGuestLiveView.enterRoomComplete(true);
}
@Override
public void onError(String module, int errCode, String errMsg) {
LogUtils.d("suyan", "===========加入房间失败" + module + errCode + errMsg);
if (null != mGuestLiveView) {
mGuestLiveView.enterRoomComplete(false);
}
}
});
}
3.加入房间成功后,在房间发送自定义消息,告诉房间内有人加入,方便主播统计目前的房间人数。
/**
* 发送信令,点赞,进入房间
*/
public int sendGroupCmd(int cmd, String param) {
ILVCustomCmd customCmd = new ILVCustomCmd();
//1.传cmd
customCmd.setCmd(cmd);
//2.创建自定义数据
String json = sendCusJsn();
//3.传自定义数据
customCmd.setParam(json);
//4.设置自定义类型
customCmd.setType(ILVText.ILVTextType.eGroupMsg);
return sendCmd(customCmd);
}
4.退出房间前:发送房间自定义消息,告诉主播观众退出,方便主播统计目前的在线人数。
5.退出直播间。
public void startExitRoom() {
if (ILiveSDK.getInstance() != null && ILiveSDK.getInstance().getAvVideoCtrl() != null) {
ILiveSDK.getInstance().getAvVideoCtrl().setLocalVideoPreProcessCallback(null);
}
quitLiveRoom();
}
private void quitLiveRoom() {
ILVLiveManager.getInstance().quitRoom(new ILiveCallBack() {
@Override
public void onSuccess(Object data) {
//1.回调给页面
if (null != mGuestLiveView) {
LogUtils.d("suyan", "=========退出成功");
mGuestLiveView.quiteRoomComplete(true);
}
}
@Override
public void onError(String module, int errCode, String errMsg) {
if (null != mGuestLiveView) {
mGuestLiveView.quiteRoomComplete(false);
}
}
});
}
自己封装了一个观众端的使用类,也是根据互动直播demo改的
package com.jyjt.ydyl.txlive.presenters;
import android.app.Activity;
import android.content.Context;
import android.text.TextUtils;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonSyntaxException;
import com.jyjt.ydyl.Entity.LiveCusEntity;
import com.jyjt.ydyl.tools.ConfigUtils;
import com.jyjt.ydyl.tools.Constans;
import com.jyjt.ydyl.tools.LogUtils;
import com.jyjt.ydyl.tools.ToastUtil;
import com.jyjt.ydyl.txlive.LiveDetailActivity;
import com.jyjt.ydyl.txlive.event.LiveMessageEvent;
import com.jyjt.ydyl.txlive.views.GuestLiveView;
import com.tencent.TIMConversationType;
import com.tencent.TIMCustomElem;
import com.tencent.TIMElem;
import com.tencent.TIMElemType;
import com.tencent.TIMGroupSystemElem;
import com.tencent.TIMGroupSystemElemType;
import com.tencent.TIMMessage;
import com.tencent.av.sdk.AVRoomMulti;
import com.tencent.ilivesdk.ILiveCallBack;
import com.tencent.ilivesdk.ILiveConstants;
import com.tencent.ilivesdk.ILiveSDK;
import com.tencent.ilivesdk.core.ILiveLog;
import com.tencent.ilivesdk.core.ILiveRoomManager;
import com.tencent.ilivesdk.core.ILiveRoomOption;
import com.tencent.livesdk.ILVCustomCmd;
import com.tencent.livesdk.ILVLiveConstants;
import com.tencent.livesdk.ILVLiveManager;
import com.tencent.livesdk.ILVLiveRoomOption;
import com.tencent.livesdk.ILVText;
import com.tencent.openqq.protocol.imsdk.im_common;
import org.json.JSONObject;
import org.json.JSONTokener;
import java.util.Observable;
import java.util.Observer;
/**
* 观众端,直播帮助类
* <p>
* 1.加入房间
* 2.离开房间
* 3.发送点赞,和加入房间指令
* 4.解析传过来的自定义消息(点赞,其他成员加入,用户退出直播, 达到上限,主播回来了)
* 5.房间链接断开监听
* <p>
* 苏艳
*/
public class GuestLiveHelper implements ILiveRoomOption.onRoomDisconnectListener, Observer {
private final String TAG = "LiveHelper";
private GuestLiveView mGuestLiveView;
public Activity mContext;
private String rommId;
Gson gson;
public GuestLiveHelper(Activity context, GuestLiveView mGuestLiveView) {
mContext = context;
this.mGuestLiveView = mGuestLiveView;
LiveMessageEvent.getInstance().addObserver(this);
GsonBuilder builder = new GsonBuilder();
gson = builder.create();
}
//加入房间
public void joinRoom(int roomId) {
ILVLiveManager.getInstance().quitRoom(null);
this.rommId = roomId + "";
ILVLiveRoomOption memberOption = new ILVLiveRoomOption(ConfigUtils.getUid())
.autoCamera(false)
.roomDisconnectListener(this)
.videoMode(ILiveConstants.VIDEOMODE_BSUPPORT)
.controlRole("Guest")
.authBits(AVRoomMulti.AUTH_BITS_JOIN_ROOM | AVRoomMulti.AUTH_BITS_RECV_AUDIO | AVRoomMulti.AUTH_BITS_RECV_CAMERA_VIDEO | AVRoomMulti.AUTH_BITS_RECV_SCREEN_VIDEO)
.videoRecvMode(AVRoomMulti.VIDEO_RECV_MODE_SEMI_AUTO_RECV_CAMERA_VIDEO)
.autoMic(false);
int ret = ILVLiveManager.getInstance().joinRoom(roomId, memberOption, new ILiveCallBack() {
@Override
public void onSuccess(Object data) {
ILiveLog.d(TAG, "ILVB-Suixinbo|startEnterRoom->join room sucess");
if (null != mGuestLiveView)
mGuestLiveView.enterRoomComplete(true);
}
@Override
public void onError(String module, int errCode, String errMsg) {
LogUtils.d("suyan", "===========加入房间失败" + module + errCode + errMsg);
if (null != mGuestLiveView) {
mGuestLiveView.enterRoomComplete(false);
}
}
});
}
//退出房间
public void startExitRoom() {
if (ILiveSDK.getInstance() != null && ILiveSDK.getInstance().getAvVideoCtrl() != null) {
ILiveSDK.getInstance().getAvVideoCtrl().setLocalVideoPreProcessCallback(null);
}
quitLiveRoom();
}
private void quitLiveRoom() {
ILVLiveManager.getInstance().quitRoom(new ILiveCallBack() {
@Override
public void onSuccess(Object data) {
//1.回调给页面
if (null != mGuestLiveView) {
LogUtils.d("suyan", "=========退出成功");
mGuestLiveView.quiteRoomComplete(true);
}
}
@Override
public void onError(String module, int errCode, String errMsg) {
if (null != mGuestLiveView) {
mGuestLiveView.quiteRoomComplete(false);
}
}
});
}
/**
* 发送信令,点赞,进入房间
*/
public int sendGroupCmd(int cmd, String param) {
ILVCustomCmd customCmd = new ILVCustomCmd();
//1.传cmd
customCmd.setCmd(cmd);
//2.创建自定义数据
String json = sendCusJsn();
//3.传自定义数据
customCmd.setParam(json);
//4.设置自定义类型
customCmd.setType(ILVText.ILVTextType.eGroupMsg);
return sendCmd(customCmd);
}
//邀请上麦用的,暂时用不到
public int sendC2CCmd(final int cmd, String param, String destId) {
ILVCustomCmd customCmd = new ILVCustomCmd();
customCmd.setDestId(destId);
customCmd.setCmd(cmd);
customCmd.setParam(param);
customCmd.setType(ILVText.ILVTextType.eC2CMsg);
return sendCmd(customCmd);
}
private int sendCmd(final ILVCustomCmd cmd) {
return ILVLiveManager.getInstance().sendCustomCmd(cmd, new ILiveCallBack() {
@Override
public void onSuccess(Object data) {
LogUtils.d(TAG, "suyan" + cmd.getCmd() + "|" + cmd.getParam() + "====发送成功");
}
@Override
public void onError(String module, int errCode, String errMsg) {
// Toast.makeText(mContext, "sendCmd->failed:" + module + "|" + errCode + "|" + errMsg, Toast.LENGTH_SHORT).show();
}
});
}
@Override
public void onRoomDisconnect(int errCode, String errMsg) {
if (null != mGuestLiveView) {
//房间异常退出,通常指断网,或者没电
mGuestLiveView.roomDisconnect(errCode, errMsg);
}
}
// 解析文本消息
private void processTextMsg(LiveMessageEvent.SxbMsgInfo info) {
if (null == info.data || !(info.data instanceof ILVText)) {
return;
}
ILVText text = (ILVText) info.data;
if (text.getType() == ILVText.ILVTextType.eGroupMsg
&& !text.getDestId().equals(rommId)) {
if (TextUtils.isEmpty(rommId)) {
LogUtils.d("suyan","========当前的roomid为空");
}
return;
}
String name = info.senderId;
if (null != info.profile && !TextUtils.isEmpty(info.profile.getNickName())) {
name = info.profile.getNickName();
}
if (null != mGuestLiveView)
mGuestLiveView.refreshText(text.getText(), name);
}
// 解析自定义信令
private void processCmdMsg(LiveMessageEvent.SxbMsgInfo info) {
if (null == info.data || !(info.data instanceof ILVCustomCmd)) {
return;
}
ILVCustomCmd cmd = (ILVCustomCmd) info.data;
if (cmd.getType() == ILVText.ILVTextType.eGroupMsg
&& !cmd.getDestId().equals(rommId)) {
if (TextUtils.isEmpty(rommId)) {
LogUtils.d("suyan","========当前的roomid为空");
}
return;
}
String name = info.senderId;
if (null != info.profile && !TextUtils.isEmpty(info.profile.getNickName())) {
name = info.profile.getNickName();
}
handleCustomMsg(cmd.getCmd(), cmd.getParam(), info.senderId, name);
}
private void processOtherMsg(LiveMessageEvent.SxbMsgInfo info) {
if (null == info.data || !(info.data instanceof TIMMessage)) {
return;
}
TIMMessage currMsg = (TIMMessage) info.data;
// 过滤非当前群组消息
if (currMsg.getConversation() != null && currMsg.getConversation().getPeer() != null) {
if (currMsg.getConversation().getType() == TIMConversationType.Group
&& !rommId.equals(currMsg.getConversation().getPeer())) {
if (TextUtils.isEmpty(rommId)) {
LogUtils.d("suyan","========当前的roomid为空");
}
return;
}
}
for (int j = 0; j < currMsg.getElementCount(); j++) {
if (currMsg.getElement(j) == null)
continue;
TIMElem elem = currMsg.getElement(j);
TIMElemType type = elem.getType();
//系统消息
if (type == TIMElemType.GroupSystem) { // 群组解散消息
if (TIMGroupSystemElemType.TIM_GROUP_SYSTEM_DELETE_GROUP_TYPE == ((TIMGroupSystemElem) elem).getSubtype()) {
if (null != mGuestLiveView)
mGuestLiveView.dismissGroup("host", null);
}
} else if (type == TIMElemType.Custom) {
try {
final String strMagic = "__ACTION__";
String customText = new String(((TIMCustomElem) elem).getData(), "UTF-8");
if (!customText.startsWith(strMagic)) // 检测前缀
continue;
JSONTokener jsonParser = new JSONTokener(customText.substring(strMagic.length() + 1));
JSONObject json = (JSONObject) jsonParser.nextValue();
String action = json.optString("action", "");
if (action.equals("force_exit_room") || action.equals("force_disband_room")) {
JSONObject objData = json.getJSONObject("data");
String strRoomNum = objData.optString("room_num", "");
if (strRoomNum.equals(String.valueOf(ILiveRoomManager.getInstance().getRoomId()))) {
if (null != mGuestLiveView) {
mGuestLiveView.forceQuitRoom("管理员已将房间解散或将您踢出房间!");
}
}
}
} catch (Exception e) {
}
}
}
}
@Override
public void update(Observable observable, Object o) {
LiveMessageEvent.SxbMsgInfo info = (LiveMessageEvent.SxbMsgInfo) o;
LogUtils.d("suyan", "======收到消息" + info.msgType);
switch (info.msgType) {
case LiveMessageEvent.MSGTYPE_TEXT:
processTextMsg(info);
break;
case LiveMessageEvent.MSGTYPE_CMD:
processCmdMsg(info);
break;
case LiveMessageEvent.MSGTYPE_OTHER:
LogUtils.d("suyan", "=======其他消息");
processOtherMsg(info);
case LiveMessageEvent.MSGTYPE_DDETAIL:
LogUtils.d("suyan", "=======刷新房间数据");
processDetailMsg(info);
break;
}
}
// 解析自定义信令
private void processDetailMsg(LiveMessageEvent.SxbMsgInfo info) {
if (null != info && !TextUtils.isEmpty(info.senderId)) {
if (null != mGuestLiveView) {
mGuestLiveView.refreshData(info.senderId);
}
}
}
private void handleCustomMsg(int action, String param, String identifier, String nickname) {
LogUtils.d(TAG, "handleCustomMsg->action: " + action);
if (null == mGuestLiveView) {
return;
}
LiveCusEntity mliveCusEntity = updateGson(param);
if (mliveCusEntity != null) {
LogUtils.d("suyan", "===========解析后的数据" + mliveCusEntity.getContent().getLike_num() + "=" + mliveCusEntity.getContent().getOnline_num() + "=" + mliveCusEntity.getUser().getUid());
}
switch (action) {
case Constans.AVIMCMD_PRAISE://点赞
mGuestLiveView.refreshGoodLike();
break;
case Constans.AVIMCMD_ENTERLIVE://其他成员加入
mGuestLiveView.memberJoin(identifier, nickname);
break;
case Constans.AVIMCMD_EXITLIVE: //用户退出直播
mGuestLiveView.hostOffline(identifier, nickname);
break;
case ILVLiveConstants.ILVLIVE_CMD_LINKROOM_LIMIT: // 达到上限
LogUtils.d("suyan","========到达上限");
break;
case Constans.AVIMCMD_HOST_BACK://主播回来了
LogUtils.d("suyan","========主播回来了");
//昵称:nickname id:identifier
case Constans.AVIMCMD_PRAISE_TOTAL://观众进入房间,主播发送现在的总点赞数
try {
if (mliveCusEntity != null && !TextUtils.isEmpty(mliveCusEntity.getContent().getLike_num())) {
mGuestLiveView.refreshTotalGoodLike(Long.parseLong(mliveCusEntity.getContent().getLike_num()));
}
} catch (NumberFormatException e) {
e.printStackTrace();
}
default:
break;
}
}
//解析jsong
public LiveCusEntity updateGson(String jsonTest) {
if (!TextUtils.isEmpty(jsonTest)) {
LiveCusEntity mLiveCusEntity;
try {
mLiveCusEntity = gson.fromJson(jsonTest, LiveCusEntity.class);
return mLiveCusEntity;
} catch (JsonSyntaxException e) {
e.printStackTrace();
return null;
}
} else {
return null;
}
}
//发送json
public String sendCusJsn() {
LiveCusEntity liveCusEntity = new LiveCusEntity();
LiveCusEntity.LiveContent mLiveContent = new LiveCusEntity.LiveContent();
liveCusEntity.setContent(mLiveContent);
LiveCusEntity.User mUser = new LiveCusEntity.User();
mUser.setUid(ConfigUtils.getUid());
liveCusEntity.setUser(mUser);
String json = gson.toJson(liveCusEntity, LiveCusEntity.class);
LogUtils.d("suyan", "==========自定义数据" + json);
return json;
}
//销毁
public void onDestory() {
mGuestLiveView = null;
mContext = null;
LiveMessageEvent.getInstance().deleteObserver(this);
ILVLiveManager.getInstance().quitRoom(null);
ILiveRoomManager.getInstance().onDestory();
ILVLiveManager.getInstance().onDestory();
}
}