@nddeps/barcode-scanner 0.5.0 → 0.5.2

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.
@@ -0,0 +1,7 @@
1
+ declare const WORKER_DECODE_TIMEOUT: number;
2
+ declare const WORKER_DECODE_TIMEOUT_CAUSE = "decode-timeout";
3
+ declare const WORKER_DECODE_FAILURE_CAUSE = "decode-failure";
4
+ declare const WORKER_LOAD_FAILURE_CAUSE = "worker-load-failure";
5
+ declare const WORKER_LOAD_TIMEOUT: number;
6
+ declare const WORKER_LOAD_TIMEOUT_CAUSE = "worker-load-timeout";
7
+ export { WORKER_DECODE_FAILURE_CAUSE, WORKER_DECODE_TIMEOUT, WORKER_DECODE_TIMEOUT_CAUSE, WORKER_LOAD_FAILURE_CAUSE, WORKER_LOAD_TIMEOUT, WORKER_LOAD_TIMEOUT_CAUSE, };
@@ -0,0 +1,54 @@
1
+ import type { BarcodeFormat } from 'barcode-detector/ponyfill';
2
+ import { type ScanArea } from './utils';
3
+ type Context = {
4
+ state: State;
5
+ };
6
+ type DecodeFailureHandler = () => Promise<void> | void;
7
+ type DecodeSuccessHandler = (data: string, area: ScanArea) => Promise<void> | void;
8
+ type Lifecycle = {
9
+ onBeforeCreate?: LifecycleHook;
10
+ onBeforeDecode?: LifecycleHook;
11
+ onBeforePause?: LifecycleHook;
12
+ onBeforeStart?: LifecycleHook;
13
+ onBeforeStop?: LifecycleHook;
14
+ onCreate?: LifecycleHook;
15
+ onDecode?: LifecycleHook;
16
+ onPause?: LifecycleHook;
17
+ onStart?: LifecycleHook;
18
+ onStop?: LifecycleHook;
19
+ };
20
+ type LifecycleHook = (ctx: Context) => void;
21
+ type Options = {
22
+ debug?: boolean;
23
+ formats?: BarcodeFormat[];
24
+ getScanArea?: (video: HTMLVideoElement) => ScanArea;
25
+ handleDecodeFailure?: DecodeFailureHandler;
26
+ handleDecodeSuccess?: DecodeSuccessHandler;
27
+ lifecycle?: Lifecycle;
28
+ scanRate?: number;
29
+ };
30
+ type State = {
31
+ decodeFrameTs: number;
32
+ isDecodeFrameProcessed: boolean;
33
+ isDestroyed: boolean;
34
+ isVideoActive: boolean;
35
+ isVideoPaused: boolean;
36
+ isWorkerLoadFailure: boolean;
37
+ scanArea: ScanArea;
38
+ scanRate: number;
39
+ video: HTMLVideoElement;
40
+ };
41
+ declare function createBarcodeScanner(video: HTMLVideoElement, { debug, formats, getScanArea, handleDecodeFailure, handleDecodeSuccess, lifecycle, scanRate, }?: Options): Promise<{
42
+ decode: (imageData: ImageData) => Promise<import("barcode-detector/ponyfill").DetectedBarcode | null>;
43
+ destroy: () => void;
44
+ pause: () => Promise<void>;
45
+ start: ({ facingMode, ...rest }?: {
46
+ facingMode?: "environment" | "user";
47
+ handleDecodeFailure?: DecodeFailureHandler;
48
+ handleDecodeSuccess?: DecodeSuccessHandler;
49
+ }) => Promise<void>;
50
+ state: State;
51
+ stop: () => Promise<void>;
52
+ }>;
53
+ export type { DecodeFailureHandler, DecodeSuccessHandler, LifecycleHook, State };
54
+ export { createBarcodeScanner };
@@ -0,0 +1,8 @@
1
+ import type { BarcodeFormat, DetectedBarcode } from 'barcode-detector/ponyfill';
2
+ declare function createWorker({ formats }: {
3
+ formats: BarcodeFormat[];
4
+ }): {
5
+ decode: (imageData: ImageData) => Promise<DetectedBarcode | null>;
6
+ worker: Worker;
7
+ };
8
+ export { createWorker };
@@ -0,0 +1,5 @@
1
+ import { createBarcodeScanner } from './create-barcode-scanner';
2
+ export { createWorker } from './create-worker';
3
+ export * from './utils';
4
+ export { createBarcodeScanner };
5
+ export default createBarcodeScanner;
package/dist/index.js ADDED
@@ -0,0 +1,267 @@
1
+ const L = "decode-timeout", O = "worker-load-failure";
2
+ const R = "worker-load-timeout", V = "" + new URL("zxing-reader.wasm", import.meta.url).href;
3
+ function H({ formats: e }) {
4
+ const o = new Worker(new URL(
5
+ /* @vite-ignore */
6
+ "" + new URL("worker.js", import.meta.url).href,
7
+ import.meta.url
8
+ ), { type: "module" }), a = new Promise((i, s) => {
9
+ const r = setTimeout(() => {
10
+ o.removeEventListener("message", h), s(new Error(R));
11
+ }, 32e3), h = ({ data: { payload: c, type: u } }) => {
12
+ u === "init" && (clearTimeout(r), o.removeEventListener("message", h), c.status === "success" ? i(!0) : s(new Error(O)));
13
+ };
14
+ o.addEventListener("message", h), o.postMessage({
15
+ payload: {
16
+ formats: e,
17
+ wasmUrl: V
18
+ },
19
+ type: "config"
20
+ });
21
+ });
22
+ async function n(i) {
23
+ await a;
24
+ const s = `${performance.now()}-${Math.random().toString(36).slice(2)}`;
25
+ return new Promise((r, h) => {
26
+ const c = setTimeout(() => {
27
+ o.removeEventListener("message", u), h(new Error(L));
28
+ }, 16e3), u = ({ data: { payload: g, type: p } }) => {
29
+ p !== "decode" || g.uuid !== s || (clearTimeout(c), o.removeEventListener("message", u), r(g.data));
30
+ };
31
+ o.addEventListener("message", u), o.postMessage({
32
+ payload: {
33
+ data: i,
34
+ uuid: s
35
+ },
36
+ type: "decode"
37
+ });
38
+ });
39
+ }
40
+ return {
41
+ decode: n,
42
+ worker: o
43
+ };
44
+ }
45
+ async function _() {
46
+ try {
47
+ return (await navigator.permissions.query({ name: "camera" })).state === "granted";
48
+ } catch {
49
+ return (await navigator.mediaDevices.enumerateDevices()).filter((a) => a.deviceId && a.kind === "videoinput").length > 0;
50
+ }
51
+ }
52
+ async function b() {
53
+ if (await _())
54
+ return !0;
55
+ try {
56
+ const o = (await navigator.mediaDevices.getUserMedia({ video: !0 })).getTracks();
57
+ for (const a of o)
58
+ a.stop();
59
+ return !0;
60
+ } catch {
61
+ return !1;
62
+ }
63
+ }
64
+ function C(e) {
65
+ const o = Math.round(0.6666666666666666 * Math.min(e.videoWidth, e.videoHeight));
66
+ return {
67
+ height: o,
68
+ width: o,
69
+ x: Math.round((e.videoWidth - o) / 2),
70
+ y: Math.round((e.videoHeight - o) / 2)
71
+ };
72
+ }
73
+ function W(e, o) {
74
+ const a = window.getComputedStyle(e), [n, i] = a.objectPosition.split(" ").map(
75
+ (s, r) => s.endsWith("%") ? (r === 0 ? e.offsetWidth - o.width : e.offsetHeight - o.height) * parseFloat(s) / 100 : parseFloat(s)
76
+ );
77
+ return {
78
+ x: n,
79
+ y: i
80
+ };
81
+ }
82
+ function S(e) {
83
+ const o = window.getComputedStyle(e), a = { height: e.offsetHeight, width: e.offsetWidth }, n = a.width / a.height, i = { height: e.videoHeight, width: e.videoWidth }, s = i.width / i.height;
84
+ switch (o.objectFit) {
85
+ case "contain":
86
+ return {
87
+ height: s < n ? e.offsetHeight : e.offsetWidth / s,
88
+ width: s < n ? e.offsetHeight * s : e.offsetWidth
89
+ };
90
+ case "cover":
91
+ return {
92
+ height: s > n ? e.offsetHeight : e.offsetWidth / s,
93
+ width: s > n ? e.offsetHeight * s : e.offsetWidth
94
+ };
95
+ case "fill":
96
+ return a;
97
+ case "none":
98
+ return i;
99
+ case "scale-down":
100
+ return {
101
+ height: Math.min(
102
+ s < n ? e.offsetHeight : e.offsetWidth / s,
103
+ e.videoHeight
104
+ ),
105
+ width: Math.min(
106
+ s < n ? e.offsetHeight * s : e.offsetWidth,
107
+ e.videoWidth
108
+ )
109
+ };
110
+ default:
111
+ return i;
112
+ }
113
+ }
114
+ function U(e) {
115
+ return "BarcodeDetector" in e;
116
+ }
117
+ function I(e, o) {
118
+ const a = /scaleX\(-1\)/.test(e.style.transform), n = S(e), i = W(e, n), s = a ? e.videoWidth - o.x - o.width : o.x, r = o.y;
119
+ return {
120
+ height: o.height / e.videoHeight * n.height,
121
+ width: o.width / e.videoWidth * n.width,
122
+ x: s / e.videoWidth * n.width + i.x,
123
+ y: r / e.videoHeight * n.height + i.y
124
+ };
125
+ }
126
+ function B(e, o) {
127
+ const a = /scaleX\(-1\)/.test(e.style.transform), n = S(e), i = W(e, n), s = a ? n.width - (o.x - i.x) - o.width : o.x - i.x, r = o.y - i.y, h = e.videoHeight / n.height, c = e.videoWidth / n.width;
128
+ return {
129
+ height: o.height * h,
130
+ width: o.width * c,
131
+ x: s * c,
132
+ y: r * h
133
+ };
134
+ }
135
+ function j(e) {
136
+ return new Promise((o) => setTimeout(o, e));
137
+ }
138
+ async function q(e, {
139
+ debug: o,
140
+ formats: a = ["qr_code"],
141
+ getScanArea: n = C,
142
+ handleDecodeFailure: i,
143
+ handleDecodeSuccess: s,
144
+ lifecycle: r = {},
145
+ scanRate: h = 24
146
+ } = {}) {
147
+ if (!(e instanceof HTMLVideoElement))
148
+ throw new Error("video is not a HTMLVideoElement");
149
+ if (!(s instanceof Function))
150
+ throw new Error("handleDecodeSuccess is not a function");
151
+ if (!(i instanceof Function))
152
+ throw new Error("handleDecodeFailure is not a function");
153
+ const c = document.createElement("canvas"), u = c.getContext("2d", { willReadFrequently: !0 });
154
+ if (!u)
155
+ throw new Error("canvas context is not supported");
156
+ const { decode: g, worker: p } = H({ formats: a }), t = {
157
+ decodeFrameTs: performance.now(),
158
+ isDecodeFrameProcessed: !1,
159
+ isDestroyed: !1,
160
+ isVideoActive: !1,
161
+ isVideoPaused: !1,
162
+ isWorkerLoadFailure: !1,
163
+ scanArea: n(e),
164
+ scanRate: h,
165
+ video: e
166
+ }, f = { state: t }, v = e.requestVideoFrameCallback?.bind(e) ?? requestAnimationFrame;
167
+ t.video.autoplay = !0, t.video.disablePictureInPicture = !0, t.video.hidden = !1, t.video.muted = !0, t.video.playsInline = !0, r.onCreate && r.onCreate(f);
168
+ function T(m, l = () => {
169
+ }) {
170
+ v(w);
171
+ async function w() {
172
+ if (t.isDestroyed || t.isVideoActive === !1)
173
+ return;
174
+ if (
175
+ // Skip if the time since the last request frame is less than the scan rate
176
+ performance.now() - t.decodeFrameTs < 1e3 / t.scanRate || // Skip if the frame is already processed
177
+ t.isDecodeFrameProcessed || // Skip if the video is not ready
178
+ t.video.readyState <= 1
179
+ ) {
180
+ v(w);
181
+ return;
182
+ }
183
+ t.isDecodeFrameProcessed = !0, t.scanArea = n(t.video), r.onBeforeDecode && r.onBeforeDecode(f), c.height = t.scanArea.height, c.width = t.scanArea.width, u.clearRect(0, 0, c.width, c.height), u.drawImage(
184
+ t.video,
185
+ t.scanArea.x,
186
+ t.scanArea.y,
187
+ t.scanArea.width,
188
+ t.scanArea.height,
189
+ 0,
190
+ 0,
191
+ c.width,
192
+ c.height
193
+ );
194
+ const E = u.getImageData(0, 0, c.width, c.height);
195
+ o && window.dispatchEvent(
196
+ new CustomEvent("barcode-scanner:decode-frame", {
197
+ detail: {
198
+ imageData: E
199
+ }
200
+ })
201
+ );
202
+ try {
203
+ const d = await g(E);
204
+ if (d) {
205
+ const D = d.cornerPoints.map((y) => y.x), A = d.cornerPoints.map((y) => y.y), k = {
206
+ height: Math.max(...A) - Math.min(...A),
207
+ width: Math.max(...D) - Math.min(...D),
208
+ x: Math.min(...D) + t.scanArea.x,
209
+ y: Math.min(...A) + t.scanArea.y
210
+ };
211
+ await Promise.resolve(m(d.rawValue, k));
212
+ } else
213
+ await Promise.resolve(l());
214
+ } catch (d) {
215
+ console.warn("Failed to decode barcode"), d && (console.error(d), d instanceof Error && (d.cause === O || d.cause === R) && (t.isWorkerLoadFailure = !0));
216
+ } finally {
217
+ r.onDecode && r.onDecode(f), t.isWorkerLoadFailure === !1 && (t.isDecodeFrameProcessed = !1, t.decodeFrameTs = performance.now(), v(w));
218
+ }
219
+ }
220
+ }
221
+ function F() {
222
+ t.isDestroyed || (M(), p.terminate(), t.isDestroyed = !0);
223
+ }
224
+ async function P() {
225
+ t.isVideoActive === !1 || t.isVideoPaused || t.isDestroyed || (r.onBeforePause && r.onBeforePause(f), t.video.srcObject instanceof MediaStream && (t.video.srcObject.getTracks().forEach((m) => m.stop()), t.video.srcObject = null), t.isVideoPaused = !0, r.onPause && r.onPause(f));
226
+ }
227
+ async function x({
228
+ facingMode: m = "environment",
229
+ ...l
230
+ } = {}) {
231
+ const w = l.handleDecodeSuccess ?? s, E = l.handleDecodeFailure ?? i;
232
+ if (!w)
233
+ throw new Error("handleDecodeSuccess is required");
234
+ if (r.onBeforeStart && r.onBeforeStart(f), !await b())
235
+ throw new Error("No camera access");
236
+ t.video.srcObject instanceof MediaStream || (t.video.srcObject = await navigator.mediaDevices.getUserMedia({
237
+ video: {
238
+ facingMode: m
239
+ }
240
+ }), await t.video.play(), t.isVideoActive = !0, t.isVideoPaused = !1, t.scanArea = n(t.video), t.video.style.transform = m === "user" ? "scaleX(-1)" : "none", r.onStart && r.onStart(f), T(w, E));
241
+ }
242
+ async function M() {
243
+ t.isVideoActive === !1 || t.isDestroyed || (r.onBeforeStop && r.onBeforeStop(f), t.video.srcObject instanceof MediaStream && (t.video.srcObject.getTracks().forEach((m) => m.stop()), t.video.srcObject = null), t.isVideoActive = !1, t.isVideoPaused = !1, t.video.poster = "", r.onStop && r.onStop(f));
244
+ }
245
+ return {
246
+ decode: g,
247
+ destroy: F,
248
+ pause: P,
249
+ start: x,
250
+ state: t,
251
+ stop: M
252
+ };
253
+ }
254
+ export {
255
+ q as createBarcodeScanner,
256
+ H as createWorker,
257
+ q as default,
258
+ b as getCameraAccess,
259
+ C as getScanArea,
260
+ W as getVideoRenderOffset,
261
+ S as getVideoRenderSize,
262
+ _ as hasCameraAccess,
263
+ U as isBarcodeDetectorAvailable,
264
+ I as translateAreaToVideoRender,
265
+ B as translateAreaToVideoSource,
266
+ j as wait
267
+ };
@@ -0,0 +1,2 @@
1
+ declare function getCameraAccess(): Promise<boolean>;
2
+ export { getCameraAccess };
@@ -0,0 +1,9 @@
1
+ type ScanArea = {
2
+ height: number;
3
+ width: number;
4
+ x: number;
5
+ y: number;
6
+ };
7
+ declare function getScanArea(video: HTMLVideoElement): ScanArea;
8
+ export type { ScanArea };
9
+ export { getScanArea };
@@ -0,0 +1,8 @@
1
+ import type { RenderSize } from './get-video-render-size';
2
+ type RenderOffset = {
3
+ x: number;
4
+ y: number;
5
+ };
6
+ declare function getVideoRenderOffset(video: HTMLVideoElement, renderSize: RenderSize): RenderOffset;
7
+ export type { RenderOffset };
8
+ export { getVideoRenderOffset };
@@ -0,0 +1,7 @@
1
+ type RenderSize = {
2
+ height: number;
3
+ width: number;
4
+ };
5
+ declare function getVideoRenderSize(video: HTMLVideoElement): RenderSize;
6
+ export type { RenderSize };
7
+ export { getVideoRenderSize };
@@ -0,0 +1,2 @@
1
+ declare function hasCameraAccess(): Promise<boolean>;
2
+ export { hasCameraAccess };
@@ -0,0 +1,9 @@
1
+ export * from './get-camera-access';
2
+ export * from './get-scan-area';
3
+ export * from './get-video-render-offset';
4
+ export * from './get-video-render-size';
5
+ export * from './has-camera-access';
6
+ export * from './is-barcode-detector-available';
7
+ export * from './translate-area-to-video-render';
8
+ export * from './translate-area-to-video-source';
9
+ export * from './wait';
@@ -0,0 +1,7 @@
1
+ import type { BarcodeDetector, BarcodeDetectorOptions } from 'barcode-detector/ponyfill';
2
+ declare function isBarcodeDetectorAvailable<T extends object>(value: T): value is {
3
+ BarcodeDetector: {
4
+ new (barcodeDetectorOptions?: BarcodeDetectorOptions): BarcodeDetector;
5
+ } & BarcodeDetector;
6
+ } & T;
7
+ export { isBarcodeDetectorAvailable };
@@ -0,0 +1,3 @@
1
+ import type { ScanArea } from './get-scan-area';
2
+ declare function translateAreaToVideoRender(video: HTMLVideoElement, area: ScanArea): ScanArea;
3
+ export { translateAreaToVideoRender };
@@ -0,0 +1,3 @@
1
+ import type { ScanArea } from './get-scan-area';
2
+ declare function translateAreaToVideoSource(video: HTMLVideoElement, area: ScanArea): ScanArea;
3
+ export { translateAreaToVideoSource };
@@ -0,0 +1,2 @@
1
+ declare function wait(ms: number): Promise<unknown>;
2
+ export { wait };