Merge pull request #260 from itsgareth/gareth/fix-ios-lockscreen-controls

feat: fix ios lockscreen controls
This commit is contained in:
edidealt 2026-03-01 12:23:08 +02:00 committed by GitHub
commit eb61e87980
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -256,70 +256,81 @@ export class Player {
setupMediaSession() { setupMediaSession() {
if (!('mediaSession' in navigator)) return; if (!('mediaSession' in navigator)) return;
navigator.mediaSession.setActionHandler('play', async () => { const setHandlers = () => {
// Initialize and resume audio context first (required for iOS lock screen) navigator.mediaSession.setActionHandler('play', async () => {
// Must happen before audio.play() or audio won't route through Web Audio // Initialize and resume audio context first (required for iOS lock screen)
if (!audioContextManager.isReady()) { // Must happen before audio.play() or audio won't route through Web Audio
audioContextManager.init(this.audio); if (!audioContextManager.isReady()) {
this.applyReplayGain(); audioContextManager.init(this.audio);
this.applyReplayGain();
}
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', async () => {
// Ensure audio context is active for iOS lock screen controls
if (!audioContextManager.isReady()) {
audioContextManager.init(this.audio);
this.applyReplayGain();
}
await audioContextManager.resume();
this.playPrev();
});
navigator.mediaSession.setActionHandler('nexttrack', async () => {
// Ensure audio context is active for iOS lock screen controls
if (!audioContextManager.isReady()) {
audioContextManager.init(this.audio);
this.applyReplayGain();
}
await audioContextManager.resume();
this.playNext();
});
if (!this.isIOS) {
navigator.mediaSession.setActionHandler('seekbackward', (details) => {
const skipTime = details.seekOffset || 10;
this.seekBackward(skipTime);
});
navigator.mediaSession.setActionHandler('seekforward', (details) => {
const skipTime = details.seekOffset || 10;
this.seekForward(skipTime);
});
} }
await audioContextManager.resume();
try { navigator.mediaSession.setActionHandler('seekto', (details) => {
await this.audio.play(); if (details.seekTime !== undefined) {
} catch (e) { this.audio.currentTime = Math.max(0, details.seekTime);
console.error('MediaSession play failed:', e); this.updateMediaSessionPositionState();
// If play fails, try to handle it like a regular play/pause }
this.handlePlayPause(); });
}
});
navigator.mediaSession.setActionHandler('pause', () => { navigator.mediaSession.setActionHandler('stop', () => {
this.audio.pause(); this.audio.pause();
}); this.audio.currentTime = 0;
this.updateMediaSessionPlaybackState();
});
};
navigator.mediaSession.setActionHandler('previoustrack', async () => { if (this.isIOS) {
// Ensure audio context is active for iOS lock screen controls // iOS: set handlers only when playback starts. Setting them in the constructor makes
if (!audioContextManager.isReady()) { // the lock screen show +10/-10. Registering on first 'playing' gives next/previous track
audioContextManager.init(this.audio); this.audio.addEventListener('playing', () => setHandlers(), { once: true });
this.applyReplayGain(); } else {
} setHandlers();
await audioContextManager.resume(); }
this.playPrev();
});
navigator.mediaSession.setActionHandler('nexttrack', async () => {
// Ensure audio context is active for iOS lock screen controls
if (!audioContextManager.isReady()) {
audioContextManager.init(this.audio);
this.applyReplayGain();
}
await audioContextManager.resume();
this.playNext();
});
navigator.mediaSession.setActionHandler('seekbackward', (details) => {
const skipTime = details.seekOffset || 10;
this.seekBackward(skipTime);
});
navigator.mediaSession.setActionHandler('seekforward', (details) => {
const skipTime = details.seekOffset || 10;
this.seekForward(skipTime);
});
navigator.mediaSession.setActionHandler('seekto', (details) => {
if (details.seekTime !== undefined) {
this.audio.currentTime = Math.max(0, details.seekTime);
this.updateMediaSessionPositionState();
}
});
navigator.mediaSession.setActionHandler('stop', () => {
this.audio.pause();
this.audio.currentTime = 0;
this.updateMediaSessionPlaybackState();
});
} }
setQuality(quality) { setQuality(quality) {