fix: stop audio on navigation - #137
Conversation
Review Summary by QodoStop audio playback on page navigation
WalkthroughsDescription• Add cleanup effect to stop audio on component unmount • Prevent audio from continuing during page navigation • Update TLA+ model with Unmount action for state verification • Document issue and solution in GitHub issue tracker Diagramflowchart LR
A["AudioPlayer Component"] -->|useEffect cleanup| B["cleanupAudio function"]
B -->|stops playback| C["Audio stops on unmount"]
D["Page Navigation"] -->|triggers unmount| C
E["TLA+ Model"] -->|Unmount action| F["Verify state transitions"]
File Changes1. .github/issues/stop-audio-on-navigation.md
|
📝 WalkthroughWalkthroughAdds an explicit Unmount transition to the AudioPlayer TLA+ model and introduces component-level unmount cleanup in the React AudioPlayer to stop playback, clear timeouts, and prevent post-unmount callbacks. Changes
Estimated code review effort🎯 2 (Simple) | ⏱️ ~10 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
⚔️ Resolve merge conflicts (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
Visit the preview URL for this PR (updated for commit f86879d): https://izuminokami-kanesada--pr137-issue-136-stop-audio-3satj9qw.web.app (expires Fri, 20 Feb 2026 09:25:10 GMT) 🔥 via Firebase Hosting GitHub Action 🌎 Sign: 4c4412227845b968bcb4c8b6996048cdd07fd6de |
f6d11db to
5104015
Compare
Code Review by Qodo
1. Async resume restarts audio
|
| // Stop audio on unmount (e.g. page navigation) | ||
| useEffect(() => { | ||
| return () => { | ||
| cleanupAudio(); | ||
| }; | ||
| }, [cleanupAudio]); |
There was a problem hiding this comment.
1. Async resume restarts audio 🐞 Bug ⛯ Reliability
The new unmount cleanup only calls cleanupAudio(), but AudioPlayer schedules a setTimeout resume on language change that is not canceled on unmount. If navigation happens before the timeout fires, the callback can run after unmount, call playSegment(), and create/play a new Audio instance—so audio may still continue after navigation.
Agent Prompt
### Issue description
AudioPlayer now calls `cleanupAudio()` on unmount, but there are asynchronous callbacks that can outlive the component (notably a `setTimeout` used to resume after language change). If navigation happens before that timer fires, the callback can still run after unmount and call `playSegment()` (creating a new `Audio()`), so audio may continue after navigation.
### Issue Context
- Unmount cleanup currently only pauses/removes listeners on the current `audioRef`.
- `handleLanguageChange()` schedules a timer that resumes playback.
- `playSegment()` also has async promise callbacks (`audio.play().catch`) that can call `setState` after unmount.
### Fix Focus Areas
- src/components/AudioPlayer.tsx[109-130]
- src/components/AudioPlayer.tsx[224-240]
- src/components/AudioPlayer.tsx[132-148]
- src/components/AudioPlayer.tsx[180-184]
### Suggested approach
1. Add a `resumeTimeoutRef` to store the timeout id; clear it:
- before scheduling a new timeout
- inside the unmount cleanup effect
2. Add an `isUnmountedRef` (or `isMountedRef`) set in the unmount cleanup and check it before:
- calling `setIsPlaying`/`setCurrentSegment`
- calling `playSegment` from delayed callbacks
- handling `audio.play().catch(...)`
This ensures the PR guarantee (“stop audio on navigation”) holds even if the user navigates during pending async operations.
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
5104015 to
f86879d
Compare
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/components/AudioPlayer.tsx (1)
246-256:⚠️ Potential issue | 🟠 MajorStale closure:
playSegmentcaptures the oldlanginside the timeout.
setLang(newLang)on line 243 triggers a re-render that produces a newplaySegmentwith the updated language. However, thesetTimeoutcallback on line 254 closes over theplaySegmentfrom the current render (still using the oldlang). After the 100ms delay, this will start playback with the previous language, not the newly selected one.You already maintain
playSegmentRef(line 210-212) for exactly this purpose — use it here.Proposed fix
resumeTimeoutRef.current = setTimeout(() => { resumeTimeoutRef.current = null; if (!isMountedRef.current) return; setIsPlaying(true); - playSegment(wasSegment); + playSegmentRef.current?.(wasSegment); }, 100);
Summary by CodeRabbit
Bug Fixes
New Features