Render Captured Video to Full Page Canvas
In modern HTML 5 browsers you can render video from your camera inside a web page using the video
element. However, to further process the captured video (e.g. parse a QR code) or add some custom rendering on top of it, the canvas
element needs to be used. Due to the ever changing APIs in this field, it's not easy to find up-to-date working sample code for achieving this.
Directly Render Video from Camera
To capture camera video, we need to use MediaDevices.getUserMedia
API:
var video = document.getElementById('video');
if (navigator.mediaDevices.getUserMedia) {
var successCallback = function(stream) {
video.srcObject = stream;
};
var errorCallback = function(error) {
console.log(error);
};
navigator.mediaDevices.getUserMedia({
audio: false,
video: { facingMode: { ideal: 'environment' } } // prefer rear-facing camera
}).then(successCallback, errorCallback);
}
The code assumes a video
element with id="video"
on the page:
<video id="video" autoplay="true"></video>
It's a good idea to also add WebRTC adapter to your page, so that the code will also work in browsers, which don't support MediaDevices.getUserMedia yet and still require the use of the older navigator.getUserMedia API. Unfortunately, Safari supports neither, so there's still no way to make this work in iOS. Additionally, camera preference isn't respected by all devices, therefore you might need to implement a manual camera selection.
Render Captured Video on Canvas
To render the video on a canvas
instead, we will register a callback at the end of the above initialization procedure using requestAnimationFrame:
requestAnimationFrame(renderFrame);
This will ensure that our method gets called on each repaint. Inside it we will draw the current image from the camera:
var canvas = document.getElementById('canvas');
var context = canvas.getContext('2d');
function renderFrame() {
// re-register callback
requestAnimationFrame(renderFrame);
// set internal canvas size to match HTML element size
canvas.width = canvas.scrollWidth;
canvas.height = canvas.scrollHeight;
if (video.readyState === video.HAVE_ENOUGH_DATA) {
// scale and horizontally center the camera image
var videoSize = { width: video.videoWidth, height: video.videoHeight };
var canvasSize = { width: canvas.width, height: canvas.height };
var renderSize = calculateSize(videoSize, canvasSize);
var xOffset = (canvasSize.width - renderSize.width) / 2;
context.drawImage(video, xOffset, 0, renderSize.width, renderSize.height);
}
}
A couple of things probably require further explanation:
- We need to re-register our callback with
requestAnimationFrame
on every call, because it would otherwise only get called once. - If we allow our
canvas
HTML element to resize, we need to make sure that the size of the internal canvas always matches the current size of the HTML element. This does not happen automatically as the two sizes are independent. Camera captures the video at a fixed resolution. We want to scale it to match the canvas size, while preserving the aspect ratio:
function calculateSize(srcSize, dstSize) { var srcRatio = srcSize.width / srcSize.height; var dstRatio = dstSize.width / dstSize.height; if (dstRatio > srcRatio) { return { width: dstSize.height * srcRatio, height: dstSize.height }; } else { return { width: dstSize.width, height: dstSize.width / srcRatio }; } }
Of course, we can expend renderFrame
to draw anything else on the canvas. Or as I did, to grab the render image from the camera and scan it for QR codes using jsQR:
var imageData = this.context.getImageData(
xOffset, 0, renderSize.width, renderSize.height);
var qrData = jsQR.decodeQRFromImage(
imageData.data, imageData.width, imageData.height);
Stretch Canvas to Full Page
The code above now requires our HTML to contain a canvas
element with id="canvas"
as well:
<div id="container">
<video id="video" autoplay="true"></video>
<canvas id="canvas"></canvas>
</div>
I wrapped both in a single div
container which fills the full size of the parent element, thanks to the following CSS:
#container {
width: 100%;
height: 100%;
}
#video {
display: none; /* user can see video on canvas */
}
#canvas {
width: 100%;
height: 100%;
}
If the parent is the page root, the following CSS will make sure that the canvas will fill the page completely:
html, body {
height: 100%;
}
I created a Plunk, where you can see everything in action, but haven't embedded it, because modern browsers won't allow the use of camera if the whole page is not served using https.