Add YouTube Music now-playing server, OBS overlay, and browser extension

Node.js server on port 9095 receives now-playing data via POST and broadcasts
to connected WebSocket clients. Includes a Firefox/Chrome extension that scrapes
YouTube Music's player bar every 3s, an OBS-ready transparent overlay with
animated equalizer bars, and an image proxy to bypass CDN restrictions in OBS.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-12 11:55:04 -07:00
co-authored by Claude Opus 4.6
parent 74bbf4e9b6
commit e36c4d1a74
9 changed files with 1563 additions and 0 deletions
+98
View File
@@ -0,0 +1,98 @@
const express = require('express');
const http = require('http');
const path = require('path');
const { WebSocketServer } = require('ws');
const app = express();
const server = http.createServer(app);
const wss = new WebSocketServer({ server });
const PORT = 9095;
// Current now-playing state
let currentState = {
title: '',
artist: '',
album: '',
albumArt: '',
isPlaying: false,
progress: 0,
duration: 0,
};
// CORS middleware
app.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
res.header('Access-Control-Allow-Headers', 'Content-Type');
if (req.method === 'OPTIONS') {
return res.sendStatus(204);
}
next();
});
// Parse JSON bodies
app.use(express.json());
// Serve static files from the overlay directory
app.use(express.static(path.join(__dirname, '..', 'overlay')));
// Image proxy — fetches album art server-side so OBS can display it
app.get('/api/image-proxy', async (req, res) => {
const url = req.query.url;
if (!url) return res.status(400).send('Missing url parameter');
try {
const response = await fetch(url);
if (!response.ok) return res.status(response.status).send('Upstream error');
res.set('Content-Type', response.headers.get('content-type') || 'image/jpeg');
res.set('Cache-Control', 'public, max-age=3600');
const buffer = await response.arrayBuffer();
res.send(Buffer.from(buffer));
} catch (err) {
res.status(502).send('Failed to fetch image');
}
});
// Health check
app.get('/api/health', (req, res) => {
res.json({ status: 'ok' });
});
// Get current now-playing state
app.get('/api/now-playing', (req, res) => {
res.json(currentState);
});
// Update now-playing state
app.post('/api/now-playing', (req, res) => {
const { title, artist, album, albumArt, isPlaying, progress, duration } = req.body;
currentState = {
title: title ?? currentState.title,
artist: artist ?? currentState.artist,
album: album ?? currentState.album,
albumArt: albumArt ?? currentState.albumArt,
isPlaying: isPlaying ?? currentState.isPlaying,
progress: progress ?? currentState.progress,
duration: duration ?? currentState.duration,
};
// Broadcast to all connected WebSocket clients
const message = JSON.stringify(currentState);
wss.clients.forEach((client) => {
if (client.readyState === 1) { // WebSocket.OPEN
client.send(message);
}
});
res.json(currentState);
});
// Send current state to newly connected WebSocket clients
wss.on('connection', (ws) => {
ws.send(JSON.stringify(currentState));
});
server.listen(PORT, () => {
console.log(`Server running on http://localhost:${PORT}`);
});