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;

}

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • Android实现微信朋友圈发本地视频功能

    一.前言 前一篇文章已经详细介绍了如何使用Xposed框架编写第一个微信插件:摇骰子和猜拳作弊器 本文继续来介绍如何使用Xposed框架编写第二个微信插件,可以将本地小视频发布到朋友圈的功能.在这之前我们还是要有老套路,准备工作要做好,这里还是使用微信6.3.9版本进行操作,准备工作: 1.使用apktool工具进行反编译,微信没有做加固防护,所以这个版本的微信包反编译是没有任何问题的. 2.借助于可视化反编译工具Jadx打开微信包,后续几乎重要分析都是借助这个工具来操作的. 二.猜想与假设 做

  • Android仿微信多人音视频通话界面

    工作中需要实现一个类似微信多人视频通话功能的界面,分别使用自定义viewgroup和自定义layoutManager的方式进行了实现.最终工作中采用了layoutManager,因为可以使用payload更新单个布局控件,效率更好.下面放出两种具体的实现效果代码. 1.使用自定义ViewGroup方式实现 下面是三个人通话时候的效果,其他的可以参考微信多人音视频通话界面. package com.dnaer.android.telephone.widgets; import android.co

  • Android仿微信录制小视频

    本文实例为大家分享了Android仿微信录制小视频的具体代码,供大家参考,具体内容如下 先上张图片看看效果 简单叙述下 首先通过Camera类调用系统相机 通过surfaceview绘制出来 通过MediaRecorder来录制视频 闪光灯 和 切换摄像头 需要重新配置Camera的参数 Camera预览界面画面拉升的原因是因为Surfaceview的大小与设定的比例不一致的 **本次版本更新了 切换前置摄像头录制视频问题 Android部分手机录制视频适配IOS手机问题 (OPPO手机部分不适

  • Android 微信小视频录制功能实现详细介绍

    Android 微信小视频录制功能 开发之前 这几天接触了一下和视频相关的控件, 所以, 继之前的微信摇一摇, 我想到了来实现一下微信小视频录制的功能, 它的功能点比较多, 我每天都抽出点时间来写写, 说实话, 有些东西还是比较费劲, 希望大家认真看看, 说得不对的地方还请大家在评论中指正. 废话不多说, 进入正题. 开发环境 最近刚更新的, 没更新的小伙伴们抓紧了 Android Studio 2.2.2 JDK1.7 API 24 Gradle 2.2.2 相关知识点 视频录制界面 Surf

  • 微信小程序实现点击拍照长按录像功能的实现代码

    代码里面注释写的都很详细,直接上代码.官方的组件属性中有触摸开始和触摸结束属性.本功能依靠这些属性实现. .wxml代码: <!-- 相机 pages/camera/camera.wxml--> <!-- 相机 --> <camera wx:if="{{!videoSrc}}" device-position="back" flash="off" binderror="error" style=&

  • Android仿微信拍摄短视频

    近期做项目需要添加上传短视频功能,功能设置为类似于微信,点击开始拍摄,设置最长拍摄时间,经过研究最终实现了这个功能,下面就和大家分享一下,希望对你有帮助. 1.视频录制自定义控件: /** * 视频播放控件 */ public class MovieRecorderView extends LinearLayout implements OnErrorListener { private SurfaceView mSurfaceView; private SurfaceHolder mSurfa

  • Android采用消息推送实现类似微信视频接听

    本文实例为大家分享了Android实现类似微信视频接听的具体代码,供大家参考,具体内容如下 1.背景需求:业务需要接入视频审核功能,在PC 端发起视频通话,移动端显示通话界面点击接听后进行1对1视频通话. 2.解决方案:因为项目没有IM功能.只集成了极光消息推送(极光消息推送接入参考官方文档,经过跟需求沟通,采用消息推送调起通话接听界面.再集成腾讯实时音视频SDK(具体集成方式参考官方文档).最终实现类似微信1对1通话功能. 3.技术实现: A:编写一个广播接收器,并且在 AndroidMani

  • Android基于腾讯云实时音视频仿微信视频通话最小化悬浮

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

  • Android仿微信视屏悬浮窗效果

    在项目中需要对接入的腾讯云音视频,可以悬浮窗显示,悬浮窗可拖拽,并且在悬浮窗不影响其他的activity的焦点. 这个大神的文章Android基于腾讯云实时音视频仿微信视频通话最小化悬浮,他讲的是视频通话时,将远端视频以悬浮窗形式展示,根据他的代码我进行了部分简化 1.悬浮窗效果:点击缩小按钮,将当前远端视屏加载进悬浮窗,且悬浮窗可拖拽,不影响其他界面焦点:点击悬浮窗可返回原来的Activity 2.实现悬浮窗需要: 在androidManifest中申请悬浮窗权限<uses-permissio

  • Android自定义View实现通讯录字母索引(仿微信通讯录)

    一.效果:我们看到很多软件的通讯录在右侧都有一个字母索引功能,像微信,小米通讯录,QQ,还有美团选择地区等等.这里我截了一张美团选择城市的图片来看看: 我们今天就来实现图片中右侧模块的索引功能,包括触摸显示以选中的索引字母.这里我的UI界面主要是参照微信的界面来实现,所以各位也可以对照微信来看看效果,什么都不说了,只有效果图最具有说服力! 二.分析: 我们看到这样的效果我们心理都回去琢磨,他是如何实现的: 首先,它肯定是通过自定义 View 来实现的,因为 Android 没有提供类似这样的控件

  • Android音视频开发之MediaCodec的使用教程

    目录 前言 MediaCodec 编解码流程 生命周期 接口简介 前言 获取到音视频轨道(编解码格式),知道设备支持哪些编解码器,下一步就是创建编解码器去实现数据流的编解码过程了.在Android开发中提供了实现音视频编解码工具MediaCodec,针对对应音视频解码类型通过该类创建对应解码器就能实现对数据进行解码操作. MediaCodec MediaCodec所支持的数据类型:压缩的音视频数据.原始音频数据和原始视频数据. 首先show代码,紧接着之前MediaExtactor提取资源,Me

  • 基于Android引入IjkPlayer无法播放mkv格式视频的解决方法

    写在前面 项目中直接引用或者直接编译源码得到的ijkplayer在播放mkv文件时出现(-10000)的错误,去项目github查看了才知道,默认是不支持mkv和rmvb格式视频的播放的. 用了一天时间解决(为什么用了一天,因为我蠢啊),这里记录一下解决的方法(官方上面其实已经有了详细的教程,无奈我当时没有很认真看.)这里为我自己这个新手做个记录: 仍然是采用编译源码的方式引入,只是需要按照官方的方法更改一下脚本文件 ijkplayer官方地址:https://github.com/Bilibi

  • android采用FFmpeg实现音视频合成与分离

    上一篇文章谈到音频剪切.混音.拼接与转码,也详细介绍cMake配置与涉及FFmpeg文件的导入: android端采用FFmpeg进行音频混合与拼接剪切.现在接着探讨音视频的合成与分离. 1.音频提取 从多媒体文件中提取音频,关键命令为"-acodec copy -vn",其中"-acodec copy"是采用音频编码器拷贝音频流,"-vn"是去掉video视频流: /** * 使用ffmpeg命令行进行抽取音频 * @param srcFile

  • python基于moviepy实现音视频剪辑

    目录 实践步骤 完整代码 参考 实践步骤 1.寻找合适的Python库(安装是否麻烦.使用是否简便.执行会不会太久) moviepy 音视频库.分析需要用的API:代码示例 2.定义输入输出 输入:一个音视频文件的地址,需要剪出来的时间段 输出:剪辑片段的文件 3.设计执行流程并一步步实现(定义函数,与使用具体API相关) 读入并创建clip对象. 剪辑subclip,输入时间参数可以是时间格式的字符串. 导出write_videofile. 4.结论:时间太久,片段多长就花了多久的时间:CPU

  • Android音视频开发之VideoView使用指南

    目录 VideoView介绍 MediaController 使用 源码分析 进度显示 播放尺寸适配 VideoView介绍 之前介绍过使用MediaPlayer+SurfaceView实现播放视频功能.无意间发现官方封装了VideoView组件来实现简单视频播放功能,内部同样是使用MediaPlayer+SurfaceView的形式控制MediaPlayer对视频文件进行播放.使用场景比较简单,适用于只是播放视频的场景,其提供能力有限不太适合使用在调节视频亮度等其他功能. MediaContr

  • Android音视频开发之MediaExtactor使用教程

    目录 前言 MediaExtactor 使用MediaExtactor 加载音视频文件代码 获取轨道代码 提取轨道数据信息 一些源码细节分析 前言 在之前学习如何使用MediaPlayer后,了解到Android系统提供开发者播放多媒体全家桶能力,但对于开发者希望DIY自由度更高的播放器能力也是可以利用Android内部提供组件包自行实现一个播放器的.举例实现一个视频播放这个流程,它大致流程是[多媒体文件解析提取视频文件]-> [视频流解码]-> [解码数据播放渲染到Render].首要需要实

  • Android开发之音视频协议介绍

    目录 什么是视频文件 什么是264 了解音视频协议有啥用? 两大电信联盟 ITU-T ISO ITU-T 视频编码发展历程 H.26X系列(由ITU[国际电传视讯联盟]主导) 其他音视频协议 Google(VP8/VP9) Microsoft (VC-1) 国产自主标准: AVS/AVS+/AVS2 什么是视频文件 一般是指以某种格式封装了音视频数据的文件 常见的音频格式:mp3.wma.avi.rm.rmvb.flv.mpg.mov.mkv等. 常见的视频格式:rmvb.rm.wmv.avi.

随机推荐