diff --git a/components/player/DesktopVideoPlayer.tsx b/components/player/DesktopVideoPlayer.tsx index 1ed1b24..0f502d7 100644 --- a/components/player/DesktopVideoPlayer.tsx +++ b/components/player/DesktopVideoPlayer.tsx @@ -147,6 +147,8 @@ export function DesktopVideoPlayer({ onSpeedChange={logic.changePlaybackSpeed} onSpeedMenuMouseEnter={logic.clearSpeedMenuTimeout} onSpeedMenuMouseLeave={logic.startSpeedMenuTimeout} + // Portal container + containerRef={containerRef} /> void; onMouseLeave: () => void; onCopyLink: (type?: 'original' | 'proxy') => void; + containerRef: React.RefObject; } export function DesktopMoreMenu({ @@ -21,7 +22,8 @@ export function DesktopMoreMenu({ onToggleMoreMenu, onMouseEnter, onMouseLeave, - onCopyLink + onCopyLink, + containerRef }: DesktopMoreMenuProps) { const { autoNextEpisode, @@ -42,21 +44,25 @@ export function DesktopMoreMenu({ const [menuPosition, setMenuPosition] = React.useState({ top: 0, left: 0 }); React.useEffect(() => { - if (showMoreMenu && buttonRef.current) { - const rect = buttonRef.current.getBoundingClientRect(); + if (showMoreMenu && buttonRef.current && containerRef.current) { + const buttonRect = buttonRef.current.getBoundingClientRect(); + const containerRect = containerRef.current.getBoundingClientRect(); + setMenuPosition({ - top: rect.bottom + 10, // 10px spacing - left: rect.left + top: buttonRect.bottom - containerRect.top + 10, + left: buttonRect.left - containerRect.left }); } - }, [showMoreMenu]); + }, [showMoreMenu, containerRef]); const handleToggle = () => { - if (!showMoreMenu && buttonRef.current) { - const rect = buttonRef.current.getBoundingClientRect(); + if (!showMoreMenu && buttonRef.current && containerRef.current) { + const buttonRect = buttonRef.current.getBoundingClientRect(); + const containerRect = containerRef.current.getBoundingClientRect(); + setMenuPosition({ - top: rect.bottom + 10, // 10px spacing - left: rect.left + top: buttonRect.bottom - containerRect.top + 10, + left: buttonRect.left - containerRect.left }); } onToggleMoreMenu(); @@ -64,7 +70,7 @@ export function DesktopMoreMenu({ const MenuContent = (
{/* More Menu Dropdown (Portal) */} - {showMoreMenu && typeof document !== 'undefined' && createPortal(MenuContent, document.body)} + {showMoreMenu && typeof document !== 'undefined' && createPortal(MenuContent, containerRef.current || document.body)}
); } diff --git a/components/player/desktop/DesktopOverlay.tsx b/components/player/desktop/DesktopOverlay.tsx index 0083eeb..930eecc 100644 --- a/components/player/desktop/DesktopOverlay.tsx +++ b/components/player/desktop/DesktopOverlay.tsx @@ -33,6 +33,7 @@ interface DesktopOverlayProps { onSpeedChange: (speed: number) => void; onSpeedMenuMouseEnter: () => void; onSpeedMenuMouseLeave: () => void; + containerRef: React.RefObject; } export function DesktopOverlay({ @@ -62,7 +63,8 @@ export function DesktopOverlay({ onToggleSpeedMenu, onSpeedChange, onSpeedMenuMouseEnter, - onSpeedMenuMouseLeave + onSpeedMenuMouseLeave, + containerRef }: DesktopOverlayProps) { // Show navigation buttons when controls are visible or when paused (controls usually show when paused anyway) const showNavButtons = showControls || !isPlaying; @@ -70,7 +72,7 @@ export function DesktopOverlay({ return ( <> {/* More Menu (Top Left) */} -
+
{/* Speed Menu (Top Right) */} -
+
diff --git a/components/player/desktop/DesktopOverlayWrapper.tsx b/components/player/desktop/DesktopOverlayWrapper.tsx index f8490cb..02664ef 100644 --- a/components/player/desktop/DesktopOverlayWrapper.tsx +++ b/components/player/desktop/DesktopOverlayWrapper.tsx @@ -22,6 +22,7 @@ interface DesktopOverlayWrapperProps { onSpeedChange: (speed: number) => void; onSpeedMenuMouseEnter: () => void; onSpeedMenuMouseLeave: () => void; + containerRef: React.RefObject; } export function DesktopOverlayWrapper({ @@ -42,7 +43,8 @@ export function DesktopOverlayWrapper({ onToggleSpeedMenu, onSpeedChange, onSpeedMenuMouseEnter, - onSpeedMenuMouseLeave + onSpeedMenuMouseLeave, + containerRef }: DesktopOverlayWrapperProps) { const { isLoading, @@ -86,6 +88,7 @@ export function DesktopOverlayWrapper({ onSpeedChange={onSpeedChange} onSpeedMenuMouseEnter={onSpeedMenuMouseEnter} onSpeedMenuMouseLeave={onSpeedMenuMouseLeave} + containerRef={containerRef} /> ); } diff --git a/components/player/desktop/DesktopSpeedMenu.tsx b/components/player/desktop/DesktopSpeedMenu.tsx index c2d4e50..4d44e64 100644 --- a/components/player/desktop/DesktopSpeedMenu.tsx +++ b/components/player/desktop/DesktopSpeedMenu.tsx @@ -9,6 +9,7 @@ interface DesktopSpeedMenuProps { onToggleSpeedMenu: () => void; onMouseEnter: () => void; onMouseLeave: () => void; + containerRef: React.RefObject; } export function DesktopSpeedMenu({ @@ -18,27 +19,32 @@ export function DesktopSpeedMenu({ onSpeedChange, onToggleSpeedMenu, onMouseEnter, - onMouseLeave + onMouseLeave, + containerRef }: DesktopSpeedMenuProps) { const buttonRef = React.useRef(null); const [menuPosition, setMenuPosition] = React.useState({ top: 0, left: 0 }); React.useEffect(() => { - if (showSpeedMenu && buttonRef.current) { - const rect = buttonRef.current.getBoundingClientRect(); + if (showSpeedMenu && buttonRef.current && containerRef.current) { + const buttonRect = buttonRef.current.getBoundingClientRect(); + const containerRect = containerRef.current.getBoundingClientRect(); + setMenuPosition({ - top: rect.bottom + 10, - left: rect.right // Align with right edge + top: buttonRect.bottom - containerRect.top + 10, + left: buttonRect.right - containerRect.left }); } - }, [showSpeedMenu]); + }, [showSpeedMenu, containerRef]); const handleToggle = () => { - if (!showSpeedMenu && buttonRef.current) { - const rect = buttonRef.current.getBoundingClientRect(); + if (!showSpeedMenu && buttonRef.current && containerRef.current) { + const buttonRect = buttonRef.current.getBoundingClientRect(); + const containerRect = containerRef.current.getBoundingClientRect(); + setMenuPosition({ - top: rect.bottom + 10, - left: rect.right // Align with right edge + top: buttonRect.bottom - containerRect.top + 10, + left: buttonRect.right - containerRect.left }); } onToggleSpeedMenu(); @@ -47,7 +53,7 @@ export function DesktopSpeedMenu({ const MenuContent = (
onSpeedChange(speed)} - className={`w-full px-3 py-2 rounded-[var(--radius-2xl)] text-sm font-medium transition-colors ${playbackRate === speed + className={`w-full px-4 py-1.5 rounded-[var(--radius-2xl)] text-sm font-medium transition-colors ${playbackRate === speed ? 'bg-[var(--accent-color)] text-white' : 'text-[var(--text-color)] hover:bg-[color-mix(in_srgb,var(--accent-color)_15%,transparent)]' }`} @@ -85,7 +91,7 @@ export function DesktopSpeedMenu({ {/* Speed Menu (Portal) */} - {showSpeedMenu && typeof document !== 'undefined' && createPortal(MenuContent, document.body)} + {showSpeedMenu && typeof document !== 'undefined' && createPortal(MenuContent, containerRef.current || document.body)}
); } diff --git a/components/player/hooks/desktop/useFullscreenControls.ts b/components/player/hooks/desktop/useFullscreenControls.ts index 45dd571..7d28ce1 100644 --- a/components/player/hooks/desktop/useFullscreenControls.ts +++ b/components/player/hooks/desktop/useFullscreenControls.ts @@ -47,6 +47,15 @@ export function useFullscreenControls({ // Fallback for browsers that only support fullscreen on video element (like some car browsers) (videoRef.current as any).webkitEnterFullscreen(); } + + // Lock orientation to landscape on mobile devices if supported + if (window.screen && (window.screen as any).orientation && (window.screen as any).orientation.lock) { + try { + await (window.screen as any).orientation.lock('landscape'); + } catch (e) { + console.warn('Orientation lock failed:', e); + } + } } catch (error) { console.warn('Fullscreen request failed, trying fallback:', error); // Last ditch effort: try native video fullscreen if container failed @@ -69,6 +78,15 @@ export function useFullscreenControls({ } else if ((document as any).msExitFullscreen) { await (document as any).msExitFullscreen(); } + + // Unlock orientation when exiting fullscreen + if (window.screen && (window.screen as any).orientation && (window.screen as any).orientation.unlock) { + try { + (window.screen as any).orientation.unlock(); + } catch (e) { + console.warn('Orientation unlock failed:', e); + } + } } catch (error) { console.error('Failed to exit fullscreen:', error); } @@ -84,6 +102,19 @@ export function useFullscreenControls({ (document as any).msFullscreenElement ); setIsFullscreen(isInFullscreen); + + // Double check orientation lock/unlock on change + if (isInFullscreen) { + if (window.screen && (window.screen as any).orientation && (window.screen as any).orientation.lock) { + (window.screen as any).orientation.lock('landscape').catch(() => { }); + } + } else { + if (window.screen && (window.screen as any).orientation && (window.screen as any).orientation.unlock) { + try { + (window.screen as any).orientation.unlock(); + } catch (e) { } + } + } }; document.addEventListener('fullscreenchange', handleFullscreenChange); diff --git a/package-lock.json b/package-lock.json index 2d728c4..8bbdfab 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "kvideo", - "version": "3.7.2", + "version": "3.7.5", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "kvideo", - "version": "3.7.2", + "version": "3.7.5", "dependencies": { "@dnd-kit/core": "^6.3.1", "@dnd-kit/sortable": "^10.0.0", diff --git a/package.json b/package.json index 59f47c4..3f28ff8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "kvideo", - "version": "3.7.2", + "version": "3.7.5", "private": true, "scripts": { "dev": "next dev",