@polygonjs/polygonjs
Version:
node-based WebGL 3D engine https://polygonjs.com
175 lines (174 loc) • 6.18 kB
JavaScript
;
import { TypedCopNode } from "./_Base";
import { VideoTexture, SRGBColorSpace } from "three";
import { NodeParamsConfig, ParamConfig } from "../utils/params/ParamsConfig";
import { TextureParamsController, TextureParamConfig } from "./utils/TextureParamsController";
import { InputCloneMode } from "../../poly/InputCloneMode";
import { CopType } from "../../poly/registers/nodes/types/Cop";
var WebCamFacingMode = /* @__PURE__ */ ((WebCamFacingMode2) => {
WebCamFacingMode2["USER"] = "user";
WebCamFacingMode2["ENVIRONMENT"] = "environment";
return WebCamFacingMode2;
})(WebCamFacingMode || {});
const WEBCAM_FACING_MODES = ["user" /* USER */, "environment" /* ENVIRONMENT */];
let videoCount = 0;
let streamCount = 0;
let canPlayCount = 0;
export function WebCamCopParamConfig(Base) {
return class Mixin extends Base {
constructor() {
super(...arguments);
/** @param texture resolution */
this.res = ParamConfig.VECTOR2([1024, 1024]);
/** @param facingMode (on a mobile device, 'user' is the front camera, 'environment' is the back one ) */
this.facingMode = ParamConfig.INTEGER(WEBCAM_FACING_MODES.indexOf("user" /* USER */), {
menu: {
entries: WEBCAM_FACING_MODES.map((name, value) => ({ name, value }))
}
});
}
};
}
class WebCamCopParamsConfig extends TextureParamConfig(WebCamCopParamConfig(NodeParamsConfig), {
tcolorSpace: true,
colorSpace: SRGBColorSpace
}) {
}
const ParamsConfig = new WebCamCopParamsConfig();
export class WebCamCopNode extends TypedCopNode {
constructor() {
super(...arguments);
this.paramsConfig = ParamsConfig;
this.textureParamsController = new TextureParamsController(this);
}
static type() {
return CopType.WEB_CAM;
}
HTMLVideoElement() {
return this._video;
}
initializeNode() {
this.io.inputs.setCount(0, 1);
this.io.inputs.initInputsClonedState(InputCloneMode.NEVER);
}
dispose() {
super.dispose();
this._cancelWebcamRequest();
}
setFacingMode(facingMode) {
this.p.facingMode.set(WEBCAM_FACING_MODES.indexOf(facingMode));
}
_cancelWebcamRequest() {
try {
if (this._stream) {
this._stream.getTracks().forEach((track) => track.stop());
}
} catch (err) {
console.error(err);
console.warn(`failed to cancel webcam request`);
}
}
_createHTMLVideoElement(width, height) {
const element = document.createElement("video");
element.width = width;
element.height = height;
element.autoplay = true;
element.setAttribute("autoplay", "true");
element.setAttribute("muted", "true");
element.setAttribute("playsinline", "true");
return element;
}
async cook(inputContents) {
const inputTexture = inputContents[0];
const result = await this._streamToTexture(inputTexture, false);
if (result && result.dimensionsSwapRequired) {
await this._streamToTexture(inputTexture, true);
}
}
async _streamToTexture(inputTexture, swapDimensions) {
const x = swapDimensions ? this.pv.res.y : this.pv.res.x;
const y = swapDimensions ? this.pv.res.x : this.pv.res.y;
const width = x;
const height = y;
if (width <= 0 || height <= 0) {
this.states.error.set(`invalid resolution ${width}x${height}`);
return;
}
const _checkDimensionsValid = () => {
return width == x && height == y;
};
const setErrorFromInvalidDimensions = () => {
this.states.error.set(`recompute needed ${width}x${height} is not ${x}x${y}`);
};
const videoElement = this._createHTMLVideoElement(width, height);
videoCount++;
const texture = new VideoTexture(videoElement);
if (inputTexture) {
TextureParamsController.copyTextureAttributes(texture, inputTexture);
}
await this.textureParamsController.update(texture);
if (!_checkDimensionsValid()) {
setErrorFromInvalidDimensions();
return;
}
return new Promise((resolve) => {
if (navigator && navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
const facingMode = WEBCAM_FACING_MODES[this.pv.facingMode];
const constraints = {
video: {
width,
height,
aspectRatio: width / height,
facingMode
},
audio: false
};
this._cancelWebcamRequest();
navigator.mediaDevices.getUserMedia(constraints).then((stream) => {
if (!_checkDimensionsValid()) {
setErrorFromInvalidDimensions();
return;
}
this._stream = stream;
streamCount++;
let _onCanPlayProcessed = false;
videoElement.oncanplay = () => {
if (_onCanPlayProcessed) {
return;
}
_onCanPlayProcessed = true;
canPlayCount++;
if (!_checkDimensionsValid()) {
setErrorFromInvalidDimensions();
return;
}
this._video = videoElement;
const dimensionsMatchWithoutSwap = Math.round(videoElement.videoWidth) == Math.round(width) && Math.round(videoElement.videoHeight) == Math.round(height);
if (swapDimensions || dimensionsMatchWithoutSwap) {
this.setTexture(texture);
} else {
resolve({ dimensionsSwapRequired: true });
}
};
videoElement.onerror = (err) => {
this.states.error.set(`webcam video error: ${err}`);
};
videoElement.srcObject = stream;
videoElement.play();
}).catch((error) => {
console.log(`error ${width}x${height}`, error, { constraints });
this.states.error.set("Unable to access the camera/webcam");
});
} else {
const isHttps = globalThis.location.protocol.startsWith("https");
if (isHttps) {
this.states.error.set(
"MediaDevices interface not available. Please check that your connection is secure (using https)"
);
} else {
this.states.error.set("https is required to use the webcam node");
}
}
});
}
}