|
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());
|
|
|
|
|
|
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');
|
|
});
|
|
});
|
|
|
|
|
|
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', () => {
|
|
|
|
this.tabButtons.forEach(btn => btn.classList.remove('active'));
|
|
document.querySelectorAll('.tab-content').forEach(content => content.classList.remove('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();
|
|
|
|
|
|
document.querySelectorAll('.track-item').forEach(t => t.classList.remove('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) {
|
|
|
|
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 {
|
|
|
|
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;
|
|
}
|
|
|
|
|
|
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();
|
|
}); |