add: stylesheet
This commit is contained in:
Binary file not shown.
@@ -1,4 +1,16 @@
|
||||
import { useHandTrackingSnapshot } from "@/hooks/useHandTrackingSnapshot";
|
||||
import type { HandTrackingStatus } from "@/types/handTracking";
|
||||
|
||||
const STATUS_LABELS: Record<HandTrackingStatus, string> = {
|
||||
idle: "Idle",
|
||||
requesting_camera: "Requesting camera",
|
||||
starting_camera: "Starting camera",
|
||||
connecting_server: "Connecting server",
|
||||
connecting: "Connecting",
|
||||
connected: "Connected",
|
||||
disconnected: "Disconnected",
|
||||
error: "Error",
|
||||
};
|
||||
|
||||
export function HandTrackingOverlay(): React.JSX.Element | null {
|
||||
const { hands, status, serverStatus, error } = useHandTrackingSnapshot();
|
||||
@@ -12,7 +24,7 @@ export function HandTrackingOverlay(): React.JSX.Element | null {
|
||||
return (
|
||||
<aside className="hand-tracking-overlay" aria-label="Hand tracking status">
|
||||
<strong>Hand tracking</strong>
|
||||
<span>Status: {status}</span>
|
||||
<span>Status: {STATUS_LABELS[status]}</span>
|
||||
{serverStatus ? <span>Server: {serverStatus}</span> : null}
|
||||
<span>Hands: {hands.length}</span>
|
||||
<span>Pinch: {pinching ? "yes" : "no"}</span>
|
||||
|
||||
@@ -5,6 +5,7 @@ export const HAND_TRACKING_FRAME_WIDTH = 320;
|
||||
export const HAND_TRACKING_FRAME_HEIGHT = 240;
|
||||
export const HAND_TRACKING_TARGET_FPS = 10;
|
||||
export const HAND_TRACKING_JPEG_QUALITY = 0.55;
|
||||
export const HAND_TRACKING_CAMERA_TIMEOUT_MS = 8_000;
|
||||
export const HAND_TRACKING_RESPONSE_TIMEOUT_MS = 1_500;
|
||||
|
||||
export function getHandTrackingWsUrl(): string {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import {
|
||||
HAND_TRACKING_CAMERA_TIMEOUT_MS,
|
||||
HAND_TRACKING_FRAME_HEIGHT,
|
||||
HAND_TRACKING_FRAME_WIDTH,
|
||||
HAND_TRACKING_JPEG_QUALITY,
|
||||
@@ -29,6 +30,32 @@ function getBase64Payload(dataUrl: string): string {
|
||||
return dataUrl.slice(dataUrl.indexOf(",") + 1);
|
||||
}
|
||||
|
||||
function getCameraStreamWithTimeout(
|
||||
constraints: MediaStreamConstraints,
|
||||
): Promise<MediaStream> {
|
||||
let didTimeout = false;
|
||||
const streamPromise = navigator.mediaDevices.getUserMedia(constraints);
|
||||
|
||||
const timeoutPromise = new Promise<never>((_, reject) => {
|
||||
window.setTimeout(() => {
|
||||
didTimeout = true;
|
||||
reject(
|
||||
new Error(
|
||||
"Camera request timed out. Restart Arc or check camera permissions for localhost:5173.",
|
||||
),
|
||||
);
|
||||
}, HAND_TRACKING_CAMERA_TIMEOUT_MS);
|
||||
});
|
||||
|
||||
streamPromise.then((stream) => {
|
||||
if (didTimeout) {
|
||||
stream.getTracks().forEach((track) => track.stop());
|
||||
}
|
||||
});
|
||||
|
||||
return Promise.race([streamPromise, timeoutPromise]);
|
||||
}
|
||||
|
||||
export function useRemoteHandTracking({
|
||||
enabled,
|
||||
websocketUrl = getHandTrackingWsUrl(),
|
||||
@@ -116,13 +143,13 @@ export function useRemoteHandTracking({
|
||||
|
||||
setSnapshot({
|
||||
hands: [],
|
||||
status: "connecting",
|
||||
status: "requesting_camera",
|
||||
serverStatus: null,
|
||||
error: null,
|
||||
});
|
||||
|
||||
try {
|
||||
const stream = await navigator.mediaDevices.getUserMedia({
|
||||
const stream = await getCameraStreamWithTimeout({
|
||||
video: {
|
||||
width: HAND_TRACKING_FRAME_WIDTH,
|
||||
height: HAND_TRACKING_FRAME_HEIGHT,
|
||||
@@ -136,12 +163,27 @@ export function useRemoteHandTracking({
|
||||
return;
|
||||
}
|
||||
|
||||
setSnapshot((current) => ({
|
||||
...current,
|
||||
status: "starting_camera",
|
||||
}));
|
||||
|
||||
const video = document.createElement("video");
|
||||
video.muted = true;
|
||||
video.playsInline = true;
|
||||
video.srcObject = stream;
|
||||
await video.play();
|
||||
|
||||
if (cancelled) {
|
||||
stream.getTracks().forEach((track) => track.stop());
|
||||
return;
|
||||
}
|
||||
|
||||
setSnapshot((current) => ({
|
||||
...current,
|
||||
status: "connecting_server",
|
||||
}));
|
||||
|
||||
const canvas = document.createElement("canvas");
|
||||
canvas.width = HAND_TRACKING_FRAME_WIDTH;
|
||||
canvas.height = HAND_TRACKING_FRAME_HEIGHT;
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
:root {
|
||||
color-scheme: dark;
|
||||
font-family: Inter;
|
||||
font-family: Helvetica, Arial, sans-serif;
|
||||
}
|
||||
|
||||
html,
|
||||
@@ -83,7 +83,7 @@ canvas {
|
||||
.hand-tracking-overlay {
|
||||
position: fixed;
|
||||
right: 16px;
|
||||
bottom: 16px;
|
||||
bottom: 132px;
|
||||
z-index: 20;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
@@ -10,6 +10,9 @@ export interface HandTrackingHand {
|
||||
|
||||
export type HandTrackingStatus =
|
||||
| "idle"
|
||||
| "requesting_camera"
|
||||
| "starting_camera"
|
||||
| "connecting_server"
|
||||
| "connecting"
|
||||
| "connected"
|
||||
| "disconnected"
|
||||
|
||||
Reference in New Issue
Block a user