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