Skip to content

Commit

Permalink
Demo and Debug pages
Browse files Browse the repository at this point in the history
danielzotti committed Mar 23, 2024
1 parent 4a0f919 commit c185e6d
Showing 14 changed files with 898 additions and 556 deletions.
13 changes: 13 additions & 0 deletions src/app/debug/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
"use client";
import ViewContextLayout from "@/layouts/view-context-layout";
import {ReactNode} from "react";

export default function DemoLayout({
children,
}: Readonly<{
children: ReactNode;
}>) {
return <ViewContextLayout>
{children}
</ViewContextLayout>
}
71 changes: 71 additions & 0 deletions src/app/debug/page.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
.container {
scroll-snap-type: y mandatory;
max-height: 100vh;
overflow: auto;
&:global(.eyes) {
cursor:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='40' height='48' viewport='0 0 100 100' style='fill:black;font-size:24px;'><text y='50%'>👀</text></svg>") 16 0,auto;
}
}
.contentContainer {
scroll-snap-align: center;
height: 100vh;
display: flex;
align-items: center;
}
.content {
margin: 30px auto;
display: flex;
position: relative;
border: 1px solid red;
aspect-ratio: 1;
perspective: 10cm;
overflow: hidden;

@media(orientation: portrait) {
width: 90dvw;
}
@media(orientation: landscape) {
height: 90dvh;
}
}

.webcamControls {
position: fixed;
top: 0;
left: 0;
}

.video {
transform: scaleX(-1); // Selfie mode
position: fixed;
right: 8px;
bottom: 8px;
width: 300px;
cursor: pointer;
transition: opacity 0.25s ease-in-out, width 0.25s ease-in-out;

&:global(.tiny) {
width: 100px;
transform: scaleX(1);
}

&:global(.hide) {
pointer-events: none;
opacity: 0;
width: 50px;
}
}

.videoMini {
position: fixed;
right: 8px;
bottom: 8px;
display: block;
font-size: 30px;
background: black;
z-index: 100;
border-radius: 10px;
overflow: hidden;
cursor: pointer;
}

235 changes: 235 additions & 0 deletions src/app/debug/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,235 @@
"use client";
import {useMouse} from "@/hooks/useMouse";
import {Box3d} from "@/components/box-3d/box-3d";
import {useWebcam} from "@/hooks/useWebcam";

import {ViewContext} from "@/providers/ViewContextProvider";
import {useContext, useMemo, useState} from "react";

import styles from "./page.module.scss";

interface State {
// mouse
isMouseEnabled: boolean;
mousePosition: { x: number, y: number };
screenWidth: number;
screenHeight: number;
// debug
isDebugEnabled: boolean;
// UI
isSettingsVisible: boolean;
}

