Apparence-io / CamerAwesome

📸 Embedding a camera experience within your own app shouldn't be that hard. A flutter plugin to integrate awesome Android / iOS camera experience.
https://ApparenceKit.dev
MIT License
912 stars 201 forks source link

[Discussion] Should there be a global controller / global key? #319

Open apalala-dev opened 1 year ago

apalala-dev commented 1 year ago

Proposal

This proposal follows #313.

CameraAwesomeBuilder doesn't take any controller or global key as parameter. If an user wants to control the camera API outside of the builders functions, they need to first register the state in the builder callback in order to be able to use it later.

A traditional way to work with such workflows is to provide a controller or a global key in order to access properties & methods of a given widget.

This issue is here to discuss what properties and methods should be accessibles, and once it's more clear on that area, we might provide a better way to access them.

Pros:

Cons:

Like (👍) this issue if you think it's worth implementing or dislike (👎) if you don't think it's worth it.

pinpong commented 1 year ago

Plugin looking good but without a controller it's not the right plugin for our usecase.

apalala-dev commented 1 year ago

Plugin looking good but without a controller it's not the right plugin for our usecase.

Could you share what exactly you expect from a controller? If you could include a list of features, that would be a great insight 💪

Also, remember that the controller is probably just an other way of getting the CameraState that one can already access through CameraAwesomeBuilder builders.

E.g.:

CameraAwesomeBuilder.awesome(
    previewDecoratorBuilder: (state, previewSize, previewRect) {
          // You could save the current state in a variable, but don't forget to refresh it every time this method is called
          _currentState = state;

          // Return a widget, just return an empty SizedBox if you don't want to show anything
          return SizedBox();
        },
)

Then in an other method, you could have something like this:

void myOtherMethod() {
    _currentState.when(
                onPhotoMode: (photoState) => photoState.takePhoto(),
                onVideoMode: (videoState) => videoState.startRecording(),
                onVideoRecordingMode: (videoState) =>
                    videoState.stopRecording(),
     );
}
pinpong commented 1 year ago

For example with camera plugin you are able to stream images from camera without any widgets.

here is a basic example how we want to use the controller. With the camerasValuesStream you are able to build your ui with the state management of your choice.


  late final _cameraValuesSubject = behaviorSubject<List<CameraValue>>();

  Stream<List<CameraValue>> get camerasValuesStream =>
      _cameraValuesSubject.stream;

    late List<CameraDescription> _cameras;

    List<CameraController> cameraControllers = [];

  void initCameras() async {
    _cameras = await availableCameras();

    for (final camera in _cameras) {
      final CameraController controller = CameraController(
        cameraDescription,
        ResolutionPreset.medium,
        enableAudio: true,
        imageFormatGroup: ImageFormatGroup.jpeg,
      );

      cameraControllers.add(controller);

      controller.addListener(() {
        updateCameraValues();
      });

      await controller.initialize();
      controller.startImageStream(...);
    }
  }

  void updateCameraValues() {
    List<CameraValue> values = [];
    for (final controller in cameraControllers) {
      values.add(controller.value);
    }
    _cameraValuesSubject.sink.add(values);
  }
apalala-dev commented 1 year ago

An alternative for this use case would be to use CameraAwesomeBuilder.analysisOnly() and either return your UI or an empty SizedBox from the builder method.

Here is an example:

CameraAwesomeBuilder.analysisOnly(
        aspectRatio: CameraAspectRatios.ratio_1_1,
        sensor: Sensors.front,
        onImageForAnalysis: (img) async => _imageStreamController.add(img),
        imageAnalysisConfig: AnalysisConfig(
          androidOptions: const AndroidAnalysisOptions.yuv420(
            width: 150,
          ),
          maxFramesPerSecond: 30,
        ),
        builder: (state, previewSize, previewRect) {
          // Return an empty widget if you don't want to show anything
          return SizedBox();
          // Or return your UI with the state as parameter
          // return MyAnalyticsUI(state: state);
        },
      )

You could pass the state to your UI (like for MyAnalyticsUI) or even the state.analysisController if you only want to start/stop the analytics stream manually.

pinpong commented 1 year ago

Need to use the camera without any imports package:flutter/widgets.dart or classes that extends the widget class.

pitriq commented 1 year ago

In our case, we are subscribing to state to add some custom behaviour when a picture is taken.

With the current implementation, we need to set this up in builder and check for the existence of a previous subscription on every state change, which feels pretty bad.

return CameraAwesomeBuilder.custom(
  // ...
  builder: (state, _, __) {
    _setUpCaptureStateSubscription(state);

    // Custom UI
    // ...
  },
);

void _setUpCaptureStateSubscription(CameraState state) {
  if (_cameraFilesSubscription != null) return;
  _cameraFilesSubscription =
      state.captureState$.listen((event) async {
    if (event == null) return;
    if (event.status == MediaCaptureStatus.capturing) {
      _isCapturing = true;
      if (event.isPicture) _showBarrier();
      return;
    }
    if (event.status == MediaCaptureStatus.success) {
      await widget.onMediaSaved?.call(event.filePath);
    }
    _isCapturing = false;
  });
}

A controller/key would allow us to set this subscription up somewhere else, resulting in a cleaner implementation. I agree with the previously stated in this issue, builders should only be responsible of displaying the UI.

live9080 commented 9 months ago

In my case, I need an automatic photo taken every few seconds. So, I have a Timer in the Bloc. Currently, I have to pass the cameraState Blob when building.

CameraAwesomeBuilder.awesome(
  saveConfig: SaveConfig.photo(),
  previewFit: CameraPreviewFit.contain,
  topActionsBuilder: (CameraState cameraState) {
    blob.add(SetCameraStateEvent(cameraState));
    return AwesomeTopActions(state: cameraState);
  },
)

I also tried the static CamerawesomePlugin.takePhoto(). It works but feel weird. // no matter which sensor was pass, It will be omitted(current preview view is be adoptive).

// These two method had the same behavior.
await CamerawesomePlugin.takePhoto(SingleCaptureRequest(
  imagePath,
  Sensor.position(SensorPosition.back),
));
await CamerawesomePlugin.takePhoto(SingleCaptureRequest(
  imagePath,
  Sensor.position(SensorPosition.front),
));