Search code examples
javascriptcsssafarihtml5-canvasgetusermedia

Webcamjs: Live images are not getting captured properly in safari


I'm developing a monitoring system where we need to take images of our user(under their permission ofc) every x minute and the webcam HTML element wouldn't be visible to the user.

But, sometimes the snapped images are giving old images in Safari. I've created a mock app with webcamjs and I could able to reproduce this in safari only when the webcam node is hidden from the viewport.

For hiding webcam element from viewport, I've used the following style

#webcam{
  position: fixed;
  top: -10000px;
  left: -10000px;
}

Steps to reproduce

Specifications

Browser: Safari version 16.0 OS: MacOS 12.6 WebcamJS: 1.0.26

  • Access this url in Safari. For demo purpose, I've snapped the images every 10 seconds and rendered it into the DOM.
  • You could see repeated images rendered into the DOM

Code

function loadWebcamJS(){
        const webcam = document.getElementById("webcam");
        Webcam.set({
          width: 640,
          height: 480
        });
        Webcam.attach(webcam);
        Webcam.on('load', afterLoad);
      }
      const getTime = (d) =>
          `${d.getHours()}:${d.getMinutes()}:${d.getSeconds()}`;

      function snap(){
        Webcam.snap((dataURI) => {
          const results = document.getElementById('results');
          const date = new Date();
          const time = getTime(date);
          
          results.innerHTML += `
          <div class="image">
            <img src=${dataURI}
              alt="Snapped Image">
            <h4>${time}</h4>
          </div>
          
          `

        })
      }
      loadWebcamJS();
      function afterLoad(){
        
        setInterval(() => {
          snap();
        }, 1000 * 10); //Snap images every 10 seconds
        
      }

Solution

  • Note that I'm not used to this library, so maybe they've got a cleaner way to handle this.

    It seems that the autoplay fails on Safari. You can remedy this by calling yourself the play() method of the <video> that the library will append in your element.
    Note that if you don't want that element to be visible in the page, you're not forced to have it in the DOM at all. I'd even advise against this since browsers do use IntersectionObserver instances to pause muted <video> elements that aren't visible. They don't pause detached <video> though.

    So you can remove your <div>, and the related CSS, and only do something like

    const webcam = document.createElement("div");
    function loadWebcamJS() {
      Webcam.set({
        width: 640,
        height: 480
      });
      Webcam.attach(webcam);
      // force playing for Safari
      webcam.querySelector("video").play();
      Webcam.on("load", afterLoad);
    }
    

    Updated codepen (StackSnippets don't allow for gUM).