export default function HomePage() {
const {viewState} = useContext(ViewContext);

const {enableMouse, disableMouse} = useMouse();

const {
state: webcamState, videoRef, isDetectingVideo,
disableDetectingVideo,
enableDetectingVideo, handleVideoLoaded, enableWebcam, disableWebcam,
showWebcam,
hideWebcam
} = useWebcam();

const [state, setState] = useState<State>({
// mouse
isMouseEnabled: false,
screenWidth: 1,
screenHeight: 1,
mousePosition: {x: 0, y: 0},
// debug
isDebugEnabled: false,
// UI
isSettingsVisible: true
});

const enableSettings = () => {
setState((s) => ({
...s,
isSettingsVisible: true,
}));
};

const disableSettings = () => {
setState((s) => ({
...s,
isSettingsVisible: false,
}));
}

const enableDebug = () => {
setState((s) => ({
...s,
isDebugEnabled: true,
}));
};

const disableDebug = () => {
setState((s) => ({
...s,
isDebugEnabled: false,
}));
}

const activateMouse = () => {
setState((s) => ({
...s,
isMouseEnabled: true,
}));
enableMouse();
}
const deactivateMouse = () => {
setState((s) => ({
...s,
isMouseEnabled: false,
mousePosition: {x: 0, y: 0},
}));
disableMouse();
}

const currentState = useMemo(() => ({
mouse: {
x: state.mousePosition.x.toFixed(2),
y: state.mousePosition.y.toFixed(2),
},
eyes: {
x: webcamState.eyesPosition.x.toFixed(2),
y: webcamState.eyesPosition.y.toFixed(2),
},
view: {
x: viewState.x.toFixed(2),
y: viewState.y.toFixed(2),
z: viewState.z?.toFixed(2),
},
webcam: {
height: webcamState.webcamHeight,
width: webcamState.webcamWidth
},
screen: {
height: state.screenHeight,
width: state.screenWidth
}
}), [state.mousePosition.x, state.mousePosition.y, state.screenHeight, state.screenWidth, viewState.x, viewState.y, viewState.z, webcamState.eyesPosition.x, webcamState.eyesPosition.y, webcamState.webcamHeight, webcamState.webcamWidth]);

return (
<>
<main className={`${styles.container} ${state.isMouseEnabled ? 'eyes' : ''}`}>
<div className={styles.contentContainer}>
<div className={styles.content}>
<Box3d layer={-100}></Box3d>
<Box3d layer={-50}></Box3d>
<Box3d layer={0}></Box3d>
<Box3d layer={20}></Box3d>
<Box3d layer={50}></Box3d>
</div>
</div>
<div className={styles.contentContainer}>
<div className={styles.content}>
<Box3d layer={0}></Box3d>
<Box3d layer={10}></Box3d>
<Box3d layer={20}></Box3d>
<Box3d layer={50}></Box3d>
<Box3d layer={100}></Box3d>
</div>
</div>
<div className={styles.contentContainer}>
<div className={styles.content}>
<Box3d layer={-100}></Box3d>
<Box3d layer={-50}></Box3d>
<Box3d layer={-20}></Box3d>
<Box3d layer={-10}></Box3d>
<Box3d layer={0}></Box3d>
</div>
</div>

</main>


<div className={styles.webcamControls}>
{!state.isSettingsVisible && <button onClick={enableSettings} style={{fontSize: "30px"}}>⚙️</button>}
{state.isSettingsVisible &&
<button onClick={disableSettings} style={{fontSize: "30px"}}>&times;</button>}

{state.isSettingsVisible && <>
{/*DEBUG*/}
{!state.isDebugEnabled &&
<button onClick={enableDebug} className="btn-inverse">
Enable Debug
</button>}
{state.isDebugEnabled && <button onClick={disableDebug} className="btn-inverse">
Disable Debug
</button>}

{/*MOUSE*/}
{!state.isMouseEnabled && !webcamState.isWebcamEnabled &&
<button onClick={activateMouse}>
Enable Mouse
</button>}
{state.isMouseEnabled && <button onClick={deactivateMouse}>
Disable Mouse
</button>}

{/*WEBCAM*/}
{!state.isMouseEnabled && webcamState.hasWebcamSupport && <>
{!webcamState.isWebcamEnabled && <button onClick={enableWebcam}>
Enable Webcam
</button>}
{webcamState.isWebcamEnabled &&
<button onClick={disableWebcam}>
Disable Webcam
{!webcamState.isVideoLoaded && <small>&nbsp;(Loading video...)</small>}
</button>}
</>}

{webcamState.hasWebcamSupport === false &&
<p>No webcam support, I&apos;m sorry</p>}

{webcamState.isWebcamEnabled && <>
{!isDetectingVideo.current && webcamState.isVideoLoaded &&
<button
onClick={enableDetectingVideo}
disabled={webcamState.isModelLoading}
>
<span>
Enable 3D
</span>
{webcamState.isModelLoading && <small>&nbsp;(loading...)</small>}
</button>
}
{isDetectingVideo.current &&
<button
onClick={disableDetectingVideo}
>
Disable 3D
</button>}
</>}

{state.isDebugEnabled &&
<pre>State: {JSON.stringify(currentState, null, 2)}</pre>}
{/*<pre>State: {JSON.stringify(state, null, 2)}</pre>*/}
</>}


</div>

{webcamState.hasWebcamSupport && webcamState.isWebcamEnabled && <>
<video
className={`${styles.video} ${!webcamState.isWebcamVisible ? 'hide' : ''}`}
onClick={() => hideWebcam()}
autoPlay
muted
ref={videoRef}
onLoadedData={handleVideoLoaded}
style={{display: webcamState.isVideoLoaded ? 'block' : 'none'}}
></video>

{!webcamState.isWebcamVisible &&
<button onClick={() => showWebcam()}
className={styles.videoMini}>📷</button>}
</>}

</>
);
}
13 changes: 13 additions & 0 deletions src/app/demo/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
"use client";
import ViewContextLayout from "@/layouts/view-context-layout";
import {ReactNode} from "react";

