【Android】Jetpack全组件实战开发短视频应用App(二十一)

前言

项目地址
这一篇我们主要是介绍下CameraX的使用

介绍

官方地址
官方Demo
这里我们最好是通过官方地址来了解下Camera
【Android】Jetpack全组件实战开发短视频应用App(二十一)_第1张图片
同时Google也为我们写了一个CameraX的Demo供我们参考,下面我们就简单介绍下CameraX的使用

使用

导入依赖

 	def camerax_core_version = "1.0.0-beta03"
    def camerax_version = "1.0.0-alpha10"
    api "androidx.camera:camera-core:${camerax_core_version}"
    api "androidx.camera:camera-camera2:${camerax_core_version}"
// If you want to use the CameraX View class
    api "androidx.camera:camera-view:${camerax_version}"
// If you want to use the CameraX Extensions library
    api "androidx.camera:camera-extensions:${camerax_version}"
// If you want to use the CameraX Lifecycle library
    api "androidx.camera:camera-lifecycle:${camerax_version}"

同时需要JDK1.8

compileOptions {
    sourceCompatibility JavaVersion.VERSION_1_8
    targetCompatibility JavaVersion.VERSION_1_8
}

权限申请

<uses-permission android:name="android.permission.CAMERA" />

这里我们还需要动态申请拍照权限

    private static final String[] PERMISSIONS = new String[]{Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.RECORD_AUDIO};
    
    //onCreate方法中调用
	ActivityCompat.requestPermissions(this, PERMISSIONS, PERMISSION_CODE);

	//权限回调
	    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if (requestCode == PERMISSION_CODE) {
            deniedPermission.clear();
            for (int i = 0; i < permissions.length; i++) {
                String permission = permissions[i];
                int result = grantResults[i];
                if (result != PackageManager.PERMISSION_GRANTED) {
                    deniedPermission.add(permission);
                }
            }

            if (deniedPermission.isEmpty()) {
                bindCameraX();
            } else {
                new AlertDialog.Builder(this)
                        .setMessage(getString(R.string.capture_permission_message))
                        .setNegativeButton(getString(R.string.capture_permission_no), new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialog, int which) {
                                dialog.dismiss();
                                CaptureActivity.this.finish();
                            }
                        })
                        .setPositiveButton(getString(R.string.capture_permission_ok), new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialog, int which) {
                                String[] denied = new String[deniedPermission.size()];
                                ActivityCompat.requestPermissions(CaptureActivity.this, deniedPermission.toArray(denied), PERMISSION_CODE);
                            }
                        }).create().show();
            }
        }
    }

启动相机

	 private void bindCameraX() {

        executor = ContextCompat.getMainExecutor(this);

        cameraProviderFuture = ProcessCameraProvider.getInstance(this);
        cameraProviderFuture.addListener(() -> {
            try {
                cameraProvider = cameraProviderFuture.get();
                bindPreview();
            } catch (ExecutionException | InterruptedException e) {
                // No errors need to be handled for this Future.
                // This should never be reached.
            }
        }, executor);

    }

绑定PreView

 @SuppressLint("RestrictedApi")
    private void bindPreview() {
        cameraProvider.unbindAll();

        cameraSelector = new CameraSelector.Builder()
                .requireLensFacing(CameraSelector.LENS_FACING_BACK)
                .build();

        //查询一下当前要使用的设备摄像头(比如后置摄像头)是否存在
        boolean hasAvailableCameraId = false;
        hasAvailableCameraId = CameraX.hasCamera(cameraSelector);

        if (!hasAvailableCameraId) {
            showErrorToast("无可用的设备cameraId!,请检查设备的相机是否被占用");
            finish();
            return;
        }
//
        //查询一下是否存在可用的cameraId.形式如:后置:"0",前置:"1"
        String cameraIdForLensFacing = null;
        try {
            cameraIdForLensFacing = CameraX.getCameraFactory().cameraIdForLensFacing(CameraSelector.LENS_FACING_BACK);
        } catch (CameraInfoUnavailableException e) {
            e.printStackTrace();
        }
        if (TextUtils.isEmpty(cameraIdForLensFacing)) {
            showErrorToast("无可用的设备cameraId!,请检查设备的相机是否被占用");
            finish();
            return;
        }

        preview = new Preview.Builder()
                .setCameraSelector(cameraSelector) //前后摄像头
                .setTargetAspectRatio(AspectRatio.RATIO_16_9) //宽高比
                .setTargetRotation(rotation) //旋转角度
                //.setTargetResolution(resolution) //分辨率
                .build();


        imageCapture = new ImageCapture.Builder()
                .setCameraSelector(cameraSelector)
                .setTargetAspectRatio(AspectRatio.RATIO_16_9)
                .setTargetRotation(rotation)
                //.setTargetResolution(resolution)
                .build();

        videoCapture = new VideoCaptureConfig.Builder()
                .setCameraSelector(cameraSelector)
                .setTargetAspectRatio(AspectRatio.RATIO_16_9)
                .setTargetRotation(rotation)
                //.setTargetResolution(resolution)
                //视频帧率
                .setVideoFrameRate(25)
                //bit率
                .setBitRate(10440).build();

        //Caused by: java.lang.IllegalArgumentException: No supported surface combination is found for camera device - Id : 0.  May be attempting to bind too many use cases.
        //cameraSelector与videoCapture不能同时绑定
//        Camera camera = cameraProvider.bindToLifecycle(this, cameraSelector, imageCapture, videoCapture, preview);

        if (takingPicture) {
            camera = cameraProvider.bindToLifecycle(this, cameraSelector, imageCapture, preview);
        } else {
            camera = cameraProvider.bindToLifecycle(this, cameraSelector, videoCapture, preview);

        }
        CameraInfo cameraInfo = camera.getCameraInfo();
        preview.setSurfaceProvider(mBinding.previewView.createSurfaceProvider(cameraInfo));

    }

