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
JavaScript
/**
* 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;