当前位置:   article > 正文

Android基于腾讯云实时音视频实现类似微信视频通话最小化悬浮_融云和trtc

融云和trtc

最近项目中有需要语音、视频通话需求,看到这个像环信、融云等SDK都有具体Demo实现,但咋的领导对腾讯情有独钟啊,IM要用腾讯云IM,不妙的是腾讯云IM并不包含有音视频通话都要自己实现,没办法深入了解腾讯云产品后,决定自己基于腾讯云实时音视频做去语音、视频通话功能。在这里把实现过程记录下为以后用到便于查阅,另一方面也给有需要的人提供一个思路,让大家少走弯路,有可能我的实现的方法不是最好,但是这或许是一个可行的方案,大家不喜勿喷。基于腾讯云实时音视频SDK 6.5.7272版本,腾讯DEMO下载地址

一、实现效果

     

二、实现思路

我把实现思路拆分为了两步:1、视频通话Activity的最小化。 2、视频通话悬浮框的开启

具体思路是这样的:当用户点击左上角最小化按钮的时候,最小化视频通话Activity(这时Activity处于后台状态),于此同时开启悬浮框,新建一个新的ViewGroup将全局Constents.mVideoViewLayout中用户选中的最大View动态添加到悬浮框里面去,监听悬浮框的触摸事件,让悬浮框可以拖拽移动;自定义点击事件,如果用户点击了悬浮框,则移除悬浮框然后重新调起我们在后台的视频通话Activity。

1.Activity是如何实现最小化的?

Activity本身自带了一个moveTaskToBack(boolean nonRoot),我们要实现最小化只需要调用moveTaskToBack(true)传入一个true值就可以了,但是这里有一个前提,就是需要设置Activity的启动模式为singleInstance模式,两步搞定。(注:activity最小化后重新从后台回到前台会回调onRestart()方法)

  1. @Override
  2. public boolean moveTaskToBack(boolean nonRoot) {
  3. return super.moveTaskToBack(nonRoot);
  4. }

2.悬浮框是如何开启的?

悬浮框的实现方法最好写在Service里面,将悬浮框的开启关闭与服务Service的绑定解绑所关联起来,开启服务即相当于开启我们的悬浮框,解绑服务则相当于关闭关闭的悬浮框,以此来达到更好的控制效果。

a. 首先我们声明一个服务类,取名为FloatVideoWindowService:

  1. public class FloatVideoWindowService extends Service {
  2. @Nullable
  3. @Override
  4. public IBinder onBind(Intent intent) {
  5. return new MyBinder();
  6. }
  7. public class MyBinder extends Binder {
  8. public FloatVideoWindowService getService() {
  9. return FloatVideoWindowService.this;
  10. }
  11. }
  12. @Override
  13. public void onCreate() {
  14. super.onCreate();
  15. }
  16. @Override
  17. public int onStartCommand(Intent intent, int flags, int startId) {
  18. return super.onStartCommand(intent, flags, startId);
  19. }
  20. @Override
  21. public void onDestroy() {
  22. super.onDestroy();
  23. }
  24. }

b. 为悬浮框建立一个布局文件float_video_window_layout,悬浮框大小我这里固定为长80dp,高120dp,id为small_size_preview的RelativeLayout主要是一个容器,可以动态的添加view到里面去

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3. android:id="@+id/small_size_frame_layout"
  4. android:layout_width="wrap_content"
  5. android:layout_height="wrap_content"
  6. android:background="@color/colorComBg"
  7. android:orientation="vertical">
  8. <com.tencent.rtmp.ui.TXCloudVideoView
  9. android:id="@+id/float_videoview"
  10. android:layout_width="80dp"
  11. android:layout_height="120dp"
  12. android:descendantFocusability="blocksDescendants"
  13. android:orientation="vertical" />
  14. </LinearLayout>

c. 布局定义好后,接下来就要对悬浮框做一些初始化操作了,初始化操作这里我们放在服务的onCreate()生命周期里面执行,因为只需要执行一次就行了。这里的初始化主要包括对:悬浮框的基本参数(位置,宽高等),悬浮框的点击事件以及悬浮框的触摸事件(即可拖动范围)等的设置,在onBind()中从Intent中取出了Activity中用户选中最大View的id,以便在后面从 Constents.mVideoViewLayout中取出对应View,然后加入悬浮窗布局中

  1. /**
  2. * 视频悬浮窗服务
  3. */
  4. public class FloatVideoWindowService extends Service {
  5. private WindowManager mWindowManager;
  6. private WindowManager.LayoutParams wmParams;
  7. private LayoutInflater inflater;
  8. private String currentBigUserId;
  9. //浮动布局view
  10. private View mFloatingLayout;
  11. //容器父布局
  12. private RelativeLayout smallSizePreviewLayout;
  13. private TXCloudVideoView mLocalVideoView;
  14. @Override
  15. public void onCreate() {
  16. super.onCreate();
  17. initWindow();//设置悬浮窗基本参数(位置、宽高等)
  18. }
  19. @Nullable
  20. @Override
  21. public IBinder onBind(Intent intent) {
  22. currentBigUserId = intent.getStringExtra("userId");
  23. initFloating();//悬浮框点击事件的处理
  24. return new MyBinder();
  25. }
  26. public class MyBinder extends Binder {
  27. public FloatVideoWindowService getService() {
  28. return FloatVideoWindowService.this;
  29. }
  30. }
  31. @Override
  32. public int onStartCommand(Intent intent, int flags, int startId) {
  33. return super.onStartCommand(intent, flags, startId);
  34. }
  35. /**
  36. * 设置悬浮框基本参数(位置、宽高等)
  37. */
  38. private void initWindow() {
  39. mWindowManager = (WindowManager) getApplicationContext().getSystemService(Context.WINDOW_SERVICE);
  40. //设置好悬浮窗的参数
  41. wmParams = getParams();
  42. // 悬浮窗默认显示以左上角为起始坐标
  43. wmParams.gravity = Gravity.LEFT | Gravity.TOP;
  44. //悬浮窗的开始位置,因为设置的是从左上角开始,所以屏幕左上角是x=0;y=0
  45. wmParams.x = 70;
  46. wmParams.y = 210;
  47. //得到容器,通过这个inflater来获得悬浮窗控件
  48. inflater = LayoutInflater.from(getApplicationContext());
  49. // 获取浮动窗口视图所在布局
  50. mFloatingLayout = inflater.inflate(R.layout.alert_float_video_layout, null);
  51. // 添加悬浮窗的视图
  52. mWindowManager.addView(mFloatingLayout, wmParams);
  53. }
  54. private WindowManager.LayoutParams getParams() {
  55. wmParams = new WindowManager.LayoutParams();
  56. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
  57. wmParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
  58. } else {
  59. wmParams.type = WindowManager.LayoutParams.TYPE_PHONE;
  60. }
  61. //设置可以显示在状态栏上
  62. wmParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL |
  63. WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR |
  64. WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
  65. //设置悬浮窗口长宽数据
  66. wmParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
  67. wmParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
  68. return wmParams;
  69. }
  70. private void initFloating() {
  71. }
  72. }

d. 在悬浮框成功被初始化以及相关参数被设置后,接下来就需要将Activity中用户选中最大的View添加到悬浮框里面去了,这样我们才能看到视频画面嘛,同样我们是在Service的onCreate这个生命周期中initFloating()完成这个操作的,代码如下所示:

  1. TRTCVideoViewLayout mTRTCVideoViewLayout = Constents.mVideoViewLayout;
  2. TXCloudVideoView mLocalVideoView = mTRTCVideoViewLayout.getCloudVideoViewByUseId(currentBigUserId);
  3. if (mLocalVideoView == null) {
  4. mLocalVideoView = mTRTCVideoViewLayout.getCloudVideoViewByIndex(0);
  5. }
  6. if (ConstData.userid.equals(currentBigUserId)) {
  7. TXCGLSurfaceView mTXCGLSurfaceView = mLocalVideoView.getGLSurfaceView();
  8. if (mTXCGLSurfaceView != null && mTXCGLSurfaceView.getParent() != null) {
  9. ((ViewGroup) mTXCGLSurfaceView.getParent()).removeView(mTXCGLSurfaceView);
  10. mTXCloudVideoView.addVideoView(mTXCGLSurfaceView);
  11. }
  12. } else {
  13. TextureView mTextureView = mLocalVideoView.getVideoView();
  14. if (mTextureView != null && mTextureView.getParent() != null) {
  15. ((ViewGroup) mTextureView.getParent()).removeView(mTextureView);
  16. mTXCloudVideoView.addVideoView(mTextureView);
  17. }
  18. }

e. 我们上面说到要将服务Service的绑定与解绑与悬浮框的开启和关闭相结合,所以既然我们在服务的onCreate()方法中开启了悬浮框,那么就应该在其onDestroy()方法中对悬浮框进行关闭,关闭悬浮框的本质是将相关View给移除掉,在服务的onDestroy()方法中执行如下代码:

  1. @Override
  2. public void onDestroy() {
  3. super.onDestroy();
  4. if (mFloatingLayout != null) {
  5. // 移除悬浮窗口
  6. mWindowManager.removeView(mFloatingLayout);
  7. mFloatingLayout = null;
  8. Constents.isShowFloatWindow = false;
  9. }
  10. }

