diff --git a/README.md b/README.md index 30dcb30..41dd852 100644 --- a/README.md +++ b/README.md @@ -9,38 +9,43 @@ A simple Angular webcam component. Pure & minimal, no Flash-fallback. Live-Demo or see the Demo-Project. ## Features -* Webcam live view -* Photo capturing -* Smartphone compatibility for modern OS's (OS must support [WebRTC/UserMedia API](https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices)) -* Access to front- and back-camera, if multiple cameras exist -* Portrait & Landscape mode on smartphones -* Mirrored live-view for user-facing cameras - "selfie view" on phones -* Capturing of lossless pixel image data for better post-processing. +- Webcam live view +- Photo capturing +- Smartphone compatibility for modern OS's (OS must support [WebRTC/UserMedia API](https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices)) +- Access to front- and back-camera, if multiple cameras exist +- Portrait & Landscape mode on smartphones +- Mirrored live-view for user-facing cameras - "selfie view" on phones +- Capturing of lossless pixel image data for better post-processing. ## Prerequisites ### Runtime Dependencies + **Note:** Starting from version `0.3.0` this project requires TypeScript `>= 3.7.0` (Angular 9). For older versions of Angular/TypeScript, please use version `0.2.6` of this library. -* Angular: `>=9.0.0` -* Typescript: `>=3.7.0` -* RxJs: `>=5.0.0` -* **Important:** Your app must be served on a secure context using `https://` or on localhost, for modern browsers to permit WebRTC/UserMedia access. + +- Angular: `>=9.0.0` +- Typescript: `>=3.7.0` +- RxJs: `>=5.0.0` +- **Important:** Your app must be served on a secure context using `https://` or on localhost, for modern browsers to permit WebRTC/UserMedia access. ### Client -* [Current browser](https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia#Browser_compatibility) w/ HTML5 and WebRTC/UserMedia support (Chrome >53, Safari >11, Firefox >38, Edge) -* Webcam / camera -* User permissions to access the camera + +- [Current browser](https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia#Browser_compatibility) w/ HTML5 and WebRTC/UserMedia support (Chrome >53, Safari >11, Firefox >38, Edge) +- Webcam / camera +- User permissions to access the camera ## Usage -1) Install the library via standard npm command: + +1. Install the library via standard npm command: `npm install --save ngx-webcam` -2) Import the `WebcamModule` into your Angular module: +2. Import the `WebcamModule` into your Angular module: ```typescript import {WebcamModule} from 'ngx-webcam'; @@ -55,43 +60,53 @@ import {WebcamModule} from 'ngx-webcam'; export class AppModule { } ``` -3) Use the `WebcamComponent` on your pages: +3. Use the `WebcamComponent` on your pages: `` -As simple as that. +As simple as that. For more examples, see the code in the Demo-Project. ## Options and Events + This section describes the basic inputs/outputs of the component. All inputs are optional. + ### Inputs -* `trigger: Observable`: An `Observable` to trigger image capturing. When it fires, an image will be captured and emitted (see Outputs). -* `width: number`: The maximal video width of the webcam live view. -* `height: number`: The maximal video height of the webcam live view. The actual view will be placed within these boundaries, respecting the aspect ratio of the video stream. -* `videoOptions: MediaTrackConstraints`: Defines constraints ([MediaTrackConstraints](https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackConstraints)) to apply when requesting the video track. -* `mirrorImage: string | WebcamMirrorProperties`: Flag to control image mirroring. If the attribute is missing or `null` and the camera claims to be user-facing, the image will be mirrored (x-axis) to provide a better user experience ("selfie view"). A string value of `"never"` will prevent mirroring, whereas a value of `"always"` will mirror every camera stream, even if the camera cannot be detected as user-facing. For future extensions, the `WebcamMirrorProperties` object can also be used to set these values. -* `allowCameraSwitch: boolean`: Flag to enable/disable camera switch. If enabled, a switch icon will be displayed if multiple cameras are found. -* `switchCamera: Observable`: Can be used to cycle through available cameras (true=forward, false=backwards), or to switch to a specific device by deviceId (string). -* `captureImageData: boolean = false`: Flag to enable/disable capturing of a lossless pixel ImageData object when a snapshot is taken. ImageData will be included in the emitted `WebcamImage` object. -* `imageType: string = 'image/jpeg'`: [Image type](https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toDataURL) to use when capturing snapshots. Default is 'image/jpeg'. -* `imageQuality: number = 0.92`: [Image quality](https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toDataURL) to use when capturing snapshots. Must be a number between 0..1. Default is 0.92. + +- `trigger: Observable`: An `Observable` to trigger image capturing. When it fires, an image will be captured and emitted (see Outputs). +- `width: number`: The maximal video width of the webcam live view. +- `height: number`: The maximal video height of the webcam live view. The actual view will be placed within these boundaries, respecting the aspect ratio of the video stream. +- `videoOptions: MediaTrackConstraints`: Defines constraints ([MediaTrackConstraints](https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackConstraints)) to apply when requesting the video track. +- `mirrorImage: string | WebcamMirrorProperties`: Flag to control image mirroring. If the attribute is missing or `null` and the camera claims to be user-facing, the image will be mirrored (x-axis) to provide a better user experience ("selfie view"). A string value of `"never"` will prevent mirroring, whereas a value of `"always"` will mirror every camera stream, even if the camera cannot be detected as user-facing. For future extensions, the `WebcamMirrorProperties` object can also be used to set these values. +- `allowCameraSwitch: boolean`: Flag to enable/disable camera switch. If enabled, a switch icon will be displayed if multiple cameras are found. +- `switchCamera: Observable`: Can be used to cycle through available cameras (true=forward, false=backwards), or to switch to a specific device by deviceId (string). +- `captureImageData: boolean = false`: Flag to enable/disable capturing of a lossless pixel ImageData object when a snapshot is taken. ImageData will be included in the emitted `WebcamImage` object. +- `imageType: string = 'image/jpeg'`: [Image type](https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toDataURL) to use when capturing snapshots. Default is 'image/jpeg'. +- `imageQuality: number = 0.92`: [Image quality](https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toDataURL) to use when capturing snapshots. Must be a number between 0..1. Default is 0.92. ### Outputs -* `imageCapture: EventEmitter`: Whenever an image is captured (i.e. triggered by `[trigger]`), the image is emitted via this `EventEmitter`. The image data is contained in the `WebcamImage` data structure as both, plain Base64 string and data-url. -* `imageClick: EventEmitter`: An `EventEmitter` to signal clicks on the webcam area. -* `initError: EventEmitter`: An `EventEmitter` to signal errors during the webcam initialization. -* `cameraSwitched: EventEmitter`: Emits the active deviceId after the active video device has been switched. + +- `imageCapture: EventEmitter`: Whenever an image is captured (i.e. triggered by `[trigger]`), the image is emitted via this `EventEmitter`. The image data is contained in the `WebcamImage` data structure as both, plain Base64 string and data-url. +- `imageClick: EventEmitter`: An `EventEmitter` to signal clicks on the webcam area. +- `initError: EventEmitter`: An `EventEmitter` to signal errors during the webcam initialization. +- `initSuccess: EventEmitter`: Emits when the user accepts permissions. `true` if there is a camera in the system. +- `cameraSwitched: EventEmitter`: Emits the active deviceId after the active video device has been switched. ## Good To Know + ### How to determine if a user has denied camera access + When camera initialization fails for some reason, the component emits a `WebcamInitError` via the `initError` EventEmitter. If provided by the browser, this object contains a field `mediaStreamError: MediaStreamError` which contains information about why UserMedia initialization failed. According to [Mozilla API docs](https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia), this object contains a `name` attribute which gives insight about the reason. + > If the user denies permission, or matching media is not available, then the promise is rejected with NotAllowedError or NotFoundError respectively. Determine if a user has denied permissions: + ``` ``` + ``` public handleInitError(error: WebcamInitError): void { if (error.mediaStreamError && error.mediaStreamError.name === "NotAllowedError") { @@ -101,16 +116,21 @@ Determine if a user has denied permissions: ``` ## Development + Here you can find instructions on how to start developing this library. ### Build + Run `npm run packagr` to build the library. The build artifacts will be stored in the `dist/` directory. ### Start + Run `npm start` to build and run the surrounding demo app with the `WebcamModule`. Essential for live-developing. ### Generate docs/ + Run `npm run docs` to generate the live-demo documentation pages in the `docs/` directory. ### Running Unit Tests + Run `npm run test` to run unit-tests. diff --git a/src/app/modules/webcam/webcam/webcam.component.ts b/src/app/modules/webcam/webcam/webcam.component.ts index 2758d5f..fd91ee6 100644 --- a/src/app/modules/webcam/webcam/webcam.component.ts +++ b/src/app/modules/webcam/webcam/webcam.component.ts @@ -1,18 +1,28 @@ -import {AfterViewInit, Component, EventEmitter, Input, OnDestroy, Output, ViewChild} from '@angular/core'; -import {WebcamInitError} from '../domain/webcam-init-error'; -import {WebcamImage} from '../domain/webcam-image'; -import {Observable, Subscription} from 'rxjs'; -import {WebcamUtil} from '../util/webcam.util'; -import {WebcamMirrorProperties} from '../domain/webcam-mirror-properties'; +import { + AfterViewInit, + Component, + EventEmitter, + Input, + OnDestroy, + Output, + ViewChild, +} from "@angular/core"; +import { WebcamInitError } from "../domain/webcam-init-error"; +import { WebcamImage } from "../domain/webcam-image"; +import { Observable, Subscription } from "rxjs"; +import { WebcamUtil } from "../util/webcam.util"; +import { WebcamMirrorProperties } from "../domain/webcam-mirror-properties"; @Component({ - selector: 'webcam', - templateUrl: './webcam.component.html', - styleUrls: ['./webcam.component.scss'] + selector: "webcam", + templateUrl: "./webcam.component.html", + styleUrls: ["./webcam.component.scss"], }) export class WebcamComponent implements AfterViewInit, OnDestroy { - private static DEFAULT_VIDEO_OPTIONS: MediaTrackConstraints = {facingMode: 'environment'}; - private static DEFAULT_IMAGE_TYPE: string = 'image/jpeg'; + private static DEFAULT_VIDEO_OPTIONS: MediaTrackConstraints = { + facingMode: "environment", + }; + private static DEFAULT_IMAGE_TYPE: string = "image/jpeg"; private static DEFAULT_IMAGE_QUALITY: number = 0.92; /** Defines the max width of the webcam area in px */ @@ -20,7 +30,8 @@ export class WebcamComponent implements AfterViewInit, OnDestroy { /** Defines the max height of the webcam area in px */ @Input() public height: number = 480; /** Defines base constraints to apply when requesting video track from UserMedia */ - @Input() public videoOptions: MediaTrackConstraints = WebcamComponent.DEFAULT_VIDEO_OPTIONS; + @Input() public videoOptions: MediaTrackConstraints = + WebcamComponent.DEFAULT_VIDEO_OPTIONS; /** Flag to enable/disable camera switch. If enabled, a switch icon will be displayed if multiple cameras were found */ @Input() public allowCameraSwitch: boolean = true; /** Parameter to control image mirroring (i.e. for user-facing camera). ["auto", "always", "never"] */ @@ -33,13 +44,23 @@ export class WebcamComponent implements AfterViewInit, OnDestroy { @Input() public imageQuality: number = WebcamComponent.DEFAULT_IMAGE_QUALITY; /** EventEmitter which fires when an image has been captured */ - @Output() public imageCapture: EventEmitter = new EventEmitter(); + @Output() public imageCapture: EventEmitter = new EventEmitter< + WebcamImage + >(); /** Emits a mediaError if webcam cannot be initialized (e.g. missing user permissions) */ - @Output() public initError: EventEmitter = new EventEmitter(); + @Output() public initError: EventEmitter = new EventEmitter< + WebcamInitError + >(); + /** Emits when the user accepts permissions */ + @Output() public initSuccess: EventEmitter = new EventEmitter< + Boolean + >(); /** Emits when the webcam video was clicked */ @Output() public imageClick: EventEmitter = new EventEmitter(); /** Emits the active deviceId after the active video device was switched */ - @Output() public cameraSwitched: EventEmitter = new EventEmitter(); + @Output() public cameraSwitched: EventEmitter = new EventEmitter< + string + >(); /** available video devices */ public availableVideoInputs: MediaDeviceInfo[] = []; @@ -56,9 +77,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; @@ -92,15 +113,17 @@ export class WebcamComponent implements AfterViewInit, OnDestroy { } // Subscribe to events from this Observable to switch video device - this.switchCameraSubscription = switchCamera.subscribe((value: boolean | string) => { - if (typeof value === 'string') { - // deviceId was specified - this.switchToVideoInput(value); - } else { - // direction was specified - this.rotateVideoInput(value !== false); + this.switchCameraSubscription = switchCamera.subscribe( + (value: boolean | string) => { + if (typeof value === "string") { + // deviceId was specified + this.switchToVideoInput(value); + } else { + // direction was specified + this.rotateVideoInput(value !== false); + } } - }); + ); } /** @@ -109,10 +132,15 @@ export class WebcamComponent implements AfterViewInit, OnDestroy { * @param baseMediaTrackConstraints base constraints to merge deviceId-constraint into * @returns */ - private static getMediaConstraintsForDevice(deviceId: string, baseMediaTrackConstraints: MediaTrackConstraints): MediaTrackConstraints { - const result: MediaTrackConstraints = baseMediaTrackConstraints ? baseMediaTrackConstraints : this.DEFAULT_VIDEO_OPTIONS; + private static getMediaConstraintsForDevice( + deviceId: string, + baseMediaTrackConstraints: MediaTrackConstraints + ): MediaTrackConstraints { + const result: MediaTrackConstraints = baseMediaTrackConstraints + ? baseMediaTrackConstraints + : this.DEFAULT_VIDEO_OPTIONS; if (deviceId) { - result.deviceId = {exact: deviceId}; + result.deviceId = { exact: deviceId }; } return result; @@ -125,11 +153,22 @@ export class WebcamComponent implements AfterViewInit, OnDestroy { * @param mediaStreamTrack * @returns deviceId if found in the mediaStreamTrack */ - private static getDeviceIdFromMediaStreamTrack(mediaStreamTrack: MediaStreamTrack): string { - if (mediaStreamTrack.getSettings && mediaStreamTrack.getSettings() && mediaStreamTrack.getSettings().deviceId) { + private static getDeviceIdFromMediaStreamTrack( + mediaStreamTrack: MediaStreamTrack + ): string { + if ( + mediaStreamTrack.getSettings && + mediaStreamTrack.getSettings() && + mediaStreamTrack.getSettings().deviceId + ) { return mediaStreamTrack.getSettings().deviceId; - } else if (mediaStreamTrack.getConstraints && mediaStreamTrack.getConstraints() && mediaStreamTrack.getConstraints().deviceId) { - const deviceIdObj: ConstrainDOMString = mediaStreamTrack.getConstraints().deviceId; + } else if ( + mediaStreamTrack.getConstraints && + mediaStreamTrack.getConstraints() && + mediaStreamTrack.getConstraints().deviceId + ) { + const deviceIdObj: ConstrainDOMString = mediaStreamTrack.getConstraints() + .deviceId; return WebcamComponent.getValueFromConstrainDOMString(deviceIdObj); } } @@ -141,13 +180,26 @@ export class WebcamComponent implements AfterViewInit, OnDestroy { * @param mediaStreamTrack * @returns facingMode if found in the mediaStreamTrack */ - private static getFacingModeFromMediaStreamTrack(mediaStreamTrack: MediaStreamTrack): string { + private static getFacingModeFromMediaStreamTrack( + mediaStreamTrack: MediaStreamTrack + ): string { if (mediaStreamTrack) { - if (mediaStreamTrack.getSettings && mediaStreamTrack.getSettings() && mediaStreamTrack.getSettings().facingMode) { + if ( + mediaStreamTrack.getSettings && + mediaStreamTrack.getSettings() && + mediaStreamTrack.getSettings().facingMode + ) { return mediaStreamTrack.getSettings().facingMode; - } else if (mediaStreamTrack.getConstraints && mediaStreamTrack.getConstraints() && mediaStreamTrack.getConstraints().facingMode) { - const facingModeConstraint: ConstrainDOMString = mediaStreamTrack.getConstraints().facingMode; - return WebcamComponent.getValueFromConstrainDOMString(facingModeConstraint); + } else if ( + mediaStreamTrack.getConstraints && + mediaStreamTrack.getConstraints() && + mediaStreamTrack.getConstraints().facingMode + ) { + const facingModeConstraint: ConstrainDOMString = mediaStreamTrack.getConstraints() + .facingMode; + return WebcamComponent.getValueFromConstrainDOMString( + facingModeConstraint + ); } } } @@ -157,25 +209,32 @@ export class WebcamComponent implements AfterViewInit, OnDestroy { * @param mediaStreamTrack */ private static isUserFacing(mediaStreamTrack: MediaStreamTrack): boolean { - const facingMode: string = WebcamComponent.getFacingModeFromMediaStreamTrack(mediaStreamTrack); - return facingMode ? 'user' === facingMode.toLowerCase() : false; + const facingMode: string = WebcamComponent.getFacingModeFromMediaStreamTrack( + mediaStreamTrack + ); + return facingMode ? "user" === facingMode.toLowerCase() : false; } /** * Extracts the value from the given ConstrainDOMString * @param constrainDOMString */ - private static getValueFromConstrainDOMString(constrainDOMString: ConstrainDOMString): string { + private static getValueFromConstrainDOMString( + constrainDOMString: ConstrainDOMString + ): string { if (constrainDOMString) { if (constrainDOMString instanceof String) { return String(constrainDOMString); - } else if (Array.isArray(constrainDOMString) && Array(constrainDOMString).length > 0) { + } else if ( + Array.isArray(constrainDOMString) && + Array(constrainDOMString).length > 0 + ) { return String(constrainDOMString[0]); - } else if (typeof constrainDOMString === 'object') { - if (constrainDOMString['exact']) { - return String(constrainDOMString['exact']); - } else if (constrainDOMString['ideal']) { - return String(constrainDOMString['ideal']); + } else if (typeof constrainDOMString === "object") { + if (constrainDOMString["exact"]) { + return String(constrainDOMString["exact"]); + } else if (constrainDOMString["ideal"]) { + return String(constrainDOMString["ideal"]); } } } @@ -190,7 +249,7 @@ export class WebcamComponent implements AfterViewInit, OnDestroy { this.switchToVideoInput(null); }) .catch((err: string) => { - this.initError.next({message: err}); + this.initError.next({ message: err }); // fallback: still try to load webcam, even if device enumeration failed this.switchToVideoInput(null); }); @@ -207,7 +266,7 @@ export class WebcamComponent implements AfterViewInit, OnDestroy { public takeSnapshot(): void { // set canvas size to actual video size const _video = this.nativeVideoElement; - const dimensions = {width: this.width, height: this.height}; + const dimensions = { width: this.width, height: this.height }; if (_video.videoWidth) { dimensions.width = _video.videoWidth; dimensions.height = _video.videoHeight; @@ -218,12 +277,16 @@ export class WebcamComponent implements AfterViewInit, OnDestroy { _canvas.height = dimensions.height; // paint snapshot image to canvas - const context2d = _canvas.getContext('2d'); + const context2d = _canvas.getContext("2d"); context2d.drawImage(_video, 0, 0); // read canvas content as image - const mimeType: string = this.imageType ? this.imageType : WebcamComponent.DEFAULT_IMAGE_TYPE; - const quality: number = this.imageQuality ? this.imageQuality : WebcamComponent.DEFAULT_IMAGE_QUALITY; + const mimeType: string = this.imageType + ? this.imageType + : WebcamComponent.DEFAULT_IMAGE_TYPE; + const quality: number = this.imageQuality + ? this.imageQuality + : WebcamComponent.DEFAULT_IMAGE_QUALITY; const dataUrl: string = _canvas.toDataURL(mimeType, quality); // get the ImageData object from the canvas' context. @@ -242,9 +305,15 @@ export class WebcamComponent implements AfterViewInit, OnDestroy { */ public rotateVideoInput(forward: boolean) { if (this.availableVideoInputs && this.availableVideoInputs.length > 1) { - const increment: number = forward ? 1 : (this.availableVideoInputs.length - 1); - const nextInputIndex = (this.activeVideoInputIndex + increment) % this.availableVideoInputs.length; - this.switchToVideoInput(this.availableVideoInputs[nextInputIndex].deviceId); + const increment: number = forward + ? 1 + : this.availableVideoInputs.length - 1; + const nextInputIndex = + (this.activeVideoInputIndex + increment) % + this.availableVideoInputs.length; + this.switchToVideoInput( + this.availableVideoInputs[nextInputIndex].deviceId + ); } } @@ -257,7 +326,6 @@ export class WebcamComponent implements AfterViewInit, OnDestroy { this.initWebcam(deviceId, this.videoOptions); } - /** * Event-handler for video resize event. * Triggers Angular change detection so that new video dimensions get applied @@ -277,10 +345,10 @@ export class WebcamComponent implements AfterViewInit, OnDestroy { } public get videoStyleClasses() { - let classes: string = ''; + let classes: string = ""; if (this.isMirrorImage()) { - classes += 'mirrored '; + classes += "mirrored "; } return classes.trim(); @@ -296,9 +364,12 @@ export class WebcamComponent implements AfterViewInit, OnDestroy { private getVideoAspectRatio(): number { // calculate ratio from video element dimensions if present const videoElement = this.nativeVideoElement; - if (videoElement.videoWidth && videoElement.videoWidth > 0 && - videoElement.videoHeight && videoElement.videoHeight > 0) { - + if ( + videoElement.videoWidth && + videoElement.videoWidth > 0 && + videoElement.videoHeight && + videoElement.videoHeight > 0 + ) { return videoElement.videoWidth / videoElement.videoHeight; } @@ -309,21 +380,29 @@ export class WebcamComponent implements AfterViewInit, OnDestroy { /** * Init webcam live view */ - private initWebcam(deviceId: string, userVideoTrackConstraints: MediaTrackConstraints) { + private initWebcam( + deviceId: string, + userVideoTrackConstraints: MediaTrackConstraints + ) { const _video = this.nativeVideoElement; if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) { - // merge deviceId -> userVideoTrackConstraints - const videoTrackConstraints = WebcamComponent.getMediaConstraintsForDevice(deviceId, userVideoTrackConstraints); + const videoTrackConstraints = WebcamComponent.getMediaConstraintsForDevice( + deviceId, + userVideoTrackConstraints + ); - navigator.mediaDevices.getUserMedia({video: videoTrackConstraints}) + navigator.mediaDevices + .getUserMedia({ video: videoTrackConstraints }) .then((stream: MediaStream) => { this.mediaStream = stream; _video.srcObject = stream; _video.play(); this.activeVideoSettings = stream.getVideoTracks()[0].getSettings(); - const activeDeviceId: string = WebcamComponent.getDeviceIdFromMediaStreamTrack(stream.getVideoTracks()[0]); + const activeDeviceId: string = WebcamComponent.getDeviceIdFromMediaStreamTrack( + stream.getVideoTracks()[0] + ); this.cameraSwitched.next(activeDeviceId); @@ -331,20 +410,31 @@ export class WebcamComponent implements AfterViewInit, OnDestroy { // 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.activeVideoInputIndex = activeDeviceId + ? this.availableVideoInputs.findIndex( + (mediaDeviceInfo: MediaDeviceInfo) => + mediaDeviceInfo.deviceId === activeDeviceId + ) + : -1; this.videoInitialized = true; + this.initSuccess.next(true); }) .catch(() => { this.activeVideoInputIndex = -1; this.videoInitialized = true; + this.initSuccess.next(false); }); }) .catch((err: MediaStreamError) => { - this.initError.next({message: err.message, mediaStreamError: err}); + this.initError.next({ + message: err.message, + mediaStreamError: err, + }); }); } else { - this.initError.next({message: 'Cannot read UserMedia from MediaDevices.'}); + this.initError.next({ + message: "Cannot read UserMedia from MediaDevices.", + }); } } @@ -359,9 +449,9 @@ export class WebcamComponent implements AfterViewInit, OnDestroy { // check for explicit mirror override parameter { - let mirror: string = 'auto'; + let mirror: string = "auto"; if (this.mirrorImage) { - if (typeof this.mirrorImage === 'string') { + if (typeof this.mirrorImage === "string") { mirror = String(this.mirrorImage).toLowerCase(); } else { // WebcamMirrorProperties @@ -372,9 +462,9 @@ export class WebcamComponent implements AfterViewInit, OnDestroy { } switch (mirror) { - case 'always': + case "always": return true; - case 'never': + case "never": return false; } } @@ -391,7 +481,8 @@ export class WebcamComponent implements AfterViewInit, OnDestroy { private stopMediaTracks() { if (this.mediaStream && this.mediaStream.getTracks) { // getTracks() returns all media tracks (video+audio) - this.mediaStream.getTracks() + this.mediaStream + .getTracks() .forEach((track: MediaStreamTrack) => track.stop()); } } @@ -418,11 +509,10 @@ export class WebcamComponent implements AfterViewInit, OnDestroy { this.availableVideoInputs = devices; resolve(devices); }) - .catch(err => { + .catch((err) => { this.availableVideoInputs = []; reject(err); }); }); } - }