Open sGerli opened 3 years ago
Happening on my end as well. There's a PR already in place, any chance to get a new build anytime soon?
I have the same problem on Android. Is there any plans for the fix?
"@zxing/browser": "0.0.9"
"@zxing/library": "^0.18.6"
"@zxing/ngx-scanner": "3.2.0"
Reload helps.
<zxing-scanner [enable]="enable" [autostart]="true" [timeBetweenScans]="2000" [delayBetweenScanSuccess]="2000" (scanSuccess)="onScanSuccess($event)" (permissionResponse)="onPermissionResponse($event)" (hasDevices)="onHasDevices($event)" [formats]="['QR_CODE']"> </zxing-scanner>
So I was hoping 3.2.0 would fix the issue which is obviously not the case... Thanks for the information, I'll see what I can do... PRs much appreciated!
On my app I opted to keep the scanner hot all the time (I just hide it and disable the scan results when not needed), that reduces the error probability to initial load. Although it's not a fix that workaround has worked ok in my use case.
Hi Guys,
I don't know if I have the same problem right away, but my app only works with version 3.0.0. In my app there is a drop-down list where you can choose a camera. After I installed version 3.2.0 and when I choose a camera, I get the following error message:
Is that really a bug?
Does anyone have the same problem?
Thanks in advance!
If it helps, I have been able to pinpoint this happens when the camera is what I am calling "loading." For me, the error has always meant that the camera device is in use.
Once the scanner gets an enabled=true, there is some delay before the video feed is actually visible. If the component is destroyed (navigation change, enabled=false, etc) during this period, I get what I am now calling a "ghost camera" because the video feed from the camera is still in use. Sadly, there is nothing for me to grab and tell it to stop, even though it is within my app (through the zxing scanner component).
What I have done as a workaround (I have an angular app) is I have created a service that has a scanner status. It tracks when the camera is starting (when passing true to enabled property) and when its actually started. I bind to the scanner's camerasFound event to know when the video feed is live. I then use this service in my app to disable buttons and actions that would cause the component to be destroyed while the camera is starting.
Not ideal, but it has allowed me to prevent my user base from getting the "ghost camera" event and having to refresh my app constantly. Maybe this information can also help in figuring out what is going on as it took a bit of trial and error to figure out how and where I was constantly getting this error.
I think setting [enable] to false on OnDestroy event fix this problem on Version 3.1.3
Could someone confirm this? I no longer have this problem.
Code example
template
<zxing-scanner [enable]=scannerEnable [device]="currentDevice" (camerasFound)="camerasFoundHandler($event)" (scanSuccess)=onScanSuccess($event)></zxing-scanner>
ts
ngOnDestroy(): void { this.scannerEnable = false; }
I think setting [enable] to false on OnDestroy event fix this problem on Version 3.1.3
This does not work for me (Samsung A12, Angular 11.2.14) on version
"@zxing/browser": "0.0.9",
"@zxing/library": "^0.18.3",
"@zxing/ngx-scanner": "^3.2.0",
When I switch between pages I am seeing
It was not possible to play the video. DOMException: The play() request was interrupted by a new load request.
Trying to play video that is already playing.
Unhandled Promise rejection: The associated Track is in an invalid state ; Zone: <root> ; Task: Promise.then ; Value: DOMException: The associated Track is in an invalid state undefined
@zxing/ngx-scanner Error when asking for permission. DOMException: Could not start video source
@zxing/ngx-scanner Couldn't read the device(s)'s stream, it's probably in use by another app.
@zxing/ngx-scanner Error when asking for permission. DOMException: Could not start video source
@zxing/ngx-scanner Couldn't read the device(s)'s stream, it's probably in use by another app.
Upgrading @zxing/library
to ^0.18.6
i.e
"@zxing/browser": "0.0.9",
"@zxing/library": "^0.18.6",
"@zxing/ngx-scanner": "^3.2.0",
resolve my issue. I can move between components without any errors.
I've tried all those workarounds and they've helped a bit but I still get many errors.
NotReadableError: Could not start video source
Uncaught (in promise): Error: No scanning is running at the time. Error: No scanning is running at the time.
Cannot read properties of undefined (reading 'stop')
What I did since this issue came to my attention is to keep the scanner component always open in the background, because if it gets closed the user has to reload the browser every time.
The issue is still occurring on my side. Seems like the camera is still being used. This only happen when I rapidly switch pages.
I checked the source a bit and I wonder could it be caused by async init() at ngOnInit() ?
Maybe the tracks didnt get stopped properly in this case.
Could someone enlighten us on this?
@siir could you please share code snippet of your workaround for the solution?
I wrote a wrapper component to hold the zxing scanner and related services. It makes it possible to hook into the zxing scanner consistently, and I do this for most npm packages I'm using so I can change out packages by changing this one component instead of having to change everything that consumes the 3rd party package directly.
I've tried to trim this down as much as possible to give the relevant parts. This will feel like a lot (perhaps it is) to just have a way to cancel navigation while the camera is between the starting up step (initial load/enable=true) and started step (camerasFound event fires), but its what worked for me. Feel free to simplify or improve and share back.
The key is using the scanner service in your child/individual components that can navigate the router to a new route while the camera is starting. The example here, some-other-component-consuming-scanner.component
has both the scanner and an a
tag, but you can have navigation points in other components depending on how specialized your app components get.
<zxing-scanner #zxing
*ngIf="scannerEnabled"
[autofocusEnabled]="true"
[autostart]="true"
[enable]="true"
(camerasFound)="camerasFound($event)"
(camerasNotFound)="camerasNotFound($event)"
(scanSuccess)="onscan($event)"
(scanError)="onerror($event,'e')"
></zxing-scanner>
import { Component, OnInit, Output, ViewChild, EventEmitter, Input, OnDestroy } from '@angular/core';
import { ZXingScannerComponent } from '@zxing/ngx-scanner';
import { Subject, BehaviorSubject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { ScannerService } from './scanner.service';
export enum ScannerStatus {
STARTING = 'STARTING',
STARTED = 'STARTED',
STOPPED = 'STOPPED',
ERROR = 'ERROR',
ERROR_INUSE = 'ERROR_INUSE'
}
@Component({
selector: 'app-scanner-camera',
templateUrl: './scanner-camera.component.html',
styleUrls: ['./scanner-camera.component.css']
})
export class ScannerCameraComponent implements OnInit, OnDestroy {
private componentDestroyed = new Subject<boolean>();
@Input() format: string | string[];
@Output() scanned: EventEmitter<string> = new EventEmitter();
@Output() scannerStatus: EventEmitter<ScannerStatus> = new EventEmitter();
// local scanner status subject for tracking what condition the camera is in
private _scannerStatusSubject = new BehaviorSubject<ScannerStatus>(undefined);
private get scannerStatus$() { return this._scannerStatusSubject.asObservable(); }
@ViewChild('zxing') zxing: ZXingScannerComponent;
scannerEnabled = false;
errorMessage: string;
private _scannerStartUpError: boolean;
get scannerStartUpError() { return this._scannerStartUpError; }
// used to know if there is a scanner camera starting somewhere else in the application
get scannerStarting(): boolean { return this.scannerService.scannerStarting; }
// to control (i.e. pause/play) the video feed of the camera
private get _video() { return this.zxing?.previewElemRef?.nativeElement; }
constructor(
private scannerService: ScannerService
) { }
ngOnInit() {
// subscribe to local scanner status to pass new status to service
this.scannerStatus$.pipe(takeUntil(this.componentDestroyed))
.subscribe(s => setTimeout(() => { this.scannerService.scannerStatus = s; }));
// hook into scanner actions from the service
this.scannerService.scannerActions$.pipe(takeUntil(this.componentDestroyed))
.subscribe(s => {
switch (s) {
case 'pause': this.pause(); break;
case 'play': this.resume(); break;
}
});
// Check for existence of a camera/ask permissions on first time then "start" camera
this.checkForCameras();
}
ngOnDestroy() {
this.scannerStatusEnded();
this.componentDestroyed.next(true);
this.componentDestroyed.unsubscribe();
}
// Check for existence of a camera/ask permissions on first time then "start" camera
private checkForCameras(): void {
if (!navigator.mediaDevices) {
this.scannerStatusError('No Cameras were found / MediaDevices not defined', {startUpError: true});
}
if (!navigator.mediaDevices.enumerateDevices) {
this.scannerStatusError('No Cameras were found / EnumerateDevices not defined', {startUpError: true});
}
navigator.mediaDevices.enumerateDevices().then(r => {
if (r.filter(d => d.kind === 'videoinput')) {
this.scannerStatusStarting(); // set status to starting
} else {
this.scannerStatusError('No Cameras were found', {startUpError: true});
}
}).catch(error => this.scannerStatusError(error, {startUpError: true}));
}
//#region - status setting functions
private scannerStatusStarting(): void {
this.scannerEnabled = true;
this._scannerStatusSubject.next(ScannerStatus.STARTING);
}
private scannerStatusStarted(): void {
this.scannerEnabled = true;
this._scannerStatusSubject.next(ScannerStatus.STARTED);
}
private scannerStatusStopped(): void {
this.scannerEnabled = false;
this._scannerStatusSubject.next(ScannerStatus.STOPPED);
}
private scannerStatusEnded(): void {
this._scannerStatusSubject.next(undefined);
}
private scannerStatusError(errorMessage, opts?: {inUse?: boolean, startUpError?: boolean}): void {
if (!!errorMessage) {
this.errorMessage = errorMessage;
this._scannerStartUpError = opts?.startUpError;
this._scannerStatusSubject.next(opts?.inUse ? ScannerStatus.ERROR_INUSE : ScannerStatus.ERROR);
} else {
this.errorMessage = undefined;
}
}
//#endregion
//#region - scanner event hooks
public onscan(scanResult: string): void {
this.scanned.emit(scanResult);
}
public onstart(): void {
this.scannerStatusStarted();
}
public onerror(e: Error, s): void {
if (!!e && e.name !== 'NotFoundException') {
this.scannerStatusError(e.message);
}
}
public camerasFound(c): void {
setTimeout(() => this.onstart(), 500);
}
public camerasNotFound($event) {
// this fires when the camera is in use by a previous scanner in "ghost" mode when its destroyed before starting
if ($event instanceof DOMException) {
const e: DOMException = $event;
let errorMessage = `${e.message}.`;
if (e.name === 'NotReadableError') {
errorMessage += ` Something else is likely using the camera or there is a ghost camera incident. You can try refreshing the app to reset this app's ghost camera usage.`;
}
this.scannerStatusError(errorMessage, {inUse: true, startUpError: true});
} else {
this.scannerStatusError($event, {startUpError: true});
}
}
//#endregion
//#region - scanner/camera controls
private pause(): void {
if (this.scannerStarting) {
setTimeout(() => this.pause(), 100);
return;
}
this._video?.pause();
}
private resume(): void {
this._video?.play();
}
public reset(): void {
this.scannerEnabled = false;
this.errorMessage = '';
this.scannerStatusStarting();
// start video if its in dom but still paused
const video = this._video;
if (video && !(video.currentTime > 0 && !video.paused && !video.ended && video.readyState > 2)) {
video.play();
}
}
//#endregion
}
import { Injectable } from '@angular/core';
import { BehaviorSubject, Observable } from 'rxjs';
import { ScannerStatus } from './scanner-status.enum';
export type ScannerActions = 'pause' | 'play';
@Injectable({
providedIn: 'root'
})
export class ScannerService {
//#region - Scanner Status
private _scannerStatusSubject = new BehaviorSubject<ScannerStatus>(undefined);
get scannerStatus$(): Observable<ScannerStatus> {
return this._scannerStatusSubject.asObservable();
}
set scannerStatus(val: ScannerStatus) {
this._scannerStatusSubject.next(val);
}
get scannerStatus(): ScannerStatus {
return this._scannerStatusSubject.value;
}
get scannerStarting(): boolean {
return this.scannerStatus === ScannerStatus.STARTING;
}
get scannerExists(): boolean {
return !(this.scannerStatus === undefined);
}
//#endregion
private _scannerActions = new BehaviorSubject<ScannerActions>(undefined);
get scannerActions$() { return this._scannerActions.asObservable(); }
constructor() { }
pause(): void {
this._scannerActions.next('pause');
}
play(): void {
this._scannerActions.next('play');
}
destroyed(): void {
this._scannerActions.next(undefined);
this._scannerStatusSubject.next(undefined);
}
}
<app-scanner (scanned)="scanned($event)"></app-scanner>
<a mat-fab color="primary" class="floating-mat-fab" [disabled]="scannerStarting" (click)="scannerStarting ? undefined : stopComplete()">
<mat-icon>done</mat-icon>
</a>
@Component({
selector: 'some-other-component-consuming-scanner',
templateUrl: './some-other-component-consuming-scanner.component.html',
styleUrls: ['./some-other-component-consuming-scanner.component.css']
})
export class SomeOtherComponentConsumingScanner implements OnInit, OnDestroy {
private componentDestroyed: Subject<boolean> = new Subject<boolean>();
private _scannerStarting: boolean;
get scannerStarting(): boolean { return this._scannerStarting; }
get showScanner(): boolean { return !!this.validRacks; }
constructor(
private scannerService: ScannerService,
) { }
ngOnInit(): void {
this.scannerService.scannerStatus$.pipe(takeUntil(this.componentDestroyed))
.subscribe(s => this._scannerStarting = this.scannerService.scannerStarting);
}
ngOnDestroy() {
this.componentDestroyed.next(true);
this.componentDestroyed.unsubscribe();
}
scanned(result) {
// do something with result
}
navigate() {
if (this.scannerStarting) { return; }
// navigate if not starting
}
}
I would like to add that after upgrade to ngx-scanner 3.2 and ng 11 (from 3.0 and 10.2 respectively), i am having issues where the camera feed not starting. There appears to be a promise rejection error inside ngx-scanner when navigating away from a component with the scanner in it. Something about the track is in an invalid state. More soon.
@siir Thank you for the detailed explanation.
I had one more query with respect to scanning barcodes. Have you tried scanning data matrix codes? I am trying the same and unable to scan it.
If I take a pic of the data matrix code, zoom-in and scan then the scanner is able to scan successfully.
Attached data matrix code I am trying to scan.
That looks pretty small. I would wager to guess that its too small for the camera to focus in properly and the barcode library is not detecting anything to scan. Can you try a larger version just to make sure?
I test with this site: Tec-it Data Matrix Generator
@siir I generated the same data matrix code with Tec-it Data Matrix Generator and I am able to scan it.
However, my application needs to scan data matrix of the size I shared. Is there any way this could be achieved?
I think that we're going out of topic with the last comments on this issue
@sGerli Yes. I will create/discuss it in a separate relevant issue.
Describe the bug After successfully scanning, navigating away and then back to scanner page it fails to open on some devices. It throws the error
NotReadableError NotReadableError: Could not start video source
.And then
The only way to make it work again is to reload the page through the browser.
Expected behavior Scanner should restart successfully after reopening the scanner page and not require a refresh.
Smartphone (please complete the following information):
Additional context Angular: 11.2.11 @zxing/browser: 0.0.7 @zxing/library: ^0.18.3 @zxing/ngx-scanner: ^3.1.3
As far as I know this didn't happen a few versions ago with Angular 10