8

I have a html/javascript client that is listening to a mjpeg video stream:

myImg = document.getElementById('my-image');
myImg.src = 'http://myserver.com/camera.mjpeg';

Works fine but if the video stream dies for whatever reason the video feed "freezes" on the last received image and I have no opportunity to display an error to the user. I've see this post that offers a solution (creating a long running ajax request alongside the stream) that only works some of the time. I was hoping there would be a more supported method like through a disconnect event or something.

Even an event for when data is received would be better than nothing. At least that way I could tell if it's been a while since a frame came through. Using addEventListener('load') only works on the very first frame.

Any ideas?

Update:

Based on comments I have tried the following approaches, none of which has worked:

myImg.addEventListener('error', event => { ... });
myImg.addEventListener('stalled', event => { ... });
myImg.addEventListener('suspend', event => { ... });
d512
  • 32,267
  • 28
  • 81
  • 107
  • Does it emit an `error` event? `myImg.addEventListener("error", () => {...});`? – zero298 Apr 21 '21 at 22:34
  • @zero298 Unfortunately it does not – d512 Apr 22 '21 at 03:15
  • could your try to listen on `stalled` and `suspend` event one of them may work with your case – Joseph Apr 30 '21 at 16:50
  • @Joseph Thanks for the suggestion, unfortunately neither event seemed to fire. I tried `myImg.addEventListener('stalled', event => { ... });` and `myImg.addEventListener('suspend', event => { ... });` – d512 Apr 30 '21 at 20:52
  • 1
    Have you ever tried to put in a video attribute – Hudson Moreira da Cunha Apr 30 '21 at 21:06
  • @HudsonMoreiradaCunha I have not. I'll look into that but in the end I need an image, not a video. So as long as I can extract images from the video that might work. – d512 Apr 30 '21 at 22:14
  • You can make a setInterval removing and adding after that you can monitor if the image has been successfully loaded, in this topic https://stackoverflow.com/questions/1977871/check-if-an-image- is-loaded-no-errors-with-jquery can monitor loading with javascript or jquery – Hudson Moreira da Cunha May 02 '21 at 00:45
  • Is there some way we can test this ourselves in some sort of playground environment? – Brandon McConnell May 06 '21 at 01:56

5 Answers5

2

This is common with a normal implementation of a mjpeg, for example

   <video src="http://myserver.com/camera.mjpeg" controls>
      Your browser does not support the <code>video</code> element.
   </video>

the mjpeg is a series of images and eventually it will not get the next one for whatever reason, breaking the connection. (this is sometimes because the source is cached, causing the browser to use the last image every time). I don't consider this an error, more something to program around with mjpeg streams.

A simple solution you can do, set a refresh rate and set the src continuously refreshing the connection every ~500ms (or less depending on your network connection/resources).

setInterval(function() {
    var myImg = document.getElementById('myImg');
    myImg.src = 'http://myserver.com/camera.mjpeg?rand=' + Math.random();
}, 5000);

The random number is added to prevent browser side caching in the event the server sends those headers.

Or you can create a ReadableStream, and keep reading a blob of bytes directly into the source of the image. There is a robust example in this repo, from this other question.

Beau Bouchard
  • 835
  • 11
  • 28
1

In Safari document.readyState will change from interactive to complete.

For example put this before the image loads:

  <script>
    console.log('Initial ready state', document.readyState);
    document.onreadystatechange = function() {
      console.log('Ready state changed to:', document.readyState);
    }
  </script>

And the output will be:

Initial ready state – "loading"
Ready state changed to: – "interactive"

// When the connection disconnects:
Ready state changed to: – "complete"

In google chrome the readyState doesn't stay on interactive, but it looks like chrome is better at reconnecting, so might not be an issue for you.

Edit: One way to make use of this is to drop the image in an iframe, you'll continually get load events in safari (this does not work in chrome).

iframe = document.createElement('iframe')
iframe.onload = console.log
iframe.src = "http://10.0.0.119:8080/stream"
document.body.append(iframe)

