mirror of
https://github.com/KuekHaoYang/KVideo.git
synced 2026-08-15 00:33:44 +08:00
refactor: clean up SourceBadges component by removing unused elements refactor: update useDoubleTap hook to support continuous skipping in skip mode
160 lines
4.3 KiB
TypeScript
160 lines
4.3 KiB
TypeScript
'use client';
|
|
|
|
import { useEffect, useRef, useState } from 'react';
|
|
|
|
interface DoubleTapHandler {
|
|
onDoubleTapLeft: () => void;
|
|
onDoubleTapRight: () => void;
|
|
onSingleTap: () => void;
|
|
onSkipContinueLeft: () => void;
|
|
onSkipContinueRight: () => void;
|
|
isSkipModeActive: boolean;
|
|
}
|
|
|
|
/**
|
|
* Hook for handling double-tap gestures on mobile devices
|
|
* Divides the video into left/right zones for skip forward/backward
|
|
*/
|
|
export function useDoubleTap({
|
|
onDoubleTapLeft,
|
|
onDoubleTapRight,
|
|
onSingleTap,
|
|
onSkipContinueLeft,
|
|
onSkipContinueRight,
|
|
isSkipModeActive,
|
|
}: DoubleTapHandler) {
|
|
const lastTapRef = useRef<{ time: number; side: 'left' | 'right' | null }>({
|
|
time: 0,
|
|
side: null,
|
|
});
|
|
const singleTapTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
|
|
|
const handleTap = (e: React.TouchEvent<HTMLVideoElement>) => {
|
|
const currentTime = Date.now();
|
|
const videoElement = e.currentTarget;
|
|
const touch = e.touches[0] || e.changedTouches[0];
|
|
|
|
if (!touch || !videoElement) return;
|
|
|
|
// Calculate touch position relative to video element
|
|
const rect = videoElement.getBoundingClientRect();
|
|
const x = touch.clientX - rect.left;
|
|
const width = rect.width;
|
|
const side = x < width / 2 ? 'left' : 'right';
|
|
|
|
const timeDiff = currentTime - lastTapRef.current.time;
|
|
const sameSide = lastTapRef.current.side === side;
|
|
|
|
// Clear any pending single tap
|
|
if (singleTapTimeoutRef.current) {
|
|
clearTimeout(singleTapTimeoutRef.current);
|
|
singleTapTimeoutRef.current = null;
|
|
}
|
|
|
|
// If skip mode is active, single tap continues skipping
|
|
if (isSkipModeActive) {
|
|
if (side === 'left') {
|
|
onSkipContinueLeft();
|
|
} else {
|
|
onSkipContinueRight();
|
|
}
|
|
lastTapRef.current = { time: currentTime, side };
|
|
return;
|
|
}
|
|
|
|
// Double tap detected (within 300ms on the same side)
|
|
if (timeDiff < 300 && sameSide) {
|
|
e.preventDefault();
|
|
|
|
if (side === 'left') {
|
|
onDoubleTapLeft();
|
|
} else {
|
|
onDoubleTapRight();
|
|
}
|
|
|
|
// Reset to prevent triple-tap
|
|
lastTapRef.current = { time: 0, side: null };
|
|
} else {
|
|
// Possible single tap - wait to see if there's a double tap
|
|
lastTapRef.current = { time: currentTime, side };
|
|
|
|
singleTapTimeoutRef.current = setTimeout(() => {
|
|
// After 300ms, no double tap detected, execute single tap action
|
|
onSingleTap();
|
|
singleTapTimeoutRef.current = null;
|
|
}, 300);
|
|
}
|
|
};
|
|
|
|
return { handleTap };
|
|
}
|
|
|
|
/**
|
|
* Hook for managing screen orientation on mobile devices
|
|
* Auto-rotates to landscape on fullscreen, portrait on exit
|
|
*/
|
|
export function useScreenOrientation(isFullscreen: boolean) {
|
|
useEffect(() => {
|
|
if (typeof window === 'undefined' || !('screen' in window)) return;
|
|
|
|
const handleOrientation = async () => {
|
|
try {
|
|
const screen = window.screen as any;
|
|
|
|
if (isFullscreen) {
|
|
// Fullscreen: Lock to landscape
|
|
if (screen.orientation?.lock) {
|
|
await screen.orientation.lock('landscape').catch((err: any) => {
|
|
console.warn('Could not lock orientation:', err);
|
|
});
|
|
}
|
|
} else {
|
|
// Exit fullscreen: Unlock to allow portrait
|
|
if (screen.orientation?.unlock) {
|
|
screen.orientation.unlock();
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.warn('Orientation API not supported:', error);
|
|
}
|
|
};
|
|
|
|
handleOrientation();
|
|
|
|
// Cleanup: Always unlock on unmount
|
|
return () => {
|
|
try {
|
|
const screen = window.screen as any;
|
|
if (screen.orientation?.unlock) {
|
|
screen.orientation.unlock();
|
|
}
|
|
} catch (error) {
|
|
// Ignore cleanup errors
|
|
}
|
|
};
|
|
}, [isFullscreen]);
|
|
}
|
|
|
|
/**
|
|
* Hook to detect if the device is mobile
|
|
*/
|
|
export function useIsMobile() {
|
|
const [isMobile, setIsMobile] = useState(false);
|
|
|
|
useEffect(() => {
|
|
const checkMobile = () => {
|
|
const mobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(
|
|
navigator.userAgent
|
|
) || window.innerWidth < 768;
|
|
setIsMobile(mobile);
|
|
};
|
|
|
|
checkMobile();
|
|
window.addEventListener('resize', checkMobile);
|
|
|
|
return () => window.removeEventListener('resize', checkMobile);
|
|
}, []);
|
|
|
|
return isMobile;
|
|
}
|