Skip to content

feat: add YouTube-style keyboard shortcuts to video player - #241

Open
polferov wants to merge 13 commits into
MakD:masterfrom
polferov:feat/player-keyboard-shortcuts
Open

polferov wants to merge 13 commits into
MakD:masterfrom
polferov:feat/player-keyboard-shortcuts

Conversation

@polferov

@polferov polferov commented Oct 5, 2026 •

Copy link
Copy Markdown

Summary

Adds keyboard shortcuts to the video player. Before this, a physical keyboard did nothing during playback; for example, Space did not pause on a tablet with a keyboard attached. The keymap copies the YouTube web player's shortcuts. The keys are handled in PlayerActivity.dispatchKeyEvent, so they work with both ExoPlayer and mpv. Every action goes through an existing PlayerEvent or ViewModel call, so sync-play, sleep-timer resume and the on-screen indicators behave exactly as they do with touch controls.

Changes

Player: keyboard shortcuts (PlayerActivity.kt)

Key Action
Space / K Play / pause and show the controls. Also pauses while buffering and resumes after the end. Space always toggles playback, even when a button has focus.
Enter Presses the visible Skip Intro / Skip Outro button. When a control has focus, Enter clicks that control instead.
J / L Seek −10 s / +10 s
← / → Seek −5 s / +5 s
↑ / ↓ Volume up / down by one system step (shows the volume indicator)
M Mute / unmute (goes back to the previous volume)
0–9 (top row, or numpad with NumLock on) Jump to 0–90 %
Home / End Jump to start / end
< / > Speed −/+0.25 (0.25×–2×, same range as the speed panel); matched by typed character, so it works on QWERTZ and AltGr layouts
Shift + N / Shift + P Next / previous episode (only when the queue has one)
I Picture-in-picture

Which input gets shortcuts. Only physical full (alphabetic) keyboards. Remotes, gamepads, D-pads, HDMI-CEC remote input and virtual devices behave exactly as on master.

Keys that wait for typing. Arrows, digits and Home/End only become shortcuts after that same keyboard has pressed a letter or Space in the player. Until then they keep master's behaviour. Some TV remotes report as full keyboards and have arrow and number keys, so this keeps them working as before.

When shortcuts are off. Keys go to the framework untouched when:

  • the player isn't ready, or no item is loaded,
  • a cast session is connected,
  • the controls lock is on,
  • an in-window panel is open (tracks, quality, speed, episodes, chapters, sleep timer, SyncPlay members, version picker), or
  • Ctrl, left Alt or Meta is held.

Live TV and intros. Seeking and episode skips are disabled there. Their keys are consumed but do nothing, so they don't turn into focus moves.

Focus. If a control has keyboard focus (after Tab), it gets each key first, except Space. Arrow keys stay with focus navigation even at the edge of the controls.

Key repeat. Holding a seek or volume key repeats it. Inside a SyncPlay group, held seek keys fire once per press, so they don't flood the server with seeks.

Panel-open signal (PlayerControls.kt, PlayerScreen.kt, PlayerViewModel.kt)

PlayerControls reports whether a panel is open through a new onPanelOpenChange callback. The check uses the same conditions each panel renders under. PlayerScreen adds the version picker and writes the result to PlayerViewModel.isOverlayPanelOpen, which the activity reads.

Test plan

  • ./gradlew :app:compileDebugKotlin: passes
  • ./gradlew :app:testDebugUnitTest: passes
  • ./gradlew :app:assembleDebug: passes
  • ./gradlew ktfmtCheck: none of the changed files is newly flagged. The task fails on 19 files, an identical list on master. PlayerViewModel.kt is on that list already, and its new lines are kept out of the formatter's rewrite.
  • ./gradlew checkCancellationRethrow: fails on 2 sites in AudiobookshelfProgressSyncer.kt that fail identically on master. This diff doesn't touch them.
  • Repeated independent code reviews, with fixes between rounds.
  • Manual check on an Android tablet with a hardware keyboard (in progress). Feedback so far is already included: Space no longer presses Skip Outro, and pausing shows the controls.
  • Manual test of every shortcut:
    • lock mode
    • intro / outro skip

