chore: switched to zxing for barcode scanning if native not available af336d9f
Steve · 2026-04-25 13:51 1 file(s) · +55 −20
apps/library/src/templates/admin.html +55 −20
87 87
      {% endif %}
88 88
    </section>
89 89
90 +
    <script src="https://unpkg.com/@zxing/browser@0.1.5/umd/zxing-browser.min.js"></script>
90 91
    <script>
91 92
      async function searchBooks() {
92 93
        const q = document.getElementById('book-query').value.trim();
193 194
194 195
      let scanStream = null;
195 196
      let scanRaf = null;
197 +
      let zxingControls = null;
198 +
199 +
      const hasNativeBarcode = 'BarcodeDetector' in window;
200 +
      const hasZxing = typeof ZXingBrowser !== 'undefined';
196 201
197 202
      (function initScan() {
198 -
        if ('BarcodeDetector' in window && navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
203 +
        if ((hasNativeBarcode || hasZxing) && navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
199 204
          document.getElementById('scan-btn').hidden = false;
200 205
        }
201 206
      })();
206 211
        const status = document.getElementById('scan-status');
207 212
        status.textContent = 'Point camera at barcode';
208 213
        modal.hidden = false;
214 +
215 +
        const onHit = (isbn) => {
216 +
          closeScanner();
217 +
          document.getElementById('book-query').value = isbn;
218 +
          searchBooks();
219 +
        };
220 +
209 221
        try {
210 -
          scanStream = await navigator.mediaDevices.getUserMedia({
211 -
            video: { facingMode: 'environment' }
212 -
          });
213 -
          video.srcObject = scanStream;
214 -
          await video.play();
215 -
          const detector = new BarcodeDetector({ formats: ['ean_13', 'ean_8', 'upc_a'] });
216 -
          const tick = async () => {
217 -
            if (!scanStream) return;
222 +
          let detector = null;
223 +
          if (hasNativeBarcode) {
218 224
            try {
219 -
              const codes = await detector.detect(video);
220 -
              if (codes.length) {
221 -
                const isbn = codes[0].rawValue;
222 -
                closeScanner();
223 -
                document.getElementById('book-query').value = isbn;
224 -
                searchBooks();
225 -
                return;
225 +
              detector = new BarcodeDetector({ formats: ['ean_13', 'ean_8', 'upc_a'] });
226 +
            } catch (_) {
227 +
              detector = null;
228 +
            }
229 +
          }
230 +
231 +
          if (detector) {
232 +
            scanStream = await navigator.mediaDevices.getUserMedia({
233 +
              video: { facingMode: 'environment' }
234 +
            });
235 +
            video.srcObject = scanStream;
236 +
            await video.play();
237 +
            const tick = async () => {
238 +
              if (!scanStream) return;
239 +
              try {
240 +
                const codes = await detector.detect(video);
241 +
                if (codes.length) return onHit(codes[0].rawValue);
242 +
              } catch (_) {}
243 +
              scanRaf = requestAnimationFrame(tick);
244 +
            };
245 +
            tick();
246 +
            return;
247 +
          }
248 +
249 +
          if (hasZxing) {
250 +
            const reader = new ZXingBrowser.BrowserMultiFormatReader();
251 +
            zxingControls = await reader.decodeFromVideoDevice(undefined, video, (result, err, controls) => {
252 +
              if (result) {
253 +
                controls.stop();
254 +
                zxingControls = null;
255 +
                onHit(result.getText());
226 256
              }
227 -
            } catch (_) {}
228 -
            scanRaf = requestAnimationFrame(tick);
229 -
          };
230 -
          tick();
257 +
            });
258 +
            return;
259 +
          }
260 +
261 +
          status.textContent = 'Scanner not supported';
231 262
        } catch (e) {
232 263
          status.textContent = 'Camera unavailable';
233 264
        }
236 267
      function closeScanner() {
237 268
        if (scanRaf) cancelAnimationFrame(scanRaf);
238 269
        scanRaf = null;
270 +
        if (zxingControls) {
271 +
          try { zxingControls.stop(); } catch (_) {}
272 +
          zxingControls = null;
273 +
        }
239 274
        if (scanStream) {
240 275
          scanStream.getTracks().forEach(function(t) { t.stop(); });
241 276
          scanStream = null;