Files
client/greyscale/src/player.js
T
2026-04-17 16:52:51 -04:00

122 lines
3.8 KiB
JavaScript

import { formatDuration } from './utils.js';
export class SimplePlayer {
constructor({ audio, progressEl, currentTimeEl, durationEl }) {
this.audio = audio;
this.progressEl = progressEl;
this.currentTimeEl = currentTimeEl;
this.durationEl = durationEl;
this.queue = [];
this.currentIndex = -1;
this.currentStreamUrl = '';
this.dashPlayer = window.dashjs.MediaPlayer().create();
this.usingDash = false;
this.onTrackChanged = null;
this.onTimeUpdate = null;
this.resolveStreamFn = null;
this.bindAudioEvents();
}
bindAudioEvents() {
this.audio.addEventListener('timeupdate', () => {
const duration = this.audio.duration || 0;
const current = this.audio.currentTime || 0;
const value = duration > 0 ? (current / duration) * 100 : 0;
this.progressEl.value = String(value);
this.currentTimeEl.textContent = formatDuration(current);
if (typeof this.onTimeUpdate === 'function') {
this.onTimeUpdate(current, duration, this.getCurrentTrack());
}
});
this.audio.addEventListener('loadedmetadata', () => {
this.durationEl.textContent = formatDuration(this.audio.duration || 0);
});
this.audio.addEventListener('ended', async () => {
await this.playNext();
});
this.progressEl.addEventListener('input', () => {
const duration = this.audio.duration || 0;
if (duration <= 0) return;
const target = (Number(this.progressEl.value) / 100) * duration;
this.audio.currentTime = target;
});
}
setResolver(resolveStreamFn) {
this.resolveStreamFn = resolveStreamFn;
}
setQueue(tracks, startIndex = 0) {
this.queue = Array.isArray(tracks) ? tracks : [];
this.currentIndex = Math.max(0, Math.min(startIndex, this.queue.length - 1));
}
getCurrentTrack() {
if (this.currentIndex < 0 || this.currentIndex >= this.queue.length) return null;
return this.queue[this.currentIndex];
}
async playStream(streamUrl, isDash = false) {
if (this.currentStreamUrl?.startsWith('blob:') && this.currentStreamUrl !== streamUrl) {
URL.revokeObjectURL(this.currentStreamUrl);
}
if (this.usingDash) {
this.dashPlayer.reset();
this.usingDash = false;
}
this.currentStreamUrl = streamUrl;
if (isDash) {
this.dashPlayer.initialize(this.audio, streamUrl, true);
this.usingDash = true;
return;
}
this.audio.src = streamUrl;
await this.audio.play();
}
async playCurrent(resolveStreamFn = null) {
const resolver = resolveStreamFn || this.resolveStreamFn;
const track = this.getCurrentTrack();
if (!track || typeof resolver !== 'function') return;
const { streamUrl, isDash } = await resolver(track);
await this.playStream(streamUrl, isDash);
if (typeof this.onTrackChanged === 'function') {
this.onTrackChanged(track);
}
}
async playNext(resolveStreamFn = null) {
if (this.queue.length === 0) return;
this.currentIndex = (this.currentIndex + 1) % this.queue.length;
await this.playCurrent(resolveStreamFn);
}
async playPrev(resolveStreamFn = null) {
if (this.queue.length === 0) return;
this.currentIndex = (this.currentIndex - 1 + this.queue.length) % this.queue.length;
await this.playCurrent(resolveStreamFn);
}
async togglePlayPause() {
if (this.audio.paused) {
await this.audio.play();
} else {
this.audio.pause();
}
}
}