Is it possible to control the camera light on a phone via a website?

I fixed Daniel's answer and now button works properly on Android phones. IOS is still unsupported.

https://jsfiddle.net/nzw5tv1q/

//have a console on mobile
const consoleOutput = document.getElementById("console");
const log = function (msg) {
  consoleOutput.innerText = `${consoleOutput.innerText}\n${msg}`;
  console.log(msg);
}

//Test browser support
const SUPPORTS_MEDIA_DEVICES = 'mediaDevices' in navigator;

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) {
      log('No camera found on this device.');
    }
    // Create stream and get video track
    navigator.mediaDevices.getUserMedia({
      video: {
        facingMode: 'environment',
      }
    }).then(stream => {
      const track = stream.getVideoTracks()[0];

      //Create image capture object and get camera capabilities
      const imageCapture = new ImageCapture(track)
      imageCapture.getPhotoCapabilities().then(capabilities => {
        //let there be light!
        const btn = document.querySelector('.switch');
        const torchSupported = !!capabilities.torch || (
          'fillLightMode' in capabilities &&
          capabilities.fillLightMode.length != 0 &&
          capabilities.fillLightMode != 'none'
        );

        if (torchSupported) {
          let torch = false;
          btn.addEventListener('click', function (e) {
            try {
              track.applyConstraints({
                advanced: [{
                  torch: (torch = !torch)
                }]
              });
            } catch (err) {
              log(err);
            }
          });
        } else {
          log("No torch found");
        }
      }).catch(log);
    }).catch(log);
  }).catch(log);

  //The light will be on as long the track exists
}
<button class="switch">On / Off</button>
<h2>
Console output
</h2>
<div id="console">

</div>

Here is a little "torch-app" for a website:

Edit 1: I also made a jsfiddle

//Test browser support
const SUPPORTS_MEDIA_DEVICES = 'mediaDevices' in navigator;

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(){
          track.applyConstraints({
            advanced: [{torch: true}]
          });
        });
      });
    });
  });
  
  //The light will be on as long the track exists
  
  
}
<button class="switch">On / Off</button>

The code is heavily inspired by this repository, this webseries and this blog-post

Edit 2: This does only works in Chrome (and maybe Opera). It does not work in Chrome on iOS, because Chrome cannot access the camera. I cannot test it on android for now. I created a new jsfiddle, with an output. If you have an android phone and it does not work for you, it will maybe tell why: https://jsfiddle.net/jpa1vwed/

Feel free to debug, comment and edit.


You can use the MediaStream Image Capture API by creating an ImageCapture from a VideoStreamTrack and setting the option "fillLightMode" to "flash" or "torch". Example:

<video autoplay="true"></video>
<img />
<button onclick="takePhoto()">Take Photo</button>
<script type="text/javascript">
    var imageCapture = null;
    var deviceConfig = {
        video: {
            width: 480,
            height: 640,
            facingMode: "environment", /* may not work, see https://bugs.chromium.org/p/chromium/issues/detail?id=290161 */
            deviceId: null
        }
    };

    var imageCaptureConfig = {
        fillLightMode: "torch", /* or "flash" */
        focusMode: "continuous"
    };

    // get the available video input devices and choose the one that represents the backside camera
    navigator.mediaDevices.enumerateDevices()
            /* replacement for not working "facingMode: 'environment'": use filter to get the backside camera with the flash light */
            .then(mediaDeviceInfos => mediaDeviceInfos.filter(mediaDeviceInfo => ((mediaDeviceInfo.kind === 'videoinput')/* && mediaDeviceInfo.label.includes("back")*/)))
            .then(mediaDeviceInfos => {
                console.log("mediaDeviceInfos[0].label: " + mediaDeviceInfos[0].label);

                // get the device ID of the backside camera and use it for media stream initialization
                deviceConfig.video.deviceId = mediaDeviceInfos[0].deviceId;
                navigator.mediaDevices.getUserMedia(deviceConfig)
                        .then(_gotMedia)
                        .catch(err => console.error('getUserMedia() failed: ', err));
            });

    function takePhoto () {
        imageCapture.takePhoto()
                .then(blob => {
                    console.log('Photo taken: ' + blob.type + ', ' + blob.size + 'B');

                    // get URL for blob data and use as source for the image element
                    const image = document.querySelector('img');
                    image.src = URL.createObjectURL(blob);
                })
                .catch(err => console.error('takePhoto() failed: ', err));
    }

    function _gotMedia (mediastream) {
        // use the media stream as source for the video element
        const video = document.querySelector('video');
        video.srcObject = mediastream;

        // create an ImageCapture from the first video track
        const track = mediastream.getVideoTracks()[0];
        imageCapture = new ImageCapture(track);

        // set the image capture options (e.g. flash light, autofocus, ...)
        imageCapture.setOptions(imageCaptureConfig)
                .catch(err => console.error('setOptions(' + JSON.stringify(imageCaptureConfig) + ') failed: ', err));
    }
</script>

Note:

  • As of this writing the API is still under development and may change in the future.
  • For enabling ImageCapture in Chrome the flag "chrome://flags/#enable-experimental-web-platform-features" has to be set to "true"
  • For enabling ImageCapture in Firefox the flag "dom.imagecapture.enabled" in "about:config" has to be set to "true". But "setOptions" is not supported as of this writing!

See also:

  • Mediastream Image Capture on GitHub
  • NPM module ImageCapture polyfill

Here, it is a static class for handling flashlight. You can call flashlightHandler.accessFlashlight() when the window loads. And then use the flashlightHandler.setFlashlightStatus() method, passing it true or false, as you want the flashlight status.

class flashlightHandler {

    static track; //the video track which is used to turn on/off the flashlight

    static accessFlashlight() {
        //Test browser support
        if (!('mediaDevices' in window.navigator)) {
            alert("Media Devices not available. Use HTTPS!");
            return;
        };

        //Get the environment camera (usually the second one)
        window.navigator.mediaDevices.enumerateDevices().then((devices) => {

            const cameras = devices.filter((device) => device.kind === 'videoinput');
            if (cameras.length === 0) {
                alert("No camera found. If your device has camera available, check permissions.");
                return;
            };
            
            const camera = cameras[cameras.length - 1];
            
            window.navigator.mediaDevices.getUserMedia({
                video: {
                    deviceId: camera.deviceId
                }
            }).then((stream) => {
                this.track = stream.getVideoTracks()[0];
                
                if (!(this.track.getCapabilities().torch)) {
                    alert("No torch available.");
                }; 
            });
        });
    }

    static setFlashlightStatus(status) {
        this.track.applyConstraints({
            advanced: [{
                torch: status
            }]
        });
    }
}