This article mainly introduces the relevant information on the implementation of the Android WeChat short video recording function. Specific implementation ideas and codes are provided here. Friends in need can refer to
Android WeChat short video Recording function
Before development
I have been exposed to video-related controls in the past few days, so after the previous WeChat shake, I thought of implementing a WeChat short video The recording function has many functions. I take some time to write about it every day. To be honest, some things are quite difficult. I hope everyone will take a look at it carefully. Please correct me in the comments if I am wrong. Nonsense. Without further ado, let’s get to the point.
Development environment
It was just updated recently, friends who haven’t updated please hurry up
Android Studio 2.2.2
Case analysis
You can open the short video in your WeChat and briefly analyze its functions: Which?Build the layout
The implementation of the layout interface is okay and not difficult<?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:id="@+id/main_tv_tip" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="bottom|center_horizontal" android:layout_marginBottom="150dp" android:elevation="1dp" android:text="双击放大" android:textColor="#FFFFFF"/> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <SurfaceView android:id="@+id/main_surface_view" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="3"/> <LinearLayout android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" android:background="@color/colorApp" android:orientation="vertical"> <RelativeLayout android:id="@+id/main_press_control" android:layout_width="match_parent" android:layout_height="match_parent"> <com.lulu.weichatsamplevideo.BothWayProgressBar android:id="@+id/main_progress_bar" android:layout_width="match_parent" android:layout_height="2dp" android:background="#000"/> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:text="按住拍" android:textAppearance="@style/TextAppearance.AppCompat.Large" android:textColor="#00ff00"/> </RelativeLayout> </LinearLayout> </LinearLayout> </FrameLayout>
Video Preview Implementation
#step1: Get the SurfaceView control, set the basic properties and corresponding monitoring (the creation of the control is asynchronous, and it is only when it is truly "ready" It can only be called later)mSurfaceView = (SurfaceView) findViewById(R.id.main_surface_view); //设置屏幕分辨率 mSurfaceHolder.setFixedSize(videoWidth, videoHeight); mSurfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); mSurfaceHolder.addCallback(this);
////////////////////////////////////////////// // SurfaceView回调 ///////////////////////////////////////////// @Override public void surfaceCreated(SurfaceHolder holder) { mSurfaceHolder = holder; startPreView(holder); } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { } @Override public void surfaceDestroyed(SurfaceHolder holder) { if (mCamera != null) { Log.d(TAG, "surfaceDestroyed: "); //停止预览并释放摄像头资源 mCamera.stopPreview(); mCamera.release(); mCamera = null; } if (mMediaRecorder != null) { mMediaRecorder.release(); mMediaRecorder = null; } }
/** * 开启预览 * * @param holder */ private void startPreView(SurfaceHolder holder) { Log.d(TAG, "startPreView: "); if (mCamera == null) { mCamera = Camera.open(Camera.CameraInfo.CAMERA_FACING_BACK); } if (mMediaRecorder == null) { mMediaRecorder = new MediaRecorder(); } if (mCamera != null) { mCamera.setDisplayOrientation(90); try { mCamera.setPreviewDisplay(holder); Camera.Parameters parameters = mCamera.getParameters(); //实现Camera自动对焦 List<String> focusModes = parameters.getSupportedFocusModes(); if (focusModes != null) { for (String mode : focusModes) { mode.contains("continuous-video"); parameters.setFocusMode("continuous-video"); } } mCamera.setParameters(parameters); mCamera.startPreview(); } catch (IOException e) { e.printStackTrace(); } } }
Customized two-way reduction progress bar
Some beginners like me feel that it is awesome when they see a customized View. In fact, Google has already replaced it We have written a lot of code, so we can just use it. And our progress bar is nothing, just a line, let’s talk about it today. step1: Inherit View and complete initializationprivate static final String TAG = "BothWayProgressBar"; //取消状态为红色bar, 反之为绿色bar private boolean isCancel = false; private Context mContext; //正在录制的画笔 private Paint mRecordPaint; //上滑取消时的画笔 private Paint mCancelPaint; //是否显示 private int mVisibility; // 当前进度 private int progress; //进度条结束的监听 private OnProgressEndListener mOnProgressEndListener; public BothWayProgressBar(Context context) { super(context, null); } public BothWayProgressBar(Context context, AttributeSet attrs) { super(context, attrs); mContext = context; init(); } private void init() { mVisibility = INVISIBLE; mRecordPaint = new Paint(); mRecordPaint.setColor(Color.GREEN); mCancelPaint = new Paint(); mCancelPaint.setColor(Color.RED); }
public interface OnProgressEndListener{ void onProgressEndListener(); } /** * 当进度条结束后的 监听 * @param onProgressEndListener */ public void setOnProgressEndListener(OnProgressEndListener onProgressEndListener) { mOnProgressEndListener = onProgressEndListener; }
/** * 设置进度 * @param progress */ public void setProgress(int progress) { this.progress = progress; invalidate(); } /** * 设置录制状态 是否为取消状态 * @param isCancel */ public void setCancel(boolean isCancel) { this.isCancel = isCancel; invalidate(); } /** * 重写是否可见方法 * @param visibility */ @Override public void setVisibility(int visibility) { mVisibility = visibility; //重新绘制 invalidate(); }
@Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); if (mVisibility == View.VISIBLE) { int height = getHeight(); int width = getWidth(); int mid = width / 2; //画出进度条 if (progress < mid){ canvas.drawRect(progress, 0, width-progress, height, isCancel ? mCancelPaint : mRecordPaint); } else { if (mOnProgressEndListener != null) { mOnProgressEndListener.onProgressEndListener(); } } } else { canvas.drawColor(Color.argb(0, 0, 0, 0)); } }
For the first three events, I have placed an onTouch() callback method Winning
For the fourth one, we will talk about it later
Let’s first list the local variables in onTouch():
@Override public boolean onTouch(View v, MotionEvent event) { boolean ret = false; int action = event.getAction(); float ey = event.getY(); float ex = event.getX(); //只监听中间的按钮处 int vW = v.getWidth(); int left = LISTENER_START; int right = vW - LISTENER_START; float downY = 0; // ... }
Long press to record
We need to listen to the ACTION_DOWN event for long press recording, and use the thread to delay sending the Handler to update the progress barswitch (action) { case MotionEvent.ACTION_DOWN: if (ex > left && ex < right) { mProgressBar.setCancel(false); //显示上滑取消 mTvTip.setVisibility(View.VISIBLE); mTvTip.setText("↑ 上滑取消"); //记录按下的Y坐标 downY = ey; // TODO: 2016/10/20 开始录制视频, 进度条开始走 mProgressBar.setVisibility(View.VISIBLE); //开始录制 Toast.makeText(this, "开始录制", Toast.LENGTH_SHORT).show(); startRecord(); mProgressThread = new Thread() { @Override public void run() { super.run(); try { mProgress = 0; isRunning = true; while (isRunning) { mProgress++; mHandler.obtainMessage(0).sendToTarget(); Thread.sleep(20); } } catch (InterruptedException e) { e.printStackTrace(); } } }; mProgressThread.start(); ret = true; } break; // ... return true; }
//////////////////////////////////////////////////// // Handler处理 ///////////////////////////////////////////////////// private static class MyHandler extends Handler { private WeakReference<MainActivity> mReference; private MainActivity mActivity; public MyHandler(MainActivity activity) { mReference = new WeakReference<MainActivity>(activity); mActivity = mReference.get(); } @Override public void handleMessage(Message msg) { switch (msg.what) { case 0: mActivity.mProgressBar.setProgress(mActivity.mProgress); break; } } }
Lift up and save
We also need to listen to the ACTION_UP event here, but we must consider that when the user lifts up too quickly (the recording time is too short), there is no need to save. Moreover, This event includes the lifting of the cancellation state. To explain: it cancels the recording the moment it is lifted when swiping up to cancel. Please look at the codecase MotionEvent.ACTION_UP: if (ex > left && ex < right) { mTvTip.setVisibility(View.INVISIBLE); mProgressBar.setVisibility(View.INVISIBLE); //判断是否为录制结束, 或者为成功录制(时间过短) if (!isCancel) { if (mProgress < 50) { //时间太短不保存 stopRecordUnSave(); Toast.makeText(this, "时间太短", Toast.LENGTH_SHORT).show(); break; } //停止录制 stopRecordSave(); } else { //现在是取消状态,不保存 stopRecordUnSave(); isCancel = false; Toast.makeText(this, "取消录制", Toast.LENGTH_SHORT).show(); mProgressBar.setCancel(false); } ret = false; } break;
Swipe up to cancel
In conjunction with the previous part, raise the cancellation event to realize slide up to cancelcase MotionEvent.ACTION_MOVE: if (ex > left && ex < right) { float currentY = event.getY(); if (downY - currentY > 10) { isCancel = true; mProgressBar.setCancel(true); } } break;
Note: 主要原理不难, 只要按下并且向上移动一定距离 就会触发,当手抬起时视频录制取消
双击放大(变焦)
这个事件比较特殊, 使用了Google提供的GestureDetector手势检测 来判断双击事件
step1: 对SurfaceView进行单独的Touch事件监听, why? 因为GestureDetector需要Touch事件的完全托管, 如果只给它传部分事件会造成某些事件失效
mDetector = new GestureDetector(this, new ZoomGestureListener()); /** * 单独处理mSurfaceView的双击事件 */ mSurfaceView.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { mDetector.onTouchEvent(event); return true; } });
step2: 重写GestureDetector.SimpleOnGestureListener, 实现双击事件
/////////////////////////////////////////////////////////////////////////// // 变焦手势处理类 /////////////////////////////////////////////////////////////////////////// class ZoomGestureListener extends GestureDetector.SimpleOnGestureListener { //双击手势事件 @Override public boolean onDoubleTap(MotionEvent e) { super.onDoubleTap(e); Log.d(TAG, "onDoubleTap: 双击事件"); if (mMediaRecorder != null) { if (!isZoomIn) { setZoom(20); isZoomIn = true; } else { setZoom(0); isZoomIn = false; } } return true; } }
step3: 实现相机的变焦的方法
/** * 相机变焦 * * @param zoomValue */ public void setZoom(int zoomValue) { if (mCamera != null) { Camera.Parameters parameters = mCamera.getParameters(); if (parameters.isZoomSupported()) {//判断是否支持 int maxZoom = parameters.getMaxZoom(); if (maxZoom == 0) { return; } if (zoomValue > maxZoom) { zoomValue = maxZoom; } parameters.setZoom(zoomValue); mCamera.setParameters(parameters); } } }
Note: 至此我们已经完成了对所有事件的监听, 看到这里大家也许有些疲惫了, 不过不要灰心, 现在完成我们的核心部分, 实现视频的录制
实现视频的录制
说是核心功能, 也只不过是我们不知道某些API方法罢了, 下面代码中我已经加了详细的注释, 部分不能理解的记住就好^v^
/** * 开始录制 */ private void startRecord() { if (mMediaRecorder != null) { //没有外置存储, 直接停止录制 if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { return; } try { //mMediaRecorder.reset(); mCamera.unlock(); mMediaRecorder.setCamera(mCamera); //从相机采集视频 mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA); // 从麦克采集音频信息 mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); // TODO: 2016/10/20 设置视频格式 mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4); mMediaRecorder.setVideoSize(videoWidth, videoHeight); //每秒的帧数 mMediaRecorder.setVideoFrameRate(24); //编码格式 mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.DEFAULT); mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB); // 设置帧频率,然后就清晰了 mMediaRecorder.setVideoEncodingBitRate(1 * 1024 * 1024 * 100); // TODO: 2016/10/20 临时写个文件地址, 稍候该!!! File targetDir = Environment. getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES); mTargetFile = new File(targetDir, SystemClock.currentThreadTimeMillis() + ".mp4"); mMediaRecorder.setOutputFile(mTargetFile.getAbsolutePath()); mMediaRecorder.setPreviewDisplay(mSurfaceHolder.getSurface()); mMediaRecorder.prepare(); //正式录制 mMediaRecorder.start(); isRecording = true; } catch (Exception e) { e.printStackTrace(); } } }
实现视频的停止
大家可能会问, 视频的停止为什么单独抽出来说呢? 仔细的同学看上面代码会看到这两个方法: stopRecordSave和stopRecordUnSave, 一个停止保存, 一个是停止不保存, 接下来我们就补上这个坑
停止并保存
private void stopRecordSave() { if (isRecording) { isRunning = false; mMediaRecorder.stop(); isRecording = false; Toast.makeText(this, "视频已经放至" + mTargetFile.getAbsolutePath(), Toast.LENGTH_SHORT).show(); } }
停止不保存
private void stopRecordUnSave() { if (isRecording) { isRunning = false; mMediaRecorder.stop(); isRecording = false; if (mTargetFile.exists()) { //不保存直接删掉 mTargetFile.delete(); } } }
Note: 这个停止不保存是我自己的一种想法, 如果大家有更好的想法, 欢迎大家到评论中指出, 不胜感激
The above is the detailed content of Detailed introduction to using Android to implement WeChat short video recording function. For more information, please follow other related articles on the PHP Chinese website!