Услуги Записи Для Андроид Камеры


Я создал сервис, который записывает камера.

Целью данной услуги является в основном для записи видео, в то время как приложение находится в фоновом режиме.

public class RecordingService extends LifecycleService {


    public static final String ACTION_RETURN_TO_ACTIVITY = "ACTION_RETURN_TO_ACTIVITY";
    public static final String ACTION_TOGGLE_RECORD = "ACTION_TOGGLE_RECORD";
    public static final String ACTION_TOGGLE_SOUND = "ACTION_TOGGLE_SOUND";
    public static final String ACTION_TOGGLE_SAVE = "ACTION_TOGGLE_SAVE";
    public static final String ACTION_KILL_SERVICE = "ACTION_KILL_SERVICE";
    public static final String ACTION_FINISH_ACTIVITY = "ACTION_FINISH_ACTIVITY";
    public static final String NO_ACTION = "NO_ACTION";
    public static final String RECORD_AFTER_START = "recordAfterStart";
    private static final int REQUEST_CODE = 341;
    private static final int SENSOR_ORIENTATION_DEFAULT_DEGREES = 90;
    private static final int SENSOR_ORIENTATION_INVERSE_DEGREES = 270;
    private static final SparseIntArray DEFAULT_ORIENTATIONS = new SparseIntArray();
    private static final SparseIntArray INVERSE_ORIENTATIONS = new SparseIntArray();
    private static final String TAG = "Camera2VideoFragment";
    private static boolean hasBeenInitialized = false;

    static {
        DEFAULT_ORIENTATIONS.append(Surface.ROTATION_0, 90);
        DEFAULT_ORIENTATIONS.append(Surface.ROTATION_90, 0);
        DEFAULT_ORIENTATIONS.append(Surface.ROTATION_180, 270);
        DEFAULT_ORIENTATIONS.append(Surface.ROTATION_270, 180);
    }

    static {
        INVERSE_ORIENTATIONS.append(Surface.ROTATION_0, 270);
        INVERSE_ORIENTATIONS.append(Surface.ROTATION_90, 180);
        INVERSE_ORIENTATIONS.append(Surface.ROTATION_180, 90);
        INVERSE_ORIENTATIONS.append(Surface.ROTATION_270, 0);
    }

