ngx-webcam2
Version:
A simple Angular webcam component. Pure & 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
JavaScript
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