// Специальный радио плеер для Linux с изолированным управлением громкостью const { spawn } = require('child_process'); const fs = require('fs'); const path = require('path'); class LinuxRadioPlayer { constructor() { this.radioProcess = null; this.isPlaying = false; this.volume = 70; this.currentUrl = null; this.playerType = null; } detectPlayer() { // Определяем доступный плеер в порядке приоритета const players = [ { name: 'cvlc', check: 'vlc --version' }, { name: 'mplayer', check: 'mplayer -version' }, { name: 'mpg123', check: 'mpg123 --version' }, { name: 'ffplay', check: 'ffplay -version' } ]; for (const player of players) { try { require('child_process').execSync(player.check, { stdio: 'ignore' }); console.log(`[LINUX-RADIO] ✅ Найден плеер: ${player.name}`); return player.name; } catch (e) { // Плеер не найден } } console.log('[LINUX-RADIO] ⚠️ Не найдены рекомендуемые плееры, используем paplay'); return 'paplay'; } play(url) { this.stop(); if (!url.startsWith('http://') && !url.startsWith('https://')) { url = 'http://' + url; } console.log('[LINUX-RADIO] 📻 Запуск радио:', url); this.currentUrl = url; this.playerType = this.detectPlayer(); let command, args; switch (this.playerType) { case 'cvlc': // VLC - лучший вариант, поддерживает программное управление громкостью command = 'cvlc'; args = [ '--intf', 'dummy', '--no-video', '--no-video-title-show', '--quiet', '--volume', Math.floor(this.volume * 2.56).toString(), // VLC использует 0-256 '--no-volume-save', // Не сохранять громкость url ]; break; case 'mplayer': // MPlayer - хороший вариант с программной громкостью command = 'mplayer'; args = [ '-quiet', '-really-quiet', '-nolirc', '-novideo', '-volume', this.volume.toString(), '-softvol', '-softvol-max', '100', url ]; break; case 'mpg123': // mpg123 - поддерживает масштабирование громкости command = 'mpg123'; args = [ '--quiet', '--scale', (this.volume / 100).toString(), url ]; break; case 'ffplay': // FFplay - часть FFmpeg command = 'ffplay'; args = [ '-nodisp', '-autoexit', '-loglevel', 'error', '-volume', this.volume.toString(), url ]; break; default: // Fallback на paplay с громкостью const paVolume = Math.floor((this.volume / 100) * 65536); command = 'sh'; args = ['-c', `curl -s "${url}" | paplay --raw --format=s16le --rate=44100 --channels=2 --volume=${paVolume}`]; } console.log(`[LINUX-RADIO] Используем ${this.playerType}:`, command, args.join(' ')); this.radioProcess = spawn(command, args, { stdio: 'ignore', // Игнорируем весь вывод detached: false }); this.radioProcess.on('error', (error) => { console.error('[LINUX-RADIO] Ошибка запуска:', error.message); this.isPlaying = false; }); this.radioProcess.on('exit', (code) => { console.log('[LINUX-RADIO] Процесс завершен с кодом:', code); this.isPlaying = false; this.radioProcess = null; }); this.isPlaying = true; } stop() { if (this.radioProcess) { console.log('[LINUX-RADIO] ⏹️ Остановка радио'); try { this.radioProcess.kill('SIGTERM'); setTimeout(() => { if (this.radioProcess) { this.radioProcess.kill('SIGKILL'); } }, 1000); } catch (e) { console.error('[LINUX-RADIO] Ошибка остановки:', e.message); } this.radioProcess = null; } this.isPlaying = false; this.currentUrl = null; } setVolume(volume) { this.volume = Math.max(0, Math.min(100, volume)); console.log('[LINUX-RADIO] 🔊 Новая громкость:', this.volume); // Для изменения громкости на лету нужно перезапустить поток // Это не идеально, но гарантирует отсутствие влияния на системную громкость if (this.isPlaying && this.currentUrl) { console.log('[LINUX-RADIO] Перезапуск потока с новой громкостью...'); const url = this.currentUrl; this.stop(); setTimeout(() => this.play(url), 100); } } getStatus() { return { isPlaying: this.isPlaying, volume: this.volume, url: this.currentUrl, player: this.playerType }; } } module.exports = { LinuxRadioPlayer };