    // This is the object that receives interactions from clients.
    private final IBinder binder = new RecordingService.RecordBinder();
    @Inject
    IRecordRepository recordRepository;
    @Inject
    IPreferencesRepository preferencesRepository;
    private Notification notif;
    private Handler timerHandler;
    private Runnable timerRunnable;
    private String nextVideoFilePath;
    private RemoteViews contentView;
    private IRecordingInitialization recordingInitialization;
    private RecordViewModel recordViewModel;
    private boolean shouldSaveVideo = false;
    private Surface previewSurface;
    private int oldWidth;
    private int oldHeight;
    private ArrayList<RecordLocalisationEntity> savedLocalisation;
    private MutableLiveData<Boolean> isRecording;
    private boolean recordOnCameraOpening = false;
    private MutableLiveData<String> addressLiveData;
    /**
     * An {@link AutoFitTextureView} for camera preview.
     */
    private AutoFitTextureView mTextureView;
    /**
     * A reference to the opened {@link android.hardware.camera2.CameraDevice}.
     */
    private CameraDevice mCameraDevice;
    /**
     * A reference to the current {@link android.hardware.camera2.CameraCaptureSession} for
     * preview.
     */
    private CameraCaptureSession mPreviewSession;
    /**
     * The {@link android.util.Size} of camera preview.
     */
    private Size mPreviewSize;
    /**
     * The {@link android.util.Size} of video recording.
     */
    private Size mVideoSize;
    /**
     * MediaRecorder
     */
    private MediaRecorder mMediaRecorder;
    /**
     * An additional thread for running tasks that shouldn't block the UI.
     */
    private HandlerThread mBackgroundThread;
    /**
     * A {@link Handler} for running tasks in the background.
     */
    private Handler mBackgroundHandler;
    /**
     * A {@link Semaphore} to prevent the app from exiting before closing the camera.
     */
    private Semaphore mCameraOpenCloseLock = new Semaphore(1);
    private Integer mSensorOrientation;
    private CaptureRequest.Builder mPreviewBuilder;
    private Geocoder geocoder;
    private FusedLocationProviderClient locationProviderClient;
    private Task<LocationSettingsResponse> locationSettingsResponseTask;
    private long startingTime;
    private LocationCallback locationCallback = new LocationCallback() {
        @SuppressLint("MissingPermission")
        @Override
        public void onLocationResult(LocationResult locationResult) {

            Log.d(TAG,"onLocationResult");
            for (Location location : locationResult.getLocations()) {
                try {
                    Address addressObject = geocoder.getFromLocation(location.getLatitude(), location.getLongitude(), 1).get(0);
                    String numeroRue = addressObject.getSubThoroughfare();
                    numeroRue = (numeroRue == null) ? "" : numeroRue;
                    String address = numeroRue + " " + addressObject.getThoroughfare() + ", \n" + addressObject.getPostalCode() + " " + addressObject.getLocality();
                    addressLiveData.setValue(address);
                    if (isRecording.getValue()) {
                        RecordLocalisationEntity entity = new RecordLocalisationEntity();
                        entity.setAddress(address);
                        entity.setTime(SystemClock.elapsedRealtime() - startingTime);
                        savedLocalisation.add(entity);
                    }

                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    };
    /**
     * {@link CameraDevice.StateCallback} is called when {@link CameraDevice} changes its status.
     */
    private CameraDevice.StateCallback mStateCallback = new CameraDevice.StateCallback() {

        @Override
        public void onOpened(@NonNull CameraDevice cameraDevice) {
            Log.d(TAG,"CameraDevice.StateCallback onOpened");
            mCameraDevice = cameraDevice;
            cameraOpening = false;
            mCameraOpenCloseLock.release();
            Log.d(TAG,"statecallback onopened semaphore released");
            if (null != mTextureView) {
                configureTransform(mTextureView.getWidth(), mTextureView.getHeight());
            }
            if(recordingInitialization != null) {
                recordingInitialization.onComplete();
                recordingInitialization = null;
            }
            if(recordOnCameraOpening){
                startRecordingVideo();
                recordOnCameraOpening =false;
            }
            else{
                startPreview();
            }
        }

        @Override
        public void onClosed(@NonNull CameraDevice camera) {
            Log.d(TAG,"CameraDevice.StateCallback onClosed");
            super.onClosed(camera);
            mCameraOpenCloseLock.release();
            Log.d(TAG,"statecallback onclosed semaphore released");
            mCameraDevice = null;
        }

        @Override
        public void onDisconnected(@NonNull CameraDevice cameraDevice) {
            Log.d(TAG,"CameraDevice.StateCallback onDisconnected");
            mCameraOpenCloseLock.release();
            Log.d(TAG,"statecallback ondisconnected semaphore released");
            cameraDevice.close();
            mCameraDevice = null;
            if(mTextureView != null) {
                oldWidth = mTextureView.getWidth();
                oldHeight = mTextureView.getHeight();
                openCamera(mTextureView.getWidth(), mTextureView.getHeight(), recordViewModel.getCurrentCamera().getValue());
            }
            else{
                openCamera(oldWidth, oldHeight, recordViewModel.getCurrentCamera().getValue());
            }

        }

        @Override
        public void onError(@NonNull CameraDevice cameraDevice, int error) {
            Log.d(TAG,"CameraDevice.StateCallback onError");
            mCameraOpenCloseLock.release();
            Log.d(TAG,"semaphore released");
            cameraDevice.close();
            mCameraDevice = null;
            if(error == ERROR_CAMERA_DEVICE){
                if(mTextureView != null) {
                    oldWidth = mTextureView.getWidth();
                    oldHeight = mTextureView.getHeight();
                    openCamera(mTextureView.getWidth(), mTextureView.getHeight(), recordViewModel.getCurrentCamera().getValue());
                }
                else{
                    openCamera(oldWidth, oldHeight, recordViewModel.getCurrentCamera().getValue());
                }
            }
        }

    };
    private Surface recorderSurface;
    private SurfaceTexture previewSurfaceTexture;
    private boolean cameraOpening =false;


    public RecordingService() {
    }

    /**
     * In this sample, we choose a video size with 3x4 aspect ratio. Also, we don't use sizes
     * larger than 1080p, since MediaRecorder cannot handle such a high-resolution video.
     *
     * @param choices The list of available sizes
     * @return The video size
     */
    private static Size chooseVideoSize(Size[] choices, Size aspectRatio) {
        Log.d(TAG,"chooseVideoSize");
        for (Size size : choices) {
            if (size.getWidth() == size.getHeight() * aspectRatio.getHeight() / aspectRatio.getWidth() && size.getWidth() <= 1080) {
                return size;
            }
            if (size.getWidth() == size.getHeight() * aspectRatio.getWidth() / aspectRatio.getHeight() && size.getWidth() <= 1080) {
                return size;
            }
        }
        return choices[0];
    }

    /**
     * Given {@code choices} of {@code Size}s supported by a camera, chooses the smallest one whose
     * width and height are at least as large as the respective requested values, and whose aspect
     * ratio matches with the specified value.
     *
     * @param choices     The list of sizes that the camera supports for the intended output class
     * @param width       The minimum desired width
     * @param height      The minimum desired height
     * @param aspectRatio The aspect ratio
     * @return The optimal {@code Size}, or an arbitrary one if none were big enough
     */
    private static Size chooseOptimalSize(Size[] choices, int width, int height, Size aspectRatio) {
        Log.d(TAG,"chooseOptimalSize");
        // Collect the supported resolutions that are at least as big as the preview Surface
        // Collect the supported resolutions that are at least as big as the preview Surface
        List<Size> bigEnough = new ArrayList<>();
        int w = aspectRatio.getWidth();
        int h = aspectRatio.getHeight();
        for (Size option : choices) {
            if (option.getHeight()== width * h / w && option.getWidth() >= width && option.getHeight() >= height) {
                bigEnough.add(option);
            }
        }

        // Pick the smallest of those, assuming we found any
        if (bigEnough.size() > 0) {
            return Collections.min(bigEnough, new CompareSizesByArea());
        } else {
            return aspectRatio;
        }
    }

    public MutableLiveData<String> getAddressLiveData() {
        return addressLiveData;
    }

    public void setCurrentCamera(int currentCamera) {
        Log.d(TAG,"setCurrentCamera");
        if(this.getIsRecordingVideo().getValue() || mTextureView == null){
            return;
        }

        this.closeCamera();
        if(mTextureView != null) {
            oldWidth = mTextureView.getWidth();
            oldHeight = mTextureView.getHeight();
            openCamera(mTextureView.getWidth(), mTextureView.getHeight(), recordViewModel.getCurrentCamera().getValue());
        }
        else{
            openCamera(oldWidth, oldHeight, recordViewModel.getCurrentCamera().getValue());
        }
    }

    public void toggleSound() {
        Log.d(TAG,"toggleSound");
        if(this.getIsRecordingVideo().getValue()){
            return;
        }
        recordViewModel.toggleSound();
        AudioManager myAudioManager = (AudioManager)getSystemService(Context.AUDIO_SERVICE);

        myAudioManager.setMode(AudioManager.MODE_NORMAL);
        if(myAudioManager.isMicrophoneMute() != recordViewModel.getIsSoundEnable().getValue()) {
            myAudioManager.setMicrophoneMute(recordViewModel.getIsSoundEnable().getValue());
        }
    }

    public void launchNotif() {
        Log.d(TAG,"launchNotif");
        Intent toggleRecordingIntent = new Intent(getApplicationContext(), RecordingService.class);
        toggleRecordingIntent.setAction(ACTION_TOGGLE_RECORD);
        PendingIntent piToggleRecording = PendingIntent.getService(getApplicationContext(), REQUEST_CODE, toggleRecordingIntent, 0);

        Intent toggleSaveCurrentRecordIntent = new Intent(getApplicationContext(), RecordingService.class);
        toggleSaveCurrentRecordIntent.setAction(ACTION_TOGGLE_SAVE);
        PendingIntent piToggleSaveCurrentRecordIntent = PendingIntent.getService(getApplicationContext(), REQUEST_CODE, toggleSaveCurrentRecordIntent, 0);


        contentView = new RemoteViews(getPackageName(),R.layout.notification_layout);
        contentView.setOnClickPendingIntent(R.id.toggle_recording,piToggleRecording);
        contentView.setOnClickPendingIntent(R.id.toggle_save_current_record,piToggleSaveCurrentRecordIntent);

        notif = getNotificationBuilder()
                .build();
        final NotificationManager notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
        notificationManager.notify(01, notif);
    }

    public NotificationCompat.Builder getNotificationBuilder() {
        Log.d(TAG,"getNotificationBuilder");
        Intent returnToActivityIntent = new Intent(getApplicationContext(), RecordingService.class);
        returnToActivityIntent.setAction(ACTION_RETURN_TO_ACTIVITY);
        PendingIntent piReturnToActivity = PendingIntent.getService(getApplicationContext(), REQUEST_CODE, returnToActivityIntent, 0);

        return new NotificationCompat.Builder(this, DEFAULT_CHANNEL_ID)
                .setSmallIcon(R.drawable.ic_notification_icon)
                .setContent(contentView)
                .setPriority(Notification.PRIORITY_MAX)
                .setCustomBigContentView(contentView)
                .setContentIntent(piReturnToActivity)
                .setUsesChronometer(true);
    }

    public void onSurfaceTextureDestroyed() {
        Timber.e("onSurfaceTextureDestroyed");
        this.mTextureView = null;
        this.closePreviewSession();
        try {

            mCameraOpenCloseLock.acquire();
            Log.d(TAG,"semaphore acquire");
            closeCamera();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        finally {
            mCameraOpenCloseLock.release();
            Log.d(TAG,"onsurfacetexture semaphore released");
        }

    }

    public void reloadCamera() {

        Log.d(TAG,"reloadCamera");
        boolean shouldRecordAfterReload = false;
        if(this.getIsRecordingVideo().getValue()){
            shouldRecordAfterReload = true;
            this.toggleRecording();
        }
        closeCamera();

        this.recordOnCameraOpening = shouldRecordAfterReload;

        if(mTextureView != null) {
            oldWidth = mTextureView.getWidth();
            oldHeight = mTextureView.getHeight();
            openCamera(mTextureView.getWidth(), mTextureView.getHeight(), recordViewModel.getCurrentCamera().getValue());

        }
        else{
            openCamera(oldWidth, oldHeight, recordViewModel.getCurrentCamera().getValue());
        }


    }

    public MutableLiveData<Boolean> getIsRecordingVideo() {
        return isRecording;
    }

    public void beforeSendToBackground() {
        Log.d(TAG,"beforeSendToBackground");
        closeCamera();

    }

    public void afterSendToBackground() {
        Log.d(TAG,"afterSendToBackground");
        this.recordOnCameraOpening = true;
        if(mTextureView != null) {
            oldWidth = mTextureView.getWidth();
            oldHeight = mTextureView.getHeight();
            openCamera(mTextureView.getWidth(), mTextureView.getHeight(), recordViewModel.getCurrentCamera().getValue());
        }
        else{
            openCamera(oldWidth, oldHeight, recordViewModel.getCurrentCamera().getValue());
        }
    }

    @SuppressLint("MissingPermission")
    @Override
    public void onCreate() {

        Log.d(TAG,"onCreate");
        super.onCreate();

        addressLiveData = new MutableLiveData<>();
        addressLiveData.setValue(getApplication().getResources().getString(R.string.disabled_gps));

        isRecording = new MutableLiveData<>();
        isRecording.setValue(false);
        geocoder = new Geocoder(getApplication(), Locale.getDefault());
        locationProviderClient = LocationServices.getFusedLocationProviderClient(getApplication());
        initLocationTask();
        LocationManager lm = (LocationManager) getApplication().getSystemService(Context.LOCATION_SERVICE);
        lm.addGpsStatusListener(event -> {
            switch (event) {
                case GPS_EVENT_STARTED:
                    initLocationTask();
                    break;
                case GPS_EVENT_STOPPED:
                    locationSettingsResponseTask = null;
                    locationProviderClient.removeLocationUpdates(locationCallback);
                    addressLiveData.setValue(getApplication().getResources().getString(R.string.disabled_gps));
                    break;
            }
        });
    }

    @SuppressLint("MissingPermission")
    private void initLocationTask() {

        Log.d(TAG,"initLocationTask");
        LocationRequest locationRequest = createLocationRequest();
        LocationSettingsRequest.Builder builder = new LocationSettingsRequest.Builder()
                .addLocationRequest(locationRequest);
        SettingsClient client = LocationServices.getSettingsClient(getApplication());
        locationSettingsResponseTask = client.checkLocationSettings(builder.build());


        locationSettingsResponseTask.addOnSuccessListener(locationSettingsResponse -> {
            locationProviderClient.requestLocationUpdates(locationRequest,
                    locationCallback,
                    null /* Looper */);
        });
    }

    private LocationRequest createLocationRequest() {

        Log.d(TAG,"createLocationRequest");
        LocationRequest mLocationRequest = new LocationRequest();
        mLocationRequest.setInterval(10000);
        mLocationRequest.setFastestInterval(5000);
        mLocationRequest.setMaxWaitTime(10000);
        mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
        return mLocationRequest;
    }

    @Override
    public IBinder onBind(Intent intent) {
        Log.d(TAG,"onBind");
        super.onBind(intent);
        AndroidInjection.inject(this);
        return binder;
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.d(TAG,"onStartCommand");
        super.onStartCommand(intent,flags,startId);
        if(intent == null || intent.getAction() == null) return START_STICKY;
        switch (intent.getAction()){
            case ACTION_RETURN_TO_ACTIVITY:
                if(this.recordViewModel != null && this.getIsRecordingVideo() != null && this.getIsRecordingVideo().getValue()){
                    this.toggleRecording();
                }
                beforeSendToBackground();
                Intent goToActivityIntent = new Intent(getApplicationContext(), MainActivity.class);
                goToActivityIntent.addFlags(FLAG_ACTIVITY_NEW_TASK);
                goToActivityIntent.addCategory(CATEGORY_LAUNCHER);
                startActivity(goToActivityIntent);

                break;
            case ACTION_TOGGLE_RECORD:
                this.toggleRecording();
                Toast.makeText(this, R.string.video_saved, Toast.LENGTH_SHORT).show();
                break;
            case ACTION_TOGGLE_SAVE:
                this.toggleSaveRecord();
                break;
            case ACTION_TOGGLE_SOUND:
                this.recordViewModel.toggleSound();
                this.toggleSound();
                break;
            case ACTION_KILL_SERVICE:
                if(!isRecording.getValue()) {
                    this.onUnbind(intent);
                }
                break;
            case NO_ACTION:
            default:
                break;
        }
        return START_STICKY;
    }

    private void toggleSaveRecord() {
        Log.d(TAG,"toggleSaveRecord");
        shouldSaveVideo = !shouldSaveVideo;
        if(shouldSaveVideo){
            Toast.makeText(this, R.string.video_starred, Toast.LENGTH_SHORT).show();
        }
        else{
            Toast.makeText(this, R.string.video_not_starred, Toast.LENGTH_SHORT).show();
        }
        this.updateNotification();
    }


    public void initialize(AutoFitTextureView textureView, RecordViewModel recordViewModel, IRecordingInitialization recordingInitialization){
        Log.d(TAG,"initialize");
        this.mTextureView = textureView;
        timerHandler = new Handler();

        savedLocalisation = new ArrayList<>();
        this.recordViewModel = recordViewModel;
        this.launchNotif();
        this.startForeground(01,notif);
        startBackgroundThread();
        int currentCamera = recordViewModel.getCurrentCamera().getValue();
        oldHeight = mTextureView.getHeight();
        oldWidth = mTextureView.getWidth();
        openCamera(mTextureView.getWidth(), mTextureView.getHeight(), currentCamera);

        if(this.recordViewModel.getAutoRecordingValue() && !hasBeenInitialized ){
            hasBeenInitialized = true;
            this.recordingInitialization = recordingInitialization;
        }
    }

    @Override
    public boolean onUnbind(Intent intent) {
        Log.d(TAG,"onUnbind");
        this.stopForeground(true);
        final NotificationManager notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
        notificationManager.cancel(01);
        locationProviderClient.removeLocationUpdates(locationCallback);
        stopBackgroundThread();
        return true;
    }

    public void toggleRecording() {
        Log.d(TAG,"toggleRecording");
        if (this.getIsRecordingVideo().getValue()) {
            stopRecordingVideo();
        } else {
            startRecordingVideo();
        }
        updateNotification();
    }

    public long getStartingTime() {
        Log.d(TAG,"getStartingTime");
        return startingTime;
    }

    private void updateNotification() {
        Log.d(TAG,"updateNotification");
        String notificationButton = this.getIsRecordingVideo().getValue()? getResources().getString(R.string.stop):getResources().getString(R.string.start);
        contentView.setTextViewText(R.id.toggle_recording,notificationButton);

        int starResourceId = shouldSaveVideo? R.drawable.ic_star_black_24dp:R.drawable.ic_star_border_black_24dp;
        contentView.setImageViewResource(R.id.toggle_save_current_record, starResourceId);

        String notificationTitle = this.getIsRecordingVideo().getValue()? getResources().getString(R.string.notification_recording):getResources().getString(R.string.notification_stopped);
        contentView.setTextViewText(R.id.title,notificationTitle);

        contentView.setViewVisibility(R.id.touch_here, this.getIsRecordingVideo().getValue()?View.GONE:View.VISIBLE);

        String duration = new SimpleDateFormat("mm:ss").format(new Date(preferencesRepository.getVideoDuration().getValue()));
        contentView.setChronometer(R.id.chronometer,this.getStartingTime(),"%s / "+duration,true);

        if(this.getIsRecordingVideo().getValue()){
            this.contentView.setViewVisibility(R.id.toggle_save_current_record, View.VISIBLE);
            this.contentView.setViewVisibility(R.id.toggle_recording, View.VISIBLE);
            this.contentView.setViewVisibility(R.id.chronometer, View.VISIBLE);
        }
        else{
            this.contentView.setViewVisibility(R.id.toggle_save_current_record, View.INVISIBLE);
            this.contentView.setViewVisibility(R.id.toggle_recording, View.INVISIBLE);
            this.contentView.setViewVisibility(R.id.chronometer, View.INVISIBLE);
        }

        final NotificationManager notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
        notificationManager.notify(01, notif);

    }

    /**
     * Starts a background thread and its {@link Handler}.
     */
    private void startBackgroundThread() {
        Log.d(TAG,"startBackgroundThread");
        mBackgroundThread = new HandlerThread("CameraBackground");
        mBackgroundThread.start();
        mBackgroundHandler = new Handler(mBackgroundThread.getLooper());
    }

    /**
     * Stops the background thread and its {@link Handler}.
     */
    private void stopBackgroundThread() {
        Log.d(TAG,"stopBackgroundThread");
        try {
            mBackgroundThread.quitSafely();
            mBackgroundThread.join();
            mBackgroundThread = null;
            mBackgroundHandler = null;
        } catch (InterruptedException | NullPointerException e) {
            e.printStackTrace();
        }
    }

    /**
     * Tries to open a {@link CameraDevice}. The result is listened by `mStateCallback`.
     */
    @SuppressWarnings("MissingPermission")
    private void openCamera(int width, int height, int currentCamera) {
        if(cameraOpening) return;
        if(mCameraDevice != null) return;

        Log.d(TAG,"openCamera");
        cameraOpening = true;
        CameraManager manager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
        try {
            Log.d(TAG, "tryAcquire");
            if (!mCameraOpenCloseLock.tryAcquire(2500, TimeUnit.MILLISECONDS)) {
                throw new RuntimeException("Time out waiting to lock camera opening.");
            }
            Log.d(TAG,"semaphore locked");
            String cameraId;
            if(manager.getCameraIdList().length > 1 ){
                cameraId = manager.getCameraIdList()[currentCamera];
            }
            else {
                cameraId = manager.getCameraIdList()[0];
            }

            // Choose the sizes for camera preview and video recording
            CameraCharacteristics characteristics = manager.getCameraCharacteristics(cameraId);
            StreamConfigurationMap map = characteristics
                    .get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
            mSensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);
            if (map == null) {
                throw new RuntimeException("Cannot get available preview/video sizes");
            }

            Size aspectRatio = new Size(width,height);
            mVideoSize = chooseVideoSize(map.getOutputSizes(MediaRecorder.class), aspectRatio);
            mPreviewSize = chooseOptimalSize(map.getOutputSizes(SurfaceTexture.class),
                    width, height, mVideoSize);

            int orientation = getResources().getConfiguration().orientation;
            if(mTextureView != null) {
                if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
                    mTextureView.setAspectRatio(mPreviewSize.getWidth(), mPreviewSize.getHeight());
                } else {
                    mTextureView.setAspectRatio(mPreviewSize.getHeight(), mPreviewSize.getWidth());
                }
            }
            configureTransform(width, height);
            mMediaRecorder = new MediaRecorder();
            manager.openCamera(cameraId, mStateCallback, null);
        } catch (CameraAccessException | NullPointerException e) {
            Timber.e(e);
            cameraOpening = false;
            mCameraOpenCloseLock.release();
            Log.d(TAG,"open camera semaphore released");
        } catch (InterruptedException e) {
            cameraOpening = false;
            mCameraOpenCloseLock.release();
            Log.d(TAG,"open camera semaphore released");
        }
    }

    private void closeCamera() {
        Log.d(TAG,"closeCamera");
        try {

            if (null != mCameraDevice) {
                mCameraOpenCloseLock.acquire();
                Log.d(TAG,"semaphore acquire");
                mCameraDevice.close();
                mCameraDevice = null;
            }
            closePreviewSession();

        } catch (InterruptedException e) {
            throw new RuntimeException("Interrupted while trying to lock camera closing.");
        } finally {
            mCameraOpenCloseLock.release();
            Log.d(TAG,"close camera semaphore released");
        }
    }

    /**
     * Start the camera preview.
     */
    private void startPreview() {
        Log.d(TAG,"Start Preview");
        if (null == mCameraDevice || mTextureView == null || !mTextureView.isAvailable() || null == mPreviewSize) {
            return;
        }
        try {
            this.mCameraOpenCloseLock.acquire();
            Log.d(TAG,"semaphore acquire");
            closePreviewSession();
            SurfaceTexture texture = mTextureView.getSurfaceTexture();
            assert texture != null;
            texture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());
            mPreviewBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);

            previewSurface = new Surface(texture);
            mPreviewBuilder.addTarget(previewSurface);

            mCameraDevice.createCaptureSession(Collections.singletonList(previewSurface),
                    new CameraCaptureSession.StateCallback() {

                        @Override
                        public void onConfigured(@NonNull CameraCaptureSession session) {
                            mPreviewSession = session;
                            updatePreview();

                        }

                        @Override
                        public void onConfigureFailed(@NonNull CameraCaptureSession session) {
                            Timber.e("Exception while creating captureSession");
                        }
                    }, mBackgroundHandler);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        finally {
            this.mCameraOpenCloseLock.release();
            Log.d(TAG,"startpreview semaphore released");
        }
    }

    /**
     * Update the camera preview. {@link #startPreview()} needs to be called in advance.
     */
    private void updatePreview() {
        Log.d(TAG,"updatePreview");
        if (null == mCameraDevice) {
            return;
        }
        try {
            setUpCaptureRequestBuilder(mPreviewBuilder);
            HandlerThread thread = new HandlerThread("CameraPreview");
            thread.start();
            mPreviewSession.setRepeatingRequest(mPreviewBuilder.build(), null, mBackgroundHandler);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }

    private void setUpCaptureRequestBuilder(CaptureRequest.Builder builder) {
        builder.set(CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO);
    }

    /**
     * Configures the necessary {@link android.graphics.Matrix} transformation to `mTextureView`.
     * This method should not to be called until the camera preview size is determined in
     * openCamera, or until the size of `mTextureView` is fixed.
     *
     * @param viewWidth  The width of `mTextureView`
     * @param viewHeight The height of `mTextureView`
     */
    public void configureTransform(int viewWidth, int viewHeight) {
        Log.d(TAG,"configureTransform");
        WindowManager window = (WindowManager) getSystemService(Context.WINDOW_SERVICE);

        int rotation = window.getDefaultDisplay().getRotation();
        Matrix matrix = new Matrix();
        RectF viewRect = new RectF(0, 0, viewWidth, viewHeight);
        RectF bufferRect = new RectF(0, 0, mPreviewSize.getHeight(), mPreviewSize.getWidth());
        float centerX = viewRect.centerX();
        float centerY = viewRect.centerY();
        if (Surface.ROTATION_90 == rotation || Surface.ROTATION_270 == rotation) {
            bufferRect.offset(centerX - bufferRect.centerX(), centerY - bufferRect.centerY());
            matrix.setRectToRect(viewRect, bufferRect, Matrix.ScaleToFit.FILL);
            float scale = Math.max(
                    (float) viewHeight / mPreviewSize.getHeight(),
                    (float) viewWidth / mPreviewSize.getWidth());
            matrix.postScale(scale, scale, centerX, centerY);
            matrix.postRotate(90 * (rotation - 2), centerX, centerY);
        }
        if(mTextureView != null) {
            mTextureView.setTransform(matrix);
        }
    }

    private void setUpMediaRecorder() throws IOException {
        Log.d(TAG,"setUpMediaRecorder");
        WindowManager window = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
        if(this.recordViewModel.getIsSoundEnable().getValue()) {
            mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
        }
        mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);
        mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
        mMediaRecorder.setOutputFile(getNextVideoFilePath(this));
        mMediaRecorder.setVideoEncodingBitRate(10000000);
        mMediaRecorder.setVideoFrameRate(30);
        mMediaRecorder.setVideoSize(mVideoSize.getWidth(), mVideoSize.getHeight());
        mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
        if(this.recordViewModel.getIsSoundEnable().getValue()) {
            mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
            mMediaRecorder.setAudioSamplingRate(16000);
        }
        int rotation = window.getDefaultDisplay().getRotation();
        switch (mSensorOrientation) {
            case SENSOR_ORIENTATION_DEFAULT_DEGREES:
                mMediaRecorder.setOrientationHint(DEFAULT_ORIENTATIONS.get(rotation));
                break;
            case SENSOR_ORIENTATION_INVERSE_DEGREES:
                mMediaRecorder.setOrientationHint(INVERSE_ORIENTATIONS.get(rotation));
                break;
        }
        mMediaRecorder.setOnErrorListener((mediaRecorder, i, i1) -> {
            Log.d(TAG, "mMedia Recorder on Error : "+ i);
        });

        mMediaRecorder.setOnInfoListener((mediaRecorder, i, i1) -> {
            Log.d(TAG, "mMedia Recorder on Info : "+ i);
        });
        mMediaRecorder.prepare();
    }

    private void startRecordingVideo() {
        Log.d(TAG,"startRecordingVideo");
        if (null == mCameraDevice || null == mPreviewSize) {
            Log.d(TAG,"startRecordingVideo camera or preview size null");
            if(mTextureView != null) {
                oldWidth = mTextureView.getWidth();
                oldHeight = mTextureView.getHeight();
                openCamera(mTextureView.getWidth(), mTextureView.getHeight(), recordViewModel.getCurrentCamera().getValue());
            }
            else{
                openCamera(oldWidth, oldHeight, recordViewModel.getCurrentCamera().getValue());
            }
            this.recordOnCameraOpening = true;
            return;
        }
        try {
            closePreviewSession();

            setUpMediaRecorder();

            try {
                mCameraOpenCloseLock.acquire();
                Log.d(TAG,"semaphore acquired");
                mPreviewBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_RECORD);
                mCameraOpenCloseLock.release();
                Log.d(TAG,"startrecording video semaphore released");
            } catch (InterruptedException|IllegalArgumentException e) {
                e.printStackTrace();
            }


            List<Surface> surfaces = new ArrayList<>();
            if(mTextureView !=null) {
                previewSurfaceTexture = mTextureView.getSurfaceTexture();
                assert previewSurfaceTexture != null;
                previewSurfaceTexture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());            // Set up Surface for the camera preview
                previewSurface = new Surface(previewSurfaceTexture);
                if(previewSurface.isValid())
                    surfaces.add(previewSurface);
                mPreviewBuilder.addTarget(previewSurface);
            }

            // Set up Surface for the MediaRecorder
            recorderSurface = mMediaRecorder.getSurface();
            if(recorderSurface.isValid() )
                surfaces.add(recorderSurface);
            mPreviewBuilder.addTarget(recorderSurface);

            RecordLocalisationEntity entity = new RecordLocalisationEntity();
            entity.setAddress(this.addressLiveData.getValue());
            entity.setTime(0);
            this.savedLocalisation.add(entity);
            startingTime = SystemClock.elapsedRealtime();
            this.isRecording.setValue(true);
            updateNotification();
            timerRunnable = () -> {
                Long elapsedMillis = SystemClock.elapsedRealtime() - this.getStartingTime();
                if (elapsedMillis > recordViewModel.getVideoDuration().getValue()) {
                    toggleRecording();
                }
                timerHandler.postDelayed(timerRunnable, 2000);
            };
            timerHandler.post(timerRunnable);

            // Start a capture session
            // Once the session starts, we can update the UI and start recording
            mCameraDevice.createCaptureSession(surfaces, new CameraCaptureSession.StateCallback() {
                @Override
                public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) {
                    mPreviewSession = cameraCaptureSession;
                    updatePreview();
                    mMediaRecorder.start();
                }

                @Override
                public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) {
                    Timber.e("Error when creating captureSession");
                }
            }, mBackgroundHandler);
        } catch (CameraAccessException | IOException e) {
            e.printStackTrace();
        }

    }

    private void closePreviewSession() {
        Log.d(TAG,"closePreviewSession");
        if (mPreviewSession != null) {
            mPreviewSession.close();
            mPreviewSession = null;
        }
    }

    private void stopRecordingVideo() {
        Log.d(TAG,"stoprecordingvideo");
        this.timerHandler.removeCallbacks(timerRunnable);

        this.isRecording.setValue(false);
        recordRepository.insert(this.nextVideoFilePath, shouldSaveVideo,this.savedLocalisation);
        this.savedLocalisation.clear();

        this.nextVideoFilePath = null;
        Long elapsedMillis = SystemClock.elapsedRealtime() - startingTime;
        boolean doesVideoReachMaxDuration = false;
        if (preferencesRepository.getVideoDuration().getValue() != null && elapsedMillis > preferencesRepository.getVideoDuration().getValue()) {
            doesVideoReachMaxDuration = true;
        }
        startingTime = SystemClock.elapsedRealtime();
        this.stopForeground(false);
        this.shouldSaveVideo = false;
        this.updateNotification();

        try {
            if(mPreviewSession != null) {
                mPreviewSession.stopRepeating();
                mPreviewSession.abortCaptures();
            }
            startPreview();
            mMediaRecorder.stop();
            mMediaRecorder.reset();
        }
        catch(CameraAccessException | RuntimeException stopException){
            //handle cleanup here
            Timber.e(stopException);
        }

        if(preferencesRepository.getShouldRestartRecording().getValue() && doesVideoReachMaxDuration){
            new Handler().postDelayed(
                    this::toggleRecording, 1000
            );
        }
    }

    public String getNextVideoFilePath(Context context){
        this.nextVideoFilePath = getVideoFilePath(context);
        return this.nextVideoFilePath;
    }

    private String getVideoFilePath(Context context) {
        final File dir = context.getExternalFilesDir(null);
        return (dir == null ? "" : (dir.getAbsolutePath() + "/"))
                + System.currentTimeMillis() + ".mp4";
    }

    /**
     * Compares two {@code Size}s based on their areas.
     */
    static class CompareSizesByArea implements Comparator<Size> {

        @Override
        public int compare(Size lhs, Size rhs) {
            // We cast here to ensure the multiplications won't overflow
            return Long.signum((long) lhs.getWidth() * lhs.getHeight() -
                    (long) rhs.getWidth() * rhs.getHeight());
        }

    }

    /**
     * Class for clients to access.  Because we know this service always
     * runs in the same process as its clients, we don't need to deal with
     * IPC.
     */
    public class RecordBinder extends Binder {
        public RecordingService getService() {
            return RecordingService.this;
        }
    }


}
  1. Я хотел бы иметь больше понимания о том, что я сделал и если я сделал ни одной ошибки.
  2. Я хотел бы знать, если это хорошая практика, использующие API камера2?
  3. Как я могу улучшить его ?


265
1
задан 12 февраля 2018 в 01:02 Источник Поделиться
Комментарии
1 ответ

одно можно сказать точно - этот класс является слишком большой...


  • с учетом класса фабрики для создания обратных вызовов

  • учитывая, используя отдельный класс для определения правильного размера

  • учитывая, используя отдельный класс для камеры (отдельная услуга от камеры) и делегировать вызовы (начать запись, остановить запись и т. д. К классу камеры)

  • учитывая использование класса для доступа к файлу (думаю, файл-менеджера)

одно в фоновом режиме: ваш класс является реализацией услуг (public class RecordingService extends LifecycleService) и должно нести ответственность только за услуги соответствующие вещи - удалить что-нибудь из этого класса, что не является частью сервиса

1
ответ дан 13 февраля 2018 в 09:02 Источник Поделиться