Add playback speed multiplier - #23
Closed
emlcpfx wants to merge 1 commit into
Closed
Conversation
Playback > Speed, a timeline combobox, and Ctrl+[ / Ctrl+] / Ctrl+\ select 0.25x through 4x. The two players keep time differently, so speed applies differently to each, and playback/speed.py holds the arithmetic so it can be tested on its own: - SequencePlayer is timer-driven: it fires every 1000/fps ms and steps one frame. Speed scales that interval, so every frame is still shown, just sooner or later. A running timer is re-armed immediately rather than waiting for the next tick. - MoviePlayer is clock-driven: it reads a monotonic elapsed time and presents whichever decoded frame is due. Speed scales the elapsed clock, so more (or fewer) frames fall due per tick. Presentation stays timestamp-driven, so frames still appear in order and none are decoded twice. Changing speed mid-playback re-anchors the movie clock first: it banks the position already reached at the old speed and restarts the elapsed timer, so the multiplier applies only from that point. Without this the whole elapsed span is rescaled retroactively and playback jumps. Audio is dropped at any speed other than 1x. The samples decode at their native rate, so submitting them against a scaled video clock drifts steadily out of sync, and playing them faster without resampling shifts the pitch. Off-speed playback is silent, which is what a reviewer expects when they shuttle, and it keeps A/V sync honest. The status bar says so. An unusable fps yields no interval rather than dividing by zero, and a junk speed falls back to 1x rather than stopping playback. 23 tests. Note SequencePlayer starts a decode QThread in its constructor and only reset() shuts it down; the test fixtures do that, or the process dies at exit. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TexnzYbmCjjTDB8zzZuUPb
Owner
|
Thank you, Eric. The authored speed feature has been preserved and rebased onto current main in #29. Review found that speed reset silently when a concrete player was replaced, and A/B could inherit mismatched speeds; #29 fixes persistence across source/playlist/Compare/session changes and adds a regression test. Closing this branch in favor of #29. |
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.
Adds a playback speed multiplier:
Playback > Speed, a combobox on the timeline toolbar, andCtrl+[/Ctrl+]/Ctrl+\to step down, up, and reset. Presets run 0.25x to 4x.Why the two players need different treatment
The interesting part of this one is that FrameDeck's two players keep time in completely different ways, so a single "multiply by N" doesn't apply to both.
playback/speed.pyholds the arithmetic so it can be tested without a player at all.SequencePlayer is timer-driven. It fires every
1000 / fpsms and steps exactly one frame. Speed scales that interval — so every frame is still shown, just sooner or later. A running timer is re-armed immediately on a speed change rather than waiting for the next tick to notice.MoviePlayer is clock-driven. It reads a monotonic elapsed time and presents whichever decoded frame is due. Speed scales the elapsed clock, so more (or fewer) decoded frames fall due per tick. Presentation stays timestamp-driven, so frames still appear in order and none get decoded twice.
Two things that would otherwise bite
The movie clock has to be re-anchored before the multiplier changes.
set_speedbanks the position already reached at the old speed and restarts the elapsed timer, so the new multiplier applies only from that point forward. Without it, the entire elapsed span gets rescaled retroactively and playback visibly jumps — switch to 2x ten seconds in and you'd leap to twenty.test_movie_set_speed_reanchors_the_clockpins this.Audio is dropped at any speed other than 1x. The samples decode at their native rate, so submitting them against a scaled video clock drifts steadily out of sync, and playing them faster without resampling shifts the pitch. Rather than ship either of those, off-speed playback is silent — which is what a reviewer expects when they shuttle, and it keeps A/V sync honest. The status bar says so when you leave 1x, so nobody wonders where the sound went.
If you'd rather have pitch-corrected audio at speed, that's a real feature but a much bigger one (a resampler in the audio path); say the word and I'll scope it separately.
Robustness
Tests
23 new tests, 274 on the branch, green three runs in a row.
One gotcha worth flagging for anyone else writing player tests:
SequencePlayerstarts a decodeQThreadin its constructor, and onlyreset()shuts it down. A test that constructs one and doesn't tear it down leaves the thread running into interpreter exit and kills the process with a fail-fast exception — which looks like a flaky suite, not a leak. The fixtures here handle it.Verified end to end through a real
MainWindow: the combobox drives the window, the menu drives the combobox back, and the shortcuts step through the presets and clamp at both ends without falling off the list.Branches off current
main.