About a year ago we started development of Web based mobile application development targeting to run in mobile web browsers. In short mobile app was about having some tool to locate specific warehouse inventory by scanning QR codes.
Here is the main flow for our application
- Open device native camera from requesting browser HTML5 permission
- Capture QR code and get text from that image
- Send scanned text to the server
- If code exists in database, navigate to that inventory details page.
Seems pretty simple, and it is!
We started this project as a simple React.js app, and used jsQR library to get QR code text out of captured image.
On desktop it worked perfectly. Whenever we tried to scan QR code from laptop webcam, it worked instantly without any issue. So we thought that’s it! BUT it turns out on mobile device processing of captured images with
window.requestAnimationFrame frequency was a hard task for mobile device CPU. So we started to think about tweaking somehow this process to get maximum performance without freezing UI thread.
Idea 1: Getting less image frames
window.requestAnimationFrame callback works with a smallest time that device can handle during the animation. In most of the mobile devices web animation frames are about 20-24 frame/second, which means callback would be triggered every ~50 milliseconds, and on every callback we will capture camera frame with jsQR processing. This is extremely hard task for a mobile device if you consider most of the devices now taking photos with 4K image quality.
We thought, maybe we will stop using
window.requestAnimationFrame and will use
setInterval instead providing more sleep time, to get less images per second.
That actually improved performance! BUT suddenly most of the QR codes recognition stopped working, especially during bad lighting. After some debugging and trying out other apps, we understood that jsQR principle is to parse a lot of images per second in other to catch at least one image that will contain enough pixel data for extracting QR code content. So capturing images with
window.requestAnimationFrame is needed to get more reliable QR code parser app.
Idea 2: Lets use concurrency with WebWorkers
The idea is to have WebWorker available to handle image frame from main application frame, put it in some queue array, and start processing them with jsQR, then when text is found, send back that string to main application thread.
By design in this case when there is a lot of frames captured per second, UI wouldn’t freeze during jsQR sync processing, that part would be handled by separate WebWorker thread.
This worked perfectly!
QR Codes parsing started to work without any issue, even on low performance Android devices.
Most of the web development happening inside single thread, which is OK till some point where you don’t have to have sync operation blocking entire UI, and it is sad that most of the UI developers don’t have experience with parallel programming.
WebWorkers saved our Web project from embedding it into Cordova, which is awesome. But generally, you don’t need to use them for everything. They have some limitations, and can do basic things, especially, then can’t call function from main thread and can’t access to
document or window objects, which means, they are very useful only if you have some sync operations that you want to offload from UI.
My QR Code React component is here Github — Reactive QR