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:
@ -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;
|
||||||
|
125
static/global-audio-manager.js
Normal file
125
static/global-audio-manager.js
Normal 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;
|
||||||
|
}
|
@ -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;
|
||||||
|
Reference in New Issue
Block a user