export default function DemoLayout({
children,
}: Readonly<{
children: ReactNode;
}>) {
return <ViewContextLayout>
{children}
</ViewContextLayout>
}
38 changes: 38 additions & 0 deletions src/app/demo/page.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
.container {
display: block;
height: 100dvh;
width: 100dvw;
overflow: hidden;
position: absolute;
perspective: 10cm;
}

.settings {
position: fixed;
z-index: 10000;
bottom: 0;
left: 0;
right: 0;
}

.video {
visibility: hidden;
position: fixed;
bottom: 0;
right: 0;
max-width: 100%;
}

.webcamWarning {
position: fixed;
z-index: 10000;
top: 0;
left: 0;
padding: 10px;
margin: 10px;
display: inline-block;
background-color: #ffd54d;
color: black;
border: 1px solid #d36c04;
border-radius: 5px;
}
145 changes: 145 additions & 0 deletions src/app/demo/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
"use client";
import {Box3d} from "@/components/box-3d/box-3d";
import {useMouse} from "@/hooks/useMouse";
import {usePermissions} from "@/hooks/usePermissions";
import {useWebcam} from "@/hooks/useWebcam";
import {ViewState} from "@/models/view-state.models";
import {ViewContext} from "@/providers/ViewContextProvider";
import {CSSProperties, useCallback, useContext, useEffect, useMemo, useRef, useState} from "react";

import styles from "./page.module.scss";

const Element = ({layer, style}: { layer: number; style: CSSProperties }) => {
const {viewState: {x, y, z}} = useContext(ViewContext);

const transform = useMemo(() => {
const newLayer = layer ?? 0;
const newZ = (z ?? 1) * newLayer;
const newX = -(x ?? 0) * newZ;
const newY = -(y ?? 0) * newZ;
return `translate3d(${newX}%, ${newY}%, ${newZ}px)`;
}, [layer, x, y, z]);

return <div style={{
position: "absolute",
backgroundColor: "red",
display: "flex",
alignContent: "center",
justifyContent: "center",
alignItems: "center",
opacity: 0.5,
...style,
transform,
}}>X</div>;
}

export default function DemoPage() {
const {permissionState, handlePermission} = usePermissions("camera" as PermissionName);
const [isSettingsVisible, setIsSettingsVisible] = useState(false);

// const {enableMouse, disableMouse} = useMouse();
// useEffect(() => {
// enableMouse();
// }, [disableMouse, enableMouse]);

const {
videoRef,
state: webcamState,
hideWebcam,
handleVideoLoaded,
isDetectingVideo,
enableWebcam,
disableWebcam,
enableDetectingVideo,
disableDetectingVideo,
setHasWebcamSupport
} = useWebcam();

useEffect(() => {
enableWebcam();

return () => {
disableWebcam();
disableDetectingVideo();
}
}, [disableDetectingVideo, disableWebcam, enableWebcam]);

useEffect(() => {
if (permissionState === "granted") {
setHasWebcamSupport(true);
enableWebcam();
}
}, [disableWebcam, enableWebcam, permissionState, setHasWebcamSupport]);

return (
<>
<div className={styles.settings}>

{!isSettingsVisible && permissionState === "granted" &&
<button onClick={() => setIsSettingsVisible(true)}>⚙️</button>}

{isSettingsVisible && permissionState === "granted" && <div>
<button onClick={() => setIsSettingsVisible(false)}>&times;️</button>

{/*3D*/}
{isDetectingVideo.current &&
<button onClick={() => disableDetectingVideo()}>Disable 3D</button>}
{!isDetectingVideo.current && webcamState.isWebcamEnabled &&
<button onClick={() => enableDetectingVideo()}>Enable 3D</button>}
</div>}

</div>

{permissionState !== "granted" &&
<p className={styles.webcamWarning}>Please, grant webcam permission manually on your browser. Current
permission: {permissionState}</p>}


<main className={`${styles.container}`}>

<Element layer={0} style={{
top: "45%",
right: "45%",
height: "10%",
width: "10%",
backgroundColor: "blue"
}}></Element>
<Element layer={100} style={{
top: "45%",
right: "45%",
height: "10%",
width: "10%",
backgroundColor: "lightblue"
}}></Element>

<Element layer={100} style={{
top: 0,
left: "-10%",
height: "200%",
width: "20%",
backgroundColor: "green"
}}></Element>

<Element layer={100} style={{
"top": 0,
"left": "40%",
"height": "50%",
"width": "20%",
"backgroundColor": "yellow"
}}></Element>

</main>

{webcamState.hasWebcamSupport && webcamState.isWebcamEnabled &&
<video
className={styles.video}
onClick={() => hideWebcam()}
autoPlay
muted
ref={videoRef}
onLoadedData={handleVideoLoaded}
></video>
}
</>
);
}
16 changes: 16 additions & 0 deletions src/app/globals.scss
Original file line number Diff line number Diff line change
@@ -2,3 +2,19 @@ html, body {
margin: 0;
padding:0;
}

