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

原理说明

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

 

 

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

使用方法

    // 录制视频
    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));
        }
    }
复制代码

实现视频录制

新建录制与播放界面

录制界面

 

 

 

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

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

预览界面

 

 

 

<?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官方示例中的代码

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

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. 初始化摄像头并预览
        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
        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. 录制按钮事件
    /**
     * 录制按钮触摸事件
     * 单击录制(再次单击停止录制)、按住录制(松手停止录制)
     */
    @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();
            }
        }
    }
复制代码

播放界面

    /**
     * 放弃该视频
     */
    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
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
很不错的一款直播视频码,支持二次开发原生视频app码 1.登录注册:支持手机验证码注册登录,QQ、微信、Facebook、Twitter一键登录及分享,登录后需要进行手机验证; 2.首页列表:首页展示热门直播、附近直播、关注主播列表,可在后台进行直播间推荐置顶操作。直播间封面显示主播名称、直播状态、房间类型、房间标题等,附近直播列表显示主播距离; 3.搜索功能:可根据主播名称和主播ID进行主播搜索,并添加关注; 4.每日签到:用户每日签到送礼,可收到系统赠送的金币奖励; 5.互动直播:开播前可进行封面图编辑上传,添加直播标题,选择直播房间类型,包含密码房间、计时收费房间、门票房间等,进行美颜设置,开播位置定位等; 6.美颜滤镜:全局美颜功能,美肤美形,可实现十级美颜调节,20余款不同风格的滤镜素材,支持50余款动态贴纸素材,可用于直播间和小视频拍摄编辑; 7.送礼打赏:在线送礼打赏,礼物可实现多重连发,支持豪华礼物定制,新增热门礼物、守护礼物、幸运礼物等礼物类别; 8.弹幕私信:直播间内可发送弹幕消息,也可以给主播和其他联系人发送私信进行聊天; 9.排行榜单:可在排行榜单查看主播收益排行及用户打赏排行信息; 10.用户中心:可进行用户基础信息设置,查看各类系统功能选项菜单; 特色功能 1.互动连麦:主播开播后,用户可向主播发起互动连麦请求,主播接受请求后进行连麦互动; 2.互动游戏:主播可在直播间内开启互动小游戏,丰富互动直播玩法,系统支持5款小游戏; 3.创建家族:上传认证资料即可创建家族,也可加入已有的家族,分享家族主播礼物收益; 4.在线商城:可充值购买VIP会员,购买靓号及坐骑,用户进入直播间时会有进场特效; 5.三级分销:单独的直播间分享邀请码和推广二维码图片,分享直播获取礼物分成收益; 7.私密直播:支持普通直播房间、密码房间、门票房间、计时收费房间等私密直播间类型; 8.连麦PK:主播可搜索当前在线主播,发起连麦礼物PK邀请,对方接受邀请后可进行连麦PK; 9.主播守护:直播间可充值守护主播,守护时长后台可进行自定义设置,开通守护会有守护礼物; 10.直播间红包:主播和用户都可以在直播间内发送红包,红包分为普通红包和随机红包; 11.代理推广:单独的代理商和推广员管理后台,可设置三者之间的佣金分成方式; 12.引导图功能:后台可上传图片或视频内容作为引导图,视频内容可跳过,点击显示广告内容; 13.数据统计:后台首页显示平台运营数据内容,需申请接入三方数据统计服务; 码包括;安卓app+苹果app带后台 pc后端管理:thinkphp 安卓:java原生开发 ios:obje-ctive-c开发 码完整,搭建过于复杂,所以站长没有测试 如果有需要的朋友可以自行下载测试 如果自己没有技术的话不建议自己搭建!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值