前言
近期项目做即时聊天,以前已经实现,使用环信实现的,当时用时不到一个星期,但是视频通话质量不是很好,老板不是很喜欢,要求换成腾讯,然后开始调研腾讯im ,腾讯im即时聊天的文档中没有音视频通话,要自己实现,使用互动直播实现,因此要实现这个功能就要花钱,公司有钱任性,但是自己也可以接触下腾讯
这一番接触下,腾讯给我的感觉并不友好,demo功能不是很全,很多自己实现,文档写的很不完善,大数要靠自己猜,猜中了像中了500万,猜不中就跟谢谢惠顾的。。。吐槽无用还是要实现功能,现在我们来说一下实现音视频通话。
腾讯专门封装了一个callSdk 来实现音视频通话,Android Ios,两个客户端都有,通过callSdk 实现音视频会简单很多,主要设置监听,进行业务处理
CallSdk 的api文档:https://zhaoyang21cn.github.io/iLiveSDK_Help/callsdk/
音视频通话的demo:https://github.com/zhaoyang21cn/CallSDK_Android_Demo
看到这个github上的demo图片和运行出来的图片不符,可能会伤心了,具体的逻辑要自己实现,demo运行出来没有是黑的因为没有开权限
互动直播的api文档:https://zhaoyang21cn.github.io/iLiveSDK_Help/android_help/
因为用到AVRootView 控件,所以参看这个文档
先来看一下我们视频通话实现的效果
视频呼叫页面

视频应答页面

