juliansteenbakker / mobile_scanner

A universal scanner for Flutter based on MLKit. Uses CameraX on Android and AVFoundation on iOS.
BSD 3-Clause "New" or "Revised" License
744 stars 444 forks source link

iOS / MacOS wrongly map torchMode index 2 to "unavailable" #1033

Closed sharathguvvala closed 2 weeks ago

sharathguvvala commented 3 weeks ago

mobile_scanner: ^5.0.0

Torch state is by default unavailable and I am not able to turn on/off the flashlight, if i initialise the controller with torchEnabled as true, then I could turn it off/on. Is it not possible for torch be in off state and on enabling changing to on state.

class Scan extends StatefulWidget {
  const Scan({super.key});

  @override
  State<Scan> createState() => _ScanState();
}

class _ScanState extends State<Scan>
    with WidgetsBindingObserver {
  MobileScannerController cameraController = MobileScannerController();

  @override
  void initState() {
    super.initState();
    cameraController.start();
  }

  @override
  void dispose() async {
    super.dispose();
    await cameraController.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(),
      body: Column(
        crossAxisAlignment: CrossAxisAlignment.center,
        children: [
          const SizedBox(
            height: 20,
          ), 
          ValueListenableBuilder(
            valueListenable: cameraController,
            builder: (context, state, child) {
              if (!state.isInitialized || !state.isRunning) {
                return const SizedBox.shrink();
              }
              switch (state.torchState) {
                case TorchState.off:
                  return IconButton(
                    icon: const Icon(
                      Icons.flash_off,
                      color: Colors.grey,
                    ),
                    iconSize: 32.0,
                    onPressed: () async {
                      await cameraController.toggleTorch();
                    },
                  );
                case TorchState.on:
                  return IconButton(
                    icon: const Icon(
                      Icons.flash_on,
                      color: Colors.yellow,
                    ),
                    iconSize: 32.0,
                    onPressed: () async {
                      await cameraController.toggleTorch();
                    },
                  );
                case TorchState.unavailable:
                  return const Icon(
                    Icons.no_flash,
                    color: Colors.black,
                  );
              }
            },
          ),
        ],
      ),
    );
  }
}
MarioMuYao commented 3 weeks ago

value = value.copyWith( availableCameras: viewAttributes.numberOfCameras, cameraDirection: effectiveDirection, isInitialized: true, isRunning: true, size: viewAttributes.size, // If the device has a flashlight, let the platform update the torch state. // If it does not have one, provide the unavailable state directly. torchState: viewAttributes.hasTorch ? null : TorchState.unavailable, );

MobileScannerState copyWith({ int? availableCameras, CameraFacing? cameraDirection, MobileScannerException? error, bool? isInitialized, bool? isRunning, Size? size, TorchState? torchState, double? zoomScale, }) { return MobileScannerState( availableCameras: availableCameras ?? this.availableCameras, cameraDirection: cameraDirection ?? this.cameraDirection, error: error, isInitialized: isInitialized ?? this.isInitialized, isRunning: isRunning ?? this.isRunning, size: size ?? this.size, torchState: torchState ?? this.torchState, zoomScale: zoomScale ?? this.zoomScale, ); }

torchState == null ???
sharathguvvala commented 3 weeks ago

@MarioMuYao were you able to solve the issue? Is there any work around?

MarioMuYao commented 3 weeks ago

used mobile_scanner: ^4.0.1

In 5.0.0 when torchState == null cannot set I try listen MobileScannerController and first set torch off,but it is not quite effective

navaronbracke commented 3 weeks ago

@sharathguvvala Initially the torch state is indeed unavailable since we do not know in advance if the selected camera supports a torch. When you call start() with torchEnabled, the controller tries to turn on the torch if there is one available. If you do not provide torchEnabled: true to the start method, you'll have to observe the value in the controller, to see if the selected camera has a torch.

It is indeed not possible to turn on the torch before the camera is started, as the torch is connected to the active camera and turns off when the camera turns off.

navaronbracke commented 3 weeks ago

@sharathguvvala I tested the example app with both torchEnabled: true and torchEnabled: false on a device that has a torch for its camera and I was able to toggle the torch on/off in both cases.

Does the example app work for you?

Bear in mind that Android, iOS and MacOS provide the actual state of the torch through an observable property on the native side, so the torch state might not be immediately set after start() returns a value. Therefore you need to observe the value of the controller, which is updated when the native platform updates the torch state.

sharathguvvala commented 3 weeks ago

@navaronbracke Is there any example that could be shared, I tried initialising the controller with torchEnabled: false, but it always goes to the unavailable state and not able to turn it on, torch will be available in my case as I am using it for ios device alone.

navaronbracke commented 3 weeks ago

You could try running one of the samples here https://github.com/juliansteenbakker/mobile_scanner/tree/master/example

So your ValueListenableBuilder in the sample above starts with state.torchState set to TorchState.unavailable and never changes on iOS? Could you try testing this on Android?

sharathguvvala commented 3 weeks ago

Yeah I followed the sample here by setting torchEnabled: false in my controller

MobileScannerController cameraController = MobileScannerController(
    torchEnabled: false,
  );

and the builder initially sets to TorchState.unavailable and the state never gets changed, I can't try on android (don't have android setup currently), testing on ios (17.4.1)

navaronbracke commented 3 weeks ago

@sharathguvvala Found the bug (I think). Is your camera on auto TorchMode on iOS?

We have the TorchMode enum map value 2 to unavailable. However, index 2 is auto on iOS (and probably also MacOS)

See https://developer.apple.com/documentation/avfoundation/avcapturedevice/torchmode

sharathguvvala commented 3 weeks ago

@navaronbracke Yes you are correct, my camera was on auto flash mode by default. But changing it to false and testing again didn't help, as it's again setting to unavailable state.

Will there be a fix for this or any work around?

navaronbracke commented 3 weeks ago

I will be working on a fix for this in the next few days. We should probably do the following:

1) Report the auto mode properly 2) Change unavailable to use -1 as value (to avoid collisions) 3) Report the current torch state when the camera is started; updates still happen through the listeners

navaronbracke commented 2 weeks ago

@sharathguvvala Version 5.0.2 adds support for the auto torch mode on iOS / MacOS. Can you verify if this fix resolves your issue?

sharathguvvala commented 2 weeks ago

@navaronbracke I just tested 5.0.2, the issue is resolved, thanks for the quick fix.