fix(player): refactor audio element handling and improve auto-play reliability
- Replace detached `new Audio()` with rendered `<audio>` element to better support mobile behaviors and standard DOM events - Introduce `autoAdvanceLockRef` to prevent race conditions where the next song might be triggered multiple times - Add manual time check near the end of the track to trigger auto-advance, acting as a fallback for the `ended` event - Update `playNext` logic to handle immediate playback transitions more robustly
This commit is contained in:
83
index.html
83
index.html
@@ -875,14 +875,18 @@
|
||||
const [showPlaylist, setShowPlaylist] = useState(false);
|
||||
const [showSideDrawer, setShowSideDrawer] = useState(false);
|
||||
|
||||
const audioRef = useRef(new Audio());
|
||||
|
||||
const audioRef = useRef(null);
|
||||
const autoAdvanceLockRef = useRef(false);
|
||||
const currentSongRef = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
currentSongRef.current = currentSong;
|
||||
}, [currentSong]);
|
||||
|
||||
useEffect(() => {
|
||||
autoAdvanceLockRef.current = false;
|
||||
}, [currentSong]);
|
||||
|
||||
// Media Session Refs
|
||||
const playNextRef = useRef(null);
|
||||
const playPrevRef = useRef(null);
|
||||
@@ -997,12 +1001,27 @@
|
||||
|
||||
useEffect(() => {
|
||||
const audio = audioRef.current;
|
||||
if (!audio) return;
|
||||
audio.volume = volume;
|
||||
|
||||
const triggerAutoNext = () => {
|
||||
if (autoAdvanceLockRef.current) return;
|
||||
autoAdvanceLockRef.current = true;
|
||||
playNext(true, { immediate: true });
|
||||
};
|
||||
|
||||
const updateTime = () => {
|
||||
setCurrentTime(audio.currentTime);
|
||||
if (autoAdvanceLockRef.current) return;
|
||||
const durationSeconds = resolveDurationSeconds(audio, currentSongRef.current);
|
||||
if (Number.isFinite(durationSeconds) && durationSeconds > 0) {
|
||||
if (audio.currentTime >= durationSeconds - 0.35) {
|
||||
triggerAutoNext();
|
||||
}
|
||||
}
|
||||
};
|
||||
const updateDuration = () => setDuration(resolveDurationSeconds(audio, currentSongRef.current));
|
||||
const onEnded = () => playNext(true);
|
||||
const onEnded = () => triggerAutoNext();
|
||||
|
||||
audio.addEventListener('timeupdate', updateTime);
|
||||
audio.addEventListener('loadedmetadata', updateDuration);
|
||||
@@ -1013,23 +1032,25 @@
|
||||
audio.removeEventListener('loadedmetadata', updateDuration);
|
||||
audio.removeEventListener('ended', onEnded);
|
||||
};
|
||||
}, [playlist, currentSong, mode]);
|
||||
}, [playlist, currentSong, mode, volume, quality]);
|
||||
|
||||
useEffect(() => {
|
||||
if (currentSong) {
|
||||
const audio = audioRef.current;
|
||||
if (!audio) return;
|
||||
// Update URL when quality changes or song changes
|
||||
const url = api.getSongUrl(currentSong.id, currentSong.platform || currentSong.source, quality);
|
||||
|
||||
// Only update src if it's different to avoid reloading same song on re-render (unless quality changed)
|
||||
// Note: audioRef.current.src returns full absolute URL
|
||||
const currentSrc = audioRef.current.src;
|
||||
const currentSrc = audio.src;
|
||||
|
||||
// Simple check if src changed significantly (avoiding minor encoding diffs if possible, but exact match is safer)
|
||||
if (currentSrc !== url) {
|
||||
const wasPlaying = isPlaying;
|
||||
audioRef.current.src = url;
|
||||
audio.src = url;
|
||||
if (wasPlaying) {
|
||||
audioRef.current.play()
|
||||
audio.play()
|
||||
.then(() => setIsPlaying(true))
|
||||
.catch(e => {
|
||||
console.warn("Auto-play prevented:", e);
|
||||
@@ -1042,8 +1063,10 @@
|
||||
|
||||
useEffect(() => {
|
||||
if (currentSong) {
|
||||
if (isPlaying) audioRef.current.play().catch(() => setIsPlaying(false));
|
||||
else audioRef.current.pause();
|
||||
const audio = audioRef.current;
|
||||
if (!audio) return;
|
||||
if (isPlaying) audio.play().catch(() => setIsPlaying(false));
|
||||
else audio.pause();
|
||||
}
|
||||
}, [isPlaying]);
|
||||
|
||||
@@ -1117,12 +1140,15 @@
|
||||
|
||||
const togglePlay = () => setIsPlaying(!isPlaying);
|
||||
|
||||
const playNext = (auto = false) => {
|
||||
const playNext = (auto = false, options = {}) => {
|
||||
if (playlist.length === 0) return;
|
||||
|
||||
if (auto && mode === 'one') {
|
||||
audioRef.current.currentTime = 0;
|
||||
audioRef.current.play();
|
||||
const audio = audioRef.current;
|
||||
if (!audio) return;
|
||||
audio.currentTime = 0;
|
||||
audio.play().catch(() => setIsPlaying(false));
|
||||
autoAdvanceLockRef.current = false;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1135,8 +1161,24 @@
|
||||
nextIdx = (currIdx + 1) % playlist.length;
|
||||
}
|
||||
|
||||
setCurrentSong(playlist[nextIdx]);
|
||||
const nextSong = playlist[nextIdx];
|
||||
setCurrentSong(nextSong);
|
||||
setIsPlaying(true);
|
||||
|
||||
if (options.immediate) {
|
||||
const audio = audioRef.current;
|
||||
if (!audio || !nextSong) return;
|
||||
const url = api.getSongUrl(nextSong.id, nextSong.platform || nextSong.source, quality);
|
||||
if (audio.src !== url) {
|
||||
audio.src = url;
|
||||
}
|
||||
audio.play()
|
||||
.then(() => setIsPlaying(true))
|
||||
.catch(e => {
|
||||
console.warn("Auto-play prevented:", e);
|
||||
setIsPlaying(false);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const playPrev = () => {
|
||||
@@ -1172,7 +1214,9 @@
|
||||
|
||||
const handleSeek = (time) => {
|
||||
if (Number.isFinite(time)) {
|
||||
audioRef.current.currentTime = time;
|
||||
const audio = audioRef.current;
|
||||
if (!audio) return;
|
||||
audio.currentTime = time;
|
||||
setCurrentTime(time);
|
||||
updateMediaSessionPosition();
|
||||
}
|
||||
@@ -1263,6 +1307,7 @@
|
||||
if (!('mediaSession' in navigator)) return;
|
||||
|
||||
const audio = audioRef.current;
|
||||
if (!audio) return;
|
||||
|
||||
const updateState = () => {
|
||||
if (!audio) return;
|
||||
@@ -1317,6 +1362,14 @@
|
||||
|
||||
return (
|
||||
<div className="flex flex-col h-full bg-darker font-sans">
|
||||
<audio
|
||||
ref={audioRef}
|
||||
className="hidden"
|
||||
preload="auto"
|
||||
playsInline
|
||||
webkit-playsinline="true"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<SideDrawer
|
||||
isOpen={showSideDrawer}
|
||||
onClose={() => setShowSideDrawer(false)}
|
||||
@@ -1506,4 +1559,4 @@
|
||||
document.getElementById('my-manifest').setAttribute('href', manifestURL);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user