Edit2: Another technique -- use image.decode to detect when the connection is down and reload the image:

    <img id="stream" src="http://10.0.0.119:8080/stream"></img>
    <script>
      let image = document.getElementById('stream');

      async function check() {
        while (true) {
          try {
            await image.decode();
          } catch {
            let src = image.src;
            image.src = "";
            image.src = src;
          }
      
          await new Promise((resolve) => setTimeout(resolve, 5000));
        }
      }
      
      check();
    </script>
ibash
  • 1,477
  • 17
  • 31
  • The `image.decode` is the only valid solution in this thread, all others are crazy workarounds that ruins usex experience (because of blinking) or are simply wrong. Of course this requires polling, which isn't the best one could expect, but at least it does what is really needed - to check if image stopped working. Thanks! – dzek May 18 '22 at 13:32
0

Would something like this work?

function hasLoaded(myImg) {
  return myImg.complete && myImg.naturalHeight !== 0;
}
R3FL3CT
  • 551
  • 3
  • 14
  • 1
    I don't think so. The `complete` attribute remains `true` and `naturalHeight` stays the same even after the feed has stopped. – d512 May 04 '21 at 02:38
0

Following Beau Bouchard answers.

  1. The setInterval timer, works fine but it tends to max out active client listening. ( if ur mjpeg stream are coming directly from an IP Camera). Could possibly create a restreaming server the mjpeg server to allow more clients to be able to be listening to it) Short pooling though does tend to be very resource heavy.

  2. Tried the Restream Api as well. When loading the image back into the img tag, you do get a jittery effect, most likely because the chunks are coming in randomly and not smooth out via time?

  3. In the end, i use the onload img tag event. This triggers whenever an img is loaded. Then a time interval to check if the img tag has stop loading to determine if the mjpeg stream has stop.

KennyS
  • 31
  • 3
  • 3
    Please provide additional details in your answer. As it's currently written, it's hard to understand your solution. – Community Sep 06 '21 at 08:17
0

Met the same requirement, test on Image onload event and works!!

If FFMPEG feed FFSERVER stop, although the MJPEG in a still image,

After couple times error count, mjpeg FAIL! detected.

<!DOCTYPE HTML>
<HTML>
 <HEAD>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
  <TITLE> mjpeg detect </TITLE>
<script type="text/javascript" language="JavaScript" src="/js/jquery.js"></script>
<script type="text/javascript" language="JavaScript">
//------------------------------------------------------------------------------
// localhost/tool/mjpeg.htm
// document.ready
$(function(){
    setTimeout("mjpegRefresh()", 10000);
});

var mTmjpegRefresh, mBmjpegStatus=0, mNmjpegError=0;
var mjpegRefresh = function()
{
    clearTimeout(mTmjpegRefresh);
    mBmjpegStatus=0;
    $('#myMJPEG').attr('src', "http://192.168.1.17:8090/live.mjpeg?rand=" + Math.random()); 
    console.log("mjpeg refresh: ", Math.round( (new Date()).getTime()/1000)) ;
    mTmjpegRefresh = setTimeout("mjpegRefresh()", 10000);
    mTmjpegStatusCheck = setTimeout("mjpegStatusCheck()", 5000);
}

var mjpegOnload = function()
{
    console.log("mjpeg Onload");
    mBmjpegStatus=1;
}

var mTmjpegStatusCheck;
var mjpegStatusCheck = function()
{
    clearTimeout(mTmjpegStatusCheck);
    if(mBmjpegStatus>0)
    {
        mNmjpegError=0;
    }
    else
    {
        mNmjpegError++;
    }
    if(mNmjpegError>5)
    {
        console.log("mjpeg FAIL!");
    }
    mTmjpegStatusCheck = setTimeout("mjpegStatusCheck()", 5000);
}
//------------------------------------------------------------------------------
</script>
 </HEAD>

 <BODY>

<img src="http://192.168.1.17:8090/live.mjpeg" width="720" height="404" id="myMJPEG" onload="mjpegOnload()">

 </BODY>
</HTML>

Lo Vega
  • 121
  • 1
  • 3