diff --git a/app/api/addStream/route.ts b/app/api/addStream/route.ts index bb9df52..783c05f 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, createStreamGroup } from '../../../lib/obsClient'; +import { connectToOBS, getOBSClient, disconnectFromOBS, addSourceToSwitcher, createGroupIfNotExists, addSourceToGroup, 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 - await createStreamGroup(groupName, name, teamInfo.team_name, url); + const result = 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: { sceneName: string; sceneUuid: string }) => s.sceneName === groupName); + const scene = scenes.find((s: any) => s.sceneName === groupName); if (scene) { await db.run( diff --git a/lib/obsClient.js b/lib/obsClient.js index 0ee68b7..9271d3d 100644 --- a/lib/obsClient.js +++ b/lib/obsClient.js @@ -321,65 +321,68 @@ 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 globally - const { inputs } = await obsClient.call('GetInputList'); - const browserSourceExists = inputs.some(input => input.inputName === sourceName); + // 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); if (!browserSourceExists) { await obsClient.call('CreateInput', { - sceneName: streamGroupName, // Create in the nested scene + sceneName: groupName, inputName: sourceName, inputKind: 'browser_source', inputSettings: { - width: 1920, - height: 1080, + width: 1600, + height: 900, url, control_audio: true, }, }); - console.log(`Created browser source "${sourceName}" in nested scene`); - } else { - // Add existing source to nested scene - await obsClient.call('CreateSceneItem', { - sceneName: streamGroupName, - sourceName: sourceName - }); } - // Add text source to nested scene - try { + // Add text source to team scene if not already there + const textInTeamScene = teamSceneItems.some(item => item.sourceName === textSourceName); + if (!textInTeamScene) { await obsClient.call('CreateSceneItem', { - sceneName: streamGroupName, + sceneName: groupName, sourceName: textSourceName }); - } catch (e) { - console.log('Text source might already be in nested scene'); } - // Get the scene items in the nested scene - const { sceneItems: nestedSceneItems } = await obsClient.call('GetSceneItemList', { sceneName: streamGroupName }); + // Get the scene items after adding sources + const { sceneItems: updatedSceneItems } = await obsClient.call('GetSceneItemList', { sceneName: groupName }); - // 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); + // Find the browser source and text source items + const browserSourceItem = updatedSceneItems.find(item => item.sourceName === sourceName); + const textSourceItem = updatedSceneItems.find(item => item.sourceName === textSourceName); - // Position the sources properly in the nested scene + // Create a group within the team scene containing both sources if (browserSourceItem && textSourceItem) { try { - // Position text overlay at top-left of the browser source + // 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 await obsClient.call('SetSceneItemTransform', { - sceneName: streamGroupName, // In the nested scene + sceneName: groupName, sceneItemId: textSourceItem.sceneItemId, sceneItemTransform: { positionX: 10, @@ -389,49 +392,29 @@ async function createStreamGroup(groupName, streamName, teamName, url) { } }); - console.log(`Stream sources positioned in nested scene "${streamGroupName}"`); - } catch (positionError) { - console.error('Failed to position sources:', positionError.message || positionError); - } - } - - // 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 { - const { sceneItemId } = await obsClient.call('CreateSceneItem', { + // Lock the group items + await obsClient.call('SetSceneItemLocked', { sceneName: groupName, - sourceName: streamGroupName, - sceneItemEnabled: true + sceneItemId: browserSourceItem.sceneItemId, + sceneItemLocked: true }); - console.log(`Added nested scene "${streamGroupName}" to team scene "${groupName}"`); - // Set bounds to 1600x900 to match the source switcher dimensions - await obsClient.call('SetSceneItemTransform', { + await obsClient.call('SetSceneItemLocked', { sceneName: groupName, - sceneItemId: sceneItemId, - sceneItemTransform: { - alignment: 5, // Center alignment - boundsAlignment: 0, // Center bounds alignment - boundsType: 'OBS_BOUNDS_SCALE_INNER', // Scale to fit inside bounds - boundsWidth: 1600, - boundsHeight: 900, - scaleX: 1.0, - scaleY: 1.0 - } + sceneItemId: textSourceItem.sceneItemId, + sceneItemLocked: true }); - console.log(`Set bounds for nested scene to 1600x900`); - } catch (e) { - console.error('Failed to add nested scene to team scene:', e.message); + + 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 group "${streamGroupName}" created as nested scene in team "${groupName}"`); + console.log(`Stream sources added to team scene "${groupName}" with text overlay`); return { success: true, - message: 'Stream group created as nested scene', + message: 'Stream group created within team scene', streamGroupName, sourceName, textSourceName