@scanly/browser 2.0.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 +3 -0
- package/dist/batch/batch-controller.d.ts +71 -0
- package/dist/batch/batch-controller.d.ts.map +1 -0
- package/dist/batch/batch-controller.js +422 -0
- package/dist/batch/batch-controller.js.map +1 -0
- package/dist/batch/batch-session.d.ts +53 -0
- package/dist/batch/batch-session.d.ts.map +1 -0
- package/dist/batch/batch-session.js +267 -0
- package/dist/batch/batch-session.js.map +1 -0
- package/dist/batch/index.d.ts +4 -0
- package/dist/batch/index.d.ts.map +1 -0
- package/dist/batch/index.js +4 -0
- package/dist/batch/index.js.map +1 -0
- package/dist/batch/types.d.ts +101 -0
- package/dist/batch/types.d.ts.map +1 -0
- package/dist/batch/types.js +2 -0
- package/dist/batch/types.js.map +1 -0
- package/dist/browser-session.d.ts +51 -0
- package/dist/browser-session.d.ts.map +1 -0
- package/dist/browser-session.js +168 -0
- package/dist/browser-session.js.map +1 -0
- package/dist/camera-source.d.ts +89 -0
- package/dist/camera-source.d.ts.map +1 -0
- package/dist/camera-source.js +366 -0
- package/dist/camera-source.js.map +1 -0
- package/dist/camera-strategy.d.ts +36 -0
- package/dist/camera-strategy.d.ts.map +1 -0
- package/dist/camera-strategy.js +94 -0
- package/dist/camera-strategy.js.map +1 -0
- package/dist/decode-upload.d.ts +13 -0
- package/dist/decode-upload.d.ts.map +1 -0
- package/dist/decode-upload.js +23 -0
- package/dist/decode-upload.js.map +1 -0
- package/dist/image-loader.d.ts +11 -0
- package/dist/image-loader.d.ts.map +1 -0
- package/dist/image-loader.js +63 -0
- package/dist/image-loader.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +13 -0
- package/dist/index.js.map +1 -0
- package/dist/runtime.d.ts +8 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +14 -0
- package/dist/runtime.js.map +1 -0
- package/dist/scanner/camera-capabilities.d.ts +20 -0
- package/dist/scanner/camera-capabilities.d.ts.map +1 -0
- package/dist/scanner/camera-capabilities.js +181 -0
- package/dist/scanner/camera-capabilities.js.map +1 -0
- package/dist/scanner/camera-platform.d.ts +75 -0
- package/dist/scanner/camera-platform.d.ts.map +1 -0
- package/dist/scanner/camera-platform.js +145 -0
- package/dist/scanner/camera-platform.js.map +1 -0
- package/dist/scanner/decode-escalation.d.ts +20 -0
- package/dist/scanner/decode-escalation.d.ts.map +1 -0
- package/dist/scanner/decode-escalation.js +40 -0
- package/dist/scanner/decode-escalation.js.map +1 -0
- package/dist/scanner/frame-quality.d.ts +24 -0
- package/dist/scanner/frame-quality.d.ts.map +1 -0
- package/dist/scanner/frame-quality.js +90 -0
- package/dist/scanner/frame-quality.js.map +1 -0
- package/dist/scanner/frame-scheduler.d.ts +59 -0
- package/dist/scanner/frame-scheduler.d.ts.map +1 -0
- package/dist/scanner/frame-scheduler.js +148 -0
- package/dist/scanner/frame-scheduler.js.map +1 -0
- package/dist/scanner/frame-source.d.ts +70 -0
- package/dist/scanner/frame-source.d.ts.map +1 -0
- package/dist/scanner/frame-source.js +263 -0
- package/dist/scanner/frame-source.js.map +1 -0
- package/dist/scanner/index.d.ts +12 -0
- package/dist/scanner/index.d.ts.map +1 -0
- package/dist/scanner/index.js +12 -0
- package/dist/scanner/index.js.map +1 -0
- package/dist/scanner/repeat-suppressor.d.ts +23 -0
- package/dist/scanner/repeat-suppressor.d.ts.map +1 -0
- package/dist/scanner/repeat-suppressor.js +69 -0
- package/dist/scanner/repeat-suppressor.js.map +1 -0
- package/dist/scanner/scanner-session.d.ts +160 -0
- package/dist/scanner/scanner-session.d.ts.map +1 -0
- package/dist/scanner/scanner-session.js +766 -0
- package/dist/scanner/scanner-session.js.map +1 -0
- package/dist/scanner/temporal-confirmation.d.ts +30 -0
- package/dist/scanner/temporal-confirmation.d.ts.map +1 -0
- package/dist/scanner/temporal-confirmation.js +71 -0
- package/dist/scanner/temporal-confirmation.js.map +1 -0
- package/dist/scanner/temporal-roi.d.ts +27 -0
- package/dist/scanner/temporal-roi.d.ts.map +1 -0
- package/dist/scanner/temporal-roi.js +50 -0
- package/dist/scanner/temporal-roi.js.map +1 -0
- package/dist/scanner/types.d.ts +232 -0
- package/dist/scanner/types.d.ts.map +1 -0
- package/dist/scanner/types.js +2 -0
- package/dist/scanner/types.js.map +1 -0
- package/dist/tracking/barcode-tracker.d.ts +28 -0
- package/dist/tracking/barcode-tracker.d.ts.map +1 -0
- package/dist/tracking/barcode-tracker.js +279 -0
- package/dist/tracking/barcode-tracker.js.map +1 -0
- package/dist/tracking/geometry.d.ts +13 -0
- package/dist/tracking/geometry.d.ts.map +1 -0
- package/dist/tracking/geometry.js +51 -0
- package/dist/tracking/geometry.js.map +1 -0
- package/dist/tracking/index.d.ts +8 -0
- package/dist/tracking/index.d.ts.map +1 -0
- package/dist/tracking/index.js +8 -0
- package/dist/tracking/index.js.map +1 -0
- package/dist/tracking/scanner-tracking-runtime.d.ts +45 -0
- package/dist/tracking/scanner-tracking-runtime.d.ts.map +1 -0
- package/dist/tracking/scanner-tracking-runtime.js +93 -0
- package/dist/tracking/scanner-tracking-runtime.js.map +1 -0
- package/dist/tracking/track-association.d.ts +19 -0
- package/dist/tracking/track-association.d.ts.map +1 -0
- package/dist/tracking/track-association.js +191 -0
- package/dist/tracking/track-association.js.map +1 -0
- package/dist/tracking/track-overlay.d.ts +4 -0
- package/dist/tracking/track-overlay.d.ts.map +1 -0
- package/dist/tracking/track-overlay.js +16 -0
- package/dist/tracking/track-overlay.js.map +1 -0
- package/dist/tracking/track-roi-set.d.ts +17 -0
- package/dist/tracking/track-roi-set.d.ts.map +1 -0
- package/dist/tracking/track-roi-set.js +119 -0
- package/dist/tracking/track-roi-set.js.map +1 -0
- package/dist/tracking/types.d.ts +154 -0
- package/dist/tracking/types.d.ts.map +1 -0
- package/dist/tracking/types.js +2 -0
- package/dist/tracking/types.js.map +1 -0
- package/dist/worker/decode-worker.d.ts +2 -0
- package/dist/worker/decode-worker.d.ts.map +1 -0
- package/dist/worker/decode-worker.js +84 -0
- package/dist/worker/decode-worker.js.map +1 -0
- package/dist/worker/transferable-buffer.d.ts +18 -0
- package/dist/worker/transferable-buffer.d.ts.map +1 -0
- package/dist/worker/transferable-buffer.js +24 -0
- package/dist/worker/transferable-buffer.js.map +1 -0
- package/dist/worker/worker-client.d.ts +86 -0
- package/dist/worker/worker-client.d.ts.map +1 -0
- package/dist/worker/worker-client.js +286 -0
- package/dist/worker/worker-client.js.map +1 -0
- package/dist/worker/worker-messages.d.ts +75 -0
- package/dist/worker/worker-messages.d.ts.map +1 -0
- package/dist/worker/worker-messages.js +244 -0
- package/dist/worker/worker-messages.js.map +1 -0
- package/package.json +30 -0
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { sdkError } from "@scanly/core";
|
|
2
|
+
const record = (value) => {
|
|
3
|
+
if (!value || typeof value !== "object")
|
|
4
|
+
return "unavailable";
|
|
5
|
+
return { ...value };
|
|
6
|
+
};
|
|
7
|
+
function safeCall(read) {
|
|
8
|
+
try {
|
|
9
|
+
return read?.();
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
return undefined;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
export function cameraError(error, fallback = "Camera operation failed.") {
|
|
16
|
+
const existing = error;
|
|
17
|
+
if (existing?.code && existing.category && existing.message && typeof existing.retryable === "boolean")
|
|
18
|
+
return existing;
|
|
19
|
+
const value = error;
|
|
20
|
+
const name = value?.name ?? "";
|
|
21
|
+
const message = (value?.message || String(error || fallback)).slice(0, 2_048);
|
|
22
|
+
const code = name === "NotAllowedError" || name === "SecurityError" || /permission denied|not allowed/i.test(message)
|
|
23
|
+
? "camera_permission_denied"
|
|
24
|
+
: name === "NotFoundError" || name === "DevicesNotFoundError" || /requested device not found|no camera/i.test(message)
|
|
25
|
+
? "camera_not_found"
|
|
26
|
+
: name === "NotReadableError" || name === "TrackStartError" || /could not start video source|camera.*busy/i.test(message)
|
|
27
|
+
? "camera_busy"
|
|
28
|
+
: name === "OverconstrainedError" || name === "ConstraintNotSatisfiedError" || /constraint/i.test(message)
|
|
29
|
+
? "camera_constraint_failed"
|
|
30
|
+
: /track ended/i.test(message)
|
|
31
|
+
? "camera_track_ended"
|
|
32
|
+
: "source_disconnected";
|
|
33
|
+
return sdkError(code, message || fallback, { browserErrorName: name || "unavailable" }, error);
|
|
34
|
+
}
|
|
35
|
+
function supportedMediaConstraints() {
|
|
36
|
+
if (typeof navigator === "undefined")
|
|
37
|
+
return undefined;
|
|
38
|
+
return safeCall(() => navigator.mediaDevices?.getSupportedConstraints?.());
|
|
39
|
+
}
|
|
40
|
+
function preferredConstraints(policy) {
|
|
41
|
+
const supported = supportedMediaConstraints();
|
|
42
|
+
const accepts = (key) => supported?.[key] !== false;
|
|
43
|
+
return {
|
|
44
|
+
...(policy.deviceId && accepts("deviceId")
|
|
45
|
+
? { deviceId: policy.exactDevice === false ? { ideal: policy.deviceId } : { exact: policy.deviceId } }
|
|
46
|
+
: {}),
|
|
47
|
+
...(policy.facingMode && accepts("facingMode") ? { facingMode: { ideal: policy.facingMode } } : {}),
|
|
48
|
+
...(policy.preferredWidth && accepts("width") ? { width: { ideal: policy.preferredWidth } } : {}),
|
|
49
|
+
...(policy.preferredHeight && accepts("height") ? { height: { ideal: policy.preferredHeight } } : {}),
|
|
50
|
+
...(policy.preferredFrameRate && accepts("frameRate") ? { frameRate: { ideal: policy.preferredFrameRate } } : {}),
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
function negotiationCandidates(policy) {
|
|
54
|
+
const preferred = preferredConstraints(policy);
|
|
55
|
+
const fixedDevice = policy.deviceId
|
|
56
|
+
? { deviceId: policy.exactDevice === false ? { ideal: policy.deviceId } : { exact: policy.deviceId } }
|
|
57
|
+
: {};
|
|
58
|
+
const facing = policy.facingMode ? { facingMode: { ideal: policy.facingMode } } : {};
|
|
59
|
+
const candidates = [preferred];
|
|
60
|
+
candidates.push({ ...fixedDevice, ...facing });
|
|
61
|
+
candidates.push(Object.keys(fixedDevice).length > 0 ? fixedDevice : true);
|
|
62
|
+
const maximum = Math.max(1, Math.min(4, Math.floor(policy.maximumAttempts ?? 3)));
|
|
63
|
+
return candidates.filter((candidate, index, values) => index === values.findIndex((entry) => JSON.stringify(entry) === JSON.stringify(candidate))).slice(0, maximum);
|
|
64
|
+
}
|
|
65
|
+
export async function negotiateCameraStream(policy) {
|
|
66
|
+
if (typeof navigator === "undefined" || !navigator.mediaDevices?.getUserMedia) {
|
|
67
|
+
throw cameraError({ name: "NotSupportedError", message: "Camera capture is not supported by this browser." });
|
|
68
|
+
}
|
|
69
|
+
const attempts = [];
|
|
70
|
+
let finalError;
|
|
71
|
+
for (const [index, constraints] of negotiationCandidates(policy).entries()) {
|
|
72
|
+
try {
|
|
73
|
+
const stream = await navigator.mediaDevices.getUserMedia({ video: constraints, audio: false });
|
|
74
|
+
attempts.push({ index: index + 1, constraints, result: "negotiated" });
|
|
75
|
+
const track = stream.getVideoTracks()[0];
|
|
76
|
+
const supported = supportedMediaConstraints();
|
|
77
|
+
return {
|
|
78
|
+
stream,
|
|
79
|
+
diagnostics: {
|
|
80
|
+
requestedConstraints: { ...policy },
|
|
81
|
+
supportedConstraints: supported ? { ...supported } : "unavailable",
|
|
82
|
+
attempts,
|
|
83
|
+
negotiatedSettings: record(safeCall(() => track?.getSettings?.())),
|
|
84
|
+
trackCapabilities: record(safeCall(() => track?.getCapabilities?.())),
|
|
85
|
+
trackConstraints: record(safeCall(() => track?.getConstraints?.())),
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
finalError = error;
|
|
91
|
+
const typed = cameraError(error);
|
|
92
|
+
attempts.push({ index: index + 1, constraints, result: "failed", errorCode: typed.code, errorMessage: typed.message });
|
|
93
|
+
if (typed.code !== "camera_constraint_failed")
|
|
94
|
+
break;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
const typed = cameraError(finalError);
|
|
98
|
+
typed.details = { ...typed.details, negotiationAttempts: attempts.length };
|
|
99
|
+
throw typed;
|
|
100
|
+
}
|
|
101
|
+
export function diagnosticsForTrack(policy, track, attempts = []) {
|
|
102
|
+
const supported = supportedMediaConstraints();
|
|
103
|
+
return {
|
|
104
|
+
requestedConstraints: { ...policy },
|
|
105
|
+
supportedConstraints: supported ? { ...supported } : "unavailable",
|
|
106
|
+
attempts,
|
|
107
|
+
negotiatedSettings: record(safeCall(() => track?.getSettings?.())),
|
|
108
|
+
trackCapabilities: record(safeCall(() => track?.getCapabilities?.())),
|
|
109
|
+
trackConstraints: record(safeCall(() => track?.getConstraints?.())),
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
export class CameraRecoveryController {
|
|
113
|
+
attempts = 0;
|
|
114
|
+
restarts = 0;
|
|
115
|
+
failures = 0;
|
|
116
|
+
exhausted = false;
|
|
117
|
+
maximumRetries;
|
|
118
|
+
baseDelayMs;
|
|
119
|
+
maximumDelayMs;
|
|
120
|
+
waiter;
|
|
121
|
+
constructor(policy = {}) {
|
|
122
|
+
this.maximumRetries = Math.max(0, Math.min(10, Math.floor(policy.maximumRetries ?? 3)));
|
|
123
|
+
this.baseDelayMs = Math.max(0, Math.min(10_000, Math.floor(policy.baseDelayMs ?? 200)));
|
|
124
|
+
this.maximumDelayMs = Math.max(this.baseDelayMs, Math.min(30_000, Math.floor(policy.maximumDelayMs ?? 2_000)));
|
|
125
|
+
this.waiter = policy.wait ?? ((delayMs) => new Promise((resolve) => setTimeout(resolve, delayMs)));
|
|
126
|
+
}
|
|
127
|
+
next(reason) {
|
|
128
|
+
if (this.attempts >= this.maximumRetries) {
|
|
129
|
+
this.exhausted = true;
|
|
130
|
+
return { retry: false, attempt: this.attempts, delayMs: 0, reason };
|
|
131
|
+
}
|
|
132
|
+
this.attempts += 1;
|
|
133
|
+
const delayMs = Math.min(this.maximumDelayMs, this.baseDelayMs * (2 ** (this.attempts - 1)));
|
|
134
|
+
return { retry: true, attempt: this.attempts, delayMs, reason };
|
|
135
|
+
}
|
|
136
|
+
async wait(decision) {
|
|
137
|
+
if (decision.retry && decision.delayMs > 0)
|
|
138
|
+
await this.waiter(decision.delayMs);
|
|
139
|
+
}
|
|
140
|
+
markRestarted() { this.restarts += 1; }
|
|
141
|
+
markFailed() { this.failures += 1; }
|
|
142
|
+
reset() { this.attempts = 0; this.restarts = 0; this.failures = 0; this.exhausted = false; }
|
|
143
|
+
getStatistics() { return { attempts: this.attempts, restarts: this.restarts, failures: this.failures, exhausted: this.exhausted }; }
|
|
144
|
+
}
|
|
145
|
+
//# sourceMappingURL=camera-platform.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"camera-platform.js","sourceRoot":"","sources":["../../src/scanner/camera-platform.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAoC,MAAM,cAAc,CAAC;AAiE1E,MAAM,MAAM,GAAG,CAAC,KAAc,EAA2C,EAAE;IACzE,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,aAAa,CAAC;IAC9D,OAAO,EAAE,GAAI,KAAiC,EAAE,CAAC;AACnD,CAAC,CAAC;AAEF,SAAS,QAAQ,CAAI,IAA2B;IAC9C,IAAI,CAAC;QAAC,OAAO,IAAI,EAAE,EAAE,CAAC;IAAC,CAAC;IAAC,MAAM,CAAC;QAAC,OAAO,SAAS,CAAC;IAAC,CAAC;AACtD,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,KAAc,EAAE,QAAQ,GAAG,0BAA0B;IAC/E,MAAM,QAAQ,GAAG,KAAsC,CAAC;IACxD,IAAI,QAAQ,EAAE,IAAI,IAAI,QAAQ,CAAC,QAAQ,IAAI,QAAQ,CAAC,OAAO,IAAI,OAAO,QAAQ,CAAC,SAAS,KAAK,SAAS;QAAE,OAAO,QAAoB,CAAC;IACpI,MAAM,KAAK,GAAG,KAAwD,CAAC;IACvE,MAAM,IAAI,GAAG,KAAK,EAAE,IAAI,IAAI,EAAE,CAAC;IAC/B,MAAM,OAAO,GAAG,CAAC,KAAK,EAAE,OAAO,IAAI,MAAM,CAAC,KAAK,IAAI,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;IAC9E,MAAM,IAAI,GACR,IAAI,KAAK,iBAAiB,IAAI,IAAI,KAAK,eAAe,IAAI,gCAAgC,CAAC,IAAI,CAAC,OAAO,CAAC;QACtG,CAAC,CAAC,0BAA0B;QAC5B,CAAC,CAAC,IAAI,KAAK,eAAe,IAAI,IAAI,KAAK,sBAAsB,IAAI,uCAAuC,CAAC,IAAI,CAAC,OAAO,CAAC;YACpH,CAAC,CAAC,kBAAkB;YACpB,CAAC,CAAC,IAAI,KAAK,kBAAkB,IAAI,IAAI,KAAK,iBAAiB,IAAI,4CAA4C,CAAC,IAAI,CAAC,OAAO,CAAC;gBACvH,CAAC,CAAC,aAAa;gBACf,CAAC,CAAC,IAAI,KAAK,sBAAsB,IAAI,IAAI,KAAK,6BAA6B,IAAI,aAAa,CAAC,IAAI,CAAC,OAAO,CAAC;oBACxG,CAAC,CAAC,0BAA0B;oBAC5B,CAAC,CAAC,cAAc,CAAC,IAAI,CAAC,OAAO,CAAC;wBAC5B,CAAC,CAAC,oBAAoB;wBACtB,CAAC,CAAC,qBAAqB,CAAC;IACpC,OAAO,QAAQ,CAAC,IAAI,EAAE,OAAO,IAAI,QAAQ,EAAE,EAAE,gBAAgB,EAAE,IAAI,IAAI,aAAa,EAAE,EAAE,KAAK,CAAC,CAAC;AACjG,CAAC;AAED,SAAS,yBAAyB;IAChC,IAAI,OAAO,SAAS,KAAK,WAAW;QAAE,OAAO,SAAS,CAAC;IACvD,OAAO,QAAQ,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,YAAY,EAAE,uBAAuB,EAAE,EAAE,CAAC,CAAC;AAC7E,CAAC;AAED,SAAS,oBAAoB,CAAC,MAA8B;IAC1D,MAAM,SAAS,GAAG,yBAAyB,EAAE,CAAC;IAC9C,MAAM,OAAO,GAAG,CAAC,GAAyC,EAAW,EAAE,CAAC,SAAS,EAAE,CAAC,GAAG,CAAC,KAAK,KAAK,CAAC;IACnG,OAAO;QACL,GAAG,CAAC,MAAM,CAAC,QAAQ,IAAI,OAAO,CAAC,UAAU,CAAC;YACxC,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,CAAC,WAAW,KAAK,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,QAAQ,EAAE,EAAE;YACtG,CAAC,CAAC,EAAE,CAAC;QACP,GAAG,CAAC,MAAM,CAAC,UAAU,IAAI,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,EAAE,KAAK,EAAE,MAAM,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACnG,GAAG,CAAC,MAAM,CAAC,cAAc,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,KAAK,EAAE,MAAM,CAAC,cAAc,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACjG,GAAG,CAAC,MAAM,CAAC,eAAe,IAAI,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,MAAM,CAAC,eAAe,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACrG,GAAG,CAAC,MAAM,CAAC,kBAAkB,IAAI,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,EAAE,KAAK,EAAE,MAAM,CAAC,kBAAkB,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAClH,CAAC;AACJ,CAAC;AAED,SAAS,qBAAqB,CAAC,MAA8B;IAC3D,MAAM,SAAS,GAAG,oBAAoB,CAAC,MAAM,CAAC,CAAC;IAC/C,MAAM,WAAW,GAAG,MAAM,CAAC,QAAQ;QACjC,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,CAAC,WAAW,KAAK,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,QAAQ,EAAE,EAAE;QACtG,CAAC,CAAC,EAAE,CAAC;IACP,MAAM,MAAM,GAAG,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,EAAE,KAAK,EAAE,MAAM,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IACrF,MAAM,UAAU,GAAwC,CAAC,SAAS,CAAC,CAAC;IACpE,UAAU,CAAC,IAAI,CAAC,EAAE,GAAG,WAAW,EAAE,GAAG,MAAM,EAAE,CAAC,CAAC;IAC/C,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;IAC1E,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,eAAe,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;IAClF,OAAO,UAAU,CAAC,MAAM,CAAC,CAAC,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,CACpD,KAAK,KAAK,MAAM,CAAC,SAAS,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,KAAK,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC,CAC3F,CAAC,KAAK,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC;AACtB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,qBAAqB,CAAC,MAA8B;IAIxE,IAAI,OAAO,SAAS,KAAK,WAAW,IAAI,CAAC,SAAS,CAAC,YAAY,EAAE,YAAY,EAAE,CAAC;QAC9E,MAAM,WAAW,CAAC,EAAE,IAAI,EAAE,mBAAmB,EAAE,OAAO,EAAE,kDAAkD,EAAE,CAAC,CAAC;IAChH,CAAC;IACD,MAAM,QAAQ,GAA8B,EAAE,CAAC;IAC/C,IAAI,UAAmB,CAAC;IACxB,KAAK,MAAM,CAAC,KAAK,EAAE,WAAW,CAAC,IAAI,qBAAqB,CAAC,MAAM,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC;QAC3E,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,YAAY,CAAC,YAAY,CAAC,EAAE,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;YAC/F,QAAQ,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,KAAK,GAAG,CAAC,EAAE,WAAW,EAAE,MAAM,EAAE,YAAY,EAAE,CAAC,CAAC;YACvE,MAAM,KAAK,GAAG,MAAM,CAAC,cAAc,EAAE,CAAC,CAAC,CAAC,CAAC;YACzC,MAAM,SAAS,GAAG,yBAAyB,EAAE,CAAC;YAC9C,OAAO;gBACL,MAAM;gBACN,WAAW,EAAE;oBACX,oBAAoB,EAAE,EAAE,GAAG,MAAM,EAAE;oBACnC,oBAAoB,EAAE,SAAS,CAAC,CAAC,CAAC,EAAE,GAAG,SAAS,EAA6B,CAAC,CAAC,CAAC,aAAa;oBAC7F,QAAQ;oBACR,kBAAkB,EAAE,MAAM,CAAC,QAAQ,CAAC,GAAG,EAAE,CAAC,KAAK,EAAE,WAAW,EAAE,EAAE,CAAC,CAAC;oBAClE,iBAAiB,EAAE,MAAM,CAAC,QAAQ,CAAC,GAAG,EAAE,CAAC,KAAK,EAAE,eAAe,EAAE,EAAE,CAAC,CAAC;oBACrE,gBAAgB,EAAE,MAAM,CAAC,QAAQ,CAAC,GAAG,EAAE,CAAC,KAAK,EAAE,cAAc,EAAE,EAAE,CAAC,CAAC;iBACpE;aACF,CAAC;QACJ,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,UAAU,GAAG,KAAK,CAAC;YACnB,MAAM,KAAK,GAAG,WAAW,CAAC,KAAK,CAAC,CAAC;YACjC,QAAQ,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,KAAK,GAAG,CAAC,EAAE,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,KAAK,CAAC,IAAI,EAAE,YAAY,EAAE,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;YACvH,IAAI,KAAK,CAAC,IAAI,KAAK,0BAA0B;gBAAE,MAAM;QACvD,CAAC;IACH,CAAC;IACD,MAAM,KAAK,GAAG,WAAW,CAAC,UAAU,CAAC,CAAC;IACtC,KAAK,CAAC,OAAO,GAAG,EAAE,GAAG,KAAK,CAAC,OAAO,EAAE,mBAAmB,EAAE,QAAQ,CAAC,MAAM,EAAE,CAAC;IAC3E,MAAM,KAAK,CAAC;AACd,CAAC;AAED,MAAM,UAAU,mBAAmB,CACjC,MAA8B,EAC9B,KAAmC,EACnC,WAA+C,EAAE;IAEjD,MAAM,SAAS,GAAG,yBAAyB,EAAE,CAAC;IAC9C,OAAO;QACL,oBAAoB,EAAE,EAAE,GAAG,MAAM,EAAE;QACnC,oBAAoB,EAAE,SAAS,CAAC,CAAC,CAAC,EAAE,GAAG,SAAS,EAA6B,CAAC,CAAC,CAAC,aAAa;QAC7F,QAAQ;QACR,kBAAkB,EAAE,MAAM,CAAC,QAAQ,CAAC,GAAG,EAAE,CAAC,KAAK,EAAE,WAAW,EAAE,EAAE,CAAC,CAAC;QAClE,iBAAiB,EAAE,MAAM,CAAC,QAAQ,CAAC,GAAG,EAAE,CAAC,KAAK,EAAE,eAAe,EAAE,EAAE,CAAC,CAAC;QACrE,gBAAgB,EAAE,MAAM,CAAC,QAAQ,CAAC,GAAG,EAAE,CAAC,KAAK,EAAE,cAAc,EAAE,EAAE,CAAC,CAAC;KACpE,CAAC;AACJ,CAAC;AAED,MAAM,OAAO,wBAAwB;IAC3B,QAAQ,GAAG,CAAC,CAAC;IACb,QAAQ,GAAG,CAAC,CAAC;IACb,QAAQ,GAAG,CAAC,CAAC;IACb,SAAS,GAAG,KAAK,CAAC;IACT,cAAc,CAAS;IACvB,WAAW,CAAS;IACpB,cAAc,CAAS;IACvB,MAAM,CAAqC;IAE5D,YAAY,SAA+B,EAAE;QAC3C,IAAI,CAAC,cAAc,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,cAAc,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;QACxF,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,WAAW,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC;QACxF,IAAI,CAAC,cAAc,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,WAAW,EAAE,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,cAAc,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC;QAC/G,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC,IAAI,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;IACrG,CAAC;IAED,IAAI,CAAC,MAAc;QACjB,IAAI,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,cAAc,EAAE,CAAC;YACzC,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC;YACtB,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,CAAC,QAAQ,EAAE,OAAO,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC;QACtE,CAAC;QACD,IAAI,CAAC,QAAQ,IAAI,CAAC,CAAC;QACnB,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,cAAc,EAAE,IAAI,CAAC,WAAW,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;QAC7F,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,CAAC,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC;IAClE,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,QAAgC;QACzC,IAAI,QAAQ,CAAC,KAAK,IAAI,QAAQ,CAAC,OAAO,GAAG,CAAC;YAAE,MAAM,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;IAClF,CAAC;IAED,aAAa,KAAW,IAAI,CAAC,QAAQ,IAAI,CAAC,CAAC,CAAC,CAAC;IAC7C,UAAU,KAAW,IAAI,CAAC,QAAQ,IAAI,CAAC,CAAC,CAAC,CAAC;IAC1C,KAAK,KAAW,IAAI,CAAC,QAAQ,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,GAAG,KAAK,CAAC,CAAC,CAAC;IAClG,aAAa,KAA+B,OAAO,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,SAAS,EAAE,IAAI,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC;CAC/J"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { DecodeProfile, FrameQuality } from "./types.js";
|
|
2
|
+
export interface BoundedDecodeEscalationOptions {
|
|
3
|
+
balancedEveryMisses?: number;
|
|
4
|
+
robustAfterMisses?: number;
|
|
5
|
+
robustCooldownFrames?: number;
|
|
6
|
+
}
|
|
7
|
+
/** Deterministic Fast-first escalation; Robust is always separated by a cooldown. */
|
|
8
|
+
export declare class BoundedDecodeEscalation {
|
|
9
|
+
private readonly options;
|
|
10
|
+
private misses;
|
|
11
|
+
private frame;
|
|
12
|
+
private lastRobustFrame;
|
|
13
|
+
private lastSuccessAt;
|
|
14
|
+
constructor(options?: BoundedDecodeEscalationOptions);
|
|
15
|
+
select(quality: FrameQuality, hasStableRoi: boolean, now?: number): DecodeProfile;
|
|
16
|
+
observe(success: boolean, now?: number): void;
|
|
17
|
+
reset(): void;
|
|
18
|
+
get consecutiveMisses(): number;
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=decode-escalation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"decode-escalation.d.ts","sourceRoot":"","sources":["../../src/scanner/decode-escalation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE9D,MAAM,WAAW,8BAA8B;IAC7C,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,oBAAoB,CAAC,EAAE,MAAM,CAAC;CAC/B;AAED,qFAAqF;AACrF,qBAAa,uBAAuB;IAMtB,OAAO,CAAC,QAAQ,CAAC,OAAO;IALpC,OAAO,CAAC,MAAM,CAAK;IACnB,OAAO,CAAC,KAAK,CAAK;IAClB,OAAO,CAAC,eAAe,CAA4B;IACnD,OAAO,CAAC,aAAa,CAAK;gBAEG,OAAO,GAAE,8BAAmC;IAEzE,MAAM,CAAC,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,OAAO,EAAE,GAAG,SAAa,GAAG,aAAa;IAerF,OAAO,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,SAAa,GAAG,IAAI;IAKjD,KAAK,IAAI,IAAI;IACb,IAAI,iBAAiB,IAAI,MAAM,CAAwB;CACxD"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/** Deterministic Fast-first escalation; Robust is always separated by a cooldown. */
|
|
2
|
+
export class BoundedDecodeEscalation {
|
|
3
|
+
options;
|
|
4
|
+
misses = 0;
|
|
5
|
+
frame = 0;
|
|
6
|
+
lastRobustFrame = Number.NEGATIVE_INFINITY;
|
|
7
|
+
lastSuccessAt = 0;
|
|
8
|
+
constructor(options = {}) {
|
|
9
|
+
this.options = options;
|
|
10
|
+
}
|
|
11
|
+
select(quality, hasStableRoi, now = Date.now()) {
|
|
12
|
+
this.frame += 1;
|
|
13
|
+
const balancedEvery = Math.max(2, this.options.balancedEveryMisses ?? 4);
|
|
14
|
+
const robustAfter = Math.max(balancedEvery + 1, this.options.robustAfterMisses ?? 8);
|
|
15
|
+
const cooldown = Math.max(2, this.options.robustCooldownFrames ?? 12);
|
|
16
|
+
// A stable ROI is preferred, but a bounded full-frame Robust probe is still
|
|
17
|
+
// necessary when the first code has not been found yet. This avoids a
|
|
18
|
+
// policy deadlock where Robust could never run because ROI only appears
|
|
19
|
+
// after a successful decode.
|
|
20
|
+
const robustEligible = quality.usable && (hasStableRoi || this.misses >= robustAfter + balancedEvery) && this.misses >= robustAfter && this.frame - this.lastRobustFrame >= cooldown && now - this.lastSuccessAt > 500;
|
|
21
|
+
if (robustEligible) {
|
|
22
|
+
this.lastRobustFrame = this.frame;
|
|
23
|
+
return "robust";
|
|
24
|
+
}
|
|
25
|
+
if (quality.usable && this.misses > 0 && this.misses % balancedEvery === 0)
|
|
26
|
+
return "balanced";
|
|
27
|
+
return "fast";
|
|
28
|
+
}
|
|
29
|
+
observe(success, now = Date.now()) {
|
|
30
|
+
if (success) {
|
|
31
|
+
this.misses = 0;
|
|
32
|
+
this.lastSuccessAt = now;
|
|
33
|
+
}
|
|
34
|
+
else
|
|
35
|
+
this.misses = Math.min(10_000, this.misses + 1);
|
|
36
|
+
}
|
|
37
|
+
reset() { this.misses = 0; this.frame = 0; this.lastRobustFrame = Number.NEGATIVE_INFINITY; this.lastSuccessAt = 0; }
|
|
38
|
+
get consecutiveMisses() { return this.misses; }
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=decode-escalation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"decode-escalation.js","sourceRoot":"","sources":["../../src/scanner/decode-escalation.ts"],"names":[],"mappings":"AAQA,qFAAqF;AACrF,MAAM,OAAO,uBAAuB;IAML;IALrB,MAAM,GAAG,CAAC,CAAC;IACX,KAAK,GAAG,CAAC,CAAC;IACV,eAAe,GAAG,MAAM,CAAC,iBAAiB,CAAC;IAC3C,aAAa,GAAG,CAAC,CAAC;IAE1B,YAA6B,UAA0C,EAAE;QAA5C,YAAO,GAAP,OAAO,CAAqC;IAAG,CAAC;IAE7E,MAAM,CAAC,OAAqB,EAAE,YAAqB,EAAE,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE;QACnE,IAAI,CAAC,KAAK,IAAI,CAAC,CAAC;QAChB,MAAM,aAAa,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,OAAO,CAAC,mBAAmB,IAAI,CAAC,CAAC,CAAC;QACzE,MAAM,WAAW,GAAG,IAAI,CAAC,GAAG,CAAC,aAAa,GAAG,CAAC,EAAE,IAAI,CAAC,OAAO,CAAC,iBAAiB,IAAI,CAAC,CAAC,CAAC;QACrF,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,OAAO,CAAC,oBAAoB,IAAI,EAAE,CAAC,CAAC;QACtE,4EAA4E;QAC5E,sEAAsE;QACtE,wEAAwE;QACxE,6BAA6B;QAC7B,MAAM,cAAc,GAAG,OAAO,CAAC,MAAM,IAAI,CAAC,YAAY,IAAI,IAAI,CAAC,MAAM,IAAI,WAAW,GAAG,aAAa,CAAC,IAAI,IAAI,CAAC,MAAM,IAAI,WAAW,IAAI,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,eAAe,IAAI,QAAQ,IAAI,GAAG,GAAG,IAAI,CAAC,aAAa,GAAG,GAAG,CAAC;QACvN,IAAI,cAAc,EAAE,CAAC;YAAC,IAAI,CAAC,eAAe,GAAG,IAAI,CAAC,KAAK,CAAC;YAAC,OAAO,QAAQ,CAAC;QAAC,CAAC;QAC3E,IAAI,OAAO,CAAC,MAAM,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,IAAI,IAAI,CAAC,MAAM,GAAG,aAAa,KAAK,CAAC;YAAE,OAAO,UAAU,CAAC;QAC9F,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,OAAO,CAAC,OAAgB,EAAE,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE;QACxC,IAAI,OAAO,EAAE,CAAC;YAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC;YAAC,IAAI,CAAC,aAAa,GAAG,GAAG,CAAC;QAAC,CAAC;;YACtD,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IACvD,CAAC;IAED,KAAK,KAAW,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,eAAe,GAAG,MAAM,CAAC,iBAAiB,CAAC,CAAC,IAAI,CAAC,aAAa,GAAG,CAAC,CAAC,CAAC,CAAC;IAC3H,IAAI,iBAAiB,KAAa,OAAO,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC;CACxD"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { type BarcodeDifficultyDiagnosis, type NormalizedFrame } from "@scanly/core";
|
|
2
|
+
import type { FrameQuality } from "./types.js";
|
|
3
|
+
export interface FrameQualityAnalyzerOptions {
|
|
4
|
+
sampleTarget?: number;
|
|
5
|
+
underexposedThreshold?: number;
|
|
6
|
+
overexposedThreshold?: number;
|
|
7
|
+
blurThreshold?: number;
|
|
8
|
+
contrastThreshold?: number;
|
|
9
|
+
glareThreshold?: number;
|
|
10
|
+
}
|
|
11
|
+
/** Low-cost luminance/gradient quality analysis; no ML model or retained frame buffer. */
|
|
12
|
+
export declare class FrameQualityAnalyzer {
|
|
13
|
+
private readonly options;
|
|
14
|
+
private previous;
|
|
15
|
+
private previousShape;
|
|
16
|
+
private readonly difficultyAnalyzer;
|
|
17
|
+
constructor(options?: FrameQualityAnalyzerOptions);
|
|
18
|
+
analyze(frame: NormalizedFrame): FrameQuality;
|
|
19
|
+
reset(): void;
|
|
20
|
+
/** Separate Beta 3 routing evidence; this does not turn quality heuristics into Ground Truth. */
|
|
21
|
+
diagnose(frame: NormalizedFrame, quality?: FrameQuality): BarcodeDifficultyDiagnosis;
|
|
22
|
+
private luminance;
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=frame-quality.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"frame-quality.d.ts","sourceRoot":"","sources":["../../src/scanner/frame-quality.ts"],"names":[],"mappings":"AAAA,OAAO,EAA6B,KAAK,0BAA0B,EAAE,KAAK,eAAe,EAAE,MAAM,cAAc,CAAC;AAChH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE/C,MAAM,WAAW,2BAA2B;IAC1C,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAID,0FAA0F;AAC1F,qBAAa,oBAAoB;IAKnB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAJpC,OAAO,CAAC,QAAQ,CAA6B;IAC7C,OAAO,CAAC,aAAa,CAAM;IAC3B,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAA4B;gBAElC,OAAO,GAAE,2BAAgC;IAItE,OAAO,CAAC,KAAK,EAAE,eAAe,GAAG,YAAY;IAiE7C,KAAK,IAAI,IAAI;IAEb,iGAAiG;IACjG,QAAQ,CAAC,KAAK,EAAE,eAAe,EAAE,OAAO,eAAsB,GAAG,0BAA0B;IAI3F,OAAO,CAAC,SAAS;CAOlB"}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { BarcodeDifficultyAnalyzer } from "@scanly/core";
|
|
2
|
+
function clamp01(value) { return Math.max(0, Math.min(1, value)); }
|
|
3
|
+
/** Low-cost luminance/gradient quality analysis; no ML model or retained frame buffer. */
|
|
4
|
+
export class FrameQualityAnalyzer {
|
|
5
|
+
options;
|
|
6
|
+
previous = null;
|
|
7
|
+
previousShape = "";
|
|
8
|
+
difficultyAnalyzer;
|
|
9
|
+
constructor(options = {}) {
|
|
10
|
+
this.options = options;
|
|
11
|
+
this.difficultyAnalyzer = new BarcodeDifficultyAnalyzer({ sampleTarget: options.sampleTarget });
|
|
12
|
+
}
|
|
13
|
+
analyze(frame) {
|
|
14
|
+
const target = Math.max(64, Math.min(4_096, this.options.sampleTarget ?? 1_024));
|
|
15
|
+
const stride = Math.max(1, Math.floor(Math.sqrt((frame.width * frame.height) / target)));
|
|
16
|
+
const values = [];
|
|
17
|
+
let sum = 0;
|
|
18
|
+
let sumSquares = 0;
|
|
19
|
+
let glare = 0;
|
|
20
|
+
let edgeSum = 0;
|
|
21
|
+
let edgeSquares = 0;
|
|
22
|
+
let edgeCount = 0;
|
|
23
|
+
let previousRow = null;
|
|
24
|
+
for (let y = 0; y < frame.height; y += stride) {
|
|
25
|
+
const row = [];
|
|
26
|
+
for (let x = 0; x < frame.width; x += stride) {
|
|
27
|
+
const luminance = this.luminance(frame, x, y);
|
|
28
|
+
values.push(luminance);
|
|
29
|
+
row.push(luminance);
|
|
30
|
+
sum += luminance;
|
|
31
|
+
sumSquares += luminance * luminance;
|
|
32
|
+
if (luminance >= 250)
|
|
33
|
+
glare += 1;
|
|
34
|
+
const left = row.length > 1 ? row[row.length - 2] : luminance;
|
|
35
|
+
const above = previousRow?.[row.length - 1] ?? luminance;
|
|
36
|
+
const gradient = Math.abs(luminance - left) + Math.abs(luminance - above);
|
|
37
|
+
edgeSum += gradient;
|
|
38
|
+
edgeSquares += gradient * gradient;
|
|
39
|
+
edgeCount += 1;
|
|
40
|
+
}
|
|
41
|
+
previousRow = row;
|
|
42
|
+
}
|
|
43
|
+
const count = Math.max(1, values.length);
|
|
44
|
+
const mean = sum / count;
|
|
45
|
+
const variance = Math.max(0, sumSquares / count - mean * mean);
|
|
46
|
+
const gradientMean = edgeSum / Math.max(1, edgeCount);
|
|
47
|
+
const gradientVariance = Math.max(0, edgeSquares / Math.max(1, edgeCount) - gradientMean * gradientMean);
|
|
48
|
+
const brightness = clamp01(mean / 255);
|
|
49
|
+
const contrast = clamp01(Math.sqrt(variance) / 96);
|
|
50
|
+
const edgeDensity = clamp01(gradientMean / 64);
|
|
51
|
+
const blurScore = clamp01((gradientVariance + gradientMean * gradientMean) / 2_500);
|
|
52
|
+
const glareRatio = glare / count;
|
|
53
|
+
const current = Float32Array.from(values, (value) => value / 255);
|
|
54
|
+
const shape = `${frame.width}x${frame.height}:${stride}:${current.length}`;
|
|
55
|
+
let motionEstimate;
|
|
56
|
+
if (this.previous && this.previousShape === shape && this.previous.length === current.length) {
|
|
57
|
+
let delta = 0;
|
|
58
|
+
for (let index = 0; index < current.length; index += 1)
|
|
59
|
+
delta += Math.abs(current[index] - this.previous[index]);
|
|
60
|
+
motionEstimate = clamp01(delta / current.length);
|
|
61
|
+
}
|
|
62
|
+
this.previous = current;
|
|
63
|
+
this.previousShape = shape;
|
|
64
|
+
const underexposed = brightness < (this.options.underexposedThreshold ?? 0.12);
|
|
65
|
+
const overexposed = brightness > (this.options.overexposedThreshold ?? 0.92);
|
|
66
|
+
const blurred = blurScore < (this.options.blurThreshold ?? 0.08);
|
|
67
|
+
const glareDominated = glareRatio > (this.options.glareThreshold ?? 0.22);
|
|
68
|
+
const lowContrast = contrast < (this.options.contrastThreshold ?? 0.08);
|
|
69
|
+
return {
|
|
70
|
+
blurScore, brightness, contrast, glareRatio, edgeDensity,
|
|
71
|
+
...(motionEstimate === undefined ? {} : { motionEstimate }),
|
|
72
|
+
underexposed, overexposed, blurred, glareDominated,
|
|
73
|
+
usable: !(underexposed || overexposed || blurred || glareDominated || lowContrast),
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
reset() { this.previous = null; this.previousShape = ""; }
|
|
77
|
+
/** Separate Beta 3 routing evidence; this does not turn quality heuristics into Ground Truth. */
|
|
78
|
+
diagnose(frame, quality = this.analyze(frame)) {
|
|
79
|
+
return this.difficultyAnalyzer.analyze(frame, quality);
|
|
80
|
+
}
|
|
81
|
+
luminance(frame, x, y) {
|
|
82
|
+
const row = y * frame.rowStride;
|
|
83
|
+
if (frame.pixelFormat === "gray8" || frame.pixelFormat === "yuv420")
|
|
84
|
+
return frame.data[row + x] ?? 0;
|
|
85
|
+
const channels = frame.pixelFormat === "rgba8888" ? 4 : 3;
|
|
86
|
+
const offset = row + x * channels;
|
|
87
|
+
return (frame.data[offset] ?? 0) * 0.2126 + (frame.data[offset + 1] ?? 0) * 0.7152 + (frame.data[offset + 2] ?? 0) * 0.0722;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
//# sourceMappingURL=frame-quality.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"frame-quality.js","sourceRoot":"","sources":["../../src/scanner/frame-quality.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { NormalizedFrame } from "@scanly/core";
|
|
2
|
+
import type { FrameQuality } from "./types.js";
|
|
3
|
+
export interface FrameSchedulerOptions {
|
|
4
|
+
initialDecodeFps?: number;
|
|
5
|
+
minimumDecodeFps?: number;
|
|
6
|
+
maximumDecodeFps?: number;
|
|
7
|
+
now?: () => number;
|
|
8
|
+
onAdmitted?: () => void;
|
|
9
|
+
onDropped?: () => void;
|
|
10
|
+
onState?: (state: {
|
|
11
|
+
active: number;
|
|
12
|
+
pending: number;
|
|
13
|
+
peakPending: number;
|
|
14
|
+
effectiveDecodeFps: number;
|
|
15
|
+
}) => void;
|
|
16
|
+
}
|
|
17
|
+
export interface FrameProcessFeedback {
|
|
18
|
+
decodeMs?: number;
|
|
19
|
+
success?: boolean;
|
|
20
|
+
quality?: FrameQuality;
|
|
21
|
+
}
|
|
22
|
+
/** One-active-decode scheduler with latest-frame replacement and adaptive sampling cadence. */
|
|
23
|
+
export declare class FrameScheduler {
|
|
24
|
+
private readonly process;
|
|
25
|
+
private readonly options;
|
|
26
|
+
private running;
|
|
27
|
+
private paused;
|
|
28
|
+
private active;
|
|
29
|
+
private pending;
|
|
30
|
+
private timer;
|
|
31
|
+
private activePromise;
|
|
32
|
+
private idleWaiters;
|
|
33
|
+
private targetFps;
|
|
34
|
+
private peakPending;
|
|
35
|
+
private completed;
|
|
36
|
+
private startedAt;
|
|
37
|
+
constructor(process: (frame: NormalizedFrame) => Promise<FrameProcessFeedback | void>, options?: FrameSchedulerOptions);
|
|
38
|
+
start(): void;
|
|
39
|
+
pause(): void;
|
|
40
|
+
resume(): void;
|
|
41
|
+
submit(frame: NormalizedFrame): "admitted" | "queued-latest" | "dropped";
|
|
42
|
+
waitForIdle(): Promise<void>;
|
|
43
|
+
stop(): Promise<void>;
|
|
44
|
+
reset(): void;
|
|
45
|
+
getStatistics(): {
|
|
46
|
+
active: number;
|
|
47
|
+
pending: number;
|
|
48
|
+
peakPending: number;
|
|
49
|
+
effectiveDecodeFps: number;
|
|
50
|
+
targetDecodeFps: number;
|
|
51
|
+
};
|
|
52
|
+
private drain;
|
|
53
|
+
private adapt;
|
|
54
|
+
private boundFps;
|
|
55
|
+
private release;
|
|
56
|
+
private resolveIdle;
|
|
57
|
+
private notify;
|
|
58
|
+
}
|
|
59
|
+
//# sourceMappingURL=frame-scheduler.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"frame-scheduler.d.ts","sourceRoot":"","sources":["../../src/scanner/frame-scheduler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AACpD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE/C,MAAM,WAAW,qBAAqB;IACpC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,IAAI,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;IACvB,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAC;QAAC,kBAAkB,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;CACjH;AAED,MAAM,WAAW,oBAAoB;IAAG,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAC;IAAC,OAAO,CAAC,EAAE,YAAY,CAAA;CAAE;AAEtG,+FAA+F;AAC/F,qBAAa,cAAc;IAcvB,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAd1B,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,OAAO,CAAgC;IAC/C,OAAO,CAAC,KAAK,CAA8C;IAC3D,OAAO,CAAC,aAAa,CAA8B;IACnD,OAAO,CAAC,WAAW,CAAyB;IAC5C,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,WAAW,CAAK;IACxB,OAAO,CAAC,SAAS,CAAK;IACtB,OAAO,CAAC,SAAS,CAAK;gBAGH,OAAO,EAAE,CAAC,KAAK,EAAE,eAAe,KAAK,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC,EACzE,OAAO,GAAE,qBAA0B;IAKtD,KAAK,IAAI,IAAI;IAOb,KAAK,IAAI,IAAI;IAYb,MAAM,IAAI,IAAI;IAEd,MAAM,CAAC,KAAK,EAAE,eAAe,GAAG,UAAU,GAAG,eAAe,GAAG,SAAS;IAmBlE,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC;IAK5B,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAY3B,KAAK,IAAI,IAAI;IAOb,aAAa,IAAI;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAC;QAAC,kBAAkB,EAAE,MAAM,CAAC;QAAC,eAAe,EAAE,MAAM,CAAA;KAAE;IAK9H,OAAO,CAAC,KAAK;IA0Bb,OAAO,CAAC,KAAK;IAQb,OAAO,CAAC,QAAQ;IAMhB,OAAO,CAAC,OAAO;IACf,OAAO,CAAC,WAAW;IAKnB,OAAO,CAAC,MAAM;CAIf"}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/** One-active-decode scheduler with latest-frame replacement and adaptive sampling cadence. */
|
|
2
|
+
export class FrameScheduler {
|
|
3
|
+
process;
|
|
4
|
+
options;
|
|
5
|
+
running = false;
|
|
6
|
+
paused = false;
|
|
7
|
+
active = false;
|
|
8
|
+
pending = null;
|
|
9
|
+
timer = null;
|
|
10
|
+
activePromise = null;
|
|
11
|
+
idleWaiters = new Set();
|
|
12
|
+
targetFps;
|
|
13
|
+
peakPending = 0;
|
|
14
|
+
completed = 0;
|
|
15
|
+
startedAt = 0;
|
|
16
|
+
constructor(process, options = {}) {
|
|
17
|
+
this.process = process;
|
|
18
|
+
this.options = options;
|
|
19
|
+
this.targetFps = this.boundFps(options.initialDecodeFps ?? 10);
|
|
20
|
+
}
|
|
21
|
+
start() {
|
|
22
|
+
this.running = true;
|
|
23
|
+
this.paused = false;
|
|
24
|
+
this.startedAt = this.options.now?.() ?? Date.now();
|
|
25
|
+
this.drain();
|
|
26
|
+
}
|
|
27
|
+
pause() {
|
|
28
|
+
this.paused = true;
|
|
29
|
+
if (this.timer)
|
|
30
|
+
clearTimeout(this.timer);
|
|
31
|
+
this.timer = null;
|
|
32
|
+
if (this.pending) {
|
|
33
|
+
this.release(this.pending);
|
|
34
|
+
this.pending = null;
|
|
35
|
+
this.options.onDropped?.();
|
|
36
|
+
}
|
|
37
|
+
this.resolveIdle();
|
|
38
|
+
this.notify();
|
|
39
|
+
}
|
|
40
|
+
resume() { if (!this.running)
|
|
41
|
+
return; this.paused = false; this.drain(); }
|
|
42
|
+
submit(frame) {
|
|
43
|
+
if (!this.running || this.paused) {
|
|
44
|
+
this.release(frame);
|
|
45
|
+
this.options.onDropped?.();
|
|
46
|
+
this.notify();
|
|
47
|
+
return "dropped";
|
|
48
|
+
}
|
|
49
|
+
if (this.active || this.timer) {
|
|
50
|
+
if (this.pending) {
|
|
51
|
+
this.release(this.pending);
|
|
52
|
+
this.options.onDropped?.();
|
|
53
|
+
}
|
|
54
|
+
this.pending = frame;
|
|
55
|
+
this.peakPending = Math.max(this.peakPending, 1);
|
|
56
|
+
this.notify();
|
|
57
|
+
return "queued-latest";
|
|
58
|
+
}
|
|
59
|
+
this.pending = frame;
|
|
60
|
+
this.drain();
|
|
61
|
+
return "admitted";
|
|
62
|
+
}
|
|
63
|
+
async waitForIdle() {
|
|
64
|
+
if (!this.active && !this.pending && !this.timer)
|
|
65
|
+
return;
|
|
66
|
+
await new Promise((resolve) => this.idleWaiters.add(resolve));
|
|
67
|
+
}
|
|
68
|
+
async stop() {
|
|
69
|
+
this.running = false;
|
|
70
|
+
this.paused = false;
|
|
71
|
+
if (this.timer)
|
|
72
|
+
clearTimeout(this.timer);
|
|
73
|
+
this.timer = null;
|
|
74
|
+
if (this.pending)
|
|
75
|
+
this.release(this.pending);
|
|
76
|
+
this.pending = null;
|
|
77
|
+
await this.activePromise;
|
|
78
|
+
this.resolveIdle();
|
|
79
|
+
this.notify();
|
|
80
|
+
}
|
|
81
|
+
reset() {
|
|
82
|
+
this.targetFps = this.boundFps(this.options.initialDecodeFps ?? 10);
|
|
83
|
+
this.peakPending = 0;
|
|
84
|
+
this.completed = 0;
|
|
85
|
+
this.startedAt = this.options.now?.() ?? Date.now();
|
|
86
|
+
}
|
|
87
|
+
getStatistics() {
|
|
88
|
+
const elapsed = Math.max(1, (this.options.now?.() ?? Date.now()) - this.startedAt);
|
|
89
|
+
return { active: this.active ? 1 : 0, pending: this.pending ? 1 : 0, peakPending: this.peakPending, effectiveDecodeFps: this.completed * 1_000 / elapsed, targetDecodeFps: this.targetFps };
|
|
90
|
+
}
|
|
91
|
+
drain() {
|
|
92
|
+
if (!this.running || this.paused || this.active || this.timer || !this.pending) {
|
|
93
|
+
this.resolveIdle();
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
const frame = this.pending;
|
|
97
|
+
this.pending = null;
|
|
98
|
+
this.active = true;
|
|
99
|
+
this.options.onAdmitted?.();
|
|
100
|
+
const started = this.options.now?.() ?? Date.now();
|
|
101
|
+
this.notify();
|
|
102
|
+
this.activePromise = (async () => {
|
|
103
|
+
try {
|
|
104
|
+
const feedback = await this.process(frame);
|
|
105
|
+
this.completed += 1;
|
|
106
|
+
const elapsed = feedback?.decodeMs ?? Math.max(0, (this.options.now?.() ?? Date.now()) - started);
|
|
107
|
+
this.adapt(elapsed, feedback?.quality, feedback?.success);
|
|
108
|
+
}
|
|
109
|
+
finally {
|
|
110
|
+
this.active = false;
|
|
111
|
+
this.activePromise = null;
|
|
112
|
+
const delay = Math.max(0, Math.round(1_000 / this.targetFps));
|
|
113
|
+
if (this.running && !this.paused && this.pending && delay > 0) {
|
|
114
|
+
this.timer = setTimeout(() => { this.timer = null; this.drain(); }, delay);
|
|
115
|
+
}
|
|
116
|
+
else
|
|
117
|
+
this.drain();
|
|
118
|
+
this.notify();
|
|
119
|
+
}
|
|
120
|
+
})();
|
|
121
|
+
}
|
|
122
|
+
adapt(decodeMs, quality, success) {
|
|
123
|
+
const maximum = this.options.maximumDecodeFps ?? 15;
|
|
124
|
+
const minimum = this.options.minimumDecodeFps ?? 5;
|
|
125
|
+
const latencyBound = decodeMs >= 180 ? minimum : decodeMs >= 85 ? Math.min(10, maximum) : maximum;
|
|
126
|
+
const qualityBound = quality && !quality.usable ? minimum : success === false ? Math.min(10, maximum) : maximum;
|
|
127
|
+
this.targetFps = this.boundFps(Math.min(latencyBound, qualityBound));
|
|
128
|
+
}
|
|
129
|
+
boundFps(value) {
|
|
130
|
+
const minimum = Math.max(0.1, this.options.minimumDecodeFps ?? 5);
|
|
131
|
+
const maximum = Math.max(minimum, this.options.maximumDecodeFps ?? 15);
|
|
132
|
+
return Math.max(minimum, Math.min(maximum, value));
|
|
133
|
+
}
|
|
134
|
+
release(frame) { if (frame.ownership !== "borrowed")
|
|
135
|
+
frame.dispose?.(); }
|
|
136
|
+
resolveIdle() {
|
|
137
|
+
if (this.active || this.pending || this.timer)
|
|
138
|
+
return;
|
|
139
|
+
for (const resolve of this.idleWaiters)
|
|
140
|
+
resolve();
|
|
141
|
+
this.idleWaiters.clear();
|
|
142
|
+
}
|
|
143
|
+
notify() {
|
|
144
|
+
const stats = this.getStatistics();
|
|
145
|
+
this.options.onState?.({ active: stats.active, pending: stats.pending, peakPending: stats.peakPending, effectiveDecodeFps: stats.effectiveDecodeFps });
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
//# sourceMappingURL=frame-scheduler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"frame-scheduler.js","sourceRoot":"","sources":["../../src/scanner/frame-scheduler.ts"],"names":[],"mappings":"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"}
|