Initial commit - OBS Source Switcher Plugin UI
Some checks failed
Lint and Build / build (20) (push) Has been cancelled
Lint and Build / build (22) (push) Has been cancelled

Complete Next.js application for managing OBS Source Switcher
- Stream management with multiple screen layouts
- Team management CRUD operations
- SQLite database integration
- OBS WebSocket API integration
- Updated to latest versions (Next.js 15.4.1, React 19.1.0, Tailwind CSS 4.0.0)
- Enhanced .gitignore for privacy and development
This commit is contained in:
Decobus 2025-07-15 22:15:57 -04:00
commit 1d4b1eefba
43 changed files with 9596 additions and 0 deletions

42
lib/constants.ts Normal file
View file

@ -0,0 +1,42 @@
// Base table names
export const BASE_TABLE_NAMES = {
STREAMS: 'streams',
TEAMS: 'teams',
} as const;
// Table configuration interface
export interface TableConfig {
year: number;
season: 'spring' | 'summer' | 'fall' | 'winter';
suffix?: string;
}
// Default configuration
export const DEFAULT_TABLE_CONFIG: TableConfig = {
year: 2025,
season: 'spring',
suffix: 'adr'
};
/**
* Generates a full table name using the provided configuration
* @param baseTableName - The base table name (e.g., 'streams' or 'teams')
* @param config - Optional configuration object. If not provided, uses DEFAULT_TABLE_CONFIG
* @returns The full table name with year, season, and suffix
*/
export function getTableName(
baseTableName: typeof BASE_TABLE_NAMES[keyof typeof BASE_TABLE_NAMES],
config: Partial<TableConfig> = {}
): string {
const finalConfig = {...DEFAULT_TABLE_CONFIG, ...config};
const suffix = finalConfig.suffix ? `_${finalConfig.suffix}` : '';
return `${baseTableName}_${finalConfig.year}_${finalConfig.season}${suffix}`;
}
// Export commonly used full table names with default configuration
export const TABLE_NAMES = {
STREAMS: getTableName(BASE_TABLE_NAMES.STREAMS),
TEAMS: getTableName(BASE_TABLE_NAMES.TEAMS),
} as const;

60
lib/database.ts Normal file
View file

@ -0,0 +1,60 @@
import sqlite3 from 'sqlite3';
import { open, Database } from 'sqlite';
import path from 'path';
import fs from 'fs';
import { TABLE_NAMES } from './constants';
let db: Database<sqlite3.Database, sqlite3.Statement> | null = null;
const FILE_DIRECTORY = path.resolve(process.env.FILE_DIRECTORY || './files')
const ensureDirectoryExists = (dirPath: string) => {
if (!fs.existsSync(dirPath)) {
fs.mkdirSync(dirPath, { recursive: true });
console.log(`Created directory: ${dirPath}`);
}
};
const initializeDatabase = async (database: Database<sqlite3.Database, sqlite3.Statement>) => {
// Create streams table
await database.exec(`
CREATE TABLE IF NOT EXISTS ${TABLE_NAMES.STREAMS} (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
obs_source_name TEXT NOT NULL,
url TEXT NOT NULL,
team_id INTEGER NOT NULL
)
`);
// Create teams table
await database.exec(`
CREATE TABLE IF NOT EXISTS ${TABLE_NAMES.TEAMS} (
team_id INTEGER PRIMARY KEY,
team_name TEXT NOT NULL
)
`);
console.log('Database tables initialized.');
};
export const getDatabase = async () => {
if (!db) {
// Ensure the files directory exists
ensureDirectoryExists(FILE_DIRECTORY);
const dbPath = path.join(FILE_DIRECTORY, 'sources.db');
db = await open({
filename: dbPath,
driver: sqlite3.Database,
});
console.log('Database connection established.');
// Initialize database tables
await initializeDatabase(db);
}
return db;
}
// export default getDatabase

125
lib/obsClient.js Normal file
View file

@ -0,0 +1,125 @@
// const config = require('../config');
const { OBSWebSocket } = require('obs-websocket-js');
let obs = null;
async function connectToOBS() {
if (!obs) {
obs = new OBSWebSocket();
}
try {
const OBS_HOST = process.env.OBS_WEBSOCKET_HOST || '127.0.0.1';
const OBS_PORT = process.env.OBS_WEBSOCKET_PORT || '4455';
const OBS_PASSWORD = process.env.OBS_WEBSOCKET_PASSWORD || '';
console.log('Connecting to OBS WebSocket...');
console.log('Host:', OBS_HOST);
console.log('Port:', OBS_PORT);
console.log('Password:', OBS_PASSWORD ? '***' : '(none)');
await obs.connect(`ws://${OBS_HOST}:${OBS_PORT}`, OBS_PASSWORD);
console.log('Connected to OBS WebSocket.');
} catch (err) {
console.error('Failed to connect to OBS WebSocket:', err.message);
throw err;
}
}
function getOBSClient() {
if (!obs) {
throw new Error('OBS WebSocket client is not initialized. Call connectToOBS() first.');
}
// console.log('client', obs)
return obs;
}
async function disconnectFromOBS() {
if (obs) {
await obs.disconnect();
console.log('Disconnected from OBS WebSocket.');
obs = null;
}
}
async function addSourceToSwitcher(inputName, newSources) {
if (!obs) {
obs = new OBSWebSocket();
}
try {
const OBS_HOST = process.env.OBS_WEBSOCKET_HOST || '127.0.0.1';
const OBS_PORT = process.env.OBS_WEBSOCKET_PORT || '4455';
const OBS_PASSWORD = process.env.OBS_WEBSOCKET_PASSWORD || '';
await obs.connect(`ws://${OBS_HOST}:${OBS_PORT}`, OBS_PASSWORD);
// Step 1: Get current input settings
const { inputSettings } = await obs.call('GetInputSettings', { inputName });
// console.log('Current Settings:', inputSettings);
// Step 2: Add new sources to the sources array
const updatedSources = [...inputSettings.sources, ...newSources];
// Step 3: Update the settings with the new sources array
await obs.call('SetInputSettings', {
inputName,
inputSettings: {
...inputSettings,
sources: updatedSources,
},
});
console.log('Updated settings successfully for', inputName);
obs.disconnect();
} catch (error) {
console.error('Error updating settings:', error.message);
}
}
// async function addSourceToGroup(obs, teamName, obs_source_name, url) {
// try {
// // Step 1: Check if the group exists
// const { scenes } = await obs.call('GetSceneList');
// const groupExists = scenes.some((scene) => scene.sceneName === teamName);
// // Step 2: Create the group if it doesn't exist
// if (!groupExists) {
// console.log(`Group "${teamName}" does not exist. Creating it.`);
// await obs.call('CreateScene', { sceneName: teamName });
// } else {
// console.log(`Group "${teamName}" already exists.`);
// }
// // Step 3: Add the source to the group
// console.log(`Adding source "${obs_source_name}" to group "${teamName}".`);
// await obs.call('CreateInput', {
// sceneName: teamName,
// inputName: obs_source_name,
// inputKind: 'browser_source',
// inputSettings: {
// width: 1600,
// height: 900,
// url,
// control_audio: true,
// },
// });
// // Step 4: Enable "Control audio via OBS"
// await obs.call('SetInputSettings', {
// inputName: obs_source_name,
// inputSettings: {
// control_audio: true, // Enable audio control
// },
// overlay: true, // Keep existing settings and apply changes
// });
// console.log(`Source "${obs_source_name}" successfully added to group "${teamName}".`);
// } catch (error) {
// console.error('Error adding source to group:', error.message);
// }
// }
// Export all functions
module.exports = { connectToOBS, getOBSClient, disconnectFromOBS, addSourceToSwitcher};