200 lines
9.5 KiB
JavaScript
200 lines
9.5 KiB
JavaScript
import { app } from "../../../scripts/app.js";
|
|
//TODO: fix occlusion of dimension editing popups
|
|
app.registerExtension({
|
|
name: "Core.FastWebcam",
|
|
async beforeRegisterNodeDef(nodeType, nodeData, app) {
|
|
if (nodeData.name === "FastWebcamCapture") {
|
|
console.log("Registering FastWebcamCapture node type");
|
|
|
|
// Store webcam stream at extension level to avoid reinitializing
|
|
let webcamStream = null;
|
|
let webcamVideo = null;
|
|
let lastCapture = null;
|
|
|
|
// Initialize webcam once for all nodes
|
|
const initWebcam = async () => {
|
|
// console.log("Initializing global webcam stream...");
|
|
if (!webcamStream) {
|
|
try {
|
|
webcamStream = await navigator.mediaDevices.getUserMedia({
|
|
video: true,
|
|
audio: false // No need for audio anymore
|
|
});
|
|
// console.log("Got media stream:", webcamStream);
|
|
|
|
webcamVideo = document.createElement("video");
|
|
webcamVideo.srcObject = webcamStream;
|
|
webcamVideo.style.display = 'none';
|
|
document.body.appendChild(webcamVideo);
|
|
await webcamVideo.play();
|
|
|
|
console.log("Video playing. Video state:", {
|
|
paused: webcamVideo.paused,
|
|
readyState: webcamVideo.readyState,
|
|
videoWidth: webcamVideo.videoWidth,
|
|
videoHeight: webcamVideo.videoHeight
|
|
});
|
|
} catch (error) {
|
|
// console.error("Webcam initialization error:", error);
|
|
throw error;
|
|
}
|
|
}
|
|
return { stream: webcamStream, video: webcamVideo };
|
|
};
|
|
|
|
// Function to capture frames for a given duration
|
|
const captureFrames = async (duration, fps = 30) => {
|
|
return new Promise(async (resolve, reject) => {
|
|
try {
|
|
// Initialize webcam if not already done
|
|
const { video } = await initWebcam();
|
|
|
|
// Create canvas for frame capture
|
|
const canvas = document.createElement('canvas');
|
|
canvas.width = video.videoWidth;
|
|
canvas.height = video.videoHeight;
|
|
const ctx = canvas.getContext('2d');
|
|
|
|
// Calculate total frames to capture
|
|
const totalFrames = Math.ceil(duration * fps);
|
|
const frameInterval = 1000 / fps; // ms between frames
|
|
|
|
console.log(`Capturing ${totalFrames} frames at ${fps} FPS (${frameInterval}ms intervals)`);
|
|
|
|
// Store captured frames
|
|
const frames = [];
|
|
|
|
// Start capturing frames at regular intervals
|
|
let frameCount = 0;
|
|
const startTime = Date.now();
|
|
|
|
const captureFrame = () => {
|
|
// Draw current video frame to canvas
|
|
ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
|
|
const frameDataUrl = canvas.toDataURL('image/jpeg', 0.95);
|
|
frames.push(frameDataUrl);
|
|
|
|
frameCount++;
|
|
if (frameCount < totalFrames) {
|
|
// Calculate next frame time based on start time to avoid drift
|
|
const nextFrameTime = startTime + Math.round(frameCount * frameInterval);
|
|
const delay = Math.max(0, nextFrameTime - Date.now());
|
|
|
|
// Schedule next frame capture
|
|
setTimeout(captureFrame, delay);
|
|
} else {
|
|
// All frames captured
|
|
console.log(`Completed capture of ${frames.length} frames`);
|
|
|
|
const result = {
|
|
frames: frames,
|
|
width: canvas.width,
|
|
height: canvas.height,
|
|
duration: duration,
|
|
fps: fps
|
|
};
|
|
|
|
// Store for potential reuse
|
|
lastCapture = result;
|
|
|
|
resolve(result);
|
|
}
|
|
};
|
|
|
|
// Start capture process
|
|
captureFrame();
|
|
} catch (error) {
|
|
console.error("Error during capture:", error);
|
|
reject(error);
|
|
}
|
|
});
|
|
};
|
|
|
|
nodeType.prototype.getCustomWidgets = function() {
|
|
// console.log("Getting custom widgets");
|
|
return {
|
|
WEBCAM: (node, inputName) => {
|
|
// console.log("Creating WEBCAM widget");
|
|
const container = document.createElement("div");
|
|
container.style.background = "rgba(0,0,0,0.25)";
|
|
container.style.textAlign = "center";
|
|
|
|
const statusLabel = document.createElement("div");
|
|
statusLabel.textContent = "Webcam Ready";
|
|
container.appendChild(statusLabel);
|
|
|
|
const widget = node.addDOMWidget(inputName, "WEBCAM", container);
|
|
return { widget };
|
|
}
|
|
};
|
|
};
|
|
|
|
const onNodeCreated = nodeType.prototype.onNodeCreated;
|
|
nodeType.prototype.onNodeCreated = function() {
|
|
// console.log("Node created");
|
|
const r = onNodeCreated ? onNodeCreated.apply(this, arguments) : undefined;
|
|
|
|
const camera = this.widgets.find((w) => w.name === "image");
|
|
const canvas = document.createElement("canvas");
|
|
|
|
camera.serializeValue = async () => {
|
|
// Check if we should reuse previous capture
|
|
const captureOnQueueWidget = this.widgets.find(w => w.name === "capture_on_queue");
|
|
if (!captureOnQueueWidget.value && lastCapture) {
|
|
console.log("Reusing previous capture");
|
|
return JSON.stringify(lastCapture);
|
|
}
|
|
|
|
// Only initialize webcam when actually needed
|
|
if (!webcamVideo || webcamVideo.paused || webcamVideo.readyState !== 4) {
|
|
// console.log("Initializing webcam for first use...");
|
|
const { video } = await initWebcam();
|
|
webcamVideo = video;
|
|
}
|
|
|
|
// Find record_seconds input value
|
|
const recordSecondsWidget = this.widgets.find(w => w.name === "record_seconds");
|
|
const recordSeconds = recordSecondsWidget ? recordSecondsWidget.value : 0;
|
|
|
|
// Single image capture mode
|
|
if (recordSeconds <= 0) {
|
|
console.log("Capturing single frame from video:", {
|
|
videoWidth: webcamVideo.videoWidth,
|
|
videoHeight: webcamVideo.videoHeight
|
|
});
|
|
|
|
// Always use native webcam resolution
|
|
canvas.width = webcamVideo.videoWidth;
|
|
canvas.height = webcamVideo.videoHeight;
|
|
|
|
const ctx = canvas.getContext("2d");
|
|
ctx.drawImage(webcamVideo, 0, 0, canvas.width, canvas.height);
|
|
|
|
const dataUrl = canvas.toDataURL("image/png");
|
|
|
|
// Store for potential reuse
|
|
lastCapture = {
|
|
frames: [dataUrl],
|
|
width: canvas.width,
|
|
height: canvas.height,
|
|
duration: 0,
|
|
fps: 30
|
|
};
|
|
|
|
return dataUrl;
|
|
}
|
|
// Video capture mode - capture frames
|
|
else {
|
|
// Capture frames
|
|
const captureResult = await captureFrames(recordSeconds);
|
|
|
|
// Convert to JSON and return
|
|
return JSON.stringify(captureResult);
|
|
}
|
|
};
|
|
|
|
return r;
|
|
};
|
|
}
|
|
}
|
|
});
|