短视频app源码开发,短视频录制的实现

2021-05-25 17:54:52 浏览数 (1)

原理说明

利用SurfaceView预览视频 利用系统自带的MediaRecorder实现短视频app源码中短视频视频的录制

  1. 实例化
  2. 设置音频输入
  3. 设置输出格式
  4. 设置视频编码格式
  5. 设置输出路径
  6. 调用prepare()进行资源初始化
  7. 调用start()开始录制 注意: 这里的步骤先后顺序非常重要,如果对MediaRecorder不是那么熟悉,还是照着步骤写比较好

使用方法

代码语言:javascript复制
    // 录制视频
    private void toRecordVideo() {
        RecordConfig
                .getInstance()
                .with(this)
                .setQuality(RecordConfig.Quality.QUALITY_480P)
                .setMaxDuration(6*1000)
                .setFocusMode(RecordConfig.FocusMode.FOCUS_MODE_CONTINUOUS_VIDEO)
                .setOutputPath("/smallvideo/")
                .obtainVideo(REQUEST_CODE_VIDEO);
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (requestCode==REQUEST_CODE_VIDEO&&resultCode==RESULT_OK){
            //接收视频输出路径
            String videoPath=RecordConfig.obtainVideoPath(data);
            int duration=RecordConfig.obtainVideoDuration(data);
            Log.i(this.getClass().getSimpleName(),"obtainVideoPath=" videoPath " duration=" generateTime(duration));
        }
    }
复制代码

实现视频录制

新建录制与播放界面

录制界面

代码语言:javascript复制
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".RecordVideoActivity">
    <!--预览视频-->
    <SurfaceView
        android:id="@ id/surfaceView"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
    <!--录制按钮-->
    <Button
        android:id="@ id/btnRecord"
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:layout_marginBottom="60dp"
        android:background="@drawable/selector_record_point"
        app:layout_constraintBottom_toBottomOf="@ id/surfaceView"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toStartOf="parent" />
    <!--录制进度条-->
    <com.junt.videorecorderlib.CustomProgressBar
        android:id="@ id/progressBar"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:layout_margin="5dp"
        android:visibility="gone"
        app:layout_constraintBottom_toBottomOf="@ id/btnRecord"
        app:layout_constraintEnd_toEndOf="@ id/btnRecord"
        app:layout_constraintStart_toStartOf="@ id/btnRecord"
        app:layout_constraintTop_toTopOf="@ id/btnRecord"
        app:ringWidth="10"
        app:style="ring" />
    <!--取消按钮-->
    <ImageView
        android:id="@ id/ivBack"
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:paddingStart="5dp"
        android:paddingTop="10dp"
        android:paddingEnd="5dp"
        android:paddingBottom="10dp"
        android:scaleType="fitXY"
        android:src="@drawable/down"
        app:layout_constraintBottom_toBottomOf="@ id/btnRecord"
        app:layout_constraintEnd_toStartOf="@ id/btnRecord"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="@ id/btnRecord" />

</androidx.constraintlayout.widget.ConstraintLayout>
复制代码

录制按钮动画效果 内部白色按钮缩小放大利用selector实现

代码语言:javascript复制
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_pressed="true">
        <layer-list>
            <item android:drawable="@drawable/record_bg_recording" android:gravity="center" />

            <item android:drawable="@drawable/record_fg_recording" android:gravity="center" />
        </layer-list>
    </item>

    <item>
        <layer-list>
            <item android:drawable="@drawable/record_bg_default" android:gravity="center" />

            <item android:drawable="@drawable/record_fg_default" android:gravity="center" />
        </layer-list>
    </item>

</selector>
复制代码

自定义圆形进度条: github.com/xiaojigugu/…

预览界面

