ngx-simple-webcam
Version:
A simple Angular 4+ Webcam-Component. Pure & 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
JavaScript
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