@gueshbzh-perso/scanner 0.9.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -2
- package/dist/core.d.ts +12 -4
- package/dist/core.d.ts.map +1 -1
- package/dist/core.js +24 -11
- package/dist/core.js.map +1 -1
- package/dist/engine.d.ts +13 -2
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +24 -4
- package/dist/engine.js.map +1 -1
- package/dist/native.d.ts +48 -0
- package/dist/native.d.ts.map +1 -0
- package/dist/native.js +114 -0
- package/dist/native.js.map +1 -0
- package/dist/reader.d.ts +43 -1
- package/dist/reader.d.ts.map +1 -1
- package/dist/reader.js +211 -30
- package/dist/reader.js.map +1 -1
- package/dist/types.d.ts +31 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/wasm/scanner_wasm.d.ts +20 -0
- package/dist/wasm/scanner_wasm.js +53 -0
- package/dist/wasm/scanner_wasm_bg.wasm +0 -0
- package/dist/wasm/scanner_wasm_bg.wasm.d.ts +2 -0
- package/package.json +1 -1
- package/src/core.ts +24 -11
- package/src/engine.ts +43 -5
- package/src/native.ts +147 -0
- package/src/reader.ts +231 -34
- package/src/types.ts +31 -2
package/src/reader.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// BarcodeReader: camera preview + frame pipeline + engine, wired together.
|
|
2
2
|
import { capabilities, listCameras, openCamera, rememberCamera, setTorch, setZoom } from './camera.js';
|
|
3
3
|
import { pickEngineOptions } from './core.js';
|
|
4
|
-
import { createEngine, type Engine } from './engine.js';
|
|
4
|
+
import { createEngine, type Engine, type FrameReply } from './engine.js';
|
|
5
5
|
import { Feedback } from './feedback.js';
|
|
6
6
|
import {
|
|
7
7
|
clampRegion,
|
|
@@ -11,22 +11,36 @@ import {
|
|
|
11
11
|
regionToVideoRect,
|
|
12
12
|
videoToElement,
|
|
13
13
|
} from './geometry.js';
|
|
14
|
-
import { CanvasGrabber, createGrabber, type FrameGrabber } from './grabber.js';
|
|
14
|
+
import { CanvasGrabber, createGrabber, type FrameGrabber, type Rect } from './grabber.js';
|
|
15
|
+
import type { FrameOutcome } from './core.js';
|
|
16
|
+
import { NativeDetector } from './native.js';
|
|
15
17
|
import { Preview } from './overlay.js';
|
|
16
18
|
import type { Detection, EngineOptions, ReaderEvents, ReaderOptions, ReaderStats, Region } from './types.js';
|
|
17
19
|
|
|
18
20
|
const DEFAULT_REGION: Region = { x: 0.1, y: 0.2, width: 0.8, height: 0.6 };
|
|
19
21
|
|
|
22
|
+
/**
|
|
23
|
+
* With the native detector and `processing.engineFallback`, delay between two engine frames while the
|
|
24
|
+
* native decoder finds nothing (~3 fallbacks per second). A code that only
|
|
25
|
+
* the fallbacks read is still found within a couple of seconds (the
|
|
26
|
+
* strategies share CPU time, the cheap Linear one runs most often), while
|
|
27
|
+
* the workers stay idle most of the time. After a frame where the engine saw
|
|
28
|
+
* a code, the next one is immediate: a 1D code needs a second read.
|
|
29
|
+
*/
|
|
30
|
+
const FALLBACK_INTERVAL_MS = 300;
|
|
31
|
+
|
|
20
32
|
/** A frame read from the video, waiting for (or being decoded by) the engine. */
|
|
21
33
|
interface GrabbedFrame {
|
|
22
34
|
buffer: ArrayBuffer;
|
|
23
35
|
width: number;
|
|
24
36
|
height: number;
|
|
25
37
|
/** Region of the video the frame was cropped from (to map points back). */
|
|
26
|
-
rect:
|
|
38
|
+
rect: Rect;
|
|
27
39
|
grabMs: number;
|
|
28
40
|
/** Grab order: decodes finish out of order when several workers run. */
|
|
29
41
|
seq: number;
|
|
42
|
+
/** Run the engine's fast path (`false`: the native detector already searched this frame). */
|
|
43
|
+
fastPath: boolean;
|
|
30
44
|
}
|
|
31
45
|
|
|
32
46
|
type Listener<T> = (payload: T) => void;
|
|
@@ -55,8 +69,20 @@ export class BarcodeReader {
|
|
|
55
69
|
private engineOptions: EngineOptions;
|
|
56
70
|
private running = false;
|
|
57
71
|
private paused = false;
|
|
58
|
-
/** A grab (GPU readback)
|
|
72
|
+
/** A frame is being acquired: grab (GPU readback) and native detection. */
|
|
59
73
|
private grabbing = false;
|
|
74
|
+
/** Native `BarcodeDetector` used as the fast path, when enabled and available. */
|
|
75
|
+
private native: NativeDetector | undefined;
|
|
76
|
+
/** Bumped when the native detector is replaced: results of the old one are dropped. */
|
|
77
|
+
private nativeGeneration = 0;
|
|
78
|
+
/** The native detector failed once: not used again by this reader. */
|
|
79
|
+
private nativeFailed = false;
|
|
80
|
+
/** Native detector active: a fallback-only frame is being grabbed or decoded. */
|
|
81
|
+
private fallbackInFlight = false;
|
|
82
|
+
/** Native detector active: earliest time of the next fallback-only frame. */
|
|
83
|
+
private nextFallbackAt = 0;
|
|
84
|
+
/** Which decoder drew the current highlights: each one only clears its own. */
|
|
85
|
+
private highlightSource: 'native' | 'engine' | null = null;
|
|
60
86
|
/** Frames being decoded (at most `engine.concurrency`). */
|
|
61
87
|
private decoding = 0;
|
|
62
88
|
/** Sequence number of the last grabbed frame, and of the last one drawn. */
|
|
@@ -77,13 +103,26 @@ export class BarcodeReader {
|
|
|
77
103
|
private torchOn = false;
|
|
78
104
|
private buffers: ArrayBuffer[] = [];
|
|
79
105
|
private frameHandle = 0;
|
|
80
|
-
private perf = {
|
|
106
|
+
private perf = {
|
|
107
|
+
frames: 0,
|
|
108
|
+
engineFrames: 0,
|
|
109
|
+
since: 0,
|
|
110
|
+
fps: 0,
|
|
111
|
+
engineFps: 0,
|
|
112
|
+
decodeMs: 0,
|
|
113
|
+
grabMs: 0,
|
|
114
|
+
nativeMs: 0,
|
|
115
|
+
w: 0,
|
|
116
|
+
h: 0,
|
|
117
|
+
};
|
|
81
118
|
|
|
82
119
|
private constructor(
|
|
83
120
|
private engine: Engine,
|
|
84
121
|
private options: ReaderOptions,
|
|
85
122
|
host: HTMLElement,
|
|
123
|
+
native: NativeDetector | undefined,
|
|
86
124
|
) {
|
|
125
|
+
this.native = native;
|
|
87
126
|
this.region = clampRegion(options.regionOfInterest ?? DEFAULT_REGION);
|
|
88
127
|
this.engineOptions = pickEngineOptions(options);
|
|
89
128
|
this.preview = new Preview(host, options.overlay);
|
|
@@ -101,8 +140,10 @@ export class BarcodeReader {
|
|
|
101
140
|
typeof options.host === 'string' ? document.querySelector<HTMLElement>(options.host) : options.host;
|
|
102
141
|
if (!host) throw new Error(`BarcodeReader: host element not found (${String(options.host)})`);
|
|
103
142
|
const engine = await createEngine(options);
|
|
104
|
-
|
|
105
|
-
|
|
143
|
+
const engineOptions = pickEngineOptions(options);
|
|
144
|
+
await engine.configure(engineOptions);
|
|
145
|
+
const native = await createNative(options, engineOptions);
|
|
146
|
+
return new BarcodeReader(engine, options, host, native);
|
|
106
147
|
}
|
|
107
148
|
|
|
108
149
|
/** Video input devices (labels are empty until permission is granted). */
|
|
@@ -134,6 +175,9 @@ export class BarcodeReader {
|
|
|
134
175
|
backend: this.grabber.kind,
|
|
135
176
|
worker: this.engine.inWorker,
|
|
136
177
|
workers: this.engine.concurrency,
|
|
178
|
+
native: this.native !== undefined,
|
|
179
|
+
nativeMs: this.perf.nativeMs,
|
|
180
|
+
engineFps: this.perf.engineFps,
|
|
137
181
|
};
|
|
138
182
|
}
|
|
139
183
|
|
|
@@ -282,7 +326,11 @@ export class BarcodeReader {
|
|
|
282
326
|
async configure(options: EngineOptions): Promise<void> {
|
|
283
327
|
this.assertAlive();
|
|
284
328
|
this.engineOptions = { ...this.engineOptions, ...options };
|
|
329
|
+
// Detections in flight were made for the old formats: dropped.
|
|
330
|
+
const generation = ++this.nativeGeneration;
|
|
285
331
|
await this.engine.configure(this.engineOptions);
|
|
332
|
+
const native = this.nativeFailed ? undefined : await createNative(this.options, this.engineOptions);
|
|
333
|
+
if (generation === this.nativeGeneration) this.native = native;
|
|
286
334
|
}
|
|
287
335
|
|
|
288
336
|
/** Allow `text` to be reported again right away. */
|
|
@@ -352,13 +400,14 @@ export class BarcodeReader {
|
|
|
352
400
|
* Back-pressure: at most one grab in flight and one frame waiting for a
|
|
353
401
|
* free worker. While a frame waits, no new grab starts, so frames are
|
|
354
402
|
* skipped, never queued, and the waiting frame is at most one decode old.
|
|
403
|
+
* The native detection is part of the grab stage, so the same rule keeps
|
|
404
|
+
* at most one `detect()` in flight.
|
|
355
405
|
*/
|
|
356
406
|
private tick(): void {
|
|
357
407
|
if (!this.running) return;
|
|
358
408
|
if (!this.grabbing && !this.pending && !this.paused && this.preview.video.videoWidth > 0) {
|
|
359
409
|
this.grabbing = true;
|
|
360
|
-
this.
|
|
361
|
-
.then((frame) => this.enqueue(frame))
|
|
410
|
+
this.acquire()
|
|
362
411
|
.catch((err) => this.handleFrameError(err))
|
|
363
412
|
.finally(() => (this.grabbing = false));
|
|
364
413
|
}
|
|
@@ -366,11 +415,87 @@ export class BarcodeReader {
|
|
|
366
415
|
this.schedule();
|
|
367
416
|
}
|
|
368
417
|
|
|
369
|
-
|
|
418
|
+
/**
|
|
419
|
+
* Grab stage. With the native `BarcodeDetector`, the frame is searched
|
|
420
|
+
* natively first, straight from the `<video>` (Chrome reads it outside the
|
|
421
|
+
* page's threads): no grab of our own. Codes it finds are committed right
|
|
422
|
+
* away. When it finds nothing and `processing.engineFallback` is on, the
|
|
423
|
+
* engine gets a frame **from time to time** (`FALLBACK_INTERVAL_MS`),
|
|
424
|
+
* without its fast path, for one fallback strategy: a safety net for what
|
|
425
|
+
* the native decoder has no preprocessing for (laser engraving,
|
|
426
|
+
* light-on-dark codes, `micro_qr`…). Off by default (choice of the project
|
|
427
|
+
* after testing on a Galaxy A14, where the native decoder read codes very
|
|
428
|
+
* well): the net mostly costs CPU and battery.
|
|
429
|
+
* Grabbing and decoding every frame as well would double the reads of the
|
|
430
|
+
* frame and keep the workers busy on frames the native decoder handles,
|
|
431
|
+
* costing battery and CPU it also needs.
|
|
432
|
+
*/
|
|
433
|
+
private async acquire(): Promise<void> {
|
|
434
|
+
const native = this.native;
|
|
435
|
+
const rect = this.videoRect();
|
|
436
|
+
if (!native) {
|
|
437
|
+
this.enqueue(await this.grabFrame(rect, true));
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
440
|
+
const generation = this.nativeGeneration;
|
|
441
|
+
const t0 = performance.now();
|
|
442
|
+
let codes: Detection[];
|
|
443
|
+
try {
|
|
444
|
+
codes = await native.detect(this.preview.video, rect);
|
|
445
|
+
} catch (err) {
|
|
446
|
+
this.disableNative(err);
|
|
447
|
+
this.enqueue(await this.grabFrame(rect, true));
|
|
448
|
+
return;
|
|
449
|
+
}
|
|
450
|
+
const nativeMs = performance.now() - t0;
|
|
451
|
+
// Replaced by `configure()` meanwhile: results made for the old formats.
|
|
452
|
+
if (generation !== this.nativeGeneration || !this.running || this.paused) return;
|
|
453
|
+
|
|
454
|
+
if (codes.length === 0 && this.options.processing?.engineFallback === true && this.fallbackDue()) {
|
|
455
|
+
this.fallbackInFlight = true;
|
|
456
|
+
this.updatePerf({ nativeMs, counted: false });
|
|
457
|
+
try {
|
|
458
|
+
this.enqueue(await this.grabFrame(rect, false));
|
|
459
|
+
} catch (err) {
|
|
460
|
+
this.fallbackInFlight = false;
|
|
461
|
+
throw err;
|
|
462
|
+
}
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
const outcome = await this.engine.commitExternal(codes, nativeMs);
|
|
466
|
+
if (generation !== this.nativeGeneration || !this.running || this.paused) return;
|
|
467
|
+
this.failures = 0;
|
|
468
|
+
this.updatePerf({ nativeMs });
|
|
469
|
+
// Native points are already in video pixels.
|
|
470
|
+
this.present(outcome, ++this.grabbed, 'native', (d) => d);
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
/** The engine may decode a frame for its fallbacks (native detector active). */
|
|
474
|
+
private fallbackDue(): boolean {
|
|
475
|
+
return !this.fallbackInFlight && performance.now() >= this.nextFallbackAt;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
/** The native decoder threw: decode with the engine only from now on. */
|
|
479
|
+
private disableNative(err: unknown): void {
|
|
480
|
+
if (this.nativeFailed) return;
|
|
481
|
+
// Like the WebGL → Canvas 2D fallback: the reader keeps working, so this
|
|
482
|
+
// is a warning, not an `error` event.
|
|
483
|
+
console.warn('[scanner] native BarcodeDetector failed, decoding with the engine only:', err);
|
|
484
|
+
this.nativeFailed = true;
|
|
485
|
+
this.native = undefined;
|
|
486
|
+
this.nativeGeneration++;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
/** The region of interest, in video pixels. */
|
|
490
|
+
private videoRect(): Rect {
|
|
370
491
|
const video = this.preview.video;
|
|
371
492
|
const ew = this.preview.width || video.videoWidth;
|
|
372
493
|
const eh = this.preview.height || video.videoHeight;
|
|
373
|
-
|
|
494
|
+
return regionToVideoRect(this.region, video.videoWidth, video.videoHeight, ew, eh);
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
private async grabFrame(rect: Rect, fastPath: boolean): Promise<GrabbedFrame> {
|
|
498
|
+
const video = this.preview.video;
|
|
374
499
|
const { width, height } = outputSize(rect, this.options.processing?.maxDimension ?? 1024);
|
|
375
500
|
|
|
376
501
|
const t0 = performance.now();
|
|
@@ -381,12 +506,21 @@ export class BarcodeReader {
|
|
|
381
506
|
this.buffers.push(buffer);
|
|
382
507
|
throw new GrabError(err);
|
|
383
508
|
}
|
|
384
|
-
return {
|
|
509
|
+
return {
|
|
510
|
+
buffer,
|
|
511
|
+
width,
|
|
512
|
+
height,
|
|
513
|
+
rect,
|
|
514
|
+
grabMs: performance.now() - t0,
|
|
515
|
+
seq: ++this.grabbed,
|
|
516
|
+
fastPath,
|
|
517
|
+
};
|
|
385
518
|
}
|
|
386
519
|
|
|
387
520
|
private enqueue(frame: GrabbedFrame): void {
|
|
388
521
|
if (!this.running || this.paused) {
|
|
389
522
|
this.buffers.push(frame.buffer);
|
|
523
|
+
if (!frame.fastPath) this.fallbackInFlight = false;
|
|
390
524
|
} else if (this.decoding >= this.engine.concurrency) {
|
|
391
525
|
this.pending = frame;
|
|
392
526
|
} else {
|
|
@@ -408,31 +542,58 @@ export class BarcodeReader {
|
|
|
408
542
|
|
|
409
543
|
private async decodeFrame(frame: GrabbedFrame): Promise<void> {
|
|
410
544
|
const { width, height, rect } = frame;
|
|
411
|
-
|
|
545
|
+
let reply: FrameReply;
|
|
546
|
+
try {
|
|
547
|
+
reply = await this.engine.processFrame(frame.buffer, width, height, 'luma', frame.fastPath);
|
|
548
|
+
} finally {
|
|
549
|
+
if (!frame.fastPath) {
|
|
550
|
+
this.fallbackInFlight = false;
|
|
551
|
+
this.nextFallbackAt = performance.now() + FALLBACK_INTERVAL_MS;
|
|
552
|
+
}
|
|
553
|
+
}
|
|
412
554
|
this.buffers.push(reply.buffer);
|
|
413
555
|
if (!this.running) return;
|
|
414
556
|
this.failures = 0;
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
const
|
|
557
|
+
if (!frame.fastPath && reply.live.length > 0) this.nextFallbackAt = 0;
|
|
558
|
+
|
|
559
|
+
this.updatePerf({ grab: frame, decodeMs: reply.decodeMs });
|
|
560
|
+
// Fallback-only frames (native detector active) are decoded one at a
|
|
561
|
+
// time, so they cannot finish out of order: numbered when they finish,
|
|
562
|
+
// after the native frames that ran meanwhile.
|
|
563
|
+
const seq = frame.fastPath ? frame.seq : ++this.grabbed;
|
|
564
|
+
this.present(reply, seq, 'engine', (d) => ({
|
|
422
565
|
...d,
|
|
423
566
|
points: decoderToVideo(d.points, rect, width, height),
|
|
424
|
-
});
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
567
|
+
}));
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
/** Draw the live codes of frame `seq` and report the new ones, mapped to video pixels by `toVideo`. */
|
|
571
|
+
private present(
|
|
572
|
+
outcome: Pick<FrameOutcome, 'results' | 'live'>,
|
|
573
|
+
seq: number,
|
|
574
|
+
source: 'native' | 'engine',
|
|
575
|
+
toVideo: (d: Detection) => Detection,
|
|
576
|
+
): void {
|
|
577
|
+
// An older frame finishing after a newer one must not move the highlights
|
|
578
|
+
// back. With the native detector, a decoder that found nothing does not
|
|
579
|
+
// erase what the other one drew: the native decoder would otherwise
|
|
580
|
+
// erase, ~30 times per second, a code only the fallbacks can read.
|
|
581
|
+
const owns = outcome.live.length > 0 || this.highlightSource === source || !this.highlightSource;
|
|
582
|
+
if (seq > this.drawn && owns) {
|
|
583
|
+
this.drawn = seq;
|
|
584
|
+
this.highlightSource = outcome.live.length > 0 ? source : null;
|
|
585
|
+
const video = this.preview.video;
|
|
586
|
+
const vw = video.videoWidth;
|
|
587
|
+
const vh = video.videoHeight;
|
|
429
588
|
const layout = coverLayout(vw, vh, this.preview.width || vw, this.preview.height || vh);
|
|
430
|
-
this.preview.setDetections(
|
|
589
|
+
this.preview.setDetections(
|
|
590
|
+
outcome.live.map((d) => toVideo(d).points.map((p) => videoToElement(p, layout))),
|
|
591
|
+
);
|
|
431
592
|
}
|
|
432
593
|
|
|
433
|
-
if (
|
|
594
|
+
if (outcome.results && outcome.results.length > 0) {
|
|
434
595
|
this.feedback.play();
|
|
435
|
-
this.emit('detected',
|
|
596
|
+
this.emit('detected', outcome.results.map(toVideo));
|
|
436
597
|
}
|
|
437
598
|
}
|
|
438
599
|
|
|
@@ -461,17 +622,41 @@ export class BarcodeReader {
|
|
|
461
622
|
return new ArrayBuffer(size);
|
|
462
623
|
}
|
|
463
624
|
|
|
464
|
-
|
|
625
|
+
/**
|
|
626
|
+
* Account for a processed frame. `counted: false` for a native miss handed
|
|
627
|
+
* to the engine: that frame is counted once, when the engine is done.
|
|
628
|
+
*/
|
|
629
|
+
private updatePerf(s: {
|
|
630
|
+
grab?: GrabbedFrame;
|
|
631
|
+
decodeMs?: number;
|
|
632
|
+
nativeMs?: number;
|
|
633
|
+
counted?: boolean;
|
|
634
|
+
}): void {
|
|
465
635
|
const p = this.perf;
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
636
|
+
const ema = (avg: number, v: number) => (avg ? avg * 0.9 + v * 0.1 : v);
|
|
637
|
+
if (s.counted !== false) p.frames++;
|
|
638
|
+
if (s.grab) {
|
|
639
|
+
p.grabMs = ema(p.grabMs, s.grab.grabMs);
|
|
640
|
+
p.w = s.grab.width;
|
|
641
|
+
p.h = s.grab.height;
|
|
642
|
+
}
|
|
643
|
+
if (s.decodeMs !== undefined) {
|
|
644
|
+
p.decodeMs = ema(p.decodeMs, s.decodeMs);
|
|
645
|
+
p.engineFrames++;
|
|
646
|
+
}
|
|
647
|
+
if (s.nativeMs !== undefined) p.nativeMs = ema(p.nativeMs, s.nativeMs);
|
|
471
648
|
const now = performance.now();
|
|
472
649
|
if (now - p.since >= 1000) {
|
|
473
650
|
p.fps = (p.frames * 1000) / (now - p.since);
|
|
651
|
+
p.engineFps = (p.engineFrames * 1000) / (now - p.since);
|
|
652
|
+
// With the native detector the engine may decode nothing for long
|
|
653
|
+
// stretches: its averages would show stale values, as if it were busy.
|
|
654
|
+
if (p.engineFrames === 0) {
|
|
655
|
+
p.decodeMs = 0;
|
|
656
|
+
p.grabMs = 0;
|
|
657
|
+
}
|
|
474
658
|
p.frames = 0;
|
|
659
|
+
p.engineFrames = 0;
|
|
475
660
|
p.since = now;
|
|
476
661
|
}
|
|
477
662
|
}
|
|
@@ -518,6 +703,18 @@ class GrabError extends Error {
|
|
|
518
703
|
}
|
|
519
704
|
}
|
|
520
705
|
|
|
706
|
+
/** The native detector unless `processing.nativeDetector` is `'off'`, when it can serve `engineOptions`. */
|
|
707
|
+
function createNative(
|
|
708
|
+
options: ReaderOptions,
|
|
709
|
+
engineOptions: EngineOptions,
|
|
710
|
+
): Promise<NativeDetector | undefined> {
|
|
711
|
+
// On by default: on a Galaxy A14 (Chrome, Play Services) it read codes very
|
|
712
|
+
// well whatever the orientation of the phone, where the engine's fast pass
|
|
713
|
+
// only reads 1D codes roughly aligned with the image rows.
|
|
714
|
+
if (options.processing?.nativeDetector === 'off') return Promise.resolve(undefined);
|
|
715
|
+
return NativeDetector.create(engineOptions.formats, undefined, options.processing?.engineFallback === true);
|
|
716
|
+
}
|
|
717
|
+
|
|
521
718
|
function waitForVideo(video: HTMLVideoElement): Promise<void> {
|
|
522
719
|
if (video.readyState >= 2 && video.videoWidth > 0) return Promise.resolve();
|
|
523
720
|
return new Promise((resolve, reject) => {
|
package/src/types.ts
CHANGED
|
@@ -118,6 +118,26 @@ export interface ProcessingOptions {
|
|
|
118
118
|
maxDimension?: number;
|
|
119
119
|
/** Frame pipeline. `'auto'` uses WebGL2 and falls back to Canvas 2D. Default: `'auto'`. */
|
|
120
120
|
backend?: 'auto' | 'webgl' | 'canvas';
|
|
121
|
+
/**
|
|
122
|
+
* Use the browser's native `BarcodeDetector` (Chrome on Android, macOS and
|
|
123
|
+
* ChromeOS) as the fast pass, when it supports every requested format
|
|
124
|
+
* (`micro_qr` excepted). The engine's fallback strategies keep running on
|
|
125
|
+
* the frames it finds nothing in: hard codes (laser engraving, shadows…)
|
|
126
|
+
* are still read. Codes get the same confirmation and de-duplication.
|
|
127
|
+
* It reads codes whatever their orientation. Elsewhere (Safari, Firefox,
|
|
128
|
+
* Chrome on Windows / Linux), or if it fails, the engine does everything.
|
|
129
|
+
* `'off'`: always the engine alone. Default: `'auto'`.
|
|
130
|
+
*/
|
|
131
|
+
nativeDetector?: 'auto' | 'off';
|
|
132
|
+
/**
|
|
133
|
+
* With the native detector: while it finds nothing, also hand a frame to
|
|
134
|
+
* the engine's fallback strategies from time to time (~3 per second), a
|
|
135
|
+
* safety net for codes the native decoder may miss (laser engraving,
|
|
136
|
+
* shadows, light-on-dark codes, `micro_qr`). Costs a little CPU and
|
|
137
|
+
* battery. Without it, `micro_qr` listed in `formats` keeps the engine
|
|
138
|
+
* instead of the native detector. Default: `false`.
|
|
139
|
+
*/
|
|
140
|
+
engineFallback?: boolean;
|
|
121
141
|
}
|
|
122
142
|
|
|
123
143
|
export interface ReaderOptions extends EngineOptions, AssetOptions {
|
|
@@ -139,9 +159,9 @@ export interface ReaderOptions extends EngineOptions, AssetOptions {
|
|
|
139
159
|
export interface ReaderStats {
|
|
140
160
|
/** Frames decoded per second. */
|
|
141
161
|
fps: number;
|
|
142
|
-
/** Average decode time in the engine, in ms. */
|
|
162
|
+
/** Average decode time in the engine, in ms (`0` when it decoded nothing in the last second). */
|
|
143
163
|
decodeMs: number;
|
|
144
|
-
/** Average GPU/canvas frame preparation time, in ms. */
|
|
164
|
+
/** Average GPU/canvas frame preparation time, in ms (`0` when nothing was grabbed in the last second). */
|
|
145
165
|
grabMs: number;
|
|
146
166
|
/** Size of the image handed to the decoder. */
|
|
147
167
|
frameWidth: number;
|
|
@@ -152,6 +172,15 @@ export interface ReaderStats {
|
|
|
152
172
|
worker: boolean;
|
|
153
173
|
/** Frames decoded in parallel (number of workers, `1` on the main thread). */
|
|
154
174
|
workers: number;
|
|
175
|
+
/** `true` when the native `BarcodeDetector` does the fast pass (see `processing.nativeDetector`). */
|
|
176
|
+
native: boolean;
|
|
177
|
+
/** Average native detection time, in ms (`0` when not used). */
|
|
178
|
+
nativeMs: number;
|
|
179
|
+
/**
|
|
180
|
+
* Frames decoded by the engine per second. With the native detector: `0`
|
|
181
|
+
* while it reads everything, a few per second with `engineFallback`.
|
|
182
|
+
*/
|
|
183
|
+
engineFps: number;
|
|
155
184
|
}
|
|
156
185
|
|
|
157
186
|
export interface ReaderEvents {
|