rewrite audio/spawn as single polling interval, add debug logging
Replace fragile multi-effect coordination with one effect + one interval that polls activeRef for state transitions. No more missed play() calls from effects not re-running. Added console.log for: effect init/cleanup, first play, resume, pause, and play success. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>main
parent
c608e992aa
commit
f939ecf3f9
|
|
@ -141,51 +141,70 @@ export const ArrowGame = forwardRef<ArrowGameHandle, Props>(({ active = true, ch
|
|||
},
|
||||
}))
|
||||
|
||||
// Use a ref to track active so the audio effect can read it without depending on it
|
||||
// All audio/spawn logic in one effect + one interval that polls activeRef
|
||||
const activeRef = useRef(active)
|
||||
activeRef.current = active
|
||||
const pausedAtRef = useRef(0)
|
||||
|
||||
// Single effect for audio + spawn lifecycle
|
||||
useEffect(() => {
|
||||
if (!chartNotes || !audioUrl) return
|
||||
|
||||
// Reset state for fresh start
|
||||
// Reset
|
||||
arrowsRef.current = []
|
||||
setArrows([])
|
||||
spawnedUpToRef.current = 0
|
||||
comboRef.current = 0
|
||||
setScore({ perfect: 0, good: 0, miss: 0, combo: 0 })
|
||||
pausedAtRef.current = 0
|
||||
|
||||
const audio = new Audio(audioUrl)
|
||||
audioRef.current = audio
|
||||
songStartRef.current = null
|
||||
let started = false
|
||||
|
||||
let wasActive = false
|
||||
let pausedAt = 0
|
||||
|
||||
console.log('[ArrowGame] effect init, audioUrl:', audioUrl, 'chartNotes:', chartNotes.length)
|
||||
|
||||
audio.addEventListener('ended', () => onSongEndRef.current?.())
|
||||
|
||||
function tryPlay() {
|
||||
if (started || !activeRef.current) return
|
||||
audio.play().then(() => {
|
||||
started = true
|
||||
songStartRef.current = performance.now()
|
||||
}).catch((e) => {
|
||||
console.warn('Audio play failed:', e)
|
||||
})
|
||||
}
|
||||
|
||||
// Try to play immediately if already active
|
||||
tryPlay()
|
||||
|
||||
// Spawn + progress interval
|
||||
const iv = setInterval(() => {
|
||||
const isActive = activeRef.current
|
||||
|
||||
// Handle active transitions
|
||||
if (isActive && !wasActive) {
|
||||
// Becoming active: play audio
|
||||
if (songStartRef.current === null) {
|
||||
// First play
|
||||
console.log('[ArrowGame] first play attempt')
|
||||
audio.play().then(() => {
|
||||
songStartRef.current = performance.now()
|
||||
console.log('[ArrowGame] audio playing, songStart set')
|
||||
}).catch((e) => console.warn('[ArrowGame] Audio play failed:', e))
|
||||
} else {
|
||||
// Resume from pause
|
||||
console.log('[ArrowGame] resuming from pause')
|
||||
const pauseDuration = performance.now() - pausedAt
|
||||
songStartRef.current += pauseDuration
|
||||
for (const a of arrowsRef.current) {
|
||||
a.targetTime += pauseDuration
|
||||
}
|
||||
audio.play().catch(() => {})
|
||||
}
|
||||
wasActive = true
|
||||
} else if (!isActive && wasActive) {
|
||||
// Becoming inactive: pause audio
|
||||
console.log('[ArrowGame] pausing')
|
||||
pausedAt = performance.now()
|
||||
audio.pause()
|
||||
wasActive = false
|
||||
}
|
||||
|
||||
// Report progress
|
||||
if (audio.duration) {
|
||||
onProgressRef.current?.(audio.currentTime / audio.duration)
|
||||
}
|
||||
// Only spawn when song is playing
|
||||
if (songStartRef.current === null) return
|
||||
|
||||
// Spawn arrows when playing
|
||||
if (songStartRef.current === null || !isActive) return
|
||||
|
||||
const now = performance.now()
|
||||
const songTime = (now - songStartRef.current) / 1000
|
||||
|
|
@ -209,6 +228,7 @@ export const ArrowGame = forwardRef<ArrowGameHandle, Props>(({ active = true, ch
|
|||
}, 50)
|
||||
|
||||
return () => {
|
||||
console.log('[ArrowGame] effect cleanup')
|
||||
clearInterval(iv)
|
||||
audio.pause()
|
||||
audio.src = ''
|
||||
|
|
@ -217,35 +237,6 @@ export const ArrowGame = forwardRef<ArrowGameHandle, Props>(({ active = true, ch
|
|||
}
|
||||
}, [chartNotes, audioUrl])
|
||||
|
||||
// Pause/resume audio when active changes
|
||||
useEffect(() => {
|
||||
const audio = audioRef.current
|
||||
if (!audio) return
|
||||
|
||||
if (active) {
|
||||
if (songStartRef.current === null) {
|
||||
// First play — audio was created but not started yet
|
||||
audio.play().then(() => {
|
||||
songStartRef.current = performance.now()
|
||||
}).catch((e) => {
|
||||
console.warn('Audio play failed:', e)
|
||||
})
|
||||
} else if (pausedAtRef.current > 0) {
|
||||
// Resuming from pause
|
||||
const pauseDuration = performance.now() - pausedAtRef.current
|
||||
songStartRef.current += pauseDuration
|
||||
for (const a of arrowsRef.current) {
|
||||
a.targetTime += pauseDuration
|
||||
}
|
||||
pausedAtRef.current = 0
|
||||
audio.play().catch(() => {})
|
||||
}
|
||||
} else if (songStartRef.current !== null) {
|
||||
pausedAtRef.current = performance.now()
|
||||
audio.pause()
|
||||
}
|
||||
}, [active])
|
||||
|
||||
// Miss detection + cleanup
|
||||
useEffect(() => {
|
||||
if (!active) return
|
||||
|
|
|
|||
Loading…
Reference in New Issue