feat: add keyboard controls for playback and seeking actions

This commit is contained in:
tapframe 2026-06-09 00:40:17 +05:30
parent 9381693482
commit d7545b6924
5 changed files with 65 additions and 17 deletions

View file

@ -28,8 +28,11 @@ enum class PlayerControlsAction {
RevealLockedOverlay,
Back,
TogglePlayback,
KeyboardTogglePlayback,
SeekBack,
KeyboardSeekBack,
SeekForward,
KeyboardSeekForward,
ResizeMode,
Speed,
Subtitles,

View file

@ -162,9 +162,11 @@ internal fun PlayerScreenRuntime.togglePlayback() {
controlsVisible = true
}
internal fun PlayerScreenRuntime.prepareTogglePlaybackForNativeFallback() {
internal fun PlayerScreenRuntime.prepareTogglePlaybackForNativeFallback(revealControls: Boolean = true) {
shouldPlay = !playbackSnapshot.isPlaying
controlsVisible = true
if (revealControls) {
controlsVisible = true
}
}
internal fun PlayerScreenRuntime.seekBy(offsetMs: Long) {
@ -172,13 +174,21 @@ internal fun PlayerScreenRuntime.seekBy(offsetMs: Long) {
applySeekByControlFeedback(offsetMs)
}
internal fun PlayerScreenRuntime.prepareSeekByForNativeFallback(offsetMs: Long) {
applySeekByControlFeedback(offsetMs)
internal fun PlayerScreenRuntime.prepareSeekByForNativeFallback(
offsetMs: Long,
revealControls: Boolean = true,
) {
applySeekByControlFeedback(offsetMs, revealControls)
}
private fun PlayerScreenRuntime.applySeekByControlFeedback(offsetMs: Long) {
private fun PlayerScreenRuntime.applySeekByControlFeedback(
offsetMs: Long,
revealControls: Boolean = true,
) {
scheduleProgressSyncAfterSeek()
controlsVisible = true
if (revealControls) {
controlsVisible = true
}
when {
offsetMs > 0L -> showSeekFeedback(PlayerSeekDirection.Forward, offsetMs)
offsetMs < 0L -> showSeekFeedback(PlayerSeekDirection.Backward, abs(offsetMs))

View file

@ -226,7 +226,7 @@ internal fun PlayerScreenRuntime.RenderPlayerRuntimeUi() {
isLoading = playbackSnapshot.isLoading,
isLocked = playerControlsLocked,
lockedOverlayVisible = lockedOverlayVisible,
controlsVisible = (controlsVisible || showParentalGuide) && !playerControlsLocked,
controlsVisible = controlsVisible && !playerControlsLocked,
parentalWarnings = parentalWarnings,
showParentalGuide = showParentalGuide,
showSubmitIntro = isSeries &&
@ -508,14 +508,26 @@ private fun PlayerScreenRuntime.handlePlayerControlsAction(action: PlayerControl
prepareTogglePlaybackForNativeFallback()
return false
}
PlayerControlsAction.KeyboardTogglePlayback -> {
prepareTogglePlaybackForNativeFallback(revealControls = false)
return false
}
PlayerControlsAction.SeekBack -> {
prepareSeekByForNativeFallback(-10_000L)
return false
}
PlayerControlsAction.KeyboardSeekBack -> {
prepareSeekByForNativeFallback(-10_000L, revealControls = false)
return false
}
PlayerControlsAction.SeekForward -> {
prepareSeekByForNativeFallback(10_000L)
return false
}
PlayerControlsAction.KeyboardSeekForward -> {
prepareSeekByForNativeFallback(10_000L, revealControls = false)
return false
}
PlayerControlsAction.ResizeMode -> cycleResizeMode()
PlayerControlsAction.Speed -> cyclePlaybackSpeed()
PlayerControlsAction.Subtitles -> {

View file

@ -164,7 +164,8 @@ internal class NativePlayerController(
private fun handleFallbackAction(action: PlayerControlsAction) {
when (action) {
PlayerControlsAction.TogglePlayback -> {
PlayerControlsAction.TogglePlayback,
PlayerControlsAction.KeyboardTogglePlayback -> {
val current = handle
if (current == 0L) return
val isEnded = NativePlayerBridge.isEnded(current)
@ -176,8 +177,10 @@ internal class NativePlayerController(
NativePlayerBridge.setPaused(current, !isPaused)
}
}
PlayerControlsAction.SeekBack -> fallbackSeekBy(-10_000L)
PlayerControlsAction.SeekForward -> fallbackSeekBy(10_000L)
PlayerControlsAction.SeekBack,
PlayerControlsAction.KeyboardSeekBack -> fallbackSeekBy(-10_000L)
PlayerControlsAction.SeekForward,
PlayerControlsAction.KeyboardSeekForward -> fallbackSeekBy(10_000L)
PlayerControlsAction.DoubleTapSeekBack -> fallbackSeekBy(-10_000L)
PlayerControlsAction.DoubleTapSeekForward -> fallbackSeekBy(10_000L)
PlayerControlsAction.Speed -> cycleFallbackSpeed()
@ -432,8 +435,11 @@ private fun String.toPlayerControlsAction(): PlayerControlsAction? =
"revealLockedOverlay" -> PlayerControlsAction.RevealLockedOverlay
"back" -> PlayerControlsAction.Back
"toggle" -> PlayerControlsAction.TogglePlayback
"keyboardToggle" -> PlayerControlsAction.KeyboardTogglePlayback
"seekBack" -> PlayerControlsAction.SeekBack
"keyboardSeekBack" -> PlayerControlsAction.KeyboardSeekBack
"seekForward" -> PlayerControlsAction.SeekForward
"keyboardSeekForward" -> PlayerControlsAction.KeyboardSeekForward
"resize" -> PlayerControlsAction.ResizeMode
"speed" -> PlayerControlsAction.Speed
"subtitles" -> PlayerControlsAction.Subtitles

View file

@ -166,7 +166,6 @@
.player.chrome-hidden .top-gradient,
.player.chrome-hidden .bottom-gradient,
.player.chrome-hidden .metadata,
.player.chrome-hidden .parental-guide,
.player.chrome-hidden .header-actions,
.player.chrome-hidden .center-controls,
.player.chrome-hidden .progress,
@ -226,7 +225,7 @@
.header {
position: absolute;
top: calc(var(--vp) / 4 + env(safe-area-inset-top));
top: calc(var(--slider-bottom) + env(safe-area-inset-top));
left: var(--hp);
right: var(--hp);
display: flex;
@ -310,7 +309,7 @@
.parental-guide {
position: absolute;
top: calc(var(--vp) / 4 + env(safe-area-inset-top));
top: calc(var(--slider-bottom) + env(safe-area-inset-top));
left: var(--hp);
z-index: 5;
display: flex;
@ -3090,18 +3089,28 @@
switch (event.code) {
case "Space":
case "KeyK":
return "toggle";
return "keyboardToggle";
case "ArrowLeft":
case "KeyJ":
return "seekBack";
return "keyboardSeekBack";
case "ArrowRight":
case "KeyL":
return "seekForward";
return "keyboardSeekForward";
default:
return "";
}
};
const toggleChrome = () => {
if (state.isLocked) {
send("revealLockedOverlay", 0);
return;
}
state = { ...state, controlsVisible: !state.controlsVisible };
renderChrome();
send("toggleChrome", 0);
};
const clearPressedButton = () => {
if (!pressedButton) return;
pressedButton.classList.remove("is-pressed");
@ -3158,6 +3167,9 @@
openingOverlay.addEventListener("click", event => {
event.stopPropagation();
if (!event.target.closest("button,input")) {
toggleChrome();
}
});
modalElements.forEach(modal => {
@ -3420,7 +3432,7 @@
if (event.target.closest("button,input")) return;
window.clearTimeout(tapTimer);
tapTimer = window.setTimeout(() => {
send(state.isLocked ? "revealLockedOverlay" : "toggleChrome");
toggleChrome();
}, 220);
});
@ -3439,6 +3451,11 @@
focusShortcutRoot();
return;
}
if (event.key === "Escape") {
event.preventDefault();
send("back", 0);
return;
}
if (activeModal || isTextEntryTarget(event.target)) {
return;
}