class MusicPlayer { constructor() { this.audio = new Audio(); this.currentTrackIndex = 0; this.isPlaying = false; this.isShuffled = false; this.shuffledIndexes = []; // Элементы управления this.playBtn = document.getElementById('play-btn'); this.prevBtn = document.getElementById('prev-btn'); this.nextBtn = document.getElementById('next-btn'); this.shuffleBtn = document.getElementById('shuffle-btn'); this.volumeControl = document.getElementById('volume'); this.progressBar = document.getElementById('progress-bar'); this.progress = document.getElementById('progress'); this.currentTimeSpan = document.getElementById('current-time'); this.durationSpan = document.getElementById('duration'); this.currentTrackName = document.getElementById('current-track-name'); this.trackList = document.getElementById('track-list'); this.likedList = document.getElementById('liked-list'); this.tabButtons = document.querySelectorAll('.tab-btn'); this.searchInput = document.getElementById('search-input'); this.dropArea = document.getElementById('drop-area'); this.fileInput = document.getElementById('file-input'); this.tracks = Array.from(this.trackList.getElementsByClassName('track-item')); this.allTracks = [...this.tracks]; // Сохраняем копию всех треков this.favoriteButtons = document.querySelectorAll('.favorite-btn'); this.initEventListeners(); } initEventListeners() { // Поиск треков this.searchInput.addEventListener('input', (e) => this.searchTracks(e.target.value)); // Кнопки управления this.playBtn.addEventListener('click', () => this.togglePlay()); this.prevBtn.addEventListener('click', () => this.playPrevious()); this.nextBtn.addEventListener('click', () => this.playNext()); this.shuffleBtn.addEventListener('click', () => this.toggleShuffle()); // Обработчики для drag-and-drop загрузки файлов if (this.dropArea && this.fileInput) { // Предотвращаем стандартное поведение браузера при перетаскивании файлов ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => { this.dropArea.addEventListener(eventName, (e) => { e.preventDefault(); e.stopPropagation(); }); }); // Добавляем визуальные эффекты при перетаскивании ['dragenter', 'dragover'].forEach(eventName => { this.dropArea.addEventListener(eventName, () => { this.dropArea.classList.add('highlight'); }); }); ['dragleave', 'drop'].forEach(eventName => { this.dropArea.addEventListener(eventName, () => { this.dropArea.classList.remove('highlight'); }); }); // Обработка события drop для загрузки файлов this.dropArea.addEventListener('drop', (e) => { const files = e.dataTransfer.files; if (files.length) { this.uploadFiles(files); } }); // Обработка выбора файлов через кнопку this.fileInput.addEventListener('change', (e) => { const files = e.target.files; if (files.length) { this.uploadFiles(files); } }); } // Обработчики для вкладок this.tabButtons.forEach(button => { button.addEventListener('click', () => { // Удаляем класс active у всех кнопок и контента this.tabButtons.forEach(btn => btn.classList.remove('active')); document.querySelectorAll('.tab-content').forEach(content => content.classList.remove('active')); // Добавляем класс active текущей кнопке и соответствующему контенту button.classList.add('active'); const tabName = button.dataset.tab; const content = document.querySelector(`.tab-content[data-tab="${tabName}"]`); if (content) { content.classList.add('active'); // Обновляем список треков this.tracks = Array.from(content.getElementsByClassName('track-item')); // Переназначаем обработчики событий для треков this.tracks.forEach((track, index) => { track.addEventListener('click', (e) => { if (!e.target.closest('.favorite-btn')) { this.currentTrackIndex = index; this.loadAndPlayTrack(); } }); }); } }); }); // Управление громкостью this.volumeControl.addEventListener('input', (e) => { this.audio.volume = e.target.value; }); // Прогресс воспроизведения this.progressBar.addEventListener('click', (e) => { const rect = this.progressBar.getBoundingClientRect(); const percent = (e.clientX - rect.left) / rect.width; this.audio.currentTime = percent * this.audio.duration; }); // События аудио this.audio.addEventListener('timeupdate', () => this.updateProgress()); this.audio.addEventListener('ended', () => this.playNext()); // Клики по трекам this.tracks.forEach((track, index) => { track.addEventListener('click', (e) => { if (!e.target.closest('.favorite-btn')) { this.currentTrackIndex = index; this.loadAndPlayTrack(); } }); }); // Обработчики для кнопок избранного this.favoriteButtons.forEach(btn => { btn.addEventListener('click', async (e) => { e.preventDefault(); const track = btn.dataset.track; const response = await fetch(`/favorite/${track}`, { method: 'POST', headers: { 'Content-Type': 'application/json' } }); const result = await response.json(); if (result.status === 'added') { btn.classList.add('active'); } else { btn.classList.remove('active'); } }); }); } togglePlay() { if (this.isPlaying) { this.pause(); } else { if (this.audio.src) { this.play(); } else if (this.tracks.length > 0) { this.loadAndPlayTrack(); } } } play() { this.audio.play(); this.isPlaying = true; this.playBtn.innerHTML = ''; this.tracks[this.currentTrackIndex].classList.add('active'); } pause() { this.audio.pause(); this.isPlaying = false; this.playBtn.innerHTML = ''; } playPrevious() { if (this.tracks.length === 0) return; this.tracks[this.currentTrackIndex].classList.remove('active'); if (this.isShuffled) { const currentShuffleIndex = this.shuffledIndexes.indexOf(this.currentTrackIndex); const prevShuffleIndex = (currentShuffleIndex - 1 + this.tracks.length) % this.tracks.length; this.currentTrackIndex = this.shuffledIndexes[prevShuffleIndex]; } else { this.currentTrackIndex = (this.currentTrackIndex - 1 + this.tracks.length) % this.tracks.length; } this.loadAndPlayTrack(); } playNext() { if (this.tracks.length === 0) return; this.tracks[this.currentTrackIndex].classList.remove('active'); if (this.isShuffled) { const currentShuffleIndex = this.shuffledIndexes.indexOf(this.currentTrackIndex); const nextShuffleIndex = (currentShuffleIndex + 1) % this.tracks.length; this.currentTrackIndex = this.shuffledIndexes[nextShuffleIndex]; } else { this.currentTrackIndex = (this.currentTrackIndex + 1) % this.tracks.length; } this.loadAndPlayTrack(); } loadAndPlayTrack() { if (!this.tracks || this.tracks.length === 0 || this.currentTrackIndex >= this.tracks.length) { console.error('No tracks available or invalid track index'); return; } const track = this.tracks[this.currentTrackIndex]; if (!track || !track.dataset.src) { console.error('Invalid track data'); return; } const trackSrc = track.dataset.src; this.audio.src = trackSrc; this.currentTrackName.textContent = track.textContent.trim(); // Удаляем класс active у всех треков в обеих вкладках document.querySelectorAll('.track-item').forEach(t => t.classList.remove('active')); // Добавляем класс active текущему треку track.classList.add('active'); this.play(); } updateProgress() { const duration = this.audio.duration; const currentTime = this.audio.currentTime; if (duration) { // Обновляем прогресс-бар const progressPercent = (currentTime / duration) * 100; this.progress.style.width = progressPercent + '%'; // Обновляем время this.currentTimeSpan.textContent = this.formatTime(currentTime); this.durationSpan.textContent = this.formatTime(duration); } } formatTime(seconds) { const minutes = Math.floor(seconds / 60); const remainingSeconds = Math.floor(seconds % 60); return `${minutes}:${remainingSeconds.toString().padStart(2, '0')}`; } toggleShuffle() { this.isShuffled = !this.isShuffled; if (this.isShuffled) { // Активируем кнопку shuffle this.shuffleBtn.classList.add('active'); // Создаем массив индексов и перемешиваем его this.shuffledIndexes = Array.from({ length: this.tracks.length }, (_, i) => i); this.shuffleArray(this.shuffledIndexes); // Убедимся, что текущий трек будет первым в перемешанном списке const currentIndex = this.shuffledIndexes.indexOf(this.currentTrackIndex); if (currentIndex !== -1) { // Меняем местами текущий индекс с первым элементом [this.shuffledIndexes[0], this.shuffledIndexes[currentIndex]] = [this.shuffledIndexes[currentIndex], this.shuffledIndexes[0]]; } } else { // Деактивируем кнопку shuffle this.shuffleBtn.classList.remove('active'); } } shuffleArray(array) { // Алгоритм Фишера-Йейтса для перемешивания массива for (let i = array.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [array[i], array[j]] = [array[j], array[i]]; } return array; } searchTracks(query) { query = query.toLowerCase(); const activeTab = document.querySelector('.tab-content.active'); const tracksList = activeTab.getElementsByClassName('track-item'); Array.from(tracksList).forEach(track => { const trackName = track.querySelector('.track-name').textContent.toLowerCase(); if (trackName.includes(query)) { track.style.display = ''; } else { track.style.display = 'none'; } }); } async uploadFiles(files) { const uploadList = document.getElementById('upload-list'); const uploadProgressContainer = document.getElementById('upload-progress-container'); if (uploadProgressContainer) { uploadProgressContainer.style.display = 'block'; } if (uploadList) { uploadList.innerHTML = ''; } // Перебираем все файлы и загружаем их на сервер for (const file of files) { // Проверяем, что файл имеет допустимый формат if (!file.name.match(/\.(mp3|wav|m4a|webm)$/i)) { this.showUploadStatus(file.name, 'Недопустимый формат файла', 'error'); continue; } // Создаем объект FormData для отправки файла const formData = new FormData(); formData.append('file', file); try { // Отображаем статус загрузки this.showUploadStatus(file.name, 'Загрузка...', 'loading'); // Отправляем файл на сервер const response = await fetch('/upload', { method: 'POST', body: formData }); const result = await response.json(); if (result.status === 'success') { this.showUploadStatus(file.name, 'Загружено успешно', 'success'); // Добавляем новый трек в список без перезагрузки страницы this.addTrackToList(result.filename); } else { this.showUploadStatus(file.name, result.message || 'Ошибка загрузки', 'error'); } } catch (error) { console.error('Ошибка загрузки файла:', error); this.showUploadStatus(file.name, 'Ошибка загрузки', 'error'); } } } showUploadStatus(filename, message, status) { const uploadList = document.getElementById('upload-list'); if (!uploadList) return; const statusItem = document.createElement('div'); statusItem.className = `upload-status ${status}`; statusItem.innerHTML = ` ${filename} ${message} `; uploadList.appendChild(statusItem); } addTrackToList(filename) { // Добавляем новый трек в список всех треков const trackList = document.getElementById('track-list'); if (!trackList) return; const newTrack = document.createElement('li'); newTrack.className = 'track-item'; newTrack.dataset.src = `/play/${filename}`; newTrack.innerHTML = ` ${filename} `; // Добавляем обработчик клика для нового трека newTrack.addEventListener('click', (e) => { if (!e.target.closest('.favorite-btn')) { this.currentTrackIndex = this.tracks.length; this.tracks.push(newTrack); this.loadAndPlayTrack(); } }); // Добавляем обработчик для кнопки избранного const favoriteBtn = newTrack.querySelector('.favorite-btn'); favoriteBtn.addEventListener('click', async (e) => { e.preventDefault(); const track = favoriteBtn.dataset.track; const response = await fetch(`/favorite/${track}`, { method: 'POST', headers: { 'Content-Type': 'application/json' } }); const result = await response.json(); if (result.status === 'added') { favoriteBtn.classList.add('active'); } else { favoriteBtn.classList.remove('active'); } }); trackList.appendChild(newTrack); // Обновляем список треков this.tracks = Array.from(trackList.getElementsByClassName('track-item')); } } // Инициализация плеера document.addEventListener('DOMContentLoaded', () => { const player = new MusicPlayer(); });