From c3744213ae32d8644b24ce307bbf7536729b51cd Mon Sep 17 00:00:00 2001 From: ShiGuangAlex Date: Wed, 15 Apr 2026 19:35:24 +0800 Subject: [PATCH] Fix Safari playback follow-ups and same-episode reload stall --- src/app/play/page.tsx | 54 ++++++++++++------------------ src/components/EpisodeSelector.tsx | 12 +++++-- 2 files changed, 31 insertions(+), 35 deletions(-) diff --git a/src/app/play/page.tsx b/src/app/play/page.tsx index 313608f..4f59dd8 100644 --- a/src/app/play/page.tsx +++ b/src/app/play/page.tsx @@ -4421,11 +4421,17 @@ function PlayPageClient() { // 处理集数切换 const handleEpisodeChange = async (episodeNumber: number) => { - if (episodeNumber >= 0 && episodeNumber < totalEpisodes) { - await prepareEpisodeSwitch(); - await primeEpisodeResumeState(episodeNumber); - setCurrentEpisodeIndex(episodeNumber); + if (episodeNumber < 0 || episodeNumber >= totalEpisodes) { + return; } + + if (episodeNumber === currentEpisodeIndexRef.current) { + return; + } + + await prepareEpisodeSwitch(); + await primeEpisodeResumeState(episodeNumber); + setCurrentEpisodeIndex(episodeNumber); }; const handlePreviousEpisode = async () => { @@ -6052,27 +6058,6 @@ function PlayPageClient() { hls.on(Hls.Events.MEDIA_ATTACHED, () => { kickStartHlsPlayback(); - - if (!isWebkit) { - return; - } - - // Safari 偶发出现 media 已 attach 但 video.src 仍为空的状态。 - // 这里延迟自检一次,必要时重新 attach,强制回到 blob: MSE 路径。 - schedulePlayerTimeout(() => { - if (!shouldRescueWebkitHls(video)) { - return; - } - - console.warn('[HLS] Safari attach watchdog triggered, reattaching media'); - try { - hls.detachMedia(); - hls.attachMedia(video); - kickStartHlsPlayback(); - } catch (error) { - console.warn('[HLS] Safari reattach failed:', error); - } - }, 1200); }); hls.loadSource(url); @@ -6085,15 +6070,15 @@ function PlayPageClient() { return; } - console.warn('[HLS] Safari post-attach watchdog triggered, forcing reattach'); + console.warn('[HLS] Safari attach watchdog triggered, forcing reattach'); try { hls.detachMedia(); hls.attachMedia(video); kickStartHlsPlayback(); } catch (error) { - console.warn('[HLS] Safari post-attach reattach failed:', error); + console.warn('[HLS] Safari attach reattach failed:', error); } - }, 1200); + }, 3000); } ensureVideoSource(video, url); @@ -6108,10 +6093,15 @@ function PlayPageClient() { hls.on(Hls.Events.MANIFEST_PARSED, () => { console.log('[HLS] Manifest解析完成'); - if (video.paused && (artPlayerRef.current?.option.autoplay || artPlayerRef.current?.loading)) { - video.play().catch((error) => { + const player = artPlayerRef.current; + if (video.paused && (player?.option.autoplay || player?.loading)) { + try { + Promise.resolve(player?.play?.()).catch((error) => { + console.warn('[HLS] play after manifest parsed failed:', error); + }); + } catch (error) { console.warn('[HLS] play after manifest parsed failed:', error); - }); + } } // 只在首次加载时启动定时器(后续刷新会在refreshXiaoyaUrl中启动) @@ -7303,7 +7293,7 @@ function PlayPageClient() { if (shouldIgnoreSafariReset) { // Safari 切集后可能偷偷回到 1x,这不是用户真实选择,不要覆盖记忆值。 - setTimeout(() => { + schedulePlayerTimeout(() => { if ( artPlayerRef.current && Math.abs( diff --git a/src/components/EpisodeSelector.tsx b/src/components/EpisodeSelector.tsx index 37fbd85..4f5c746 100644 --- a/src/components/EpisodeSelector.tsx +++ b/src/components/EpisodeSelector.tsx @@ -188,7 +188,7 @@ const EpisodeSelector: React.FC = ({ ); window.removeEventListener('storage', handlePlayRecordsUpdated); }; - }, [currentSource, currentId, episodeProgressContentKey, totalEpisodes, value]); + }, [currentSource, currentId, episodeProgressContentKey, totalEpisodes]); // 主要的 tab 状态:'danmaku' | 'episodes' | 'sources' // 默认显示选集选项卡,但如果是房员则显示弹幕 @@ -576,9 +576,13 @@ const EpisodeSelector: React.FC = ({ const handleEpisodeClick = useCallback( (episodeNumber: number) => { + if (episodeNumber + 1 === value) { + return; + } + onChange?.(episodeNumber); }, - [onChange] + [onChange, value] ); const handleSourceClick = useCallback( @@ -794,6 +798,7 @@ const EpisodeSelector: React.FC = ({ return (