最近剛用完互動直播sdk 做完直播卦方,簡單記錄下曼氛。
官網(wǎng):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() 加入的房間揣炕,所以不屬于旁路直播。
二.主播方
大概流程是這個樣子稠腊,根據(jù)自己需求進行變更
1.向服務器請求房間號躁染。
2.根據(jù)房間號,創(chuàng)建房間架忌。
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.創(chuàng)建房間成功后有旁路直播開啟旁路推流吞彤。
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協(xié)商");
//推流格式 到時候跟ios統(tǒng)一
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", "推流成功J嗡 !>觥B袂丁!>愣瘛雹嗦!");
}
@Override
public void onError(String module, int errCode, String errMsg) {
Log.i("pushStream", "推流失敗:鲜恰A俗铩!4先2磁骸!难礼!");
}
});
}
}
5.退出房間前:在房間發(fā)送自定義消息告訴觀眾主播要退出房間吱七,方便觀眾端切換主播已離開界面,并停止推流。
發(fā)送自定義消息退出房間
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();
}
三.觀眾方
大概流程是這個樣子踊餐,根據(jù)自己需求進行變更。
1.向服務器請求房間號臀稚。
2.根據(jù)房間號加入房間吝岭。
//加入房間
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.加入房間成功后,在房間發(fā)送自定義消息,告訴房間內有人加入窜管,方便主播統(tǒng)計目前的房間人數(shù)散劫。
/**
* 發(fā)送信令,點贊幕帆,進入房間
*/
public int sendGroupCmd(int cmd, String param) {
ILVCustomCmd customCmd = new ILVCustomCmd();
//1.傳cmd
customCmd.setCmd(cmd);
//2.創(chuàng)建自定義數(shù)據(jù)
String json = sendCusJsn();
//3.傳自定義數(shù)據(jù)
customCmd.setParam(json);
//4.設置自定義類型
customCmd.setType(ILVText.ILVTextType.eGroupMsg);
return sendCmd(customCmd);
}
4.退出房間前:發(fā)送房間自定義消息获搏,告訴主播觀眾退出,方便主播統(tǒng)計目前的在線人數(shù)失乾。
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);
}
}
});
}
自己封裝了一個觀眾端的使用類,也是根據(jù)互動直播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.發(fā)送點贊裸卫,和加入房間指令
* 4.解析傳過來的自定義消息(點贊,其他成員加入纽竣,用戶退出直播墓贿, 達到上限,主播回來了)
* 5.房間鏈接斷開監(jiān)聽
* <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);
}
}
});
}
/**
* 發(fā)送信令蜓氨,點贊聋袋,進入房間
*/
public int sendGroupCmd(int cmd, String param) {
ILVCustomCmd customCmd = new ILVCustomCmd();
//1.傳cmd
customCmd.setCmd(cmd);
//2.創(chuàng)建自定義數(shù)據(jù)
String json = sendCusJsn();
//3.傳自定義數(shù)據(jù)
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() + "====發(fā)送成功");
}
@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) {
//房間異常退出穴吹,通常指斷網(wǎng)幽勒,或者沒電
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();
//系統(tǒng)消息
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", "=======刷新房間數(shù)據(jù)");
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", "===========解析后的數(shù)據(jù)" + 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://觀眾進入房間,主播發(fā)送現(xiàn)在的總點贊數(shù)
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;
}
}
//發(fā)送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", "==========自定義數(shù)據(jù)" + json);
return json;
}
//銷毀
public void onDestory() {
mGuestLiveView = null;
mContext = null;
LiveMessageEvent.getInstance().deleteObserver(this);
ILVLiveManager.getInstance().quitRoom(null);
ILiveRoomManager.getInstance().onDestory();
ILVLiveManager.getInstance().onDestory();
}
}