I have a work around.
First on the html side, I check to see if the video is streaming.
function startScanner(callbackName) {
navigator.mediaDevices
.getUserMedia({ video: { facingMode: "environment" } })
.then(function(stream) {
qrResult.hidden = true;
video.setAttribute("playsinline", true); // required to tell iOS safari we don't want fullscreen
video.srcObject = stream;
video.play();
// Check if the video starts playing
videoTimeout = setTimeout(() => {
if (video.readyState !== 4) { // 4 means 'HAVE_ENOUGH_DATA'
handleNoVideo(); // Trigger alternative function
}
}, 5000); // Wait 5 seconds to check if video starts
video.addEventListener("playing", () => {
clearTimeout(videoTimeout); // Clear the timeout if video is playing
const scanInterval = setInterval(() => {
if (!scanning) {
clearInterval(scanInterval); // Stop scanning if the flag is false
return;
}
canvasElement.width = video.videoWidth;
canvasElement.height = video.videoHeight;
canvas.drawImage(video, 0, 0, canvasElement.width, canvasElement.height);
const imageData = canvas.getImageData(0, 0, canvasElement.width, canvasElement.height);
const code = jsQR(imageData.data, canvasElement.width, canvasElement.height);
if (code) {
qrCodeCallback(code.data, callbackName);
scanning = false;
}
}, 500); // Scan every 500ms
});
})
.catch(() => {
handleNoVideo(); // Handle if the user denies camera access
});
}
function handleNoVideo() {
alert("Unable to access the camera. Please check your device settings.");
anvil.call(qrResult,'upload_file')
}
Then I have a file upload button where you can upload an image using the camera and process that image instead.
Python
def upload_file(self):
self.button_scan_qr.visible = False
self.file_loader.visible = True
def button_scan_qr_click(self, **event_args):
"""This method is called when the button is clicked"""
self.start_scanning()
def file_loader_change(self, file, **event_args):
"""This method is called when a new file is loaded into this FileLoader"""
if file.content_type.startswith("image/"):
# Read the file as a URL-safe base64 string
new_file = anvil.image.generate_thumbnail(file, 300) #phones will high definition photos overwhelm the QR processing code
file_data = new_file.get_bytes()
base64_data = base64.b64encode(file_data).decode("utf-8")
# Create a Data URL with the Base64 string
data_url = f"data:{file.content_type};base64,{base64_data}"
# Pass the base64 string to JavaScript for processing
anvil.js.call_js("processImage", data_url)
else:
alert("Please upload a valid image file.")
Javascript
function processImage(base64Image) {
// const canvasElement = document.createElement("canvas");
// const canvas = canvasElement.getContext("2d");
const img = new Image();
img.onload = () => {
// Resize the canvas to match the image dimensions
canvasElement.width = img.width;
canvasElement.height = img.height;
// Draw the image onto the canvas
canvas.drawImage(img, 0, 0, img.width, img.height);
// Extract image data from the canvas
const imageData = canvas.getImageData(0, 0, img.width, img.height);
// Pass the image data to jsQR
const code = jsQR(imageData.data, img.width, img.height);
if (code) {
alert(`QR Code Data: ${code.data}`); // Display the QR code data
} else {
alert("No QR code found in the image.");
}
};
img.src = base64Image; // Set the image source to the base64 string
}
Cheers!