- Клиентская часть Vue 3 + Vite - Серверная часть Node.js + WebSocket - Система авторизации и смен - Управление игровыми портами - Поддержка тем (светлая/темная) - Адаптивный дизайн 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
249 lines
10 KiB
JavaScript
249 lines
10 KiB
JavaScript
// Универсальный музыкальный плеер для фоновой музыки
|
||
const { spawn } = require('child_process');
|
||
const fs = require('fs');
|
||
const os = require('os');
|
||
const path = require('path');
|
||
|
||
class MusicPlayer {
|
||
constructor() {
|
||
this.process = null;
|
||
this.isPlaying = false;
|
||
this.isPaused = false;
|
||
this.currentTrack = null;
|
||
this.volume = 70; // Громкость от 0 до 100
|
||
this.duckedVolume = 20; // Приглушенная громкость во время баркера
|
||
this.isDucked = false;
|
||
this.repeatMode = false;
|
||
this.onTrackEnd = null;
|
||
this.platform = os.platform();
|
||
|
||
console.log('[MUSIC] Музыкальный плеер инициализирован для платформы:', this.platform);
|
||
}
|
||
|
||
// Получение списка музыкальных файлов
|
||
getAvailableTracks() {
|
||
const musicDir = path.resolve('./music/background');
|
||
console.log('[MUSIC] Проверяем папку с музыкой:', musicDir);
|
||
|
||
if (!fs.existsSync(musicDir)) {
|
||
console.log('[MUSIC] Папка с музыкой не найдена:', musicDir);
|
||
return [];
|
||
}
|
||
|
||
try {
|
||
const files = fs.readdirSync(musicDir);
|
||
console.log('[MUSIC] Все файлы в папке:', files);
|
||
|
||
const musicFiles = files.filter(file =>
|
||
file.endsWith('.mp3') || file.endsWith('.wav') || file.endsWith('.ogg')
|
||
);
|
||
console.log('[MUSIC] Музыкальные файлы:', musicFiles);
|
||
|
||
const tracks = musicFiles.map(file => ({
|
||
id: path.parse(file).name,
|
||
name: path.parse(file).name,
|
||
path: path.join(musicDir, file)
|
||
}));
|
||
|
||
console.log('[MUSIC] Готовый список треков:', tracks);
|
||
return tracks;
|
||
} catch (error) {
|
||
console.error('[MUSIC] Ошибка чтения папки с музыкой:', error);
|
||
return [];
|
||
}
|
||
}
|
||
|
||
// Остановка текущего воспроизведения
|
||
stop() {
|
||
if (this.process && this.isPlaying) {
|
||
console.log('[MUSIC] Останавливаем воспроизведение музыки');
|
||
this.process.kill('SIGTERM');
|
||
this.process = null;
|
||
}
|
||
|
||
this.isPlaying = false;
|
||
this.isPaused = false;
|
||
this.currentTrack = null;
|
||
this.isDucked = false;
|
||
|
||
return this;
|
||
}
|
||
|
||
// Воспроизведение трека
|
||
play(trackPath, onStart = null, onEnd = null) {
|
||
console.log('[MUSIC] 🎵 Запуск воспроизведения:', trackPath);
|
||
console.log('[MUSIC] 📊 Текущее состояние перед запуском:', {
|
||
isPlaying: this.isPlaying,
|
||
currentTrack: this.currentTrack,
|
||
volume: this.volume,
|
||
repeatMode: this.repeatMode
|
||
});
|
||
|
||
// Останавливаем предыдущее воспроизведение
|
||
this.stop();
|
||
console.log('[MUSIC] ⏹️ Предыдущее воспроизведение остановлено');
|
||
|
||
// Проверяем существование файла
|
||
if (!fs.existsSync(trackPath)) {
|
||
console.error('[MUSIC] ❌ Файл не найден:', trackPath);
|
||
return this;
|
||
}
|
||
console.log('[MUSIC] ✅ Файл найден, размер:', fs.statSync(trackPath).size, 'байт');
|
||
|
||
this.currentTrack = trackPath;
|
||
this.onTrackEnd = onEnd;
|
||
|
||
// Запускаем воспроизведение
|
||
console.log('[MUSIC] 🚀 Вызываем _startPlayback');
|
||
this._startPlayback(trackPath, onStart, onEnd);
|
||
|
||
return this;
|
||
}
|
||
|
||
// Внутренний метод запуска воспроизведения
|
||
_startPlayback(trackPath, onStart, onEnd) {
|
||
console.log('[MUSIC] 🎛️ _startPlayback вызван для:', trackPath);
|
||
console.log('[MUSIC] 🖥️ Платформа:', this.platform);
|
||
|
||
let command, args;
|
||
const currentVolume = this.isDucked ? this.duckedVolume : this.volume;
|
||
console.log('[MUSIC] 🔊 Громкость:', currentVolume);
|
||
|
||
// Выбираем команду в зависимости от ОС
|
||
if (this.platform === 'linux') {
|
||
// Используем mpg123 для MP3 или aplay для WAV
|
||
if (trackPath.endsWith('.mp3')) {
|
||
command = 'mpg123';
|
||
args = ['-g', Math.floor(currentVolume / 10), trackPath]; // mpg123 использует gain от 0 до 10
|
||
} else {
|
||
command = 'aplay';
|
||
args = [trackPath];
|
||
}
|
||
} else if (this.platform === 'win32') {
|
||
// Для Windows используем PowerShell MediaPlayer
|
||
command = 'powershell';
|
||
const escapedPath = trackPath.replace(/\\/g, '\\\\').replace(/"/g, '""');
|
||
args = ['-c', `
|
||
Add-Type -AssemblyName presentationCore;
|
||
$mediaPlayer = New-Object system.windows.media.mediaplayer;
|
||
$mediaPlayer.open('${escapedPath}');
|
||
$mediaPlayer.Volume = ${currentVolume / 100};
|
||
$mediaPlayer.Play();
|
||
while($mediaPlayer.Position -lt $mediaPlayer.NaturalDuration) {
|
||
Start-Sleep -Milliseconds 100;
|
||
}
|
||
`];
|
||
} else if (this.platform === 'darwin') {
|
||
// Для macOS используем afplay
|
||
command = 'afplay';
|
||
args = ['-v', currentVolume / 100, trackPath];
|
||
} else {
|
||
console.error('[MUSIC] Неподдерживаемая ОС:', this.platform);
|
||
return;
|
||
}
|
||
|
||
console.log('[MUSIC] 🚀 Выполняем команду:', command);
|
||
console.log('[MUSIC] 📝 Аргументы (первые 2):', args.slice(0, 2));
|
||
|
||
// Запускаем процесс
|
||
console.log('[MUSIC] 🎯 Вызываем spawn...');
|
||
this.process = spawn(command, args);
|
||
console.log('[MUSIC] ✅ Процесс запущен, PID:', this.process.pid);
|
||
this.isPlaying = true;
|
||
this.isPaused = false;
|
||
|
||
if (onStart) onStart();
|
||
|
||
this.process.on('close', (code) => {
|
||
console.log('[MUSIC] Воспроизведение завершено:', trackPath, ', код:', code);
|
||
|
||
if (this.isPlaying && this.repeatMode && this.currentTrack) {
|
||
// Повторное воспроизведение
|
||
console.log('[MUSIC] Повторное воспроизведение трека');
|
||
setTimeout(() => {
|
||
if (this.repeatMode && this.currentTrack) {
|
||
this._startPlayback(this.currentTrack, null, this.onTrackEnd);
|
||
}
|
||
}, 1000);
|
||
} else {
|
||
this.isPlaying = false;
|
||
this.currentTrack = null;
|
||
if (onEnd) onEnd();
|
||
}
|
||
});
|
||
|
||
this.process.on('error', (error) => {
|
||
console.error('[MUSIC] Ошибка процесса:', error.message);
|
||
this.isPlaying = false;
|
||
this.currentTrack = null;
|
||
});
|
||
}
|
||
|
||
// Установка громкости
|
||
setVolume(volume) {
|
||
this.volume = Math.max(0, Math.min(100, volume));
|
||
console.log('[MUSIC] Установлена громкость:', this.volume);
|
||
|
||
// Если музыка играет, перезапускаем с новой громкостью
|
||
if (this.isPlaying && this.currentTrack) {
|
||
const currentTrack = this.currentTrack;
|
||
this.stop();
|
||
this._startPlayback(currentTrack, null, this.onTrackEnd);
|
||
}
|
||
|
||
return this;
|
||
}
|
||
|
||
// Приглушение звука (во время баркера)
|
||
duck() {
|
||
if (!this.isDucked && this.isPlaying) {
|
||
console.log('[MUSIC] Приглушаем музыку для баркера');
|
||
this.isDucked = true;
|
||
|
||
if (this.currentTrack) {
|
||
const currentTrack = this.currentTrack;
|
||
this.stop();
|
||
this._startPlayback(currentTrack, null, this.onTrackEnd);
|
||
}
|
||
}
|
||
return this;
|
||
}
|
||
|
||
// Восстановление звука (после баркера)
|
||
unduck() {
|
||
if (this.isDucked && this.isPlaying) {
|
||
console.log('[MUSIC] Восстанавливаем громкость музыки');
|
||
this.isDucked = false;
|
||
|
||
if (this.currentTrack) {
|
||
const currentTrack = this.currentTrack;
|
||
this.stop();
|
||
this._startPlayback(currentTrack, null, this.onTrackEnd);
|
||
}
|
||
}
|
||
return this;
|
||
}
|
||
|
||
// Включение/отключение повтора
|
||
setRepeat(repeat) {
|
||
console.log('[MUSIC] setRepeat вызван с параметром:', repeat, 'тип:', typeof repeat);
|
||
console.log('[MUSIC] Текущий режим повтора до изменения:', this.repeatMode);
|
||
this.repeatMode = Boolean(repeat);
|
||
console.log('[MUSIC] Режим повтора установлен на:', this.repeatMode ? 'включен' : 'отключен');
|
||
return this;
|
||
}
|
||
|
||
// Получение состояния плеера
|
||
getStatus() {
|
||
return {
|
||
isPlaying: this.isPlaying,
|
||
isPaused: this.isPaused,
|
||
currentTrack: this.currentTrack ? path.basename(this.currentTrack) : null,
|
||
volume: this.volume,
|
||
repeatMode: this.repeatMode,
|
||
isDucked: this.isDucked
|
||
};
|
||
}
|
||
}
|
||
|
||
module.exports = MusicPlayer; |