Fix Safari playback follow-ups and same-episode reload stall
This commit is contained in:
+22
-32
@@ -4421,11 +4421,17 @@ function PlayPageClient() {
|
|||||||
|
|
||||||
// 处理集数切换
|
// 处理集数切换
|
||||||
const handleEpisodeChange = async (episodeNumber: number) => {
|
const handleEpisodeChange = async (episodeNumber: number) => {
|
||||||
if (episodeNumber >= 0 && episodeNumber < totalEpisodes) {
|
if (episodeNumber < 0 || episodeNumber >= totalEpisodes) {
|
||||||
await prepareEpisodeSwitch();
|
return;
|
||||||
await primeEpisodeResumeState(episodeNumber);
|
|
||||||
setCurrentEpisodeIndex(episodeNumber);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (episodeNumber === currentEpisodeIndexRef.current) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await prepareEpisodeSwitch();
|
||||||
|
await primeEpisodeResumeState(episodeNumber);
|
||||||
|
setCurrentEpisodeIndex(episodeNumber);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handlePreviousEpisode = async () => {
|
const handlePreviousEpisode = async () => {
|
||||||
@@ -6052,27 +6058,6 @@ function PlayPageClient() {
|
|||||||
|
|
||||||
hls.on(Hls.Events.MEDIA_ATTACHED, () => {
|
hls.on(Hls.Events.MEDIA_ATTACHED, () => {
|
||||||
kickStartHlsPlayback();
|
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);
|
hls.loadSource(url);
|
||||||
@@ -6085,15 +6070,15 @@ function PlayPageClient() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.warn('[HLS] Safari post-attach watchdog triggered, forcing reattach');
|
console.warn('[HLS] Safari attach watchdog triggered, forcing reattach');
|
||||||
try {
|
try {
|
||||||
hls.detachMedia();
|
hls.detachMedia();
|
||||||
hls.attachMedia(video);
|
hls.attachMedia(video);
|
||||||
kickStartHlsPlayback();
|
kickStartHlsPlayback();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn('[HLS] Safari post-attach reattach failed:', error);
|
console.warn('[HLS] Safari attach reattach failed:', error);
|
||||||
}
|
}
|
||||||
}, 1200);
|
}, 3000);
|
||||||
}
|
}
|
||||||
|
|
||||||
ensureVideoSource(video, url);
|
ensureVideoSource(video, url);
|
||||||
@@ -6108,10 +6093,15 @@ function PlayPageClient() {
|
|||||||
hls.on(Hls.Events.MANIFEST_PARSED, () => {
|
hls.on(Hls.Events.MANIFEST_PARSED, () => {
|
||||||
console.log('[HLS] Manifest解析完成');
|
console.log('[HLS] Manifest解析完成');
|
||||||
|
|
||||||
if (video.paused && (artPlayerRef.current?.option.autoplay || artPlayerRef.current?.loading)) {
|
const player = artPlayerRef.current;
|
||||||
video.play().catch((error) => {
|
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);
|
console.warn('[HLS] play after manifest parsed failed:', error);
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 只在首次加载时启动定时器(后续刷新会在refreshXiaoyaUrl中启动)
|
// 只在首次加载时启动定时器(后续刷新会在refreshXiaoyaUrl中启动)
|
||||||
@@ -7303,7 +7293,7 @@ function PlayPageClient() {
|
|||||||
|
|
||||||
if (shouldIgnoreSafariReset) {
|
if (shouldIgnoreSafariReset) {
|
||||||
// Safari 切集后可能偷偷回到 1x,这不是用户真实选择,不要覆盖记忆值。
|
// Safari 切集后可能偷偷回到 1x,这不是用户真实选择,不要覆盖记忆值。
|
||||||
setTimeout(() => {
|
schedulePlayerTimeout(() => {
|
||||||
if (
|
if (
|
||||||
artPlayerRef.current &&
|
artPlayerRef.current &&
|
||||||
Math.abs(
|
Math.abs(
|
||||||
|
|||||||
@@ -188,7 +188,7 @@ const EpisodeSelector: React.FC<EpisodeSelectorProps> = ({
|
|||||||
);
|
);
|
||||||
window.removeEventListener('storage', handlePlayRecordsUpdated);
|
window.removeEventListener('storage', handlePlayRecordsUpdated);
|
||||||
};
|
};
|
||||||
}, [currentSource, currentId, episodeProgressContentKey, totalEpisodes, value]);
|
}, [currentSource, currentId, episodeProgressContentKey, totalEpisodes]);
|
||||||
|
|
||||||
// 主要的 tab 状态:'danmaku' | 'episodes' | 'sources'
|
// 主要的 tab 状态:'danmaku' | 'episodes' | 'sources'
|
||||||
// 默认显示选集选项卡,但如果是房员则显示弹幕
|
// 默认显示选集选项卡,但如果是房员则显示弹幕
|
||||||
@@ -576,9 +576,13 @@ const EpisodeSelector: React.FC<EpisodeSelectorProps> = ({
|
|||||||
|
|
||||||
const handleEpisodeClick = useCallback(
|
const handleEpisodeClick = useCallback(
|
||||||
(episodeNumber: number) => {
|
(episodeNumber: number) => {
|
||||||
|
if (episodeNumber + 1 === value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
onChange?.(episodeNumber);
|
onChange?.(episodeNumber);
|
||||||
},
|
},
|
||||||
[onChange]
|
[onChange, value]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleSourceClick = useCallback(
|
const handleSourceClick = useCallback(
|
||||||
@@ -794,6 +798,7 @@ const EpisodeSelector: React.FC<EpisodeSelectorProps> = ({
|
|||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
key={episodeNumber}
|
key={episodeNumber}
|
||||||
|
disabled={isActive}
|
||||||
onClick={() => handleEpisodeClick(episodeNumber - 1)}
|
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
|
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
|
${isActive
|
||||||
@@ -801,8 +806,9 @@ const EpisodeSelector: React.FC<EpisodeSelectorProps> = ({
|
|||||||
: isWatched
|
: 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-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'
|
: '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}
|
title={isWatched && !isActive ? '已观看过' : undefined}
|
||||||
|
aria-current={isActive ? 'true' : undefined}
|
||||||
>
|
>
|
||||||
{isWatched && !isActive && (
|
{isWatched && !isActive && (
|
||||||
<span className='absolute top-1 right-1 h-1.5 w-1.5 rounded-full bg-emerald-500 dark:bg-emerald-400' />
|
<span className='absolute top-1 right-1 h-1.5 w-1.5 rounded-full bg-emerald-500 dark:bg-emerald-400' />
|
||||||
|
|||||||
Reference in New Issue
Block a user