Maison > Applet WeChat > Développement WeChat > le corps du texte

Introduction détaillée à l'utilisation d'Android pour mettre en œuvre la fonction d'enregistrement vidéo courte de WeChat

高洛峰
Libérer: 2017-03-23 13:39:44
original
2257 Les gens l'ont consulté

Cet article présente principalement des informations pertinentes sur la mise en œuvre de la fonction d'enregistrement vidéo courte d'Android WeChat. Des idées et des codes de mise en œuvre spécifiques sont fournis ici. Les amis dans le besoin peuvent se référer à

Enregistrement vidéo courte d'Android WeChat. fonction

Avant le développement

J'ai été exposé à des contrôles liés à la vidéo ces derniers jours, donc, suite au précédent tremblement de WeChat, j'ai pensé à implémenter une courte vidéo WeChat. La fonction d'enregistrement a de nombreuses fonctions. Je prends le temps d'écrire à ce sujet chaque jour. Pour être honnête, certaines choses sont assez difficiles. J'espère que tout le monde y jettera un œil attentif. Veuillez me corriger dans les commentaires si je me trompe. . Sans plus tarder, entrons dans le vif du sujet.

Environnement de développement

Il vient d'être mis à jour récemment, amis qui n'ont pas mis à jour, dépêchez-vous

  1. Android Studio 2.2.2

  2. JDK1.7

  3. API 24

  4. Gradle 2.2 .2

Points de connaissances associés

  1. Utilisation de l'interface d'enregistrement vidéo SurfaceView

  2. L'utilisation de la caméra

  3. La mise au point et le zoom de la caméra

  4. L'utilisation du contrôle d'enregistrement vidéo MediaRecorder

  5. Vue personnalisée simple

  6. Utilisation de GestureDetector (détection de gestes)

Il y a beaucoup de choses utilisées , mais ne vous inquiétez pas, allons-y un par un Venez.

Commencer le développement

Analyse de cas

Vous pouvez ouvrir la courte vidéo dans votre WeChat et analysez brièvement ses fonctions : laquelle ?

  1. Fonction de prévisualisation vidéo de base

  2. Appuyez longuement sur « appuyez et maintenez pour filmer » pour enregistrer une vidéo

  3. La barre de progression pendant l'enregistrement devient plus courte des deux côtés jusqu'au milieu

  4. Lorsque vous lâchez prise ou que la barre de progression atteint la fin , la vidéo arrête l'enregistrement et est enregistrée

  5. Faites glisser votre doigt vers le haut depuis « Maintenir pour filmer » pour annuler l'enregistrement vidéo

  6. Appuyez deux fois sur l'écran pour zoomer

Selon l'analyse ci-dessus, nous l'avons complété étape par étape

Construire la mise en page

La mise en œuvre de l'interface de mise en page est correcte et pas difficile


<?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>
Copier après la connexion

Mise en œuvre de l'aperçu vidéo

étape 1 : obtenir le SurfaceView contrôle, définir les propriétés de base et la surveillance correspondante (la création du contrôle est asynchrone, seulement lorsqu'il est vraiment "préparé" » ne peut être appelé qu'une fois qu'il est prêt)


mSurfaceView = (SurfaceView) findViewById(R.id.main_surface_view);
 //设置屏幕分辨率
mSurfaceHolder.setFixedSize(videoWidth, videoHeight);
mSurfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
mSurfaceHolder.addCallback(this);
Copier après la connexion

étape 2 : implémentez la méthode d'interface, ouvrez l'aperçu de la vidéo dans la méthode surfaceCreated et détruisez-la dans surfaceDestroyed


//////////////////////////////////////////////
// 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;
  }
}
Copier après la connexion

étape 3 : méthode d'implémentation de la vidéo aperçu


/**
 * 开启预览
 *
 * @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();
    }
  }

}
Copier après la connexion

Remarque : le code de mise au point automatique est ajouté ci-dessus, mais certains téléphones mobiles peuvent

Rétrécissement bidirectionnel personnalisé la barre de progression n'est pas prise en charge

Certains débutants comme moi se sentent bien lorsqu'ils voient personnaliser une certaine vue X. En fait, Google a déjà écrit beaucoup de code pour nous, nous pouvons donc simplement l'utiliser. . Et notre barre de progression n'est rien, juste une ligne, parlons-en aujourd'hui

étape 1 : Hériter la vue et terminer l'initialisation


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);
}
Copier après la connexion

Remarque. : OnProgressEndListener, principalement utilisé lorsque la barre de progression atteint le milieu pour avertir la caméra d'arrêter l'enregistrement. L'interface est la suivante :


public interface OnProgressEndListener{
  void onProgressEndListener();
}
/**
 * 当进度条结束后的 监听
 * @param onProgressEndListener
 */
public void setOnProgressEndListener(OnProgressEndListener onProgressEndListener) {
  mOnProgressEndListener = onProgressEndListener;
}
Copier après la connexion

étape 2 : Définissez la méthode Setter. pour informer notre progression du changement de statut


/**
 * 设置进度
 * @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();
}
Copier après la connexion

étape 3 : L'étape la plus importante consiste à dessiner notre barre de progression, en utilisant la méthode onDraw (Canvas Canvas) dans View


@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));
  }
}
Copier après la connexion

Gestion des événements d'enregistrement

Les événements déclenchés lors de l'enregistrement sont au nombre de quatre :

  1. Long appuyez pour enregistrer

  2. Soulevez pour enregistrer

  3. Faites glisser vers le haut pour annuler

  4. Double-cliquez pour zoomer (zoom)

Analysez maintenant ces 4 événements un par un :
J'ai placé les trois premiers événements dans une méthode de rappel onTouch()
Pour le quatrième un, nous en reparlerons plus tard
Listons d'abord les variables locales dans 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;
  // ...
}
Copier après la connexion

Appuyez longuement pour enregistrer

Appuyez longuement pour enregistrer, nous devons écouter l'événement ACTION_DOWN et utiliser le fil de discussion pour retarder l'envoi du gestionnaire pour mettre à jour la barre de progression


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;
}
Copier après la connexion

Remarque : ne parlons pas de la méthode startRecord(). Il suffit de savoir qu'elle peut être enregistrée après son exécution, mais il faut quand même mentionner l'événement Handler. Seul responsable de la mise à jour de la progression de la barre de progression<🎜. >


////////////////////////////////////////////////////
// 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;
    }
  }
}
Copier après la connexion

Soulevez et enregistrez

De même, nous devons écouter l'événement ACTION_UP ici, mais considérez cela lorsque l'utilisateur soulève trop rapidement (la durée d'enregistrement est trop courte), il n'est pas nécessaire de sauvegarder. De plus, cet événement inclut le levage dans l'état d'annulation. Expliquez : c'est le moment où l'utilisateur se lève pour annuler l'enregistrement. , veuillez regarder le code



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;
Copier après la connexion
Remarque : De même, les fonctions internes stopRecordUnSave() et stopRecordSave(); nous le présenterons plus tard, vous pouvez voir à leurs noms que le premier est utilisé pour arrêter l'enregistrement mais pas pour sauvegarder, le second est utilisé pour arrêter l'enregistrement et sauvegarder


glissez vers le haut pour annuler

coopérer Une partie dit que vous devez déclencher l'événement d'annulation et réaliser un glissement vers le haut pour annuler


case MotionEvent.ACTION_MOVE:
 if (ex > left && ex < right) {
   float currentY = event.getY();
   if (downY - currentY > 10) {
     isCancel = true;
     mProgressBar.setCancel(true);
   }
 }
 break;
Copier après la connexion

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;
  }
});
Copier après la connexion

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;
  }
}
Copier après la connexion

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

}
Copier après la connexion

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

  }
}
Copier après la connexion

实现视频的停止

大家可能会问, 视频的停止为什么单独抽出来说呢? 仔细的同学看上面代码会看到这两个方法: stopRecordSave和stopRecordUnSave, 一个停止保存, 一个是停止不保存, 接下来我们就补上这个坑

停止并保存


private void stopRecordSave() {
  if (isRecording) {
    isRunning = false;
    mMediaRecorder.stop();
    isRecording = false;
    Toast.makeText(this, "视频已经放至" + mTargetFile.getAbsolutePath(), Toast.LENGTH_SHORT).show();
  }
}
Copier après la connexion

停止不保存


private void stopRecordUnSave() {
  if (isRecording) {
    isRunning = false;
    mMediaRecorder.stop();
    isRecording = false;
    if (mTargetFile.exists()) {
      //不保存直接删掉
      mTargetFile.delete();
    }
  }
}
Copier après la connexion

Note: 这个停止不保存是我自己的一种想法, 如果大家有更好的想法, 欢迎大家到评论中指出, 不胜感激

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Étiquettes associées:
source:php.cn
Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Tutoriels populaires
Plus>
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal
À propos de nous Clause de non-responsabilité Sitemap
Site Web PHP chinois:Formation PHP en ligne sur le bien-être public,Aidez les apprenants PHP à grandir rapidement!