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",