f. 服务的绑定方式有bindService和startService两种,使用不同的绑定方式其生命周期也会不一样,已知我们需要让悬浮框在视频通话activity finish掉的时候也顺便关掉,那么理所当然我们就应该采用bind方式来启动服务,让他的生命周期跟随他的开启者,也即是跟随开启它的activity生命周期。

  1. intent = new Intent(this, FloatVideoWindowService.class);//开启服务显示悬浮框
  2. bindService(intent, mVideoServiceConnection, Context.BIND_AUTO_CREATE);
  3. ServiceConnection mVideoServiceConnection = new ServiceConnection() {
  4. @Override
  5. public void onServiceConnected(ComponentName name, IBinder service) {
  6. // 获取服务的操作对象
  7. FloatVideoWindowService.MyBinder binder = (FloatVideoWindowService.MyBinder) service;
  8. binder.getService();
  9. }
  10. @Override
  11. public void onServiceDisconnected(ComponentName name) {
  12. }
  13. };

Service完整代码如下:

  1. /**
  2. * 视频悬浮窗服务
  3. */
  4. public class FloatVideoWindowService extends Service {
  5. private WindowManager mWindowManager;
  6. private WindowManager.LayoutParams wmParams;
  7. private LayoutInflater inflater;
  8. private String currentBigUserId;
  9. //浮动布局view
  10. private View mFloatingLayout;
  11. //容器父布局
  12. private TXCloudVideoView mTXCloudVideoView;
  13. @Override
  14. public void onCreate() {
  15. super.onCreate();
  16. initWindow();//设置悬浮窗基本参数(位置、宽高等)
  17. }
  18. @Nullable
  19. @Override
  20. public IBinder onBind(Intent intent) {
  21. currentBigUserId = intent.getStringExtra("userId");
  22. initFloating();//悬浮框点击事件的处理
  23. return new MyBinder();
  24. }
  25. public class MyBinder extends Binder {
  26. public FloatVideoWindowService getService() {
  27. return FloatVideoWindowService.this;
  28. }
  29. }
  30. @Override
  31. public int onStartCommand(Intent intent, int flags, int startId) {
  32. return super.onStartCommand(intent, flags, startId);
  33. }
  34. @Override
  35. public void onDestroy() {
  36. super.onDestroy();
  37. if (mFloatingLayout != null) {
  38. // 移除悬浮窗口
  39. mWindowManager.removeView(mFloatingLayout);
  40. mFloatingLayout = null;
  41. Constents.isShowFloatWindow = false;
  42. }
  43. }
  44. /**
  45. * 设置悬浮框基本参数(位置、宽高等)
  46. */
  47. private void initWindow() {
  48. mWindowManager = (WindowManager) getApplicationContext().getSystemService(Context.WINDOW_SERVICE);
  49. //设置好悬浮窗的参数
  50. wmParams = getParams();
  51. // 悬浮窗默认显示以左上角为起始坐标
  52. wmParams.gravity = Gravity.LEFT | Gravity.TOP;
  53. //悬浮窗的开始位置,因为设置的是从左上角开始,所以屏幕左上角是x=0;y=0
  54. wmParams.x = 70;
  55. wmParams.y = 210;
  56. //得到容器,通过这个inflater来获得悬浮窗控件
  57. inflater = LayoutInflater.from(getApplicationContext());
  58. // 获取浮动窗口视图所在布局
  59. mFloatingLayout = inflater.inflate(R.layout.alert_float_video_layout, null);
  60. // 添加悬浮窗的视图
  61. mWindowManager.addView(mFloatingLayout, wmParams);
  62. }
  63. private WindowManager.LayoutParams getParams() {
  64. wmParams = new WindowManager.LayoutParams();
  65. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
  66. wmParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
  67. } else {
  68. wmParams.type = WindowManager.LayoutParams.TYPE_PHONE;
  69. }
  70. //设置可以显示在状态栏上
  71. wmParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL |
  72. WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR |
  73. WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
  74. //设置悬浮窗口长宽数据
  75. wmParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
  76. wmParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
  77. return wmParams;
  78. }
  79. private void initFloating() {
  80. mTXCloudVideoView = mFloatingLayout.findViewById(R.id.float_videoview);
  81. TRTCVideoViewLayout mTRTCVideoViewLayout = Constents.mVideoViewLayout;
  82. TXCloudVideoView mLocalVideoView = mTRTCVideoViewLayout.getCloudVideoViewByUseId(currentBigUserId);
  83. if (mLocalVideoView == null) {
  84. mLocalVideoView = mTRTCVideoViewLayout.getCloudVideoViewByIndex(0);
  85. }
  86. if (ConstData.userid.equals(currentBigUserId)
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Monodyee/article/detail/440611?site
推荐阅读
相关标签
  

闽ICP备14008679号