Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support Safari 12.1, fix canvas leak, and improve recognition #214

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 51 additions & 16 deletions src/browser/src/createQRScannerInternal.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ module.exports = function(){
var thisScanCycle = null;
var nextScan = null;
var cancelNextScan = null;
var snapshotCanvas = null;
var snapshotCanvasContext = null;

// standard screen widths/heights, from 4k down to 320x240
// widths and heights are each tested separately to account for screen rotation
Expand Down Expand Up @@ -282,7 +284,16 @@ module.exports = function(){
}).then(function(mediaStream){
activeMediaStream = mediaStream;
var video = getVideoPreview();
video.src = URL.createObjectURL(mediaStream);

// Newer browsers have deprecated `video.src`, so we attempt video.srcObject
// first, and then fall back to video.src if that's not supported,
// as per https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/srcObject#Supporting_fallback_to_the_src_property
try {
video.srcObject = mediaStream;
} catch(error) {
video.src = URL.createObjectURL(mediaStream);
}

success(calcStatus());
}, function(err){
// something bad happened
Expand All @@ -292,38 +303,57 @@ module.exports = function(){
});
}

function getTempCanvasAndContext(videoElement){
var tempCanvas = document.createElement('canvas');
var camera = getCurrentCamera();
tempCanvas.height = camera.height;
tempCanvas.width = camera.width;
var tempCanvasContext = tempCanvas.getContext('2d');
tempCanvasContext.drawImage(videoElement, 0, 0, camera.width, camera.height);
return {
canvas: tempCanvas,
context: tempCanvasContext
};
function updateSnapshotCanvas(videoElement){
// Use the dimensions of the on-screen display, not the camera dimensions.
//
// Since we're snapshotting the content of the html element into an image,
// we want the width/height to reflect the current rotation of the html
// element. Without this, we can run into issues where the image is
// vertically squashed.
var width = videoElement.clientWidth;
var height = videoElement.clientHeight;

// Force the canvas to match the underlying video presentation element,
// to handle cases where the device has changed rotation since the last snapshot
snapshotCanvas.width = width;
snapshotCanvas.height = height;

snapshotCanvasContext.drawImage(videoElement, 0, 0, width, height);
}

function getCurrentImageData(videoElement){
var snapshot = getTempCanvasAndContext(videoElement);
return snapshot.context.getImageData(0, 0, snapshot.canvas.width, snapshot.canvas.height);
updateSnapshotCanvas(videoElement);

return snapshotCanvasContext.getImageData(0, 0, snapshotCanvas.width, snapshotCanvas.height);
}

// take a screenshot of the video preview with a temp canvas
function captureCurrentFrame(videoElement){
return getTempCanvasAndContext(videoElement).canvas.toDataURL('image/png');
updateSnapshotCanvas(videoElement);

return snapshotCanvas.toDataURL('image/png');
}

function initialize(success, error){
if(scanWorker === null){
var workerBlob = new Blob([workerScript],{type: "text/javascript"});
scanWorker = new Worker(URL.createObjectURL(workerBlob));
}

// Create only one in-memory canvas, otherwise memory leaks can lead to
// hundreds of canvases, which then causes the browser to run out of
// canvas memory
if(snapshotCanvas === null){
snapshotCanvas = document.createElement('canvas');
snapshotCanvasContext = snapshotCanvas.getContext('2d');
}

if(!getVideoPreview()){
// prepare DOM (sync)
var videoPreview = document.createElement('video');
videoPreview.setAttribute('autoplay', 'autoplay');
videoPreview.setAttribute('playsinline', 'playsinline');
videoPreview.setAttribute('muted', 'muted');
videoPreview.setAttribute('id', ELEMENTS.preview);
videoPreview.setAttribute('style', 'display:block;position:fixed;top:50%;left:50%;' +
'width:auto;height:auto;min-width:100%;min-height:100%;z-index:' + ZINDEXES.preview +
Expand Down Expand Up @@ -408,6 +438,7 @@ module.exports = function(){
var video = getVideoPreview();
if(video){
video.src = '';
video.srcObject = '';
Copy link

@fujiwaraizuho fujiwaraizuho Mar 22, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'' is not an Object
You should substitute null here.

}
success(calcStatus());
}
Expand All @@ -429,7 +460,11 @@ module.exports = function(){
}
};
thisScanCycle = function(){
scanWorker.postMessage(getCurrentImageData(video));
var imageData = getCurrentImageData(video);
// imageData may be null if we have run out of canvas memory
if (imageData){
scanWorker.postMessage(imageData);
}
if(cancelNextScan !== null){
// avoid race conditions, always clear before starting a cycle
cancelNextScan();
Expand Down