代码语言:javascript复制
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".PlayVideoActivity">
    
    <!--自定义View播放视频-->
    <com.junt.videorecorderlib.MediaPlayView
        android:id="@ id/playView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>
    <!--取消-->
    <ImageButton
        android:id="@ id/btnGiveUp"
        android:layout_width="80dp"
        android:layout_height="80dp"
        android:background="@drawable/selector_video_play_button"
        android:src="@drawable/fanhui"
        app:layout_constraintBottom_toTopOf="@ id/guideline2"
        app:layout_constraintEnd_toStartOf="@ id/guideline"
        app:layout_constraintHorizontal_bias="0.44"
        app:layout_constraintStart_toStartOf="parent" />
    <!--确定-->
    <ImageButton
        android:id="@ id/btnConfirm"
        android:layout_width="80dp"
        android:layout_height="80dp"
        android:background="@drawable/selector_video_play_button"
        android:src="@drawable/duigou"
        app:layout_constraintBottom_toBottomOf="@ id/btnGiveUp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="@ id/guideline" />
    <!--辅助线-->
    <androidx.constraintlayout.widget.Guideline
        android:id="@ id/guideline"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        app:layout_constraintGuide_percent="0.5" />
    <!--辅助线-->
    <androidx.constraintlayout.widget.Guideline
        android:id="@ id/guideline2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        app:layout_constraintGuide_percent="0.9" />
</androidx.constraintlayout.widget.ConstraintLayout>  
复制代码

自定义MediaPlayView 继承SurfaceView,利用SurfaceHolder的回调方法进行MediaPlayer的初始化

新建相机管理类

这里直接copy Google官方示例中的代码

代码语言:javascript复制
public class CameraHelper {

    public static final int MEDIA_TYPE_IMAGE = 1;
    public static final int MEDIA_TYPE_VIDEO = 2;

    /**
     * Iterate over supported camera video sizes to see which one best fits the
     * dimensions of the given view while maintaining the aspect ratio. If none can,
     * be lenient with the aspect ratio.
     *
     * @param supportedVideoSizes Supported camera video sizes.
     * @param previewSizes Supported camera preview sizes.
     * @param w     The width of the view.
     * @param h     The height of the view.
     * @return Best match camera video size to fit in the view.
     */
    public static Camera.Size getOptimalVideoSize(List<Camera.Size> supportedVideoSizes,
            List<Camera.Size> previewSizes, int w, int h) {
        // Use a very small tolerance because we want an exact match.
        final double ASPECT_TOLERANCE = 0.1;
        double targetRatio = (double) w / h;

        // Supported video sizes list might be null, it means that we are allowed to use the preview
        // sizes
        List<Camera.Size> videoSizes;
        if (supportedVideoSizes != null) {
            videoSizes = supportedVideoSizes;
        } else {
            videoSizes = previewSizes;
        }
        Camera.Size optimalSize = null;

        // Start with max value and refine as we iterate over available video sizes. This is the
        // minimum difference between view and camera height.
        double minDiff = Double.MAX_VALUE;

        // Target view height
        int targetHeight = h;

        // Try to find a video size that matches aspect ratio and the target view size.
        // Iterate over all available sizes and pick the largest size that can fit in the view and
        // still maintain the aspect ratio.
        for (Camera.Size size : videoSizes) {
            double ratio = (double) size.width / size.height;
            if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE)
                continue;
            if (Math.abs(size.height - targetHeight) < minDiff && previewSizes.contains(size)) {
                optimalSize = size;
                minDiff = Math.abs(size.height - targetHeight);
            }
        }

