feat: Add iOS detection and specific fullscreen handling, and remove redundant touch event handlers from player controls.

This commit is contained in:
kuekhaoyang
2025-11-20 09:30:48 +08:00
parent 33b5cc36e9
commit d199c7aa6f
2 changed files with 31 additions and 43 deletions
+8 -38
View File
@@ -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' }}
+23 -5
View File
@@ -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;
}