RedApparat / Fotoapparat

Making Camera for Android more friendly. 📸
Apache License 2.0
3.82k stars 405 forks source link

Camera preview goes blank when ViewPager pauses and resumes from another fragment #215

Closed iamutkarshtiwari closed 6 years ago

iamutkarshtiwari commented 6 years ago

Please check if your issue exists.

Issues should only be posted after you have been able to reproduce them and confirm that they are either a missing functionality or a bug.

What are you trying to achieve or the steps to reproduce?

To describe best, here is the issue that I am facing - https://drive.google.com/file/d/1T4IbE9plnR_5fXnoTjFkAwGY7lKHblh6/view?usp=sharing

// Wrap code in markdown source tags

How did you initialize FA?

@Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        binding = DataBindingUtil.inflate(inflater, R.layout.fragment_comment_image_picker_camera_tab, null, false);

        return binding.getRoot();
    }

    @Override
    public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);

        if (!isEnabled()) {
            return;
        }

        mCameraView = binding.viewFinder;
        mFocusView = binding.focusView;
        mImageProcessingProgressBar = binding.imageProcessingProgressLoader;

        // Disable buttons until permissions check
        toggleCameraOptionsClickability(false);
        toggleShutterButtonAvailability(false);

        mCameraConfiguration = getCameraConfiguration();
        mFotoapparat = createFotoapparat();
    }

    private void setupUi() {
        setupShutterClick();
        setupSwitchCameraClick();
        setupFlashClick();

        binding.infoButton.setOnClickListener(v -> {
            new CameraPhotoPickerTipsDialog().show(getFragmentManager(), TAG_TIPS_DIALOG);
        });
    }

    private Fotoapparat createFotoapparat() {
        mActiveLensPosition = LensPositions.initialMode();
        mActiveFlashMode = FlashModes.initialMode();
        return Fotoapparat
                .with(getContext())
                .into(mCameraView)
                .previewScaleType(ScaleType.CenterCrop)
                .lensPosition(getLensSelector(mActiveLensPosition))
                .flash(getFlashSelector(mActiveFlashMode))
                .cameraErrorCallback(this)
                .build();
    }

    private CameraConfiguration getCameraConfiguration() {
        return CameraConfiguration
                .builder()
                .photoResolution(standardRatio(
                        highestResolution()
                ))
                .focusMode(firstAvailable(
                        continuousFocusPicture(),
                        autoFocus(),
                        fixed()
                ))
                .flash(firstAvailable(
                        autoFlash(),
                        torch(),
                        off()
                ))
                .previewFpsRange(highestFps())
                .sensorSensitivity(highestSensorSensitivity())
                .build();
    }

    @Override
    public void onResume() {
        super.onResume();
        mFotoapparat.start();
        if (mSavePictureSubscription == null || mSavePictureSubscription.isUnsubscribed()) {
            toggleCameraOptionsClickability(true);
            toggleCameraOptionsAvailability(true);
            toggleShutterButtonAvailability(true);
            toggleProgressLoaderLayer(false);
        }
    }

    @Override
    public void onPause() {
        super.onPause();
        mFotoapparat.stop();
        if (mSavePictureSubscription != null && !mSavePictureSubscription.isUnsubscribed()) {
            mSavePictureSubscription.unsubscribe();
        }
    }

    @Override
    public void onStop() {
        super.onStop();
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
    }

What was the result you received?

What did you expect?

Context:

Diolor commented 6 years ago

Can you please attach a logger an paste here only the logs with the Fotoapparat tag?

Diolor commented 6 years ago

Attach .logger(logcat()) to your FA builder. We need the logs emitted from this. They have "Fotoapparat" as TAG (instead of Mercari_JP)

iamutkarshtiwari commented 6 years ago

@Diolor Here is the gist for all the Fotoapparat logs for the steps as I did in the video - https://gist.github.com/iamutkarshtiwari/a0063915812e66f76ed575c3f5017cd7

Diolor commented 6 years ago

Thank you! I see a Can't start preview because of the exception: java.io.IOException: setPreviewTexture failed which looks like the issue.

This is happening only if you have the fragment paused, then pause/resume the app.

I suspect the CameraView is still active and being held from your viewpager and when you try to restart the camera it's conflicting the current start with the previous stop.

I think it's an issue with fragments but maybe we have missed something here. I need to make a demo to get a hands on understanding.

If you figure this out let us know!

Diolor commented 6 years ago

Closing this due to inactivity. Feel free to open new issue

amadeu01 commented 6 years ago

Have any update on that issue?

amadeu01 commented 6 years ago

I did the code below before creating the Fotoapprat instance.

        CameraManager manager =
                (CameraManager) view.getContext().getSystemService(CAMERA_SERVICE);
        try {
            for (String cameraId : manager.getCameraIdList()) {
                CameraCharacteristics chars
                        = manager.getCameraCharacteristics(cameraId);
                App.logException(Log.DEBUG, "PhotoAction", chars.toString());

                manager.openCamera(cameraId, new CameraDevice.StateCallback() {
                    @Override
                    public void onOpened(@NonNull CameraDevice camera) {
                        App.logException(Log.DEBUG, "PhotoAction", "onOpened");
                    }

                    @Override
                    public void onDisconnected(@NonNull CameraDevice camera) {
                        App.logException(Log.DEBUG, "PhotoAction", "onOpened");
                    }

                    @Override
                    public void onError(@NonNull CameraDevice camera, int error) {
                        App.logException(Log.DEBUG, "PhotoAction", "onError");
                        camera.close();
                    }
                }, new Handler());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

It worked. Then, I released that onCreate of the Activity was being called twice as well. But, only in one specific device. What I believe be the problem is the number of connections on the camera hardware exceed its limit because of unkilled instance of CameraView as @Diolor told.

On the,

@Override
public void onError(@NonNull CameraDevice camera, int error) {
         App.logException(Log.DEBUG, "PhotoAction", "onError");
         camera.close();
}

It raises ERROR_MAX_CAMERAS_IN_USE which implies

An error code that can be reported by onError(CameraDevice, int) indicating that the camera device could not be opened because there are too many other open camera devices.

The system-wide limit for number of open cameras has been reached, and more camera devices cannot be opened until previous instances are closed.

This error can be produced when opening the camera fails.


I believe that should be possible to force Fotoapparat release the camera hardware as camera.close();. Or, on errors like ERROR_MAX_CAMERAS_IN_USE, it tries to reload the CameraView