1 | import { ResizeObserver as ResizeObserverPolyfill } from "@juggle/resize-observer";
|
2 |
|
3 |
|
4 | const ResizeObserver: typeof ResizeObserverPolyfill =
|
5 |
|
6 | "ResizeObserver" in window ? (<any>window).ResizeObserver : ResizeObserverPolyfill;
|
7 |
|
8 | import {
|
9 | cameraImage,
|
10 | laserActiveImage,
|
11 | laserPausedImage,
|
12 | scanditLogoImage,
|
13 | switchCameraImage,
|
14 | toggleTorchImage
|
15 | } from "./assets/base64assets";
|
16 | import { BarcodePicker } from "./barcodePicker";
|
17 | import { BrowserHelper } from "./browserHelper";
|
18 | import { Camera } from "./camera";
|
19 | import { CameraAccess } from "./cameraAccess";
|
20 | import { CameraManager } from "./cameraManager";
|
21 | import { ImageSettings } from "./imageSettings";
|
22 | import { Scanner } from "./scanner";
|
23 | import { ScanSettings } from "./scanSettings";
|
24 | import { SearchArea } from "./searchArea";
|
25 |
|
26 |
|
27 |
|
28 |
|
29 | export class BarcodePickerGui {
|
30 | public static readonly grandParentElementClassName: string = "scandit scandit-container";
|
31 | public static readonly parentElementClassName: string = "scandit scandit-barcode-picker";
|
32 | public static readonly hiddenClassName: string = "scandit-hidden";
|
33 | public static readonly hiddenOpacityClassName: string = "scandit-hidden-opacity";
|
34 | public static readonly videoElementClassName: string = "scandit-video";
|
35 | public static readonly scanditLogoImageElementClassName: string = "scandit-logo";
|
36 | public static readonly laserContainerElementClassName: string = "scandit-laser";
|
37 | public static readonly viewfinderElementClassName: string = "scandit-viewfinder";
|
38 | public static readonly cameraSwitcherElementClassName: string = "scandit-camera-switcher";
|
39 | public static readonly torchTogglerElementClassName: string = "scandit-torch-toggle";
|
40 | public static readonly cameraUploadElementClassName: string = "scandit-camera-upload";
|
41 | public static readonly flashColorClassName: string = "scandit-flash-color";
|
42 | public static readonly flashWhiteClassName: string = "scandit-flash-white";
|
43 | public static readonly flashWhiteInsetClassName: string = "scandit-flash-white-inset";
|
44 | public static readonly opacityPulseClassName: string = "scandit-opacity-pulse";
|
45 | public static readonly mirroredClassName: string = "mirrored";
|
46 | public static readonly pausedClassName: string = "paused";
|
47 |
|
48 | public readonly videoElement: HTMLVideoElement;
|
49 | public readonly cameraSwitcherElement: HTMLImageElement;
|
50 | public readonly torchTogglerElement: HTMLImageElement;
|
51 |
|
52 | private readonly scanner: Scanner;
|
53 | private readonly singleImageMode: boolean;
|
54 | private readonly grandParentElement: HTMLDivElement;
|
55 | private readonly parentElement: HTMLDivElement;
|
56 | private readonly laserContainerElement: HTMLDivElement;
|
57 | private readonly laserActiveImageElement: HTMLImageElement;
|
58 | private readonly laserPausedImageElement: HTMLImageElement;
|
59 | private readonly viewfinderElement: HTMLDivElement;
|
60 | private readonly cameraUploadElement: HTMLDivElement;
|
61 | private readonly cameraUploadInputElement: HTMLInputElement;
|
62 | private readonly cameraUploadLabelElement: HTMLLabelElement;
|
63 | private readonly cameraUploadProgressElement: HTMLDivElement;
|
64 | private readonly videoImageCanvasContext: CanvasRenderingContext2D;
|
65 | private readonly visibilityListener: EventListenerOrEventListenerObject;
|
66 | private readonly videoResizeListener: EventListenerOrEventListenerObject;
|
67 | private readonly newScanSettingsListener: (scanSettings: ScanSettings) => void;
|
68 | private readonly licenseFeaturesReadyListener: (licenseFeatures: object) => void;
|
69 | private readonly resizeObserver: ResizeObserverPolyfill;
|
70 | private readonly cameraUploadCallback: () => Promise<void>;
|
71 | private readonly mirrorImageOverrides: Map<string, boolean>;
|
72 |
|
73 | private cameraManager?: CameraManager;
|
74 | private originElement: HTMLElement;
|
75 | private scanningPaused: boolean;
|
76 | private visible: boolean;
|
77 | private guiStyle: BarcodePicker.GuiStyle;
|
78 | private videoFit: BarcodePicker.ObjectFit;
|
79 | private customLaserArea?: SearchArea;
|
80 | private customViewfinderArea?: SearchArea;
|
81 |
|
82 | public constructor(options: {
|
83 | scanner: Scanner;
|
84 | originElement: HTMLElement;
|
85 | singleImageMode: boolean;
|
86 | scanningPaused: boolean;
|
87 | visible: boolean;
|
88 | guiStyle: BarcodePicker.GuiStyle;
|
89 | videoFit: BarcodePicker.ObjectFit;
|
90 | hideLogo: boolean;
|
91 | laserArea?: SearchArea;
|
92 | viewfinderArea?: SearchArea;
|
93 | cameraUploadCallback(): Promise<void>;
|
94 | }) {
|
95 | this.scanner = options.scanner;
|
96 | this.originElement = options.originElement;
|
97 | this.singleImageMode = options.singleImageMode;
|
98 | this.scanningPaused = options.scanningPaused;
|
99 | this.cameraUploadCallback = options.cameraUploadCallback;
|
100 | this.mirrorImageOverrides = new Map<string, boolean>();
|
101 |
|
102 | this.grandParentElement = document.createElement("div");
|
103 | this.grandParentElement.className = BarcodePickerGui.grandParentElementClassName;
|
104 | this.originElement.appendChild(this.grandParentElement);
|
105 | this.parentElement = document.createElement("div");
|
106 | this.parentElement.className = BarcodePickerGui.parentElementClassName;
|
107 | this.grandParentElement.appendChild(this.parentElement);
|
108 |
|
109 | this.videoImageCanvasContext = <CanvasRenderingContext2D>document.createElement("canvas").getContext("2d");
|
110 |
|
111 | this.videoElement = document.createElement("video");
|
112 | this.cameraSwitcherElement = document.createElement("img");
|
113 | this.torchTogglerElement = document.createElement("img");
|
114 | this.laserContainerElement = document.createElement("div");
|
115 | this.laserActiveImageElement = document.createElement("img");
|
116 | this.laserPausedImageElement = document.createElement("img");
|
117 | this.viewfinderElement = document.createElement("div");
|
118 |
|
119 | if (options.singleImageMode) {
|
120 | this.cameraUploadElement = document.createElement("div");
|
121 | this.cameraUploadInputElement = document.createElement("input");
|
122 | this.cameraUploadLabelElement = document.createElement("label");
|
123 | this.cameraUploadProgressElement = document.createElement("div");
|
124 |
|
125 | this.setupCameraUploadGuiAssets();
|
126 |
|
127 | this.guiStyle = BarcodePicker.GuiStyle.NONE;
|
128 | } else {
|
129 | this.setupVideoElement();
|
130 | this.setupCameraSwitcher();
|
131 | this.setupTorchToggler();
|
132 | this.setupFullGuiAssets();
|
133 | this.setGuiStyle(options.guiStyle);
|
134 | this.setVideoFit(options.videoFit);
|
135 | this.setLaserArea(options.laserArea);
|
136 | this.setViewfinderArea(options.viewfinderArea);
|
137 |
|
138 | this.visibilityListener = this.checkAndRecoverPlayback.bind(this);
|
139 | document.addEventListener("visibilitychange", this.visibilityListener);
|
140 |
|
141 | this.newScanSettingsListener = this.handleNewScanSettings.bind(this);
|
142 | this.scanner.onNewScanSettings(this.newScanSettingsListener);
|
143 | this.handleNewScanSettings();
|
144 |
|
145 | this.videoResizeListener = this.handleVideoResize.bind(this);
|
146 | this.videoElement.addEventListener("resize", this.videoResizeListener);
|
147 | }
|
148 |
|
149 | if (options.hideLogo) {
|
150 | this.licenseFeaturesReadyListener = this.showScanditLogo.bind(this, options.hideLogo);
|
151 | this.scanner.onLicenseFeaturesReady(this.licenseFeaturesReadyListener);
|
152 | } else {
|
153 | this.showScanditLogo(false);
|
154 | }
|
155 |
|
156 | this.resize();
|
157 | this.resizeObserver = new ResizeObserver(() => {
|
158 | this.resize();
|
159 | });
|
160 | this.resizeObserver.observe(this.originElement);
|
161 |
|
162 | this.setVisible(options.visible);
|
163 | }
|
164 |
|
165 | public destroy(): void {
|
166 | if (this.visibilityListener != null) {
|
167 | document.removeEventListener("visibilitychange", this.visibilityListener);
|
168 | }
|
169 | if (this.newScanSettingsListener != null) {
|
170 | this.scanner.removeListener("newScanSettings", this.newScanSettingsListener);
|
171 | }
|
172 | if (this.videoResizeListener != null) {
|
173 | document.removeEventListener("resize", this.videoResizeListener);
|
174 | }
|
175 | if (this.licenseFeaturesReadyListener != null) {
|
176 | this.scanner.removeListener("licenseFeaturesReady", this.licenseFeaturesReadyListener);
|
177 | }
|
178 | this.resizeObserver.disconnect();
|
179 | this.grandParentElement.remove();
|
180 | this.originElement.classList.remove(BarcodePickerGui.hiddenClassName);
|
181 | }
|
182 |
|
183 | public setCameraManager(cameraManager: CameraManager): void {
|
184 | this.cameraManager = cameraManager;
|
185 | }
|
186 |
|
187 | public pauseScanning(): void {
|
188 | this.scanningPaused = true;
|
189 | this.laserActiveImageElement.classList.add(BarcodePickerGui.hiddenOpacityClassName);
|
190 | this.laserPausedImageElement.classList.remove(BarcodePickerGui.hiddenOpacityClassName);
|
191 | this.viewfinderElement.classList.add(BarcodePickerGui.pausedClassName);
|
192 | }
|
193 |
|
194 | public resumeScanning(): void {
|
195 | this.scanningPaused = false;
|
196 | this.laserPausedImageElement.classList.add(BarcodePickerGui.hiddenOpacityClassName);
|
197 | this.laserActiveImageElement.classList.remove(BarcodePickerGui.hiddenOpacityClassName);
|
198 | this.viewfinderElement.classList.remove(BarcodePickerGui.pausedClassName);
|
199 | }
|
200 |
|
201 | public isVisible(): boolean {
|
202 | return this.visible;
|
203 | }
|
204 |
|
205 | public setVisible(visible: boolean): void {
|
206 | const browserName: string | undefined = BrowserHelper.userAgentInfo.getBrowser().name;
|
207 | if (browserName != null && browserName.includes("Safari") && this.visible != null && !this.visible && visible) {
|
208 |
|
209 |
|
210 |
|
211 | this.videoElement.pause();
|
212 | this.videoElement.currentTime = 0;
|
213 | this.videoElement.load();
|
214 | this.playVideo();
|
215 | }
|
216 |
|
217 | this.visible = visible;
|
218 |
|
219 | if (visible) {
|
220 | this.originElement.classList.remove(BarcodePickerGui.hiddenClassName);
|
221 | if (this.guiStyle === BarcodePicker.GuiStyle.LASER) {
|
222 | this.laserActiveImageElement.classList.remove(BarcodePickerGui.flashColorClassName);
|
223 | } else if (this.guiStyle === BarcodePicker.GuiStyle.VIEWFINDER) {
|
224 | this.viewfinderElement.classList.remove(BarcodePickerGui.flashWhiteClassName);
|
225 | }
|
226 | } else {
|
227 | this.originElement.classList.add(BarcodePickerGui.hiddenClassName);
|
228 | }
|
229 | }
|
230 |
|
231 | public isMirrorImageEnabled(): boolean {
|
232 | if (
|
233 | this.cameraManager != null &&
|
234 | this.cameraManager.selectedCamera != null &&
|
235 | this.cameraManager.activeCamera != null
|
236 | ) {
|
237 | const mirrorImageOverride: boolean | undefined = this.mirrorImageOverrides.get(
|
238 | this.cameraManager.activeCamera.deviceId + this.cameraManager.activeCamera.label
|
239 | );
|
240 | if (mirrorImageOverride != null) {
|
241 | return mirrorImageOverride;
|
242 | } else {
|
243 | return this.cameraManager.activeCamera.cameraType === Camera.Type.FRONT;
|
244 | }
|
245 | } else {
|
246 | return false;
|
247 | }
|
248 | }
|
249 |
|
250 | public setMirrorImageEnabled(enabled: boolean, override: boolean): void {
|
251 | if (this.cameraManager != null && this.cameraManager.selectedCamera != null) {
|
252 | if (enabled) {
|
253 | this.videoElement.classList.add(BarcodePickerGui.mirroredClassName);
|
254 | } else {
|
255 | this.videoElement.classList.remove(BarcodePickerGui.mirroredClassName);
|
256 | }
|
257 |
|
258 | if (override) {
|
259 | this.mirrorImageOverrides.set(
|
260 | this.cameraManager.selectedCamera.deviceId + this.cameraManager.selectedCamera.label,
|
261 | enabled
|
262 | );
|
263 | }
|
264 | }
|
265 | }
|
266 |
|
267 | public setGuiStyle(guiStyle: BarcodePicker.GuiStyle): void {
|
268 | if (this.singleImageMode) {
|
269 | return;
|
270 | }
|
271 |
|
272 | switch (guiStyle) {
|
273 | case BarcodePicker.GuiStyle.LASER:
|
274 | this.guiStyle = guiStyle;
|
275 | this.laserContainerElement.classList.remove(BarcodePickerGui.hiddenClassName);
|
276 | this.viewfinderElement.classList.add(BarcodePickerGui.hiddenClassName);
|
277 | break;
|
278 | case BarcodePicker.GuiStyle.VIEWFINDER:
|
279 | this.guiStyle = guiStyle;
|
280 | this.laserContainerElement.classList.add(BarcodePickerGui.hiddenClassName);
|
281 | this.viewfinderElement.classList.remove(BarcodePickerGui.hiddenClassName);
|
282 | break;
|
283 | case BarcodePicker.GuiStyle.NONE:
|
284 | default:
|
285 | this.guiStyle = BarcodePicker.GuiStyle.NONE;
|
286 | this.laserContainerElement.classList.add(BarcodePickerGui.hiddenClassName);
|
287 | this.viewfinderElement.classList.add(BarcodePickerGui.hiddenClassName);
|
288 | break;
|
289 | }
|
290 | }
|
291 |
|
292 | public setLaserArea(area?: SearchArea): void {
|
293 | this.customLaserArea = area;
|
294 | if (area == null) {
|
295 | area = this.scanner.getScanSettings().getSearchArea();
|
296 | }
|
297 | const borderPercentage: number = 0.025;
|
298 | const usablePercentage: number = 1 - borderPercentage * 2;
|
299 | this.laserContainerElement.style.left = `${(borderPercentage + area.x * usablePercentage) * 100}%`;
|
300 | this.laserContainerElement.style.width = `${area.width * usablePercentage * 100}%`;
|
301 | this.laserContainerElement.style.top = `${(borderPercentage + area.y * usablePercentage) * 100}%`;
|
302 | this.laserContainerElement.style.height = `${area.height * usablePercentage * 100}%`;
|
303 | }
|
304 |
|
305 | public setViewfinderArea(area?: SearchArea): void {
|
306 | this.customViewfinderArea = area;
|
307 | if (area == null) {
|
308 | area = this.scanner.getScanSettings().getSearchArea();
|
309 | }
|
310 | const borderPercentage: number = 0.025;
|
311 | const usablePercentage: number = 1 - borderPercentage * 2;
|
312 | this.viewfinderElement.style.left = `${(borderPercentage + area.x * usablePercentage) * 100}%`;
|
313 | this.viewfinderElement.style.width = `${area.width * usablePercentage * 100}%`;
|
314 | this.viewfinderElement.style.top = `${(borderPercentage + area.y * usablePercentage) * 100}%`;
|
315 | this.viewfinderElement.style.height = `${area.height * usablePercentage * 100}%`;
|
316 | }
|
317 |
|
318 | public setVideoFit(objectFit: BarcodePicker.ObjectFit): void {
|
319 | if (this.singleImageMode) {
|
320 | return;
|
321 | }
|
322 |
|
323 | this.videoFit = objectFit;
|
324 |
|
325 | if (objectFit === BarcodePicker.ObjectFit.COVER) {
|
326 | this.videoElement.style.objectFit = "cover";
|
327 | this.videoElement.dataset.objectFit = "cover";
|
328 | } else {
|
329 | this.videoElement.style.objectFit = "contain";
|
330 | this.videoElement.dataset.objectFit = "contain";
|
331 |
|
332 | this.scanner.applyScanSettings(
|
333 | this.scanner.getScanSettings().setBaseSearchArea({ x: 0, y: 0, width: 1.0, height: 1.0 })
|
334 | );
|
335 | }
|
336 |
|
337 | this.resize();
|
338 | }
|
339 |
|
340 | public reassignOriginElement(originElement: HTMLElement): void {
|
341 | if (!this.visible) {
|
342 | this.originElement.classList.remove(BarcodePickerGui.hiddenClassName);
|
343 | originElement.classList.add(BarcodePickerGui.hiddenClassName);
|
344 | }
|
345 |
|
346 | originElement.appendChild(this.grandParentElement);
|
347 | this.checkAndRecoverPlayback();
|
348 | this.resize();
|
349 | this.resizeObserver.disconnect();
|
350 | this.resizeObserver.observe(originElement);
|
351 |
|
352 | this.originElement = originElement;
|
353 | }
|
354 |
|
355 | public flashGUI(): void {
|
356 | if (this.guiStyle === BarcodePicker.GuiStyle.LASER) {
|
357 | this.flashLaser();
|
358 | } else if (this.guiStyle === BarcodePicker.GuiStyle.VIEWFINDER) {
|
359 | this.flashViewfinder();
|
360 | }
|
361 | }
|
362 |
|
363 | public getVideoImageData(): Uint8ClampedArray | undefined {
|
364 | if (!this.singleImageMode) {
|
365 |
|
366 | if (
|
367 | this.videoElement.readyState !== 4 ||
|
368 | this.videoImageCanvasContext.canvas.width <= 2 ||
|
369 | this.videoImageCanvasContext.canvas.height <= 2
|
370 | ) {
|
371 | return undefined;
|
372 | }
|
373 |
|
374 | this.videoImageCanvasContext.drawImage(this.videoElement, 0, 0);
|
375 | }
|
376 |
|
377 | return this.videoImageCanvasContext.getImageData(
|
378 | 0,
|
379 | 0,
|
380 | this.videoImageCanvasContext.canvas.width,
|
381 | this.videoImageCanvasContext.canvas.height
|
382 | ).data;
|
383 | }
|
384 |
|
385 | public getVideoCurrentTime(): number {
|
386 | return this.videoElement.currentTime;
|
387 | }
|
388 |
|
389 | public setCameraSwitcherVisible(visible: boolean): void {
|
390 | if (visible) {
|
391 | this.cameraSwitcherElement.classList.remove(BarcodePickerGui.hiddenClassName);
|
392 | } else {
|
393 | this.cameraSwitcherElement.classList.add(BarcodePickerGui.hiddenClassName);
|
394 | }
|
395 | }
|
396 |
|
397 | public setTorchTogglerVisible(visible: boolean): void {
|
398 | if (visible) {
|
399 | this.torchTogglerElement.classList.remove(BarcodePickerGui.hiddenClassName);
|
400 | } else {
|
401 | this.torchTogglerElement.classList.add(BarcodePickerGui.hiddenClassName);
|
402 | }
|
403 | }
|
404 |
|
405 | public playVideo(): void {
|
406 | const playPromise: Promise<void> = this.videoElement.play();
|
407 | if (playPromise != null) {
|
408 | playPromise.catch(
|
409 | () => {
|
410 |
|
411 | }
|
412 | );
|
413 | }
|
414 | }
|
415 |
|
416 | private setCameraUploadGuiAvailable(available: boolean): void {
|
417 | if (available) {
|
418 | this.cameraUploadProgressElement.classList.add(BarcodePickerGui.flashWhiteInsetClassName);
|
419 | this.cameraUploadElement.classList.remove(BarcodePickerGui.opacityPulseClassName);
|
420 | } else {
|
421 | this.cameraUploadProgressElement.classList.remove(BarcodePickerGui.flashWhiteInsetClassName);
|
422 | this.cameraUploadElement.classList.add(BarcodePickerGui.opacityPulseClassName);
|
423 | }
|
424 | }
|
425 |
|
426 | private setupVideoElement(): void {
|
427 | this.videoElement.setAttribute("autoplay", "autoplay");
|
428 | this.videoElement.setAttribute("playsinline", "true");
|
429 | this.videoElement.setAttribute("muted", "muted");
|
430 | this.videoElement.className = BarcodePickerGui.videoElementClassName;
|
431 | this.parentElement.appendChild(this.videoElement);
|
432 | }
|
433 |
|
434 | private setupCameraUploadGuiAssets(): void {
|
435 | this.cameraUploadElement.className = BarcodePickerGui.cameraUploadElementClassName;
|
436 | this.parentElement.appendChild(this.cameraUploadElement);
|
437 | this.cameraUploadInputElement.type = "file";
|
438 | this.cameraUploadInputElement.accept = "image/*";
|
439 | this.cameraUploadInputElement.setAttribute("capture", "environment");
|
440 | this.cameraUploadInputElement.addEventListener("change", this.cameraUploadFile.bind(this));
|
441 | this.cameraUploadInputElement.addEventListener(
|
442 | "click",
|
443 | event => {
|
444 | if (this.scanningPaused || this.scanner.isBusyProcessing()) {
|
445 | event.preventDefault();
|
446 | }
|
447 | }
|
448 | );
|
449 | this.cameraUploadLabelElement.appendChild(this.cameraUploadInputElement);
|
450 | this.cameraUploadElement.appendChild(this.cameraUploadLabelElement);
|
451 | const cameraUploadImageElement: HTMLImageElement = document.createElement("img");
|
452 | cameraUploadImageElement.src = cameraImage;
|
453 | this.cameraUploadLabelElement.appendChild(cameraUploadImageElement);
|
454 | const cameraUploadTextElement: HTMLDivElement = document.createElement("div");
|
455 | cameraUploadTextElement.innerText = "Scan from Camera";
|
456 | this.cameraUploadLabelElement.appendChild(cameraUploadTextElement);
|
457 |
|
458 | this.cameraUploadProgressElement.classList.add("radial-progress");
|
459 | this.cameraUploadElement.appendChild(this.cameraUploadProgressElement);
|
460 | }
|
461 |
|
462 | private setupFullGuiAssets(): void {
|
463 | this.laserActiveImageElement.src = laserActiveImage;
|
464 | this.laserContainerElement.appendChild(this.laserActiveImageElement);
|
465 |
|
466 | this.laserPausedImageElement.src = laserPausedImage;
|
467 | this.laserContainerElement.appendChild(this.laserPausedImageElement);
|
468 |
|
469 | this.laserContainerElement.className = BarcodePickerGui.laserContainerElementClassName;
|
470 | this.parentElement.appendChild(this.laserContainerElement);
|
471 |
|
472 | this.viewfinderElement.className = BarcodePickerGui.viewfinderElementClassName;
|
473 | this.parentElement.appendChild(this.viewfinderElement);
|
474 |
|
475 |
|
476 | this.laserActiveImageElement.classList.add(BarcodePickerGui.hiddenOpacityClassName);
|
477 | this.laserPausedImageElement.classList.remove(BarcodePickerGui.hiddenOpacityClassName);
|
478 | this.viewfinderElement.classList.add(BarcodePickerGui.pausedClassName);
|
479 | }
|
480 |
|
481 | private flashLaser(): void {
|
482 | this.laserActiveImageElement.classList.remove(BarcodePickerGui.flashColorClassName);
|
483 |
|
484 | this.laserActiveImageElement.offsetHeight;
|
485 | this.laserActiveImageElement.classList.add(BarcodePickerGui.flashColorClassName);
|
486 | }
|
487 |
|
488 | private flashViewfinder(): void {
|
489 | this.viewfinderElement.classList.remove(BarcodePickerGui.flashWhiteClassName);
|
490 |
|
491 | this.viewfinderElement.offsetHeight;
|
492 | this.viewfinderElement.classList.add(BarcodePickerGui.flashWhiteClassName);
|
493 | }
|
494 |
|
495 | private resize(): void {
|
496 | if (this.singleImageMode) {
|
497 | this.resizeCameraUpload();
|
498 | } else {
|
499 | this.resizeVideo();
|
500 | }
|
501 | }
|
502 |
|
503 | private resizeCameraUpload(): void {
|
504 | const width: number = this.originElement.clientWidth;
|
505 | const height: number = this.originElement.clientHeight;
|
506 |
|
507 | this.cameraUploadLabelElement.style.transform = `scale(${Math.min(1, width / 500, height / 300)})`;
|
508 | this.cameraUploadProgressElement.style.transform = `scale(${Math.min(1, width / 500, height / 300)})`;
|
509 | }
|
510 |
|
511 | private resizeVideo(): void {
|
512 | if (this.videoElement.videoWidth <= 2 || this.videoElement.videoHeight <= 2) {
|
513 | return;
|
514 | }
|
515 |
|
516 | this.parentElement.style.maxWidth = null;
|
517 | this.parentElement.style.maxHeight = null;
|
518 |
|
519 | let width: number = this.originElement.clientWidth;
|
520 | let height: number = this.originElement.clientHeight;
|
521 |
|
522 | const videoRatio: number = this.videoElement.videoWidth / this.videoElement.videoHeight;
|
523 |
|
524 | if (this.videoFit === BarcodePicker.ObjectFit.COVER) {
|
525 | let widthPercentage: number = 1;
|
526 | let heightPercentage: number = 1;
|
527 |
|
528 | if (videoRatio < width / height) {
|
529 | heightPercentage = Math.min(1, height / (width / videoRatio));
|
530 | } else {
|
531 | widthPercentage = Math.min(1, width / (height * videoRatio));
|
532 | }
|
533 |
|
534 | this.scanner.applyScanSettings(
|
535 | this.scanner.getScanSettings().setBaseSearchArea({
|
536 | x: (1 - widthPercentage) / 2,
|
537 | y: (1 - heightPercentage) / 2,
|
538 | width: widthPercentage,
|
539 | height: heightPercentage
|
540 | })
|
541 | );
|
542 |
|
543 | return;
|
544 | }
|
545 |
|
546 | if (videoRatio > width / height) {
|
547 | height = width / videoRatio;
|
548 | } else {
|
549 | width = height * videoRatio;
|
550 | }
|
551 |
|
552 | this.parentElement.style.maxWidth = `${Math.ceil(width)}px`;
|
553 | this.parentElement.style.maxHeight = `${Math.ceil(height)}px`;
|
554 |
|
555 | window.objectFitPolyfill(this.videoElement);
|
556 | }
|
557 |
|
558 | private checkAndRecoverPlayback(): void {
|
559 | if (
|
560 | this.cameraManager != null &&
|
561 | this.cameraManager.activeCamera != null &&
|
562 | this.videoElement != null &&
|
563 | this.videoElement.srcObject != null
|
564 | ) {
|
565 | if (!(<MediaStream>this.videoElement.srcObject).active) {
|
566 | this.cameraManager.reinitializeCamera();
|
567 | } else {
|
568 | this.playVideo();
|
569 | }
|
570 | }
|
571 | }
|
572 |
|
573 | private updateCameraUploadProgress(progressPercentageValue: string): void {
|
574 | this.cameraUploadProgressElement.setAttribute("data-progress", progressPercentageValue);
|
575 | }
|
576 |
|
577 | private async cameraUploadImageLoad(image: HTMLImageElement): Promise<void> {
|
578 | this.updateCameraUploadProgress("100");
|
579 |
|
580 | let resizedImageWidth: number;
|
581 | let resizedImageHeight: number;
|
582 | const resizedImageSizeLimit: number = 1440;
|
583 | if (image.naturalWidth <= resizedImageSizeLimit && image.naturalHeight <= resizedImageSizeLimit) {
|
584 | resizedImageWidth = image.naturalWidth;
|
585 | resizedImageHeight = image.naturalHeight;
|
586 | } else {
|
587 | if (image.naturalWidth > image.naturalHeight) {
|
588 | resizedImageWidth = resizedImageSizeLimit;
|
589 | resizedImageHeight = Math.round((image.naturalHeight / image.naturalWidth) * resizedImageSizeLimit);
|
590 | } else {
|
591 | resizedImageWidth = Math.round((image.naturalWidth / image.naturalHeight) * resizedImageSizeLimit);
|
592 | resizedImageHeight = resizedImageSizeLimit;
|
593 | }
|
594 | }
|
595 |
|
596 | await this.cameraUploadFileProcess(image, resizedImageWidth, resizedImageHeight);
|
597 | }
|
598 |
|
599 | private async cameraUploadFileProcess(image: HTMLImageElement, width: number, height: number): Promise<void> {
|
600 | this.videoImageCanvasContext.canvas.width = width;
|
601 | this.videoImageCanvasContext.canvas.height = height;
|
602 |
|
603 | this.videoImageCanvasContext.drawImage(image, 0, 0, width, height);
|
604 | this.scanner.applyImageSettings({
|
605 | width,
|
606 | height,
|
607 | format: ImageSettings.Format.RGBA_8U
|
608 | });
|
609 |
|
610 | this.setCameraUploadGuiAvailable(false);
|
611 |
|
612 | await this.cameraUploadCallback();
|
613 | this.setCameraUploadGuiAvailable(true);
|
614 | }
|
615 |
|
616 | private cameraUploadFile(): void {
|
617 | const files: FileList | null = this.cameraUploadInputElement.files;
|
618 | if (files != null && files.length !== 0) {
|
619 | const image: HTMLImageElement = new Image();
|
620 | const fileReader: FileReader = new FileReader();
|
621 | fileReader.onload = () => {
|
622 | this.cameraUploadInputElement.value = "";
|
623 | if (fileReader.result != null) {
|
624 | image.onload = this.cameraUploadImageLoad.bind(this, image);
|
625 | image.onprogress = event2 => {
|
626 | if (event2.lengthComputable) {
|
627 | const progress: number = Math.round((event2.loaded / event2.total) * 20) * 5;
|
628 | if (progress <= 100) {
|
629 | this.updateCameraUploadProgress(progress.toString());
|
630 | }
|
631 | }
|
632 | };
|
633 | image.src = <string>fileReader.result;
|
634 | }
|
635 | };
|
636 | this.updateCameraUploadProgress("0");
|
637 | fileReader.readAsDataURL(files[0]);
|
638 | }
|
639 | }
|
640 |
|
641 | private setupCameraSwitcher(): void {
|
642 | this.cameraSwitcherElement.src = switchCameraImage;
|
643 | this.cameraSwitcherElement.className = BarcodePickerGui.cameraSwitcherElementClassName;
|
644 | this.cameraSwitcherElement.classList.add(BarcodePickerGui.hiddenClassName);
|
645 | this.parentElement.appendChild(this.cameraSwitcherElement);
|
646 | ["touchstart", "mousedown"].forEach(eventName => {
|
647 | this.cameraSwitcherElement.addEventListener(eventName, async event => {
|
648 | if (this.cameraManager != null) {
|
649 | const cameraManager: CameraManager = this.cameraManager;
|
650 | event.preventDefault();
|
651 | try {
|
652 | const cameras: Camera[] = await CameraAccess.getCameras();
|
653 | const newCameraIndex: number =
|
654 | (cameras.findIndex(camera => {
|
655 | return (
|
656 | camera.deviceId ===
|
657 | (cameraManager.activeCamera == null ? camera.deviceId : cameraManager.activeCamera.deviceId)
|
658 | );
|
659 | }) +
|
660 | 1) %
|
661 | cameras.length;
|
662 | cameraManager
|
663 | .initializeCameraWithSettings(cameras[newCameraIndex], cameraManager.activeCameraSettings)
|
664 | .catch(console.error);
|
665 | } catch (error) {
|
666 | console.error(error);
|
667 | }
|
668 | }
|
669 | });
|
670 | });
|
671 | }
|
672 |
|
673 | private setupTorchToggler(): void {
|
674 | this.torchTogglerElement.src = toggleTorchImage;
|
675 | this.torchTogglerElement.className = BarcodePickerGui.torchTogglerElementClassName;
|
676 | this.torchTogglerElement.classList.add(BarcodePickerGui.hiddenClassName);
|
677 | this.parentElement.appendChild(this.torchTogglerElement);
|
678 | ["touchstart", "mousedown"].forEach(eventName => {
|
679 | this.torchTogglerElement.addEventListener(eventName, event => {
|
680 | if (this.cameraManager != null) {
|
681 | event.preventDefault();
|
682 | this.cameraManager.toggleTorch();
|
683 | }
|
684 | });
|
685 | });
|
686 | }
|
687 |
|
688 | private showScanditLogo(hideLogo: boolean, licenseFeatures?: { hiddenScanditLogoAllowed?: boolean }): void {
|
689 | if (
|
690 | hideLogo &&
|
691 | licenseFeatures != null &&
|
692 | licenseFeatures.hiddenScanditLogoAllowed != null &&
|
693 | licenseFeatures.hiddenScanditLogoAllowed
|
694 | ) {
|
695 | return;
|
696 | }
|
697 |
|
698 | const scanditLogoImageElement: HTMLImageElement = document.createElement("img");
|
699 | scanditLogoImageElement.src = scanditLogoImage;
|
700 | scanditLogoImageElement.className = BarcodePickerGui.scanditLogoImageElementClassName;
|
701 | this.parentElement.appendChild(scanditLogoImageElement);
|
702 | }
|
703 |
|
704 | private handleNewScanSettings(): void {
|
705 | if (this.customLaserArea == null) {
|
706 | this.setLaserArea();
|
707 | }
|
708 | if (this.customViewfinderArea == null) {
|
709 | this.setViewfinderArea();
|
710 | }
|
711 | }
|
712 |
|
713 | private handleVideoResize(): void {
|
714 | if (
|
715 | this.videoImageCanvasContext.canvas.width === this.videoElement.videoWidth &&
|
716 | this.videoImageCanvasContext.canvas.height === this.videoElement.videoHeight
|
717 | ) {
|
718 | return;
|
719 | }
|
720 |
|
721 | this.videoImageCanvasContext.canvas.width = this.videoElement.videoWidth;
|
722 | this.videoImageCanvasContext.canvas.height = this.videoElement.videoHeight;
|
723 |
|
724 | this.scanner.applyImageSettings({
|
725 | width: this.videoElement.videoWidth,
|
726 | height: this.videoElement.videoHeight,
|
727 | format: ImageSettings.Format.RGBA_8U
|
728 | });
|
729 |
|
730 | this.resize();
|
731 | }
|
732 | }
|