PhilBot PhilBot - 1 month ago 10
HTTP Question

HTML img tag auto refresh browser causes image tearing

I basically want to show a video in a web browser on Linux by continually capturing from my USB webcam in a separate process to the file /tmp/ir.bmp, and then using Javascript to refresh that image tag in my webpage at around 7 Hz ( which is the capture rate of my webcam ). I am using Lua with OpenResty and NGINX on the Linux machine.

It actually works pretty well, and most of the time the webpage looks like a fluid video. However, somewhat frequently there are hiccups and I briefly see the placeholder message for the missing image before the next image is displayed.

I think this has to do with the file be written by the second process and attempted to be read by NGINX at the same time. What can I do to mitigate this and make the video fluid? Thank you.

<!DOCTYPE html>
<html>
<script>
setInterval("document.getElementById('img').src='ir.bmp?'+new Date().getMilliseconds()", 143);
</script>
<body>
<img id="img" src="ir.bmp" alt="My Video Stream" style="width:320px;height:240px;">
</body>
</html>

Answer

I would say that you should use some sort of video encoder on the server and then serve that using an HTML tag. You'll get much better bandwidth usage.

However, that's a bit out of scope for this question. You can probably improve the quality of what you're already doing by simply ensuring you don't display broken images.

There are a few issues with your code:

  1. You're using the old string-based version of setInterval (setInterval("...") vs setInterval(function(){ ... });
  2. You're using setInterval at all. You should really use requestAnimationFrame for updating the view. I wouldn't worry too much about making additional requests beyond your 7fps.
  3. You're not handling error cases. That's probably why you get some bad frames.
  4. You're using Date.getMilliseconds, which returns the milliseconds in the current second meaning only 1000 possible values, which are bound to repeat. I think you confused it with new Date().getTime() which returns the number of milliseconds since the epoch.

The following will probably work better, but there is certainly still room for improvement (and this code is untested). Use it as a starting point.

function loadNextImage(){
    // An image element for loading our next image
    var ourImage = new Image();

    // Setup the event handlers before setting the source
    ourImage.onload = function(){
        // Request the next frame
        requestAnimationFrame(loadNextImage);

        // Swap out the image element on the page with the new one
        var oldImage = document.getElementById('img');
        oldImage.parentElement.insertBefore(ourImage, oldImage);
        oldImage.parentElement.removeChild(oldImage);
    };
    ourImage.onerror = function(){
        // Something went wrong with this frame. Skip it and move on.
        requestAnimationFrame(loadNextImage);
    }

    // Finally load the image
    ourImage.src = "ir.bmp?" + new Date().getTime();
};
requestAnimationFrame(loadNextImage);

This code may have some issues as well, but it should run smoother. Good luck.