mirror of
https://github.com/KuekHaoYang/KVideo.git
synced 2026-08-15 08:43:44 +08:00
feat: Add iOS detection and specific fullscreen handling, and remove redundant touch event handlers from player controls.
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
|
||||
import { useRef, useState, useEffect } from 'react';
|
||||
import { Icons } from '@/components/ui/Icon';
|
||||
import { useDoubleTap, useScreenOrientation } from '@/lib/hooks/useMobilePlayer';
|
||||
import { useDoubleTap, useScreenOrientation, useIsIOS } from '@/lib/hooks/useMobilePlayer';
|
||||
|
||||
interface MobileVideoPlayerProps {
|
||||
src: string;
|
||||
@@ -43,6 +43,7 @@ export function MobileVideoPlayer({
|
||||
const [toastMessage, setToastMessage] = useState<string | null>(null);
|
||||
const [showToast, setShowToast] = useState(false);
|
||||
const [viewportWidth, setViewportWidth] = useState(0);
|
||||
const isIOS = useIsIOS();
|
||||
|
||||
const controlsTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
||||
const skipTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
||||
@@ -394,6 +395,12 @@ export function MobileVideoPlayer({
|
||||
if (!containerRef.current) return;
|
||||
|
||||
if (!isFullscreen) {
|
||||
// iOS specific handling
|
||||
if (isIOS && videoRef.current && (videoRef.current as any).webkitEnterFullscreen) {
|
||||
(videoRef.current as any).webkitEnterFullscreen();
|
||||
return;
|
||||
}
|
||||
|
||||
// Use container fullscreen to maintain custom controls
|
||||
// Try standard fullscreen API first
|
||||
if (containerRef.current.requestFullscreen) {
|
||||
@@ -599,9 +606,6 @@ export function MobileVideoPlayer({
|
||||
e.stopPropagation();
|
||||
togglePlay();
|
||||
}}
|
||||
onTouchEnd={(e) => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
className={`btn-icon ${buttonPadding} flex-shrink-0 touch-manipulation relative z-[60]`}
|
||||
aria-label={isPlaying ? 'Pause' : 'Play'}
|
||||
style={{ WebkitTapHighlightColor: 'transparent' }}
|
||||
@@ -624,11 +628,6 @@ export function MobileVideoPlayer({
|
||||
e.stopPropagation();
|
||||
setShowMoreMenu(!showMoreMenu);
|
||||
}}
|
||||
onTouchEnd={(e) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
setShowMoreMenu(!showMoreMenu);
|
||||
}}
|
||||
className={`btn-icon ${buttonPadding} flex-shrink-0 touch-manipulation`}
|
||||
aria-label="更多"
|
||||
style={{ WebkitTapHighlightColor: 'transparent' }}
|
||||
@@ -651,12 +650,6 @@ export function MobileVideoPlayer({
|
||||
setShowMoreMenu(false);
|
||||
handleCopyLink();
|
||||
}}
|
||||
onTouchEnd={(e) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
setShowMoreMenu(false);
|
||||
handleCopyLink();
|
||||
}}
|
||||
className="w-full px-4 py-3 text-left text-sm text-white hover:bg-white/20 flex items-center gap-3 transition-all touch-manipulation"
|
||||
style={{ WebkitTapHighlightColor: 'transparent' }}
|
||||
>
|
||||
@@ -673,12 +666,6 @@ export function MobileVideoPlayer({
|
||||
setShowMoreMenu(false);
|
||||
setShowVolumeMenu(true);
|
||||
}}
|
||||
onTouchEnd={(e) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
setShowMoreMenu(false);
|
||||
setShowVolumeMenu(true);
|
||||
}}
|
||||
className="w-full px-4 py-3 text-left text-sm text-white hover:bg-white/20 flex items-center gap-3 transition-all touch-manipulation"
|
||||
style={{ WebkitTapHighlightColor: 'transparent' }}
|
||||
>
|
||||
@@ -693,12 +680,6 @@ export function MobileVideoPlayer({
|
||||
setShowMoreMenu(false);
|
||||
setShowSpeedMenu(true);
|
||||
}}
|
||||
onTouchEnd={(e) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
setShowMoreMenu(false);
|
||||
setShowSpeedMenu(true);
|
||||
}}
|
||||
className="w-full px-4 py-3 text-left text-sm text-white hover:bg-white/20 flex items-center gap-3 transition-all touch-manipulation"
|
||||
style={{ WebkitTapHighlightColor: 'transparent' }}
|
||||
>
|
||||
@@ -717,12 +698,6 @@ export function MobileVideoPlayer({
|
||||
setShowMoreMenu(false);
|
||||
togglePictureInPicture();
|
||||
}}
|
||||
onTouchEnd={(e) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
setShowMoreMenu(false);
|
||||
togglePictureInPicture();
|
||||
}}
|
||||
className="w-full px-4 py-3 text-left text-sm text-white hover:bg-white/20 flex items-center gap-3 transition-all touch-manipulation"
|
||||
style={{ WebkitTapHighlightColor: 'transparent' }}
|
||||
>
|
||||
@@ -741,11 +716,6 @@ export function MobileVideoPlayer({
|
||||
e.stopPropagation();
|
||||
toggleFullscreen();
|
||||
}}
|
||||
onTouchEnd={(e) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
toggleFullscreen();
|
||||
}}
|
||||
className={`btn-icon ${buttonPadding} flex-shrink-0 touch-manipulation relative z-[60]`}
|
||||
aria-label={isFullscreen ? '退出全屏' : '全屏'}
|
||||
style={{ WebkitTapHighlightColor: 'transparent' }}
|
||||
|
||||
@@ -33,7 +33,7 @@ export function useDoubleTap({
|
||||
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
|
||||
@@ -65,19 +65,19 @@ export function useDoubleTap({
|
||||
// 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();
|
||||
@@ -100,7 +100,7 @@ export function useScreenOrientation(isFullscreen: boolean) {
|
||||
const handleOrientation = async () => {
|
||||
try {
|
||||
const screen = window.screen as any;
|
||||
|
||||
|
||||
if (isFullscreen) {
|
||||
// Fullscreen: Lock to landscape
|
||||
if (screen.orientation?.lock) {
|
||||
@@ -157,3 +157,21 @@ export function useIsMobile() {
|
||||
|
||||
return isMobile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook to detect if the device is iOS
|
||||
*/
|
||||
export function useIsIOS() {
|
||||
const [isIOS, setIsIOS] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const checkIOS = () => {
|
||||
const ios = /iPad|iPhone|iPod/.test(navigator.userAgent) && !(window as any).MSStream;
|
||||
setIsIOS(ios);
|
||||
};
|
||||
|
||||
checkIOS();
|
||||
}, []);
|
||||
|
||||
return isIOS;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user