[Feature]: Allow customising / remapping keyboard shortcuts (hotkeys)
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 #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
1when I mean to hitEscapein the player — but1is bound tonavigateTabs(["1"..."6"]inshortcuts.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 stablename, an i18nlabel, and a list ofcombos(e.g.playPause→[["Space"]],seekForward→[["ArrowRight"], ["Shift","ArrowRight"]]), grouped intogeneralandplayercategories.src/common/Shortcuts/Shortcuts.tsx— the provider statically importsshortcuts.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 (viauseShortcuts()), 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 actionname, 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:
- In Settings → Shortcuts, make each row editable: click a binding, capture the next key combo, and assign it to that action.
- Persist overrides (a
{ [shortcutName]: Combo[] }map) locally on the device, falling back to the defaults inshortcuts.jsonwhen there's no override. - Conflict detection — warn (or block) when a combo is already bound to another action in the same context.
- Reset to default — per‑shortcut and a "reset all" button.
Describe alternatives you've considered
- Editing
shortcuts.jsonand 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 coreSettingsstruct. 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(the1–6tab range) is read‑only. Its keys are hard‑wired to tab indices in the consumer (Number(key) - 1inApp.js), so remapping/unbinding it would require changing that call site. Solving the "accidental1in the player" case (unbind it, or suppress global tab navigation while the player is focused) is a separate, small follow‑up.
Source: Stremio/stremio-web