feat: Enhance web fullscreen experience on iOS by forcing windowed mode, handling orientation, and preventing native player takeover.

This commit is contained in:
kuekhaoyang
2026-02-03 20:08:47 +08:00
parent dcc897f139
commit a68be420af
2 changed files with 53 additions and 4 deletions
+39 -3
View File
@@ -9,6 +9,7 @@ import { useStallDetection } from './hooks/useStallDetection';
import { DesktopControlsWrapper } from './desktop/DesktopControlsWrapper';
import { DesktopOverlayWrapper } from './desktop/DesktopOverlayWrapper';
import { usePlayerSettings } from './hooks/usePlayerSettings';
import { useIsIOS } from '@/lib/hooks/mobile/useDeviceDetection';
import './web-fullscreen.css';
interface DesktopVideoPlayerProps {
@@ -38,7 +39,11 @@ export function DesktopVideoPlayer({
isReversed = false,
}: DesktopVideoPlayerProps) {
const { refs, data, actions } = useDesktopPlayerState();
const { fullscreenType } = usePlayerSettings();
const { fullscreenType: settingsFullscreenType } = usePlayerSettings();
const isIOS = useIsIOS();
// Force windowed fullscreen on iOS to avoid native player hijacking
const fullscreenType = isIOS ? 'window' : settingsFullscreenType;
// Initialize HLS Player
useHlsPlayer({
@@ -114,11 +119,34 @@ export function DesktopVideoPlayer({
handleVideoError,
} = logic;
// State to track if device is in landscape mode
const [isLandscape, setIsLandscape] = React.useState(true);
React.useEffect(() => {
const checkOrientation = () => {
// Check if width > height
if (typeof window !== 'undefined') {
setIsLandscape(window.innerWidth > window.innerHeight);
}
};
checkOrientation();
window.addEventListener('resize', checkOrientation);
window.addEventListener('orientationchange', checkOrientation);
return () => {
window.removeEventListener('resize', checkOrientation);
window.removeEventListener('orientationchange', checkOrientation);
};
}, []);
// Check if we need to force landscape (iOS + Fullscreen + Portrait)
const shouldForceLandscape = data.isFullscreen && fullscreenType === 'window' && isIOS && !isLandscape;
return (
<div
ref={containerRef}
className={`kvideo-container relative aspect-video bg-black rounded-[var(--radius-2xl)] group ${data.isFullscreen && fullscreenType === 'window' ? 'is-web-fullscreen' : ''
}`}
} ${shouldForceLandscape ? 'force-landscape' : ''}`}
onMouseMove={handleMouseMove}
onMouseLeave={() => isPlaying && setShowControls(false)}
>
@@ -132,6 +160,8 @@ export function DesktopVideoPlayer({
className="w-full h-full object-contain"
poster={poster}
x-webkit-airplay="allow"
playsInline={true} // Crucial for iOS custom fullscreen to work without native player taking over
controls={false} // Explicitly disable native controls
onPlay={handlePlay}
onPause={handlePause}
onTimeUpdate={handleTimeUpdateEvent}
@@ -139,7 +169,13 @@ export function DesktopVideoPlayer({
onError={handleVideoError}
onWaiting={() => setIsLoading(true)}
onCanPlay={() => setIsLoading(false)}
onClick={togglePlay}
onClick={(e) => {
// Prevent native behavior on iOS
// e.preventDefault();
// React synthetic event doesn't always stop native video toggle on iOS, but good practice
togglePlay();
}}
{...({ 'webkit-playsinline': 'true' } as any)} // Legacy iOS support
/>
<DesktopOverlayWrapper
+14 -1
View File
@@ -5,7 +5,9 @@
left: 0 !important;
width: 100vw !important;
height: 100vh !important;
z-index: 9999 !important;
height: 100dvh !important;
/* Fallback for browsers supporting dvh */
z-index: 2147483647 !important;
background: black !important;
border-radius: 0 !important;
}
@@ -16,3 +18,14 @@
height: 100% !important;
border-radius: 0 !important;
}
/* Force Landscape Mode for Mobile (iOS mainly) */
.kvideo-container.is-web-fullscreen.force-landscape {
width: 100vh !important;
width: 100dvh !important;
height: 100vw !important;
top: 50% !important;
left: 50% !important;
transform: translate(-50%, -50%) rotate(90deg) !important;
border-radius: 0 !important;
}