diff --git a/packages/apps/client/src/hooks/useControllerMessages.ts b/packages/apps/client/src/hooks/useControllerMessages.ts new file mode 100644 index 0000000..47bcbf0 --- /dev/null +++ b/packages/apps/client/src/hooks/useControllerMessages.ts @@ -0,0 +1,40 @@ +import { useEffect, useRef } from 'react' +import { RootAppStore } from 'src/stores/RootAppStore' + +export function useControllerMessages(ref: React.RefObject, heightPx: number, fontSizePx: number) { + const speed = useRef(0) + const position = useRef(0) + const lastRequest = useRef(null) + const lastFrameTime = useRef(null) + + useEffect(() => { + const onMessage = (message: { speed: number }) => { + console.log('received message', message) + + speed.current = message.speed + } + + RootAppStore.connection.controller.on('message', onMessage) + RootAppStore.connection.controller.subscribeToMessages().catch(console.error) + + const onFrame = (now: number) => { + const frameTime = lastFrameTime.current === null ? 16 : now - lastFrameTime.current + const scrollBy = ((speed.current * fontSizePx) / 300) * frameTime + position.current = Math.max(0, position.current + scrollBy) + console.log(position.current) + + ref.current?.scrollTo(0, position.current) + + lastFrameTime.current = now + lastRequest.current = window.requestAnimationFrame(onFrame) + } + + lastRequest.current = window.requestAnimationFrame(onFrame) + + return () => { + RootAppStore.connection.controller.off('message', onMessage) + + if (lastRequest.current !== null) window.cancelAnimationFrame(lastRequest.current) + } + }, [ref, heightPx, fontSizePx]) +} diff --git a/packages/apps/client/src/views/Output/Output.tsx b/packages/apps/client/src/views/Output/Output.tsx index 0d4bdb1..c40d70e 100644 --- a/packages/apps/client/src/views/Output/Output.tsx +++ b/packages/apps/client/src/views/Output/Output.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useEffect, useMemo, useRef } from 'react' +import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { observer } from 'mobx-react-lite' import { RootAppStore } from 'src/stores/RootAppStore.ts' @@ -9,41 +9,38 @@ import { getCurrentTime } from 'src/lib/getCurrentTime' import { useQueryParam } from 'src/lib/useQueryParam' import classes from './Output.module.scss' +import { useControllerMessages } from 'src/hooks/useControllerMessages' const Output = observer(function Output(): React.ReactElement { const rootEl = useRef(null) - const speed = useRef(0) + const [size, setSize] = useState({ height: window.innerHeight, width: window.innerWidth }) const isPrimary = useQueryParam('primary') !== null // On startup useEffect(() => { RootAppStore.outputSettingsStore.initialize() // load and subscribe + }) - RootAppStore.connection.controller.on('message', (message) => { - console.log('received message', message) - - speed.current = message.speed - }) - RootAppStore.connection.controller.subscribeToMessages().catch(console.error) + const outputSettings = RootAppStore.outputSettingsStore.outputSettings - // don't do this, it's just for testing: - const interval = setInterval(() => { - rootEl.current?.scrollBy(0, speed.current) - }, 1000 / 60) + const fontSize = outputSettings.fontSize + const scaleVertical = outputSettings.mirrorVertically ? '-1' : '1' + const scaleHorizontal = outputSettings.mirrorHorizontally ? '-1' : '1' - return () => { - RootAppStore.connection.controller.off('message') - clearInterval(interval) - } - }, []) + useControllerMessages(rootEl, size.height, (fontSize * size.width) / 100) const onViewPortSizeChanged = useCallback(() => { + const width = window.innerWidth + const height = window.innerHeight + const aspectRatio = width / height + setSize({ width, height }) + if (!isPrimary) return RootAppStore.connection.viewPort.update(null, { _id: '', - aspectRatio: window.innerWidth / window.innerHeight, + aspectRatio, // TODO: This should return the actual lastKnownState lastKnownState: { timestamp: getCurrentTime(), @@ -65,8 +62,6 @@ const Output = observer(function Output(): React.ReactElement { } }, [onViewPortSizeChanged]) - const outputSettings = RootAppStore.outputSettingsStore.outputSettings - const activeRundownPlaylistId = outputSettings?.activeRundownPlaylistId useEffect(() => { @@ -142,10 +137,6 @@ const Output = observer(function Output(): React.ReactElement { */ - const fontSize = outputSettings.fontSize - const scaleVertical = outputSettings.mirrorVertically ? '-1' : '1' - const scaleHorizontal = outputSettings.mirrorHorizontally ? '-1' : '1' - const styleVariables = useMemo( () => ({ diff --git a/packages/apps/client/src/views/RundownScript/PreviewPanel.tsx b/packages/apps/client/src/views/RundownScript/PreviewPanel.tsx index a847af3..f4d2934 100644 --- a/packages/apps/client/src/views/RundownScript/PreviewPanel.tsx +++ b/packages/apps/client/src/views/RundownScript/PreviewPanel.tsx @@ -1,10 +1,11 @@ import { observer } from 'mobx-react-lite' -import React, { useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react' +import React, { useEffect, useMemo, useRef } from 'react' import { RundownOutput } from 'src/components/RundownOutput/RundownOutput' import { RootAppStore } from 'src/stores/RootAppStore' import classes from './PreviewPanel.module.scss' import { useSize } from 'src/lib/useSize' +import { useControllerMessages } from 'src/hooks/useControllerMessages' export const PreviewPanel = observer(function PreviewPanel(): React.ReactNode { const rootEl = useRef(null) @@ -19,16 +20,21 @@ export const PreviewPanel = observer(function PreviewPanel(): React.ReactNode { const viewPortAspectRatio = RootAppStore.viewportStore.viewPort.aspectRatio + const fontSize = outputSettings.fontSize + const size = useSize(rootEl) const previewWidth = size?.width ?? 0 + const previewHeight = size?.height ?? 0 + + useControllerMessages(rootEl, previewHeight, (previewWidth * fontSize) / 100) const style = useMemo( () => ({ - '--prompter-font-size-base': `${(previewWidth * outputSettings.fontSize) / 100}px`, + '--prompter-font-size-base': `${(previewWidth * fontSize) / 100}px`, height: `${previewWidth / viewPortAspectRatio}px`, } as React.CSSProperties), - [outputSettings.fontSize, previewWidth, viewPortAspectRatio] + [fontSize, previewWidth, viewPortAspectRatio] ) return (