mirror of
https://github.com/zoriya/Kyoo.git
synced 2025-12-26 23:00:26 -05:00
Remake touch controls
This commit is contained in:
parent
fc9695a2dc
commit
9343bb524c
@ -150,131 +150,6 @@ export const Controls = ({
|
||||
);
|
||||
};
|
||||
|
||||
export const HoverTouch = ({ children, ...props }: { children: ReactNode }) => {
|
||||
const hover = useAtomValue(hoverAtom);
|
||||
const setHover = useSetAtom(hoverReasonAtom);
|
||||
const mouseCallback = useRef<NodeJS.Timeout | null>(null);
|
||||
const touch = useRef<{ count: number; timeout?: NodeJS.Timeout }>({
|
||||
count: 0,
|
||||
});
|
||||
const playerWidth = useRef<number | null>(null);
|
||||
const isTouch = useIsTouch();
|
||||
|
||||
const show = useCallback(() => {
|
||||
setHover((x) => ({ ...x, mouseMoved: true }));
|
||||
if (mouseCallback.current) clearTimeout(mouseCallback.current);
|
||||
mouseCallback.current = setTimeout(() => {
|
||||
setHover((x) => ({ ...x, mouseMoved: false }));
|
||||
}, 2500);
|
||||
}, [setHover]);
|
||||
|
||||
// On mouse move
|
||||
useEffect(() => {
|
||||
if (Platform.OS !== "web") return;
|
||||
const handler = (e: PointerEvent) => {
|
||||
if (e.pointerType !== "mouse") return;
|
||||
show();
|
||||
};
|
||||
|
||||
document.addEventListener("pointermove", handler);
|
||||
return () => document.removeEventListener("pointermove", handler);
|
||||
}, [show]);
|
||||
|
||||
// When the controls hide, remove focus so space can be used to play/pause instead of triggering the button
|
||||
// It also serves to hide the tooltip.
|
||||
useEffect(() => {
|
||||
if (Platform.OS !== "web") return;
|
||||
if (!hover && document.activeElement instanceof HTMLElement)
|
||||
document.activeElement.blur();
|
||||
}, [hover]);
|
||||
|
||||
const { css } = useYoshiki();
|
||||
|
||||
const duration = useAtomValue(durationAtom);
|
||||
const setPlay = useSetAtom(playAtom);
|
||||
const setProgress = useSetAtom(progressAtom);
|
||||
const setFullscreen = useSetAtom(fullscreenAtom);
|
||||
|
||||
const onPress = (e: { pointerType: string; x: number }) => {
|
||||
if (Platform.OS === "web" && e.pointerType === "mouse") {
|
||||
setPlay((x) => !x);
|
||||
return;
|
||||
}
|
||||
if (hover) setHover((x) => ({ ...x, mouseMoved: false }));
|
||||
else show();
|
||||
};
|
||||
const onDoublePress = (e: { pointerType: string; x: number }) => {
|
||||
if (Platform.OS === "web" && e.pointerType === "mouse") {
|
||||
// Only reset touch count for the web, on mobile you can continue to seek by pressing again.
|
||||
touch.current.count = 0;
|
||||
setFullscreen((x) => !x);
|
||||
return;
|
||||
}
|
||||
|
||||
show();
|
||||
if (!duration || !playerWidth.current) return;
|
||||
|
||||
if (e.x < playerWidth.current * 0.33) {
|
||||
setProgress((x) => Math.max(x - 10, 0));
|
||||
}
|
||||
if (e.x > playerWidth.current * 0.66) {
|
||||
setProgress((x) => Math.min(x + 10, duration));
|
||||
}
|
||||
};
|
||||
|
||||
const onAnyPress = (e: { pointerType: string; x: number }) => {
|
||||
touch.current.count++;
|
||||
if (touch.current.count >= 2) {
|
||||
onDoublePress(e);
|
||||
clearTimeout(touch.current.timeout);
|
||||
} else {
|
||||
onPress(e);
|
||||
}
|
||||
|
||||
touch.current.timeout = setTimeout(() => {
|
||||
touch.current.count = 0;
|
||||
touch.current.timeout = undefined;
|
||||
}, 400);
|
||||
};
|
||||
|
||||
return (
|
||||
<Pressable
|
||||
tabIndex={-1}
|
||||
onPointerLeave={(e) => {
|
||||
if (e.nativeEvent.pointerType === "mouse")
|
||||
setHover((x) => ({ ...x, mouseMoved: false }));
|
||||
}}
|
||||
onPress={(e) => {
|
||||
e.preventDefault();
|
||||
onAnyPress({
|
||||
pointerType: isTouch ? "touch" : "mouse",
|
||||
x: e.nativeEvent.locationX ?? e.nativeEvent.pageX,
|
||||
});
|
||||
}}
|
||||
onLayout={(e) => {
|
||||
playerWidth.current = e.nativeEvent.layout.width;
|
||||
}}
|
||||
{...css(
|
||||
// @ts-expect-error Web only property (cursor: unset)
|
||||
{
|
||||
flexDirection: "row",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
cursor: hover ? "unset" : "none",
|
||||
},
|
||||
props,
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</Pressable>
|
||||
);
|
||||
};
|
||||
|
||||
const VideoPoster = ({
|
||||
poster,
|
||||
alt,
|
||||
|
||||
117
front/src/ui/player/controls/touch.tsx
Normal file
117
front/src/ui/player/controls/touch.tsx
Normal file
@ -0,0 +1,117 @@
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import {
|
||||
type GestureResponderEvent,
|
||||
Platform,
|
||||
Pressable,
|
||||
type PressableProps,
|
||||
} from "react-native";
|
||||
import type { VideoPlayer } from "react-native-video";
|
||||
import { useYoshiki } from "yoshiki/native";
|
||||
import { useIsTouch } from "~/primitives";
|
||||
|
||||
export const TouchControls = ({
|
||||
player,
|
||||
children,
|
||||
...props
|
||||
}: { player: VideoPlayer } & PressableProps) => {
|
||||
const { css } = useYoshiki();
|
||||
const isTouch = useIsTouch();
|
||||
|
||||
const [shouldShow, setShow] = useState(true);
|
||||
const hideTimeout = useRef<NodeJS.Timeout | null>(null);
|
||||
const show = useCallback((val: boolean = true) => {
|
||||
setShow(val);
|
||||
if (hideTimeout.current) clearTimeout(hideTimeout.current);
|
||||
hideTimeout.current = setTimeout(() => {
|
||||
hideTimeout.current = null;
|
||||
setShow(false);
|
||||
}, 2500);
|
||||
}, []);
|
||||
// TODO: handle mouse hover & seek
|
||||
|
||||
// On mouse move
|
||||
useEffect(() => {
|
||||
if (Platform.OS !== "web") return;
|
||||
const handler = (e: PointerEvent) => {
|
||||
if (e.pointerType !== "mouse") return;
|
||||
show();
|
||||
};
|
||||
|
||||
document.addEventListener("pointermove", handler);
|
||||
return () => document.removeEventListener("pointermove", handler);
|
||||
}, [show]);
|
||||
|
||||
const playerWidth = useRef<number | null>(null);
|
||||
|
||||
return (
|
||||
<DoublePressable
|
||||
onPress={() => {
|
||||
if (isTouch) {
|
||||
show(!shouldShow);
|
||||
return;
|
||||
}
|
||||
if (player.isPlaying) player.pause();
|
||||
else player.play();
|
||||
}}
|
||||
onDoublePress={(e) => {
|
||||
if (!isTouch) {
|
||||
player.toggleFullscreen();
|
||||
return;
|
||||
}
|
||||
|
||||
show();
|
||||
if (Number.isNaN(player.duration) || !playerWidth.current) return;
|
||||
|
||||
const x = e.nativeEvent.locationX ?? e.nativeEvent.pageX;
|
||||
if (x < playerWidth.current * 0.33) player.seekBy(-10);
|
||||
if (x > playerWidth.current * 0.66) player.seekBy(10);
|
||||
// Do not reset press count, you can continue to seek by pressing again.
|
||||
return true;
|
||||
}}
|
||||
onLayout={(e) => {
|
||||
playerWidth.current = e.nativeEvent.layout.width;
|
||||
}}
|
||||
onPointerLeave={(e) => {
|
||||
// instantly hide the controls when mouse leaves the view
|
||||
if (e.nativeEvent.pointerType === "mouse") show(false);
|
||||
}}
|
||||
{...css({ cursor: "none" as any }, props)}
|
||||
>
|
||||
{shouldShow && children}
|
||||
</DoublePressable>
|
||||
);
|
||||
};
|
||||
|
||||
const DoublePressable = ({
|
||||
onPress,
|
||||
onDoublePress,
|
||||
...props
|
||||
}: {
|
||||
onDoublePress: (e: GestureResponderEvent) => boolean | undefined;
|
||||
} & PressableProps) => {
|
||||
const touch = useRef<{ count: number; timeout?: NodeJS.Timeout }>({
|
||||
count: 0,
|
||||
});
|
||||
|
||||
return (
|
||||
<Pressable
|
||||
onPress={(e) => {
|
||||
e.preventDefault();
|
||||
touch.current.count++;
|
||||
if (touch.current.count >= 2) {
|
||||
const keepCount = onDoublePress(e);
|
||||
if (!keepCount) touch.current.count = 0;
|
||||
clearTimeout(touch.current.timeout);
|
||||
} else {
|
||||
onPress?.(e);
|
||||
}
|
||||
|
||||
touch.current.timeout = setTimeout(() => {
|
||||
touch.current.count = 0;
|
||||
touch.current.timeout = undefined;
|
||||
}, 400);
|
||||
}}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
Loading…
x
Reference in New Issue
Block a user