微信朋友圈錄制小視頻癌幕,效果圖如下:
怎么使用员寇,大家應(yīng)該不陌生了。其中關(guān)鍵技術(shù)有兩個(gè):
- 錄制視頻技術(shù)厕妖;
- “按住拍”的動畫效果首尼;
在網(wǎng)上搜了幾個(gè)demo,最終發(fā)現(xiàn)下面兩個(gè)開源項(xiàng)目比較靠譜:
- RecordVideoDemo ← 重點(diǎn)推薦
- WeiXinCamera
RecordVideoDemo中實(shí)現(xiàn)了兩種錄制方法:
a. 采用系統(tǒng)類MediaRecorder言秸。
b. 直接采集攝像頭畫面和聲卡的聲音软能,再保存為視頻格式。
經(jīng)過統(tǒng)計(jì)举畸,6s的視頻查排,方案a獲取的視頻非常清晰,大小為32M抄沮,方案比為200多k跋核。考慮到小視頻上傳叛买、加載速度的要求高于清晰度砂代,所以果斷選擇了方案b。
WeiXinCamera里面實(shí)現(xiàn)“按住拍率挣、線條逐步變窄為0”的動畫效果刻伊,抽取封裝一下也可以用。
經(jīng)過試驗(yàn)椒功,采用動畫方案反應(yīng)會慢幾個(gè)幾秒捶箱,體驗(yàn)不好,在VideoCapture里面用ProgressBar來模擬动漾,效果很好
集成步驟
- 把
RecordVideoDemo
中的WXLikeVideoRecorderLib拷貝到項(xiàng)目目錄 -
settings.gradle
中添加:
include ':WXLikeVideoRecorderLib'
- app項(xiàng)目的
build.gradle
中添加依賴:
dependencies{
compile project(':WXLikeVideoRecorderLib')
}
- 添加 攝像頭丁屎、音頻、存儲器 的讀寫權(quán)限
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
- 修改WXLikeVideoRecorder旱眯,增加設(shè)置最長錄制時(shí)間的接口悦屏。
// 最長錄制時(shí)間private long maxRecordTime = 15000;
/**
* 設(shè)置最長錄制時(shí)間
* @param maxRecordTime
*/
public void setMaxRecordTime(long maxRecordTime) {
this.maxRecordTime = maxRecordTime;
}
- 封裝RecordFragmentHolder节沦。
package lib;
import android.animation.Animator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.hardware.Camera;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Toast;
import sz.itguy.utils.FileUtil;
import sz.itguy.wxlikevideo.camera.CameraHelper;
import sz.itguy.wxlikevideo.recorder.WXLikeVideoRecorder;
import sz.itguy.wxlikevideo.views.CameraPreviewView;
import sz.itguy.wxlikevideo.views.CircleBackgroundTextView;
/**
* Created by shitianci on 16/6/28.
*/
public class RecordFragmentHolder {
private static final String TAG = RecordFragmentHolder.class.getSimpleName();
private final Context mContext;
private final OnRecordListener mListener;
private Camera mCamera;
private WXLikeVideoRecorder mRecorder;
private boolean isCancelRecord = false;
private ValueAnimator animation;
// 輸出寬度
private int outputWidth = 320;
// 輸出高度
private int outputHeight = 240;
public interface OnRecordListener{
void onEnd(String videoPath);
}
public RecordFragmentHolder(Context context, OnRecordListener listener) {
mContext = context;
mListener = listener;
}
/**
* 初始化空間
* @param preview 攝像頭預(yù)覽界面
* @param btnRecord 錄制按鈕
* @param animationLine 控制線
* @param duration 時(shí)長
* @return
*/
public boolean init(CameraPreviewView preview, CircleBackgroundTextView btnRecord, final View animationLine, final long duration) {
// Create an instance of Camera
int cameraId = CameraHelper.getDefaultCameraID();
mCamera = CameraHelper.getCameraInstance(cameraId);
if (null == mCamera) {
Toast.makeText(mContext, "打開相機(jī)失敿肌础爬!", Toast.LENGTH_SHORT).show();
return false;
}
// 初始化錄像機(jī)
mRecorder = new WXLikeVideoRecorder(mContext, FileUtil.MEDIA_FILE_DIR);
mRecorder.setOutputSize(outputWidth, outputHeight);
preview.setCamera(mCamera, cameraId);
mRecorder.setCameraPreviewView(preview);
btnRecord.setOnTouchListener(new CircleBackgroundTextView.OnTouchListener() {
@Override
public void onDownListener(MotionEvent event) {
}
@Override
public void onLongListener(final MotionEvent event) {
Log.d(TAG, "onLongListener");
isCancelRecord = false;
startRecord();
animation = AnimationUtil.startAnimation(animationLine, duration, new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animator) {
}
@Override
public void onAnimationEnd(Animator animator) {
stopRecord();
}
@Override
public void onAnimationCancel(Animator animator) {
}
@Override
public void onAnimationRepeat(Animator animator) {
}
});
}
@Override
public void onUpListener(MotionEvent event) {
animation.cancel();
stopRecord();
}
});
return true;
}
/**
* 設(shè)置輸出的寬高
* @param outputWidth
* @param outputHeight
*/
public void setOutputWidthAndHeight(int outputWidth, int outputHeight) {
this.outputWidth = outputWidth;
this.outputHeight = outputHeight;
}
public void onPause() {
if (mRecorder != null) {
boolean recording = mRecorder.isRecording();
// 頁面不可見就要停止錄制
mRecorder.stopRecording();
// 錄制時(shí)退出,直接舍棄視頻
if (recording) {
FileUtil.deleteFile(mRecorder.getFilePath());
}
}
releaseCamera(); // release the camera immediately on pause event
}
private void releaseCamera() {
if (mCamera != null) {
mCamera.setPreviewCallback(null);
// 釋放前先停止預(yù)覽
mCamera.stopPreview();
mCamera.release(); // release the camera for other applications
mCamera = null;
}
}
/**
* 開始錄制
*/
public void startRecord() {
if (mRecorder.isRecording()) {
Log.d(TAG, "startRecord");
Toast.makeText(mContext, "正在錄制中…", Toast.LENGTH_SHORT).show();
return;
}
// initialize video camera
if (prepareVideoRecorder()) {
// 錄制視頻
if (!mRecorder.startRecording())
Toast.makeText(mContext, "錄制失敗…", Toast.LENGTH_SHORT).show();
}
}
/**
* 準(zhǔn)備視頻錄制器
*
* @return
*/
private boolean prepareVideoRecorder() {
if (!FileUtil.isSDCardMounted()) {
Toast.makeText(mContext, "SD卡不可用吼鳞!", Toast.LENGTH_SHORT).show();
return false;
}
return true;
}
/**
* 停止錄制
*/
public void stopRecord() {
mRecorder.stopRecording();
String videoPath = mRecorder.getFilePath();
mListener.onEnd(videoPath);
// 沒有錄制視頻
if (null == videoPath) {
return;
}
// 若取消錄制看蚜,則刪除文件,否則通知宿主頁面發(fā)送視頻
if (isCancelRecord) {
FileUtil.deleteFile(videoPath);
} else {
// 告訴宿主頁面錄制視頻的路徑
// mContext.startActivity(new Intent(mContext, PlayVideoActiviy.class).putExtra(PlayVideoActiviy.KEY_FILE_PATH, videoPath));
}
}
}
- 在Fragment引用就可以了
package com.hbbohan.growmemory.view;
import android.Manifest;
import android.os.Bundle;
import android.view.View;
import com.hbbohan.growmemory.B;
import com.hbbohan.growmemory.R;
import java.io.File;
import butterfork.Bind;
import lib.RecordFragmentHolder;
import panda.android.lib.base.ui.fragment.BaseFragment;
import panda.android.lib.base.util.DevUtil;
import panda.android.lib.base.util.IntentUtil;
import sz.itguy.wxlikevideo.views.CameraPreviewView;
import sz.itguy.wxlikevideo.views.CircleBackgroundTextView;
/**
* Created by shitianci on 16/6/28.
*/
public class RecordVideoFragment extends BaseFragment {
@Bind(B.id.view_camera_preview)
CameraPreviewView mViewCameraPreview;
@Bind(B.id.btn_record)
CircleBackgroundTextView mBtnRecord;
@Bind(B.id.view_animation_line)
View mViewAnimationLine;
private RecordFragmentHolder mRecordFragmentHolder;
@Override
public String[] getPermissions() {
return new String[]{
Manifest.permission.CAMERA,
Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.RECORD_AUDIO
};
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
mRecordFragmentHolder = new RecordFragmentHolder(getActivity(), new RecordFragmentHolder.OnRecordListener() {
@Override
public void onEnd(String videoPath) {
DevUtil.showInfo(getActivity(), "視頻存放在:" + videoPath);
IntentUtil.openFile(getActivity(), new File(videoPath));
}
});
if (!mRecordFragmentHolder.init(mViewCameraPreview, mBtnRecord, mViewAnimationLine, 15000)){
getActivity().finish();
}
}
@Override
public void onPause() {
super.onPause();
mRecordFragmentHolder.onPause();
getActivity().finish();
}
@Override
public int getLayoutId() {
return R.layout.fragment_record_video;
}
}
- 添加動畫的引用庫
package lib;
import android.animation.Animator;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
/**
* Created by shitianci on 16/6/28.
*/
public class AnimationUtil {
private static final String TAG = AnimationUtil.class.getSimpleName();
/**
* 動畫效果:開始的寬度為父容器的寬度赔桌,逐步向中間縮減為0供炎。
* 使用場景:微信錄制小視頻
*
*/
public static ValueAnimator startAnimation(final View view, final long duration, final Animator.AnimatorListener animatorListener) {
ValueAnimator va = ObjectAnimator.ofInt(view.getWidth(), 0);
va.setDuration(duration);
va.addListener(animatorListener);
va.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
int value = (int) animation.getAnimatedValue();
ViewGroup.LayoutParams params = view.getLayoutParams();
params.width = value;
view.setLayoutParams(params);
view.requestLayout();
}
});
//結(jié)束時(shí)恢復(fù)寬高
final int width = view.getWidth();
final int height = view.getHeight();
va.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animator) {
Log.d(TAG, "onAnimationStart");
}
@Override
public void onAnimationEnd(Animator animator) {
Log.d(TAG, "onAnimationEnd");
setViewLayoutParams(view, width, height);
}
@Override
public void onAnimationCancel(Animator animator) {
Log.d(TAG, "onAnimationCancel");
}
@Override
public void onAnimationRepeat(Animator animator) {
Log.d(TAG, "onAnimationRepeat");
}
});
va.start();
return va;
}
/**
* 設(shè)置view的寬高
* @param view
* @param width
* @param height
*/
public static void setViewLayoutParams(View view, int width, int height) {
ViewGroup.LayoutParams params = view.getLayoutParams();
params.width = width;
params.height = height;
view.setLayoutParams(params);
view.requestLayout();
}
}
備注:如果采用23以上的sdk編譯,在6.0設(shè)備上會碰到權(quán)限問題疾党,具體解決方案音诫,參考Android M上的權(quán)限獲取問題。
Panda
2016-06-28