refactor: seek via keybind router (< / >), free arrows for navigation
This commit is contained in:
@@ -18,22 +18,21 @@ const DEBUG = import.meta.env.DEBUG;
|
|||||||
|
|
||||||
export function App() {
|
export function App() {
|
||||||
const nav = useNavigation();
|
const nav = useNavigation();
|
||||||
const audio = useAudio();
|
|
||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
const renderer = useRenderer();
|
const renderer = useRenderer();
|
||||||
const themeContext = useTheme();
|
const themeContext = useTheme();
|
||||||
const theme = themeContext.theme;
|
const theme = themeContext.theme;
|
||||||
const keybind = useKeybinds();
|
const keybind = useKeybinds();
|
||||||
|
|
||||||
// Multimedia keys (physical play/seek keys) still feed the audio backend
|
// Multimedia keys (physical play/volume/speed keys) still feed the audio
|
||||||
// regardless of the on-screen yazi keybinds.
|
// backend regardless of the on-screen yazi keybinds. Seek lives on the
|
||||||
|
// keybind router (< / > = shift+, / shift+.), so arrows stay on navigation.
|
||||||
useMultimediaKeys({
|
useMultimediaKeys({
|
||||||
playerFocused: () =>
|
playerFocused: () =>
|
||||||
nav.activeTab() === TABS.PLAYER && nav.mode() !== NavMode.NORMAL
|
nav.activeTab() === TABS.PLAYER && nav.mode() !== NavMode.NORMAL
|
||||||
? true
|
? true
|
||||||
: false,
|
: false,
|
||||||
inputFocused: () => nav.inputFocused(),
|
inputFocused: () => nav.inputFocused(),
|
||||||
hasEpisode: () => !!audio.currentEpisode(),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Mouse text-selection → clipboard (unchanged from the old shell).
|
// Mouse text-selection → clipboard (unchanged from the old shell).
|
||||||
|
|||||||
@@ -72,6 +72,6 @@
|
|||||||
"audio-toggle": ["P"], // play / pause (shift+p)
|
"audio-toggle": ["P"], // play / pause (shift+p)
|
||||||
"audio-next": ["N"], // next episode (shift+n)
|
"audio-next": ["N"], // next episode (shift+n)
|
||||||
"audio-prev": ["B"], // prev episode (shift+b)
|
"audio-prev": ["B"], // prev episode (shift+b)
|
||||||
"audio-seek-forward": ["shift-."], // seek forward (shift+.)
|
"audio-seek-forward": ["shift-."], // seek forward (> = shift+.)
|
||||||
"audio-seek-backward": ["shift-,"] // seek backward (shift+,)
|
"audio-seek-backward": ["shift-,"] // seek backward (< = shift+,)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -421,14 +421,6 @@ export function useAudio(): AudioControls {
|
|||||||
await doSetVolume(Math.max(0, Number((volume() - 0.05).toFixed(2))));
|
await doSetVolume(Math.max(0, Number((volume() - 0.05).toFixed(2))));
|
||||||
});
|
});
|
||||||
|
|
||||||
const unsubMediaSeekFwd = on("media.seekForward", async () => {
|
|
||||||
await seekRelative(10);
|
|
||||||
});
|
|
||||||
|
|
||||||
const unsubMediaSeekBack = on("media.seekBackward", async () => {
|
|
||||||
await seekRelative(-10);
|
|
||||||
});
|
|
||||||
|
|
||||||
const unsubMediaSpeed = on("media.speedCycle", async () => {
|
const unsubMediaSpeed = on("media.speedCycle", async () => {
|
||||||
const next = speed() >= 2 ? 0.5 : Number((speed() + 0.25).toFixed(2));
|
const next = speed() >= 2 ? 0.5 : Number((speed() + 0.25).toFixed(2));
|
||||||
await doSetSpeed(next);
|
await doSetSpeed(next);
|
||||||
@@ -515,8 +507,6 @@ export function useAudio(): AudioControls {
|
|||||||
unsubMediaToggle();
|
unsubMediaToggle();
|
||||||
unsubMediaVolUp();
|
unsubMediaVolUp();
|
||||||
unsubMediaVolDown();
|
unsubMediaVolDown();
|
||||||
unsubMediaSeekFwd();
|
|
||||||
unsubMediaSeekBack();
|
|
||||||
unsubMediaSpeed();
|
unsubMediaSpeed();
|
||||||
|
|
||||||
if (refCount <= 0) {
|
if (refCount <= 0) {
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
/**
|
/**
|
||||||
* Global multimedia key handler hook.
|
* Global multimedia key handler hook.
|
||||||
*
|
*
|
||||||
* Captures media-related key events (play/pause, volume, seek, speed)
|
* Captures media-related key events (play/pause, volume, speed)
|
||||||
* regardless of which component is focused. Uses the event bus to
|
* regardless of which component is focused. Uses the event bus to
|
||||||
* decouple key detection from audio control logic.
|
* decouple key detection from audio control logic.
|
||||||
*
|
*
|
||||||
* Volume and speed are app-level settings — adjustable with or without
|
* Volume and speed are app-level settings — adjustable with or without
|
||||||
* an episode loaded (they apply to the next playback and persist). Seek
|
* an episode loaded (they apply to the next playback and persist). Seek
|
||||||
* is playback-dependent, so it still requires a loaded episode.
|
* is NOT handled here: it lives on the yazi keybind router (`<` / `>` =
|
||||||
|
* shift+, / shift+.), so the arrow keys stay free for navigation.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { useKeyboard } from "@opentui/solid";
|
import { useKeyboard } from "@opentui/solid";
|
||||||
@@ -17,8 +18,6 @@ export type MediaKeyAction =
|
|||||||
| "media.toggle"
|
| "media.toggle"
|
||||||
| "media.volumeUp"
|
| "media.volumeUp"
|
||||||
| "media.volumeDown"
|
| "media.volumeDown"
|
||||||
| "media.seekForward"
|
|
||||||
| "media.seekBackward"
|
|
||||||
| "media.speedCycle";
|
| "media.speedCycle";
|
||||||
|
|
||||||
export interface MultimediaKeysOptions {
|
export interface MultimediaKeysOptions {
|
||||||
@@ -26,8 +25,6 @@ export interface MultimediaKeysOptions {
|
|||||||
playerFocused?: () => boolean;
|
playerFocused?: () => boolean;
|
||||||
/** When true, skip handling (text input has focus) */
|
/** When true, skip handling (text input has focus) */
|
||||||
inputFocused?: () => boolean;
|
inputFocused?: () => boolean;
|
||||||
/** Whether an episode is currently loaded */
|
|
||||||
hasEpisode?: () => boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -59,16 +56,6 @@ export function useMultimediaKeys(options: MultimediaKeysOptions = {}) {
|
|||||||
emit("media.volumeDown", {});
|
emit("media.volumeDown", {});
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "left":
|
|
||||||
if (!options.hasEpisode?.()) return;
|
|
||||||
emit("media.seekBackward", {});
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "right":
|
|
||||||
if (!options.hasEpisode?.()) return;
|
|
||||||
emit("media.seekForward", {});
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "s":
|
case "s":
|
||||||
emit("media.speedCycle", {});
|
emit("media.speedCycle", {});
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ export function PlayerPage() {
|
|||||||
|
|
||||||
<box height={1} />
|
<box height={1} />
|
||||||
<text fg={muted()}>
|
<text fg={muted()}>
|
||||||
{"P play/pause N next B prev ◀▶ seek h back"}
|
{"P play/pause N next B prev < > seek h back"}
|
||||||
</text>
|
</text>
|
||||||
</box>
|
</box>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -132,8 +132,6 @@ export type AppEvents = {
|
|||||||
"media.toggle": {};
|
"media.toggle": {};
|
||||||
"media.volumeUp": {};
|
"media.volumeUp": {};
|
||||||
"media.volumeDown": {};
|
"media.volumeDown": {};
|
||||||
"media.seekForward": {};
|
|
||||||
"media.seekBackward": {};
|
|
||||||
"media.speedCycle": {};
|
"media.speedCycle": {};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -69,8 +69,8 @@ const DEFAULT_KEYBINDS: KeybindsResolved = {
|
|||||||
"audio-toggle": ["P"],
|
"audio-toggle": ["P"],
|
||||||
"audio-next": ["N"],
|
"audio-next": ["N"],
|
||||||
"audio-prev": ["B"],
|
"audio-prev": ["B"],
|
||||||
"audio-seek-forward": ["shift-."],
|
"audio-seek-forward": ["shift-."], // > = shift+.
|
||||||
"audio-seek-backward": ["shift-,"],
|
"audio-seek-backward": ["shift-,"], // < = shift+,
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Copy keybinds.jsonc to user config directory on first run */
|
/** Copy keybinds.jsonc to user config directory on first run */
|
||||||
|
|||||||
Reference in New Issue
Block a user