Merge branch 'main' into fix/title-date

This commit is contained in:
Eduard Prigoana 2026-02-03 16:03:12 +02:00 committed by GitHub
commit 0def21e3ca
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 70 additions and 7 deletions

View file

@ -98,6 +98,17 @@ class AudioContextManager {
if (this.isInitialized) return; if (this.isInitialized) return;
if (!audioElement) return; if (!audioElement) return;
// Detect iOS - skip Web Audio initialization on iOS to avoid lock screen audio issues
// iOS suspends AudioContext when screen locks, and MediaSession controls don't count
// as user gestures to resume it, causing audio to play silently
const ua = navigator.userAgent.toLowerCase();
const isIOS = /iphone|ipad|ipod/.test(ua) || (ua.includes('mac') && navigator.maxTouchPoints > 1);
if (isIOS) {
console.log('[AudioContext] Skipping Web Audio initialization on iOS for lock screen compatibility');
this.isInitialized = true; // Mark as initialized to prevent repeated attempts
return;
}
try { try {
this.audio = audioElement; this.audio = audioElement;
@ -177,11 +188,28 @@ class AudioContextManager {
/** /**
* Resume audio context (required after user interaction) * Resume audio context (required after user interaction)
* @returns {Promise<boolean>} - Returns true if context is running
*/ */
resume() { async resume() {
if (this.audioContext && this.audioContext.state === 'suspended') { if (!this.audioContext) return false;
this.audioContext.resume();
console.log('[AudioContext] Current state:', this.audioContext.state);
if (this.audioContext.state === 'suspended') {
try {
await this.audioContext.resume();
console.log('[AudioContext] Resumed successfully, state:', this.audioContext.state);
} catch (e) {
console.warn('[AudioContext] Failed to resume:', e);
}
} }
// Ensure graph is connected after resuming (iOS may disconnect when suspended)
if (this.isInitialized && this.audioContext.state === 'running') {
this._connectGraph();
}
return this.audioContext.state === 'running';
} }
/** /**

View file

@ -10,6 +10,7 @@ import {
createQualityBadgeHTML, createQualityBadgeHTML,
} from './utils.js'; } from './utils.js';
import { queueManager, replayGainSettings, trackDateSettings } from './storage.js'; import { queueManager, replayGainSettings, trackDateSettings } from './storage.js';
import { audioContextManager } from './audio-context.js';
export class Player { export class Player {
constructor(audioElement, api, quality = 'HI_RES_LOSSLESS') { constructor(audioElement, api, quality = 'HI_RES_LOSSLESS') {
@ -51,6 +52,17 @@ export class Player {
window.addEventListener('beforeunload', () => { window.addEventListener('beforeunload', () => {
this.saveQueueState(); this.saveQueueState();
}); });
// Handle visibility change for iOS - AudioContext gets suspended when screen locks
document.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'visible' && !this.audio.paused) {
// Ensure audio context is resumed when user returns to the app
if (!audioContextManager.isReady()) {
audioContextManager.init(this.audio);
}
audioContextManager.resume();
}
});
} }
setVolume(value) { setVolume(value) {
@ -174,19 +186,42 @@ export class Player {
setupMediaSession() { setupMediaSession() {
if (!('mediaSession' in navigator)) return; if (!('mediaSession' in navigator)) return;
navigator.mediaSession.setActionHandler('play', () => { navigator.mediaSession.setActionHandler('play', async () => {
this.audio.play().catch(console.error); // Initialize and resume audio context first (required for iOS lock screen)
// Must happen before audio.play() or audio won't route through Web Audio
if (!audioContextManager.isReady()) {
audioContextManager.init(this.audio);
}
await audioContextManager.resume();
try {
await this.audio.play();
} catch (e) {
console.error('MediaSession play failed:', e);
// If play fails, try to handle it like a regular play/pause
this.handlePlayPause();
}
}); });
navigator.mediaSession.setActionHandler('pause', () => { navigator.mediaSession.setActionHandler('pause', () => {
this.audio.pause(); this.audio.pause();
}); });
navigator.mediaSession.setActionHandler('previoustrack', () => { navigator.mediaSession.setActionHandler('previoustrack', async () => {
// Ensure audio context is active for iOS lock screen controls
if (!audioContextManager.isReady()) {
audioContextManager.init(this.audio);
}
await audioContextManager.resume();
this.playPrev(); this.playPrev();
}); });
navigator.mediaSession.setActionHandler('nexttrack', () => { navigator.mediaSession.setActionHandler('nexttrack', async () => {
// Ensure audio context is active for iOS lock screen controls
if (!audioContextManager.isReady()) {
audioContextManager.init(this.audio);
}
await audioContextManager.resume();
this.playNext(); this.playNext();
}); });