diff --git a/packages/qr-code-scanner/rollup.config.js b/packages/qr-code-scanner/rollup.config.js
index 83266356c8b804099630e5bd4e88f6b0f0855126..c0dbd55cc75ea90e600807d23b448f4ed4ca01d1 100644
--- a/packages/qr-code-scanner/rollup.config.js
+++ b/packages/qr-code-scanner/rollup.config.js
@@ -72,6 +72,7 @@ export default (async () => {
                     {src: 'assets/index.html', dest: 'dist'},
                     {src: 'assets/favicon.ico', dest: 'dist'},
                     {src: await getPackagePath('dbp-common', 'assets/icons/*.svg'), dest: 'dist/local/dbp-common/icons'},
+                    {src: await getPackagePath('qr-scanner', 'qr-scanner-worker.*'), dest: 'dist/local/qr-code-scanner'},
                 ]
             }),
             (process.env.ROLLUP_WATCH === 'true') ? serve({
diff --git a/packages/qr-code-scanner/src/qr-code-scanner.js b/packages/qr-code-scanner/src/qr-code-scanner.js
index f8504b21448bb48f2fc89a69f5f27a4b1ac6ca09..5b7e42f2c58a3a3106bf0fafc99f409b94a9337b 100644
--- a/packages/qr-code-scanner/src/qr-code-scanner.js
+++ b/packages/qr-code-scanner/src/qr-code-scanner.js
@@ -5,9 +5,11 @@ import * as commonStyles from 'dbp-common/styles';
 import {ScopedElementsMixin} from '@open-wc/scoped-elements';
 import {Icon, MiniSpinner} from 'dbp-common';
 import {classMap} from 'lit-html/directives/class-map.js';
+import * as commonUtils from 'dbp-common/utils';
 import jsQR from "jsqr";
 import {getIconSVGURL} from 'dbp-common';
 import {Mutex} from 'async-mutex';
+import QrScanner from 'qr-scanner';
 
 
 /**
@@ -98,6 +100,42 @@ async function createVideoElement(deviceId) {
 }
 
 
+class jsQRScanner { // eslint-disable-line no-unused-vars
+    constructor() {
+    }
+
+    async scan(canvas, x, y, width, height) {
+        let imageData = canvas.getContext("2d").getImageData(x, y, width, height);
+        const code = jsQR(imageData.data, imageData.width, imageData.height, {
+            inversionAttempts: "dontInvert",
+        });
+        if (code === null)
+            return null;
+        return {'data': code.data};
+    }
+}
+
+
+class QRScanner {
+    constructor() {
+        QrScanner.WORKER_PATH = commonUtils.getAssetURL('qr-code-scanner', 'qr-scanner-worker.min.js');
+        this._engine = null;
+        this._canvas = document.createElement("canvas");
+    }
+
+    async scan(canvas, x, y, width, height) {
+        if (this._engine === null) {
+            this._engine = await QrScanner.createQrEngine(QrScanner.WORKER_PATH);
+        }
+        try {
+            return {data: await QrScanner.scanImage(canvas, {x: x, y: y, width: width, height: height}, this._engine, this._canvas)};
+        } catch (e) {
+            return null;
+        }
+    }
+}
+
+
 export class QrCodeScanner extends ScopedElementsMixin(DBPLitElement) {
     constructor() {
         super();
@@ -214,7 +252,9 @@ export class QrCodeScanner extends ScopedElementsMixin(DBPLitElement) {
         let lastCode = null;
         let lastSentData = null;
 
-        const tick = () => {
+        let detector = new QRScanner();
+
+        const tick = async () => {
             this._requestID = null;
             if (video.readyState === video.HAVE_ENOUGH_DATA) {
                 this._loading = false;
@@ -242,10 +282,7 @@ export class QrCodeScanner extends ScopedElementsMixin(DBPLitElement) {
                 let shouldAnalyze = Math.abs(lastVideoScanTime - video.currentTime) >= 1/3;
                 if (shouldAnalyze) {
                     lastVideoScanTime = video.currentTime;
-                    let imageData = canvas.getImageData(maskStartX, maskStartY, maskWidth, maskHeight);
-                    code = jsQR(imageData.data, imageData.width, imageData.height, {
-                        inversionAttempts: "dontInvert",
-                    });
+                    code = await detector.scan(canvasElement, maskStartX, maskStartY, maskWidth, maskHeight);
                     lastCode = code;
                 } else {
                     code = lastCode;