UNPKG

win-stream-audio

Version:

🎧 Stream Windows system audio to Android devices over WiFi with professional audio controls, EQ, pitch shifting, and effects

201 lines (175 loc) 7.17 kB
/** * Win Stream Audio - Main Entry Point * Professional Windows audio streaming to Android devices */ const path = require('path'); const express = require('express'); const http = require('http'); const WebSocket = require('ws'); const ip = require('ip'); class WinStreamAudioServer { constructor(options = {}) { this.port = options.port || process.env.PORT || 8080; this.host = options.host || '0.0.0.0'; this.app = express(); this.server = null; this.wss = null; this.sources = new Set(); this.receivers = new Set(); } start() { return new Promise((resolve, reject) => { try { this.setupExpress(); this.setupWebSocket(); this.startServer(resolve, reject); } catch (error) { reject(error); } }); } setupExpress() { // CORS headers for internet access this.app.use((req, res, next) => { res.header('Access-Control-Allow-Origin', '*'); res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS'); res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept, Authorization'); if (req.method === 'OPTIONS') { res.sendStatus(200); } else { next(); } }); // Serve static files this.app.use(express.static(path.join(__dirname, 'public'))); // Status endpoint this.app.get('/status', (req, res) => { res.json({ status: 'running', platform: process.platform, clients: this.wss ? this.wss.clients.size : 0, sources: this.sources.size, receivers: this.receivers.size, version: require('./package.json').version }); }); // Health check this.app.get('/health', (req, res) => { res.json({ status: 'healthy', timestamp: new Date().toISOString() }); }); } setupWebSocket() { this.server = http.createServer(this.app); this.wss = new WebSocket.Server({ server: this.server }); this.wss.on('connection', (ws, req) => { const clientIp = req.socket.remoteAddress || req.connection.remoteAddress; ws.clientIp = clientIp; ws.on('message', (message) => { try { if (message.toString().startsWith('{')) { const control = JSON.parse(message); if (control.type === 'role') { ws.role = control.role; console.log(`🎭 Client ${clientIp} identified as ${ws.role}`); if (ws.role === 'source') { this.sources.add(ws); console.log(`🎤 Active sources: ${this.sources.size}`); } else if (ws.role === 'receiver') { this.receivers.add(ws); console.log(`🔊 Active receivers: ${this.receivers.size}`); } this.broadcastStatus(); } else if (control.type === 'raw-audio') { console.log(`🎵 Raw audio metadata: ${control.sampleRate}Hz, ${control.channels}ch, ${control.length} samples`); this.receivers.forEach((receiver) => { if (receiver.readyState === WebSocket.OPEN) { receiver.send(JSON.stringify(control)); } }); } } else { // Binary audio data from source if (ws.role === 'source') { const audioSize = message.length; console.log(`🎵 Received audio chunk: ${audioSize} bytes from ${clientIp}`); let receiverCount = 0; this.receivers.forEach((receiver) => { if (receiver.readyState === WebSocket.OPEN) { receiver.send(message); receiverCount++; } }); if (receiverCount === 0) { console.log('⚠️ No active receivers for audio data'); } } } } catch (error) { console.error('❌ Error processing message:', error); } }); ws.on('close', () => { console.log(`👋 Client ${clientIp} disconnected`); this.sources.delete(ws); this.receivers.delete(ws); this.broadcastStatus(); }); ws.on('error', (error) => { console.error(`❌ WebSocket error from ${clientIp}:`, error); }); }); } broadcastStatus() { const status = { type: 'status', sources: this.sources.size, receivers: this.receivers.size, timestamp: Date.now() }; const statusMessage = JSON.stringify(status); this.wss.clients.forEach((client) => { if (client.readyState === WebSocket.OPEN) { client.send(statusMessage); } }); } startServer(resolve, reject) { this.server.listen(this.port, this.host, () => { const localIP = ip.address(); console.log(`🚀 Win Stream Audio server running at:`); console.log(` Local: http://localhost:${this.port}`); console.log(` Network: http://${localIP}:${this.port}`); console.log(`🎧 AudioPilot ready for connections!`); resolve({ port: this.port, host: this.host, localIP: localIP, urls: { local: `http://localhost:${this.port}`, network: `http://${localIP}:${this.port}` } }); }); this.server.on('error', (error) => { if (error.code === 'EADDRINUSE') { console.error(`❌ Port ${this.port} is already in use`); } else { console.error('❌ Server error:', error); } reject(error); }); } stop() { return new Promise((resolve) => { if (this.server) { this.server.close(() => { console.log('🛑 Win Stream Audio server stopped'); resolve(); }); } else { resolve(); } }); } } module.exports = WinStreamAudioServer;