kv-music/js/player.js
2025-10-11 19:49:17 +03:00

194 lines
No EOL
6.4 KiB
JavaScript

//js/player.js
import { REPEAT_MODE, SVG_PLAY, SVG_PAUSE, formatTime } from './utils.js';
export class Player {
constructor(audioElement, api, quality = 'LOSSLESS') {
this.audio = audioElement;
this.api = api;
this.quality = quality;
this.queue = [];
this.shuffledQueue = [];
this.originalQueueBeforeShuffle = [];
this.currentQueueIndex = -1;
this.shuffleActive = false;
this.repeatMode = REPEAT_MODE.OFF;
}
setQuality(quality) {
this.quality = quality;
}
async playTrackFromQueue() {
const currentQueue = this.shuffleActive ? this.shuffledQueue : this.queue;
if (this.currentQueueIndex < 0 || this.currentQueueIndex >= currentQueue.length) {
return;
}
const track = currentQueue[this.currentQueueIndex];
document.querySelector('.now-playing-bar .cover').src =
this.api.getCoverUrl(track.album?.cover, '1280');
document.querySelector('.now-playing-bar .title').textContent = track.title;
document.querySelector('.now-playing-bar .artist').textContent = track.artist?.name || 'Unknown Artist';
document.title = `${track.title}${track.artist?.name || 'Unknown'}`;
this.updatePlayingTrackIndicator();
this.updateMediaSession(track);
try {
const streamUrl = await this.api.getStreamUrl(track.id, this.quality);
this.audio.src = streamUrl;
await this.audio.play();
} catch (error) {
console.error(`Could not get track URL for: ${track.title}`, error);
document.querySelector('.now-playing-bar .title').textContent = `Error: ${track.title}`;
document.querySelector('.now-playing-bar .artist').textContent = error.message || 'Could not load track';
document.querySelector('.play-pause-btn').innerHTML = SVG_PLAY;
}
}
playNext() {
const currentQueue = this.shuffleActive ? this.shuffledQueue : this.queue;
const isLastTrack = this.currentQueueIndex >= currentQueue.length - 1;
if (this.repeatMode === REPEAT_MODE.ONE) {
this.audio.currentTime = 0;
this.audio.play();
return;
}
if (!isLastTrack) {
this.currentQueueIndex++;
} else if (this.repeatMode === REPEAT_MODE.ALL) {
this.currentQueueIndex = 0;
} else {
return;
}
this.playTrackFromQueue();
}
playPrev() {
if (this.audio.currentTime > 3) {
this.audio.currentTime = 0;
} else if (this.currentQueueIndex > 0) {
this.currentQueueIndex--;
this.playTrackFromQueue();
}
}
handlePlayPause() {
if (!this.audio.src) return;
this.audio.paused ? this.audio.play() : this.audio.pause();
}
seekBackward(seconds = 10) {
const newTime = Math.max(0, this.audio.currentTime - seconds);
this.audio.currentTime = newTime;
}
seekForward(seconds = 10) {
const duration = this.audio.duration || 0;
const newTime = Math.min(duration, this.audio.currentTime + seconds);
this.audio.currentTime = newTime;
}
toggleShuffle() {
this.shuffleActive = !this.shuffleActive;
if (this.shuffleActive) {
this.originalQueueBeforeShuffle = [...this.queue];
const currentTrack = this.queue[this.currentQueueIndex];
this.shuffledQueue = [...this.queue].sort(() => Math.random() - 0.5);
this.currentQueueIndex = this.shuffledQueue.findIndex(t => t.id === currentTrack?.id);
if (this.currentQueueIndex === -1 && currentTrack) {
this.shuffledQueue.unshift(currentTrack);
this.currentQueueIndex = 0;
}
} else {
const currentTrack = this.shuffledQueue[this.currentQueueIndex];
this.queue = [...this.originalQueueBeforeShuffle];
this.currentQueueIndex = this.queue.findIndex(t => t.id === currentTrack?.id);
}
}
toggleRepeat() {
this.repeatMode = (this.repeatMode + 1) % 3;
return this.repeatMode;
}
setQueue(tracks, startIndex = 0) {
this.queue = tracks;
this.currentQueueIndex = startIndex;
this.shuffleActive = false;
}
addToQueue(track) {
this.queue.push(track);
}
getCurrentQueue() {
return this.shuffleActive ? this.shuffledQueue : this.queue;
}
updatePlayingTrackIndicator() {
const currentTrack = this.getCurrentQueue()[this.currentQueueIndex];
document.querySelectorAll('.track-item').forEach(item => {
item.classList.toggle('playing',
currentTrack && item.dataset.trackId == currentTrack.id
);
});
}
updateMediaSession(track) {
if (!('mediaSession' in navigator)) return;
const artwork = [];
const sizes = ['1280'];
const coverId = track.album?.cover;
if (coverId) {
sizes.forEach(size => {
const url = this.api.getCoverUrl(coverId, size);
artwork.push({
src: url,
sizes: `${size}x${size}`,
type: 'image/jpeg'
});
});
}
navigator.mediaSession.metadata = new MediaMetadata({
title: track.title || 'Unknown Title',
artist: track.artist?.name || 'Unknown Artist',
album: track.album?.title || 'Unknown Album',
artwork: artwork.length > 0 ? artwork : undefined
});
navigator.mediaSession.playbackState = this.audio.paused ? 'paused' : 'playing';
}
updateMediaSessionPlaybackState() {
if ('mediaSession' in navigator) {
navigator.mediaSession.playbackState = this.audio.paused ? 'paused' : 'playing';
}
}
updateMediaSessionPositionState() {
if ('mediaSession' in navigator && 'setPositionState' in navigator.mediaSession) {
if (this.audio.duration && !isNaN(this.audio.duration)) {
try {
navigator.mediaSession.setPositionState({
duration: this.audio.duration,
playbackRate: this.audio.playbackRate,
position: this.audio.currentTime
});
} catch (error) {
console.debug('Failed to update position state:', error);
}
}
}
}
}