        // Cannot find video size that matches the aspect ratio, ignore the requirement
        if (optimalSize == null) {
            minDiff = Double.MAX_VALUE;
            for (Camera.Size size : videoSizes) {
                if (Math.abs(size.height - targetHeight) < minDiff && previewSizes.contains(size)) {
                    optimalSize = size;
                    minDiff = Math.abs(size.height - targetHeight);
                }
            }
        }
        return optimalSize;
    }

    /**
     * @return the default camera on the device. Return null if there is no camera on the device.
     */
    public static Camera getDefaultCameraInstance() {
        return Camera.open();
    }


    /**
     * @return the default rear/back facing camera on the device. Returns null if camera is not
     * available.
     */
    public static Camera getDefaultBackFacingCameraInstance() {
        Log.i(CameraHelper.class.getSimpleName(),"getDefaultBackFacingCameraInstance");
        return getDefaultCamera(Camera.CameraInfo.CAMERA_FACING_BACK);
    }

    /**
     * @return the default front facing camera on the device. Returns null if camera is not
     * available.
     */
    public static Camera getDefaultFrontFacingCameraInstance() {
        return getDefaultCamera(Camera.CameraInfo.CAMERA_FACING_FRONT);
    }


    /**
     *
     * @param position Physical position of the camera i.e Camera.CameraInfo.CAMERA_FACING_FRONT
     *                 or Camera.CameraInfo.CAMERA_FACING_BACK.
     * @return the default camera on the device. Returns null if camera is not available.
     */
    @TargetApi(Build.VERSION_CODES.GINGERBREAD)
    private static Camera getDefaultCamera(int position) {
        // Find the total number of cameras available
        int  mNumberOfCameras = Camera.getNumberOfCameras();

        // Find the ID of the back-facing ("default") camera
        Camera.CameraInfo cameraInfo = new Camera.CameraInfo();
        for (int i = 0; i < mNumberOfCameras; i  ) {
            Camera.getCameraInfo(i, cameraInfo);
            if (cameraInfo.facing == position) {
                return Camera.open(i);

            }
        }

        return null;
    }

    /**
     * Creates a media file in the {@code Environment.DIRECTORY_PICTURES} directory. The directory
     * is persistent and available to other applications like gallery.
     *
     * @param type Media type. Can be video or image.
     * @return A file object pointing to the newly created file.
     */
    public  static File getOutputMediaFile(int type){
        // To be safe, you should check that the SDCard is mounted
        // using Environment.getExternalStorageState() before doing this.
        if (!Environment.getExternalStorageState().equalsIgnoreCase(Environment.MEDIA_MOUNTED)) {
            return  null;
        }

        File mediaStorageDir = new File(Environment.getExternalStoragePublicDirectory(
                Environment.DIRECTORY_PICTURES), "CameraSample");
        // This location works best if you want the created images to be shared
        // between applications and persist after your app has been uninstalled.

        // Create the storage directory if it does not exist
        if (! mediaStorageDir.exists()){
            if (! mediaStorageDir.mkdirs()) {
                Log.d("CameraSample", "failed to create directory");
                return null;
            }
        }

        // Create a media file name
        String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US).format(new Date());
        File mediaFile;
        if (type == MEDIA_TYPE_IMAGE){
            mediaFile = new File(mediaStorageDir.getPath()   File.separator  
                    "IMG_"  timeStamp   ".jpg");
        } else if(type == MEDIA_TYPE_VIDEO) {
            mediaFile = new File(mediaStorageDir.getPath()   File.separator  
                    "VID_"  timeStamp   ".mp4");
        } else {
            return null;
        }
        return mediaFile;
    }
}  
复制代码

新建MediaRecorder配置类

RecordConfig.java

代码语言:javascript复制
public class RecordConfig {
    private static RecordConfig recordConfig;
    private Activity activity;

    public static final String RECORD_CONFIG_SP_NAME = "RECORD_CONFIG";
    private SharedPreferences sp;

    static final String CONFIG_QUALITY = "quality";
    static final String CONFIG_FOCUS_MODE = "focus_mode";
    static final String CONFIG_ENCODING_BIT_RATE = "bitRate";
    static final String CONFIG_FRAME_RATE = "frameRate";
    static final String CONFIG_OUTPUT_PATH = "outputPath";
    static final String CONFIG_MAX_DURATION = "duration";

    public static RecordConfig getInstance() {
        if (recordConfig == null) {
            recordConfig = new RecordConfig();
        }
        return recordConfig;
    }
    
