UNPKG

ngx-simple-webcam

Version:

A simple Angular 4+ Webcam-Component. Pure &amp; minimal, no Flash-Fallback. <a href="https://basst314.github.io/ngx-simple-webcam/?" target="_blank">See the Demo!</a>

558 lines (557 loc) 24 kB
import { Component, EventEmitter, Input, NgModule, Output, ViewChild } from '@angular/core'; import { CommonModule } from '@angular/common'; /** * Container class for a captured webcam image * @author basst314 */ var WebcamImage = /** @class */ (function () { /** * @param {?} imageAsDataUrl * @param {?} mimeType */ function WebcamImage(imageAsDataUrl, mimeType) { this._mimeType = null; this._imageAsBase64 = null; this._imageAsDataUrl = null; this._mimeType = mimeType; this._imageAsDataUrl = imageAsDataUrl; } Object.defineProperty(WebcamImage.prototype, "imageAsBase64", { /** * Get the base64 encoded image data * @return {?} */ get: function () { return this._imageAsBase64 ? this._imageAsBase64 : this._imageAsBase64 = this.getDataFromDataUrl(this._imageAsDataUrl); }, enumerable: true, configurable: true }); Object.defineProperty(WebcamImage.prototype, "imageAsDataUrl", { /** * Get the encoded image as dataUrl * @return {?} */ get: function () { return this._imageAsDataUrl; }, enumerable: true, configurable: true }); /** * Extracts the Base64 data out of the given dataUrl. * @param {?} dataUrl * @return {?} */ WebcamImage.prototype.getDataFromDataUrl = function (dataUrl) { return dataUrl.replace("data:" + this._mimeType + ";base64,", ""); }; return WebcamImage; }()); var WebcamUtil = /** @class */ (function () { function WebcamUtil() { } /** * Lists available videoInput devices * @return {?} */ WebcamUtil.getAvailableVideoInputs = function () { if (!navigator.mediaDevices || !navigator.mediaDevices.enumerateDevices) { return Promise.reject("enumerateDevices() not supported."); } return new Promise(function (resolve, reject) { navigator.mediaDevices.enumerateDevices() .then(function (devices) { resolve(devices.filter(function (device) { return device.kind === 'videoinput'; })); }) .catch(function (err) { reject(err.message || err); }); }); }; return WebcamUtil; }()); var WebcamComponent = /** @class */ (function () { function WebcamComponent() { /** * Defines the max width of the webcam area in px */ this.width = 640; /** * Defines the max height of the webcam area in px */ this.height = 480; /** * Defines base constraints to apply when requesting video track from UserMedia */ this.videoOptions = WebcamComponent.DEFAULT_VIDEO_OPTIONS; /** * Flag to enable/disable camera switch. If enabled, a switch icon will be displayed if multiple cameras were found */ this.allowCameraSwitch = true; /** * MediaStream object in use for streaming UserMedia data */ this.mediaStream = null; /** * available video devices */ this.availableVideoInputs = []; /** * Index of active video in availableVideoInputs */ this.activeVideoInputIndex = -1; /** * Indicates whether the video device is ready to be switched */ this.videoInitialized = false; /** * EventEmitter which fires when an image has been captured */ this.imageCapture = new EventEmitter(); /** * Emits a mediaError if webcam cannot be initialized (e.g. missing user permissions) */ this.initError = new EventEmitter(); /** * Emits when the webcam video was clicked */ this.imageClick = new EventEmitter(); /** * Emits the active deviceId after the active video device was switched */ this.cameraSwitched = new EventEmitter(); this.activeVideoSettings = null; } /** * @return {?} */ WebcamComponent.prototype.ngAfterViewInit = function () { var _this = this; this.detectAvailableDevices() .then(function (devices) { // start first device _this.switchToVideoInput(devices.length > 1 ? devices[1].deviceId : (devices.length > 0 ? devices[0].deviceId : null)); }) .catch(function (err) { _this.initError.next(/** @type {?} */ ({ message: err })); // fallback: still try to load webcam, even if device enumeration failed _this.switchToVideoInput(null); }); }; /** * @return {?} */ WebcamComponent.prototype.ngOnDestroy = function () { this.stopMediaTracks(); this.unsubscribeFromSubscriptions(); }; Object.defineProperty(WebcamComponent.prototype, "trigger", { /** * If the given Observable emits, an image will be captured and emitted through 'imageCapture' EventEmitter * @param {?} trigger * @return {?} */ set: function (trigger) { var _this = this; if (this.triggerSubscription) { this.triggerSubscription.unsubscribe(); } // Subscribe to events from this Observable to take snapshots this.triggerSubscription = trigger.subscribe(function () { _this.takeSnapshot(); }); }, enumerable: true, configurable: true }); Object.defineProperty(WebcamComponent.prototype, "switchCamera", { /** * @param {?} switchCamera * @return {?} */ set: function (switchCamera) { var _this = this; if (this.switchCameraSubscription) { this.switchCameraSubscription.unsubscribe(); } // Subscribe to events from this Observable to switch video device this.switchCameraSubscription = switchCamera.subscribe(function (value) { if (typeof value === 'string') { // deviceId was specified _this.switchToVideoInput(value); } else { // direction was specified _this.rotateVideoInput(value !== false); } }); }, enumerable: true, configurable: true }); /** * Get MediaTrackConstraints to request streaming the given device * @param {?} deviceId * @param {?} baseMediaTrackConstraints base constraints to merge deviceId-constraint into * @return {?} */ WebcamComponent.getMediaConstraintsForDevice = function (deviceId, baseMediaTrackConstraints) { var /** @type {?} */ result = baseMediaTrackConstraints ? baseMediaTrackConstraints : this.DEFAULT_VIDEO_OPTIONS; if (deviceId) { result.deviceId = { exact: deviceId }; } return result; }; /** * Tries to harvest the deviceId from the given mediaStreamTrack object. * Browsers populate this object differently; this method tries some different approaches * to read the id. * @param {?} mediaStreamTrack * @return {?} */ WebcamComponent.getDeviceIdFromMediaStreamTrack = function (mediaStreamTrack) { if (mediaStreamTrack.getSettings && mediaStreamTrack.getSettings() && mediaStreamTrack.getSettings().deviceId) { return mediaStreamTrack.getSettings().deviceId; } else if (mediaStreamTrack.getConstraints && mediaStreamTrack.getConstraints() && mediaStreamTrack.getConstraints().deviceId) { var /** @type {?} */ deviceIdObj = mediaStreamTrack.getConstraints().deviceId; return WebcamComponent.getValueFromConstrainDOMString(deviceIdObj); } }; /** * Tries to harvest the facingMode from the given mediaStreamTrack object. * Browsers populate this object differently; this method tries some different approaches * to read the value. * @param {?} mediaStreamTrack * @return {?} */ WebcamComponent.getFacingModeFromMediaStreamTrack = function (mediaStreamTrack) { if (mediaStreamTrack) { if (mediaStreamTrack.getSettings && mediaStreamTrack.getSettings() && mediaStreamTrack.getSettings().facingMode) { return mediaStreamTrack.getSettings().facingMode; } else if (mediaStreamTrack.getConstraints && mediaStreamTrack.getConstraints() && mediaStreamTrack.getConstraints().facingMode) { var /** @type {?} */ facingModeConstraint = mediaStreamTrack.getConstraints().facingMode; return WebcamComponent.getValueFromConstrainDOMString(facingModeConstraint); } } }; /** * Determines whether the given mediaStreamTrack claims itself as user facing * @param {?} mediaStreamTrack * @return {?} */ WebcamComponent.isUserFacing = function (mediaStreamTrack) { var /** @type {?} */ facingMode = WebcamComponent.getFacingModeFromMediaStreamTrack(mediaStreamTrack); return facingMode ? "user" === facingMode.toLowerCase() : false; }; /** * Extracts the value from the given ConstrainDOMString * @param {?} constrainDOMString * @return {?} */ WebcamComponent.getValueFromConstrainDOMString = function (constrainDOMString) { if (constrainDOMString) { if (constrainDOMString instanceof String) { return String(constrainDOMString); } 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"]); } } } return null; }; /** * Takes a snapshot of the current webcam's view and emits the image as an event * @return {?} */ WebcamComponent.prototype.takeSnapshot = function () { // set canvas size to actual video size var /** @type {?} */ _video = this.video.nativeElement; var /** @type {?} */ dimensions = { width: this.width, height: this.height }; if (_video.videoWidth) { dimensions.width = _video.videoWidth; dimensions.height = _video.videoHeight; } var /** @type {?} */ _canvas = this.canvas.nativeElement; _canvas.width = dimensions.width; _canvas.height = dimensions.height; // paint snapshot image to canvas _canvas.getContext('2d').drawImage(this.video.nativeElement, 0, 0); // read canvas content as image // TODO allow mimeType options as Input() var /** @type {?} */ mimeType = "image/jpeg"; var /** @type {?} */ dataUrl = _canvas.toDataURL(mimeType); this.imageCapture.next(new WebcamImage(dataUrl, mimeType)); }; /** * Switches to the next/previous video device * @param {?} forward * @return {?} */ WebcamComponent.prototype.rotateVideoInput = function (forward) { if (this.availableVideoInputs && this.availableVideoInputs.length > 1) { var /** @type {?} */ increment = forward ? 1 : (this.availableVideoInputs.length - 1); this.switchToVideoInput(this.availableVideoInputs[(this.activeVideoInputIndex + increment) % this.availableVideoInputs.length].deviceId); } }; /** * Switches the camera-view to the specified video device * @param {?} deviceId * @return {?} */ WebcamComponent.prototype.switchToVideoInput = function (deviceId) { this.videoInitialized = false; this.stopMediaTracks(); this.initWebcam(deviceId, this.videoOptions); }; Object.defineProperty(WebcamComponent.prototype, "videoWidth", { /** * @return {?} */ get: function () { var /** @type {?} */ videoRatio = this.getVideoAspectRatio(this.activeVideoSettings); return Math.min(this.width, this.height * videoRatio); }, enumerable: true, configurable: true }); Object.defineProperty(WebcamComponent.prototype, "videoHeight", { /** * @return {?} */ get: function () { var /** @type {?} */ videoRatio = this.getVideoAspectRatio(this.activeVideoSettings); return Math.min(this.height, this.width / videoRatio); }, enumerable: true, configurable: true }); Object.defineProperty(WebcamComponent.prototype, "videoStyleClasses", { /** * @return {?} */ get: function () { var /** @type {?} */ classes = ""; if (this.isMirrorImage()) { classes += "mirrored "; } return classes.trim(); }, enumerable: true, configurable: true }); /** * Return the video aspect ratio from the given mediaTrackSettings, if possible; * Otherwise, calculate given the width/height parameters only * @param {?} mediaTrackSettings * @return {?} */ WebcamComponent.prototype.getVideoAspectRatio = function (mediaTrackSettings) { if (mediaTrackSettings) { if (mediaTrackSettings.aspectRatio) { // if ratio is present - use it return mediaTrackSettings.aspectRatio; } else if (mediaTrackSettings.width && mediaTrackSettings.width > 0 && mediaTrackSettings.height && mediaTrackSettings.height > 0) { // if width+height are present - calculate ratio return mediaTrackSettings.width / mediaTrackSettings.height; } } // nothing present in mediaTrackSettings - calculate ratio based on width/height params return this.width / this.height; }; /** * Init webcam live view * @param {?} deviceId * @param {?} userVideoTrackConstraints * @return {?} */ WebcamComponent.prototype.initWebcam = function (deviceId, userVideoTrackConstraints) { var _this = this; var /** @type {?} */ _video = this.video.nativeElement; if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) { // merge deviceId -> userVideoTrackConstraints var /** @type {?} */ videoTrackConstraints = WebcamComponent.getMediaConstraintsForDevice(deviceId, userVideoTrackConstraints); navigator.mediaDevices.getUserMedia(/** @type {?} */ ({ video: videoTrackConstraints })) .then(function (stream) { _this.mediaStream = stream; _video.srcObject = stream; _video.play(); _this.activeVideoSettings = stream.getVideoTracks()[0].getSettings(); var /** @type {?} */ activeDeviceId = WebcamComponent.getDeviceIdFromMediaStreamTrack(stream.getVideoTracks()[0]); _this.activeVideoInputIndex = activeDeviceId ? _this.availableVideoInputs .findIndex(function (mediaDeviceInfo) { return mediaDeviceInfo.deviceId === activeDeviceId; }) : -1; _this.videoInitialized = true; _this.cameraSwitched.next(activeDeviceId); }) .catch(function (err) { _this.initError.next(/** @type {?} */ ({ message: err.message, mediaStreamError: err })); }); } else { this.initError.next(/** @type {?} */ ({ message: "Cannot read UserMedia from MediaDevices." })); } }; /** * @return {?} */ WebcamComponent.prototype.getActiveVideoTrack = function () { return this.mediaStream ? this.mediaStream.getVideoTracks()[0] : null; }; /** * @return {?} */ WebcamComponent.prototype.isMirrorImage = function () { if (!this.getActiveVideoTrack()) { return false; } // check for explicit mirror override parameter { var /** @type {?} */ mirror = "auto"; if (this.mirrorImage) { if (typeof this.mirrorImage === "string") { mirror = String(this.mirrorImage).toLowerCase(); } else { // WebcamMirrorProperties if (this.mirrorImage.x) { mirror = this.mirrorImage.x.toLowerCase(); } } } switch (mirror) { case "always": return true; case "never": return false; } } // default: enable mirroring if webcam is user facing return WebcamComponent.isUserFacing(this.getActiveVideoTrack()); }; /** * Stops all active media tracks. * This prevents the webcam from being indicated as active, * even if it is no longer used by this component. * @return {?} */ WebcamComponent.prototype.stopMediaTracks = function () { if (this.mediaStream && this.mediaStream.getTracks) { // getTracks() returns all media tracks (video+audio) this.mediaStream.getTracks() .forEach(function (track) { return track.stop(); }); } }; /** * Unsubscribe from all open subscriptions * @return {?} */ WebcamComponent.prototype.unsubscribeFromSubscriptions = function () { if (this.triggerSubscription) { this.triggerSubscription.unsubscribe(); } if (this.switchCameraSubscription) { this.switchCameraSubscription.unsubscribe(); } }; /** * Reads available input devices * @return {?} */ WebcamComponent.prototype.detectAvailableDevices = function () { var _this = this; return new Promise(function (resolve, reject) { WebcamUtil.getAvailableVideoInputs() .then(function (devices) { _this.availableVideoInputs = devices; resolve(devices); }) .catch(function (err) { _this.availableVideoInputs = []; reject(err); }); }); }; return WebcamComponent; }()); WebcamComponent.DEFAULT_VIDEO_OPTIONS = { facingMode: 'environment' }; WebcamComponent.decorators = [ { type: Component, args: [{ selector: 'webcam', template: "\n <div class=\"webcam-wrapper\" (click)=\"imageClick.next();\">\n <video #video [width]=\"videoWidth\" [height]=\"videoHeight\" [class]=\"videoStyleClasses\" autoplay muted playsinline></video>\n <div class=\"camera-switch\" *ngIf=\"allowCameraSwitch && availableVideoInputs.length > 1 && videoInitialized\" (click)=\"rotateVideoInput(true)\"></div>\n <canvas #canvas [width]=\"width\" [height]=\"height\"></canvas>\n </div>\n ", styles: ["\n .webcam-wrapper {\n display: inline-block;\n position: relative;\n line-height: 0; }\n .webcam-wrapper video.mirrored {\n -webkit-transform: scale(-1, 1);\n transform: scale(-1, 1); }\n .webcam-wrapper canvas {\n display: none; }\n .webcam-wrapper .camera-switch {\n background-color: rgba(0, 0, 0, 0.1);\n background-image: url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAE9UlEQVR42u2aT2hdRRTGf+cRQqghSqihdBFDkRISK2KDfzDWxHaRQHEhaINKqa1gKQhd6EZLN+IidCH+Q0oWIkVRC21BQxXRitVaSbKoJSGtYGoK2tQ/tU1jY5v0c5F54Xl7b/KSO/PyEt+3e5f75p7zzZwzZ74zUEIJJfyfYaEGllQGVAGZlENdBy6Z2cSiYFTSKkkfS/pH/nBF0kFJdUW9AiRVASeAukD8DgNrzOySrwEzng18KaDzALXuG8W3AiStAvqBisBRNg40mtlPxbYCOgvgPO4bncWW+JpVeDQXRQhIygDfA00F5r0XuNfMrgclQFI98DDQCNQA5ZFXqoCWBVp8XwHRHeEqcN7loy/NbHBesyqpQ1KfFj/6nC+ZvFaApFrgPaCZpYVvgCfNbDiRAElNwGFg+RIt/X8H2s2s9wYCJDUAR4HqJX7++RN40MwGpgmQVAH0AQ2BPz4AHHPl8nBOAqtyFWQjsA6oL4Ada81sPDv7uwImod8kvSJp9RyS8O2SXnb/DYVd2Y9VSroQ4ANXJO2WVJmixqh0kzMWwL4LkiqRtDnA4D1zmfE8j9g9AezcnAHaPcfXdbfdnPZ2Yps6+DwAvO/Z1naTdApY7Xng48BDZnY1MpMVQBuw3iXc5Tnb0wBwBPjUzP6eoezuArZ6svM0geJLkvZEYnl3nkntoqROSbckSW2Suj3ZOIangc7GPJuUtNGdFIfmMeavktoSSKiW9LMPw30Q8JqkekmjCbOZRhuclLQjgYSNxUBAj6RyZ9ATgUJpUtJTCSR8vpAEXHAyWK5BXYFIGHOlepSAloUk4NEYgyoknQhEwhFJ0e8h6VSaQeerCb5uZgdi9utxYBNwOUD93hIVXswM4INCi6K9wAszFC2DwLOBDjHbYp59karIUnRdzYy/3ClqVklaUhfwTICj7K25OqA7a4wWagVsm4Me/xzwg2cCqqONFzO7DPxSCAJi436GUBgHHguQD2oTlJ55oSzP9ybccsttSJw1szdjFOSnI/8dTCGZHwcORp4Nx7y3B1iZ8/sm4MW8/Euxg5wIsS/HaAp3zeP4/G7obRDXI4jiTIA22H7Xdc7X+S3A5lC7QBQ357aq3VAjCeSkwUfAJrfvz+R8A9ADLAtZB+TinpjC5JMA+//jwPZZnF8G7J+L8z4IWB/zbG+gIujVWfLBW/NStVMmqaG4POJRsIjix7h8IGnLQuoBbQki5sVAJHyYm7YkNaRRtXwQ8G1cHpX0iKRrgUjYno17Sf0LrQhJUkdCeHWkVITGJI0k1QeS3ikGSUzOyJUJJNznYneuOCnpTldcxa2kP3xJYqOeSDjqZG8ShJLnE8TTuMS6Iyu1BW7djZqkfo9N0QOuYJmYQddfB7RG+gLTNzqAY9FrL+5/nwEbvDdJJe3zzOrhNP3AWRqmk55t3ZcBuj3b2gb0Sbrbo/NNzk7fFzu7s/E5EiC+rrmeQU0Kx2skvRFoOx2ZzlmSdgbsw49JetvtBpk8nM64d/cGbNtJ0s7cGyJlwHeEv+t3nqnLSgPAUOSGyG3AHUxdzqoJbEcvcL+ZTeTeEapzJKxgaeOcc/7Mf06D7kFrguS0VDAMtGadv+E47DT9tcChJej8ISfpD+abgTe45uOkFi8mnQ+JBVQ+d4VXuOptjavcyot8pq86mfwk8LWZnaOEEkoooYQSSojDv8AhQNeGfe0jAAAAAElFTkSuQmCC\");\n background-repeat: no-repeat;\n border-radius: 5px;\n position: absolute;\n right: 13px;\n top: 10px;\n height: 48px;\n width: 48px;\n background-size: 80%;\n cursor: pointer;\n background-position: center;\n -webkit-transition: background-color 0.2s ease;\n transition: background-color 0.2s ease; }\n .webcam-wrapper .camera-switch:hover {\n background-color: rgba(0, 0, 0, 0.18); }\n "] },] }, ]; /** * @nocollapse */ WebcamComponent.ctorParameters = function () { return []; }; WebcamComponent.propDecorators = { 'width': [{ type: Input },], 'height': [{ type: Input },], 'videoOptions': [{ type: Input },], 'allowCameraSwitch': [{ type: Input },], 'mirrorImage': [{ type: Input },], 'imageCapture': [{ type: Output },], 'initError': [{ type: Output },], 'imageClick': [{ type: Output },], 'cameraSwitched': [{ type: Output },], 'video': [{ type: ViewChild, args: ['video',] },], 'canvas': [{ type: ViewChild, args: ['canvas',] },], 'trigger': [{ type: Input },], 'switchCamera': [{ type: Input },], }; var COMPONENTS = [ WebcamComponent ]; var WebcamModule = /** @class */ (function () { function WebcamModule() { } return WebcamModule; }()); WebcamModule.decorators = [ { type: NgModule, args: [{ imports: [ CommonModule ], declarations: [ COMPONENTS ], exports: [ COMPONENTS ] },] }, ]; /** * @nocollapse */ WebcamModule.ctorParameters = function () { return []; }; var WebcamInitError = /** @class */ (function () { function WebcamInitError() { this.message = null; this.mediaStreamError = null; } return WebcamInitError; }()); var WebcamMirrorProperties = /** @class */ (function () { function WebcamMirrorProperties() { } return WebcamMirrorProperties; }()); /** * Generated bundle index. Do not edit. */ export { WebcamModule, WebcamComponent, WebcamImage, WebcamInitError, WebcamMirrorProperties, WebcamUtil }; //# sourceMappingURL=ngx-simple-webcam.es5.js.map