Skip to content

[Feature]: Allow customising / remapping keyboard shortcuts (hotkeys) #1332

Description

@edkranz

Is your feature request related to a problem? Please describe.

Keyboard shortcuts in Stremio Web are currently fixed. The Settings → Shortcuts pane lists them, but there is no way to change any of them. This is a problem for a number of users:

  • People with non‑QWERTY / non‑US keyboard layouts, where some default keys are awkward or unreachable (e.g. [ / ] for playback speed, - / = for subtitle size).
  • Users coming from other players (mpv, VLC, YouTube, Jellyfin) who have muscle memory for a different set of keys and would like to match them.
  • Accessibility needs, where a user may need to move an action to a single, easy‑to‑reach key.
  • Conflicts with browser or OS shortcuts (see Keyboard shortcuts interfere with browser shortcuts #203), which a user could resolve themselves if remapping were allowed.
  • Keys that are easy to hit by accident and have a disruptive effect. For example, I frequently hit 1 when I mean to hit Escape in the player — but 1 is bound to navigateTabs (["1"..."6"] in shortcuts.json), so instead of doing nothing it yanks me completely out of the media player to a top‑level nav tab.

Describe the solution you'd like

Allow users to customise / remap the existing keyboard shortcuts from the Settings → Shortcuts pane, with sensible guard‑rails.

The codebase is already well set up for this — the shortcut system is centralised and data‑driven:

  • src/common/Shortcuts/shortcuts.json — every action is already declared with a stable name, an i18n label, and a list of combos (e.g. playPause[["Space"]], seekForward[["ArrowRight"], ["Shift","ArrowRight"]]), grouped into general and player categories.
  • src/common/Shortcuts/Shortcuts.tsx — the provider statically imports shortcuts.json (import shortcuts from './shortcuts.json') and registers the combos. This is the single place that would need to merge user overrides over the defaults.
  • src/routes/Settings/Shortcuts/Shortcuts.tsx — already renders the grouped shortcuts (via useShortcuts()), but purely read‑only. This is where "click a shortcut → press new key(s) to rebind" UI would live.
  • src/common/Shortcuts/onShortcut.ts — consumers subscribe by action name, not by raw key, so remapping requires no changes at any call site (Player, App navigation, etc.). Only the default → active combo resolution changes.

Suggested behaviour:

  1. In Settings → Shortcuts, make each row editable: click a binding, capture the next key combo, and assign it to that action.
  2. Persist overrides (a { [shortcutName]: Combo[] } map) locally on the device, falling back to the defaults in shortcuts.json when there's no override.
  3. Conflict detection — warn (or block) when a combo is already bound to another action in the same context.
  4. Reset to default — per‑shortcut and a "reset all" button.

Describe alternatives you've considered

  • Editing shortcuts.json and rebuilding — only viable for people building from source; not a real option for normal users.
  • Browser extensions / userscripts to intercept keys — brittle, breaks with UI changes, and doesn't work in the desktop shell.

Additional context

Because the shortcut layer is already action‑based and centralised, the change is largely additive: an overrides store + a merge step in Shortcuts.tsx + an editing affordance in the existing Settings pane. No consumer of onShortcut(...) needs to change.

Related: #203 (shortcuts conflicting with browser shortcuts), #75 (global actions).


Implementation scope (v1)

To keep this contained entirely within stremio-web (no stremio-core / WASM changes), the first pass is deliberately minimal:

  • Storage: overrides are persisted in localStorage (per‑device), not in account/profile settings. This avoids adding a field to the core Settings struct. Profile‑level sync can be a follow‑up — the storage layer is isolated to a couple of functions, so swapping it later is straightforward.
  • Per‑binding editing: shortcuts with multiple bindings (e.g. Volume /, Seek / Shift+→) are edited one binding at a time, preserving the others.
  • Conflict detection & reset: included (per‑shortcut reset + "reset all").

Out of scope for v1 (possible follow‑ups):

  • Not synced across devices (localStorage).
  • navigateTabs (the 16 tab range) is read‑only. Its keys are hard‑wired to tab indices in the consumer (Number(key) - 1 in App.js), so remapping/unbinding it would require changing that call site. Solving the "accidental 1 in the player" case (unbind it, or suppress global tab navigation while the player is focused) is a separate, small follow‑up.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions