Pulse / static /js /player.js
Starchik1's picture
Upload 7 files
6bab515 verified
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 = '<i class="fas fa-pause"></i>';
this.tracks[this.currentTrackIndex].classList.add('active');
}
pause() {
this.audio.pause();
this.isPlaying = false;
this.playBtn.innerHTML = '<i class="fas fa-play"></i>';
}
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 = `
<span class="filename">${filename}</span>
<span class="message">${message}</span>
`;
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 = `
<span class="track-name">${filename}</span>
<button class="favorite-btn" data-track="${filename}">
<i class="fas fa-heart"></i>
</button>
`;
// Добавляем обработчик клика для нового трека
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();
});