UNPKG

ngx-webcam2

Version:

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

574 lines (565 loc) 26.7 kB
import { EventEmitter, Component, Input, Output, ViewChild, NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { __awaiter, __generator } from 'tslib'; /** * Container class for a captured webcam image * @author basst314, davidshen84 */ var WebcamImage = /** @class */ (function () { function WebcamImage(imageAsDataUrl, mimeType, imageData) { this._mimeType = null; this._imageAsBase64 = null; this._imageAsDataUrl = null; this._imageData = null; this._mimeType = mimeType; this._imageAsDataUrl = imageAsDataUrl; this._imageData = imageData; } /** * Extracts the Base64 data out of the given dataUrl. * @param dataUrl the given dataUrl * @param mimeType the mimeType of the data */ WebcamImage.getDataFromDataUrl = function (dataUrl, mimeType) { return dataUrl.replace("data:" + mimeType + ";base64,", ''); }; Object.defineProperty(WebcamImage.prototype, "imageAsBase64", { /** * Get the base64 encoded image data * @returns base64 data of the image */ get: function () { return this._imageAsBase64 ? this._imageAsBase64 : this._imageAsBase64 = WebcamImage.getDataFromDataUrl(this._imageAsDataUrl, this._mimeType); }, enumerable: false, configurable: true }); Object.defineProperty(WebcamImage.prototype, "imageAsDataUrl", { /** * Get the encoded image as dataUrl * @returns the dataUrl of the image */ get: function () { return this._imageAsDataUrl; }, enumerable: false, configurable: true }); Object.defineProperty(WebcamImage.prototype, "imageData", { /** * Get the ImageData object associated with the canvas' 2d context. * @returns the ImageData of the canvas's 2d context. */ get: function () { return this._imageData; }, enumerable: false, configurable: true }); return WebcamImage; }()); var WebcamUtil = /** @class */ (function () { function WebcamUtil() { } WebcamUtil.hasVideoInputs = function () { var inputs = WebcamUtil.availableVideoInputs; return !!inputs.find(function (input) { return !!input.deviceId; }); }; /** * Lists available videoInput devices * @returns a list of media device info. */ WebcamUtil.getAvailableVideoInputs = function () { return __awaiter(this, void 0, void 0, function () { var devices; return __generator(this, function (_a) { switch (_a.label) { case 0: if (!navigator.mediaDevices || !navigator.mediaDevices.enumerateDevices) { throw new Error('enumerateDevices() not supported.'); } if (!!this.hasVideoInputs()) return [3 /*break*/, 2]; return [4 /*yield*/, navigator.mediaDevices.enumerateDevices()]; case 1: devices = _a.sent(); WebcamUtil.availableVideoInputs = devices.filter(function (device) { return device.kind === 'videoinput'; }); _a.label = 2; case 2: return [2 /*return*/, WebcamUtil.availableVideoInputs]; } }); }); }; WebcamUtil.availableVideoInputs = []; 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; /** Flag to control whether an ImageData object is stored into the WebcamImage object. */ this.captureImageData = false; /** The image type to use when capturing snapshots */ this.imageType = WebcamComponent.DEFAULT_IMAGE_TYPE; /** The image quality to use when capturing snapshots (number between 0 and 1) */ this.imageQuality = WebcamComponent.DEFAULT_IMAGE_QUALITY; /** 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(); /** available video devices */ this.availableVideoInputs = []; /** Indicates whether the video device is ready to be switched */ this.videoInitialized = false; /** Index of active video in availableVideoInputs */ this.activeVideoInputIndex = -1; /** MediaStream object in use for streaming UserMedia data */ this.mediaStream = null; /** width and height of the active video stream */ this.activeVideoSettings = null; } Object.defineProperty(WebcamComponent.prototype, "trigger", { /** * If the given Observable emits, an image will be captured and emitted through 'imageCapture' EventEmitter */ 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: false, configurable: true }); Object.defineProperty(WebcamComponent.prototype, "switchCamera", { /** * If the given Observable emits, the active webcam will be switched to the one indicated by the emitted value. * @param switchCamera Indicates which webcam to switch to * true: cycle forwards through available webcams * false: cycle backwards through available webcams * string: activate the webcam with the given id */ 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: false, configurable: true }); /** * Get MediaTrackConstraints to request streaming the given device * @param deviceId * @param baseMediaTrackConstraints base constraints to merge deviceId-constraint into * @returns */ WebcamComponent.getMediaConstraintsForDevice = function (deviceId, baseMediaTrackConstraints) { var 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 * @returns deviceId if found in the mediaStreamTrack */ 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 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 * @returns facingMode if found in the mediaStreamTrack */ 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 facingModeConstraint = mediaStreamTrack.getConstraints().facingMode; return WebcamComponent.getValueFromConstrainDOMString(facingModeConstraint); } } }; /** * Determines whether the given mediaStreamTrack claims itself as user facing * @param mediaStreamTrack */ WebcamComponent.isUserFacing = function (mediaStreamTrack) { var facingMode = WebcamComponent.getFacingModeFromMediaStreamTrack(mediaStreamTrack); return facingMode ? 'user' === facingMode.toLowerCase() : false; }; /** * Extracts the value from the given ConstrainDOMString * @param constrainDOMString */ 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; }; WebcamComponent.prototype.ngAfterViewInit = function () { return __awaiter(this, void 0, void 0, function () { var e_1; return __generator(this, function (_a) { switch (_a.label) { case 0: _a.trys.push([0, 2, , 3]); return [4 /*yield*/, this.detectAvailableDevices()]; case 1: _a.sent(); return [3 /*break*/, 3]; case 2: e_1 = _a.sent(); this.initError.next({ message: e_1.message }); return [3 /*break*/, 3]; case 3: this.switchToVideoInput(null); return [2 /*return*/]; } }); }); }; WebcamComponent.prototype.ngOnDestroy = function () { this.stopMediaTracks(); this.unsubscribeFromSubscriptions(); }; /** * Takes a snapshot of the current webcam's view and emits the image as an event */ WebcamComponent.prototype.takeSnapshot = function () { // set canvas size to actual video size var _video = this.nativeVideoElement; var dimensions = { width: this.width, height: this.height }; if (_video.videoWidth) { dimensions.width = _video.videoWidth; dimensions.height = _video.videoHeight; } var _canvas = this.canvas.nativeElement; _canvas.width = dimensions.width; _canvas.height = dimensions.height; // paint snapshot image to canvas var context2d = _canvas.getContext('2d'); context2d.drawImage(_video, 0, 0); // read canvas content as image var mimeType = this.imageType ? this.imageType : WebcamComponent.DEFAULT_IMAGE_TYPE; var quality = this.imageQuality ? this.imageQuality : WebcamComponent.DEFAULT_IMAGE_QUALITY; var dataUrl = _canvas.toDataURL(mimeType, quality); // get the ImageData object from the canvas' context. var imageData = null; if (this.captureImageData) { imageData = context2d.getImageData(0, 0, _canvas.width, _canvas.height); } this.imageCapture.next(new WebcamImage(dataUrl, mimeType, imageData)); }; /** * Switches to the next/previous video device * @param forward */ WebcamComponent.prototype.rotateVideoInput = function (forward) { if (this.availableVideoInputs && this.availableVideoInputs.length > 1) { var increment = forward ? 1 : (this.availableVideoInputs.length - 1); var nextInputIndex = (this.activeVideoInputIndex + increment) % this.availableVideoInputs.length; this.switchToVideoInput(this.availableVideoInputs[nextInputIndex].deviceId); } }; /** * Switches the camera-view to the specified video device */ WebcamComponent.prototype.switchToVideoInput = function (deviceId) { this.videoInitialized = false; this.stopMediaTracks(); this.initWebcam(deviceId, this.videoOptions); }; /** * Event-handler for video resize event. * Triggers Angular change detection so that new video dimensions get applied */ WebcamComponent.prototype.videoResize = function () { // here to trigger Angular change detection }; Object.defineProperty(WebcamComponent.prototype, "videoWidth", { get: function () { var videoRatio = this.getVideoAspectRatio(); return Math.min(this.width, this.height * videoRatio); }, enumerable: false, configurable: true }); Object.defineProperty(WebcamComponent.prototype, "videoHeight", { get: function () { var videoRatio = this.getVideoAspectRatio(); return Math.min(this.height, this.width / videoRatio); }, enumerable: false, configurable: true }); Object.defineProperty(WebcamComponent.prototype, "videoStyleClasses", { get: function () { var classes = ''; if (this.isMirrorImage()) { classes += 'mirrored '; } return classes.trim(); }, enumerable: false, configurable: true }); Object.defineProperty(WebcamComponent.prototype, "nativeVideoElement", { get: function () { return this.video.nativeElement; }, enumerable: false, configurable: true }); /** * Returns the video aspect ratio of the active video stream */ WebcamComponent.prototype.getVideoAspectRatio = function () { // calculate ratio from video element dimensions if present var videoElement = this.nativeVideoElement; if (videoElement.videoWidth && videoElement.videoWidth > 0 && videoElement.videoHeight && videoElement.videoHeight > 0) { return videoElement.videoWidth / videoElement.videoHeight; } // nothing present - calculate ratio based on width/height params return this.width / this.height; }; /** * Init webcam live view */ WebcamComponent.prototype.initWebcam = function (deviceId, userVideoTrackConstraints) { var _this = this; var _video = this.nativeVideoElement; if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) { // merge deviceId -> userVideoTrackConstraints var videoTrackConstraints = WebcamComponent.getMediaConstraintsForDevice(deviceId, userVideoTrackConstraints); navigator.mediaDevices.getUserMedia({ video: videoTrackConstraints }) .then(function (stream) { return __awaiter(_this, void 0, void 0, function () { var activeDeviceId; return __generator(this, function (_a) { switch (_a.label) { case 0: this.mediaStream = stream; _video.srcObject = stream; _video.play(); this.activeVideoSettings = stream.getVideoTracks()[0].getSettings(); activeDeviceId = WebcamComponent.getDeviceIdFromMediaStreamTrack(stream.getVideoTracks()[0]); this.cameraSwitched.next(activeDeviceId); // 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. return [4 /*yield*/, this.detectAvailableDevices()]; case 1: // 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. _a.sent(); this.activeVideoInputIndex = activeDeviceId ? this.availableVideoInputs .findIndex(function (mediaDeviceInfo) { return mediaDeviceInfo.deviceId === activeDeviceId; }) : -1; this.videoInitialized = true; return [2 /*return*/]; } }); }); }) .catch(function (err) { _this.initError.next({ message: err.message, mediaStreamError: err }); }); } else { this.initError.next({ message: 'Cannot read UserMedia from MediaDevices.' }); } }; WebcamComponent.prototype.getActiveVideoTrack = function () { return this.mediaStream ? this.mediaStream.getVideoTracks()[0] : null; }; WebcamComponent.prototype.isMirrorImage = function () { if (!this.getActiveVideoTrack()) { return false; } // check for explicit mirror override parameter { var 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. */ WebcamComponent.prototype.stopMediaTracks = function () { if (this.mediaStream && this.mediaStream.getTracks) { // pause video to prevent mobile browser freezes this.video.nativeElement.pause(); // getTracks() returns all media tracks (video+audio) this.mediaStream.getTracks() .forEach(function (track) { return track.stop(); }); } }; /** * Unsubscribe from all open subscriptions */ WebcamComponent.prototype.unsubscribeFromSubscriptions = function () { if (this.triggerSubscription) { this.triggerSubscription.unsubscribe(); } if (this.switchCameraSubscription) { this.switchCameraSubscription.unsubscribe(); } }; /** * Reads available input devices */ WebcamComponent.prototype.detectAvailableDevices = function () { return __awaiter(this, void 0, void 0, function () { var _a; return __generator(this, function (_b) { switch (_b.label) { case 0: _a = this; return [4 /*yield*/, WebcamUtil.getAvailableVideoInputs()]; case 1: _a.availableVideoInputs = _b.sent(); return [2 /*return*/]; } }); }); }; WebcamComponent.DEFAULT_VIDEO_OPTIONS = { facingMode: 'environment' }; WebcamComponent.DEFAULT_IMAGE_TYPE = 'image/jpeg'; WebcamComponent.DEFAULT_IMAGE_QUALITY = 0.92; WebcamComponent.decorators = [ { type: Component, args: [{ selector: 'webcam', template: "<div class=\"webcam-wrapper\" (click)=\"imageClick.next();\">\n <video #video [width]=\"videoWidth\" [height]=\"videoHeight\" [class]=\"videoStyleClasses\" autoplay muted playsinline (resize)=\"videoResize()\"></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: [".webcam-wrapper{display:inline-block;position:relative;line-height:0}.webcam-wrapper video.mirrored{transform:scale(-1,1)}.webcam-wrapper canvas{display:none}.webcam-wrapper .camera-switch{background-color:rgba(0,0,0,.1);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);background-repeat:no-repeat;border-radius:5px;position:absolute;right:13px;top:10px;height:48px;width:48px;background-size:80%;cursor:pointer;background-position:center;transition:background-color .2s}.webcam-wrapper .camera-switch:hover{background-color:rgba(0,0,0,.18)}"] },] } ]; WebcamComponent.propDecorators = { width: [{ type: Input }], height: [{ type: Input }], videoOptions: [{ type: Input }], allowCameraSwitch: [{ type: Input }], mirrorImage: [{ type: Input }], captureImageData: [{ type: Input }], imageType: [{ type: Input }], imageQuality: [{ type: Input }], imageCapture: [{ type: Output }], initError: [{ type: Output }], imageClick: [{ type: Output }], cameraSwitched: [{ type: Output }], video: [{ type: ViewChild, args: ['video', { static: true },] }], canvas: [{ type: ViewChild, args: ['canvas', { static: true },] }], trigger: [{ type: Input }], switchCamera: [{ type: Input }] }; return WebcamComponent; }()); var COMPONENTS = [ WebcamComponent ]; var WebcamModule = /** @class */ (function () { function WebcamModule() { } WebcamModule.decorators = [ { type: NgModule, args: [{ imports: [ CommonModule ], declarations: [ COMPONENTS ], exports: [ COMPONENTS ] },] } ]; return WebcamModule; }()); 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 { WebcamComponent, WebcamImage, WebcamInitError, WebcamMirrorProperties, WebcamModule, WebcamUtil }; //# sourceMappingURL=ngx-webcam.js.map