Android基于腾讯云实时音视频仿微信视频通话最小化悬浮
最近项目中有需要语音、视频通话需求,看到这个像环信、融云等sdk都有具体demo实现,但咋的领导对腾讯情有独钟啊,im要用腾讯云im,不妙的是腾讯云im并不包含有音视频通话都要自己实现,没办法深入了解腾讯云产品后,决定自己基于腾讯云实时音视频做去语音、视频通话功能。在这里把实现过程记录下为以后用到便于查阅,另一方面也给有需要的人提供一个思路,让大家少走弯路,有可能我的实现的方法不是最好,但是这或许是一个可行的方案,大家不喜勿喷。基于腾讯云实时音视频sdk 6.5.7272版本,腾讯demo下载地址:链接: https://pan.baidu.com/s/1ijsvo3kbuheiiuzcjpyv3g 提取码: ueey
一、实现效果
二、实现思路
我把实现思路拆分为了两步:1、视频通话activity的最小化。 2、视频通话悬浮框的开启
具体思路是这样的:当用户点击左上角最小化按钮的时候,最小化视频通话activity(这时activity处于后台状态),于此同时开启悬浮框,新建一个新的viewgroup将全局constents.mvideoviewlayout中用户选中的最大view动态添加到悬浮框里面去,监听悬浮框的触摸事件,让悬浮框可以拖拽移动;自定义点击事件,如果用户点击了悬浮框,则移除悬浮框然后重新调起我们在后台的视频通话activity。
1.activity是如何实现最小化的?
activity本身自带了一个movetasktoback(boolean nonroot),我们要实现最小化只需要调用movetasktoback(true)传入一个true值就可以了,但是这里有一个前提,就是需要设置activity的启动模式为singleinstance模式,两步搞定。(注:activity最小化后重新从后台回到前台会回调onrestart()方法)
@override public boolean movetasktoback(boolean nonroot) { return super.movetasktoback(nonroot); }
2.悬浮框是如何开启的?
悬浮框的实现方法最好写在service里面,将悬浮框的开启关闭与服务service的绑定解绑所关联起来,开启服务即相当于开启我们的悬浮框,解绑服务则相当于关闭关闭的悬浮框,以此来达到更好的控制效果。
a. 首先我们声明一个服务类,取名为floatvideowindowservice:
public class floatvideowindowservice extends service { @nullable @override public ibinder onbind(intent intent) { return new mybinder(); } public class mybinder extends binder { public floatvideowindowservice getservice() { return floatvideowindowservice.this; } } @override public void oncreate() { super.oncreate(); } @override public int onstartcommand(intent intent, int flags, int startid) { return super.onstartcommand(intent, flags, startid); } @override public void ondestroy() { super.ondestroy(); } }
b. 为悬浮框建立一个布局文件float_video_window_layout,悬浮框大小我这里固定为长80dp,高120dp,id为small_size_preview的relativelayout主要是一个容器,可以动态的添加view到里面去
<?xml version="1.0" encoding="utf-8"?> <linearlayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/small_size_frame_layout" android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@color/colorcombg" android:orientation="vertical"> <com.tencent.rtmp.ui.txcloudvideoview android:id="@+id/float_videoview" android:layout_width="80dp" android:layout_height="120dp" android:descendantfocusability="blocksdescendants" android:orientation="vertical" /> </linearlayout>
c. 布局定义好后,接下来就要对悬浮框做一些初始化操作了,初始化操作这里我们放在服务的oncreate()生命周期里面执行,因为只需要执行一次就行了。这里的初始化主要包括对:悬浮框的基本参数(位置,宽高等),悬浮框的点击事件以及悬浮框的触摸事件(即可拖动范围)等的设置,在onbind()中从intent中取出了activity中用户选中最大view的id,以便在后面从 constents.mvideoviewlayout中取出对应view,然后加入悬浮窗布局中
/** * 视频悬浮窗服务 */ public class floatvideowindowservice extends service { private windowmanager mwindowmanager; private windowmanager.layoutparams wmparams; private layoutinflater inflater; private string currentbiguserid; //浮动布局view private view mfloatinglayout; //容器父布局 private relativelayout smallsizepreviewlayout; private txcloudvideoview mlocalvideoview; @override public void oncreate() { super.oncreate(); initwindow();//设置悬浮窗基本参数(位置、宽高等) } @nullable @override public ibinder onbind(intent intent) { currentbiguserid = intent.getstringextra("userid"); initfloating();//悬浮框点击事件的处理 return new mybinder(); } public class mybinder extends binder { public floatvideowindowservice getservice() { return floatvideowindowservice.this; } } @override public int onstartcommand(intent intent, int flags, int startid) { return super.onstartcommand(intent, flags, startid); } /** * 设置悬浮框基本参数(位置、宽高等) */ private void initwindow() { mwindowmanager = (windowmanager) getapplicationcontext().getsystemservice(context.window_service); //设置好悬浮窗的参数 wmparams = getparams(); // 悬浮窗默认显示以左上角为起始坐标 wmparams.gravity = gravity.left | gravity.top; //悬浮窗的开始位置,因为设置的是从左上角开始,所以屏幕左上角是x=0;y=0 wmparams.x = 70; wmparams.y = 210; //得到容器,通过这个inflater来获得悬浮窗控件 inflater = layoutinflater.from(getapplicationcontext()); // 获取浮动窗口视图所在布局 mfloatinglayout = inflater.inflate(r.layout.alert_float_video_layout, null); // 添加悬浮窗的视图 mwindowmanager.addview(mfloatinglayout, wmparams); } private windowmanager.layoutparams getparams() { wmparams = new windowmanager.layoutparams(); if (build.version.sdk_int >= build.version_codes.o) { wmparams.type = windowmanager.layoutparams.type_application_overlay; } else { wmparams.type = windowmanager.layoutparams.type_phone; } //设置可以显示在状态栏上 wmparams.flags = windowmanager.layoutparams.flag_not_focusable | windowmanager.layoutparams.flag_not_touch_modal | windowmanager.layoutparams.flag_layout_in_screen | windowmanager.layoutparams.flag_layout_inset_decor | windowmanager.layoutparams.flag_watch_outside_touch; //设置悬浮窗口长宽数据 wmparams.width = windowmanager.layoutparams.wrap_content; wmparams.height = windowmanager.layoutparams.wrap_content; return wmparams; } private void initfloating() { } }
d. 在悬浮框成功被初始化以及相关参数被设置后,接下来就需要将activity中用户选中最大的view添加到悬浮框里面去了,这样我们才能看到视频画面嘛,同样我们是在service的oncreate这个生命周期中initfloating()完成这个操作的,代码如下所示:
trtcvideoviewlayout mtrtcvideoviewlayout = constents.mvideoviewlayout; txcloudvideoview mlocalvideoview = mtrtcvideoviewlayout.getcloudvideoviewbyuseid(currentbiguserid); if (mlocalvideoview == null) { mlocalvideoview = mtrtcvideoviewlayout.getcloudvideoviewbyindex(0); } if (constdata.userid.equals(currentbiguserid)) { txcglsurfaceview mtxcglsurfaceview = mlocalvideoview.getglsurfaceview(); if (mtxcglsurfaceview != null && mtxcglsurfaceview.getparent() != null) { ((viewgroup) mtxcglsurfaceview.getparent()).removeview(mtxcglsurfaceview); mtxcloudvideoview.addvideoview(mtxcglsurfaceview); } } else { textureview mtextureview = mlocalvideoview.getvideoview(); if (mtextureview != null && mtextureview.getparent() != null) { ((viewgroup) mtextureview.getparent()).removeview(mtextureview); mtxcloudvideoview.addvideoview(mtextureview); } }
e. 我们上面说到要将服务service的绑定与解绑与悬浮框的开启和关闭相结合,所以既然我们在服务的oncreate()方法中开启了悬浮框,那么就应该在其ondestroy()方法中对悬浮框进行关闭,关闭悬浮框的本质是将相关view给移除掉,在服务的ondestroy()方法中执行如下代码:
@override public void ondestroy() { super.ondestroy(); if (mfloatinglayout != null) { // 移除悬浮窗口 mwindowmanager.removeview(mfloatinglayout); mfloatinglayout = null; constents.isshowfloatwindow = false; } }
f. 服务的绑定方式有bindservice和startservice两种,使用不同的绑定方式其生命周期也会不一样,已知我们需要让悬浮框在视频通话activity finish掉的时候也顺便关掉,那么理所当然我们就应该采用bind方式来启动服务,让他的生命周期跟随他的开启者,也即是跟随开启它的activity生命周期。
intent = new intent(this, floatvideowindowservice.class);//开启服务显示悬浮框 bindservice(intent, mvideoserviceconnection, context.bind_auto_create); serviceconnection mvideoserviceconnection = new serviceconnection() { @override public void onserviceconnected(componentname name, ibinder service) { // 获取服务的操作对象 floatvideowindowservice.mybinder binder = (floatvideowindowservice.mybinder) service; binder.getservice(); } @override public void onservicedisconnected(componentname name) { } };
service完整代码如下:
/** * 视频悬浮窗服务 */ public class floatvideowindowservice extends service { private windowmanager mwindowmanager; private windowmanager.layoutparams wmparams; private layoutinflater inflater; private string currentbiguserid; //浮动布局view private view mfloatinglayout; //容器父布局 private txcloudvideoview mtxcloudvideoview; @override public void oncreate() { super.oncreate(); initwindow();//设置悬浮窗基本参数(位置、宽高等) } @nullable @override public ibinder onbind(intent intent) { currentbiguserid = intent.getstringextra("userid"); initfloating();//悬浮框点击事件的处理 return new mybinder(); } public class mybinder extends binder { public floatvideowindowservice getservice() { return floatvideowindowservice.this; } } @override public int onstartcommand(intent intent, int flags, int startid) { return super.onstartcommand(intent, flags, startid); } @override public void ondestroy() { super.ondestroy(); if (mfloatinglayout != null) { // 移除悬浮窗口 mwindowmanager.removeview(mfloatinglayout); mfloatinglayout = null; constents.isshowfloatwindow = false; } } /** * 设置悬浮框基本参数(位置、宽高等) */ private void initwindow() { mwindowmanager = (windowmanager) getapplicationcontext().getsystemservice(context.window_service); //设置好悬浮窗的参数 wmparams = getparams(); // 悬浮窗默认显示以左上角为起始坐标 wmparams.gravity = gravity.left | gravity.top; //悬浮窗的开始位置,因为设置的是从左上角开始,所以屏幕左上角是x=0;y=0 wmparams.x = 70; wmparams.y = 210; //得到容器,通过这个inflater来获得悬浮窗控件 inflater = layoutinflater.from(getapplicationcontext()); // 获取浮动窗口视图所在布局 mfloatinglayout = inflater.inflate(r.layout.alert_float_video_layout, null); // 添加悬浮窗的视图 mwindowmanager.addview(mfloatinglayout, wmparams); } private windowmanager.layoutparams getparams() { wmparams = new windowmanager.layoutparams(); if (build.version.sdk_int >= build.version_codes.o) { wmparams.type = windowmanager.layoutparams.type_application_overlay; } else { wmparams.type = windowmanager.layoutparams.type_phone; } //设置可以显示在状态栏上 wmparams.flags = windowmanager.layoutparams.flag_not_focusable | windowmanager.layoutparams.flag_not_touch_modal | windowmanager.layoutparams.flag_layout_in_screen | windowmanager.layoutparams.flag_layout_inset_decor | windowmanager.layoutparams.flag_watch_outside_touch; //设置悬浮窗口长宽数据 wmparams.width = windowmanager.layoutparams.wrap_content; wmparams.height = windowmanager.layoutparams.wrap_content; return wmparams; } private void initfloating() { mtxcloudvideoview = mfloatinglayout.findviewbyid(r.id.float_videoview); trtcvideoviewlayout mtrtcvideoviewlayout = constents.mvideoviewlayout; txcloudvideoview mlocalvideoview = mtrtcvideoviewlayout.getcloudvideoviewbyuseid(currentbiguserid); if (mlocalvideoview == null) { mlocalvideoview = mtrtcvideoviewlayout.getcloudvideoviewbyindex(0); } if (constdata.userid.equals(currentbiguserid)) { txcglsurfaceview mtxcglsurfaceview = mlocalvideoview.getglsurfaceview(); if (mtxcglsurfaceview != null && mtxcglsurfaceview.getparent() != null) { ((viewgroup) mtxcglsurfaceview.getparent()).removeview(mtxcglsurfaceview); mtxcloudvideoview.addvideoview(mtxcglsurfaceview); } } else { textureview mtextureview = mlocalvideoview.getvideoview(); if (mtextureview != null && mtextureview.getparent() != null) { ((viewgroup) mtextureview.getparent()).removeview(mtextureview); mtxcloudvideoview.addvideoview(mtextureview); } } constents.isshowfloatwindow = true; //悬浮框触摸事件,设置悬浮框可拖动 mtxcloudvideoview.setontouchlistener(new floatinglistener()); //悬浮框点击事件 mtxcloudvideoview.setonclicklistener(new view.onclicklistener() { @override public void onclick(view v) { //在这里实现点击重新回到activity intent intent = new intent(floatvideowindowservice.this, trtcvideocallactivity.class); startactivity(intent); } }); } //开始触控的坐标,移动时的坐标(相对于屏幕左上角的坐标) private int mtouchstartx, mtouchstarty, mtouchcurrentx, mtouchcurrenty; //开始时的坐标和结束时的坐标(相对于自身控件的坐标) private int mstartx, mstarty, mstopx, mstopy; //判断悬浮窗口是否移动,这里做个标记,防止移动后松手触发了点击事件 private boolean ismove; private class floatinglistener implements view.ontouchlistener { @override public boolean ontouch(view v, motionevent event) { int action = event.getaction(); switch (action) { case motionevent.action_down: ismove = false; mtouchstartx = (int) event.getrawx(); mtouchstarty = (int) event.getrawy(); mstartx = (int) event.getx(); mstarty = (int) event.gety(); break; case motionevent.action_move: mtouchcurrentx = (int) event.getrawx(); mtouchcurrenty = (int) event.getrawy(); wmparams.x += mtouchcurrentx - mtouchstartx; wmparams.y += mtouchcurrenty - mtouchstarty; mwindowmanager.updateviewlayout(mfloatinglayout, wmparams); mtouchstartx = mtouchcurrentx; mtouchstarty = mtouchcurrenty; break; case motionevent.action_up: mstopx = (int) event.getx(); mstopy = (int) event.gety(); if (math.abs(mstartx - mstopx) >= 1 || math.abs(mstarty - mstopy) >= 1) { ismove = true; } break; default: break; } //如果是移动事件不触发onclick事件,防止移动的时候一放手形成点击事件 return ismove; } } }
activity中的操作
现在我们将思路了捋一下,假设现在我正在进行视频通话,点击视频最小化按钮,我们应该按顺序执行如下步骤:应该是会出现个悬浮框。我们用mservicebound保存service注册状态,后面解绑时候用这个去判断,不能有些从其他页面过来调用onrestart()方法的会报错 说 service not register之类的错误。
/* * 开启悬浮video服务 */ private void startvideoservice() { //最小化activity movetasktoback(true); constents.mvideoviewlayout = mvideoviewlayout; //开启服务显示悬浮框 intent floatvideointent = new intent(this, floatvideowindowservice.class); floatvideointent.putextra("userid", currentbiguserid); mservicebound=bindservice(floatvideointent, mvideocallserviceconnection, context.bind_auto_create); }
注意:这里用了一个全部变量 constents.mvideoviewlayout 保存activity中的mvideoviewlayout,以便在上面的service中使用。
当我们点击悬浮框的时候,可以使用startactivity(intent)来再次打开我们的activity,这时候视频通话activity会回调onrestart()方法,我们在onrestart()生命周期里面unbind解绑掉悬浮框服务,并且重新设置mvideoviewlayout展示
@override protected void onrestart() { super.onrestart(); //不显示悬浮框 if (mservicebound) { unbindservice(mvideocallserviceconnection); mservicebound = false; } txcloudvideoview txcloudvideoview = mvideoviewlayout.getcloudvideoviewbyuseid(currentbiguserid); if (txcloudvideoview == null) { txcloudvideoview = mvideoviewlayout.getcloudvideoviewbyindex(0); } if(constdata.userid.equals(currentbiguserid)){ txcglsurfaceview mtxcglsurfaceview=txcloudvideoview.getglsurfaceview(); if (mtxcglsurfaceview!=null && mtxcglsurfaceview.getparent() != null) { ((viewgroup) mtxcglsurfaceview.getparent()).removeview(mtxcglsurfaceview); txcloudvideoview.addvideoview(mtxcglsurfaceview); } }else{ textureview mtextureview=txcloudvideoview.getvideoview(); if (mtextureview!=null && mtextureview.getparent() != null) { ((viewgroup) mtextureview.getparent()).removeview(mtextureview); txcloudvideoview.addvideoview(mtextureview); } } }
视频activity是在demo中trtcmainactivity的基础上修改完善的
视频activity全部代码如下:
public class trtcvideocallactivity extends activity implements view.onclicklistener, trtcsettingdialog.isettinglistener, trtcmoredialog.imorelistener, trtcvideoviewlayout.itrtcvideoviewlayoutlistener, trtcvideoviewlayout.onvideotochatclicklistener, trtccallmessagemanager.trtcvideocallmessagecancellistener { private final static string tag = trtcvideocallactivity.class.getsimplename(); private boolean benablevideo = true, benableaudio = true; private boolean mcamerafront = true; private textview tvroomid; private imageview ivcamera, ivvoice; private trtcvideoviewlayout mvideoviewlayout; //通话计时 private chronometer calltimechronometer; private trtcclouddef.trtcparams trtcparams; /// trtc sdk 视频通话房间进入所必须的参数 private trtccloud trtccloud; /// trtc sdk 实例对象 private trtccloudlistenerimpl trtclistener; /// trtc sdk 回调监听 private hashset<string> mroommembers = new hashset<>(); private int msdkappid = -1; private string trtccallfrom; private string trtccalltype; private int roomid; private string usersig; private countdowntimer countdowntimer; private imageview trtcsmalliv; private string currentbiguserid = constdata.userid; private homewatcher mhomewatcher; private boolean mservicebound = false; /** * 不包含自己的接收人列表(单聊情况) */ private list<sampleuser> receiveusers = new arraylist<>(); private static class videostream { string userid; int streamtype; public boolean equals(object obj) { if (obj == null || userid == null) return false; videostream stream = (videostream) obj; return (this.streamtype == stream.streamtype && this.userid.equals(stream.userid)); } } /** * 定义服务绑定的回调 开启视频通话服务连接 */ private serviceconnection mvideocallserviceconnection = new serviceconnection() { @override public void onserviceconnected(componentname name, ibinder service) { // 获取服务的操作对象 floatvideowindowservice.mybinder binder = (floatvideowindowservice.mybinder) service; binder.getservice(); } @override public void onservicedisconnected(componentname name) { } }; private arraylist<videostream> mvideosinroom = new arraylist<>(); @override protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); //应用运行时,保持屏幕高亮,不锁屏 getwindow().addflags(windowmanager.layoutparams.flag_keep_screen_on); requestwindowfeature(window.feature_no_title); getwindow().setflags(windowmanager.layoutparams.flag_fullscreen, windowmanager.layoutparams.flag_fullscreen); activityutil.adddestoryactivitytomap(trtcvideocallactivity.this, tag); trtccallmessagemanager.getinstance().settrtcvideocallmessagelistener(this); //获取前一个页面得到的进房参数 intent intent = getintent(); long msdkappidtemp = intent.getlongextra("sdkappid", 0); msdkappid = integer.parseint(string.valueof(msdkappidtemp)); roomid = intent.getintextra("roomid", 0); trtccallfrom = intent.getstringextra("trtccallfrom"); trtccalltype = intent.getstringextra("trtccalltype"); constdata.currenttrtccalltype = trtccalltype; constdata.currentroomid = roomid + ""; receiveusers = (list<sampleuser>) getintent().getserializableextra("receiveuserlist"); usersig = intent.getstringextra("usersig"); trtcparams = new trtcclouddef.trtcparams(msdkappid, constdata.userid, usersig, roomid, "", ""); trtcparams.role = trtcclouddef.trtcroleanchor; //初始化 ui 控件 initview(); //创建 trtc sdk 实例 trtclistener = new trtccloudlistenerimpl(this); trtccloud = trtccloud.sharedinstance(this); trtccloud.setlistener(trtclistener); //开始进入视频通话房间 enterroom(); /** 倒计时30秒,一次1秒 */ countdowntimer = new countdowntimer(30 * 1000, 1000) { @override public void ontick(long millisuntilfinished) { // todo auto-generated method stub if (!trtcvideocallactivity.this.isfinishing() && constdata.enterroomuseridset.size() > 0) { countdowntimer.cancel(); } } @override public void onfinish() { //倒计时全部结束执行操作 if (!trtcvideocallactivity.this.isfinishing() && constdata.enterroomuseridset.size() == 0) { exitroom(); } } }; countdowntimer.start(); /** * home键监听相关 */ mhomewatcher = new homewatcher(this); mhomewatcher.setonhomepressedlistener(new homewatcher.onhomepressedlistener() { @override public void onhomepressed() { //按了home键 //如果悬浮窗没有显示 就开启服务展示悬浮窗 if (!constents.isshowfloatwindow) { startvideoservice(); } } @override public void onrecentappspressed() { //最近app任务列表按键 if (!constents.isshowfloatwindow) { startvideoservice(); } } }); mhomewatcher.startwatch(); } @override protected void onresume() { super.onresume(); } @override protected void ondestroy() { super.ondestroy(); if (countdowntimer != null) { countdowntimer.cancel(); } trtccloud.setlistener(null); trtccloud.destroysharedinstance(); constdata.isentertrtccall = false; //解绑 不显示悬浮框 if (mservicebound) { unbindservice(mvideocallserviceconnection); mservicebound = false; } if (mhomewatcher != null) { mhomewatcher.stopwatch();// 在销毁时停止监听,不然会报错的。 } } /** * 重写onbackpressed * 屏蔽返回键 */ @override public void onbackpressed() { // super.onbackpressed();//要去掉这句 } /** * 初始化界面控件,包括主要的视频显示view,以及底部的一排功能按钮 */ private void initview() { setcontentview(r.layout.activity_trtc_video); trtcsmalliv = (imageview) findviewbyid(r.id.trtc_small_iv); trtcsmalliv.setonclicklistener(this); initclickablelayout(r.id.ll_camera); initclickablelayout(r.id.ll_voice); initclickablelayout(r.id.ll_change_camera); mvideoviewlayout = (trtcvideoviewlayout) findviewbyid(r.id.video_ll_mainview); mvideoviewlayout.setuserid(trtcparams.userid); mvideoviewlayout.setlistener(this); mvideoviewlayout.setonvideotochatlistener(this); calltimechronometer = (chronometer) findviewbyid(r.id.call_time_chronometer); ivvoice = (imageview) findviewbyid(r.id.iv_mic); ivcamera = (imageview) findviewbyid(r.id.iv_camera); tvroomid = (textview) findviewbyid(r.id.tv_room_id); tvroomid.settext(constdata.username + "(自己)"); findviewbyid(r.id.video_ring_off_btn).setonclicklistener(new view.onclicklistener() { @override public void onclick(view v) { exitroom(); /** * 单人通话时 * 新增主叫方在接收方未接听前挂断时 * 发送消息给接收方 让接收方取消响铃页面或者 来电弹框 */ if ( trtccalltype.equals(constents.one_to_one_video_call)) { //constdata.enterroomuseridset.size() == 0表示还没有接收方加入房间 if (constdata.enterroomuseridset.size() == 0) { senddeclinemsg(); } } } }); } private linearlayout initclickablelayout(int resid) { linearlayout layout = (linearlayout) findviewbyid(resid); layout.setonclicklistener(this); return layout; } /** * 设置视频通话的视频参数:需要 trtcsettingdialog 提供的分辨率、帧率和流畅模式等参数 */ private void settrtccloudparam() { // 大画面的编码器参数设置 // 设置视频编码参数,包括分辨率、帧率、码率等等,这些编码参数来自于 trtcsettingdialog 的设置 // 注意(1):不要在码率很低的情况下设置很高的分辨率,会出现较大的马赛克 // 注意(2):不要设置超过25fps以上的帧率,因为电影才使用24fps,我们一般推荐15fps,这样能将更多的码率分配给画质 trtcclouddef.trtcvideoencparam encparam = new trtcclouddef.trtcvideoencparam(); encparam.videoresolution = trtcclouddef.trtc_video_resolution_640_360; encparam.videofps = 15; encparam.videobitrate = 600; encparam.videoresolutionmode = trtcclouddef.trtc_video_resolution_mode_portrait; trtccloud.setvideoencoderparam(encparam); trtcclouddef.trtcnetworkqosparam qosparam = new trtcclouddef.trtcnetworkqosparam(); qosparam.controlmode = trtcclouddef.video_qos_control_server; qosparam.preference = trtcclouddef.trtc_video_qos_preference_clear; trtccloud.setnetworkqosparam(qosparam); trtccloud.setpriorremotevideostreamtype(trtcclouddef.trtc_video_stream_type_big); } /** * 加入视频房间:需要 trtcnewviewactivity 提供的 trtcparams 函数 */ private void enterroom() { // 预览前配置默认参数 settrtccloudparam(); // 开启视频采集预览 if (trtcparams.role == trtcclouddef.trtcroleanchor) { startlocalvideo(true); } trtccloud.setbeautystyle(trtcclouddef.trtc_beauty_style_smooth, 5, 5, 5); if (trtcparams.role == trtcclouddef.trtcroleanchor) { trtccloud.startlocalaudio(); } setvideofillmode(true); setvideorotation(true); enableaudiohandfree(true); enablegsensor(true); enableaudiovolumeevaluation(false); /** * 2019/08/08 * 默认打开是前置摄像头 * 前置摄像头就设置镜像 true */ enablevideoencmirror(true); setlocalviewmirrormode(trtcclouddef.trtc_video_mirror_type_auto); mvideosinroom.clear(); mroommembers.clear(); trtccloud.enterroom(trtcparams, trtcclouddef.trtc_app_scene_videocall); } /** * 退出视频房间 */ private void exitroom() { if (trtccloud != null) { trtccloud.exitroom(); } toastutil.toastshortmessage("通话已结束"); } @override public void onclick(view v) { if (v.getid() == r.id.trtc_small_iv) { startvideoservice(); } else if (v.getid() == r.id.ll_camera) { onenablevideo(); } else if (v.getid() == r.id.ll_voice) { onenableaudio(); } else if (v.getid() == r.id.ll_change_camera) { onchangecamera(); } } /** * 发送挂断/拒接电话消息 */ private void senddeclinemsg() { timmessage timmessage = new timmessage(); timcustomelem ele = new timcustomelem(); /** * 挂断/拒接语音、视频通话消息 * msgcontent不放内容 */ string msgstr = null; if (trtccalltype.equals(constents.one_to_one_audio_call) || trtccalltype.equals(constents.one_to_multipe_audio_call)) { msgstr = jsonutil.tojson(constents.audio_call_message_decline_desc, null); } else if (trtccalltype.equals(constents.one_to_one_video_call) || trtccalltype.equals(constents.one_to_multipe_video_call)) { msgstr = jsonutil.tojson(constents.video_call_message_decline_desc, null); } ele.setdata(msgstr.getbytes()); timmessage.addelement(ele); string receiveuserid = null; if (!receiveusers.isempty()) { sampleuser sampleuser = receiveusers.get(0); receiveuserid = sampleuser.getuserid(); } timconversation conversation = timmanager.getinstance().getconversation( timconversationtype.c2c, receiveuserid); //发送消息 conversation.sendonlinemessage(timmessage, new timvaluecallback<timmessage>() { @override public void onerror(int code, string desc) {//发送消息失败 //错误码 code 和错误描述 desc,可用于定位请求失败原因 //错误码 code 含义请参见错误码表 log.d("nnn", "send message failed. code: " + code + " errmsg: " + desc); } @override public void onsuccess(timmessage msg) {//发送消息成功 log.e("nnn", "sendmsg ok"); } }); } /** * 开启悬浮video服务 */ private void startvideoservice() { //最小化activity movetasktoback(true); constents.mvideoviewlayout = mvideoviewlayout; //开启服务显示悬浮框 intent floatvideointent = new intent(this, floatvideowindowservice.class); floatvideointent.putextra("userid", currentbiguserid); mservicebound = bindservice(floatvideointent, mvideocallserviceconnection, context.bind_auto_create); } @override protected void onrestart() { super.onrestart(); //不显示悬浮框 if (mservicebound) { unbindservice(mvideocallserviceconnection); mservicebound = false; } txcloudvideoview txcloudvideoview = mvideoviewlayout.getcloudvideoviewbyuseid(currentbiguserid); if (txcloudvideoview == null) { txcloudvideoview = mvideoviewlayout.getcloudvideoviewbyindex(0); } if(constdata.userid.equals(currentbiguserid)){ txcglsurfaceview mtxcglsurfaceview=txcloudvideoview.getglsurfaceview(); if (mtxcglsurfaceview!=null && mtxcglsurfaceview.getparent() != null) { ((viewgroup) mtxcglsurfaceview.getparent()).removeview(mtxcglsurfaceview); txcloudvideoview.addvideoview(mtxcglsurfaceview); } }else{ textureview mtextureview=txcloudvideoview.getvideoview(); if (mtextureview!=null && mtextureview.getparent() != null) { ((viewgroup) mtextureview.getparent()).removeview(mtextureview); txcloudvideoview.addvideoview(mtextureview); } } } /** * 开启/关闭视频上行 */ private void onenablevideo() { benablevideo = !benablevideo; startlocalvideo(benablevideo); mvideoviewlayout.updatevideostatus(trtcparams.userid, benablevideo); ivcamera.setimageresource(benablevideo ? r.mipmap.remote_video_enable : r.mipmap.remote_video_disable); } /** * 开启/关闭音频上行 */ private void onenableaudio() { benableaudio = !benableaudio; trtccloud.mutelocalaudio(!benableaudio); ivvoice.setimageresource(benableaudio ? r.mipmap.mic_enable : r.mipmap.mic_disable); } /** * 点击切换摄像头 */ private void onchangecamera() { mcamerafront = !mcamerafront; onswitchcamera(mcamerafront); } @override public void oncomplete() { settrtccloudparam(); setvideofillmode(true); // moredlg.updatevideofillmode(true); } /** * sdk内部状态回调 */ static class trtccloudlistenerimpl extends trtccloudlistener implements trtccloudlistener.trtcvideorenderlistener { private weakreference<trtcvideocallactivity> mcontext; private hashmap<string, testrendervideoframe> mcustomrender; public trtccloudlistenerimpl(trtcvideocallactivity activity) { super(); mcontext = new weakreference<>(activity); mcustomrender = new hashmap<>(10); } /** * 加入房间 */ @override public void onenterroom(long elapsed) { final trtcvideocallactivity activity = mcontext.get(); if (activity != null) { activity.mvideoviewlayout.onroomenter(); activity.updatecloudmixtureparams(); activity.calltimechronometer.setbase(systemclock.elapsedrealtime()); activity.calltimechronometer.start(); } } /** * 离开房间 */ @override public void onexitroom(int reason) { trtcvideocallactivity activity = mcontext.get(); constdata.enterroomuseridset.clear(); constdata.receiveuserset.clear(); constdata.isentertrtccall = false; log.e(tag, "onexitroom:11111111111111111111 "); if (activity != null) { activity.calltimechronometer.stop(); activity.finish(); } } /** * error 大多是不可恢复的错误,需要通过 ui 提示用户 */ @override public void onerror(int errcode, string errmsg, bundle extrainfo) { log.d(tag, "sdk callback onerror"); trtcvideocallactivity activity = mcontext.get(); if (activity == null) { return; } if (errcode == txliteavcode.err_room_request_token_https_timeout || errcode == txliteavcode.err_room_request_ip_timeout || errcode == txliteavcode.err_room_request_enter_room_timeout) { toast.maketext(activity, "进房超时,请检查网络或稍后重试:" + errcode + "[" + errmsg + "]", toast.length_short).show(); activity.exitroom(); return; } if (errcode == txliteavcode.err_room_request_token_invalid_parameter || errcode == txliteavcode.err_enter_room_param_null || errcode == txliteavcode.err_sdk_appid_invalid || errcode == txliteavcode.err_room_id_invalid || errcode == txliteavcode.err_user_id_invalid || errcode == txliteavcode.err_user_sig_invalid) { toast.maketext(activity, "进房参数错误:" + errcode + "[" + errmsg + "]", toast.length_short).show(); activity.exitroom(); return; } if (errcode == txliteavcode.err_accip_list_empty || errcode == txliteavcode.err_server_info_unpacking_error || errcode == txliteavcode.err_server_info_token_error || errcode == txliteavcode.err_server_info_allocate_access_failed || errcode == txliteavcode.err_server_info_generate_sign_failed || errcode == txliteavcode.err_server_info_token_timeout || errcode == txliteavcode.err_server_info_invalid_command || errcode == txliteavcode.err_server_info_generate_ken_error || errcode == txliteavcode.err_server_info_generate_token_error || errcode == txliteavcode.err_server_info_database || errcode == txliteavcode.err_server_info_bad_roomid || errcode == txliteavcode.err_server_info_bad_scene_or_role || errcode == txliteavcode.err_server_info_roomid_exchange_failed || errcode == txliteavcode.err_server_info_strgroup_has_invalid_chars || errcode == txliteavcode.err_server_acc_token_timeout || errcode == txliteavcode.err_server_acc_sign_error || errcode == txliteavcode.err_server_acc_sign_timeout || errcode == txliteavcode.err_server_center_invalid_roomid || errcode == txliteavcode.err_server_center_create_room_failed || errcode == txliteavcode.err_server_center_sign_error || errcode == txliteavcode.err_server_center_sign_timeout || errcode == txliteavcode.err_server_center_add_user_failed || errcode == txliteavcode.err_server_center_find_user_failed || errcode == txliteavcode.err_server_center_switch_termination_frequently || errcode == txliteavcode.err_server_center_location_not_exist || errcode == txliteavcode.err_server_center_route_table_error || errcode == txliteavcode.err_server_center_invalid_parameter) { toast.maketext(activity, "进房失败,请稍后重试:" + errcode + "[" + errmsg + "]", toast.length_short).show(); activity.exitroom(); return; } if (errcode == txliteavcode.err_server_center_room_full || errcode == txliteavcode.err_server_center_reach_proxy_max) { toast.maketext(activity, "进房失败,房间满了,请稍后重试:" + errcode + "[" + errmsg + "]", toast.length_short).show(); activity.exitroom(); return; } if (errcode == txliteavcode.err_server_center_room_id_too_long) { toast.maketext(activity, "进房失败,roomid超出有效范围:" + errcode + "[" + errmsg + "]", toast.length_short).show(); activity.exitroom(); return; } if (errcode == txliteavcode.err_server_acc_room_not_exist || errcode == txliteavcode.err_server_center_room_not_exist) { toast.maketext(activity, "进房失败,请确认房间号正确:" + errcode + "[" + errmsg + "]", toast.length_short).show(); activity.exitroom(); return; } if (errcode == txliteavcode.err_server_info_service_suspended) { toast.maketext(activity, "进房失败,请确认腾讯云实时音视频账号状态是否欠费:" + errcode + "[" + errmsg + "]", toast.length_short).show(); activity.exitroom(); return; } if (errcode == txliteavcode.err_server_info_privilege_flag_error || errcode == txliteavcode.err_server_center_no_priviledge_create_room || errcode == txliteavcode.err_server_center_no_priviledge_enter_room) { toast.maketext(activity, "进房失败,无权限进入房间:" + errcode + "[" + errmsg + "]", toast.length_short).show(); activity.exitroom(); return; } if (errcode <= txliteavcode.err_server_sso_sig_expired && errcode >= txliteavcode.err_server_sso_internal_error) { // 错误参考 https://cloud.tencent.com/document/product/269/1671#.e5.b8.90.e5.8f.b7.e7.b3.bb.e7.bb.9f toast.maketext(activity, "进房失败,usersig错误:" + errcode + "[" + errmsg + "]", toast.length_short).show(); activity.exitroom(); return; } toast.maketext(activity, "onerror: " + errmsg + "[" + errcode + "]", toast.length_short).show(); } /** * warning 大多是一些可以忽略的事件通知,sdk内部会启动一定的补救机制 */ @override public void onwarning(int warningcode, string warningmsg, bundle extrainfo) { log.d(tag, "sdk callback onwarning"); } /** * 有新的用户加入了当前视频房间 */ @override public void onuserenter(string userid) { trtcvideocallactivity activity = mcontext.get(); constdata.enterroomuseridset.add(userid); if (activity != null) { // 创建一个view用来显示新的一路画面 // txcloudvideoview renderview = activity.mvideoviewlayout.onmemberenter(userid + trtcclouddef.trtc_video_stream_type_big); txcloudvideoview renderview = activity.mvideoviewlayout.onmemberenter(userid); if (renderview != null) { // 设置仪表盘数据显示 renderview.setvisibility(view.visible); } } } /** * 有用户离开了当前视频房间 */ @override public void onuserexit(string userid, int reason) { trtcvideocallactivity activity = mcontext.get(); constdata.enterroomuseridset.remove(userid); if (activity != null) { if (activity.trtccallfrom.equals(userid)) { activity.exitroom(); } else { if (constdata.enterroomuseridset.size() == 0) { activity.exitroom(); } } //停止观看画面 activity.trtccloud.stopremoteview(userid); activity.trtccloud.stopremotesubstreamview(userid); //更新视频ui // activity.mvideoviewlayout.onmemberleave(userid + trtcclouddef.trtc_video_stream_type_big); // activity.mvideoviewlayout.onmemberleave(userid + trtcclouddef.trtc_video_stream_type_sub); activity.mvideoviewlayout.onmemberleave(userid ); activity.mvideoviewlayout.onmemberleave(userid ); activity.mroommembers.remove(userid); activity.updatecloudmixtureparams(); testrendervideoframe customrender = mcustomrender.get(userid); if (customrender != null) { customrender.stop(); mcustomrender.remove(userid); } } } /** * 有用户屏蔽了画面 */ @override public void onuservideoavailable(final string userid, boolean available) { trtcvideocallactivity activity = mcontext.get(); if (activity != null) { if (available) { // final txcloudvideoview renderview = activity.mvideoviewlayout.onmemberenter(userid + trtcclouddef.trtc_video_stream_type_big); final txcloudvideoview renderview = activity.mvideoviewlayout.onmemberenter(userid); if (renderview != null) { // 启动远程画面的解码和显示逻辑,fillmode 可以设置是否显示黑边 activity.trtccloud.setremoteviewfillmode(userid, trtcclouddef.trtc_video_render_mode_fit); activity.trtccloud.startremoteview(userid, renderview); activity.runonuithread(new runnable() { @override public void run() { // renderview.setuserid(userid + trtcclouddef.trtc_video_stream_type_big); renderview.setuserid(userid ); } }); } activity.mroommembers.add(userid); activity.updatecloudmixtureparams(); } else { activity.trtccloud.stopremoteview(userid); // activity.mvideoviewlayout.onmemberleave(userid + trtcclouddef.trtc_video_stream_type_big); activity.mvideoviewlayout.onmemberleave(userid ); activity.mroommembers.remove(userid); activity.updatecloudmixtureparams(); } activity.mvideoviewlayout.updatevideostatus(userid, available); } } @override public void onusersubstreamavailable(final string userid, boolean available) { trtcvideocallactivity activity = mcontext.get(); if (activity != null) { if (available) { // final txcloudvideoview renderview = activity.mvideoviewlayout.onmemberenter(userid + trtcclouddef.trtc_video_stream_type_sub); final txcloudvideoview renderview = activity.mvideoviewlayout.onmemberenter(userid ); if (renderview != null) { // 启动远程画面的解码和显示逻辑,fillmode 可以设置是否显示黑边 activity.trtccloud.setremotesubstreamviewfillmode(userid, trtcclouddef.trtc_video_render_mode_fit); activity.trtccloud.startremotesubstreamview(userid, renderview); activity.runonuithread(new runnable() { @override public void run() { // renderview.setuserid(userid + trtcclouddef.trtc_video_stream_type_sub); renderview.setuserid(userid ); } }); } } else { activity.trtccloud.stopremotesubstreamview(userid); // activity.mvideoviewlayout.onmemberleave(userid + trtcclouddef.trtc_video_stream_type_sub); activity.mvideoviewlayout.onmemberleave(userid ); } } } /** * 有用户屏蔽了声音 */ @override public void onuseraudioavailable(string userid, boolean available) { trtcvideocallactivity activity = mcontext.get(); if (activity != null) { if (available) { // final txcloudvideoview renderview = activity.mvideoviewlayout.onmemberenter(userid + trtcclouddef.trtc_video_stream_type_big); final txcloudvideoview renderview = activity.mvideoviewlayout.onmemberenter(userid ); if (renderview != null) { renderview.setvisibility(view.visible); } } } } /** * 首帧渲染回调 */ @override public void onfirstvideoframe(string userid, int streamtype, int width, int height) { trtcvideocallactivity activity = mcontext.get(); log.e(tag, "onfirstvideoframe: 77777777777777777777777"); if (activity != null) { // activity.mvideoviewlayout.freshtoolbarlayoutonmemberenter(userid + trtcclouddef.trtc_video_stream_type_big); activity.mvideoviewlayout.freshtoolbarlayoutonmemberenter(userid ); } } @override public void onstartpublishcdnstream(int err, string errmsg) { } @override public void onstoppublishcdnstream(int err, string errmsg) { } @override public void onrendervideoframe(string userid, int streamtype, trtcclouddef.trtcvideoframe frame) { // log.w(tag, string.format("onrendervideoframe userid: %s, type: %d",userid, streamtype)); } @override public void onuservoicevolume(arraylist<trtcclouddef.trtcvolumeinfo> uservolumes, int totalvolume) { // mcontext.get().mvideoviewlayout.resetaudiovolume(); for (int i = 0; i < uservolumes.size(); ++i) { mcontext.get().mvideoviewlayout.updateaudiovolume(uservolumes.get(i).userid, uservolumes.get(i).volume); } } @override public void onstatistics(trtcstatistics statics) { } @override public void onconnectotherroom(final string userid, final int err, final string errmsg) { trtcvideocallactivity activity = mcontext.get(); if (activity != null) { } } @override public void ondisconnectotherroom(final int err, final string errmsg) { trtcvideocallactivity activity = mcontext.get(); if (activity != null) { } } @override public void onnetworkquality(trtcclouddef.trtcquality localquality, arraylist<trtcclouddef.trtcquality> remotequality) { trtcvideocallactivity activity = mcontext.get(); if (activity != null) { activity.mvideoviewlayout.updatenetworkquality(localquality.userid, localquality.quality); for (trtcclouddef.trtcquality qualityinfo : remotequality) { activity.mvideoviewlayout.updatenetworkquality(qualityinfo.userid, qualityinfo.quality); } } } } @override public void onenableremotevideo(final string userid, boolean enable) { if (enable) { // final txcloudvideoview renderview = mvideoviewlayout.getcloudvideoviewbyuseid(userid + trtcclouddef.trtc_video_stream_type_big); final txcloudvideoview renderview = mvideoviewlayout.getcloudvideoviewbyuseid(userid ); if (renderview != null) { trtccloud.setremoteviewfillmode(userid, trtcclouddef.trtc_video_render_mode_fit); trtccloud.startremoteview(userid, renderview); runonuithread(new runnable() { @override public void run() { // renderview.setuserid(userid + trtcclouddef.trtc_video_stream_type_big); renderview.setuserid(userid); mvideoviewlayout.freshtoolbarlayoutonmemberenter(userid); } }); } } else { trtccloud.stopremoteview(userid); } } @override public void onenableremoteaudio(string userid, boolean enable) { trtccloud.muteremoteaudio(userid, !enable); } @override public void onchangevideofillmode(string userid, boolean adjustmode) { trtccloud.setremoteviewfillmode(userid, adjustmode ? trtcclouddef.trtc_video_render_mode_fit : trtcclouddef.trtc_video_render_mode_fill); } @override public void onchangevideoshowframe(string userid, string username) { currentbiguserid = userid; tvroomid.settext(username); } @override public void onswitchcamera(boolean bcamerafront) { trtccloud.switchcamera(); /** * 2019/08/08 * 此处增加判断 * 前置摄像头就设置镜像 true * 后置摄像头就不设置镜像 false */ if (bcamerafront) { enablevideoencmirror(true); } else { enablevideoencmirror(false); } } /** * 视频里点击进入和某人聊天 * * @param userid */ @override public void onvideotochatclick(string userid) { intent chatintent = new intent(trtcvideocallactivity.this, imsingleactivity.class); chatintent.putextra(imkeys.intent_id, userid); startactivity(chatintent); if (!constents.isshowfloatwindow) { startvideoservice(); } } /** * 拒接视频通话回调 */ @override public void ontrtcvideocallmessagecancel() { exitroom(); } @override public void onfillmodechange(boolean bfillmode) { setvideofillmode(bfillmode); } @override public void onvideorotationchange(boolean bvertical) { setvideorotation(bvertical); } @override public void onenableaudiocapture(boolean benable) { enableaudiocapture(benable); } @override public void onenableaudiohandfree(boolean benable) { enableaudiohandfree(benable); } @override public void onmirrorlocalvideo(int localviewmirror) { setlocalviewmirrormode(localviewmirror); } @override public void onmirrorremotevideo(boolean bmirror) { enablevideoencmirror(bmirror); } @override public void onenablegsensor(boolean benable) { enablegsensor(benable); } @override public void onenableaudiovolumeevaluation(boolean benable) { enableaudiovolumeevaluation(benable); } @override public void onenablecloudmixture(boolean benable) { updatecloudmixtureparams(); } private void setvideofillmode(boolean bfillmode) { if (bfillmode) { trtccloud.setlocalviewfillmode(trtcclouddef.trtc_video_render_mode_fill); } else { trtccloud.setlocalviewfillmode(trtcclouddef.trtc_video_render_mode_fit); } } private void setvideorotation(boolean bvertical) { if (bvertical) { trtccloud.setlocalviewrotation(trtcclouddef.trtc_video_rotation_0); } else { trtccloud.setlocalviewrotation(trtcclouddef.trtc_video_rotation_90); } } private void enableaudiocapture(boolean benable) { if (benable) { trtccloud.startlocalaudio(); } else { trtccloud.stoplocalaudio(); } } private void enableaudiohandfree(boolean benable) { if (benable) { trtccloud.setaudioroute(trtcclouddef.trtc_audio_route_speaker); } else { trtccloud.setaudioroute(trtcclouddef.trtc_audio_route_earpiece); } } private void enablevideoencmirror(boolean bmirror) { trtccloud.setvideoencodermirror(bmirror); } private void setlocalviewmirrormode(int mirrormode) { trtccloud.setlocalviewmirror(mirrormode); } private void enablegsensor(boolean benable) { if (benable) { trtccloud.setgsensormode(trtcclouddef.trtc_gsensor_mode_uifixlayout); } else { trtccloud.setgsensormode(trtcclouddef.trtc_gsensor_mode_disable); } } private void enableaudiovolumeevaluation(boolean benable) { if (benable) { trtccloud.enableaudiovolumeevaluation(300); mvideoviewlayout.showallaudiovolumeprogressbar(); } else { trtccloud.enableaudiovolumeevaluation(0); mvideoviewlayout.hideallaudiovolumeprogressbar(); } } private void updatecloudmixtureparams() { // 背景大画面宽高 int videowidth = 720; int videoheight = 1280; // 小画面宽高 int subwidth = 180; int subheight = 320; int offsetx = 5; int offsety = 50; int bitrate = 200; int resolution = trtcclouddef.trtc_video_resolution_640_360; switch (resolution) { case trtcclouddef.trtc_video_resolution_160_160: { videowidth = 160; videoheight = 160; subwidth = 27; subheight = 48; offsety = 20; bitrate = 200; break; } case trtcclouddef.trtc_video_resolution_320_180: { videowidth = 192; videoheight = 336; subwidth = 54; subheight = 96; offsety = 30; bitrate = 400; break; } case trtcclouddef.trtc_video_resolution_320_240: { videowidth = 240; videoheight = 320; subwidth = 54; subheight = 96; bitrate = 400; break; } case trtcclouddef.trtc_video_resolution_480_480: { videowidth = 480; videoheight = 480; subwidth = 72; subheight = 128; bitrate = 600; break; } case trtcclouddef.trtc_video_resolution_640_360: { videowidth = 368; videoheight = 640; subwidth = 90; subheight = 160; bitrate = 800; break; } case trtcclouddef.trtc_video_resolution_640_480: { videowidth = 480; videoheight = 640; subwidth = 90; subheight = 160; bitrate = 800; break; } case trtcclouddef.trtc_video_resolution_960_540: { videowidth = 544; videoheight = 960; subwidth = 171; subheight = 304; bitrate = 1000; break; } case trtcclouddef.trtc_video_resolution_1280_720: { videowidth = 720; videoheight = 1280; subwidth = 180; subheight = 320; bitrate = 1500; break; } default: break; } trtcclouddef.trtctranscodingconfig config = new trtcclouddef.trtctranscodingconfig(); config.appid = -1; // 请从"实时音视频"控制台的帐号信息中获取 config.bizid = -1; // 请进入 "实时音视频"控制台 https://console.cloud.tencent.com/rav,点击对应的应用,然后进入“帐号信息”菜单中,复制“直播信息”模块中的"bizid" config.videowidth = videowidth; config.videoheight = videoheight; config.videogop = 1; config.videoframerate = 15; config.videobitrate = bitrate; config.audiosamplerate = 48000; config.audiobitrate = 64; config.audiochannels = 1; // 设置混流后主播的画面位置 trtcclouddef.trtcmixuser broadcaster = new trtcclouddef.trtcmixuser(); broadcaster.userid = trtcparams.userid; // 以主播uid为broadcaster为例 broadcaster.zorder = 0; broadcaster.x = 0; broadcaster.y = 0; broadcaster.width = videowidth; broadcaster.height = videoheight; config.mixusers = new arraylist<>(); config.mixusers.add(broadcaster); // 设置混流后各个小画面的位置 int index = 0; for (string userid : mroommembers) { trtcclouddef.trtcmixuser audience = new trtcclouddef.trtcmixuser(); audience.userid = userid; audience.zorder = 1 + index; if (index < 3) { // 前三个小画面靠右从下往上铺 audience.x = videowidth - offsetx - subwidth; audience.y = videoheight - offsety - index * subheight - subheight; audience.width = subwidth; audience.height = subheight; } else if (index < 6) { // 后三个小画面靠左从下往上铺 audience.x = offsetx; audience.y = videoheight - offsety - (index - 3) * subheight - subheight; audience.width = subwidth; audience.height = subheight; } else { // 最多只叠加六个小画面 } config.mixusers.add(audience); ++index; } trtccloud.setmixtranscodingconfig(config); } protected string stringtomd5(string string) { if (textutils.isempty(string)) { return ""; } messagedigest md5 = null; try { md5 = messagedigest.getinstance("md5"); byte[] bytes = md5.digest(string.getbytes()); string result = ""; for (byte b : bytes) { string temp = integer.tohexstring(b & 0xff); if (temp.length() == 1) { temp = "0" + temp; } result += temp; } return result; } catch (nosuchalgorithmexception e) { e.printstacktrace(); } return ""; } private void startlocalvideo(boolean enable) { txcloudvideoview localvideoview = mvideoviewlayout.getcloudvideoviewbyuseid(trtcparams.userid); if (localvideoview == null) { localvideoview = mvideoviewlayout.getfreecloudvideoview(); } localvideoview.setuserid(trtcparams.userid); localvideoview.setvisibility(view.visible); if (enable) { // 设置 trtc sdk 的状态 trtccloud.enablecustomvideocapture(false); //启动sdk摄像头采集和渲染 trtccloud.startlocalpreview(mcamerafront, localvideoview); } else { trtccloud.stoplocalpreview(); } } }
有评论区小伙伴要求晒出constents.java,这里我也把这个类分享出来,constents类主要是定义一些全局变量
constents完整源码如下:
public class constents { /** * 1对1语音通话 */ public final static string one_to_one_audio_call = "1"; /** * 1对多语音通话 */ public final static string one_to_multipe_audio_call = "2"; /** * 1对1视频通话 */ public final static string one_to_one_video_call = "3"; /** * 1对多视频通话 */ public final static string one_to_multipe_video_call = "4"; /** * 实时语音通话消息描述内容 */ public final static string audio_call_message_desc = "audio_call_message_desc"; /** * 实时视频通话消息描述内容 */ public final static string video_call_message_desc = "video_call_message_desc"; /** * 实时语音通话消息拒接 */ public final static string audio_call_message_decline_desc = "audio_call_message_decline_desc"; /** * 实时视频通话消息拒接 */ public final static string video_call_message_decline_desc = "video_call_message_decline_desc"; /** * 悬浮窗与trtcvideoactivity共享的视频view */ public static trtcvideoviewlayout mvideoviewlayout; /** * 悬浮窗是否开启 */ public static boolean isshowfloatwindow = false; /** * 语音通话开始计时时间(悬浮窗要显示时间在这里记录开始值) */ public static long audiocallstarttime; }
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。