この記事では主に Android WeChat ショートビデオ録画機能の実装に関する関連情報を紹介します。具体的な実装アイデアとコードはここで提供されます。
Android WeChat ショートビデオ録画機能
。
ここ数日、私はビデオ関連のコントロールにさらされてきたため、前回の WeChat の揺れに続いて、WeChat の短いビデオ録画機能を実装することを考えました。これには多くの機能があり、毎回実行するのに時間がかかります。正直に言うと、いくつかの点が非常に難しいので、皆さんに注意して読んでいただきたいと思います。私が間違っている場合は、コメントで修正してください。
ケース分析
WeChatで短いビデオを開くことができます簡単な分析のために その機能は何ですか?レイアウトの構築
レイアウトインターフェイスの実装は問題ありませんが、それほど難しくありません
<?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>
ビデオ プレビューの実装
ステップ 1: SurfaceView コントロールを取得し、基本プロパティと対応する監視を設定します (このコントロールの作成は非同期であり、本当に「準備ができた」場合にのみ呼び出すことができます)
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(); } } }
カスタマイズされた双方向縮小プログレスバー
をサポートしていない場合があります。私のような初心者もいます。学者は、特定のビューのカスタマイズを見ると、それが素晴らしいと考えるでしょう。実際、Google はすでに多くのことを書いています。そして、プログレスバーは何もなく、ただの線です。今日はそれについて話しましょうステップ 1: ビューを継承して初期化を完了しますprivate 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)); } }
最初の 3 つのイベントについては、それらを onTouch() コールバック メソッドに配置しました
4 つ目については、後ほど説明します
まず 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; // ... }
長押しして記録します
長押しして記録するには、ACTION_DOWN イベントを監視し、進行状況バーを更新するためにハンドラーの送信を遅らせるためにスレッドを使用する必要がありますswitch (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; } } }
リフトアップして保存します
必要なことは同じですここで ACTION_UP イベントを聞きますが、ユーザーがリフトアップするのが早すぎる場合 (記録時間が短すぎる場合)、保存する必要はないと考えてください。また、このイベントにはキャンセル状態のリフトも含まれることを意味します。ユーザーがキャンセルするために上にスライドすると、録音が開始された瞬間に録音をキャンセルします。コードを見てくださいcase 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;
連動前の部分では、キャンセルイベントを発生させてキャンセルまでのスライドアップを実現しました
case 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: 这个停止不保存是我自己的一种想法, 如果大家有更好的想法, 欢迎大家到评论中指出, 不胜感激
以上がAndroid を使用して WeChat ショートビデオ録画機能を実装する方法の詳細な紹介の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。