This commit is contained in:
49lf
2024-09-13 16:45:36 -04:00
parent 6dc87aa4fe
commit b11772b27c
6 changed files with 305 additions and 164 deletions

View File

@ -1,4 +1,4 @@
import { MouseEventHandler, useEffect, useRef, useState, MutableRefObject } from 'react'
import { MouseEventHandler, useEffect, useRef, useState, MutableRefObject, useCallback } from 'react'
import { useAppState } from 'AppState'
import { createMachine, createActor, setup } from 'xstate'
import { createActorContext } from '@xstate/react'
@ -26,11 +26,15 @@ export const EngineStream = () => {
const [clickCoords, setClickCoords] = useState<{ x: number; y: number }>()
const { setAppState } = useAppState()
const videoRef = useRef<HTMLVideoElement>(null)
const canvasRef = useRef<HTMLCanvasElement>(null)
const { overallState } = useNetworkContext()
const { auth, settings } = useSettingsAuthContext()
const { settings } = useSettingsAuthContext()
const settingsEngine = {
theme: settings.context.app.theme.current,
enableSSAO: settings.context.app.enableSSAO.current,
highlightEdges: settings.context.modeling.highlightEdges.current,
showScaleGrid: settings.context.modeling.showScaleGrid.current,
}
const {
state: modelingMachineState,
@ -41,17 +45,7 @@ export const EngineStream = () => {
const engineStreamActor = useEngineStreamContext.useActorRef()
const engineStreamState = engineStreamActor.getSnapshot()
useEffect(() => {
engineStreamActor.send({ type: EngineStreamTransition.SetContextProperty, value: { authToken: auth?.context?.token } } )
}, [])
useEffect(() => {
engineStreamActor.send({ type: EngineStreamTransition.SetContextProperty, value: { videoRef } })
}, [videoRef.current])
useEffect(() => {
engineStreamActor.send({ type: EngineStreamTransition.SetContextProperty, value: { canvasRef } })
}, [canvasRef.current])
const streamIdleMode = settings.context.app.streamIdleMode.current
useEffect(() => {
let timestampNext: number | null = null
@ -63,8 +57,22 @@ export const EngineStream = () => {
// so those exception people don't see.
const REASONABLE_TIME_TO_REFRESH_STREAM_SIZE = 200
const configure = () => {
engineStreamActor.send({ type: EngineStreamTransition.StartOrReconfigureEngine, modelingMachineActorSend, settings, setAppState })
engineStreamActor.send({
type: EngineStreamTransition.StartOrReconfigureEngine,
modelingMachineActorSend,
settings: settingsEngine,
setAppState,
// It's possible a reconnect happens as we drag the window :')
onMediaStream(mediaStream: MediaStream) {
engineStreamActor.send({
type: EngineStreamTransition.SetMediaStream,
mediaStream
})
}
})
}
// setTimeout is avoided here because there is no need to create and destroy
@ -82,7 +90,9 @@ export const EngineStream = () => {
totalDelta = 0
timestampLast = null
needsResize = false
configure()
window.requestAnimationFrame(() => {
configure()
})
} else {
window.requestAnimationFrame(() => {
needsResize = true
@ -93,12 +103,116 @@ export const EngineStream = () => {
}
window.addEventListener('resize', onResize)
const play = () => {
engineStreamActor.send({
type: EngineStreamTransition.Play,
})
}
engineCommandManager.addEventListener(
EngineCommandManagerEvents.SceneReady,
play
)
return () => {
window.removeEventListener('resize', onResize)
}
}, [settings])
engineCommandManager.removeEventListener(
EngineCommandManagerEvents.SceneReady,
play
)
const isNetworkOkay =
}
}, [])
// When the video and canvas element references are set, start the engine.
useEffect(() => {
if (engineStreamState.context.canvasRef.current && engineStreamState.context.videoRef.current) {
engineStreamActor.send({
type: EngineStreamTransition.StartOrReconfigureEngine,
modelingMachineActorSend,
settings: settingsEngine,
setAppState,
onMediaStream(mediaStream: MediaStream) {
engineStreamActor.send({
type: EngineStreamTransition.SetMediaStream,
mediaStream
})
}
})
}
}, [engineStreamState.context.canvasRef.current, engineStreamState.context.videoRef.current])
// On settings change, reconfigure the engine.
useEffect(() => {
engineStreamActor.send({ type: EngineStreamTransition.StartOrReconfigureEngine, modelingMachineActorSend, settings: settingsEngine, setAppState })
}, [settings.context])
const IDLE_TIME_MS = 1000 * 6
// When streamIdleMode is changed, setup or teardown the timeouts
const timeoutId = useRef<ReturnType<typeof setTimeout> | undefined>(undefined)
useEffect(() => {
// If timeoutId is falsey, then reset it if steamIdleMode is true
if (streamIdleMode && !timeoutId.current) {
timeoutId.current = setTimeout(() => {
engineStreamActor.send({ type: EngineStreamTransition.Pause })
}, IDLE_TIME_MS)
} else if (!streamIdleMode) {
clearTimeout(timeoutId.current)
timeoutId.current = undefined
}
}, [streamIdleMode])
useEffect(() => {
if (!timeoutId.current) return
const onAnyInput = () => {
// Just in case it happens in the middle of the user turning off
// idle mode.
if (!streamIdleMode) {
clearTimeout(timeoutId.current)
return
}
if (engineStreamState.value === EngineStreamState.Paused) {
engineStreamActor.send({
type: EngineStreamTransition.StartOrReconfigureEngine,
modelingMachineActorSend,
settings: settingsEngine,
setAppState,
onMediaStream(mediaStream: MediaStream) {
engineStreamActor.send({
type: EngineStreamTransition.SetMediaStream,
mediaStream
})
}
})
}
clearTimeout(timeoutId.current)
timeoutId.current = setTimeout(() => {
engineStreamActor.send({ type: EngineStreamTransition.Pause })
}, IDLE_TIME_MS)
}
window.document.addEventListener('keydown', onAnyInput)
window.document.addEventListener('mousemove', onAnyInput)
window.document.addEventListener('mousedown', onAnyInput)
window.document.addEventListener('scroll', onAnyInput)
window.document.addEventListener('touchstart', onAnyInput)
return () => {
window.document.removeEventListener('keydown', onAnyInput)
window.document.removeEventListener('mousemove', onAnyInput)
window.document.removeEventListener('mousedown', onAnyInput)
window.document.removeEventListener('scroll', onAnyInput)
window.document.removeEventListener('touchstart', onAnyInput)
}
}, [streamIdleMode, engineStreamState.value])
const isNetworkOkay =
overallState === NetworkHealthState.Ok ||
overallState === NetworkHealthState.Weak
@ -106,7 +220,7 @@ export const EngineStream = () => {
const handleMouseUp: MouseEventHandler<HTMLDivElement> = (e) => {
if (!isNetworkOkay) return
if (!videoRef.current) return
if (!engineStreamState.context.videoRef.current) return
modelingMachineActorSend({
type: 'Set context',
@ -120,7 +234,7 @@ export const EngineStream = () => {
if (!modelingMachineContext.store?.didDragInStream && btnName(e).left) {
sendSelectEventToEngine(
e,
videoRef.current,
engineStreamState.context.videoRef.current,
modelingMachineContext.store?.streamDimensions
)
}
@ -136,7 +250,7 @@ export const EngineStream = () => {
const handleMouseDown: MouseEventHandler<HTMLDivElement> = (e) => {
if (!isNetworkOkay) return
if (!videoRef.current) return
if (!engineStreamState.context.videoRef.current) return
if (modelingMachineState.matches('Sketch')) return
if (modelingMachineState.matches('Sketch no face')) return
@ -144,7 +258,7 @@ export const EngineStream = () => {
const { x, y } = getNormalisedCoordinates({
clientX: e.clientX,
clientY: e.clientY,
el: videoRef.current,
el: engineStreamState.context.videoRef.current,
...modelingMachineContext.store?.streamDimensions,
})
@ -188,60 +302,20 @@ export const EngineStream = () => {
onContextMenuCapture={(e) => e.preventDefault()}
>
<video
ref={videoRef}
key={engineStreamActor.id + 'video'}
ref={engineStreamState.context.videoRef}
controls={false}
onMouseMoveCapture={handleMouseMove}
className="w-full cursor-pointer h-full"
className="cursor-pointer"
disablePictureInPicture
id="video-stream"
/>
<canvas ref={canvasRef} className="w-full cursor-pointer h-full" id="freeze-frame">No canvas support</canvas>
<canvas
key={engineStreamActor.id + 'canvas'}
ref={engineStreamState.context.canvasRef} className="cursor-pointer" id="freeze-frame">No canvas support</canvas>
<ClientSideScene
cameraControls={settings.context.modeling.mouseControls.current}
/>
{(engineStreamState.value === EngineStreamState.Paused ||
engineStreamState.value === EngineStreamState.Resuming) && (
<div className="text-center absolute inset-0">
<div
className="flex flex-col items-center justify-center h-screen"
data-testid="paused"
>
<div className="border-primary border p-2 rounded-sm">
<svg
width="8"
height="12"
viewBox="0 0 8 12"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M2 12V0H0V12H2ZM8 12V0H6V12H8Z"
fill="var(--primary)"
/>
</svg>
</div>
<p className="text-base mt-2 text-primary bold">
{engineStreamState.value === EngineStreamState.Paused && 'Paused'}
{engineStreamState.value === EngineStreamState.Resuming && 'Resuming'}
</p>
</div>
</div>
)}
{(!isNetworkOkay || isLoading) && (
<div className="text-center absolute inset-0">
<Loading>
{!isNetworkOkay && !isLoading ? (
<span data-testid="loading-stream">Stream disconnected...</span>
) : (
!isLoading && (
<span data-testid="loading-stream">Loading stream...</span>
)
)}
</Loading>
</div>
)}
</div>
)
}