    <!--接收上下文-->
    public RecordConfig with(Activity activity) {
        this.activity = activity;
        sp = activity.getSharedPreferences(RECORD_CONFIG_SP_NAME, Context.MODE_PRIVATE);
        return this;
    }
    
    <!--视频质量-->
    public RecordConfig setQuality(int quality) {
        if (sp == null) {
            throw new NullPointerException("Please innovate method 'with()' first");
        }
        //default:480P
        sp.edit().putInt(CONFIG_QUALITY, quality).commit();
        return this;
    }
    
    <!--对焦模式-->
    public RecordConfig setFocusMode(String focusMode) {
        if (sp == null) {
            throw new NullPointerException("Please innovate method 'with()' first");
        }
        //default:FOCUS_MODE_CONTINUOUS_VIDEO
        sp.edit().putString(CONFIG_FOCUS_MODE, focusMode).commit();
        return this;
    }

    <!--音频采样率-->
    public RecordConfig setEncodingBitRate(int encodingBitRate) {
        if (sp == null) {
            throw new NullPointerException("Please innovate method 'with()' first");
        }
        //default:5 * 1280 * 720
        sp.edit().putInt(CONFIG_ENCODING_BIT_RATE, encodingBitRate).commit();
        return this;
    }
    
    <!--视频帧率-->
    public RecordConfig setFrameRate(int frameRate) {
        if (sp == null) {
            throw new NullPointerException("Please innovate method 'with()' first");
        }
        //default:20
        sp.edit().putInt(CONFIG_FRAME_RATE, frameRate).commit();
        return this;
    }

    /**
     * 设置视频输出路径
     * @param outputPath 视频输出路径默认前缀为 /storage/emulated/0
     *                   eg:outputPath="/smallvideo/files/VID_timestamp.mp4",最终的文件路径为/storage/emulated/0/smallvideo/files/VID_timestamp.mp4
     * @return RecordConfig
     */
    public RecordConfig setOutputPath(String outputPath) {
        if (sp == null) {
            throw new NullPointerException("Please innovate method 'with()' first");
        }
        //default:/storage/emulated/0/junt/video/VID_timestamp.mp4
        String path = Environment.getExternalStorageDirectory()   outputPath;
        sp.edit().putString(CONFIG_OUTPUT_PATH, path).commit();
        return this;
    }

    <!--设置最大时长-->
    public RecordConfig setMaxDuration(int duration) {
        if (sp == null) {
            throw new NullPointerException("Please innovate method 'with()' first");
        }
        //default:6*1000ms
        sp.edit().putInt(CONFIG_MAX_DURATION, duration).commit();
        return this;
    }

    /**
     * 获取视频路径
     */
    public static String obtainVideoPath(Intent data){
        if (data==null){
            throw new NullPointerException("data is NULL");
        }
        return data.getStringExtra("path");
    }

    /**
     * 获取视频长度
     */
    public static int obtainVideoDuration(Intent data){
        if (data==null){
            throw new NullPointerException("data is NULL");
        }
        return data.getIntExtra("duration",0);
    }
    
    <!--跳转至视频录制界面-->
    public void obtainVideo(int requestCode) {
        if (activity == null) {
            throw new NullPointerException("Please innovate method 'with()' first");
        }
        activity.startActivityForResult(new Intent(activity, RecordVideoActivity.class), requestCode);
    }
    
    <!--视频质量-->
    public static class Quality {
        public static int QUALITY_LOW = CamcorderProfile.QUALITY_LOW;
        public static int QUALITY_HIGH = CamcorderProfile.QUALITY_HIGH;
        public static int QUALITY_QCIF = CamcorderProfile.QUALITY_QCIF;
        public static int QUALITY_CIF = CamcorderProfile.QUALITY_CIF;
        public static int QUALITY_480P = CamcorderProfile.QUALITY_480P;
        public static int QUALITY_720P = CamcorderProfile.QUALITY_720P;
        public static int QUALITY_1080P = CamcorderProfile.QUALITY_1080P;
        public static int QUALITY_QVGA = CamcorderProfile.QUALITY_QVGA;
        @TargetApi(Build.VERSION_CODES.LOLLIPOP)
        public static int QUALITY_2160P = CamcorderProfile.QUALITY_2160P;
    }

