diff --git a/system/public/aesthetic.computer/bios.mjs b/system/public/aesthetic.computer/bios.mjs index ba809e6c69..ad4a5dfd30 100644 --- a/system/public/aesthetic.computer/bios.mjs +++ b/system/public/aesthetic.computer/bios.mjs @@ -19894,16 +19894,43 @@ async function boot(parsed, bpm = 60, resolution, debug) { // const capabilities = videoTrack.getCapabilities(); settings = videoTrack.getSettings(); - // console.log( - // "Got: Width:", - // settings.width, - // "Height:", - // settings.height, - // ); // โค๏ธโ€๐Ÿ”ฅ - // Update global facingMode in case different from requested. facingMode = videoTrack.getConstraints().facingMode; + // ๐Ÿ“Š Send camera debug telemetry to the worker so pieces (e.g. cap) + // can console.log() it and the piece-runs silo captures it. + // This lets us diagnose rotation/orientation issues on devices we + // can't inspect directly. + const orientationAngle = + (typeof screen !== "undefined" && screen.orientation?.angle) ?? + (typeof window !== "undefined" ? window.orientation : null) ?? + null; + const isPortrait = + typeof window !== "undefined" + ? window.matchMedia?.("(orientation: portrait)")?.matches + : null; + send({ + type: "camera:debug", + content: { + requested: { width: reqWidth, height: reqHeight }, + bufferRequested: { width: cWidth, height: cHeight }, + trackSettings: { + width: settings?.width ?? null, + height: settings?.height ?? null, + aspectRatio: settings?.aspectRatio ?? null, + facingMode: settings?.facingMode ?? null, + frameRate: settings?.frameRate ?? null, + }, + facingMode, + iOS, + Android, + isPortrait, + orientationAngle, + userAgent: + typeof navigator !== "undefined" ? navigator.userAgent : null, + }, + }); + const devices = await navigator.mediaDevices.enumerateDevices(); const videoDevices = devices.filter( (device) => device.kind === "videoinput", @@ -20111,6 +20138,23 @@ async function boot(parsed, bpm = 60, resolution, debug) { video.videoWidth > 0 && video.videoHeight > 0 && videoIsLandscape === bufferIsPortrait; + + // ๐Ÿ“Š One-shot diagnostic: report actual video dimensions once the + // first real frame is available. Lands in piece-runs via cap.mjs. + if (!video.__acFrameDebugReported && video.videoWidth > 0) { + video.__acFrameDebugReported = true; + send({ + type: "camera:debug:frame", + content: { + video: { width: video.videoWidth, height: video.videoHeight }, + buffer: { width: buffer.width, height: buffer.height }, + videoIsLandscape, + bufferIsPortrait, + needsRotation, + facingMode, + }, + }); + } // Mirror the front camera for selfie framing. Desktop webcams are // conventionally mirrored too. Mobile rear camera stays unmirrored. const needsMirror = facingMode === "user" || (!iOS && !Android); @@ -20118,13 +20162,30 @@ async function boot(parsed, bpm = 60, resolution, debug) { // Front cameras are mounted mirrored relative to rear cameras, so the // rotation that lands the subject upright is opposite: front = +90ยฐ CW, // rear = -90ยฐ CCW. Desktop webcams behave like front cameras. - const rotationAngle = + // ๐Ÿงช URL override for camera rotation while we bisect the correct + // angle on device: ?camrot=0|cw|180|ccw (0 = no rotation). Also + // ?camforce=1 forces the rotation path even when dims say no. + let rotationAngle = facingMode === "user" || (!iOS && !Android) ? Math.PI / 2 : -Math.PI / 2; + let forceRotation = false; + if (typeof location !== "undefined" && location.search) { + const camParams = new URLSearchParams(location.search); + const camrot = camParams.get("camrot"); + if (camrot === "cw" || camrot === "90") rotationAngle = Math.PI / 2; + else if (camrot === "ccw" || camrot === "-90") + rotationAngle = -Math.PI / 2; + else if (camrot === "180") rotationAngle = Math.PI; + else if (camrot === "0" || camrot === "none") rotationAngle = 0; + if (camParams.get("camforce") === "1") forceRotation = true; + } + const needsRotationFinal = forceRotation + ? video.videoWidth > 0 && video.videoHeight > 0 + : needsRotation; // Send frames by default (non-rotation path only). - if (!needsRotation && needsMirror) { + if ((!needsRotationFinal || rotationAngle === 0) && needsMirror) { bufferCtx.translate(buffer.width / 2, buffer.height / 2); const zoom = 1; // if (hands) { @@ -20156,7 +20217,7 @@ async function boot(parsed, bpm = 60, resolution, debug) { } // Drawing a video frame to the buffer (mirrored, proportion adjusted). - if (needsRotation) { + if (needsRotationFinal && rotationAngle !== 0) { // Landscape sensor pixels in portrait buffer: rotate ยฑ90ยฐ so the // video fills the portrait frame. After rotation the effective // video width/height are swapped, so fit calculation uses the diff --git a/system/public/aesthetic.computer/disks/cap.mjs b/system/public/aesthetic.computer/disks/cap.mjs index 8bd037ee8c..a3d6ffb323 100644 --- a/system/public/aesthetic.computer/disks/cap.mjs +++ b/system/public/aesthetic.computer/disks/cap.mjs @@ -299,6 +299,14 @@ function stopRecording(rec, sound, jump) { } function act({ event: e, jump, video, cameras, sound, rec, notice, leaving, hud }) { + // ๐Ÿ“Š Camera diagnostics from bios.mjs โ†’ piece-runs via console.log. + if (e.is("camera:debug")) { + console.log("๐Ÿ“ท cam/init:", JSON.stringify(e)); + } + if (e.is("camera:debug:frame")) { + console.log("๐Ÿ“ท cam/frame:", JSON.stringify(e)); + } + // Handle microphone connection events if (e.is("microphone-connect:success")) { micConnected = true; @@ -451,5 +459,6 @@ function drawMicIcon($, x, y, color) { export { boot, paint, sim, act, beat, leave }; -// No nopaint system - cap records the screen directly via tape infrastructure -export const nohud = true; // Minimal HUD for camera view +// No nopaint system โ€” cap records the screen directly via tape infrastructure. +// We keep the standard HUD corner label ("cap") visible; hiding it (nohud) +// left the corner empty and felt broken.