I want to create a PWA which acts as a bar- and QR-code scanner. For detecting QR-codes I use jsQR (see: https://github.com/cozmo/jsQR), for barcodes, I want to use QuaggaJS (see: https://serratus.github.io/quaggaJS/). To select the type of code that should be detected, I have some radio buttons on my site, which call the function "triggerScannerInitialisation()" (see below). Scanning QR-codes is working already, but scanning barcodes causes some problems. The camera image is not loaded properly. If I run the same code on testing site that only uses QuaggaJS, scanning barcodes works as it should do. I assume that opening two camera streams from the same camera may cause a problem. Can anyone give me a hint on how to use both with the same camera stream?
// variables for stopping scanner types on next run
let stopJsQrOnNextRun = true;
function triggerScannerInitialisation() {
// get the selected code type
let codeTypeSelector = document.querySelector('input[name="code_type_selector"]:checked').value;
switch (codeTypeSelector) {
case 'barcode':
stopJsQrOnNextRun = true;
startQuaggaJs();
break;
case 'qr':
stopQuaggaJs();
stopJsQrOnNextRun = false;
startJsQr();
break;
default:
return false;
}
}
function startQuaggaJs() {
document.getElementById("barcode_camera_div").hidden = false;
Quagga.init({
inputStream: {
name: "Live",
type: "LiveStream",
target: document.querySelector('#barcode_camera_div'),
constraints: {
width: 480,
height: 320,
facingMode: "environment"
},
},
decoder: {
readers: [
"code_128_reader",
"ean_reader",
"ean_8_reader",
"code_39_reader",
"code_39_vin_reader",
"codabar_reader",
"upc_reader",
"upc_e_reader",
"i2of5_reader"
],
debug: {
showCanvas: true,
showPatches: true,
showFoundPatches: true,
showSkeleton: true,
showLabels: true,
showPatchLabels: true,
showRemainingPatchLabels: true,
boxFromPatches: {
showTransformed: true,
showTransformedBox: true,
showBB: true
}
}
},
}, function (err) {
if (err) {
console.log(err);
return
}
console.log("Initialization finished. Ready to start");
Quagga.start();
});
Quagga.onProcessed(function (result) {
var drawingCtx = Quagga.canvas.ctx.overlay,
drawingCanvas = Quagga.canvas.dom.overlay;
if (result) {
if (result.boxes) {
drawingCtx.clearRect(0, 0, parseInt(drawingCanvas.getAttribute("width")), parseInt(drawingCanvas.getAttribute("height")));
result.boxes.filter(function (box) {
return box !== result.box;
}).forEach(function (box) {
Quagga.ImageDebug.drawPath(box, { x: 0, y: 1 }, drawingCtx, { color: "green", lineWidth: 2 });
});
}
if (result.box) {
Quagga.ImageDebug.drawPath(result.box, { x: 0, y: 1 }, drawingCtx, { color: "#00F", lineWidth: 2 });
}
if (result.codeResult && result.codeResult.code) {
Quagga.ImageDebug.drawPath(result.line, { x: 'x', y: 'y' }, drawingCtx, { color: 'red', lineWidth: 3 });
}
}
});
Quagga.onDetected(function (result) {
console.log("Barcode detected and processed : [" + result.codeResult.code + "]", result);
});
}
function stopQuaggaJs() {
// stop quagga JS
Quagga.stop();
document.getElementById("barcode_camera_div").hidden = true;
}
function startJsQr() {
let video = document.createElement("video");
let canvasElement = document.getElementById("canvas");
let canvas = canvasElement.getContext("2d");
let loadingMessage = document.getElementById("loadingMessage");
function drawLine(begin, end, color) {
canvas.beginPath();
canvas.moveTo(begin.x, begin.y);
canvas.lineTo(end.x, end.y);
canvas.lineWidth = 4;
canvas.strokeStyle = color;
canvas.stroke();
}
// Use facingMode: environment to attemt to get the front camera on phones
navigator.mediaDevices.getUserMedia({video: {facingMode: "environment"}}).then(function (stream) {
video.srcObject = stream;
video.setAttribute("playsinline", true); // required to tell iOS safari we don't want fullscreen
video.play();
console.log("JSQR triggered");
requestAnimationFrame(tickQRcode);
});
function tickQRcode() {
loadingMessage.innerText = "⌛ Video laden...";
if (video.readyState === video.HAVE_ENOUGH_DATA) {
loadingMessage.hidden = true;
canvasElement.hidden = false;
canvasElement.height = video.videoHeight;
canvasElement.width = video.videoWidth;
canvas.drawImage(video, 0, 0, canvasElement.width, canvasElement.height);
let imageData = canvas.getImageData(0, 0, canvasElement.width, canvasElement.height);
let code = jsQR(imageData.data, imageData.width, imageData.height, {
inversionAttempts: "dontInvert",
});
if (code) {
drawLine(code.location.topLeftCorner, code.location.topRightCorner, "#FF3B58");
drawLine(code.location.topRightCorner, code.location.bottomRightCorner, "#FF3B58");
drawLine(code.location.bottomRightCorner, code.location.bottomLeftCorner, "#FF3B58");
drawLine(code.location.bottomLeftCorner, code.location.topLeftCorner, "#FF3B58");
codeFound(code.data, 'qr');
}
}
if (!stopJsQrOnNextRun) {
requestAnimationFrame(tickQRcode);
} else {
stopJsQr();
}
}
function stopJsQr() {
// stop the stream
video.srcObject.getTracks().forEach(function (track) {
if (track.readyState === 'live') {
track.stop();
}
});
// remove HTML element properties
let canvasElement = document.getElementById('canvas');
canvasElement.setAttribute('hidden', 1);
canvasElement.removeAttribute('height');
canvasElement.removeAttribute('width');
}
}
Thank you for your help!
Related
In my Carousel, there is an option to show more than 1 item at a time. It is activated by selecting the number of items you wish to show (max 4). The code refers to this as perView.
Items to display example
This carousel is glide.js, so everything is modular. I have 2 things of note getting activated, the Controls (next/prev button) and Breakpoints (think media queries).
The goal is to turn off the controls in mobile only if perView equals anything other than NaN (not a number)... When it is inactive, it returns NaN... so I guess the comment would be:
if perView does not equal NaN, then controls = false
and because i need this in a mobile breakpoint, I would put the code in the breakpoint bracket.
import AEM from 'base/js/aem';
import Glide, { Controls, Breakpoints, Autoplay, Keyboard, Swipe } from '#glidejs/glide/dist/glide.modular.esm';
class Carousel extends AEM.Component {
init() {
this.initCarousel();
}
initCarousel() {
const el = this.element;
const props = this.props;
const pauseButton = this.element.querySelector('.pause-button');
let perView = parseInt(props.cmpPerView, 10);
let cmpDelay = parseInt(props.cmpDelay, 10);
let autoPlay = true;
if (this.props.cmpAutoplay === 'false' ||
this.props.carouselAutoplay === 'false' ||
!Number.isNaN(perView) && perView < 1 ||
window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
autoPlay = false;
}
this.modules = {
Controls: Controls,
Breakpoints: Breakpoints,
Autoplay: Autoplay,
Keyboard: Keyboard,
Swipe: Swipe
};
this.options = {
type: props.cmpType,
perView: Number.isNaN(perView) ? 1 : perView,
autoplay: autoPlay ? cmpDelay : false,
peek: 0,
keyboard: true,
animationDuration: 400,
rewind: true,
breakpoints: {
768: {
perView: Number.isNaN(perView) || perView === 1 ? 1 : 3,
},
578: {
peek: perView > 1 ? 125 : 0,
perView: 1,
controls: perView > 1,
},
375: {
peek: perView > 1 ? 85 : 0,
perView: 1,
}
}
};
const glide = new Glide(el, this.options);
if (pauseButton && autoPlay) {
const pauseClasses = pauseButton.classList;
glide.on('play', () => {
pauseClasses.add('fa-pause');
pauseClasses.remove('fa-play');
});
glide.on('pause', () => {
pauseClasses.add('fa-play');
pauseClasses.remove('fa-pause');
});
pauseButton.addEventListener('click', () => {
if (pauseClasses.contains('fa-play')) {
glide.play();
} else {
glide.pause();
}
});
} else if (pauseButton) {
pauseButton.remove();
}
if (window.Granite && window.Granite.author && window.Granite.author.MessageChannel) {
/*
* Editor message handling:
* - subscribe to "cmp.panelcontainer" message requests sent by the editor frame
* - check that the message data panel container type is correct and that the id (path) matches this specific
* - Carousel component. If so, route the "navigate" operation to enact a navigation of the Carousel based
* - on index data
*/
this.element.querySelectorAll('.glide__slide').forEach(e => {
if (!e.classList.contains('glide__slide--active')) {
e.classList.add('visually-hidden');
}
});
window.CQ = window.CQ || {};
window.CQ.CoreComponents = window.CQ.CoreComponents || {};
window.CQ.CoreComponents.MESSAGE_CHANNEL = window.CQ.CoreComponents.MESSAGE_CHANNEL ||
new window.Granite.author.MessageChannel('cqauthor', window);
window.CQ.CoreComponents.MESSAGE_CHANNEL.subscribeRequestMessage('cmp.panelcontainer', message => {
if (message.data && message.data.type === 'cmp-carousel' &&
message.data.id === el.dataset.cmpPanelcontainerId) {
if (message.data.operation === 'navigate') {
let index = message.data.index;
if (index < 0) {
return;
}
this.element.querySelectorAll('.glide__slide').forEach((slide, i) => {
if (i === index) {
slide.classList.add('glide__slide--active');
slide.classList.remove('visually-hidden');
} else {
slide.classList.remove('glide__slide--active');
slide.classList.add('visually-hidden');
}
});
}
}
});
} else {
glide.mount(this.modules);
}
}
}
export { Carousel };
So I have
controls: perView > 1
in the 578 breakpoint... but it doesn't work.
I have create a simple animation screen using phaser and its rendering perfect and invoking all pointer events like pointerover, pointerout, pointerup and pointerdown on button btn as expected for browser. But when I render same game using WebView its rendering screen but its not invoking any pointer events. To confirm JavaScript is working I have tested by adding eventlistner on window and it was working.
var config = {
type: Phaser.CANVAS,
width: 650,
height: 900,
canvas: document.getElementById('myCustomCanvas'),
scale: {
mode: Phaser.Scale.FIT,
width: 750,
height: 1334,
parent: 'game'
},
physics: {
default: 'arcade',
arcade: {
gravity: { y: 0 }
}
},
scene : {
preload: function(){game.preload(this, 'asset_url');},
create: function(){game.create(this);},
update: function(){game.update(this);},
}
};
function game(config) {
this.start = function(config)
{
this.phaserGame = new Phaser.Game(config);
};
this.create = function()
{
var btn = this.scene.add.sprite(375, 665, 'btn');
btn.setInteractive()
.on('pointerover', () => {
console.log('pointerover');
})
.on('pointerout', () => {
console.log('pointerout');
})
.on('pointerdown', () => {
console.log('pointerdown');
})
.on('pointerup', () => {
console.log('pointerup');
});
};
}
I am using Xamarin Forms WebView to render game in mobile. My setting for WebView is as follow.
var webView = CreateNativeControl();
webView.SetWebViewClient(new HTMLGameWebClient(this));
webView.Settings.LightTouchEnabled = true;
webView.Settings.JavaScriptEnabled = true;
webView.Settings.DomStorageEnabled = true;
webView.Settings.MediaPlaybackRequiresUserGesture = false;
SetNativeControl(webView);
I also have conditional CSS which is used only if canvas is rendering in mobile.
canvas {
width: 100vw !important;
height: unset;
}
Thanks in advance for any helps or suggestions.
The goal is to preview a stream of the computer's web camera in a html video element and at the same time record audio and the Chrome tab showing another video and the preview using the MediaRecorder API, navigator.mediaDevices.getDisplayMedia and navigator.mediaDevice.getUserMedia.
The implementation runs well using Chrome and Ubuntu/Windows setup but it becomes laggy when it runs on macOS. Javascript code is provided below. Any ideas on why the implementation works bad specifically on macOS?
I have tried to change codecs from vp9 to vp8, lower the framerate to 24 and the resolution is already reduced. If I go lower the quality gets to bad.
I have tried reducing the quality of the streams and recording without any performance increase on macOS.
export default function VideoRecord(props) {
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
async function onLoad() {
try {
//some code
} catch (e) {
alert(e);
}
setIsLoading(false);
}
onLoad();
}, [props.match.params.id]);
var mediaRecorder;
var recordedChunks = [];
var videoStream;
var cameraStream;
var combinedStream;
var BLOB;
var cameraElem;
var videoElem;
$(function() {
cameraElem = document.querySelector('#camera');
videoElem = document.querySelector('#video');
})
var videoMediaOptions = {
video: {
width: 720,
height: 480,
aspectRatio: 1920/1080,
cursor: "never",
frameRate: 30
},
audio: false
};
var cameraMediaOptions = {
video: {
width: 300,
height: 300
},
audio: true
}
async function startCapture() {
try {
videoStream = await navigator.mediaDevices.getDisplayMedia(videoMediaOptions);
cameraStream = await navigator.mediaDevices.getUserMedia(cameraMediaOptions);
cameraElem.srcObject = cameraStream;
startRec();
} catch(err) {
console.error("Error: " + err);
}
}
function handleDataAvailable(event) {
if (event.data.size > 0) {
recordedChunks.push(event.data);
createBlob();
}
}
function startRec () {
var options = { mimeType: "video/webm; codecs=vp9" };
combinedStream = new MediaStream([...cameraStream.getAudioTracks(), ...videoStream.getTracks()]);
mediaRecorder = new MediaRecorder(combinedStream, options);
mediaRecorder.ondataavailable = handleDataAvailable;
mediaRecorder.start();
}
function createBlob() {
BLOB = new Blob(recordedChunks, {
type: "video/webm"
});
var url = URL.createObjectURL(BLOB);
videoElem.srcObject = null;
videoElem.src = url;
videoElem.muted = false;
}
function stopCapture() {
let cameraTracks = cameraStream.getTracks();
cameraTracks.forEach(cameraTracks => cameraTracks.stop());
let tracks = videoStream.getTracks();
tracks.forEach(track => track.stop());
mediaRecorder.stop();
}
function renderPage(){
return (
<div className="videoWrapper">
<video preload="auto" id="video"></video>
<video id="camera" autoPlay muted></video>
<Button id="startBtn" onClick={startCapture}>Start</Button>
<Button id="stopBtn" onClick={stopCapture}>Stop</Button>
</div>
)
}
return (
<div id="videoRecordID" className="VideoRecord">
{!isLoading && <div>Loading...</div>}
{!isLoading && renderPage()}
</div>
);
}
I am implementing a picture upload functionality to my app which I am developing with Ionic 4. I'm using the native plugin camera and a few others to do the following:
async selectImage() {
const actionSheet = await this.actionsheet.create({
header: "Select Image source",
buttons: [{
text: 'Load from Library',
handler: () => {
this.takePicture(this.camera.PictureSourceType.PHOTOLIBRARY);
}
},
{
text: 'Use Camera',
handler: () => {
this.takePicture(this.camera.PictureSourceType.CAMERA);
}
},
{
text: 'Cancel',
role: 'cancel'
}
]
});
await actionSheet.present();
}
takePicture(sourceType: PictureSourceType) {
var options: CameraOptions = {
quality: 100,
sourceType: sourceType,
saveToPhotoAlbum: false,
correctOrientation: true
};
this.camera.getPicture(options).then(imagePath => {
var currentName = imagePath.substr(imagePath.lastIndexOf('/') + 1);
var correctPath = imagePath.substr(0, imagePath.lastIndexOf('/') + 1);
this.copyFileToLocalDir(correctPath, currentName, this.createFileName());
});
}
copyFileToLocalDir(namePath, currentName, newFileName) {
this.file.copyFile(namePath, currentName, this.file.dataDirectory, newFileName).then(success => {
this.presentToast('Dispongo a actualizar.');
this.updateStoredImages(newFileName);
}, error => {
// this.presentToast('Error while storing file.');
});
}
updateStoredImages(name) {
this.storage.get(STORAGE_KEY).then(images => {
let arr = JSON.parse(images);
if (!arr) {
let newImages = [name];
this.storage.set(STORAGE_KEY, JSON.stringify(newImages));
} else {
arr.push(name);
this.storage.set(STORAGE_KEY, JSON.stringify(arr));
}
let filePath = this.file.dataDirectory + name;
let resPath = this.pathForImage(filePath);
let newEntry = {
name: name,
path: resPath,
filePath: filePath
};
this.images = [newEntry, ...this.images];
this.ref.detectChanges(); // trigger change detection cycle
});
}
So, in the action sheet, when I press the first option (Load from Library) it opens the library and I can choose the picture without any problem. When I press ok, it throws an error: the error expected from the copyFileToLocalDir. However, if I do the same with the second option (Use Camera) and I take a photo with the camera, it loads it fine and I can store it later.
I can't find the problem, please help.
im using this code using ionic 3 and it's working fine .
and after i chose one image it will be uploading to firebase and on the same time view it at page.html
app.module.ts
you have to import
import { Camera } from "#ionic-native/camera";
import { File } from "#ionic-native/file";
and added them #providers
then use this code at page.ts which you will chose one image :
html view
<button ion-button full (click)="openGallery()">open gallery</button>
<img [src]="camel_profile_image_path" />
ts page
import { Camera, CameraOptions } from "#ionic-native/camera";
private camera: Camera,
async openGallery() {
try {
const opstions: CameraOptions = {
quality: 100,
targetHeight: 600,
targetWidth: 600,
destinationType: this.camera.DestinationType.DATA_URL,
encodingType: this.camera.EncodingType.JPEG,
mediaType: this.camera.MediaType.PICTURE,
sourceType: this.camera.PictureSourceType.PHOTOLIBRARY,
correctOrientation: true
}
const result = await this.camera.getPicture(opstions);
const image = 'data:image/jpeg;base64,' + result;
const pictures = storage().ref('Profile Images/' + this.randomNumber + '.jpg');
pictures.putString(image, 'data_url');
this.base64Image = image;
this.camel_profile_image_path = this.randomNumber; // view the image on html page
this.slidetothis();
} catch (error) {
console.error(error);
}
}
Now I develop a barcode generator app.
Here I used html2pdf.js
I used settimeout for bulk save.
But when I input 13 barcodes and 358 barcodes, the rendering speed is different
13 barcodes : average 280ms
358 barcodes : average 6500ms
in case of 13 barcodes
in case of 358 barcodes
And my code is below
async function timerCallback(){
var elem = $('#'+elemlist[elemindex]);
var index = elem.attr('id');
var value = elem.attr('value');
console.log(index,value);
var svgelem = document.getElementById(index);
var html = svgelem.outerHTML;
var path = dir+"/"+bar_type+"_"+value+".pdf";
console.log(path);
modal.updateModal(elemindex);
var opt = {
margin: 0,
filename: bar_type+"_"+value+".pdf",
image: { type: 'jpeg', quality: 0.98 },
html2canvas: { scale: 30 },
jsPDF: { orientation : 'l', unit: 'px', format: [$('#'+index).width(), $('#'+index).height()+10]}
};
// New Promise-based usage:
var worker = new html2pdf();
await worker.set(opt).from(html).outputPdf().then(async function(e){
var buf = new Buffer(e, 'binary');
await fs.writeFile(path, buf, function(err) {
if(err) {
return console.log(err);
}
console.log("The file was saved!");
});
});
setTimeout(() => {
if(elemindex<elemlist.length)
{
timerCallback();
elemindex++;
}
else{
modal.removeModal();
}
}, 1300);
}
timerCallback();
I don't know what is wrong with my code.