- Клиентская часть Vue 3 + Vite - Серверная часть Node.js + WebSocket - Система авторизации и смен - Управление игровыми портами - Поддержка тем (светлая/темная) - Адаптивный дизайн 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
222 lines
7.0 KiB
JavaScript
222 lines
7.0 KiB
JavaScript
// Радио плеер через Node.js с поддержкой HTTP потоков
|
||
const { spawn } = require('child_process');
|
||
const fs = require('fs');
|
||
const path = require('path');
|
||
const http = require('http');
|
||
const https = require('https');
|
||
|
||
class RadioStreamPlayer {
|
||
constructor() {
|
||
this.isPlaying = false;
|
||
this.volume = 70;
|
||
this.currentUrl = null;
|
||
this.ffplayProcess = null;
|
||
this.vlcProcess = null;
|
||
this.mpvProcess = null;
|
||
}
|
||
|
||
play(url) {
|
||
this.stop();
|
||
|
||
// Добавляем протокол если его нет
|
||
if (!url.startsWith('http://') && !url.startsWith('https://')) {
|
||
url = 'http://' + url;
|
||
}
|
||
|
||
console.log('[RADIO] 🎵 Запуск радио потока:', url);
|
||
this.currentUrl = url;
|
||
|
||
// Пробуем разные плееры в порядке предпочтения
|
||
this._tryMpv(url) || this._tryVlc(url) || this._tryFfplay(url) || this._tryPowerShell(url);
|
||
}
|
||
|
||
_tryMpv(url) {
|
||
try {
|
||
const mpvPath = path.join(__dirname, 'tools', 'mpv.exe');
|
||
if (!fs.existsSync(mpvPath)) {
|
||
console.log('[RADIO] MPV не найден');
|
||
return false;
|
||
}
|
||
|
||
console.log('[RADIO] Запуск через MPV');
|
||
this.mpvProcess = spawn(mpvPath, [
|
||
'--no-video',
|
||
'--volume=' + this.volume,
|
||
'--no-terminal',
|
||
url
|
||
]);
|
||
|
||
this.mpvProcess.on('error', (err) => {
|
||
console.error('[RADIO] MPV ошибка:', err.message);
|
||
this.isPlaying = false;
|
||
});
|
||
|
||
this.mpvProcess.on('exit', (code) => {
|
||
console.log('[RADIO] MPV завершен с кодом:', code);
|
||
this.isPlaying = false;
|
||
});
|
||
|
||
this.isPlaying = true;
|
||
return true;
|
||
} catch (err) {
|
||
console.error('[RADIO] Ошибка MPV:', err.message);
|
||
return false;
|
||
}
|
||
}
|
||
|
||
_tryVlc(url) {
|
||
try {
|
||
const vlcPaths = [
|
||
path.join(__dirname, 'tools', 'vlc.exe'),
|
||
'C:\\Program Files\\VideoLAN\\VLC\\vlc.exe',
|
||
'C:\\Program Files (x86)\\VideoLAN\\VLC\\vlc.exe'
|
||
];
|
||
|
||
let vlcPath = null;
|
||
for (const p of vlcPaths) {
|
||
if (fs.existsSync(p)) {
|
||
vlcPath = p;
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (!vlcPath) {
|
||
console.log('[RADIO] VLC не найден');
|
||
return false;
|
||
}
|
||
|
||
console.log('[RADIO] Запуск через VLC');
|
||
this.vlcProcess = spawn(vlcPath, [
|
||
'--intf', 'dummy',
|
||
'--no-video',
|
||
'--volume', Math.floor(this.volume * 2.56).toString(),
|
||
url
|
||
]);
|
||
|
||
this.vlcProcess.on('error', (err) => {
|
||
console.error('[RADIO] VLC ошибка:', err.message);
|
||
this.isPlaying = false;
|
||
});
|
||
|
||
this.vlcProcess.on('exit', (code) => {
|
||
console.log('[RADIO] VLC завершен с кодом:', code);
|
||
this.isPlaying = false;
|
||
});
|
||
|
||
this.isPlaying = true;
|
||
return true;
|
||
} catch (err) {
|
||
console.error('[RADIO] Ошибка VLC:', err.message);
|
||
return false;
|
||
}
|
||
}
|
||
|
||
_tryFfplay(url) {
|
||
try {
|
||
const ffplayPath = path.join(__dirname, 'tools', 'ffplay.exe');
|
||
if (!fs.existsSync(ffplayPath)) {
|
||
console.log('[RADIO] ffplay не найден');
|
||
return false;
|
||
}
|
||
|
||
console.log('[RADIO] Запуск через ffplay');
|
||
this.ffplayProcess = spawn(ffplayPath, [
|
||
'-nodisp',
|
||
'-volume', this.volume.toString(),
|
||
'-autoexit',
|
||
url
|
||
]);
|
||
|
||
this.ffplayProcess.on('error', (err) => {
|
||
console.error('[RADIO] ffplay ошибка:', err.message);
|
||
this.isPlaying = false;
|
||
});
|
||
|
||
this.ffplayProcess.on('exit', (code) => {
|
||
console.log('[RADIO] ffplay завершен с кодом:', code);
|
||
this.isPlaying = false;
|
||
});
|
||
|
||
this.isPlaying = true;
|
||
return true;
|
||
} catch (err) {
|
||
console.error('[RADIO] Ошибка ffplay:', err.message);
|
||
return false;
|
||
}
|
||
}
|
||
|
||
_tryPowerShell(url) {
|
||
try {
|
||
console.log('[RADIO] Запуск через PowerShell и Windows Media Player');
|
||
|
||
const script = `
|
||
Add-Type -AssemblyName presentationCore
|
||
$player = New-Object System.Windows.Media.MediaPlayer
|
||
$player.Volume = ${this.volume / 100}
|
||
$player.Open([Uri]"${url}")
|
||
$player.Play()
|
||
|
||
# Держим процесс активным
|
||
while ($player.HasAudio -or $player.NaturalDuration.TimeSpan.TotalSeconds -eq 0) {
|
||
Start-Sleep -Milliseconds 100
|
||
}
|
||
`;
|
||
|
||
spawn('powershell', ['-Command', script]);
|
||
this.isPlaying = true;
|
||
return true;
|
||
} catch (err) {
|
||
console.error('[RADIO] Ошибка PowerShell:', err.message);
|
||
return false;
|
||
}
|
||
}
|
||
|
||
stop() {
|
||
console.log('[RADIO] ⏹️ Остановка радио');
|
||
|
||
if (this.mpvProcess) {
|
||
this.mpvProcess.kill();
|
||
this.mpvProcess = null;
|
||
}
|
||
|
||
if (this.vlcProcess) {
|
||
this.vlcProcess.kill();
|
||
this.vlcProcess = null;
|
||
}
|
||
|
||
if (this.ffplayProcess) {
|
||
this.ffplayProcess.kill();
|
||
this.ffplayProcess = null;
|
||
}
|
||
|
||
// Убиваем любые оставшиеся процессы
|
||
spawn('taskkill', ['/F', '/IM', 'mpv.exe'], { stdio: 'ignore' });
|
||
spawn('taskkill', ['/F', '/IM', 'vlc.exe'], { stdio: 'ignore' });
|
||
spawn('taskkill', ['/F', '/IM', 'ffplay.exe'], { stdio: 'ignore' });
|
||
|
||
this.isPlaying = false;
|
||
this.currentUrl = null;
|
||
}
|
||
|
||
setVolume(volume) {
|
||
this.volume = Math.max(0, Math.min(100, volume));
|
||
console.log('[RADIO] 🔊 Установка громкости:', this.volume);
|
||
|
||
// Для изменения громкости нужно перезапустить
|
||
if (this.isPlaying && this.currentUrl) {
|
||
const url = this.currentUrl;
|
||
this.stop();
|
||
setTimeout(() => this.play(url), 500);
|
||
}
|
||
}
|
||
|
||
getStatus() {
|
||
return {
|
||
isPlaying: this.isPlaying,
|
||
currentUrl: this.currentUrl,
|
||
volume: this.volume
|
||
};
|
||
}
|
||
}
|
||
|
||
module.exports = RadioStreamPlayer; |