gruhn / vue-qrcode-reader

A set of Vue.js components for detecting and decoding QR codes.
https://gruhn.github.io/vue-qrcode-reader
MIT License
2.09k stars 335 forks source link

Torch does not turn off #380

Closed alexandrmucha closed 11 months ago

alexandrmucha commented 1 year ago

Hi, can't start torch. I tried it both in my code and on your demo page, on different devices and in different browsers. It throws the error "Torch not supported for active camera". I tried to find out the cause by switching to another camera and it worked then. I think the bug is that the library doesn't automatically use the camera that contains torch.

https://vue-qrcode-reader.netlify.app/demos/Torch.html

Smartphone:

gruhn commented 1 year ago

the library doesn't automatically use the camera that contains torch.

That's right. As far as I know there is no mechanism to predict which camera on the device supports the flashlight. In fact we have to wait another 500ms after loading the camera, before we can detect whether torch is supported or not:

https://github.com/gruhn/vue-qrcode-reader/blob/fb2af72e92c4e74fcea37c21995199aad9e8f5c9/src/misc/camera.ts#L110-L113

I updated the demo with a camera picker in a PR, so you can at least go through all the cameras on the device and check them. Please check the deploy preview on your devices and tell me if that works you:

https://deploy-preview-381--vue-qrcode-reader.netlify.app/

alexandrmucha commented 1 year ago

On my phone, I have 4 cameras to choose from in your picker:

It shows 2 front cameras even though I only have one front camera, I have more back cameras but only 2 are shown.

The video from the front camera only works on camera2 1, facing front, on camera2 3, facing front the screen is black. (front camera has no torch)

Similarly, only one of the back cameras is functional, including the torch: camera2 0, facing back, second back camera has no video and torch is not supported: camera2 2, facing back

I find this strange because when I use the code without selecting the camera, the video works but the torch is still not supported, this option is not in the camera selection.

gruhn commented 1 year ago

I find this strange because when I use the code without selecting the camera, the video works but the torch is still not supported, this option is not in the camera selection.

That is strange. Unfortunately, the Web APIs we use here are fairly inconsistently implemented across browsers and devices. It's hard to debug this without access to your device, so I put together this pure JS demo:

https://codepen.io/gruhn/pen/PoXabmG

Maybe you can figure out what is happening.

alexandrmucha commented 1 year ago

I didn't get much out of it, somehow the camera switching doesn't work. I tried googling how to run torch in javascript and found this code: https://codepen.io/adamrifai/pen/YLxjKa, in the note it says that the second camera in the list is most often used for torch. So I tried in your codepen code to manually define as deviceId the given second back camera but failed. But in the code I sent the torch starts normally. Maybe we just need to use the second camera. What do you think?

alexandrmucha commented 1 year ago

I got it! Really just use the second camera from the end.

async mounted(){
        this.devices = (await navigator.mediaDevices.enumerateDevices())
            .filter(({ kind }) => kind === 'videoinput')

        if (this.devices.length > 0) {
            this.selected = this.devices[this.devices.length - 1]
        }
    },

This is how I successfully implemented it in my code and everything works fine, torch is recognized and can be run.

alexandrmucha commented 1 year ago

I just found one problem, the torch can be started, but if the variable defining the prop torch in is set to false again, the torch is not turned off. Even if I destroy and reload it.

gruhn commented 1 year ago

Maybe we just need to use the second camera.

