From a9a1c22fee59c0c4d239c0a940a9ba9f78f77a05 Mon Sep 17 00:00:00 2001 From: oib Date: Sun, 27 Jul 2025 09:13:55 +0200 Subject: [PATCH] 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. --- static/app.js | 22 ++++++ static/global-audio-manager.js | 125 +++++++++++++++++++++++++++++++++ static/streams-ui.js | 13 ++++ 3 files changed, 160 insertions(+) create mode 100644 static/global-audio-manager.js diff --git a/static/app.js b/static/app.js index afc81bb..9a0ceb2 100644 --- a/static/app.js +++ b/static/app.js @@ -3,6 +3,7 @@ import { playBeep } from "./sound.js"; import { showToast } from "./toast.js"; import { injectNavigation } from "./inject-nav.js"; +import { globalAudioManager } from './global-audio-manager.js'; // Global audio state let globalAudio = null; @@ -963,6 +964,19 @@ document.addEventListener("DOMContentLoaded", () => { // Initialize components 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 handlePageNavigation(); @@ -1011,6 +1025,10 @@ document.addEventListener("DOMContentLoaded", () => { 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 const playPromise = audio.play(); @@ -1046,6 +1064,10 @@ document.addEventListener("DOMContentLoaded", () => { }); } else { audio.pause(); + + // Notify global audio manager that personal player has stopped + globalAudioManager.stopPlayback('personal'); + if (window.currentlyPlayingAudio === audio) { window.currentlyPlayingAudio = null; window.currentlyPlayingButton = null; diff --git a/static/global-audio-manager.js b/static/global-audio-manager.js new file mode 100644 index 0000000..1a2cc98 --- /dev/null +++ b/static/global-audio-manager.js @@ -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; +} diff --git a/static/streams-ui.js b/static/streams-ui.js index 19521c7..d3f6765 100644 --- a/static/streams-ui.js +++ b/static/streams-ui.js @@ -1,5 +1,6 @@ // static/streams-ui.js — public streams loader and profile-link handling import { showOnly } from './router.js'; +import { globalAudioManager } from './global-audio-manager.js'; // Global variable to track if we should force refresh the stream list let shouldForceRefresh = false; @@ -24,6 +25,12 @@ export function initStreamsUI() { }); document.addEventListener('visibilitychange', 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() { @@ -539,6 +546,9 @@ function stopPlayback() { pauseTime = 0; audioStartTime = 0; + // Notify global audio manager that streams player has stopped + globalAudioManager.stopPlayback('streams'); + // Update UI if (currentlyPlayingButton) { updatePlayPauseButton(currentlyPlayingButton, false); @@ -566,6 +576,9 @@ async function loadAndPlayAudio(uid, playPauseBtn) { // Stop any current playback stopPlayback(); + // Notify global audio manager that streams player is starting + globalAudioManager.startPlayback('streams', uid); + // Update UI updatePlayPauseButton(playPauseBtn, true); currentlyPlayingButton = playPauseBtn;