Fix audio player synchronization between streams and personal pages

- Add global audio manager to coordinate playback between different players
- Integrate synchronization into streams-ui.js (streams page player)
- Integrate synchronization into app.js (personal stream player)
- Remove simultaneous playback issues - only one audio plays at a time
- Clean transitions when switching between streams and personal audio

Fixes issue where starting audio on one page didn't stop audio on the other page.
This commit is contained in:
oib
2025-07-27 09:13:55 +02:00
parent fc4a9c926f
commit a9a1c22fee
3 changed files with 160 additions and 0 deletions

View File

@ -3,6 +3,7 @@
import { playBeep } from "./sound.js"; import { playBeep } from "./sound.js";
import { showToast } from "./toast.js"; import { showToast } from "./toast.js";
import { injectNavigation } from "./inject-nav.js"; import { injectNavigation } from "./inject-nav.js";
import { globalAudioManager } from './global-audio-manager.js';
// Global audio state // Global audio state
let globalAudio = null; let globalAudio = null;
@ -963,6 +964,19 @@ document.addEventListener("DOMContentLoaded", () => {
// Initialize components // Initialize components
initNavigation(); initNavigation();
// Register with global audio manager to handle stop requests from other players
globalAudioManager.addListener('personal', () => {
console.log('[app.js] Received stop request from global audio manager');
const audio = getOrCreateAudioElement();
if (audio && !audio.paused) {
audio.pause();
const playButton = document.querySelector('.play-pause-btn');
if (playButton) {
updatePlayPauseButton(audio, playButton);
}
}
});
// Initialize account deletion section visibility // Initialize account deletion section visibility
handlePageNavigation(); handlePageNavigation();
@ -1011,6 +1025,10 @@ document.addEventListener("DOMContentLoaded", () => {
return; return;
} }
// Notify global audio manager that personal player is starting
const uid = localStorage.getItem('uid') || 'personal-stream';
globalAudioManager.startPlayback('personal', uid);
// Store the current play promise to handle aborts // Store the current play promise to handle aborts
const playPromise = audio.play(); const playPromise = audio.play();
@ -1046,6 +1064,10 @@ document.addEventListener("DOMContentLoaded", () => {
}); });
} else { } else {
audio.pause(); audio.pause();
// Notify global audio manager that personal player has stopped
globalAudioManager.stopPlayback('personal');
if (window.currentlyPlayingAudio === audio) { if (window.currentlyPlayingAudio === audio) {
window.currentlyPlayingAudio = null; window.currentlyPlayingAudio = null;
window.currentlyPlayingButton = null; window.currentlyPlayingButton = null;

View File

@ -0,0 +1,125 @@
/**
* Global Audio Manager
* Coordinates audio playback between different components to ensure only one audio plays at a time
*/
class GlobalAudioManager {
constructor() {
this.currentPlayer = null; // 'streams' or 'personal' or null
this.currentUid = null;
this.listeners = new Set();
// Bind methods
this.startPlayback = this.startPlayback.bind(this);
this.stopPlayback = this.stopPlayback.bind(this);
this.addListener = this.addListener.bind(this);
this.removeListener = this.removeListener.bind(this);
}
/**
* Register a player that wants to start playback
* @param {string} playerType - 'streams' or 'personal'
* @param {string} uid - The UID being played
* @param {Object} playerInstance - Reference to the player instance
*/
startPlayback(playerType, uid, playerInstance = null) {
// If the same player is already playing the same UID, allow it
if (this.currentPlayer === playerType && this.currentUid === uid) {
return true;
}
// Stop any currently playing audio
if (this.currentPlayer && this.currentPlayer !== playerType) {
this.notifyStop(this.currentPlayer);
}
// Update current state
this.currentPlayer = playerType;
this.currentUid = uid;
console.log(`Global Audio Manager: ${playerType} player started playing UID: ${uid}`);
return true;
}
/**
* Notify that playback has stopped
* @param {string} playerType - 'streams' or 'personal'
*/
stopPlayback(playerType) {
if (this.currentPlayer === playerType) {
console.log(`Global Audio Manager: ${playerType} player stopped`);
this.currentPlayer = null;
this.currentUid = null;
}
}
/**
* Get current playback state
*/
getCurrentState() {
return {
player: this.currentPlayer,
uid: this.currentUid
};
}
/**
* Check if a specific player is currently active
*/
isPlayerActive(playerType) {
return this.currentPlayer === playerType;
}
/**
* Add a listener for stop events
* @param {string} playerType - 'streams' or 'personal'
* @param {Function} callback - Function to call when this player should stop
*/
addListener(playerType, callback) {
const listener = { playerType, callback };
this.listeners.add(listener);
return listener;
}
/**
* Remove a listener
*/
removeListener(listener) {
this.listeners.delete(listener);
}
/**
* Notify a specific player type to stop
*/
notifyStop(playerType) {
console.log(`Global Audio Manager: Notifying ${playerType} player to stop`);
this.listeners.forEach(listener => {
if (listener.playerType === playerType) {
try {
listener.callback();
} catch (error) {
console.error(`Error calling stop callback for ${playerType}:`, error);
}
}
});
}
/**
* Force stop all playback
*/
stopAll() {
if (this.currentPlayer) {
this.notifyStop(this.currentPlayer);
this.currentPlayer = null;
this.currentUid = null;
}
}
}
// Create singleton instance
export const globalAudioManager = new GlobalAudioManager();
// Make it available globally for debugging
if (typeof window !== 'undefined') {
window.globalAudioManager = globalAudioManager;
}

View File

@ -1,5 +1,6 @@
// static/streams-ui.js — public streams loader and profile-link handling // static/streams-ui.js — public streams loader and profile-link handling
import { showOnly } from './router.js'; import { showOnly } from './router.js';
import { globalAudioManager } from './global-audio-manager.js';
// Global variable to track if we should force refresh the stream list // Global variable to track if we should force refresh the stream list
let shouldForceRefresh = false; let shouldForceRefresh = false;
@ -24,6 +25,12 @@ export function initStreamsUI() {
}); });
document.addEventListener('visibilitychange', maybeLoadStreamsOnShow); document.addEventListener('visibilitychange', maybeLoadStreamsOnShow);
maybeLoadStreamsOnShow(); maybeLoadStreamsOnShow();
// Register with global audio manager to handle stop requests from other players
globalAudioManager.addListener('streams', () => {
console.log('[streams-ui] Received stop request from global audio manager');
stopPlayback();
});
} }
function maybeLoadStreamsOnShow() { function maybeLoadStreamsOnShow() {
@ -539,6 +546,9 @@ function stopPlayback() {
pauseTime = 0; pauseTime = 0;
audioStartTime = 0; audioStartTime = 0;
// Notify global audio manager that streams player has stopped
globalAudioManager.stopPlayback('streams');
// Update UI // Update UI
if (currentlyPlayingButton) { if (currentlyPlayingButton) {
updatePlayPauseButton(currentlyPlayingButton, false); updatePlayPauseButton(currentlyPlayingButton, false);
@ -566,6 +576,9 @@ async function loadAndPlayAudio(uid, playPauseBtn) {
// Stop any current playback // Stop any current playback
stopPlayback(); stopPlayback();
// Notify global audio manager that streams player is starting
globalAudioManager.startPlayback('streams', uid);
// Update UI // Update UI
updatePlayPauseButton(playPauseBtn, true); updatePlayPauseButton(playPauseBtn, true);
currentlyPlayingButton = playPauseBtn; currentlyPlayingButton = playPauseBtn;