diff --git a/components/player/DesktopVideoPlayer.tsx b/components/player/DesktopVideoPlayer.tsx index fdf4dd7..0f965fa 100644 --- a/components/player/DesktopVideoPlayer.tsx +++ b/components/player/DesktopVideoPlayer.tsx @@ -9,7 +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 { useIsIOS, useIsMobile } from '@/lib/hooks/mobile/useDeviceDetection'; import './web-fullscreen.css'; interface DesktopVideoPlayerProps { @@ -41,6 +41,7 @@ export function DesktopVideoPlayer({ const { refs, data, actions } = useDesktopPlayerState(); const { fullscreenType: settingsFullscreenType } = usePlayerSettings(); const isIOS = useIsIOS(); + const isMobile = useIsMobile(); // State to track if device is in landscape mode const [isLandscape, setIsLandscape] = React.useState(true); @@ -62,8 +63,13 @@ export function DesktopVideoPlayer({ }; }, []); - // Force windowed fullscreen on iOS to avoid native player hijacking - const fullscreenType = isIOS ? 'window' : settingsFullscreenType; + // Use user preference for fullscreen type, resolving 'auto' to device default + // Auto Rules: + // - Mobile: Window Fullscreen (Better for Danmaku/Controls) + // - Desktop: Native Fullscreen (Better for PiP/Performance) + const fullscreenType = settingsFullscreenType === 'auto' + ? (isIOS ? 'window' : isMobile ? 'window' : 'native') // Treat all mobile as window for consistency if auto + : settingsFullscreenType; // Check if we need to force landscape (iOS + Fullscreen + Portrait) const shouldForceLandscape = data.isFullscreen && fullscreenType === 'window' && isIOS && !isLandscape; diff --git a/components/player/desktop/DesktopMoreMenu.tsx b/components/player/desktop/DesktopMoreMenu.tsx index 75febe5..c1afa62 100644 --- a/components/player/desktop/DesktopMoreMenu.tsx +++ b/components/player/desktop/DesktopMoreMenu.tsx @@ -282,11 +282,15 @@ export function DesktopMoreMenu({
diff --git a/components/player/hooks/usePlayerSettings.ts b/components/player/hooks/usePlayerSettings.ts index 520965d..4e5d877 100644 --- a/components/player/hooks/usePlayerSettings.ts +++ b/components/player/hooks/usePlayerSettings.ts @@ -93,7 +93,7 @@ export function usePlayerSettings() { updateSetting('adKeywords', value); }, [updateSetting]); - const setFullscreenType = useCallback((value: 'native' | 'window') => { + const setFullscreenType = useCallback((value: 'auto' | 'native' | 'window') => { updateSetting('fullscreenType', value); }, [updateSetting]); diff --git a/docs/ios-fullscreen-guide.md b/docs/ios-fullscreen-guide.md new file mode 100644 index 0000000..8f136c2 --- /dev/null +++ b/docs/ios-fullscreen-guide.md @@ -0,0 +1,53 @@ +# iOS Fullscreen Guide + +## Why can't I switch fullscreen modes? + +Previously, the player forced **Web Fullscreen** (Custom UI) on all iOS devices to provide advanced features like Danmaku and custom controls. This prevented the "System Fullscreen" option in settings from working even if selected. + +**We have now fixed this behavior.** You can now switch between the two modes in the player settings. + +--- + +## Fullscreen Modes Explained + +### 1. System Fullscreen (Native) +**Default behavior on iOS.** Best for standard playback, AirPlay, Picture-in-Picture. + +- **Pros:** + - Uses the native iOS video player. + - Smoothest performance and battery life. + - Native support for **AirPlay** and **Picture-in-Picture (PiP)**. + - Familiar iOS gestures (pinch to zoom, etc.). +- **Cons:** + - **No Danmaku (Bullet Comments)** in fullscreen. + - Custom subtitle styling may be limited. + - Default iOS controls instead of KVideo's custom controls. + +### 2. Web Fullscreen (Window/Custom) +**Best for:** Danmaku, Advanced Controls, Subtitles. + +- **Pros:** + - **Danmaku works in fullscreen!** + - Uses KVideo's custom interface and controls. + - Better subtitle styling and positioning. + - Quick access to playback speed, quality, and episode selection without leaving fullscreen. +- **Cons:** + - **Address Bar Issue:** On some iPhones, the Safari address bar may not disappear completely in landscape mode, requiring manual hiding (scrolling up) or adding the app to the Home Screen. + - **Not true fullscreen:** It's actually a "rotated" web page element that fills the screen (hence why it's sometimes called "Window Fullscreen" or "Fake Fullscreen"). + - Native gestures like standard PiP might require an extra tap. + +--- + +## How to Switch + +1. Open a video in the player. +2. Tap the **Settings (Gear Icon)** in the top right corner. +3. Tap **Fullscreen Mode** (全屏方式) to cycle through: + - **Auto (Default)**: + - **Mobile**: Uses Web Fullscreen. + - **Desktop**: Uses System Fullscreen. + - **System Fullscreen** (系统全屏) - Standard Experience + - **Web Fullscreen** (网页全屏) - Enhanced Experience (Danmaku) +5. Tap the Fullscreen button on the video player to enter fullscreen. + +> **Note for iOS Users:** If "Web Fullscreen" feels buggy (e.g., orientation issues), try locking your phone's orientation to Portrait before entering fullscreen, or switch to "System Fullscreen" for a more stable experience. diff --git a/lib/store/settings-store.ts b/lib/store/settings-store.ts index 2621945..e9cc30f 100644 --- a/lib/store/settings-store.ts +++ b/lib/store/settings-store.ts @@ -44,7 +44,7 @@ export interface AppSettings { realtimeLatency: boolean; // Enable real-time latency ping updates searchDisplayMode: SearchDisplayMode; // 'normal' = individual cards, 'grouped' = group same-name videos episodeReverseOrder: boolean; // Persist episode list reverse state - fullscreenType: 'native' | 'window'; // Fullscreen mode preference + fullscreenType: 'auto' | 'native' | 'window'; // Fullscreen mode preference: 'auto' (native on desktop, window on mobile) | 'native' | 'window' proxyMode: ProxyMode; // Proxy behavior: 'retry' | 'none' | 'always' rememberScrollPosition: boolean; // Remember scroll position when navigating back or refreshing } @@ -118,7 +118,7 @@ function getDefaultAppSettings(): AppSettings { realtimeLatency: false, searchDisplayMode: 'normal', episodeReverseOrder: false, - fullscreenType: 'native', + fullscreenType: 'auto', proxyMode: 'retry', rememberScrollPosition: true, }; @@ -196,7 +196,7 @@ export const settingsStore = { realtimeLatency: parsed.realtimeLatency !== undefined ? parsed.realtimeLatency : false, searchDisplayMode: parsed.searchDisplayMode === 'grouped' ? 'grouped' : 'normal', episodeReverseOrder: parsed.episodeReverseOrder !== undefined ? parsed.episodeReverseOrder : false, - fullscreenType: parsed.fullscreenType === 'window' ? 'window' : 'native', + fullscreenType: (parsed.fullscreenType === 'window' || parsed.fullscreenType === 'native' || parsed.fullscreenType === 'auto') ? parsed.fullscreenType : 'auto', proxyMode: (parsed.proxyMode === 'retry' || parsed.proxyMode === 'none' || parsed.proxyMode === 'always') ? parsed.proxyMode : 'retry', rememberScrollPosition: parsed.rememberScrollPosition !== undefined ? parsed.rememberScrollPosition : true, }; diff --git a/package-lock.json b/package-lock.json index d8a8c2b..9093cc7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "kvideo", - "version": "4.0.8", + "version": "4.0.9", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "kvideo", - "version": "4.0.8", + "version": "4.0.9", "dependencies": { "@dnd-kit/core": "^6.3.1", "@dnd-kit/sortable": "^10.0.0", diff --git a/package.json b/package.json index 557f3e4..551c362 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "kvideo", - "version": "4.0.8", + "version": "4.0.9", "private": true, "scripts": { "dev": "next dev",