diff --git a/src/app/play/page.tsx b/src/app/play/page.tsx index 997baab..04d54ec 100644 --- a/src/app/play/page.tsx +++ b/src/app/play/page.tsx @@ -1,4 +1,4 @@ -/* eslint-disable @typescript-eslint/no-explicit-any, react-hooks/exhaustive-deps, no-console, @next/next/no-img-element */ +/* eslint-disable @typescript-eslint/ban-ts-comment, @typescript-eslint/no-explicit-any, react-hooks/exhaustive-deps, no-console, @next/next/no-img-element */ 'use client'; @@ -88,6 +88,9 @@ function PlayPageClient() { // 收藏状态 const [favorited, setFavorited] = useState(false); + // 是否显示旋转提示(5s 后自动隐藏) + const [showOrientationTip, setShowOrientationTip] = useState(false); + const orientationTipTimeoutRef = useRef(null); // 用于记录是否需要在播放器 ready 后跳转到指定进度 const resumeTimeRef = useRef(null); @@ -1053,6 +1056,94 @@ function PlayPageClient() { } }; + // 监听屏幕方向变化:竖屏时显示提示蒙层 + useEffect(() => { + if (typeof window === 'undefined') return; + const mql = window.matchMedia('(orientation: portrait)'); + + const update = () => { + const portrait = mql.matches; + + // 在进入竖屏时显示提示,5 秒后自动隐藏 + if (portrait) { + setShowOrientationTip(true); + if (orientationTipTimeoutRef.current) { + clearTimeout(orientationTipTimeoutRef.current); + } + orientationTipTimeoutRef.current = setTimeout(() => { + setShowOrientationTip(false); + }, 5000); + } else { + setShowOrientationTip(false); + if (orientationTipTimeoutRef.current) { + clearTimeout(orientationTipTimeoutRef.current); + orientationTipTimeoutRef.current = null; + } + } + }; + + // 初始执行一次 + update(); + + if (mql.addEventListener) { + mql.addEventListener('change', update); + } else { + // Safari < 14 + // @ts-ignore + mql.addListener(update); + } + + return () => { + if (mql.removeEventListener) { + mql.removeEventListener('change', update); + } else { + // @ts-ignore + mql.removeListener(update); + } + }; + }, []); + + // 进入/退出全屏时锁定/解锁横屏 + useEffect(() => { + if (typeof document === 'undefined') return; + + const lockLandscape = async () => { + try { + // 某些浏览器需要在用户手势触发后才能调用 + if (screen.orientation && (screen.orientation as any).lock) { + await (screen.orientation as any).lock('landscape'); + } + } catch (err) { + console.warn('横屏锁定失败:', err); + } + }; + + const unlock = () => { + try { + if (screen.orientation && (screen.orientation as any).unlock) { + (screen.orientation as any).unlock(); + } + } catch (_) { + // 忽略解锁屏幕方向失败的错误 + } + }; + + const handleFsChange = () => { + const isFs = !!document.fullscreenElement; + if (isFs) { + lockLandscape(); + } else { + unlock(); + } + }; + + document.addEventListener('fullscreenchange', handleFsChange); + return () => { + document.removeEventListener('fullscreenchange', handleFsChange); + unlock(); + }; + }, []); + if (loading) { return (
@@ -1106,6 +1197,26 @@ function PlayPageClient() { onMouseMove={handleMouseMove} onClick={handleClick} > + {/* 竖屏提示蒙层 */} + {showOrientationTip && ( +
+ + + + 请横屏观看 +
+ )} + {/* 换源加载遮罩 */} {sourceChanging && (
@@ -1251,7 +1362,7 @@ function PlayPageClient() { {/* 侧拉面板 */}
@@ -1305,150 +1416,148 @@ function PlayPageClient() { )} {/* 换源侧拉面板 */} - {showSourcePanel && ( - <> - {/* 遮罩层 */} + <> + {/* 遮罩层 */} + {showSourcePanel && (
setShowSourcePanel(false)} /> + )} - {/* 侧拉面板 */} -
-
-
-

播放源

- -
+ + + +
- {/* 搜索结果 */} -
- {searchLoading && ( -
-
- 搜索中... -
- )} + {/* 搜索结果 */} +
+ {searchLoading && ( +
+
+ 搜索中... +
+ )} - {searchError && ( -
- {searchError} -
- )} + {searchError && ( +
+ {searchError} +
+ )} - {!searchLoading && - !searchError && - searchResults.length === 0 && ( -
- 未找到相关视频源 -
- )} + {!searchLoading && !searchError && searchResults.length === 0 && ( +
+ 未找到相关视频源 +
+ )} - {!searchLoading && !searchError && searchResults.length > 0 && ( -
- {[ - ...searchResults.filter( - (r) => + {!searchLoading && !searchError && searchResults.length > 0 && ( +
+ {[ + ...searchResults.filter( + (r) => + r.source === currentSource && + String(r.id) === String(currentId) + ), + ...searchResults.filter( + (r) => + !( r.source === currentSource && String(r.id) === String(currentId) - ), - ...searchResults.filter( - (r) => - !( - r.source === currentSource && - String(r.id) === String(currentId) + ) + ), + ].map((result) => { + const isCurrentSource = + result.source === currentSource && + String(result.id) === String(currentId); + return ( +
+ !isCurrentSource && + handleSourceChange( + result.source, + result.id, + result.title ) - ), - ].map((result) => { - const isCurrentSource = - result.source === currentSource && - String(result.id) === String(currentId); - return ( -
- !isCurrentSource && - handleSourceChange( - result.source, - result.id, - result.title - ) - } - > - {/* 视频封面 */} -
- {result.title} + } + > + {/* 视频封面 */} +
+ {result.title} - {/* 集数圆形指示器 */} - {result.episodes && ( -
- - {result.episodes} - -
- )} + {/* 集数圆形指示器 */} + {result.episodes && ( +
+ + {result.episodes} + +
+ )} - {isCurrentSource && ( -
-
- 当前播放 -
+ {isCurrentSource && ( +
+
+ 当前播放
- )} -
+
+ )} +
- {/* 视频信息 */} -
-

- {result.title} -

-
-
- {result.source_name} -
+ {/* 视频信息 */} +
+

+ {result.title} +

+
+
+ {result.source_name}
- ); - })} -
- )} -
+
+ ); + })} +
+ )}
- - )} +
+
); }