feat: Introduce 'auto' fullscreen type with device-specific defaults, update player logic and settings, and add dedicated iOS fullscreen documentation.

This commit is contained in:
kuekhaoyang
2026-02-16 10:58:47 +08:00
parent bb7bc3e75f
commit e04fe79355
7 changed files with 75 additions and 12 deletions
+9 -3
View File
@@ -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;
@@ -282,11 +282,15 @@ export function DesktopMoreMenu({
<div className="relative">
<button
onClick={() => {
setFullscreenType(fullscreenType === 'native' ? 'window' : 'native');
if (fullscreenType === 'auto') setFullscreenType('native');
else if (fullscreenType === 'native') setFullscreenType('window');
else setFullscreenType('auto');
}}
className={`flex items-center gap-1 bg-[var(--glass-bg)] border border-[var(--glass-border)] text-[var(--text-color)] rounded-[var(--radius-2xl)] outline-none hover:border-[var(--accent-color)] hover:bg-[color-mix(in_srgb,var(--accent-color)_5%,transparent)] transition-all cursor-pointer whitespace-nowrap ${isRotated ? 'px-1.5 py-0.5 text-[9px]' : 'px-2 sm:px-2.5 py-1 sm:py-1.5 text-[10px] sm:text-xs'}`}
>
<span>{fullscreenType === 'native' ? '系统全屏' : '网页全屏'}</span>
<span>
{fullscreenType === 'auto' ? '自动 (Auto)' : fullscreenType === 'native' ? '系统全屏' : '网页全屏'}
</span>
<Icons.Maximize size={isRotated ? 10 : 12} className="text-[var(--text-color-secondary)]" />
</button>
</div>
+1 -1
View File
@@ -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]);
+53
View File
@@ -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.
+3 -3
View File
@@ -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,
};
+2 -2
View File
@@ -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",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "kvideo",
"version": "4.0.8",
"version": "4.0.9",
"private": true,
"scripts": {
"dev": "next dev",