今日来公司的项目要求接入视频,如是根据需求对网易云视频,anychat,腾讯云视频进行了技术调研,因为基于响应时间,降噪等诸多效果的测试,考虑,最终选择了腾讯云视频。因为腾讯云视频刚刚推出不久,demo,文档写的不是太集中,故在集成时出现多个坑。现对集成步骤做一下简单归纳:
1.集成播放列表:
直播播放列表需要开通查询统计信息(Beta)
该接口的相关文档地址:https://www.qcloud.com/document/product/267/6110。
这个接口需要提交工单才会开通,默认是不开通的。
开通后会产生appid和推流鉴权key (这个东西会在生成sign时使用,sign生成的规则是推流鉴权key直接拼接过期时间)
String playUrl ="http://statcgi.video.qcloud.com/common_access?"+
"cmd=1251175924&interface=Get_LiveStat"+
"&Param.n.page_no=1"+"&Param.n.page_size=20"+
"&t="+time+"&sign="+signValue;
cmd后面需要根据你自己的需要更换成自己的appid,我的核心代码如下:
sign是推流鉴权key直接拼接过期时间进行md5生成:
md5工具类:
public class MD5Utils {
static String MD5(String sourceStr) {
String result = "";
try {
MessageDigest md = MessageDigest.getInstance("MD5");
md.update(sourceStr.getBytes());
byte b[] = md.digest();
int i;
StringBuffer buf = new StringBuffer("");
for (int offset = 0; offset < b.length; offset++) {
i = b[offset];
if (i < 0)
i += 256;
if (i < 16)
buf.append("0");
buf.append(Integer.toHexString(i));
}
result = buf.toString();
System.out.println("MD5(" + sourceStr + ",32) = " + result);
System.out.println("MD5(" + sourceStr + ",16) = " + buf.toString().substring(8, 24));
} catch (NoSuchAlgorithmException e) {
System.out.println(e);
}
return result;
}
}
过期时间戳转化工具类:
public class TimeUtils {
//日期转化为时间戳
public static String dateToStamp(String s) throws Exception{
String res;
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date date = simpleDateFormat.parse(s);
long ts = (date.getTime()+300000)/1000;
res = String.valueOf(ts);
return res;
}
public static String stampToDate(String s){
String res;
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
long lt = new Long(s);
Date date = new Date(lt);
res = simpleDateFormat.format(date);
return res;
}
}
查询时间列表的核心逻辑:
public static void main(String[] args) throws Exception {
OkHttpClient client = new OkHttpClient();
SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String time="";
try {
//获取当前日期的时间戳
time = TimeUtils.dateToStamp(sdf.format(new Date()));
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(time);
//推流鉴权key
String key="7407d5829e270dd92661ffcf06169bcf";
String signValue=MD5Utils.MD5(key+time);
String playUrl ="http://statcgi.video.qcloud.com/common_access?"+
"cmd=1251175924&interface=Get_LiveStat"+
"&Param.n.page_no=1"+"&Param.n.page_size=20"+
"&t="+time+"&sign="+signValue;
System.out.println(playUrl);
Request request = new Request.Builder().url(playUrl).build();
Response response = client.newCall(request).execute();
if (response.isSuccessful()) {
System.out.println(response.body().string());
} else {
throw new IOException("Unexpected code " + response);
}
获取成功会如下显示:
{"errmsg":"has no stream is living","message":"has no stream is living","output":null,"ret":0,"retcode":0}
如果有推流的话是:
{"errmsg":"","message":"","output":{"stream_count":1,"stream_info":[{"bandwidth":0.0,"client_ip":"125.46.216.125","flr":1.0,"fps":0,"online":0,"server_ip":"123.138.162.79","speed":0,"stream_name":"8818_b262bb2709","time":"2017-04-13 11:04:13"}],"total_bandwidth":0.0,"total_online":0},"ret":0,"retcode":0}
返回的是json这个由你自己的业务需求定
2.登录用户体系:
腾讯提供了两种账户体系,一种是托管的,一种是独立的,托管的是需要注册登录腾讯tls体系,账户管理交由腾讯,独立的就是不对现有用户体系有影响,而是在需要调用腾讯的时候,去到腾讯上验证一下,就像融云的token一样。
2.1托管体系注册
android:
accountHelper.TLSStrAccReg("xtfgq", "12345678", new TLSStrAccRegListener() {
@Override
public void OnStrAccRegSuccess(TLSUserInfo tlsUserInfo) {
// tlsUserInfo.
Log.e("vv","111");
}
@Override
public void OnStrAccRegFail(TLSErrInfo tlsErrInfo) {
Log.e("vv","222");
}
@Override
public void OnStrAccRegTimeout(TLSErrInfo tlsErrInfo) {
Log.e("vv","333");
}
});
需要在oncreate中完成初始化,这里面的appid与前面的不同,(特别注意),这个是开通腾讯云云通信里面的那个千万不要搞错。
accountHelper=TLSAccountHelper.getInstance().init(SettingActivity.this,1251175924,12001,"1.0.1");
托管登录:
loginHelper = TLSLoginHelper.getInstance()
.init(getApplicationContext(), 11270, 12001, "1.0.1");
loginHelper.TLSPwdLogin("用户帐号", "用户密码".getBytes(), new TLSPwdLoginListener(){
@Override
public void OnPwdLoginSuccess(TLSUserInfo tlsUserInfo) {
final TIMUser user = new TIMUser();
user.setIdentifier("用户帐号");
user.setAccountType("12001");
user.setAppIdAt3rd(String.valueOf(Constants.sdkAppId));
TIMManager.getInstance().login(
Constants.sdkAppId, //sdkAppId,由腾讯分配
user,
loginHelper.getUserSig("用户帐号"), //用户帐号签名,由私钥加密获得,具体请参考文档
new TIMCallBack() {//回调接口
@Override
public void onSuccess() {//登录成功
Log.e("vvv","succuss");
//根据逻辑跳到不同界面
}
@Override
public void onError(int code, String desc) {//登录失败
//错误码code和错误描述desc,可用于定位请求失败原因
//错误码code含义请参见错误码表
Log.e("eer","eeror");
}
});
}
@Override
public void OnPwdLoginReaskImgcodeSuccess(byte[] bytes) {
}
@Override
public void OnPwdLoginNeedImgcode(byte[] bytes, TLSErrInfo tlsErrInfo) {
}
@Override
public void OnPwdLoginFail(TLSErrInfo tlsErrInfo) {
}
@Override
public void OnPwdLoginTimeout(TLSErrInfo tlsErrInfo) {
}
});
2.2 独立模式,即usersinger需从自己的服务器获得,可参考usersinger生成文档:https://www.qcloud.com/document/product/269/1510。
客户端不需要像刚才那样先登陆tls,然后在成功回调里调用 TIMManager.getInstance().login,而是直接从自己的服务取得usersinger,
代码如下:
final TIMUser user = new TIMUser();
user.setIdentifier(userid);
user.setAccountType("12001");
user.setAppIdAt3rd(String.valueOf(Constants.sdkAppId));
TIMManager.getInstance().login(
Constants.sdkAppId, //sdkAppId,由腾讯分配
user,
sig, //用户帐号签名,由私钥加密获得,具体请参考文档
new TIMCallBack() {//回调接口
@Override
public void onSuccess() {//登录成功
Log.e("vvv","succuss");
}
@Override
public void onError(int code, String desc) {//登录失败
//错误码code和错误描述desc,可用于定位请求失败原因
//错误码code含义请参见错误码表
Log.e("eer","eeror");
}
});
最后,不管哪中模式,都需要调用修改昵称的腾讯接口,否则会出现发弹幕上用户名变成数字方面的问题。
public void setMyNickName(String nickName){
TIMFriendshipManager.getInstance().setNickName(nickName, new TIMCallBack() {
@Override
public void onError(int i, String s) {
}
@Override
public void onSuccess() {
}
});
}
3.加入聊天室:
private void joinIMChatRoom(final String chatRoomId) {
mGroupConversation = TIMManager.getInstance().getConversation(TIMConversationType.Group, chatRoomId);
TIMGroupManager.getInstance().applyJoinGroup(chatRoomId, Constants.APPLY_CHATROOM + chatRoomId, new TIMCallBack() {
@Override
public void onError(int i, String s) {
//已经在是成员了
if (i == Constants.IS_ALREADY_MEMBER) {
initTIMListener(chatRoomId);
}
}
@Override
public void onSuccess() {
initTIMListener(chatRoomId);
}
});
}
消息监听:
public void initTIMListener(String chatRoomId) {
mGroupConversation = TIMManager.getInstance().getConversation(TIMConversationType.Group, chatRoomId);
TIMManager.getInstance().addMessageListener(msgListener);
}
/**
* 群消息回调
*/
private TIMMessageListener msgListener = new TIMMessageListener() {
@Override
public boolean onNewMessages(List list) {
//SxbLog.d(TAG, "onNewMessages readMessage " + list.size());
//解析TIM推送消息
parseIMMessage(list);
return false;
}
};
/**
* 解析消息回调
*
* @param list 消息列表
*/
private void parseIMMessage(List list) {
List tlist = list;
if (tlist.size() > 0) {
if (mGroupConversation != null)
mGroupConversation.setReadMessage(tlist.get(0));
}
for (int i = tlist.size() - 1; i >= 0; i--) {
TIMMessage currMsg = tlist.get(i);
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 (type == TIMElemType.Text) {
if (currMsg.isSelf()) {
handleTextMessage(elem, name);
} else {
TIMUserProfile sendUser = currMsg.getSenderProfile();
String nick="";
if(sendUser!=null){
nick=sendUser.getNickName();
}else{
nick=currMsg.getSender();
}
handleTextMessage(elem,nick);
}
}
}
}
}
发送消息:
mGroupConversation = TIMManager.getInstance().getConversation(TIMConversationType.Group, groupId);
mGroupConversation.sendMessage(msg, new TIMValueCallBack() {//发送消息回调
@Override
public void onError(int code, String desc) {//发送消息失败
//错误码code和错误描述desc,可用于定位请求失败原因
//错误码code含义请参见错误码表
if (code == 85) { //消息体太长
if (null != getActivity()) {
Toast.makeText(getActivity(), "输入内容太长", Toast.LENGTH_SHORT).show();
}
} else if (code == 6011) {//群主不存在
if (null != getActivity()) {
Toast.makeText(getActivity(), "群主不存在", Toast.LENGTH_SHORT).show();
}
}
}
@Override
public void onSuccess(TIMMessage timMessage) {//发送消息成功
mBoolRefreshLock=false;
for (int j = 0; j < timMessage.getElementCount(); j++) {
TIMElem elem = (TIMElem) timMessage.getElement(0);
if (timMessage.isSelf()) {
handleTextMessage(elem, name);
} else {
TIMUserProfile sendUser = timMessage.getSenderProfile();
String name;
if (sendUser != null) {
name = sendUser.getNickName();
}
else {
name = timMessage.getSender();
}
//String sendId = timMessage.getSender();
handleTextMessage(elem,name);
}
}
}
});
}
处理消息文本:
/**
* 处理文本消息解析
*
* @param elem
* @param name
*/
private void handleTextMessage(TIMElem elem, String name) {
TIMTextElem textElem = (TIMTextElem) elem;
if (textElem.getText() != null) {
refreshTextListView(name, textElem.getText(), Constants.TEXT_TYPE);
}
}
/**
* 消息刷新显示
*
* @param name 发送者
* @param context 内容
* @param type 类型 (上线线消息和 聊天消息)
*/
public void refreshTextListView(String name, String context, int type) {
ChatEntity entity = new ChatEntity();
entity.setSenderName(name);
entity.setContext(context);
entity.setType(type);
notifyRefreshListView(entity);
mListViewMsgItems.setVisibility(View.VISIBLE);
if (mListViewMsgItems.getCount() > 1) {
if (true)
mListViewMsgItems.setSelection(0);
else
mListViewMsgItems.setSelection(mListViewMsgItems.getCount() - 1);
}
}
最后实现效果如图: