const { app, BrowserWindow, ipcMain, dialog } = require('electron'); const path = require('path'); const { startServer } = require('./server'); const { createTray } = require('./tray'); const PORT = 9095; const OVERLAY_DIR = app.isPackaged ? path.join(process.resourcesPath, 'overlay') : path.join(__dirname, '..', 'overlay'); const YT_MUSIC_URL = 'https://music.youtube.com'; let serverHandle = null; let mainWindow = null; let tray = null; let isQuitting = false; async function createWindow() { mainWindow = new BrowserWindow({ width: 1280, height: 800, title: "What's That Music", autoHideMenuBar: true, webPreferences: { preload: path.join(__dirname, 'yt-scraper.js'), contextIsolation: true, nodeIntegration: false, sandbox: false, }, }); mainWindow.on('close', (e) => { // Bypass beforeunload (YT Music sets one while audio is playing) so the // window actually closes when the user clicks X or chooses Quit. if (mainWindow && !mainWindow.isDestroyed()) { e.preventDefault(); mainWindow.destroy(); } }); mainWindow.on('closed', () => { mainWindow = null; }); try { await mainWindow.loadURL(YT_MUSIC_URL); } catch (err) { console.error('[wtm] Failed to load YT Music:', err.message); } } app.whenReady().then(async () => { serverHandle = await startServer({ port: PORT, overlayDir: OVERLAY_DIR }); if (serverHandle.port !== PORT) { const actual = serverHandle.port; await dialog.showMessageBox({ type: 'info', title: 'Port in use', message: `Port ${PORT} was in use; using ${actual} instead.`, detail: `If you have an OBS browser source pointed at this app, update it to http://localhost:${actual}/`, buttons: ['OK'], }); } ipcMain.on('wtm:state', (_event, payload) => { const next = serverHandle.setState(payload || { isPlaying: false }); if (tray) tray.update({ title: next.title, artist: next.artist }); }); // Create the tray before the window: the scraper can report a track as soon as // the page loads, and state arriving while `tray` is null would be dropped. tray = createTray({ port: serverHandle.port, onShow: () => { if (!mainWindow) return; if (mainWindow.isMinimized()) mainWindow.restore(); mainWindow.focus(); }, onQuit: () => app.quit(), }); await createWindow(); }).catch((err) => { dialog.showErrorBox('Failed to start', err && err.message ? err.message : String(err)); app.exit(1); }); app.on('window-all-closed', () => { app.quit(); }); app.on('before-quit', async (e) => { if (isQuitting) return; if (!serverHandle) return; e.preventDefault(); isQuitting = true; if (tray) { tray.destroy(); tray = null; } try { await serverHandle.stop(); } catch (_) {} serverHandle = null; app.exit(0); });