Files
shadowdaoandClaude Sonnet 5.5 e49078ee03 Fix now-playing scraper, quit hang, tray icon, and tray race (v1.0.1)
- Read the track from the Media Session API, falling back to the legacy
  ytmusic-player-bar scrape. Current YT Music layouts render no player bar,
  so the API and tray stayed empty while music played. Artist is also clean
  now (no "• views • likes" suffix).
- Terminate connected WebSocket clients before closing the server. ws's
  close() waits for every client to disconnect, so quitting with an overlay
  open hung forever, leaving the window playing and the port bound.
- Replace the tray icon with a valid PNG (the old one had a bad IDAT CRC and
  decoded to an empty image) and test that its chunks are well formed.
- Create the tray before the window so early state updates are not dropped.
- Bump version to 1.0.1.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-04 11:37:38 -07:00

103 lines
2.8 KiB
JavaScript

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);
});