human/src/util/webcam.ts

210 lines
6.8 KiB
TypeScript
Raw Normal View History

2022-09-30 03:28:13 +02:00
import { log } from './util';
// const log = (...msg) => console.log('webcam', ...msg); // eslint-disable-line no-console
/** WebCam configuration */
export interface WebCamConfig {
/**
* element can be:
* - string which indicates dom element id
* - actual HTMLVideo dom element
* - undefined in which case a new HTMLVideoElement will be created
*/
element: string | HTMLVideoElement | undefined,
/** print messages on console */
debug: boolean,
/** use front or back camera */
mode: 'front' | 'back',
/** camera crop mode */
crop: boolean,
/** desired webcam width */
width: number,
/** desired webcam height */
height: number,
2022-11-16 17:27:59 +01:00
/** deviceId of the video device to use */
id?: string,
2022-09-30 03:28:13 +02:00
}
export class WebCam { // eslint-disable-line @typescript-eslint/no-extraneous-class
/** current webcam configuration */
config: WebCamConfig;
/** instance of dom element associated with webcam stream */
element: HTMLVideoElement | undefined;
/** active webcam stream */
stream: MediaStream | undefined;
2022-11-16 17:27:59 +01:00
/** enumerated video devices */
devices: MediaDeviceInfo[] = [];
2022-09-30 03:28:13 +02:00
constructor() {
this.config = {
element: undefined,
debug: true,
mode: 'front',
crop: false,
width: 0,
height: 0,
};
}
/** get active webcam stream track */
public get track(): MediaStreamTrack | undefined {
if (!this.stream) return undefined;
return this.stream.getVideoTracks()[0];
}
/** get webcam capabilities */
public get capabilities(): MediaTrackCapabilities | undefined {
if (!this.track) return undefined;
return this.track.getCapabilities ? this.track.getCapabilities() : undefined;
}
/** get webcam constraints */
public get constraints(): MediaTrackConstraints | undefined {
if (!this.track) return undefined;
return this.track.getConstraints ? this.track.getConstraints() : undefined;
}
/** get webcam settings */
public get settings(): MediaTrackSettings | undefined {
if (!this.stream) return undefined;
const track: MediaStreamTrack = this.stream.getVideoTracks()[0];
return track.getSettings ? track.getSettings() : undefined;
}
/** get webcam label */
public get label(): string {
if (!this.track) return '';
return this.track.label;
}
/** is webcam paused */
public get paused(): boolean {
return this.element?.paused || false;
}
/** webcam current width */
public get width(): number {
return this.element?.videoWidth || 0;
}
/** webcam current height */
public get height(): number {
return this.element?.videoHeight || 0;
}
2022-11-16 17:27:59 +01:00
public enumerate = async (): Promise<MediaDeviceInfo[]> => {
try {
const devices = await navigator.mediaDevices.enumerateDevices();
this.devices = devices.filter((device) => device.kind === 'videoinput');
} catch {
this.devices = [];
}
return this.devices;
};
2022-09-30 03:28:13 +02:00
/** start method initializizes webcam stream and associates it with a dom video element */
public start = async (webcamConfig?: Partial<WebCamConfig>): Promise<void> => {
// set config
if (webcamConfig?.debug) this.config.debug = webcamConfig?.debug;
if (webcamConfig?.crop) this.config.crop = webcamConfig?.crop;
if (webcamConfig?.mode) this.config.mode = webcamConfig?.mode;
if (webcamConfig?.width) this.config.width = webcamConfig?.width;
if (webcamConfig?.height) this.config.height = webcamConfig?.height;
2022-11-16 17:27:59 +01:00
if (webcamConfig?.id) this.config.id = webcamConfig?.id;
2022-09-30 03:28:13 +02:00
// use or create dom element
if (webcamConfig?.element) {
if (typeof webcamConfig.element === 'string') {
const el = document.getElementById(webcamConfig.element);
if (el && el instanceof HTMLVideoElement) {
this.element = el;
} else {
if (this.config.debug) log('webcam', 'cannot get dom element', webcamConfig.element);
return;
}
} else if (webcamConfig.element instanceof HTMLVideoElement) {
this.element = webcamConfig.element;
} else {
if (this.config.debug) log('webcam', 'unknown dom element', webcamConfig.element);
return;
}
} else {
this.element = document.createElement('video');
}
// set constraints to use
2022-11-04 18:20:56 +01:00
const requestedConstraints: MediaStreamConstraints = {
2022-09-30 03:28:13 +02:00
audio: false,
video: {
facingMode: this.config.mode === 'front' ? 'user' : 'environment',
// @ts-ignore // resizeMode is still not defined in tslib
resizeMode: this.config.crop ? 'crop-and-scale' : 'none',
width: { ideal: this.config.width > 0 ? this.config.width : window.innerWidth },
height: { ideal: this.config.height > 0 ? this.config.height : window.innerHeight },
},
};
2022-11-16 17:27:59 +01:00
if (this.config.id) (requestedConstraints.video as MediaTrackConstraintSet).deviceId = this.config.id;
2022-09-30 03:28:13 +02:00
// set default event listeners
this.element.addEventListener('play', () => { if (this.config.debug) log('webcam', 'play'); });
this.element.addEventListener('pause', () => { if (this.config.debug) log('webcam', 'pause'); });
this.element.addEventListener('click', async () => { // pause when clicked on screen and resume on next click
if (!this.element || !this.stream) return;
if (this.element.paused) await this.element.play();
else this.element.pause();
});
// get webcam and set it to run in dom element
if (!navigator?.mediaDevices) {
if (this.config.debug) log('webcam', 'no devices');
return;
}
try {
this.stream = await navigator.mediaDevices.getUserMedia(requestedConstraints); // get stream that satisfies constraints
} catch (err) {
log('webcam', err);
return;
}
if (!this.stream) {
if (this.config.debug) log('webcam', 'no stream');
return;
}
this.element.srcObject = this.stream; // assign it to dom element
const ready = new Promise((resolve) => { // wait until stream is ready
if (!this.element) resolve(false);
else this.element.onloadeddata = () => resolve(true);
});
await ready;
await this.element.play(); // start playing
if (this.config.debug) {
log('webcam', {
width: this.width,
height: this.height,
label: this.label,
stream: this.stream,
track: this.track,
settings: this.settings,
constraints: this.constraints,
capabilities: this.capabilities,
});
}
};
/** pause webcam video method */
public pause = (): void => {
if (this.element) this.element.pause();
};
/** play webcam video method */
public play = async (): Promise<void> => {
if (this.element) await this.element.play();
};
/** stop method stops active webcam stream track and disconnects webcam */
public stop = (): void => {
if (this.config.debug) log('webcam', 'stop');
if (this.track) this.track.stop();
};
}