    <!--对焦模式-->
    public static class FocusMode {
        public static String FOCUS_MODE_AUTO = Camera.Parameters.FOCUS_MODE_AUTO;
        public static String FOCUS_MODE_CONTINUOUS_PICTURE = Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE;
        public static String FOCUS_MODE_CONTINUOUS_VIDEO = Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO;
        public static String FOCUS_MODE_EDOF = Camera.Parameters.FOCUS_MODE_EDOF;
        public static String FOCUS_MODE_INFINITY = Camera.Parameters.FOCUS_MODE_INFINITY;
        public static String FOCUS_MODE_FIXED = Camera.Parameters.FOCUS_MODE_FIXED;
        public static String FOCUS_MODE_MACRO = Camera.Parameters.FOCUS_MODE_MACRO;
    }
}
复制代码

新建RecordVideoActivity界面

!!!所有摄像头及视频录制的操作应该异步处理

  1. 初始化摄像头并预览
代码语言:javascript复制
        mCamera = Camera.open();
        Log.d(TAG, "Camera.open");
        //获取摄像头参数
        Camera.Parameters parameters = mCamera.getParameters();
        //获取所有预览尺寸
        List<Camera.Size> mSupportedPreviewSizes = parameters.getSupportedPreviewSizes();
        //获取所有的视频尺寸
        List<Camera.Size> mSupportedVideoSizes = parameters.getSupportedVideoSizes();
        //获取适当的尺寸
        Camera.Size optimalSize = CameraHelper.getOptimalVideoSize(mSupportedVideoSizes,
                mSupportedPreviewSizes, surfaceView.getWidth(), surfaceView.getHeight());
        //设置预览竖屏方向
        mCamera.setDisplayOrientation(90);
        //获取系统默认配置
        CamcorderProfile profile = CamcorderProfile.get(sp.getInt(RecordConfig.CONFIG_QUALITY, CamcorderProfile.QUALITY_480P));
        //设置录制尺寸
        profile.videoFrameWidth = optimalSize.width;
        profile.videoFrameHeight = optimalSize.height;
        //设置视频码率
        profile.videoBitRate = sp.getInt(RecordConfig.CONFIG_ENCODING_BIT_RATE, 5 * 1280 * 720);
        //设置视频帧率
        profile.videoFrameRate = sp.getInt(RecordConfig.CONFIG_FRAME_RATE, 30);
        //设置预览尺寸
        parameters.setPreviewSize(profile.videoFrameWidth, profile.videoFrameHeight);
        //设置对焦模式
        parameters.setFocusMode(sp.getString(RecordConfig.CONFIG_FOCUS_MODE, Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO));
        //设置闪光灯模式
        parameters.setFlashMode(Camera.Parameters.FLASH_MODE_AUTO);
        mCamera.setParameters(parameters);
        try {
            //绑定SurfaceView
            mCamera.setPreviewDisplay(surfaceView.getHolder());
            //开始预览
            mCamera.startPreview();
            Log.d(TAG, "Camera初始化结束");
        } catch (IOException e) {
            Log.e(TAG, "Surface texture is unavailable or unsuitable"   e.getMessage());
        }  
复制代码
  1. 初始化MediaRecorder
