From 94ef5a70bbc31cb8ec41333d1c8733686c9978c7 Mon Sep 17 00:00:00 2001 From: yamaha252 Date: Wed, 25 Nov 2020 15:08:24 +0500 Subject: [PATCH] Prevent double execution enumerateDevices method which breaks mobile chrome and safari Fix #95 --- src/app/modules/webcam/util/webcam.util.ts | 21 ++++---- .../modules/webcam/webcam/webcam.component.ts | 53 ++++++------------- 2 files changed, 27 insertions(+), 47 deletions(-) diff --git a/src/app/modules/webcam/util/webcam.util.ts b/src/app/modules/webcam/util/webcam.util.ts index 264b34c..d67c474 100644 --- a/src/app/modules/webcam/util/webcam.util.ts +++ b/src/app/modules/webcam/util/webcam.util.ts @@ -1,22 +1,21 @@ export class WebcamUtil { + private static availableVideoInputs: MediaDeviceInfo[] = []; + /** * Lists available videoInput devices * @returns a list of media device info. */ - public static getAvailableVideoInputs(): Promise { + public static async getAvailableVideoInputs(): Promise { if (!navigator.mediaDevices || !navigator.mediaDevices.enumerateDevices) { - return Promise.reject('enumerateDevices() not supported.'); + throw new Error('enumerateDevices() not supported.'); + } + + if (!WebcamUtil.availableVideoInputs.length) { + const devices = await navigator.mediaDevices.enumerateDevices(); + WebcamUtil.availableVideoInputs = devices.filter((device: MediaDeviceInfo) => device.kind === 'videoinput'); } - return new Promise((resolve, reject) => { - navigator.mediaDevices.enumerateDevices() - .then((devices: MediaDeviceInfo[]) => { - resolve(devices.filter((device: MediaDeviceInfo) => device.kind === 'videoinput')); - }) - .catch(err => { - reject(err.message || err); - }); - }); + return WebcamUtil.availableVideoInputs; } } diff --git a/src/app/modules/webcam/webcam/webcam.component.ts b/src/app/modules/webcam/webcam/webcam.component.ts index 2758d5f..aebde40 100644 --- a/src/app/modules/webcam/webcam/webcam.component.ts +++ b/src/app/modules/webcam/webcam/webcam.component.ts @@ -56,9 +56,9 @@ export class WebcamComponent implements AfterViewInit, OnDestroy { private switchCameraSubscription: Subscription; /** MediaStream object in use for streaming UserMedia data */ private mediaStream: MediaStream = null; - @ViewChild('video', { static: true }) private video: any; + @ViewChild('video', {static: true}) private video: any; /** Canvas for Video Snapshots */ - @ViewChild('canvas', { static: true }) private canvas: any; + @ViewChild('canvas', {static: true}) private canvas: any; /** width and height of the active video stream */ private activeVideoSettings: MediaTrackSettings = null; @@ -183,17 +183,13 @@ export class WebcamComponent implements AfterViewInit, OnDestroy { return null; } - public ngAfterViewInit(): void { - this.detectAvailableDevices() - .then(() => { - // start video - this.switchToVideoInput(null); - }) - .catch((err: string) => { - this.initError.next({message: err}); - // fallback: still try to load webcam, even if device enumeration failed - this.switchToVideoInput(null); - }); + public async ngAfterViewInit() { + try { + await this.detectAvailableDevices(); + } catch (e) { + this.initError.next({message: e.message}); + } + this.switchToVideoInput(null); } public ngOnDestroy(): void { @@ -317,7 +313,7 @@ export class WebcamComponent implements AfterViewInit, OnDestroy { const videoTrackConstraints = WebcamComponent.getMediaConstraintsForDevice(deviceId, userVideoTrackConstraints); navigator.mediaDevices.getUserMedia({video: videoTrackConstraints}) - .then((stream: MediaStream) => { + .then(async (stream: MediaStream) => { this.mediaStream = stream; _video.srcObject = stream; _video.play(); @@ -329,16 +325,11 @@ export class WebcamComponent implements AfterViewInit, OnDestroy { // Initial detect may run before user gave permissions, returning no deviceIds. This prevents later camera switches. (#47) // Run detect once again within getUserMedia callback, to make sure this time we have permissions and get deviceIds. - this.detectAvailableDevices() - .then(() => { - this.activeVideoInputIndex = activeDeviceId ? this.availableVideoInputs - .findIndex((mediaDeviceInfo: MediaDeviceInfo) => mediaDeviceInfo.deviceId === activeDeviceId) : -1; - this.videoInitialized = true; - }) - .catch(() => { - this.activeVideoInputIndex = -1; - this.videoInitialized = true; - }); + await this.detectAvailableDevices(); + + this.activeVideoInputIndex = activeDeviceId ? this.availableVideoInputs + .findIndex((mediaDeviceInfo: MediaDeviceInfo) => mediaDeviceInfo.deviceId === activeDeviceId) : -1; + this.videoInitialized = true; }) .catch((err: MediaStreamError) => { this.initError.next({message: err.message, mediaStreamError: err}); @@ -411,18 +402,8 @@ export class WebcamComponent implements AfterViewInit, OnDestroy { /** * Reads available input devices */ - private detectAvailableDevices(): Promise { - return new Promise((resolve, reject) => { - WebcamUtil.getAvailableVideoInputs() - .then((devices: MediaDeviceInfo[]) => { - this.availableVideoInputs = devices; - resolve(devices); - }) - .catch(err => { - this.availableVideoInputs = []; - reject(err); - }); - }); + private async detectAvailableDevices(): Promise { + this.availableVideoInputs = await WebcamUtil.getAvailableVideoInputs(); } }