Merge branch 'main' into fix/title-date
This commit is contained in:
commit
0def21e3ca
2 changed files with 70 additions and 7 deletions
|
|
@ -98,6 +98,17 @@ class AudioContextManager {
|
|||
if (this.isInitialized) 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 {
|
||||
this.audio = audioElement;
|
||||
|
||||
|
|
@ -177,11 +188,28 @@ class AudioContextManager {
|
|||
|
||||
/**
|
||||
* Resume audio context (required after user interaction)
|
||||
* @returns {Promise<boolean>} - Returns true if context is running
|
||||
*/
|
||||
resume() {
|
||||
if (this.audioContext && this.audioContext.state === 'suspended') {
|
||||
this.audioContext.resume();
|
||||
async resume() {
|
||||
if (!this.audioContext) return false;
|
||||
|
||||
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';
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
43
js/player.js
43
js/player.js
|
|
@ -10,6 +10,7 @@ import {
|
|||
createQualityBadgeHTML,
|
||||
} from './utils.js';
|
||||
import { queueManager, replayGainSettings, trackDateSettings } from './storage.js';
|
||||
import { audioContextManager } from './audio-context.js';
|
||||
|
||||
export class Player {
|
||||
constructor(audioElement, api, quality = 'HI_RES_LOSSLESS') {
|
||||
|
|
@ -51,6 +52,17 @@ export class Player {
|
|||
window.addEventListener('beforeunload', () => {
|
||||
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) {
|
||||
|
|
@ -174,19 +186,42 @@ export class Player {
|
|||
setupMediaSession() {
|
||||
if (!('mediaSession' in navigator)) return;
|
||||
|
||||
navigator.mediaSession.setActionHandler('play', () => {
|
||||
this.audio.play().catch(console.error);
|
||||
navigator.mediaSession.setActionHandler('play', async () => {
|
||||
// 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', () => {
|
||||
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();
|
||||
});
|
||||
|
||||
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();
|
||||
});
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue