- Клиентская часть Vue 3 + Vite - Серверная часть Node.js + WebSocket - Система авторизации и смен - Управление игровыми портами - Поддержка тем (светлая/темная) - Адаптивный дизайн 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
220 lines
8.1 KiB
JavaScript
220 lines
8.1 KiB
JavaScript
// Улучшенный радио плеер с контролем громкости процесса через 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; |