fix iOS safari bug where next song wouldn't reliably play if PWA was in background

Hunter
Tuesday, November 4th, 2025 at 11:35 PM

script.js+37 

script.js
529  	}
530  
531  	animate();
532 +
533 +	// iOS PWA background playback fix: use setInterval as backup
534 +	// setInterval is less throttled than requestAnimationFrame in background
535 +	backgroundPlaybackCheckInterval = setInterval(() => {
536 +		if (!audio.paused && audio.duration && audio.currentTime >= audio.duration - 0.5) {
537 +			console.log('Background check: song ended, triggering next');
538 +			clearInterval(backgroundPlaybackCheckInterval);
539 +			backgroundPlaybackCheckInterval = null;
540 +
541 +			if (songs[currentSongIndex].looping) {
542 +				audio.currentTime = 0;
543 +				audio.play();
544 +			} else {
545 +				nextSong();
546 +			}
547 +		}
548 +
549 +		// Update Media Session position state for iOS
550 +		if ('mediaSession' in navigator && 'setPositionState' in navigator.mediaSession) {
551 +			try {
552 +				if (audio.duration && !isNaN(audio.duration) && isFinite(audio.duration)) {
553 +					navigator.mediaSession.setPositionState({
554 +						duration: audio.duration,
555 +						playbackRate: audio.playbackRate,
556 +						position: audio.currentTime
557 +					});
558 +				}
559 +			} catch (e) {
560 +				// Ignore errors from setPositionState
561 +			}
562 +		}
563 +	}, 500); // Check every 500ms
564  }
565  
566  function stopProgressBar() {

568  		cancelAnimationFrame(animationFrameId);
569  		animationFrameId = null;
570  	}
571 +	if (backgroundPlaybackCheckInterval !== null) {
572 +		clearInterval(backgroundPlaybackCheckInterval);
573 +		backgroundPlaybackCheckInterval = null;
574 +	}
575  }
576  
577  function resetProgressBar() {

591  let isDragging = false;
592  let wasPlayingBeforeDrag = false;
593  let pendingSeekPercentage = null;
594 +let backgroundPlaybackCheckInterval = null;
595  
596  function updateVisualProgress(event) {
597  	if (!playerReady) return;