代码语言:javascript复制
        mMediaRecorder = new MediaRecorder();
        //释放摄像头,以便让MediaRecorder能够使用它
        // 该方法源码注释中明确表明必须提前调用   
        //* <p>This must be done before calling
        //* {@link android.media.MediaRecorder#setCamera(Camera)}. This cannot be
        //* called after recording starts.
        //
        mCamera.unlock();
        //绑定摄像头
        mMediaRecorder.setCamera(mCamera);
        //设置音频来源
        mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.DEFAULT);
        //设置视频来源
        mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
        //设置录制视频质量
        mMediaRecorder.setProfile(CamcorderProfile.get(sp.getInt(RecordConfig.CONFIG_QUALITY, CamcorderProfile.QUALITY_480P)));
        //设置视频码率
        mMediaRecorder.setVideoFrameRate(sp.getInt(RecordConfig.CONFIG_FRAME_RATE, 30));
        //设置视频帧率
        mMediaRecorder.setVideoEncodingBitRate(sp.getInt(RecordConfig.CONFIG_ENCODING_BIT_RATE, 5 * 1280 * 720));
        //设置视频输出文件
        String defaultPath = Environment.getExternalStorageDirectory()   "/junt/video/";
        String outputPath = sp.getString(RecordConfig.CONFIG_OUTPUT_PATH, defaultPath)   "VID_"   System.currentTimeMillis()   ".mp4";
        mOutputFile = new File(outputPath);
        if (!mOutputFile.getParentFile().exists()) {
            mOutputFile.getParentFile().mkdirs();
        }
        mMediaRecorder.setOutputFile(mOutputFile.getAbsolutePath());
        //设置录制视频为竖屏
        mMediaRecorder.setOrientationHint(90);
        //设置录制时的预览Surface
        mMediaRecorder.setPreviewDisplay(surfaceView.getHolder().getSurface());
        //设置最大时长
        //当设置次项以后可以在 android.media.MediaRecorder.OnInfoListener 监听回调方法中接收结果
        // * After recording reaches the specified duration, a notification
        //* will be sent to the {@link android.media.MediaRecorder.OnInfoListener}
        //* with a "what" code of {@link #MEDIA_RECORDER_INFO_MAX_DURATION_REACHED}
        //* and recording will be stopped.
        MAX_DURATION = sp.getInt(RecordConfig.CONFIG_MAX_DURATION, 6 * 1000);
        mMediaRecorder.setMaxDuration(MAX_DURATION);
        try {
            //设置录制监听
            mMediaRecorder.setOnInfoListener(this);
            //设置错误监听
            mMediaRecorder.setOnErrorListener(this);
            //完成初始化,等待录制
            mMediaRecorder.prepare();
        } catch (Exception e) {
            Log.d(TAG, "IllegalStateException preparing MediaRecorder: "   e.getMessage());
            return false;
        }   
        
        //录制完成监听
        @Override
        public void onInfo(MediaRecorder mr, int what, int extra) {
            Log.d(TAG, "onInfo what="   what   " extra="   extra);
            if (what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_DURATION_REACHED && isRecording) {
                recordComplete();
            }
    }
    ``` 
    注意:步骤1-2中的所有操作都是异步的  
``` java
        class MediaPrepareTask extends AsyncTask<Void, Void, Boolean> {
        @Override
        protected Boolean doInBackground(Void... voids) {
            //初始化操作,包括初始化Camera、MediaRecorder
            boolean initResult = prepareVideoRecorder();
            Log.d(TAG, "doInBackground,init:"   initResult);
            if (initResult) {
                Log.d(TAG, "开始预览");
                return true;
            } else {
                Log.d(TAG, "初始化错误");
                //初始化错误则立即释放Camera、MediaRecorder
                releaseMediaRecorder();
                return false;
            }
        }
    }   
复制代码
  1. 录制按钮事件