Risk

  • Contracts and deploy: no breaking API, schema or contract changes, no deploy ordering, no new configuration. The change is client-only.
  • First arrow press: on a real keyboard, arrows, digits and Home/End do nothing until a letter or Space has been pressed in the player. Usually that's the first Space to pause.
  • Keyboard focus while paused: after Tab, focus stays on the controls while paused, because the controls don't auto-hide then. Arrows move focus and Enter clicks the focused control, as in a browser. Touch or resuming playback leaves that state.
  • Mute: M sets the system media volume to 0, the same mechanism the volume gesture uses. If you leave the player while muted, other media apps stay silent too.
  • End key: End seeks to the end, as YouTube does, which can mark the item played, trigger autoplay or close the player on the last item.
  • Already present, not changed here: Shift+N/P and the speed keys reuse onNextEpisode/onPreviousEpisode/SetPlaybackSpeed. Like the on-screen buttons and the speed panel, these act locally only inside a SyncPlay group.

Space/K play-pause, J/L and arrows seek, up/down volume, M mute,
0-9 percent seek, Home/End, Shift+,/. speed, Shift+N/P episode,
I picture-in-picture.
Skip shortcuts before the player is ready, while casting and while
controls are locked; disable seeking and episode skips for live TV
and intros; only skip episodes when a neighbour exists; match < and >
by character so speed keys work on non-US layouts.
Track keyboard focus on the player root and dispatch keys to a focused
control first, so panel lists, sliders and buttons keep arrow/Space
navigation. Also toggle play/pause from UI state, map volume steps
exactly, respect NumLock for numpad digits and ignore speed keys during
a long-press speed-up.
Each repeated seek is forwarded to the SyncPlay server and re-syncs every
member, so held J/L/arrow keys only seek once per press while in a group.
On live TV and intros, seek shortcuts are consumed as no-ops instead of
falling through to focus navigation. Space/K pause while buffering, and
shortcuts stay off while a cast session is connected.
Shortcuts step aside while an in-window panel (tracks, quality, speed,
episodes, chapters, sleep timer, version picker) is open, and until an
item is loaded. With nothing focused, OK/Enter shows the controls and
then moves focus into them, so remotes and gamepads without Tab can
still reach every control.
Remotes, gamepads and D-pads keep master's plain focus navigation, so
drop the OK-to-Tab focus entry (it landed focus on Back, where the next
Space exited the player). Also treat the SyncPlay members popup as an
open panel, and resume instead of pausing once playback has ended.
Derive the open-panel flag from the same conditions the panels render
under, so a panel that disappears with its data no longer disables
shortcuts. When a control is focused, arrow keys stay with focus
navigation even at its edge instead of seeking or changing volume.
CEC remote keys arrive via the virtual alphabetic keyboard, so require a
physical keyboard and skip SOURCE_HDMI. Gate on left Alt only so AltGr
layouts can still type < and >.
Some TV remotes report as full keyboards. Arrow keys now keep master's
focus navigation until the same device presses a letter or Space in
the player, after which they seek and change volume.
Space no longer clicks a focused control (e.g. Skip Outro); it toggles
play/pause and shows the controls. Enter presses the visible skip
button. Digits and Home/End now also need a device that has typed, and
the volume indicator reads 0 when muted.
Consume the UP of every DOWN a shortcut took, even if state changed in
between, so Compose never sees an unpaired UP. Leave I unhandled when
picture-in-picture isn't supported.
The skip-segment button can hold focus as the only focusable control,
which swallowed arrow keys. Once a keyboard has typed, arrows (like
Space) always seek and change volume; other keys still reach a focused
control first.
@MakD

MakD commented Oct 8, 2026

Copy link
Copy Markdown
Owner

@polferov is this ready for testing?

@polferov

polferov commented Oct 8, 2026

Copy link
Copy Markdown
Author

Yes. I've been using it for the last few days without problems.

@polferov
polferov marked this pull request as ready for review October 8, 2026 09:57
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.

2 participants