UNPKG

@polygonjs/polygonjs

Version:

node-based WebGL 3D engine https://polygonjs.com

175 lines (174 loc) 6.18 kB
"use strict"; 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"); } } }); } }