Fix OBS group creation by using nested scenes
- Remove invalid CreateGroup API calls (not supported in OBS WebSocket v5) - Implement nested scenes approach for stream grouping - Create a separate scene for each stream containing browser source and text overlay - Add nested scene to team scene to simulate group behavior - Fix lint errors and remove unused imports 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
78f4d325d8
commit
caca548c45
2 changed files with 60 additions and 59 deletions
|
@ -1,6 +1,6 @@
|
||||||
import { NextRequest, NextResponse } from 'next/server';
|
import { NextRequest, NextResponse } from 'next/server';
|
||||||
import { getDatabase } from '../../../lib/database';
|
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 { open } from 'sqlite';
|
||||||
import sqlite3 from 'sqlite3';
|
import sqlite3 from 'sqlite3';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
|
@ -131,7 +131,7 @@ export async function POST(request: NextRequest) {
|
||||||
|
|
||||||
if (!sourceExists) {
|
if (!sourceExists) {
|
||||||
// Create stream group with text overlay
|
// 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
|
// Update team with group UUID if not set
|
||||||
if (!teamInfo.group_uuid) {
|
if (!teamInfo.group_uuid) {
|
||||||
|
@ -152,7 +152,7 @@ export async function POST(request: NextRequest) {
|
||||||
// Get the scene UUID for the group
|
// Get the scene UUID for the group
|
||||||
const obsClient = await getOBSClient();
|
const obsClient = await getOBSClient();
|
||||||
const { scenes } = await obsClient.call('GetSceneList');
|
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) {
|
if (scene) {
|
||||||
await db.run(
|
await db.run(
|
||||||
|
|
113
lib/obsClient.js
113
lib/obsClient.js
|
@ -321,16 +321,24 @@ async function createStreamGroup(groupName, streamName, teamName, url) {
|
||||||
const sourceName = streamName.toLowerCase().replace(/\s+/g, '_') + '_twitch';
|
const sourceName = streamName.toLowerCase().replace(/\s+/g, '_') + '_twitch';
|
||||||
const textSourceName = teamName.toLowerCase().replace(/\s+/g, '_') + '_text';
|
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)
|
// Create text source globally (reused across streams in the team)
|
||||||
await createTextSource(groupName, textSourceName, teamName);
|
await createTextSource(groupName, textSourceName, teamName);
|
||||||
|
|
||||||
// Create browser source directly in the team scene
|
// Create browser source globally
|
||||||
const { sceneItems: teamSceneItems } = await obsClient.call('GetSceneItemList', { sceneName: groupName });
|
const { inputs } = await obsClient.call('GetInputList');
|
||||||
const browserSourceExists = teamSceneItems.some(item => item.sourceName === sourceName);
|
const browserSourceExists = inputs.some(input => input.inputName === sourceName);
|
||||||
|
|
||||||
if (!browserSourceExists) {
|
if (!browserSourceExists) {
|
||||||
await obsClient.call('CreateInput', {
|
await obsClient.call('CreateInput', {
|
||||||
sceneName: groupName,
|
sceneName: streamGroupName, // Create in the nested scene
|
||||||
inputName: sourceName,
|
inputName: sourceName,
|
||||||
inputKind: 'browser_source',
|
inputKind: 'browser_source',
|
||||||
inputSettings: {
|
inputSettings: {
|
||||||
|
@ -340,49 +348,38 @@ async function createStreamGroup(groupName, streamName, teamName, url) {
|
||||||
control_audio: true,
|
control_audio: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
console.log(`Created browser source "${sourceName}" in nested scene`);
|
||||||
|
} else {
|
||||||
// Add text source to team scene if not already there
|
// Add existing source to nested scene
|
||||||
const textInTeamScene = teamSceneItems.some(item => item.sourceName === textSourceName);
|
|
||||||
if (!textInTeamScene) {
|
|
||||||
await obsClient.call('CreateSceneItem', {
|
await obsClient.call('CreateSceneItem', {
|
||||||
sceneName: groupName,
|
sceneName: streamGroupName,
|
||||||
sourceName: textSourceName
|
sourceName: sourceName
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the scene items after adding sources
|
// Add text source to nested scene
|
||||||
const { sceneItems: updatedSceneItems } = await obsClient.call('GetSceneItemList', { sceneName: groupName });
|
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
|
// Get the scene items in the nested scene
|
||||||
const browserSourceItem = updatedSceneItems.find(item => item.sourceName === sourceName);
|
const { sceneItems: nestedSceneItems } = await obsClient.call('GetSceneItemList', { sceneName: streamGroupName });
|
||||||
const textSourceItem = updatedSceneItems.find(item => item.sourceName === textSourceName);
|
|
||||||
|
|
||||||
// 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) {
|
if (browserSourceItem && textSourceItem) {
|
||||||
try {
|
try {
|
||||||
// Create a group with both items
|
// Position text overlay at top-left of the browser source
|
||||||
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', {
|
await obsClient.call('SetSceneItemTransform', {
|
||||||
sceneName: groupName,
|
sceneName: streamGroupName, // In the nested scene
|
||||||
sceneItemId: textSourceItem.sceneItemId,
|
sceneItemId: textSourceItem.sceneItemId,
|
||||||
sceneItemTransform: {
|
sceneItemTransform: {
|
||||||
positionX: 10,
|
positionX: 10,
|
||||||
|
@ -392,29 +389,33 @@ async function createStreamGroup(groupName, streamName, teamName, url) {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Lock the group items
|
console.log(`Stream sources positioned in nested scene "${streamGroupName}"`);
|
||||||
await obsClient.call('SetSceneItemLocked', {
|
} catch (positionError) {
|
||||||
sceneName: groupName,
|
console.error('Failed to position sources:', positionError.message || positionError);
|
||||||
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 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 {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
message: 'Stream group created within team scene',
|
message: 'Stream group created as nested scene',
|
||||||
streamGroupName,
|
streamGroupName,
|
||||||
sourceName,
|
sourceName,
|
||||||
textSourceName
|
textSourceName
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue