163 lines
5.9 KiB
JavaScript
163 lines
5.9 KiB
JavaScript
// shared-audio-player.js
|
|
// Unified audio player logic for both streams and personal player
|
|
|
|
import { globalAudioManager } from './global-audio-manager.js';
|
|
|
|
export class SharedAudioPlayer {
|
|
constructor({ playerType, getStreamUrl, onUpdateButton }) {
|
|
this.playerType = playerType; // 'streams' or 'personal'
|
|
this.getStreamUrl = getStreamUrl; // function(uid) => url
|
|
this.onUpdateButton = onUpdateButton; // function(button, isPlaying)
|
|
this.audioElement = null;
|
|
this.currentUid = null;
|
|
this.isPlaying = false;
|
|
this.currentButton = null;
|
|
this._eventHandlers = {};
|
|
|
|
// Register stop listener
|
|
globalAudioManager.addListener(playerType, () => {
|
|
this.stop();
|
|
});
|
|
}
|
|
|
|
pause() {
|
|
if (this.audioElement && !this.audioElement.paused && !this.audioElement.ended) {
|
|
this.audioElement.pause();
|
|
this.isPlaying = false;
|
|
if (this.onUpdateButton && this.currentButton) {
|
|
this.onUpdateButton(this.currentButton, false);
|
|
}
|
|
}
|
|
}
|
|
|
|
async play(uid, button) {
|
|
const ctx = `[SharedAudioPlayer][${this.playerType}]${uid ? `[${uid}]` : ''}`;
|
|
const isSameUid = this.currentUid === uid;
|
|
const isActive = this.audioElement && !this.audioElement.paused && !this.audioElement.ended;
|
|
|
|
// Guard: If already playing the requested UID and not paused/ended, do nothing
|
|
if (isSameUid && isActive) {
|
|
if (this.onUpdateButton) this.onUpdateButton(button || this.currentButton, true);
|
|
return;
|
|
}
|
|
|
|
// If same UID but paused, resume
|
|
if (isSameUid && this.audioElement && this.audioElement.paused && !this.audioElement.ended) {
|
|
try {
|
|
await this.audioElement.play();
|
|
this.isPlaying = true;
|
|
if (this.onUpdateButton) this.onUpdateButton(button || this.currentButton, true);
|
|
globalAudioManager.startPlayback(this.playerType, uid);
|
|
} catch (err) {
|
|
this.isPlaying = false;
|
|
if (this.onUpdateButton) this.onUpdateButton(button || this.currentButton, false);
|
|
console.error(`${ctx} play() resume failed:`, err);
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Otherwise, stop current and start new
|
|
if (!isSameUid && this.audioElement) {
|
|
} else {
|
|
}
|
|
this.stop();
|
|
this.currentUid = uid;
|
|
this.currentButton = button;
|
|
const url = this.getStreamUrl(uid);
|
|
this.audioElement = new Audio(url);
|
|
this.audioElement.preload = 'auto';
|
|
this.audioElement.crossOrigin = 'anonymous';
|
|
this.audioElement.style.display = 'none';
|
|
document.body.appendChild(this.audioElement);
|
|
this._attachEventHandlers();
|
|
try {
|
|
await this.audioElement.play();
|
|
this.isPlaying = true;
|
|
if (this.onUpdateButton) this.onUpdateButton(button, true);
|
|
globalAudioManager.startPlayback(this.playerType, uid);
|
|
} catch (err) {
|
|
this.isPlaying = false;
|
|
if (this.onUpdateButton) this.onUpdateButton(button, false);
|
|
console.error(`${ctx} play() failed:`, err);
|
|
}
|
|
}
|
|
|
|
stop() {
|
|
if (this.audioElement) {
|
|
this._removeEventHandlers();
|
|
try {
|
|
this.audioElement.pause();
|
|
this.audioElement.removeAttribute('src');
|
|
this.audioElement.load();
|
|
if (this.audioElement.parentNode) {
|
|
this.audioElement.parentNode.removeChild(this.audioElement);
|
|
}
|
|
} catch (e) {
|
|
console.warn('[shared-audio-player] Error cleaning up audio element:', e);
|
|
}
|
|
this.audioElement = null;
|
|
}
|
|
this.isPlaying = false;
|
|
this.currentUid = null;
|
|
if (this.currentButton && this.onUpdateButton) {
|
|
this.onUpdateButton(this.currentButton, false);
|
|
}
|
|
this.currentButton = null;
|
|
}
|
|
|
|
_attachEventHandlers() {
|
|
if (!this.audioElement) return;
|
|
const ctx = `[SharedAudioPlayer][${this.playerType}]${this.currentUid ? `[${this.currentUid}]` : ''}`;
|
|
const logEvent = (event) => {
|
|
// Debug logging disabled
|
|
};
|
|
// Core handlers
|
|
const onPlay = (e) => {
|
|
logEvent(e);
|
|
this.isPlaying = true;
|
|
if (this.currentButton && this.onUpdateButton) this.onUpdateButton(this.currentButton, true);
|
|
};
|
|
const onPause = (e) => {
|
|
logEvent(e);
|
|
// console.trace(`${ctx} Audio pause stack trace:`);
|
|
this.isPlaying = false;
|
|
if (this.currentButton && this.onUpdateButton) this.onUpdateButton(this.currentButton, false);
|
|
};
|
|
const onEnded = (e) => {
|
|
logEvent(e);
|
|
this.isPlaying = false;
|
|
if (this.currentButton && this.onUpdateButton) this.onUpdateButton(this.currentButton, false);
|
|
};
|
|
const onError = (e) => {
|
|
logEvent(e);
|
|
this.isPlaying = false;
|
|
if (this.currentButton && this.onUpdateButton) this.onUpdateButton(this.currentButton, false);
|
|
console.error(`${ctx} Audio error:`, e);
|
|
};
|
|
// Attach handlers
|
|
this.audioElement.addEventListener('play', onPlay);
|
|
this.audioElement.addEventListener('pause', onPause);
|
|
this.audioElement.addEventListener('ended', onEnded);
|
|
this.audioElement.addEventListener('error', onError);
|
|
// Attach debug logging for all relevant events
|
|
const debugEvents = [
|
|
'abort','canplay','canplaythrough','durationchange','emptied','encrypted','loadeddata','loadedmetadata',
|
|
'loadstart','playing','progress','ratechange','seeked','seeking','stalled','suspend','timeupdate','volumechange','waiting'
|
|
];
|
|
debugEvents.forEach(evt => {
|
|
this.audioElement.addEventListener(evt, logEvent);
|
|
}); // Logging now disabled
|
|
this._eventHandlers = { onPlay, onPause, onEnded, onError, debugEvents, logEvent };
|
|
}
|
|
|
|
_removeEventHandlers() {
|
|
if (!this.audioElement || !this._eventHandlers) return;
|
|
const { onPlay, onPause, onEnded, onError } = this._eventHandlers;
|
|
if (onPlay) this.audioElement.removeEventListener('play', onPlay);
|
|
if (onPause) this.audioElement.removeEventListener('pause', onPause);
|
|
if (onEnded) this.audioElement.removeEventListener('ended', onEnded);
|
|
if (onError) this.audioElement.removeEventListener('error', onError);
|
|
this._eventHandlers = {};
|
|
}
|
|
}
|