一.在gradle中加入
"com.tencent.ilivesdk:ilivesdk:1.8.6.3",
"com.tencent.callsdk:callsdk:1.0.31",
二.音视频的主要逻辑代码
package com.jyjt.ydyl.txim;
import android.Manifest;
import android.app.Activity;
import android.app.Application;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Handler;
import android.support.v4.app.ActivityCompat;
import android.text.TextUtils;
import android.view.GestureDetector;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.TextView;
import com.google.gson.JsonObject;
import com.jyjt.ydyl.BaseActivity;
import com.jyjt.ydyl.BasePresenter;
import com.jyjt.ydyl.R;
import com.jyjt.ydyl.application.MyApplication;
import com.jyjt.ydyl.tools.AppUtils;
import com.jyjt.ydyl.tools.ConfigUtils;
import com.jyjt.ydyl.tools.LogUtils;
import com.jyjt.ydyl.tools.SwitchActivityManager;
import com.jyjt.ydyl.tools.ToastUtil;
import com.jyjt.ydyl.txim.presentation.event.VcallEvent;
import com.tencent.av.sdk.AVAudioCtrl;
import com.tencent.callsdk.ILVBCallMemberListener;
import com.tencent.callsdk.ILVCallConstants;
import com.tencent.callsdk.ILVCallListener;
import com.tencent.callsdk.ILVCallManager;
import com.tencent.callsdk.ILVCallNotification;
import com.tencent.callsdk.ILVCallOption;
import com.tencent.ilivesdk.ILiveCallBack;
import com.tencent.ilivesdk.ILiveConstants;
import com.tencent.ilivesdk.ILiveSDK;
import com.tencent.ilivesdk.core.ILiveLoginManager;
import com.tencent.ilivesdk.view.AVRootView;
import com.tencent.ilivesdk.view.AVVideoView;
import java.util.ArrayList;
import java.util.List;
import butterknife.BindView;
/**
* 苏艳
* <p>
* 权限校验
* 音视频呼叫:呼叫的时候校验麦克分或摄像头权限
* 音视频接听:点击接受时校验麦克分或摄像头权限
* 视频通话:通话的时候时候校验麦克分权限
*/
public class VoiceCallActivity extends BaseActivity implements ILVCallListener, ILVBCallMemberListener, ILiveLoginManager.TILVBStatusListener, AVRootView.onSubViewCreatedListener {
//大小头像以及提示语========================================================
@BindView(R.id.rl_big_samll_head)
RelativeLayout rl_big_samll_head;
@BindView(R.id.tv_waring)
TextView tv_waring;
@BindView(R.id.iv_video_big_head)
ImageView iv_video_big_head;
@BindView(R.id.iv_video_small_head)
ImageView iv_video_small_head;
@BindView(R.id.tv_yuyin_timer)
public TextView tv_yuyin_timer;
//两路视频通话界面================================================================
@BindView(R.id.rl_video_conversation)
RelativeLayout rl_video_conversation;
@BindView(R.id.iv_switch_camera)
ImageView iv_switch_camera;
@BindView(R.id.av_root_view)
AVRootView av_root_view;
//视屏呼叫界面 1================================================================
@BindView(R.id.ll_bootom_call)
LinearLayout ll_bootom_call;
//视频邀请界面 2================================================================
@BindView(R.id.ll_bottom_invitation)
LinearLayout ll_bottom_invitation;
@BindView(R.id.ll_video_yes)
LinearLayout ll_video_yes;
@BindView(R.id.ll_video_no)
LinearLayout ll_video_no;
//视频通话界面 3==========================================================
@BindView(R.id.ll_bootom_conversation)
LinearLayout ll_bootom_conversation;
@BindView(R.id.ll_hangup)
LinearLayout ll_hangup;
@BindView(R.id.ll_no_voice)
LinearLayout ll_no_voice;
@BindView(R.id.ll_big_voice)
LinearLayout ll_big_voice;
@BindView(R.id.iv_no_voice)
ImageView iv_no_voice;
@BindView(R.id.iv_big_voice)
ImageView iv_big_voice;
//基本数据================================================================
//1.自己的id
private String mHostId = "";
//2.对方的id
private String mUserId = "";
//3.呼叫的类型 语音电话: CALL_TYPE_AUDIO 视频电话 : CALL_TYPE_VIDEO
private int mCallType;
//4.页面类型 1:视频呼叫 2:视频邀请 3:视频通话
private int mType;
//5.通话的id
private int mCallId;
//6.对方头像的url
private String mHeadUrl = "";
//7.对方的名字
private String mUserName = "";
//8.双人视频通话配置
private ILVCallOption mOption;
//9.麦克风切换设置
private boolean mMicEnalbe = true;
//10.扬声器切换设置
private boolean mSpeaker = true;
//11.默认设置前置摄像头
private int mCurCameraId = ILiveConstants.FRONT_CAMERA;
//12.计时器
private Handler mHandler = new Handler();
//13.当前毫秒数
private long currentSecond = 0;
//=======================================================================
@Override
protected BasePresenter loadPresenter() {
return null;
}
@Override
protected void initData() {
}
@Override
protected void initView() {
//1.获取页面类型
mType = getIntent().getIntExtra("Type", 0);
//2.获取type类型
mCallType = getIntent().getIntExtra("CallType", ILVCallConstants.CALL_TYPE_VIDEO);
//3.获取自己的id
mHostId = getIntent().getStringExtra("HostId");
//4.获取对方的id
mUserId = getIntent().getStringExtra("UserId");
//5.获取通话id
mCallId = getIntent().getIntExtra("CallId", 0);
//6.获取对方头像
mHeadUrl = getIntent().getStringExtra("HeadUrl");
//7.获取对方的名字,来电的时候展示
mUserName = getIntent().getStringExtra("UserName");
//8.初始化视频呼叫和接听的CallOption
JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("name", ConfigUtils.getMyName());
jsonObject.addProperty("head", ConfigUtils.getMyHead());
String s = jsonObject.toString();
mOption = new ILVCallOption(mHostId).callTips("").setMemberListener(this).setCallType(mCallType).customParam(s);
//9.根据type 类型展示布局
showLayout(mType);
}
@Override
protected void initListener() {
//1.设置视频通话监听
ILVCallManager.getInstance().addCallListener(this);
//2.被迫下线监听
ILiveLoginManager.getInstance().setUserStatusListener(this);
//3.AVRootView设置渲染后监听
av_root_view.setSubCreatedListener(this);
//2.结束监听
ll_bootom_call.setOnClickListener(this);
//3.接受监听
ll_video_yes.setOnClickListener(this);
//4.拒绝监听
ll_video_no.setOnClickListener(this);
//5.挂断监听
ll_hangup.setOnClickListener(this);
//6.静音监听
ll_no_voice.setOnClickListener(this);
//7.免提监听
ll_big_voice.setOnClickListener(this);
//8.摄像头切换
iv_switch_camera.setOnClickListener(this);
}
/**
* 根据mType 类型展示布局
*
* @param mType
*/
public void showLayout(int mType) {
switch (mType) {
case 1:
//1.判断摄像头权限
if (mCallType == ILVCallConstants.CALL_TYPE_VIDEO) {
openCamerePermisson();
} else {
openRecordPermisson();
}
//2.展示视频呼叫布局
showDefault();
showBigSmallHead();
ll_bootom_call.setVisibility(View.VISIBLE);
//3.修改提示语,展示头像
tv_waring.setText(((mCallType == ILVCallConstants.CALL_TYPE_VIDEO) ? "视频电话拨通中..." : "聊天拨通中..."));
//4.呼叫
makecall(mUserId);
break;
case 2:
//1.展示视频邀请布局
showDefault();
showBigSmallHead();
ll_bottom_invitation.setVisibility(View.VISIBLE);
//2.设置提示语
tv_waring.setText(mUserName + ((mCallType == ILVCallConstants.CALL_TYPE_VIDEO) ? "邀请你加入视频通话" : "邀请你加入语音通话"));
break;
case 3:
//1.展示视频通话布局
showDefault();
ll_bootom_conversation.setVisibility(View.VISIBLE);
//2.根据视频通话和语音通话展示不同的布局
if (mCallType == ILVCallConstants.CALL_TYPE_VIDEO) {
//1.校验麦克分权限(视频权限在呼叫和接听的时候校验了)
openRecordPermisson();
//2.展示两路视频界面
rl_video_conversation.setVisibility(View.VISIBLE);
//3.初始化AV基础
ILVCallManager.getInstance().initAvView(av_root_view);
} else {
//1.展示大小头像界面
rl_big_samll_head.setVisibility(View.VISIBLE);
//2.设置提示语
tv_waring.setText("通话中");
}
//3.初始化计时器
timeRunable.run();
break;
default:
break;
}
}
//打开相机权限
public void openCamerePermisson() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN // api16之后需要权限
&& ActivityCompat.checkSelfPermission(VoiceCallActivity.this, Manifest.permission.CAMERA)
!= PackageManager.PERMISSION_GRANTED) {//权限不够时,提示
ToastUtil.setToast("请打开摄像头权限");
}
}
//打开麦克风权限
public void openRecordPermisson() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN // api16之后需要权限
&& ActivityCompat.checkSelfPermission(VoiceCallActivity.this, Manifest.permission.RECORD_AUDIO)
!= PackageManager.PERMISSION_GRANTED) {//权限不够时,提示
ToastUtil.setToast("请打开麦克风权限权限");
}
}
//展示大小头像
private void showBigSmallHead() {
rl_big_samll_head.setVisibility(View.VISIBLE);
AppUtils.loadCirclePic(this, R.mipmap.personal, mHeadUrl, iv_video_small_head);
AppUtils.loadBitmapCicleIcon(this, R.mipmap.personal_center, mHeadUrl, iv_video_big_head);
}
//计时器
private Runnable timeRunable = new Runnable() {
@Override
public void run() {
currentSecond = currentSecond + 1000;
tv_yuyin_timer.setText(getFormatHMS(currentSecond));
//递归调用本runable对象,实现每隔一秒一次执行任务
mHandler.postDelayed(this, 1000);
}
};
// 根据毫秒返回时分秒
public static String getFormatHMS(long time) {
time = time / 1000;//总秒数
int s = (int) (time % 60);//秒
int m = (int) (time / 60);//分
int h = (int) (time / 3600);//秒
return String.format("%02d:%02d:%02d", h, m, s);
}
/**
* 默认隐藏所有布局,根据需求展示
*/
public void showDefault() {
//1.大小头像界面
rl_big_samll_head.setVisibility(View.GONE);
//2.两路视频界面
rl_video_conversation.setVisibility(View.GONE);
//3.呼叫底部
ll_bootom_call.setVisibility(View.GONE);
//4.邀请底部
ll_bottom_invitation.setVisibility(View.GONE);
//5.通话底部
ll_bootom_conversation.setVisibility(View.GONE);
}
/**
* type 为1时,掉发起呼叫
*
* @param mUserId:对方id
*/
public void makecall(String mUserId) {
//1.发起呼叫生成id
mCallId = ILVCallManager.getInstance().makeCall(mUserId + "", mOption, new ILiveCallBack() {
@Override
public void onSuccess(Object data) {
}
@Override
public void onError(String module, int errCode, String errMsg) {
toast(errMsg);
finish();
}
});
//2.设置呼叫类型
VoiceService.isCallType = true;
//3.设置通话Id
VoiceService.isSendNOtifacation = false;
}
@Override
protected int getLayoutId() {
return R.layout.activity_voice_call;
}
@Override
protected void otherViewClick(View view) {
switch (view.getId()) {
case R.id.ll_bootom_call://取消
//1.结束挂断电话
ILVCallManager.getInstance().endCall(mCallId);
break;
case R.id.ll_video_yes://接受
//1.判断是否有网
if (!AppUtils.isAccessNetwork(mContext)) {
toast("请检查您的网络");
return;
}
//2.权限验证
if (mCallType == ILVCallConstants.CALL_TYPE_VIDEO) {
openCamerePermisson();
} else {
openRecordPermisson();
}
//3.接受电话
ILVCallManager.getInstance().acceptCall(mCallId, mOption);
break;
case R.id.ll_video_no://拒绝
//1.若没有启动过app启动app
showMainActivity();
//2.拒绝通知对方已拒绝
ILVCallManager.getInstance().rejectCall(mCallId);
break;
case R.id.ll_hangup://挂断
//1.应答界面需要校验是否启动过app
if (!TextUtils.isEmpty(mUserName)) {
showMainActivity();
}
//2.通话挂断
ILVCallManager.getInstance().endCall(mCallId);
break;
case R.id.ll_no_voice: //静音
changeMic();
break;
case R.id.ll_big_voice: //免提
changeSpeaker();
break;
case R.id.iv_switch_camera: //摄像头切换
switchCamera();
break;
default:
break;
}
}
private void showMainActivity() {
List<Activity> activityList = MyApplication.getmApplication().getActivityList();
int size = activityList.size();
boolean isLanch = false;
for (int i = 0; i < size; i++) {
LogUtils.d("suyan", "=========栈里的acivity" + activityList.get(i).getLocalClassName());
if (activityList.get(i).getLocalClassName().equals("activity.MainActivity")) {
isLanch = true;
break;
}
}
if (!isLanch) {
LogUtils.d("suyan", "=========启动");
SwitchActivityManager.startMainActivity(mContext);
}
}
//麦克风切换
private void changeMic() {
if (mMicEnalbe) {
ILVCallManager.getInstance().enableMic(false);
} else {
ILVCallManager.getInstance().enableMic(true);
}
mMicEnalbe = !mMicEnalbe;
//关闭麦克风
iv_no_voice.setImageResource(mMicEnalbe ? R.mipmap.jingyin_f : R.mipmap.jingyin_t);
}
// 扬声器切换
private void changeSpeaker() {
if (mSpeaker) {
ILiveSDK.getInstance().getAvAudioCtrl().setAudioOutputMode(AVAudioCtrl.OUTPUT_MODE_HEADSET);
} else {
ILiveSDK.getInstance().getAvAudioCtrl().setAudioOutputMode(AVAudioCtrl.OUTPUT_MODE_SPEAKER);
}
mSpeaker = !mSpeaker;
iv_big_voice.setImageResource(mSpeaker ? R.mipmap.hands_free_f : R.mipmap.hands_free_t);
}
//切换摄像头
private void switchCamera() {
mCurCameraId = (ILiveConstants.FRONT_CAMERA == mCurCameraId) ? ILiveConstants.BACK_CAMERA : ILiveConstants.FRONT_CAMERA;
ILVCallManager.getInstance().switchCamera(mCurCameraId);
}
@Override
protected void onResume() {
ILVCallManager.getInstance().onResume();
super.onResume();
}
@Override
protected void onPause() {
ILVCallManager.getInstance().onPause();
super.onPause();
}
@Override
protected void onDestroy() {
ILVCallManager.getInstance().removeCallListener(this);
ILVCallManager.getInstance().onDestory();
tv_yuyin_timer = null;
timeRunable = null;
mHandler.removeMessages(0);
super.onDestroy();
}
@Override
public void showLoading() {
}
@Override
public void hideLoading() {
}
//摄像头事件
@Override
public void onCameraEvent(String id, boolean bEnable) {
}
//麦克风事件
@Override
public void onMicEvent(String id, boolean bEnable) {
}
//通话建立成功,视频呼叫时回来的监听
@Override
public void onCallEstablish(int callId) {
//1.修改mType类型
mType = 3;
//2.建立通话界面
showLayout(mType);
//3. 交换两路视频
av_root_view.swapVideoView(0, 1);
}
//通话结束
@Override
public void onCallEnd(int callId, int endResult, String endInfo) {
finish();
}
//通话异常
@Override
public void onException(int iExceptionId, int errCode, String errMsg) {
toast(errMsg);
finish();
}
/**
* 监听Back键按下事件
*/
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if ((keyCode == KeyEvent.KEYCODE_BACK)) {
if (mType == 1) { //取消通话
ILVCallManager.getInstance().endCall(mCallId);
} else if (mType == 2) {//拒绝通话
ILVCallManager.getInstance().rejectCall(mCallId);
} else if (mType == 3) { //结束通话
ILVCallManager.getInstance().endCall(mCallId);
}
}
return super.onKeyDown(keyCode, event);
}
@Override
public void onForceOffline(int error, String message) {
//1.被迫下线监听提示
toast("您的账号在其他地方登陆");
//2.关闭界面
finish();
}
//渲染后监听设置拖动已经切换两路视频
@Override
public void onSubViewCreated() {
LogUtils.d("suyan", "====视频建立" + ILiveLoginManager.getInstance().getMyUserId() + "=" + av_root_view.getViewByIndex(0).getIdentifier() + "/" + av_root_view.getViewByIndex(1).getIdentifier());
//4.设置点击小屏切换及可拖动
for (int i = 1; i < ILiveConstants.MAX_AV_VIDEO_NUM; i++) {
final int index = i;
AVVideoView minorView = av_root_view.getViewByIndex(i);
if (ILiveLoginManager.getInstance().getMyUserId().equals(minorView.getIdentifier())) {
minorView.setMirror(true); // 本地镜像
}
minorView.setDragable(true); // 小屏可拖动
minorView.setGestureListener(new GestureDetector.SimpleOnGestureListener() {
@Override
public boolean onSingleTapConfirmed(MotionEvent e) {
av_root_view.swapVideoView(0, index); // 与大屏交换
return false;
}
});
}
}
}
activity_voice_call 的布局文件
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#66000000"
tools:context="com.jyjt.ydyl.txim.VoiceCallActivity">
<!--大小头像 ,呼叫提示-->
<RelativeLayout
android:id="@+id/rl_big_samll_head"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!--大头像-->
<ImageView
android:id="@+id/iv_video_big_head"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop" />
<View
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/frame_blcak" />
<!--小头像-->
<ImageView
android:id="@+id/iv_video_small_head"
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_centerHorizontal="true"
android:layout_marginTop="125dp"
android:scaleType="centerCrop" />
<!--通话计时器-->
<TextView
android:id="@+id/tv_yuyin_timer"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/iv_video_small_head"
android:layout_centerHorizontal="true"
android:layout_marginTop="10dp"
android:textColor="@color/white"
android:textSize="15sp" />
<TextView
android:id="@+id/tv_waring"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/tv_yuyin_timer"
android:layout_centerHorizontal="true"
android:layout_marginTop="3dp"
android:text="通话中"
android:textColor="@color/white"
android:textSize="15sp" />
</RelativeLayout>
<!--两路视频通话界面-->
<RelativeLayout
android:id="@+id/rl_video_conversation"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/colorPrimary"
android:visibility="visible">
<com.tencent.ilivesdk.view.AVRootView
android:id="@+id/av_root_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<!--切换摄像头-->
<ImageView
android:id="@+id/iv_switch_camera"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_alignParentRight="true"
android:layout_marginRight="20dp"
android:layout_marginTop="20dp"
android:src="@mipmap/ic_sw_camer" />
</RelativeLayout>
<!--底部,视屏呼叫界面,挂断-->
<LinearLayout
android:id="@+id/ll_bootom_call"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:layout_marginBottom="20dp"
android:orientation="vertical">
<ImageView
android:id="@+id/btn_hangup_call"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:src="@mipmap/huang_up" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="10dp"
android:gravity="center"
android:text="挂断"
android:textColor="@color/white"
android:textSize="12sp" />
</LinearLayout>
<!--底部,视屏邀请界面-->
<LinearLayout
android:id="@+id/ll_bottom_invitation"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_marginBottom="20dp"
android:orientation="horizontal">
<!--挂断-->
<LinearLayout
android:id="@+id/ll_video_no"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_marginLeft="22dp"
android:orientation="vertical"
android:visibility="visible">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:src="@mipmap/huang_up" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="10dp"
android:gravity="center"
android:text="挂断"
android:textColor="@color/white"
android:textSize="12sp" />
</LinearLayout>
<View
android:layout_width="0dp"
android:layout_height="1dp"
android:layout_weight="1" />
<!--接听-->
<LinearLayout
android:id="@+id/ll_video_yes"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginRight="22dp"
android:orientation="vertical">
<ImageView
android:id="@+id/btn_answer_call"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@mipmap/jietong" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="10dp"
android:gravity="center"
android:text="接通"
android:textColor="@color/white"
android:textSize="12sp" />
</LinearLayout>
</LinearLayout>
<!--底部,视频通话界面,静音,挂断,免提-->
<LinearLayout
android:id="@+id/ll_bootom_conversation"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_marginBottom="20dp"
android:orientation="horizontal">
<!--静音-->
<LinearLayout
android:id="@+id/ll_no_voice"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_weight="1"
android:gravity="center"
android:orientation="vertical">
<ImageView
android:id="@+id/iv_no_voice"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:scaleType="centerCrop"
android:src="@mipmap/jingyin_f" />
<TextView
android:id="@+id/tv_no_voice"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:gravity="center"
android:text="@string/mute"
android:textColor="@color/white"
android:textSize="12sp" />
</LinearLayout>
<!--挂断-->
<LinearLayout
android:id="@+id/ll_hangup"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_weight="1"
android:orientation="vertical"
android:visibility="visible">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:src="@mipmap/huang_up" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="10dp"
android:gravity="center"
android:text="挂断"
android:textColor="@color/white"
android:textSize="12sp" />
</LinearLayout>
<!--免提-->
<LinearLayout
android:id="@+id/ll_big_voice"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_weight="1"
android:gravity="center"
android:orientation="vertical">
<ImageView
android:id="@+id/iv_big_voice"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:scaleType="centerCrop"
android:src="@mipmap/hands_free_f" />
<TextView
android:id="@+id/tv_big_voice"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:gravity="center"
android:text="@string/Hands_free"
android:textColor="@color/white"
android:textSize="12sp" />
</LinearLayout>
</LinearLayout>
</RelativeLayout>
三.视频呼叫
视频呼叫启动视频呼叫界面
public void callVideo() {
if (TextUtils.isEmpty(user_id) || TextUtils.isEmpty(ConfigUtils.getUid())) {
toast("ID为空");
return;
}
startVoiceCallActivity(mContext, ConfigUtils.getUid(), user_id, 1, TXChatHead.getInstance().getUserHead(user_id) == null ? "" : TXChatHead.getInstance().getUserHead(user_id), ILVCallConstants.CALL_TYPE_VIDEO);
}
/**
* 音视频界面
*
* @param HostId :自己的id,由服务器生成的
* @param UserId :对方的id,由服务器生成的
* @param Type :页面的类型 1:视频呼叫 2:视频邀请 3:视频通话,本方法只能传1
*/
public static void startVoiceCallActivity(Context context, String HostId, String UserId, int Type, String HeadUrl, int CallType) {
Intent intent = new Intent(context, VoiceCallActivity.class);
intent.putExtra("HostId", HostId);
intent.putExtra("CallType", CallType);
intent.putExtra("UserId", UserId);
intent.putExtra("Type", Type);
intent.putExtra("HeadUrl", HeadUrl);
context.startActivity(intent);
((Activity) context).overridePendingTransition(R.anim.left_out, R.anim.left_in);
}
四.应答界面主要逻辑
应答界面,思路,callsdk 提供了来电监听ILVIncomingListener,实现了这个监听就能收到所有来电,但是有个问题这个监听写在哪里能让整个app 都收到,所以我写在了服务里面
注册服务器
<service android:name="com.jyjt.ydyl.txim.VoiceService" />
在应用启动的时候打开服务
Intent serviceIntent = new Intent(MainActivity.this, VoiceService.class);
startService(serviceIntent);
服务中的应答逻辑
package com.jyjt.ydyl.txim;
import android.app.Service;
import android.content.Intent;
import android.os.Handler;
import android.os.IBinder;
import android.text.TextUtils;
import android.util.Log;
import com.jyjt.ydyl.application.MyApplication;
import com.jyjt.ydyl.tools.ConfigUtils;
import com.jyjt.ydyl.tools.LogUtils;
import com.jyjt.ydyl.tools.SwitchActivityManager;
import com.jyjt.ydyl.tools.ToastUtil;
import com.jyjt.ydyl.txim.model.TXChatHead;
import com.jyjt.ydyl.txim.presentation.event.VcallEvent;
import com.tencent.callsdk.ILVCallConfig;
import com.tencent.callsdk.ILVCallConstants;
import com.tencent.callsdk.ILVCallListener;
import com.tencent.callsdk.ILVCallManager;
import com.tencent.callsdk.ILVCallNotification;
import com.tencent.callsdk.ILVCallNotificationListener;
import com.tencent.callsdk.ILVIncomingListener;
import com.tencent.callsdk.ILVIncomingNotification;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.HashMap;
import java.util.Observable;
import java.util.Observer;
/**
* 视频邀请监听
* 1.服务在闪屏页面启动
* <p>
* 苏艳
**/
public class VoiceService extends Service implements ILVIncomingListener, ILVCallListener, ILVCallNotificationListener {
//1.是否已经启动了应答界面
boolean isStartActivity = false;
//2.是否是主动呼叫,true :呼叫 false:应答
public static boolean isCallType = false;
//3.是否有心跳,true:有心跳 false :没有心跳
public boolean havaHeardJump = false;
//4.本次会话是否发过通知,默认没有发过
public static boolean isSendNOtifacation = false;
//5.通话后对方挂断,保留通话时长
public static String mCallTime = "";
//6.视频通话建立的时间点
long mStartTime;
//7.状态码和对应的提示文字
public static HashMap<Integer, String> mWraingHashMap = new HashMap();
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onCreate() {
//1.设置监听
ILVCallManager.getInstance().addCallListener(this);
ILVCallManager.getInstance().addIncomingListener(this);
ILVCallManager.getInstance().init(new ILVCallConfig().setAutoBusy(true).setNotificationListener(this));
//2.设置通知提示语
mWraingHashMap.put(ILVCallConstants.ERR_CALL_DISCONNECT, "通话被服务器回收");
mWraingHashMap.put(ILVCallConstants.ERR_CALL_FAILED, "请求失败");
mWraingHashMap.put(ILVCallConstants.ERR_CALL_HANGUP, "通话结束");
mWraingHashMap.put(ILVCallConstants.ERR_CALL_LOCAL_CANCEL, "聊天已取消");
mWraingHashMap.put(ILVCallConstants.ERR_CALL_NOT_EXIST, "通话不存在");
mWraingHashMap.put(ILVCallConstants.ERR_CALL_RESPONDER_LINEBUSY, "接听方占线");
mWraingHashMap.put(ILVCallConstants.ERR_CALL_RESPONDER_REFUSE, "已拒绝");
mWraingHashMap.put(ILVCallConstants.ERR_CALL_SPONSOR_CANCEL, "呼叫方取消");
mWraingHashMap.put(ILVCallConstants.ERR_CALL_SPONSOR_TIMEOUT, "呼叫方超时");
mWraingHashMap.put(ILVCallConstants.TCILiveCMD_Hangup, "对方结束通话");
mWraingHashMap.put(ILVCallConstants.TCILiveCMD_Reject, "对方已拒绝");
mWraingHashMap.put(ILVCallConstants.TCILiveCMD_SponsorCancel, "对方取消");
mWraingHashMap.put(ILVCallConstants.TCILiveCMD_SponsorTimeout, "无人接听");
super.onCreate();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
return START_STICKY;
}
@Override
public void onDestroy() {
ILVCallManager.getInstance().removeIncomingListener(this);
super.onDestroy();
}
@Override
public void onNewIncomingCall(final int callId, final int callType, final ILVIncomingNotification notification) {
Log.i("suyan", "========接听电话+service" + callId + "=" + callType + "=" + notification.getSponsorId() + "=自己的id==");
if (callId != 0 && notification != null) {
//1.判断呼叫是否超时,超时时间30m
if ((System.currentTimeMillis() / 1000) - notification.getTimeStamp() > 30) {
return;
}
//2.保存通话的id
isSendNOtifacation = false;
//3.延迟两面后判断是否有心跳
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
if (havaHeardJump) {
//4.启动应答界面
try {
isStartActivity = true;
isCallType = false;
String json = ((ILVCallNotification) notification).getUserInfo();
JSONObject jsonObj = new JSONObject(json);
String name = jsonObj.getString("name");
String head = jsonObj.getString("head");
//5.保存头像和昵称,以防止首次聊天,没有存过头像和昵称
TXChatHead.getInstance().addHead(head, notification.getSponsorId() + "", name);
//6.启动应答界面
SwitchActivityManager.startNewTaskVoiceCallActivity(VoiceService.this, 2, callId, ConfigUtils.getUid(), notification.getSponsorId(), TextUtils.isEmpty(head) ? "" : head, TextUtils.isEmpty(name) ? "" : name, callType);
} catch (JSONException e) {
e.printStackTrace();
}
}
}
}, 2000);
}
}
@Override
public void onCallEstablish(int callId) {
mStartTime = System.currentTimeMillis();
}
@Override
public void onCallEnd(int callId, int endResult, String endInfo) {
LogUtils.d("suyan", "========结束通话111");
havaHeardJump = false;
isSendNOtifacation = false;
//关闭视频通话界面,处理弱网情况视频邀请唤起,但是对方已挂断问题
if (isStartActivity) {
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
MyApplication.getmApplication().finishActivity(VoiceCallActivity.class);
isStartActivity = false;
}
}, 503);
}
}
@Override
public void onException(int iExceptionId, int errCode, String errMsg) {
}
@Override
public void onRecvNotification(int callid, ILVCallNotification notification) {
//心跳 10s 超时30s
ILVCallNotification notification = (ILVCallNotification) arg;
LogUtils.d("suyan", "===========通知的id" + notification.getNotifId() + "=====时间戳" + notification.getTimeStamp());
//1.是否有过心跳
if (notification.getNotifId() == ILVCallConstants.TCILiveCMD_HeartBeat) {
havaHeardJump = true;
}
//3.设置提示语
String waring = "";
waring = mWraingHashMap.get(notification.getNotifId());
String tostString = "";
switch (notification.getNotifId()) {
case ILVCallConstants.ERR_CALL_HANGUP: // 4 通话后,挂断,显示市场,自己发的通知
waring = "聊天时长" + (TextUtils.isEmpty(notification.getUserInfo()) ? "" : notification.getUserInfo());
tostString = "通话结束";
break;
case ILVCallConstants.TCILiveCMD_Hangup: //134 我呼叫,接通后对方挂断
Long mCallTime = System.currentTimeMillis() - mStartTime;
String formatHMS = getFormatHMS(mCallTime);
waring = TextUtils.isEmpty(formatHMS) ? "对方结束通话" : "聊天时长" + formatHMS;
tostString = "对方结束通话";
break;
default:
break;
}
if (!TextUtils.isEmpty(waring) && !isSendNOtifacation) {
//1.发本地消息,isCallType=true:我主动呼叫消息由我发出 isCallType=false:我应答消息消息有对方发出
waring = "[音视频通话]" + waring;
//次方法是给在会话中存一条本地消息,在会话界面中显示,
//ChatActivity.saveLocalMessage(notification.getSender(), waring, isCallType, isCallType);
//2.本次通话已发送过通知
isSendNOtifacation = true;
//3.toast 提示
if (isStartActivity || isCallType) {
ToastUtil.setToast((notification.getNotifId() == ILVCallConstants.TCILiveCMD_Hangup || notification.getNotifId() == ILVCallConstants.ERR_CALL_HANGUP) ? tostString : waring);
}
}
}
// 根据毫秒返回时分秒
public String getFormatHMS(long time) {
time = time / 1000;//总秒数
int s = (int) (time % 60);//秒
int m = (int) (time / 60);//分
int h = (int) (time / 3600);//秒
return String.format("%02d:%02d:%02d", h, m, s);
}
}
五.跳转应答界面
/**
* 视频邀请界面
*
* @param mType :页面的类型 1:视频呼叫 2:视频邀请 3:视频通话
* @param mCallId :会话id
*/
public static void startNewTaskVoiceCallActivity(Context context, int mType, int mCallId, String mHostId, String mUserId, String mHeadUrl, String mName, int CallType) {
Intent intent = new Intent(context, VoiceCallActivity.class);
intent.setAction(DevicePolicyManager.ACTION_ADD_DEVICE_ADMIN);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.putExtra("CallType", CallType);
intent.putExtra("Type", mType);
intent.putExtra("CallId", mCallId);
intent.putExtra("HostId", mHostId);
intent.putExtra("HeadUrl", mHeadUrl);
intent.putExtra("UserName", mName);
intent.putExtra("UserId", mUserId);
context.startActivity(intent);
}
因为在service里面启动app,有可能这个app还没有启动没有栈,所以要单独开个栈,设置flag Intent.FLAG_ACTIVITY_NEW_TASK
六.总结
以上就是音视频通话的所有逻辑,主要都是参考callsdk中的demo 和callSdk的api文档去实现。其中有几个问题是demo中没有的要自己实现,本代码都已经实现
1.通话状态通知
2.通话
网友评论