| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948 |
- import { useEffect, useRef, useState } from 'react';
- import { useNavigate, useSearchParams } from 'react-router-dom';
- import { message as antMessage } from 'antd';
- import videojs from 'video.js';
- import { setStorage, STORAGE_KEYS, markRouteVisited, getVisitedRoutes, getUserId, getStorage, setUserId, setUserLevel } from '@/utils/storage';
- import { updateUserUIState, createUser, getUserInfo } from '@/utils/api';
- import 'video.js/dist/video-js.css';
- import { getOssUrl } from '@/utils/oss';
- import { px2vmin } from '@/utils/viewport';
- // 判断是否为后端生成的默认用户名
- const isBackendGeneratedName = (name: string): boolean => /^User\d+$/.test(name.trim());
- // 扩展 videojs 的类型定义
- type VideoJsPlayer = ReturnType<typeof videojs>;
- type WelcomeViewState = 'welcome' | 'loading' | 'playing';
- export const WelcomePage = () => {
- const navigate = useNavigate();
- const [searchParams] = useSearchParams();
- const videoRef = useRef<HTMLVideoElement>(null);
- const playerRef = useRef<VideoJsPlayer | null>(null);
- const [viewState, setViewState] = useState<WelcomeViewState>('welcome');
- const [isLoading, setIsLoading] = useState(false);
- const isVideoReadyRef = useRef(false); // 视频是否已加载完成
- const loadingStartTimeRef = useRef<number | null>(null); // 加载开始时间
- const userInteractionRef = useRef(false); // 用户是否已交互(用于确保声音播放)
- const viewStateRef = useRef<WelcomeViewState>('welcome'); // 用于在回调中访问最新状态
- const hasNavigatedRef = useRef(false); // 是否已经跳转到 /naming,防止重复执行
- const checkIntervalRef = useRef<ReturnType<typeof setInterval> | null>(null); // 检查定时器
- const fallbackTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null); // 兜底定时器
- const hasShownTimeoutTipRef = useRef(false); // 是否已经提示过"响应超时"
- // 处理错误参数(如QQ登录时用户未注册)
- useEffect(() => {
- const error = searchParams.get('error');
- const oauth = searchParams.get('oauth');
-
- if (error === 'user_not_registered' && oauth === 'qq') {
- antMessage.error('用户未注册,请先注册');
- // 清除URL参数
- navigate('/welcome', { replace: true });
- }
- }, [searchParams, navigate]);
- useEffect(() => {
- console.log('[WelcomePage] init effect start');
- // 记录用户当前所在页面
- const ok = setStorage(STORAGE_KEYS.CURRENT_PAGE, 'welcome');
- console.log('[WelcomePage] set CURRENT_PAGE=welcome result:', ok);
- if (!getStorage(STORAGE_KEYS.USER_LEVEL)) {
- setStorage(STORAGE_KEYS.USER_LEVEL, '0');
- }
- // 默认标记为未登录、未分享
- setStorage(STORAGE_KEYS.IS_LOGGED_IN, 'false');
- setStorage(STORAGE_KEYS.HAS_SHARED_AVATAR, 'false');
-
- // 记录路由访问
- markRouteVisited('/welcome');
- console.log('[WelcomePage] visited routes after markRouteVisited:', getVisitedRoutes());
- // 将页面与路由状态同步到后端(如果已有 user_id)
- const userId = getUserId();
- if (userId) {
- const visited = getVisitedRoutes();
- updateUserUIState(userId, {
- current_page: 'welcome',
- visited_routes: visited,
- }).catch((error) => {
- console.warn('[WelcomePage] 同步页面状态失败:', error);
- if (!hasShownTimeoutTipRef.current) {
- hasShownTimeoutTipRef.current = true;
- antMessage.warning('响应超时,请重试');
- }
- });
- }
- // 🚀 提前预加载视频资源(使用 link preload)
- const videoUrl = import.meta.env.VITE_WELCOME_VIDEO_URL || getOssUrl('Lyrria-video.mp4');
- const preloadLink = document.createElement('link');
- preloadLink.rel = 'preload';
- preloadLink.as = 'video';
- preloadLink.href = videoUrl;
- preloadLink.crossOrigin = 'anonymous';
- document.head.appendChild(preloadLink);
- console.log('[WelcomePage] 已添加视频预加载链接:', videoUrl);
- // 清理函数
- return () => {
- // 清理预加载链接
- if (preloadLink.parentNode) {
- preloadLink.parentNode.removeChild(preloadLink);
- }
- };
- }, []);
- // 页面打开后,后台预加载视频(在 video 元素挂载后)
- useEffect(() => {
- // 使用 setTimeout 确保 video 元素已经挂载到 DOM
- const initTimer = setTimeout(() => {
- if (!playerRef.current && videoRef.current) {
- console.log('[WelcomePage] 后台预加载视频...');
- const player = videojs(videoRef.current, {
- controls: false,
- autoplay: false,
- preload: 'auto', // 预加载视频
- fluid: true,
- aspectRatio: '16:9',
- muted: false,
- nativeControlsForTouch: false,
- playsinline: true,
- bigPlayButton: false,
- textTrackDisplay: false,
- posterImage: false,
- errorDisplay: false,
- });
- playerRef.current = player;
- // 配置原生 video 元素
- const videoElement = player.el().querySelector('video') as HTMLVideoElement;
- if (videoElement) {
- videoElement.removeAttribute('controls');
- videoElement.controls = false;
- videoElement.setAttribute('playsinline', '');
- videoElement.setAttribute('webkit-playsinline', '');
- videoElement.setAttribute('x5-playsinline', '');
- videoElement.setAttribute('x5-video-player-type', 'h5');
- videoElement.setAttribute('x5-video-player-fullscreen', 'false');
- videoElement.setAttribute('controlsList', 'nodownload nofullscreen noremoteplayback');
- videoElement.setAttribute('disablePictureInPicture', 'true');
-
- // 隐藏视频元素(后台加载)
- videoElement.style.visibility = 'hidden';
- videoElement.style.opacity = '0';
- videoElement.style.position = 'absolute';
- videoElement.style.top = '-9999px';
- // 🚀 强制开始加载视频(立即开始预加载)
- try {
- videoElement.load(); // 强制开始加载
- console.log('[WelcomePage] 已强制开始加载视频');
- } catch (error) {
- console.warn('[WelcomePage] 强制加载失败(可能已开始加载):', error);
- }
- // 监听视频加载状态
- const handleCanPlayThrough = () => {
- console.log('[WelcomePage] 视频已加载完成,可以播放');
- isVideoReadyRef.current = true;
- // 如果正在加载状态,检查是否可以开始播放
- if (viewStateRef.current === 'loading' && loadingStartTimeRef.current) {
- checkAndStartPlayback();
- }
- };
- const handleError = (error: Event) => {
- console.error('[WelcomePage] 视频加载错误:', error);
- const videoEl = error.target as HTMLVideoElement;
- if (videoEl && videoEl.error) {
- console.error('[WelcomePage] 视频错误详情:', {
- code: videoEl.error?.code,
- message: videoEl.error?.message,
- });
- }
- // 即使出错也尝试继续(可能是网络问题,稍后重试)
- isVideoReadyRef.current = false;
- };
- const handleLoadedData = () => {
- console.log('[WelcomePage] 视频数据已加载');
- };
- const handleLoadedMetadata = () => {
- console.log('[WelcomePage] 视频元数据已加载');
- };
- // 绑定事件
- videoElement.addEventListener('canplaythrough', handleCanPlayThrough);
- videoElement.addEventListener('error', handleError);
- videoElement.addEventListener('loadeddata', handleLoadedData);
- videoElement.addEventListener('loadedmetadata', handleLoadedMetadata);
- // 保存事件处理器以便清理
- (player as any)._canPlayThroughHandler = handleCanPlayThrough;
- (player as any)._errorHandler = handleError;
- (player as any)._loadedDataHandler = handleLoadedData;
- (player as any)._loadedMetadataHandler = handleLoadedMetadata;
- }
- // 持续监控并移除 controls 属性
- const controlWatcher = setInterval(() => {
- const playerEl = player.el();
- if (!playerEl) return;
- const videoEl = playerEl.querySelector('video') as HTMLVideoElement;
- if (videoEl && videoEl.hasAttribute('controls')) {
- videoEl.removeAttribute('controls');
- videoEl.controls = false;
- }
- }, 100);
- (player as any)._controlWatcher = controlWatcher;
- // 绑定播放结束事件
- const handleEnded = () => {
- console.log('[WelcomePage] 视频播放完成,跳转到 /begin');
- navigate('/begin');
- };
- player.on('ended', handleEnded);
- (player as any)._endedHandler = handleEnded;
- }
- }, 100);
- // 清理函数
- return () => {
- clearTimeout(initTimer);
- if (playerRef.current) {
- const player = playerRef.current;
- const watcher = (player as any)._controlWatcher;
- if (watcher) {
- clearInterval(watcher);
- }
- const endedHandler = (player as any)._endedHandler;
- if (endedHandler) {
- player.off('ended', endedHandler);
- }
- // 清理视频元素事件监听器
- const videoElement = player.el()?.querySelector('video') as HTMLVideoElement;
- if (videoElement) {
- const canPlayThroughHandler = (player as any)._canPlayThroughHandler;
- const errorHandler = (player as any)._errorHandler;
- const loadedDataHandler = (player as any)._loadedDataHandler;
- const loadedMetadataHandler = (player as any)._loadedMetadataHandler;
- const videoEndedHandler = (player as any)._videoEndedHandler;
- if (canPlayThroughHandler) {
- videoElement.removeEventListener('canplaythrough', canPlayThroughHandler);
- }
- if (errorHandler) {
- videoElement.removeEventListener('error', errorHandler);
- }
- if (loadedDataHandler) {
- videoElement.removeEventListener('loadeddata', loadedDataHandler);
- }
- if (loadedMetadataHandler) {
- videoElement.removeEventListener('loadedmetadata', loadedMetadataHandler);
- }
- if (videoEndedHandler) {
- videoElement.removeEventListener('ended', videoEndedHandler);
- }
- }
- // 注意:不在这里销毁播放器,因为视频需要保持加载状态
- }
- };
- }, [navigate, viewState]);
- // 更新 viewState 时同步更新 ref
- useEffect(() => {
- viewStateRef.current = viewState;
- }, [viewState]);
- // 组件卸载时清理定时器
- useEffect(() => {
- return () => {
- if (checkIntervalRef.current) {
- clearInterval(checkIntervalRef.current);
- checkIntervalRef.current = null;
- }
- if (fallbackTimerRef.current) {
- clearTimeout(fallbackTimerRef.current);
- fallbackTimerRef.current = null;
- }
- };
- }, []);
- // 检查并开始播放(结合加载状态和最小缓冲时间)
- const checkAndStartPlayback = () => {
- if (!playerRef.current || viewStateRef.current !== 'loading') {
- return;
- }
- const now = Date.now();
- const minLoadingTime = 3000; // 最小加载时间3秒
- const elapsed = loadingStartTimeRef.current
- ? now - loadingStartTimeRef.current
- : 0;
- const remainingTime = Math.max(0, minLoadingTime - elapsed);
- // 如果视频已准备好且达到最小加载时间,开始播放
- if (isVideoReadyRef.current && elapsed >= minLoadingTime) {
- console.log('[WelcomePage] 视频已准备好,开始播放');
- // 清理定时器
- if (checkIntervalRef.current) {
- clearInterval(checkIntervalRef.current);
- checkIntervalRef.current = null;
- }
- if (fallbackTimerRef.current) {
- clearTimeout(fallbackTimerRef.current);
- fallbackTimerRef.current = null;
- }
- setIsLoading(false);
- setViewState('playing');
- } else if (isVideoReadyRef.current && remainingTime > 0) {
- // 视频已准备好,但还需要等待最小加载时间
- console.log(`[WelcomePage] 视频已准备好,等待 ${remainingTime}ms 后播放`);
- // 清理检查定时器
- if (checkIntervalRef.current) {
- clearInterval(checkIntervalRef.current);
- checkIntervalRef.current = null;
- }
- setTimeout(() => {
- if (viewStateRef.current === 'loading') {
- setIsLoading(false);
- setViewState('playing');
- }
- }, remainingTime);
- } else if (!isVideoReadyRef.current && elapsed >= minLoadingTime) {
- // 达到最小加载时间但视频未准备好,再等待一段时间
- console.log('[WelcomePage] 达到最小加载时间,但视频未完全准备好,继续等待...');
- // 清理检查定时器
- if (checkIntervalRef.current) {
- clearInterval(checkIntervalRef.current);
- checkIntervalRef.current = null;
- }
- const waitTimer = setTimeout(() => {
- if (viewStateRef.current === 'loading') {
- if (isVideoReadyRef.current) {
- setIsLoading(false);
- setViewState('playing');
- } else {
- // 即使未完全准备好也尝试播放(可能是网络慢)
- console.warn('[WelcomePage] 视频未完全准备好,尝试播放');
- setIsLoading(false);
- setViewState('playing');
- }
- }
- }, 2000); // 额外等待2秒
- (playerRef.current as any)._waitTimer = waitTimer;
- }
- };
- // 当进入播放状态时,显示视频并开始播放
- useEffect(() => {
- if (viewState === 'playing' && playerRef.current) {
- console.log('[WelcomePage] 开始播放视频');
- const player = playerRef.current;
- if (!player) {
- return;
- }
- const videoElement = player.el().querySelector('video') as HTMLVideoElement;
-
- if (videoElement) {
- // 显示视频元素
- videoElement.style.visibility = 'visible';
- videoElement.style.opacity = '1';
- videoElement.style.position = 'absolute';
- videoElement.style.top = '0';
- videoElement.style.left = '0';
- videoElement.style.width = '100%';
- videoElement.style.height = '100%';
- videoElement.style.zIndex = '2';
- // 🚀 在视频播放期间预加载用户数据,避免进入 naming 页面时卡顿
- const preloadUserData = async () => {
- console.log('[WelcomePage] 开始预加载用户数据...');
- try {
- // 确保存在 userId:如果没有,则创建用户
- let userId = getUserId();
- if (!userId) {
- try {
- const response = await createUser();
- if (response.success && response.data?.user_id) {
- userId = response.data.user_id;
- setUserId(userId);
-
- // 保存用户名到 localStorage
- if (response.data.username) {
- if (!isBackendGeneratedName(response.data.username)) {
- setStorage(STORAGE_KEYS.USER_NAME, response.data.username);
- }
- }
-
- if (typeof response.data.user_level === 'number') {
- setUserLevel(response.data.user_level);
- }
-
- console.log('[WelcomePage] ✅ 预加载:创建用户成功', userId);
- }
- } catch (error) {
- console.error('[WelcomePage] 预加载:创建用户失败', error);
- }
- }
- // 如果 localStorage 中没有保存的用户名,从后端获取
- const savedName = getStorage(STORAGE_KEYS.USER_NAME);
- if (!savedName) {
- const user_id = userId || getUserId();
- if (user_id) {
- try {
- const response = await getUserInfo(user_id);
- if (response.success && response.data) {
- const { username, bot_name } = response.data;
-
- if (typeof response.data.user_level === 'number') {
- setUserLevel(response.data.user_level);
- }
-
- // 保存用户名到 localStorage
- if (username) {
- if (!isBackendGeneratedName(username)) {
- setStorage(STORAGE_KEYS.USER_NAME, username);
- }
- }
-
- // 保存 bot_name 到 localStorage
- if (bot_name) {
- setStorage(STORAGE_KEYS.LUMI_NAME, bot_name);
- }
-
- console.log('[WelcomePage] ✅ 预加载:获取用户信息成功');
- }
- } catch (error) {
- console.error('[WelcomePage] 预加载:获取用户信息失败', error);
- }
- }
- }
- // 更新用户 UI 状态(非阻塞)
- if (userId) {
- const visited = getVisitedRoutes();
- updateUserUIState(userId, {
- current_page: 'naming',
- visited_routes: visited,
- }).catch(() => {
- // 同步失败不影响前端流程
- });
- }
- } catch (error) {
- console.error('[WelcomePage] 预加载用户数据异常:', error);
- }
- };
-
- // 立即开始预加载(不阻塞视频播放)
- preloadUserData();
- // 🚀 统一的安全跳转函数,避免多次 navigate
- const safeNavigateToBegin = () => {
- if (hasNavigatedRef.current) {
- return;
- }
- hasNavigatedRef.current = true;
- console.log('[WelcomePage] 触发跳转到 /begin');
- if (checkIntervalRef.current) {
- clearInterval(checkIntervalRef.current);
- checkIntervalRef.current = null;
- }
- if (fallbackTimerRef.current) {
- clearTimeout(fallbackTimerRef.current);
- fallbackTimerRef.current = null;
- }
- navigate('/begin');
- };
- // 绑定视频播放结束事件(原生事件,确保可靠)
- const handleVideoEnded = () => {
- console.log('[WelcomePage] 视频播放完成(video ended),准备跳转');
- safeNavigateToBegin();
- };
- videoElement.addEventListener('ended', handleVideoEnded);
- // 保存处理器以便清理
- (player as any)._videoEndedHandler = handleVideoEnded;
- // 🔁 兼容部分浏览器不触发 ended 的情况:监听播放进度,接近结尾时跳转
- const handleTimeUpdate = () => {
- try {
- const duration = videoElement.duration;
- const currentTime = videoElement.currentTime;
- if (!duration || Number.isNaN(duration) || duration <= 0) {
- return;
- }
- const progress = currentTime / duration;
- if (progress >= 0.995) {
- console.log('[WelcomePage] 视频已播放到 99.5% 以上,触发兜底跳转');
- safeNavigateToBegin();
- }
- } catch (error) {
- console.warn('[WelcomePage] timeupdate 处理异常:', error);
- }
- };
- videoElement.addEventListener('timeupdate', handleTimeUpdate);
- (player as any)._timeUpdateHandler = handleTimeUpdate;
- // 确保视频未静音(用户已交互,应该可以播放声音)
- player.muted(false);
- videoElement.muted = false;
- videoElement.volume = 1.0;
-
- // 由于用户已点击(交互),移动端Chrome应该允许播放声音
- // 尝试播放(有声)
- const playPromise = player.play();
- if (playPromise) {
- playPromise
- .then(() => {
- console.log('[WelcomePage] 视频播放成功(有声)');
- // 确保未静音
- player.muted(false);
- videoElement.muted = false;
- videoElement.volume = 1.0;
- })
- .catch((error: unknown) => {
- console.warn('[WelcomePage] 有声播放失败,尝试静音播放:', error);
- // 如果有声播放失败,尝试静音播放
- player.muted(true);
- videoElement.muted = true;
- const mutedPlayPromise = player.play();
- if (mutedPlayPromise) {
- mutedPlayPromise
- .then(() => {
- console.log('[WelcomePage] 静音播放成功,尝试恢复声音');
- // 静音播放成功后,尝试恢复声音(需要用户再次交互)
- // 但由于用户已点击,应该可以恢复
- setTimeout(() => {
- try {
- player.muted(false);
- videoElement.muted = false;
- videoElement.volume = 1.0;
- console.log('[WelcomePage] 已尝试恢复声音');
- } catch (e) {
- console.warn('[WelcomePage] 恢复声音失败:', e);
- }
- }, 100);
- })
- .catch((err: unknown) => {
- console.error('[WelcomePage] 静音播放也失败:', err);
- });
- }
- });
- }
- }
- // 清理函数:移除事件监听器
- return () => {
- if (playerRef.current) {
- const player = playerRef.current;
- const videoElement = player.el()?.querySelector('video') as HTMLVideoElement;
- if (videoElement) {
- const videoEndedHandler = (player as any)._videoEndedHandler;
- const timeUpdateHandler = (player as any)._timeUpdateHandler;
- if (videoEndedHandler) {
- videoElement.removeEventListener('ended', videoEndedHandler);
- (player as any)._videoEndedHandler = null;
- }
- if (timeUpdateHandler) {
- videoElement.removeEventListener('timeupdate', timeUpdateHandler);
- (player as any)._timeUpdateHandler = null;
- }
- }
- }
- };
- }
- }, [viewState, navigate]);
- // 监听播放结束事件(备用方案,确保事件已绑定)
- useEffect(() => {
- if (!playerRef.current) return;
- const player = playerRef.current;
-
- // 如果还没有绑定 ended 事件,则绑定
- if (!(player as any)._endedHandler) {
- const handleEnded = () => {
- console.log('[WelcomePage] 视频播放完成,跳转到 /begin');
- navigate('/begin');
- };
- player.on('ended', handleEnded);
- (player as any)._endedHandler = handleEnded;
- }
- }, [navigate, viewState]); // 当播放状态改变时,确保事件已绑定
- const handleHelloClick = async () => {
- // 标记用户已交互(用于确保声音播放)
- userInteractionRef.current = true;
-
- // 清理之前的定时器
- if (checkIntervalRef.current) {
- clearInterval(checkIntervalRef.current);
- checkIntervalRef.current = null;
- }
- if (fallbackTimerRef.current) {
- clearTimeout(fallbackTimerRef.current);
- fallbackTimerRef.current = null;
- }
-
- // 直接进入播放状态(页面已预加载视频,无需再显示加载层)
- setIsLoading(false);
- setViewState('playing');
-
- if (!playerRef.current) {
- return;
- }
- const player = playerRef.current;
- const videoElement = player.el()?.querySelector('video') as HTMLVideoElement;
- if (!videoElement) {
- return;
- }
- // 确保未静音
- player.muted(false);
- videoElement.muted = false;
- videoElement.volume = 1.0;
- // 优先在用户交互链中直接播放
- const tryPlay = async () => {
- try {
- const playPromise = player.play();
- if (playPromise) {
- await playPromise;
- console.log('[WelcomePage] ✅ 点击后立即开始播放');
- }
- } catch (error) {
- console.warn('[WelcomePage] 立即播放失败,等待视频就绪后再试:', error);
- }
- };
- await tryPlay();
- // 如果视频尚未完全就绪,等待 canplaythrough 再尝试一次
- if (videoElement.readyState < HTMLMediaElement.HAVE_FUTURE_DATA) {
- const handleReady = async () => {
- videoElement.removeEventListener('canplaythrough', handleReady);
- await tryPlay();
- };
- videoElement.addEventListener('canplaythrough', handleReady, { once: true });
- }
- };
- return (
- <>
- {/* Video.js 全局样式 */}
- <style>{`
- .video-js .vjs-control-bar {
- display: none !important;
- opacity: 0 !important;
- visibility: hidden !important;
- }
- .video-js:hover .vjs-control-bar,
- .video-js.vjs-user-active .vjs-control-bar,
- .video-js.vjs-user-inactive .vjs-control-bar,
- .video-js.vjs-paused .vjs-control-bar {
- display: none !important;
- opacity: 0 !important;
- visibility: hidden !important;
- }
- .video-js video::-webkit-media-controls {
- display: none !important;
- -webkit-appearance: none !important;
- opacity: 0 !important;
- visibility: hidden !important;
- }
- .video-js video::-webkit-media-controls-enclosure {
- display: none !important;
- -webkit-appearance: none !important;
- opacity: 0 !important;
- visibility: hidden !important;
- }
- .video-js video::-webkit-media-controls-panel,
- .video-js video::-webkit-media-controls-play-button,
- .video-js video::-webkit-media-controls-timeline,
- .video-js video::-webkit-media-controls-current-time-display,
- .video-js video::-webkit-media-controls-time-remaining-display,
- .video-js video::-webkit-media-controls-mute-button,
- .video-js video::-webkit-media-controls-volume-slider,
- .video-js video::-webkit-media-controls-fullscreen-button {
- display: none !important;
- }
- video::-webkit-media-controls-start-playback-button,
- video::-webkit-media-controls-overlay-enclosure,
- video::-webkit-media-controls-overlay-play-button {
- display: none !important;
- -webkit-appearance: none !important;
- opacity: 0 !important;
- }
- .player-container video,
- .video-js video {
- -webkit-appearance: none !important;
- appearance: none !important;
- }
- .video-js *:focus {
- outline: none;
- }
- .video-js .vjs-big-play-button {
- display: none !important;
- opacity: 0 !important;
- visibility: hidden !important;
- pointer-events: none !important;
- }
- .video-js .vjs-loading-spinner {
- display: none !important;
- opacity: 0 !important;
- visibility: hidden !important;
- pointer-events: none !important;
- }
- .video-js,
- .video-js .vjs-tech,
- .video-js video {
- pointer-events: auto !important;
- cursor: default;
- }
- .vjs-fluid {
- padding-top: 0 !important;
- }
- .vjs-16-9 {
- padding-top: 0 !important;
- }
- .video-js video {
- width: 100% !important;
- height: 100% !important;
- object-fit: contain;
- z-index: 2;
- display: block !important;
- }
- .player-container,
- .video-js {
- user-select: none;
- -webkit-user-select: none;
- -moz-user-select: none;
- -ms-user-select: none;
- -webkit-touch-callout: none;
- }
- @keyframes spin {
- 0% { transform: rotate(0deg); }
- 100% { transform: rotate(360deg); }
- }
- @keyframes pulse {
- 0%, 100% { opacity: 1; }
- 50% { opacity: 0.6; }
- }
- `}</style>
-
- <div
- style={{
- position: 'relative',
- width: '100%',
- minHeight: 'calc(var(--vh, 1vh) * 100)',
- overflow: 'hidden',
- paddingTop: 'env(safe-area-inset-top)',
- paddingLeft: 'env(safe-area-inset-left)',
- paddingRight: 'env(safe-area-inset-right)',
- paddingBottom: 'env(safe-area-inset-bottom)',
- }}
- >
- {/* 欢迎界面内容 */}
- {viewState === 'welcome' && (
- <div
- style={{
- position: 'relative',
- width: '100%',
- minHeight: 'calc(var(--vh, 1vh) * 100)',
- margin: '0 auto',
- zIndex: 1,
- }}
- >
- {/* Hello 文字 - 可点击 */}
- <div
- onClick={handleHelloClick}
- style={{
- position: 'absolute',
- top: px2vmin(138),
- left: px2vmin(45),
- width: '100%',
- height: px2vmin(113),
- background: 'linear-gradient(97.3deg, rgba(245, 243, 240, 0.8) 2.21%, rgba(245, 243, 240, 0.3) 150.89%)',
- WebkitBackgroundClip: 'text',
- WebkitTextFillColor: 'transparent',
- backgroundClip: 'text',
- opacity: 1,
- display: 'flex',
- alignItems: 'center',
- justifyContent: 'center',
- fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Arial, sans-serif',
- fontWeight: 500,
- fontStyle: 'normal',
- fontSize: px2vmin(96),
- lineHeight: 1,
- letterSpacing: 0,
- textAlign: 'center',
- textTransform: 'capitalize',
- cursor: 'pointer',
- transition: 'opacity 0.2s ease, transform 0.2s ease',
- userSelect: 'none',
- WebkitTapHighlightColor: 'transparent',
- touchAction: 'manipulation',
- transform: 'translateZ(0)',
- willChange: 'transform',
- }}
- onMouseEnter={(e) => { e.currentTarget.style.opacity = '0.9'; }}
- onMouseLeave={(e) => { e.currentTarget.style.opacity = '1'; }}
- onMouseDown={(e) => { e.currentTarget.style.opacity = '0.8'; }}
- onMouseUp={(e) => { e.currentTarget.style.opacity = '1'; }}
- >
- Hello,
- </div>
- </div>
- )}
- {/* 加载动画遮罩 */}
- {isLoading && (
- <div
- style={{
- position: 'fixed',
- top: 0,
- left: 0,
- width: '100%',
- height: '100%',
- background: 'rgba(0, 0, 0, 0.7)',
- display: 'flex',
- justifyContent: 'center',
- alignItems: 'center',
- zIndex: 10000,
- backdropFilter: 'blur(4px)',
- }}
- >
- <div
- style={{
- display: 'flex',
- flexDirection: 'column',
- alignItems: 'center',
- gap: px2vmin(24),
- }}
- >
- {/* 加载动画 */}
- <div
- style={{
- position: 'relative',
- width: px2vmin(80),
- height: px2vmin(80),
- }}
- >
- <div
- style={{
- position: 'absolute',
- top: 0,
- left: 0,
- width: '100%',
- height: '100%',
- border: `${px2vmin(3)} solid transparent`,
- borderTopColor: 'rgba(255, 255, 255, 0.9)',
- borderRadius: '50%',
- animation: 'spin 1s linear infinite',
- }}
- />
- <div
- style={{
- position: 'absolute',
- top: '15%',
- left: '15%',
- width: '70%',
- height: '70%',
- border: `${px2vmin(3)} solid transparent`,
- borderTopColor: 'rgba(255, 255, 255, 0.7)',
- borderRadius: '50%',
- animation: 'spin 1.5s linear infinite reverse',
- }}
- />
- <div
- style={{
- position: 'absolute',
- top: '30%',
- left: '30%',
- width: '40%',
- height: '40%',
- border: `${px2vmin(3)} solid transparent`,
- borderTopColor: 'rgba(255, 255, 255, 0.5)',
- borderRadius: '50%',
- animation: 'spin 2s linear infinite',
- }}
- />
- </div>
- {/* 加载文本 */}
- <p
- style={{
- color: '#fff',
- fontSize: px2vmin(16),
- fontWeight: 500,
- textShadow: `0 ${px2vmin(2)} ${px2vmin(4)} rgba(0, 0, 0, 0.3)`,
- animation: 'pulse 1.5s ease-in-out infinite',
- }}
- >
- 视频加载中...
- </p>
- </div>
- </div>
- )}
-
- {/* 视频播放器容器 */}
- <div
- style={{
- position: 'fixed',
- top: 0,
- left: 0,
- width: '100%',
- height: '100%',
- zIndex: 10,
- transition: 'opacity 0.3s ease-in',
- opacity: viewState === 'playing' ? 1 : 0,
- visibility: viewState === 'playing' ? 'visible' : 'hidden',
- pointerEvents: viewState === 'playing' ? 'auto' : 'none',
- }}
- >
- <video
- ref={videoRef}
- className="video-js"
- playsInline
- webkit-playsinline="true"
- x5-playsinline="true"
- x5-video-player-type="h5"
- x5-video-player-fullscreen="false"
- data-setup="{}"
- controlsList="nodownload nofullscreen noremoteplayback"
- disablePictureInPicture
- preload="auto"
- style={{
- position: 'absolute',
- top: 0,
- left: 0,
- width: '100%',
- height: '100%',
- paddingTop: 0,
- background: 'transparent',
- zIndex: 1,
- }}
- >
- <source
- src={import.meta.env.VITE_WELCOME_VIDEO_URL || getOssUrl('Lyrria-video.mp4')}
- type="video/mp4"
- />
- <p className="vjs-no-js">
- 您的浏览器不支持HTML5视频,请升级浏览器。
- </p>
- </video>
- </div>
- </div>
- </>
- );
- };
|