Skip to content

command: add sub-snap flag to the seek command - #18409

Open
feldgendler wants to merge 2 commits into
mpv-player:masterfrom
feldgendler:sub_snap
Open

command: add sub-snap flag to the seek command#18409
feldgendler wants to merge 2 commits into
mpv-player:masterfrom
feldgendler:sub_snap

Conversation

@feldgendler

@feldgendler feldgendler commented Aug 25, 2026

Copy link
Copy Markdown

seek <amount> sub-snap is a relative seek that lands on the start of the next or previous primary subtitle event when one falls within <amount> seconds, and does an ordinary relative seek otherwise. It is meant to replace the plain relative seek on the arrow keys rather than to sit beside it as one more binding: because it never travels farther than the amount asked for, it does exactly what the seek it replaces would have done wherever no subtitle is in range.

Seeking back a few seconds during dialogue is nearly always an attempt to hear or read one line again. A plain seek -5 lands mid-sentence, so the line has to come round a second time before it is heard whole, and sub-seek -1 jumps to whatever event happens to be previous, which across a silent stretch can be a minute away — which is also why it cannot serve as the everyday seek key. With sub-snap bound to left and right there is nothing to decide before pressing: in dialogue it replays the line from its start, elsewhere it seeks. My own arrow keys have been bound to it since I wrote it and I have not wanted the plain seek back. Language learners are the obvious audience, since repeating individual lines is most of the activity, but this is what anyone rewinding to catch a mumbled line is reaching for.

A script can approximate this with sub-seek followed by a correction seek, but the correction is a second visible jump, and it cannot tell "there is no event within the amount" from "the decoder has not read that far yet". Only the latter is worth waiting for; conflating them makes holding the key down in a gap stall instead of seeking. SD_CTRL_SUB_STEP sees only what has been decoded, and how far that reaches is not exposed to scripts. Key repeat is the other half of it: input is drained in batches, so a script doing a property round-trip per press collapses a burst into one step.

The wait is driven from the playloop by handle_sub_snap() rather than blocking the core. A backward snap needs the region behind the play position, which cannot be read ahead, so the demuxer alone is rewound while playback stays put, and only while paused, since it is shared with the other streams. Repeated presses accumulate into one chain, and the chain starts from the target of a pending seek instead of the play position, so it composes with seeks issued in the same batch rather than replacing them.

The manual gives the limits. Like sub-seek, this only works with events already displayed or within the prefetch range, except that a backward snap also looks behind the play position while paused. It is ignored for the other seek modes and with --play-dir=backward. Whenever the event is not available it falls back to an ordinary relative seek, so it never blocks or waits visibly — it just stops snapping.

Tested with external SRT, embedded ASS and PGS from a Blu-ray rip: bursts of one to four presses from several start positions, forward and backward, on a short file and a ten-minute one; --sub-delay both signs and --sub-speed at 0.5 and 2; subtitles hidden, disabled, and present only as a secondary track; --play-dir=backward; twenty-five presses of key-repeat scrubbing through a gap, compared against a plain seek; and unrelated seeks queued in the same input batch, checked against the result without the flag.

Written with AI assistance, and reviewed with AI tooling. I take full responsibility for the code: I understand what it changes and why, I have tested it myself, I will respond to review in my own words, and it can be submitted under the same license as the files it touches.

Return the PTS of the most recent packet read from the demuxer, in the
subtitle timebase. It is only meaningful compared against itself, as a
measure of how far the decoder has advanced. The next commit uses it to
wait until a region has actually been decoded.
On a relative seek, snap to the start of the next or previous primary
subtitle event if it is within the requested amount; otherwise, or when
subtitles are hidden or disabled, do an ordinary relative seek. Unlike
sub-seek, it never jumps farther than the given amount.

The adjacent event is found with SD_CTRL_SUB_STEP, which only sees what
the decoder has already read, so "no event within the amount" has to be
told apart from "not decoded yet"; only the latter is waited for.
handle_sub_snap() drives the decoder from the playloop instead of
blocking the core. A backward snap needs the region behind the play
position, which cannot be read ahead, so the demuxer alone is rewound
while playback stays put. That is done only while paused, since the
demuxer is shared with the other streams.

Repeated presses accumulate into one chain instead of each recomputing a
target, because input is drained in batches and a seek queued with
MPSEEK_FLAG_DELAY can still be pending. The chain starts from the target
of a pending seek rather than from the play position, so that it
composes with seeks issued in the same batch instead of replacing them.

Tested with SRT and embedded ASS, with --sub-delay and --sub-speed set
both ways, and with unrelated seeks queued in the same batch.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant