From 07028b0792ea90376f88e85f0cc27cb1710e090b Mon Sep 17 00:00:00 2001 From: Decobus Date: Fri, 25 Jul 2025 20:52:42 -0400 Subject: [PATCH] Add studio mode status display and preview/program indicators MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Enhanced obsStatus API to include studio mode and preview scene information - Updated Footer component to show studio mode status (STUDIO/DIRECT) - Added preview scene display in footer when studio mode is enabled - Implemented dynamic scene button states showing Program/Preview/Both status - Scene buttons now clearly indicate preview vs program with distinct colors - Added proper state management for studio mode and preview scenes 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- app/api/obsStatus/route.ts | 18 +++++ app/page.tsx | 154 +++++++++++++++++++++++++++---------- components/Footer.tsx | 18 ++++- 3 files changed, 147 insertions(+), 43 deletions(-) diff --git a/app/api/obsStatus/route.ts b/app/api/obsStatus/route.ts index 13e8dc4..91c85f6 100644 --- a/app/api/obsStatus/route.ts +++ b/app/api/obsStatus/route.ts @@ -19,9 +19,11 @@ export async function GET() { obsWebSocketVersion: string; }; currentScene?: string; + currentPreviewScene?: string; sceneCount?: number; streaming?: boolean; recording?: boolean; + studioModeEnabled?: boolean; error?: string; } = { host: OBS_HOST, @@ -58,15 +60,31 @@ export async function GET() { // Get recording status const recordStatus = await obs.call('GetRecordStatus'); + // Get studio mode status + const studioModeStatus = await obs.call('GetStudioModeEnabled'); + + // Get preview scene if studio mode is enabled + let currentPreviewScene; + if (studioModeStatus.studioModeEnabled) { + try { + const previewSceneInfo = await obs.call('GetCurrentPreviewScene'); + currentPreviewScene = previewSceneInfo.sceneName; + } catch (previewError) { + console.log('Could not get preview scene:', previewError); + } + } + connectionStatus.connected = true; connectionStatus.version = { obsVersion: versionInfo.obsVersion, obsWebSocketVersion: versionInfo.obsWebSocketVersion }; connectionStatus.currentScene = currentSceneInfo.sceneName; + connectionStatus.currentPreviewScene = currentPreviewScene; connectionStatus.sceneCount = sceneList.scenes.length; connectionStatus.streaming = streamStatus.outputActive; connectionStatus.recording = recordStatus.outputActive; + connectionStatus.studioModeEnabled = studioModeStatus.studioModeEnabled; } catch (err) { connectionStatus.error = err instanceof Error ? err.message : 'Unknown error occurred'; diff --git a/app/page.tsx b/app/page.tsx index 7094cd5..b2e5d82 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -19,6 +19,8 @@ export default function Home() { const [isLoading, setIsLoading] = useState(true); const [openDropdown, setOpenDropdown] = useState(null); const [currentScene, setCurrentScene] = useState(null); + const [currentPreviewScene, setCurrentPreviewScene] = useState(null); + const [studioModeEnabled, setStudioModeEnabled] = useState(false); const { toasts, removeToast, showSuccess, showError } = useToast(); // Memoized active source lookup for performance @@ -59,17 +61,19 @@ export default function Home() { const fetchData = useCallback(async () => { const endTimer = PerformanceMonitor.startTimer('fetchData'); try { - // Fetch streams, active sources, and current scene in parallel - const [streamsRes, activeRes, sceneRes] = await Promise.all([ + // Fetch streams, active sources, current scene, and OBS status in parallel + const [streamsRes, activeRes, sceneRes, obsStatusRes] = await Promise.all([ fetch('/api/streams'), fetch('/api/getActive'), - fetch('/api/getCurrentScene') + fetch('/api/getCurrentScene'), + fetch('/api/obsStatus') ]); - const [streamsData, activeData, sceneData] = await Promise.all([ + const [streamsData, activeData, sceneData, obsStatusData] = await Promise.all([ streamsRes.json(), activeRes.json(), - sceneRes.json() + sceneRes.json(), + obsStatusRes.json() ]); // Handle both old and new API response formats @@ -80,6 +84,15 @@ export default function Home() { setStreams(streams); setActiveSources(activeSources); setCurrentScene(sceneName); + + // Update studio mode and preview scene from OBS status + if (obsStatusData.connected) { + setStudioModeEnabled(obsStatusData.studioModeEnabled || false); + setCurrentPreviewScene(obsStatusData.currentPreviewScene || null); + } else { + setStudioModeEnabled(false); + setCurrentPreviewScene(null); + } } catch (error) { console.error('Error fetching data:', error); showError('Failed to Load Data', 'Could not fetch streams. Please refresh the page.'); @@ -126,9 +139,16 @@ export default function Home() { const result = await response.json(); if (result.success) { - // Update local state immediately for responsive UI - setCurrentScene(sceneName); - showSuccess('Scene Changed', `Switched to ${sceneName} layout`); + // Update local state based on studio mode + if (result.data.studioMode) { + // In studio mode, update preview scene + setCurrentPreviewScene(sceneName); + showSuccess('Preview Set', result.message); + } else { + // In normal mode, update program scene + setCurrentScene(sceneName); + showSuccess('Scene Changed', `Switched to ${sceneName} layout`); + } } else { throw new Error(result.error || 'Failed to switch scene'); } @@ -138,6 +158,55 @@ export default function Home() { } }, [showSuccess, showError]); + // Helper function to get scene button state and styling + const getSceneButtonState = useCallback((sceneName: string) => { + const isProgram = currentScene === sceneName; + const isPreview = studioModeEnabled && currentPreviewScene === sceneName; + + if (studioModeEnabled) { + if (isProgram && isPreview) { + return { + isActive: true, + text: `Program & Preview: ${sceneName}`, + background: 'linear-gradient(135deg, var(--solarized-green), var(--solarized-yellow))' + }; + } else if (isProgram) { + return { + isActive: true, + text: `Program: ${sceneName}`, + background: 'linear-gradient(135deg, var(--solarized-green), var(--solarized-yellow))' + }; + } else if (isPreview) { + return { + isActive: true, + text: `Preview: ${sceneName}`, + background: 'linear-gradient(135deg, var(--solarized-yellow), var(--solarized-orange))' + }; + } else { + return { + isActive: false, + text: `Set Preview: ${sceneName}`, + background: 'linear-gradient(135deg, var(--solarized-blue), var(--solarized-cyan))' + }; + } + } else { + // Normal mode + if (isProgram) { + return { + isActive: true, + text: `Active: ${sceneName}`, + background: 'linear-gradient(135deg, var(--solarized-green), var(--solarized-yellow))' + }; + } else { + return { + isActive: false, + text: `Switch to ${sceneName}`, + background: 'linear-gradient(135deg, var(--solarized-blue), var(--solarized-cyan))' + }; + } + } + }, [currentScene, currentPreviewScene, studioModeEnabled]); + // Memoized corner displays to prevent re-renders const cornerDisplays = useMemo(() => [ { screen: 'top_left' as const, label: 'Top Left' }, @@ -182,17 +251,18 @@ export default function Home() {

Primary Display

- + {(() => { + const buttonState = getSceneButtonState('1-Screen'); + return ( + + ); + })()}

Side Displays

- + {(() => { + const buttonState = getSceneButtonState('2-Screen'); + return ( + + ); + })()}
@@ -252,17 +323,18 @@ export default function Home() {

Corner Displays

- + {(() => { + const buttonState = getSceneButtonState('4-Screen'); + return ( + + ); + })()}
{cornerDisplays.map(({ screen, label }) => ( diff --git a/components/Footer.tsx b/components/Footer.tsx index edbcb26..883daff 100644 --- a/components/Footer.tsx +++ b/components/Footer.tsx @@ -13,9 +13,11 @@ type OBSStatus = { obsWebSocketVersion: string; }; currentScene?: string; + currentPreviewScene?: string; sceneCount?: number; streaming?: boolean; recording?: boolean; + studioModeEnabled?: boolean; error?: string; }; @@ -96,7 +98,7 @@ export default function Footer() {
{obsStatus.host}:{obsStatus.port}
{obsStatus.hasPassword &&
🔒 Authenticated
} - {/* Streaming/Recording Status */} + {/* Streaming/Recording/Studio Mode Status */} {obsStatus.connected && (
@@ -108,6 +110,11 @@ export default function Footer() {
{obsStatus.recording ? 'REC' : 'IDLE'}
+ +
+
+ {obsStatus.studioModeEnabled ? 'STUDIO' : 'DIRECT'} +
)}
@@ -138,11 +145,18 @@ export default function Footer() {
{obsStatus.currentScene && (
- Scene: + {obsStatus.studioModeEnabled ? 'Program:' : 'Scene:'} {obsStatus.currentScene}
)} + {obsStatus.studioModeEnabled && obsStatus.currentPreviewScene && ( +
+ Preview: + {obsStatus.currentPreviewScene} +
+ )} + {obsStatus.sceneCount !== null && (
Total Scenes: