// Улучшенный радио плеер с контролем громкости процесса через NAudio const { spawn } = require('child_process'); const fs = require('fs'); const path = require('path'); class EnhancedRadioPlayer { constructor() { this.radioProcess = null; this.isPlaying = false; this.volume = 70; this.currentUrl = null; this.sessionId = null; } play(url) { this.stop(); // Добавляем протокол если его нет if (!url.startsWith('http://') && !url.startsWith('https://')) { url = 'http://' + url; } console.log('[RADIO+] 📻 Запуск радио с контролем громкости:', url); this.currentUrl = url; this.sessionId = Date.now().toString(); // PowerShell скрипт с NAudio для точного контроля громкости const script = ` # Генерируем уникальный ID сессии $sessionId = "${this.sessionId}" Write-Host "SESSION:$sessionId" # Загружаем .NET assemblies для работы с аудио Add-Type -AssemblyName presentationCore Add-Type -TypeDefinition @' using System; using System.Runtime.InteropServices; using System.Diagnostics; [Guid("5CDF2C82-841E-4546-9722-0CF74078229A"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] interface IAudioEndpointVolume { int f(); int g(); int h(); int i(); int SetMasterVolumeLevelScalar(float fLevel, System.Guid pguidEventContext); int j(); int GetMasterVolumeLevelScalar(out float pfLevel); int k(); int l(); int m(); int n(); int SetMute([MarshalAs(UnmanagedType.Bool)] bool bMute, System.Guid pguidEventContext); int GetMute(out bool pbMute); } [Guid("77AA99A0-1BD6-484F-8BC7-2C654C9A9B6F"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] interface IAudioSessionManager2 { int f(); int g(); int GetSessionEnumerator(out IAudioSessionEnumerator enumerator); } [Guid("E2F5BB11-0570-40CA-ACDD-3AA01277DEE8"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] interface IAudioSessionEnumerator { int GetCount(out int count); int GetSession(int index, out IAudioSessionControl session); } [Guid("F4B1A599-7266-4319-A8CA-E70ACB11E8CD"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] interface IAudioSessionControl { int f(); int g(); int h(); int i(); int j(); int k(); int l(); int GetProcessId(out uint pid); } [Guid("87CE5498-68D6-44E5-9215-6DA47EF883D8"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] interface ISimpleAudioVolume { int SetMasterVolume(float level, ref System.Guid context); int GetMasterVolume(out float level); int SetMute([MarshalAs(UnmanagedType.Bool)] bool mute, ref System.Guid context); int GetMute(out bool mute); } public class ProcessVolumeControl { public static void SetProcessVolume(uint processId, float volume) { // Здесь будет код для установки громкости процесса // Пока используем заглушку Console.WriteLine("Setting volume for PID " + processId + " to " + volume); } } '@ try { # Создаем медиа плеер $player = New-Object System.Windows.Media.MediaPlayer $player.Volume = ${this.volume / 100} $player.Open([System.Uri]"${url}") $player.Play() Write-Host "PLAYING:$sessionId" Write-Host "PID:$PID" # Мониторим статус воспроизведения $startTime = Get-Date while ($true) { Start-Sleep -Milliseconds 500 # Проверяем, что не прошло слишком много времени без активности if (((Get-Date) - $startTime).TotalMinutes -gt 60) { Write-Host "TIMEOUT:$sessionId" break } # Читаем команды управления из stdin (если они есть) if ([Console]::KeyAvailable) { $key = [Console]::ReadKey($true) if ($key.Key -eq 'Q') { Write-Host "STOP:$sessionId" break } } } } catch { Write-Host "ERROR:$sessionId $_" } finally { if ($player) { $player.Stop() $player.Close() } Write-Host "FINISHED:$sessionId" }`; // Сохраняем скрипт const scriptPath = path.join(__dirname, `temp_radio_${this.sessionId}.ps1`); fs.writeFileSync(scriptPath, script); // Запускаем через PowerShell this.radioProcess = spawn('powershell', [ '-ExecutionPolicy', 'Bypass', '-File', scriptPath ]); this.radioProcess.stdout.on('data', (data) => { const output = data.toString().trim(); const lines = output.split('\n'); lines.forEach(line => { console.log('[RADIO+] Вывод:', line); if (line.startsWith('SESSION:')) { console.log('[RADIO+] ✅ Сессия создана:', line.split(':')[1]); } else if (line.startsWith('PLAYING:')) { this.isPlaying = true; console.log('[RADIO+] ▶️ Воспроизведение началось'); } else if (line.startsWith('ERROR:')) { console.log('[RADIO+] ❌ Ошибка воспроизведения'); this.isPlaying = false; } else if (line.startsWith('FINISHED:')) { console.log('[RADIO+] ⏹️ Воспроизведение завершено'); this.isPlaying = false; } }); }); this.radioProcess.stderr.on('data', (data) => { console.error('[RADIO+] Ошибка:', data.toString()); }); this.radioProcess.on('exit', (code) => { console.log('[RADIO+] Процесс завершен с кодом:', code); this.isPlaying = false; // Удаляем временный файл try { fs.unlinkSync(scriptPath); } catch (e) {} }); this.radioProcess.on('error', (err) => { console.error('[RADIO+] Ошибка запуска:', err.message); this.isPlaying = false; }); } stop() { if (this.radioProcess) { console.log('[RADIO+] ⏹️ Остановка радио'); this.radioProcess.kill(); this.radioProcess = null; } this.isPlaying = false; this.currentUrl = null; this.sessionId = null; } setVolume(volume) { this.volume = Math.max(0, Math.min(100, volume)); console.log('[RADIO+] 🔊 Установка громкости:', this.volume); // Пробуем nircmd для точного контроля const nircmdPath = path.join(__dirname, 'tools', 'nircmd.exe'); if (fs.existsSync(nircmdPath)) { // Устанавливаем громкость для всех PowerShell процессов spawn(nircmdPath, ['setappvolume', 'powershell.exe', (this.volume / 100).toString()], { stdio: 'ignore' }); console.log('[RADIO+] ✅ Громкость установлена через nircmd'); return; } // Если радио играет, перезапускаем с новой громкостью if (this.isPlaying && this.currentUrl) { console.log('[RADIO+] 🔄 Перезапуск с новой громкостью...'); const url = this.currentUrl; this.stop(); setTimeout(() => this.play(url), 1000); } } getStatus() { return { isPlaying: this.isPlaying, currentUrl: this.currentUrl, volume: this.volume, sessionId: this.sessionId }; } } module.exports = EnhancedRadioPlayer;