Previously, we used this and other heuristics to select the right camera ( https://github.com/gruhn/vue-qrcode-reader/commit/953919a6b87b5ef7299b3d7a266a8748c9663c99#diff-8b25ec9a34edcaafe6b89fa746baf018483d13802cee3b482dfccd784add8569L28-L62 ) but it sadly doesn't work on all devices. So this is not built-in anymore and responsibility of picking the camera is shifted to the user.

I just found one problem, the torch can be started, but if the variable defining the prop torch in is set to false again, the torch is not turned off. Even if I destroy and reload it.

Ok, weird. Can you reproduce this in a pure JS context or is it unique to the Vue component?

alexandrmucha commented 1 year ago

I haven't tried it in pure js, but this is the vue component code stripped of everything necessary

<template>
    <button type="button" @click="torch = !torch" :disabled="torchNotSupported">Torch</button>

    <div>
        <qrcode-stream :constraints="{ deviceId: selected.deviceId }" :torch="torch" @camera-on="onCameraOn"></qrcode-stream>
    </div>
</template>

<script>

import { QrcodeStream } from 'vue-qrcode-reader';

export default {
    components: {
        QrcodeStream
    },

    data() {
        return {
            torch: false,
            torchNotSupported: false,
            devices: [],
            selected: null,
        };
    },

    async mounted(){
        this.devices = (await navigator.mediaDevices.enumerateDevices())
            .filter(({ kind }) => kind === 'videoinput')

        if (this.devices.length > 0) {
            this.selected = this.devices[this.devices.length - 1]
        }
    },

    methods: {
        onCameraOn(capabilities) {
            this.torchNotSupported = !capabilities.torch
        },
    }
};
</script>
gruhn commented 1 year ago

Right. Unfortunately, I can't debug this without access to your device. If you can reproduce this in pure JS, we can at least rule out that it has something to do with the component itself and maybe find some tick to kill the torch. Maybe calling:

await track.applyConstraints({ advanced: [{ torch: false }] })

or something...

alexandrmucha commented 1 year ago

Oh, if you mean the pure js code for turning the torch on and off, what you just posted works. The only problem is using the <qrcode-stream> component with the :torch prop passing, if I change its state to false it doesn't turn off. But the pure js code below works for me. (I modified the codepen js I sent to turn off the torch)

//Test browser support
const SUPPORTS_MEDIA_DEVICES = "mediaDevices" in navigator;
let state = false;

if (SUPPORTS_MEDIA_DEVICES) {
  //Get the environment camera (usually the second one)
  navigator.mediaDevices.enumerateDevices().then((devices) => {
    const cameras = devices.filter((device) => device.kind === "videoinput");

    if (cameras.length === 0) {
      throw "No camera found on this device.";
    }
    const camera = cameras[cameras.length - 1];

    // Create stream and get video track
    navigator.mediaDevices
      .getUserMedia({
        video: {
          deviceId: camera.deviceId,
          facingMode: ["user", "environment"],
          height: { ideal: 1080 },
          width: { ideal: 1920 }
        }
      })
      .then((stream) => {
        const track = stream.getVideoTracks()[0];

        //Create image capture object and get camera capabilities
        const imageCapture = new ImageCapture(track);
        const photoCapabilities = imageCapture
          .getPhotoCapabilities()
          .then(() => {
            //todo: check if camera has a torch

            //let there be light!
            const btn = document.querySelector(".switch");
            btn.addEventListener("click", function () {
              if (state == false) {
                track.applyConstraints({
                  advanced: [{ torch: true }]
                });
                state = true;
              } else {
                track.applyConstraints({
                  advanced: [{ torch: false }]
                });
                state = false;
              }
            });
          });
      });
  });

  //The light will be on as long the track exists
}
alexandrmucha commented 1 year ago

I tried setting up the back camera with torch on 2 other phones on the demo page and it wouldn't turn off either. Moreover, on my phone the switching in the picker somehow goes wrong and you can't set anything else but the front camera, I set it only with the code that selects the second camera in the list.

gruhn commented 1 year ago

Ok, I added a

await track.applyConstraints({ advanced: [{ torch: false }] })

in #383. Please check the preview URL. I hope this works.

Moreover, on my phone the switching in the picker somehow goes wrong and you can't set anything else but the front camera

Unfortunately, I have no Android phone readily at hand at the moment. If you would be willing to dig into this, I'd be happy to merge your PR, if you manage to come up with a solution :slightly_smiling_face:

I guess the crucial bits are:

alexandrmucha commented 1 year ago

I tried that but unfortunately the torch didn't turn off. I tried pure js once more, additionally modified so that the torch control functions run asynchronously, if by any chance this does not cause problems and it works in pure js. I have no idea what it could be, maybe somehow test if the code you added really starts. Does the stop() function really execute when prop :torch changes? Don't you need to unload the component to run it?

gruhn commented 1 year ago

Does the stop() function really execute when prop :torch changes?

You're right, it doesn't. I pushed a new commit.

alexandrmucha commented 1 year ago

It still doesn't work, I just feel like it reloads when I start the torch but nothing when I turn it off.

gruhn commented 1 year ago

Damn. Well, the feedback loop is a bit too slow to properly debug this :S

alexandrmucha commented 1 year ago

I used to use vue3-qrcode-reader and the on/off switching worked fine, there was no problem with automatic camera selection. What about looking there? I have a feeling it's coming from your code.

https://github.com/HJ29/vue3-qr-reader