button {
padding: 8px 16px;
background: black;
border: 0 solid gray;
color: white;
border-radius: 4px;
cursor: pointer;
margin: 5px;

&.btn-inverse {
background: white;
color: black;
border: 1px solid black;
}
}
470 changes: 0 additions & 470 deletions src/app/page-content.tsx

This file was deleted.

89 changes: 9 additions & 80 deletions src/app/page.module.scss
Original file line number Diff line number Diff line change
@@ -1,86 +1,15 @@
.container {
scroll-snap-type: y mandatory;
max-height: 100vh;
overflow: auto;
&:global(.eyes) {
cursor:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='40' height='48' viewport='0 0 100 100' style='fill:black;font-size:24px;'><text y='50%'>👀</text></svg>") 16 0,auto;
}
}
.contentContainer {
scroll-snap-align: center;
height: 100vh;
display: flex;
align-items: center;
}
.content {
margin: 30px auto;
display: flex;
position: relative;
border: 1px solid red;
aspect-ratio: 1;
perspective: 10cm;
overflow: hidden;

@media(orientation: portrait) {
width: 90dvw;
}
@media(orientation: landscape) {
height: 90dvh;
}
}

.webcamControls {
position: fixed;
top: 0;
left: 0;
}

.video {
transform: scaleX(-1); // Selfie mode
position: fixed;
right: 8px;
bottom: 8px;
width: 300px;
cursor: pointer;
transition: opacity 0.25s ease-in-out, width 0.25s ease-in-out;

&:global(.tiny) {
width: 100px;
transform: scaleX(1);
}

&:global(.hide) {
pointer-events: none;
opacity: 0;
width: 50px;
}
}

.videoMini {
position: fixed;
right: 8px;
bottom: 8px;
display: block;
font-size: 30px;
background: black;
z-index: 100;
border-radius: 10px;
overflow: hidden;
cursor: pointer;
}
text-align: center;