代码语言:javascript复制
    /**
     * 录制按钮触摸事件
     * 单击录制(再次单击停止录制)、按住录制(松手停止录制)
     */
    @Override
    public boolean onTouch(View view, MotionEvent motionEvent) {
        int action = motionEvent.getAction();
        if (action == MotionEvent.ACTION_DOWN) {
            //按下
            long downTime=System.currentTimeMillis();
            if (downTime-clickTime>1000){
                if (!isRecording){
                    btnRecord.setPressed(true);
                    //开始录制
                    RecordTask recordTask = new RecordTask();
                    recordTask.execute();
                }else if (mMediaRecorder!=null){
                    recordComplete();
                }
            }
            clickTime=downTime;
        } else if (action == MotionEvent.ACTION_UP) {
            //抬起
            if (isRecording && mMediaRecorder != null) {
                //停止录制
                recordComplete();
            }
        }
        return true;
    }  
    
     /**
     * 开始录制任务
     */
    class RecordTask extends AsyncTask<Void, Void, Boolean> {
        @Override
        protected Boolean doInBackground(Void... voids) {
            mMediaRecorder.start();
            return true;
        }

        @Override
        protected void onPostExecute(Boolean aBoolean) {
            super.onPostExecute(aBoolean);
            isRecording = true;
            //更新圆形进度条
            setProgressBar();
        }
    }
    
     /**
     * 停止录制
     */
    private void recordComplete() {
        isRecording = false;
        btnRecord.setPressed(false);
        endTime = System.currentTimeMillis();
        hideRecordController();
        //跳转到视频播放界面进行完整预览,在onActivityResult中接收是否使用该视频文件的结果
        Intent intent = new Intent(RecordVideoActivity.this, PlayVideoActivity.class);
        intent.putExtra(VIDEO_PATH, mOutputFile.getAbsolutePath());
        startActivityForResult(intent, REQUEST_CODE_TO_PLAY);
    } 
    
       /**
     * 设置进度条显示并调整其大小
     */
    private void setProgressBar() {
        progressBar.setProgress(0);
        ConstraintLayout.LayoutParams params = (ConstraintLayout.LayoutParams) progressBar.getLayoutParams();
        params.width = btnRecord.getMeasuredWidth()   20;
        params.height = btnRecord.getMeasuredHeight()   20;
        progressBar.setLayoutParams(params);
        progressBar.setVisibility(View.VISIBLE);
        startTime = System.currentTimeMillis
        //利用计时器定时更新进度条
        progressHandler = new ProgressHandler();
        new Timer().schedule(new TimerTask() {
            @Override
            public void run() {
                if (progressHandler != null && isRecording) {
                    progressHandler.sendEmptyMessage(0);
                }
            }
        }, 0, 50);
    }
    
     /**
     * 更新进度条
     */
    class ProgressHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            if (msg.what == 0) {
                int progress = (int) ((System.currentTimeMillis() - startTime) / (MAX_DURATION / 100));
                if (progress <= 100) {
                    progressBar.setProgress(progress);
                }
            }
        }
    }
    
     /**
     * 接收用户确认时事件
     * @param requestCode 跳转播放请求码
     * @param resultCode 结果码
     * @param data 传递了duration
     */
    @Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (requestCode == REQUEST_CODE_TO_PLAY) {
            if (resultCode == RESULT_CANCELED) {
                //用户选择取消,重置视频录制界面(进度条、播放按钮)
                resetProgress();
                showRecordController();
                //删除被放弃的视频
                deleteFile();
            } else if (resultCode == RESULT_OK) {
                //用户选择了该视频,将结果返回给调用方
                Intent intent = new Intent();
                intent.putExtra("duration", data.getIntExtra("duration", 0));
                intent.putExtra("path", mOutputFile.getAbsolutePath());
                setResult(RESULT_OK, intent);
                finish();
            }
        }
    }
复制代码

播放界面

代码语言:javascript复制
    /**
     * 放弃该视频
     */
    private void giveUp() {
        setResult(RESULT_CANCELED);
        finish();
    }

    /**
     * 选择该视频
     */
    private void chooseThisVideo() {
        Intent intent = new Intent();
        intent.putExtra("duration", mediaPlayView.getDuration());
        Log.i(TAG,"duration=" mediaPlayView.getDuration());
        setResult(RESULT_OK, intent);
        finish();
    }
复制代码

完结

0 人点赞