- Клиентская часть Vue 3 + Vite - Серверная часть Node.js + WebSocket - Система авторизации и смен - Управление игровыми портами - Поддержка тем (светлая/темная) - Адаптивный дизайн 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
211 lines
6.9 KiB
JavaScript
211 lines
6.9 KiB
JavaScript
// VLC-based sound player с поддержкой паузы и позиции
|
||
const { spawn } = require('child_process');
|
||
const fs = require('fs');
|
||
const path = require('path');
|
||
const net = require('net');
|
||
|
||
class VLCSound {
|
||
constructor(filePath) {
|
||
this.path = path.resolve(filePath);
|
||
this.process = null;
|
||
this.isPlaying = false;
|
||
this.isPaused = false;
|
||
this.position = 0;
|
||
this.duration = 0;
|
||
this.vlcPath = this._findVLC();
|
||
this.rcInterface = null;
|
||
this.port = 8090 + Math.floor(Math.random() * 1000); // Случайный порт для RC интерфейса
|
||
|
||
console.log(`[VLC-AUDIO] Звуковой файл загружен: ${this.path}`);
|
||
console.log(`[VLC-AUDIO] VLC путь: ${this.vlcPath || 'не найден'}`);
|
||
}
|
||
|
||
_findVLC() {
|
||
const paths = [
|
||
'C:\\Program Files\\VideoLAN\\VLC\\vlc.exe',
|
||
'C:\\Program Files (x86)\\VideoLAN\\VLC\\vlc.exe',
|
||
path.join(process.env.PROGRAMFILES || '', 'VideoLAN\\VLC\\vlc.exe'),
|
||
path.join(process.env['PROGRAMFILES(X86)'] || '', 'VideoLAN\\VLC\\vlc.exe')
|
||
];
|
||
|
||
for (const vlcPath of paths) {
|
||
if (fs.existsSync(vlcPath)) {
|
||
return vlcPath;
|
||
}
|
||
}
|
||
return null;
|
||
}
|
||
|
||
stop() {
|
||
if (this.process) {
|
||
console.log(`[VLC-AUDIO] Останавливаем воспроизведение`);
|
||
|
||
// Сначала пробуем через RC интерфейс
|
||
this._sendCommand('quit');
|
||
|
||
setTimeout(() => {
|
||
if (this.process) {
|
||
this.process.kill();
|
||
}
|
||
}, 500);
|
||
|
||
this.process = null;
|
||
this.isPlaying = false;
|
||
this.isPaused = false;
|
||
this.position = 0;
|
||
}
|
||
return this;
|
||
}
|
||
|
||
play(onStart = null, onEnd = null, startPosition = 0) {
|
||
if (!this.vlcPath) {
|
||
console.error('[VLC-AUDIO] VLC не найден! Используйте обычный плеер.');
|
||
return this;
|
||
}
|
||
|
||
console.log(`[VLC-AUDIO] Воспроизведение: ${this.path} с позиции ${startPosition}с`);
|
||
|
||
// Останавливаем предыдущее воспроизведение
|
||
this.stop();
|
||
|
||
// Проверяем файл
|
||
if (!fs.existsSync(this.path)) {
|
||
console.error(`[VLC-AUDIO] Файл не найден: ${this.path}`);
|
||
return this;
|
||
}
|
||
|
||
// Запускаем VLC с RC интерфейсом
|
||
const args = [
|
||
'--intf', 'rc',
|
||
'--rc-host', `localhost:${this.port}`,
|
||
'--no-video',
|
||
'--play-and-exit',
|
||
this.path
|
||
];
|
||
|
||
if (startPosition > 0) {
|
||
args.push('--start-time', startPosition.toString());
|
||
}
|
||
|
||
this.process = spawn(this.vlcPath, args, {
|
||
stdio: 'pipe',
|
||
windowsHide: true
|
||
});
|
||
|
||
this.isPlaying = true;
|
||
this.isPaused = false;
|
||
|
||
// Подключаемся к RC интерфейсу через небольшую задержку
|
||
setTimeout(() => {
|
||
this._connectRC();
|
||
}, 1000);
|
||
|
||
if (onStart) {
|
||
setTimeout(onStart, 500);
|
||
}
|
||
|
||
this.process.on('close', (code) => {
|
||
console.log(`[VLC-AUDIO] Воспроизведение завершено`);
|
||
this.isPlaying = false;
|
||
this.isPaused = false;
|
||
this.process = null;
|
||
if (onEnd) onEnd();
|
||
});
|
||
|
||
this.process.on('error', (error) => {
|
||
console.error(`[VLC-AUDIO] Ошибка процесса:`, error.message);
|
||
this.isPlaying = false;
|
||
this.process = null;
|
||
});
|
||
|
||
return this;
|
||
}
|
||
|
||
pause() {
|
||
if (this.isPlaying && !this.isPaused) {
|
||
console.log('[VLC-AUDIO] Пауза');
|
||
this._sendCommand('pause');
|
||
this.isPaused = true;
|
||
this.isPlaying = false;
|
||
|
||
// Получаем текущую позицию
|
||
this._sendCommand('get_time', (response) => {
|
||
const match = response.match(/(\d+)/);
|
||
if (match) {
|
||
this.position = parseInt(match[1]);
|
||
console.log('[VLC-AUDIO] Позиция сохранена:', this.position, 'сек');
|
||
}
|
||
});
|
||
}
|
||
return this;
|
||
}
|
||
|
||
resume() {
|
||
if (this.isPaused && this.process) {
|
||
console.log('[VLC-AUDIO] Возобновление');
|
||
this._sendCommand('pause'); // В VLC pause переключает состояние
|
||
this.isPaused = false;
|
||
this.isPlaying = true;
|
||
}
|
||
return this;
|
||
}
|
||
|
||
setVolume(volume) {
|
||
// VLC принимает громкость от 0 до 256 (256 = 100%)
|
||
const vlcVolume = Math.floor((volume / 100) * 256);
|
||
this._sendCommand(`volume ${vlcVolume}`);
|
||
console.log('[VLC-AUDIO] Громкость установлена:', volume + '%');
|
||
return this;
|
||
}
|
||
|
||
getPosition(callback) {
|
||
this._sendCommand('get_time', (response) => {
|
||
const match = response.match(/(\d+)/);
|
||
if (match && callback) {
|
||
callback(parseInt(match[1]));
|
||
}
|
||
});
|
||
}
|
||
|
||
_connectRC() {
|
||
this.rcInterface = new net.Socket();
|
||
|
||
this.rcInterface.connect(this.port, 'localhost', () => {
|
||
console.log('[VLC-AUDIO] Подключен к RC интерфейсу');
|
||
});
|
||
|
||
this.rcInterface.on('error', (err) => {
|
||
console.log('[VLC-AUDIO] RC интерфейс недоступен:', err.message);
|
||
});
|
||
}
|
||
|
||
_sendCommand(command, callback) {
|
||
if (this.rcInterface && !this.rcInterface.destroyed) {
|
||
this.rcInterface.write(command + '\n');
|
||
|
||
if (callback) {
|
||
this.rcInterface.once('data', (data) => {
|
||
callback(data.toString());
|
||
});
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// Фабрика для выбора правильного плеера
|
||
function createSound(filePath) {
|
||
const vlcSound = new VLCSound(filePath);
|
||
|
||
// Если VLC найден, используем его
|
||
if (vlcSound.vlcPath) {
|
||
return vlcSound;
|
||
}
|
||
|
||
// Иначе возвращаем обычный плеер
|
||
console.log('[AUDIO] VLC не найден, используем стандартный плеер');
|
||
const StandardSound = require('./node-aplay');
|
||
return new StandardSound(filePath);
|
||
}
|
||
|
||
module.exports = createSound;
|
||
module.exports.VLCSound = VLCSound; |