.button {
padding: 8px 16px;
background: black;
border: 0 solid gray;
color: white;
border-radius: 4px;
cursor: pointer;
margin: 5px;
ul {
list-style: none;
padding: 0;
display: flex;
gap: 10px;
justify-content: center;
li {

&:global(.alternate) {
background: white;
color: black;
border: 1px solid black;
}
}
}
18 changes: 12 additions & 6 deletions src/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
"use client";
import Link from "next/link";
import styles from './page.module.scss'

import {PageContent} from "@/app/page-content";
import {ViewContextProvider} from "@/providers/ViewContextProvider";
export default function HomePage() {
return (
<div className={styles.content}>
<h1>Fake 3D website</h1>

export default function Home() {
return <ViewContextProvider>
<PageContent/>
</ViewContextProvider>;
<ul>
<li><Link href={"debug"}>Debug</Link></li>
<li><Link href={"demo"}>Demo</Link></li>
</ul>
</div>
);
}
55 changes: 55 additions & 0 deletions src/hooks/useMouse.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import {ViewState} from "@/models/view-state.models";
import {ViewContext} from "@/providers/ViewContextProvider";
import {useCallback, useContext, useEffect} from "react";

export const useMouse = () => {
const {setViewState} = useContext(ViewContext);

const detectMousePosition = useCallback((e: MouseEvent) => {
setViewState(s => ({
...s,
x: ((e.clientX / window.innerWidth) * 2) - 1,
y: ((e.clientY / window.innerHeight) * 2) - 1,
z: s.z === undefined ? 1 : s.z > 0 ? s.z : 0,
viewport: {
width: window.innerWidth,
height: window.innerHeight
}
}))
}, [setViewState])

const detectMouseWheel = useCallback((e: WheelEvent) => {
e.preventDefault();
const deltaZ = e.deltaY / window.innerHeight;

setViewState((s: ViewState) => ({
...s,
z: s.z !== undefined && s.z - deltaZ > 0 ? s.z - deltaZ : 0
}));
}, [setViewState]);

const enableMouse = useCallback(() => {
window.addEventListener('mousemove', detectMousePosition)
window.addEventListener('wheel', detectMouseWheel, {passive: false}) // See https://www.uriports.com/blog/easy-fix-for-unable-to-preventdefault-inside-passive-event-listener/
}, [detectMousePosition, detectMouseWheel])

const disableMouse = useCallback(() => {
setViewState({
x: 0,
y: 0
});
window.removeEventListener('mousemove', detectMousePosition)
window.removeEventListener('wheel', detectMouseWheel)
}, [detectMousePosition, detectMouseWheel, setViewState]);

useEffect(() => {
return () => {
disableMouse();
}
}, []);

return {
enableMouse,
disableMouse
}
}
23 changes: 23 additions & 0 deletions src/hooks/usePermissions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import {useCallback, useEffect, useState} from "react";

export const usePermissions = (permissionName: PermissionName) => {
const [permissionState, setPermissionState] = useState<PermissionState>()

const handlePermission = useCallback(() => {
navigator.permissions.query({name: permissionName}).then((result) => {
setPermissionState(result.state);
result.addEventListener("change", () => {
setPermissionState(result.state);
});
});
}, [permissionName]);

useEffect(() => {
handlePermission();
}, [handlePermission]);

return {
handlePermission,
permissionState,
}
}
253 changes: 253 additions & 0 deletions src/hooks/useWebcam.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,253 @@
import {ViewState} from "@/models/view-state.models";
import {ViewContext} from "@/providers/ViewContextProvider";
import {createDetector, type FaceDetector, SupportedModels} from "@tensorflow-models/face-detection";
import type {MediaPipeFaceDetectorMediaPipeModelConfig} from "@tensorflow-models/face-detection/dist/mediapipe/types";
import {useCallback, useContext, useEffect, useRef, useState} from "react";

interface WebcamState {
hasWebcamSupport?: boolean;
isWebcamEnabled: boolean;
isWebcamVisible: boolean;
isVideoLoaded: boolean;
isModelLoading: boolean;
isVideoPictureInPicture: boolean;
eyesPosition: { x: number, y: number };
webcamWidth: number;
webcamHeight: number;
}

export const useWebcam = () => {

const {viewState, setViewState} = useContext(ViewContext);

const videoRef = useRef<HTMLVideoElement>(null);
const webcamStream = useRef<MediaStream>();
const detector = useRef<FaceDetector>()
const isDetectingVideo = useRef<boolean>(false);

const [state, setState] = useState<WebcamState>({
hasWebcamSupport: undefined,
isWebcamEnabled: false,
isWebcamVisible: true,
isVideoLoaded: false,
isModelLoading: false,
isVideoPictureInPicture: false,
webcamHeight: 480,
webcamWidth: 640,
eyesPosition: {x: 0, y: 0},
});

const showWebcam = useCallback(() => {
setState((s) => ({
...s,
isWebcamVisible: true,
}));
}, [])

const hideWebcam = useCallback(() => {
setState((s) => ({
...s,
isWebcamVisible: false,
}));
}, [])

const setHasWebcamSupport = useCallback((hasWebcamSupport: boolean) => {
setState((s) => ({
...s,
hasWebcamSupport,
}));
}, [])

const enableWebcam = useCallback(() => {
setState((s) => ({
...s,
isWebcamEnabled: true,
}));

// Activate the webcam stream.
window.navigator.mediaDevices
.getUserMedia({
video: {
facingMode: "user", // front camera
frameRate: {ideal: 25, max: 25}, // more than 25 fps not needed
},
audio: false,
})
.then(function (stream) {
webcamStream.current = stream;
if (videoRef.current) {
videoRef.current.srcObject = stream;
}
const {width: webcamWidth, height: webcamHeight} = stream.getTracks()[0].getSettings();
setState((s) => ({
...s,
webcamWidth: webcamWidth ?? 640,
webcamHeight: webcamHeight ?? 480,
isWebcamVisible: true,
}));
})
.catch(function (err) {
console.error(err)
setState((s) => ({
...s,
isWebcamEnabled: false,
hasWebcamSupport: false,
}));
});
}, [])

const disableDetectingVideo = useCallback(() => {
isDetectingVideo.current = false;
}, [])

const disableWebcam = useCallback(() => {
webcamStream.current?.getTracks().forEach((t) => t.stop());
setState((s) => ({
...s,
isWebcamEnabled: false,
isWebcamVisible: false,
eyesPosition: {x: 0, y: 0},
}));
setViewState({
x: 0,
y: 0
});
disableDetectingVideo();
}, [disableDetectingVideo, setViewState])

const detectVideo = useCallback(async () => {
if (!videoRef.current) {
console.debug("no video");
return;
}

if (!detector.current) {
console.debug("no detector");
return;
}

const estimationConfig = {flipHorizontal: true};
const faces = await detector.current.estimateFaces(videoRef.current, estimationConfig);

if (faces?.at(0)?.keypoints) {
let position: ViewState = {x: 0, y: 0};

const eyesPosition = faces
?.at(0)
?.keypoints.filter(({name}) => ["rightEye", "leftEye"].includes(name ?? "")) || [];

if (eyesPosition.length === 2) {
// center
position = {
x: (eyesPosition[1].x + eyesPosition[0].x) / 2,
y: (eyesPosition[1].y + eyesPosition[0].y) / 2,
z: Math.abs(eyesPosition[1].x - eyesPosition[0].x) / state.webcamWidth, // NB: This should definitely be improved (it works only for horizontal eyes)
}
}
if (eyesPosition.length === 1) {
// right or left eye
position = {
x: eyesPosition[0].x,
y: eyesPosition[0].y,
}
}

setState((s) => ({
...s,
eyesPosition: {
x: position.x,
y: position.y,
},
}));
setViewState({
x: ((position.x / state.webcamWidth) * 2) - 1,
y: ((position.y / state.webcamHeight) * 2) - 1,
...position.z && {z: position.z * 10} // 10 is a magic number to make the effect more visible
})
// TODO - improvement: point between 2 eyes and not just right eye
// TODO - improvement: calculate Z based on distance to the webcam -> E.G. (rightX - leftX) / webcam width
}

if (isDetectingVideo.current) {
// requestAnimationFrame(() => {
// detectVideo();
// });
setTimeout(() => {
detectVideo();
}, 100);
} else {
setState((s) => ({
...s,
eyesPosition: {x: 0, y: 0},
}));
setViewState({
x: 0,
y: 0
});
}
}, [])


const startVideoDetection = useCallback(async () => {
setState((s) => ({
...s,
isModelLoading: true,
}));

const model = SupportedModels.MediaPipeFaceDetector;
const detectorConfig: MediaPipeFaceDetectorMediaPipeModelConfig = {
runtime: "mediapipe",
solutionPath: "models/face_detection", // NB: is public/models/face_detection
};

createDetector(model, detectorConfig)
.then((d) => {
setState((s) => ({
...s,
isModelLoading: false,
}));
isDetectingVideo.current = true;
detector.current = d;
detectVideo();
})
.catch((e) => {
console.error("Problem on loading the model", e);
});
}, [detectVideo]);

const enableDetectingVideo = useCallback(async () => {
void startVideoDetection();
}, [startVideoDetection])

const handleVideoLoaded = useCallback(() => {
setState((s) => ({
...s,
isVideoLoaded: true,
}));
enableDetectingVideo(); // Comment/Uncomment to disable/enable detecting video once enable webcam is clicked
}, [enableDetectingVideo]);

useEffect(() => {
setState((s) => ({
...s,
hasWebcamSupport: !!navigator.mediaDevices.getUserMedia,
}));
return () => {
disableWebcam();
}
}, [disableWebcam]);

return {
state,
setHasWebcamSupport,
videoRef,
isDetectingVideo,
disableDetectingVideo,
enableDetectingVideo,
enableWebcam,
disableWebcam,
handleVideoLoaded,
showWebcam,
hideWebcam
}
}
15 changes: 15 additions & 0 deletions src/layouts/view-context-layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
"use client";

import {ViewContextProvider} from "@/providers/ViewContextProvider";
import {ReactNode} from "react";

export default function ViewContextLayout({
children,
}: Readonly<{
children: ReactNode;
}>) {

return <ViewContextProvider>
{children}
</ViewContextProvider>;
}

0 comments on commit c185e6d

Please sign in to comment.