diff --git a/app/api/addStream/route.ts b/app/api/addStream/route.ts index 783c05f..bb9df52 100644 --- a/app/api/addStream/route.ts +++ b/app/api/addStream/route.ts @@ -1,6 +1,6 @@ import { NextRequest, NextResponse } from 'next/server'; import { getDatabase } from '../../../lib/database'; -import { connectToOBS, getOBSClient, disconnectFromOBS, addSourceToSwitcher, createGroupIfNotExists, addSourceToGroup, createStreamGroup } from '../../../lib/obsClient'; +import { connectToOBS, getOBSClient, disconnectFromOBS, addSourceToSwitcher, createStreamGroup } from '../../../lib/obsClient'; import { open } from 'sqlite'; import sqlite3 from 'sqlite3'; import path from 'path'; @@ -131,7 +131,7 @@ export async function POST(request: NextRequest) { if (!sourceExists) { // Create stream group with text overlay - const result = await createStreamGroup(groupName, name, teamInfo.team_name, url); + await createStreamGroup(groupName, name, teamInfo.team_name, url); // Update team with group UUID if not set if (!teamInfo.group_uuid) { @@ -152,7 +152,7 @@ export async function POST(request: NextRequest) { // Get the scene UUID for the group const obsClient = await getOBSClient(); const { scenes } = await obsClient.call('GetSceneList'); - const scene = scenes.find((s: any) => s.sceneName === groupName); + const scene = scenes.find((s: { sceneName: string; sceneUuid: string }) => s.sceneName === groupName); if (scene) { await db.run( diff --git a/lib/obsClient.js b/lib/obsClient.js index 9271d3d..b717728 100644 --- a/lib/obsClient.js +++ b/lib/obsClient.js @@ -321,16 +321,24 @@ async function createStreamGroup(groupName, streamName, teamName, url) { const sourceName = streamName.toLowerCase().replace(/\s+/g, '_') + '_twitch'; const textSourceName = teamName.toLowerCase().replace(/\s+/g, '_') + '_text'; + // Create a nested scene for this stream (acts as a group) + try { + await obsClient.call('CreateScene', { sceneName: streamGroupName }); + console.log(`Created nested scene "${streamGroupName}" for stream grouping`); + } catch (sceneError) { + console.log(`Nested scene "${streamGroupName}" might already exist`); + } + // Create text source globally (reused across streams in the team) await createTextSource(groupName, textSourceName, teamName); - // Create browser source directly in the team scene - const { sceneItems: teamSceneItems } = await obsClient.call('GetSceneItemList', { sceneName: groupName }); - const browserSourceExists = teamSceneItems.some(item => item.sourceName === sourceName); + // Create browser source globally + const { inputs } = await obsClient.call('GetInputList'); + const browserSourceExists = inputs.some(input => input.inputName === sourceName); if (!browserSourceExists) { await obsClient.call('CreateInput', { - sceneName: groupName, + sceneName: streamGroupName, // Create in the nested scene inputName: sourceName, inputKind: 'browser_source', inputSettings: { @@ -340,49 +348,38 @@ async function createStreamGroup(groupName, streamName, teamName, url) { control_audio: true, }, }); - } - - // Add text source to team scene if not already there - const textInTeamScene = teamSceneItems.some(item => item.sourceName === textSourceName); - if (!textInTeamScene) { + console.log(`Created browser source "${sourceName}" in nested scene`); + } else { + // Add existing source to nested scene await obsClient.call('CreateSceneItem', { - sceneName: groupName, - sourceName: textSourceName + sceneName: streamGroupName, + sourceName: sourceName }); } - // Get the scene items after adding sources - const { sceneItems: updatedSceneItems } = await obsClient.call('GetSceneItemList', { sceneName: groupName }); + // Add text source to nested scene + try { + await obsClient.call('CreateSceneItem', { + sceneName: streamGroupName, + sourceName: textSourceName + }); + } catch (e) { + console.log('Text source might already be in nested scene'); + } - // Find the browser source and text source items - const browserSourceItem = updatedSceneItems.find(item => item.sourceName === sourceName); - const textSourceItem = updatedSceneItems.find(item => item.sourceName === textSourceName); + // Get the scene items in the nested scene + const { sceneItems: nestedSceneItems } = await obsClient.call('GetSceneItemList', { sceneName: streamGroupName }); - // Create a group within the team scene containing both sources + // Find the browser source and text source items in nested scene + const browserSourceItem = nestedSceneItems.find(item => item.sourceName === sourceName); + const textSourceItem = nestedSceneItems.find(item => item.sourceName === textSourceName); + + // Position the sources properly in the nested scene if (browserSourceItem && textSourceItem) { try { - // Create a group with both items - await obsClient.call('CreateGroup', { - sceneName: groupName, - groupName: streamGroupName - }); - - // Add both sources to the group - await obsClient.call('SetSceneItemGroup', { - sceneName: groupName, - sceneItemId: browserSourceItem.sceneItemId, - groupName: streamGroupName - }); - - await obsClient.call('SetSceneItemGroup', { - sceneName: groupName, - sceneItemId: textSourceItem.sceneItemId, - groupName: streamGroupName - }); - - // Position text overlay at top-left within the group + // Position text overlay at top-left of the browser source await obsClient.call('SetSceneItemTransform', { - sceneName: groupName, + sceneName: streamGroupName, // In the nested scene sceneItemId: textSourceItem.sceneItemId, sceneItemTransform: { positionX: 10, @@ -392,29 +389,33 @@ async function createStreamGroup(groupName, streamName, teamName, url) { } }); - // Lock the group items - await obsClient.call('SetSceneItemLocked', { - sceneName: groupName, - sceneItemId: browserSourceItem.sceneItemId, - sceneItemLocked: true - }); - - await obsClient.call('SetSceneItemLocked', { - sceneName: groupName, - sceneItemId: textSourceItem.sceneItemId, - sceneItemLocked: true - }); - - console.log(`Stream group "${streamGroupName}" created within team scene "${groupName}"`); - } catch (groupError) { - console.log('Group creation failed, sources added individually to team scene'); + console.log(`Stream sources positioned in nested scene "${streamGroupName}"`); + } catch (positionError) { + console.error('Failed to position sources:', positionError.message || positionError); } } - console.log(`Stream sources added to team scene "${groupName}" with text overlay`); + // Now add the nested scene to the team scene as a group + const { sceneItems: teamSceneItems } = await obsClient.call('GetSceneItemList', { sceneName: groupName }); + const nestedSceneInTeam = teamSceneItems.some(item => item.sourceName === streamGroupName); + + if (!nestedSceneInTeam) { + try { + await obsClient.call('CreateSceneItem', { + sceneName: groupName, + sourceName: streamGroupName, + sceneItemEnabled: true + }); + console.log(`Added nested scene "${streamGroupName}" to team scene "${groupName}"`); + } catch (e) { + console.error('Failed to add nested scene to team scene:', e.message); + } + } + + console.log(`Stream group "${streamGroupName}" created as nested scene in team "${groupName}"`); return { success: true, - message: 'Stream group created within team scene', + message: 'Stream group created as nested scene', streamGroupName, sourceName, textSourceName