Rendering Mjpeg Stream in Html5

Stream local mjpg video to html canvas

According to specs about the CanvasRenderingContext2D drawImage method,

Specifically, when a CanvasImageSource object represents an animated
image in an HTMLImageElement, the user agent must use the default
image of the animation (the one that the format defines is to be used
when animation is not supported or is disabled), or, if there is no
such image, the first frame of the animation, when rendering the image
for CanvasRenderingContext2D APIs.

This applies to .gif, SMIL animated .svg and .mjpeg media. So once you fetched the data, only one frame should be drawn onto the canvas.

Note that chrome has a bug and only respect it for .gif images, but they may fix it someday.

One solution as you noticed yourself, is to fetch an other fresh frame, with the clear-cache hack ('your.url/?' + new Date().getTime();) but you will loose any advantages of the mjpeg format (partial frame content) and can't be sure when the refreshing will happen.

So a better solution if applicable, would be to use a video format. Each frame of a video can be drawn to the canvas.


Edit 2018


A third solution came to my little mind two years later:

UAs are not tied to keep in memory the same default image for all 2DContexts in the document.

While for others format we are still kinda stuck, for MJPEG streams, which don't have a well defined default image, we actually fall to the first frame of the animation.

So by drawing the <img> containing our MJPEG stream on two different canvases, at different times, we can theoretically have two different frames of our same MJPEG stream to be drawn on the canvases.

Here is a proof of concept only tested on Firefox 62.

var ctx_stream = stream.getContext('2d');var ctx_direct = direct.getContext('2d');img.onload = function() {   stream.width = direct.width = this.naturalWidth;   stream.height = direct.height = this.naturalHeight;   // onload should fire multiple times   // but it seems it's not at every frames   // so we'll disable t and use an interval instead   this.onload = null;   setInterval(draw, 500);};function draw() {  // create a *new* 2DContext  var ctx_off = stream.cloneNode().getContext('2d');  ctx_off.drawImage(img, 0,0);  // and draw it back to our visible one  ctx_stream.drawImage(ctx_off.canvas, 0,0);    // draw the img directly on 'direct'  ctx_direct.drawImage(img, 0,0);}    img.src = "http://webcam.st-malo.com/axis-cgi/mjpg/video.cgi?resolution=704x576&dummy=1491717369754";
canvas,img{  max-height: 75vh;}
Using a new offcreen canvas every frame: <br><canvas id="stream"></canvas><br>The original image: <br><img id="img"><br>Drawing directly the <img> (if this works your browser doesn't follow the specs): <br><canvas id="direct"></canvas><br>

Include a MJPEG in a html-Site, using video.js

You can use an <video> tag

<video src="http://172.17.52.161:9000/stream/video.mjpeg" controls>
Your browser does not support the <code>video</code> element.
</video>

<img> with mjpeg-stream created with JS don't work fine in Firefox and Opera

Well, it seems to be fixed with using
<iframe src='path_to_mjpeg'>
instead of
<img src='path_to_mjpeg'>
Chrome and Opera handle its creation with JS correctly.
Firefox starts playing successfully to, but with huge memory leak.

How can I render 6 or more mjpeg streams on the same page?

Create subdomains for the camera proxies. These subdomains can all point to the same server.

Basically, this would be the setup:

    NAME                TYPE   VALUE
--------------------------------------------------
camproxy.example.com. A 127.0.0.1
cam1.example.com. CNAME camproxy.example.com.
cam2.example.com. CNAME camproxy.example.com.
cam3.example.com. CNAME camproxy.example.com.
cam4.example.com. CNAME camproxy.example.com.
...

Then, in your control page, either use every subdomain only once or keep a track of how many times you've used one. For example:

function getCamUrl($file) {
static $subdomain = 1;
static $uses = 0;

$uses++;
if($uses > 6) {
$uses = 0;
$subdomain++;
}

return "cam" . $subdomain . ".example.com/" . $file;
}

echo '<img src="'. getCamUrl('video1.mjpeg') .'">';
echo '<img src="'. getCamUrl('video2.mjpeg') .'">';
echo '<img src="'. getCamUrl('video3.mjpeg') .'">';
echo '<img src="'. getCamUrl('video4.mjpeg') .'">';

According to the spec, browsers should only use 2 connections. You can get away with setting it to 6, this works in most modern browsers.

How to show an extermal livestream on a webpage

Yes, you can show streaming video in a video element, provided you're using a separate javascript library to demux the incoming stream (Some examples are hls.js and flv.js ). Really only iOS devices support live streaming in a native <video> element by itself (HLS video).

You can sort out what kind of stream is incoming by using ffprobe or maybe by looking at the Content-Type of the http request you're making. application/octet-stream is a generic content type, so I wouldn't be certain you're actually grabbing the real content in that request.

Lastly, you'll likely be dealing with CORS/mixed-content issues for anything live streaming (unless you're loading a mjpeg stream...which you aren't if you've got H.264 video). You'll need to get that sorted out.

How to detect when mjpeg stream stops with javascript

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.

Visualize mjpeg-over-http streams in browser with html5

iOs mobile Safari supports MJPEG natively over http. Is there specific reason you need it in Canvas?
see http://bridgecam2.halton.gov.uk/mjpg/video.mjpg?camera=1 on a ipad/iphone



Related Topics



Leave a reply



Submit