Fix Safari playback follow-ups and same-episode reload stall

This commit is contained in:
ShiGuangAlex
2026-04-15 19:35:24 +08:00
parent 80fd408659
commit c3744213ae
2 changed files with 31 additions and 35 deletions
+22 -32
View File
@@ -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(
+9 -3
View File
@@ -188,7 +188,7 @@ const EpisodeSelector: React.FC<EpisodeSelectorProps> = ({
);
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<EpisodeSelectorProps> = ({
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<EpisodeSelectorProps> = ({
return (
<button
key={episodeNumber}
disabled={isActive}
onClick={() => handleEpisodeClick(episodeNumber - 1)}
className={`relative h-10 min-w-10 px-3 py-2 flex items-center justify-center text-sm font-medium rounded-md transition-all duration-200 whitespace-nowrap font-mono border
${isActive
@@ -801,8 +806,9 @@ const EpisodeSelector: React.FC<EpisodeSelectorProps> = ({
: isWatched
? 'bg-emerald-50 text-emerald-700 border-emerald-200 hover:bg-emerald-100 hover:scale-105 dark:bg-emerald-900/20 dark:text-emerald-300 dark:border-emerald-700/60 dark:hover:bg-emerald-900/30'
: 'bg-gray-200 text-gray-700 border-transparent hover:bg-gray-300 hover:scale-105 dark:bg-gray-700 dark:text-gray-300 dark:hover:bg-gray-600'
}`.trim()}
} ${isActive ? 'cursor-default' : ''}`.trim()}
title={isWatched && !isActive ? '已观看过' : undefined}
aria-current={isActive ? 'true' : undefined}
>
{isWatched && !isActive && (
<span className='absolute top-1 right-1 h-1.5 w-1.5 rounded-full bg-emerald-500 dark:bg-emerald-400' />