Resume in place when an audio effect changes - #20
Merged
Conversation
Asking for a bass boost four minutes into a song threw the listener back to 0:00. An FFmpeg filter chain is fixed for the life of the process, so changing one means respawning the stream, and the respawn always started from the top. The player now tracks playback position and passes it to the new process as an FFmpeg input option, so the effect appears to apply in place. -ss goes in before_options rather than options: as an input option FFmpeg discards packets without decoding them, which on a piped stream costs almost nothing because yt-dlp delivers at network speed rather than in realtime. Measured at 0.03s to skip 25 seconds. Position excludes time spent paused. _start_ts alone would have counted a track paused for a coffee break as still playing and resumed minutes too late, so pause and resume are routed through the player, which stops and restarts its own clock. After a respawn the clock is backdated by the seek, so a second effect change resumes from the real position rather than from the respawn point. Seeking is skipped where it would be wrong: live streams report no duration and have no position to seek to, and a position inside the last two seconds would resume into silence. The load-failure heuristic now measures from the spawn instead of _start_ts, which is no longer wall-clock once backdating is involved. Verified through the real audio path: seeking 10s into a 30s source yields 20.0s of playback and 25s yields 5.0s, with an effect filter applied on top. Closes #9
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #9
The problem
Asking for a bass boost four minutes into a song threw you back to 0:00.
An FFmpeg filter chain is fixed for the life of the process, so changing an effect means respawning the stream — and the respawn always started from the top.
The fix
MusicPlayernow tracks playback position and passes it to the new FFmpeg process, so the effect appears to apply in place.Why
-ssgoes inbefore_optionsAs an input option FFmpeg discards packets without decoding them. As an output option it decodes every frame and throws it away.
On a piped stream that distinction matters less than it sounds, because yt-dlp delivers at network speed rather than in realtime. Measured against a real 30-second source:
-ss 10-ss 25Paused time does not count
_start_tsalone would treat a track paused for a coffee break as still playing and resume minutes too late.pauseandresumeare now routed through the player, which stops and restarts its own clock:resumes at 3:00, not 13:00.
After a respawn the clock is backdated by the seek, so a second effect change resumes from the real position rather than from where the respawn happened.
Where seeking is deliberately skipped
duration)One subtle thing this touched
The "the source failed to load" heuristic compares elapsed wall time against a 2-second threshold. It read
_start_ts, which is no longer wall-clock once backdating is involved — a track resumed at 3:00 would have looked like it had already played 180 seconds. It now measures from the spawn instead. Replays were already excluded from that check, so this was latent rather than broken, but it would have bitten the next person to touch it.Verification
230 passed, three consecutive runs, 40 of them new.Through the real audio path — not mocks.
tests/test_effect_filters.pyrenders a 30-second MP3, feeds it through the actualmake_pipe_source, drains the resultingAudioSourceframe by frame and counts 20 ms frames:bassboostfilterClock behaviour —
tests/test_player_seek.pydrives a fake clock so the assertions are exact rather than timing-dependent: paused time excluded, repeated pause/resume cycles accumulating correctly, a double!pausenot losing the clock, andapply_effectwhile paused not shifting the resume point.Notes for review
!pause/!resumenow go throughplayers.get(...)instead ofctx.voice_clientdirectly. Behaviour when nothing is playing is unchanged and still covered by the existing tests.cogs/effects.pyclaiming effects restart "(from the beginning)" is now correct again.