Android 微信小視頻錄製功能實現詳細介紹_Android

來源:互聯網
上載者:User

Android 微信小視頻錄製功能

開發之前

這幾天接觸了一下和視頻相關的控制項, 所以, 繼之前的微信搖一搖, 我想到了來實現一下微信小視頻錄製的功能, 它的功能點比較多, 我每天都抽出點時間來寫寫, 說實話, 有些東西還是比較費勁, 希望大家認真看看, 說得不對的地方還請大家在評論中指正. 廢話不多說, 進入正題.

開發環境

最近剛更新的, 沒更新的小夥伴們抓緊了

  1. Android Studio 2.2.2
  2. JDK1.7
  3. API 24
  4. Gradle 2.2.2

相關知識點

  1. 視頻錄製介面 SurfaceView 的使用
  2. Camera的使用
  3. 相機的對焦, 變焦
  4. 視頻錄製控制項MediaRecorder的使用
  5. 簡單自訂View
  6. GestureDetector(手勢檢測)的使用

用到的東西真不少, 不過別著急, 咱們一個一個來.

開始開發

案例分析

大家可以開啟自己微信裡面的小視頻, 一塊簡單的分析一下它的功能點有哪些 ?

  1. 基本的視頻預覽功能
  2. 長按 “按住拍” 實現視頻的錄製
  3. 錄製過程中的進度條從兩側向中間變短
  4. 當鬆手或者進度條走到盡頭視頻停止錄製 並儲存
  5. 從 “按住拍” 上滑取消視頻的錄製
  6. 雙擊螢幕 變焦 放大

根據上述的分析, 我們一步一步的完成

搭建布局

布局介面的實現還可以, 難度不大

<?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>

視頻預覽的實現

step1: 得到SufaceView控制項, 設定基本屬性和相應監聽(該控制項的建立是非同步, 只有在真正”準備”好之後才能調用)

mSurfaceView = (SurfaceView) findViewById(R.id.main_surface_view); //設定螢幕解析度mSurfaceHolder.setFixedSize(videoWidth, videoHeight);mSurfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);mSurfaceHolder.addCallback(this);

step2: 實現介面的方法, surfaceCreated方法中開啟視頻的預覽, 在surfaceDestroyed中銷毀

//////////////////////////////////////////////// SurfaceView回調/////////////////////////////////////////////@Overridepublic void surfaceCreated(SurfaceHolder holder) {  mSurfaceHolder = holder;  startPreView(holder);}@Overridepublic void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {}@Overridepublic void surfaceDestroyed(SurfaceHolder holder) {  if (mCamera != null) {    Log.d(TAG, "surfaceDestroyed: ");    //停止預覽並釋放網路攝影機資源    mCamera.stopPreview();    mCamera.release();    mCamera = null;  }  if (mMediaRecorder != null) {    mMediaRecorder.release();    mMediaRecorder = null;  }}

step3: 實現視頻預覽的方法

/** * 開啟預覽 * * @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();    }  }}

Note: 上面添加了自動對焦的代碼, 但是部分手機可能不支援

自訂雙向縮減的進度條

有些像我一樣的初學者一看到自訂某某View, 就覺得比較牛X. 其實呢, Google已經替我們寫好了很多代碼, 所以我們用就行了.而且咱們的這個進度條也沒啥, 不就是一根線, 今天咱就來說說.

step1: 繼承View, 完成初始化

private static final String TAG = "BothWayProgressBar";//取消狀態為紅色bar, 反之為綠色barprivate 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);}

Note: OnProgressEndListener, 主要用於當進度條走到中間了, 好通知相機停止錄製, 介面如下:

public interface OnProgressEndListener{  void onProgressEndListener();}/** * 當進度條結束後的 監聽 * @param onProgressEndListener */public void setOnProgressEndListener(OnProgressEndListener onProgressEndListener) {  mOnProgressEndListener = onProgressEndListener;}

step2 :設定Setter方法用於通知我們的Progress改變狀態

/** * 設定進度 * @param progress */public void setProgress(int progress) {  this.progress = progress;  invalidate();}/** * 設定錄製狀態 是否為取消狀態 * @param isCancel */public void setCancel(boolean isCancel) {  this.isCancel = isCancel;  invalidate();}/** * 重寫是否可見方法 * @param visibility */@Overridepublic void setVisibility(int visibility) {  mVisibility = visibility;  //重新繪製  invalidate();}

step3 :最重要的一步, 畫出我們的進度條,使用的就是View中的onDraw(Canvas canvas)方法

@Overrideprotected 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));  }}

錄製事件的處理

錄製中觸發的事件包括四個:

  1. 長按錄製
  2. 抬起儲存
  3. 上滑取消
  4. 雙擊放大(變焦) 

 現在對這4個事件逐個分析:
前三這個事件, 我都放在了一個onTouch()回調方法中了
對於第4個, 我們待會談
我們先把onTouch()中局部變數列舉一下:

@Overridepublic 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事件, 使用線程延遲發送Handler來實現進度條的更新

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;}

Note: startRecord()這個方法先不說, 我們只需要知道執行了它就可以錄製了, 但是Handler事件還是要說的, 它只負責更新進度條的進度

////////////////////////////////////////////////////// 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;

Note: 同樣的, 內部的stopRecordUnSave()和stopRecordSave();大家先不要考慮, 我們會在後面介紹, 他倆從名字就能看出 前者用來停止錄製但不儲存, 後者停止錄製並儲存

上滑取消

配合上一部分說得抬起取消事件, 實現上滑取消

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: 這個停止不儲存是我自己的一種想法, 如果大家有更好的想法, 歡迎大家到評論中指出, 不勝感激

完整代碼

源碼我已經放在了github上了, 寫部落格真是不易! 寫篇像樣的部落格更是不易, 希望大家多多支援

總結

終於寫完了!!! 這是我最想說得話, 從案例一開始到現在已經過去很長時間. 這是我寫得最長的一篇部落格, 發現能表達清楚自己的想法還是很困難的, 這是我最大的感受!!!

實話說這個案例不是很困難, 但是像我這樣的初學者拿來練練手還是非常好的, 在這裡還要感謝再見傑克的部落格, 也給我提供了很多協助

感謝閱讀,希望能協助到大家,謝謝大家對本站的支援!

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.