注意:这里有的手机会报错,至少我的小米手机是这样的

Caused by: java.lang.IllegalArgumentException: No supported surface combination is found for camera device - Id : 0.  May be attempting to bind too many use cases.

这个暂时还没解决,先记录下来

拍照

  				ImageCapture.OutputFileOptions outputFileOptions =
                        new ImageCapture.OutputFileOptions.Builder(file).build();
                imageCapture.takePicture(outputFileOptions,executor, new ImageCapture.OnImageSavedCallback() {
                    @Override
                    public void onImageSaved(@NonNull ImageCapture.OutputFileResults outputFileResults) {
                        onFileSaved(file);
                    }

                    @Override
                    public void onError(@NonNull ImageCaptureException exception) {
                        showErrorToast(Objects.requireNonNull(exception.getMessage()));
                    }

                });

录制视频

videoCapture.startRecording(file, executor, new VideoCapture.OnVideoSavedCallback() {
                    @Override
                    public void onVideoSaved(@NonNull File file) {
                        onFileSaved(file);
                    }

                    @Override
                    public void onError(int videoCaptureError, @NonNull String message, @Nullable Throwable cause) {
                        showErrorToast(message);
                    }
                });

文件保存

  private void onFileSaved(File file) {
        outputFilePath = file.getAbsolutePath();
        String mimeType = takingPicture ? "image/jpeg" : "video/mp4";
        MediaScannerConnection.scanFile(this, new String[]{outputFilePath}, new String[]{mimeType}, null);
        PreviewActivity.startActivityForResult(this, outputFilePath, !takingPicture, "完成");
    }

预览

我们拍完照或者录制完视频我们需要预览下,图片我们使用PhotoView+Glide实现,视频我们使用Exoplayer实现

我们先看下布局
【Android】Jetpack全组件实战开发短视频应用App(二十一)_第2张图片


<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <data>

    data>

    <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@color/black"
        android:orientation="vertical">

        <com.google.android.exoplayer2.ui.PlayerView
            android:id="@+id/player_view"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:visibility="gone"
            app:buffered_color="@color/color_theme"
            app:resize_mode="fixed_width"
            app:show_buffering="when_playing"
            app:surface_type="texture_view"
            app:use_controller="false">com.google.android.exoplayer2.ui.PlayerView>


        <com.github.chrisbanes.photoview.PhotoView
            android:id="@+id/photo_view"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_gravity="center"
            android:layout_marginTop="90dp"
            android:layout_marginBottom="@dimen/dp_60"
            android:scaleType="fitCenter"
            android:visibility="gone">com.github.chrisbanes.photoview.PhotoView>

        <androidx.appcompat.widget.AppCompatImageView
            android:id="@+id/action_close"
            android:layout_width="20dp"
            android:layout_height="@dimen/dimen_20"
            android:layout_marginLeft="@dimen/dp_16"
            android:layout_marginTop="@dimen/dp_16"
            app:srcCompat="@drawable/icon_close"
            app:tint="@color/color_white">androidx.appcompat.widget.AppCompatImageView>

        <com.google.android.material.button.MaterialButton
            android:id="@+id/action_ok"
            android:layout_width="@dimen/dp_60"
            android:layout_height="@dimen/dp_30"
            android:layout_gravity="right|top"
            android:layout_marginTop="@dimen/dp_16"
            android:layout_marginRight="@dimen/dp_16"
            android:gravity="center"
            android:text="@string/preview_ok"
            app:backgroundTint="@color/color_theme"
            app:cornerRadius="@dimen/dp_5">com.google.android.material.button.MaterialButton>
    FrameLayout>
layout>

点击左上角退出,点击完成回调出去.如果是视频的话中间有个播放按钮,点击使用Exoplayer播放

 private void previewImage(String previewUrl) {
        mPreviewBinding.photoView.setVisibility(View.VISIBLE);
        Glide.with(this).load(previewUrl).into(mPreviewBinding.photoView);
    }

    private void previewVideo(String previewUrl) {
        mPreviewBinding.playerView.setVisibility(View.VISIBLE);
        player = ExoPlayerFactory.newSimpleInstance(this, new DefaultRenderersFactory(this), new DefaultTrackSelector(), new DefaultLoadControl());

        Uri uri = null;
        File file = new File(previewUrl);
        if (file.exists()) {
            DataSpec dataSpec = new DataSpec(Uri.fromFile(file));
            FileDataSource fileDataSource = new FileDataSource();
            try {
                fileDataSource.open(dataSpec);
                uri = fileDataSource.getUri();
            } catch (FileDataSource.FileDataSourceException e) {
                e.printStackTrace();
            }
        } else {
            uri = Uri.parse(previewUrl);
        }

        ProgressiveMediaSource.Factory factory = new ProgressiveMediaSource.Factory(new DefaultDataSourceFactory(this, Util.getUserAgent(this, getPackageName())));
        ProgressiveMediaSource mediaSource = factory.createMediaSource(uri);
        player.prepare(mediaSource);
        player.setPlayWhenReady(true);
        mPreviewBinding.playerView.setPlayer(player);
    }

主要就是这两个方法,具体可以去项目中看PreviewActivity的源码,这样的话我们CameraX相关的使用就介绍完毕了,我们下一篇将自定义一个录制的View,完成拍照和录制视频

你可能感兴趣的:(Jetpack)