fix(player): dedupe skip-segment fetches and harden segment hook

This commit is contained in:
paregi12 2026-02-09 19:22:58 +05:30
parent 275a75b61d
commit 3a8ec97a7b
4 changed files with 40 additions and 9 deletions

View file

@ -223,7 +223,7 @@ const AndroidVideoPlayer: React.FC = () => {
const nextEpisodeHook = useNextEpisode(type, season, episode, groupedEpisodes, (metadataResult as any)?.groupedEpisodes, episodeId);
const { outroSegment } = useSkipSegments({
const { segments: skipIntervals, outroSegment } = useSkipSegments({
imdbId: imdbId || (id?.startsWith('tt') ? id : undefined),
type,
season,
@ -986,6 +986,7 @@ const AndroidVideoPlayer: React.FC = () => {
episode={episode}
malId={(metadata as any)?.mal_id || (metadata as any)?.external_ids?.mal_id}
kitsuId={id?.startsWith('kitsu:') ? id.split(':')[1] : undefined}
skipIntervals={skipIntervals}
currentTime={playerState.currentTime}
onSkip={(endTime) => controlsHook.seekToTime(endTime)}
controlsVisible={playerState.showControls}

View file

@ -210,7 +210,7 @@ const KSPlayerCore: React.FC = () => {
episodeId
});
const { outroSegment } = useSkipSegments({
const { segments: skipIntervals, outroSegment } = useSkipSegments({
imdbId: imdbId || (id?.startsWith('tt') ? id : undefined),
type,
season,
@ -956,6 +956,7 @@ const KSPlayerCore: React.FC = () => {
episode={episode}
malId={(metadata as any)?.mal_id || (metadata as any)?.external_ids?.mal_id}
kitsuId={id?.startsWith('kitsu:') ? id.split(':')[1] : undefined}
skipIntervals={skipIntervals}
currentTime={currentTime}
onSkip={(endTime) => controls.seekToTime(endTime)}
controlsVisible={showControls}
@ -1114,4 +1115,4 @@ const KSPlayerCore: React.FC = () => {
);
};
export default KSPlayerCore;
export default KSPlayerCore;

View file

@ -28,10 +28,12 @@ export const useSkipSegments = ({
useEffect(() => {
const key = `${imdbId}-${season}-${episode}-${malId}-${kitsuId}`;
if (!enabled || type !== 'series' || (!imdbId && !malId && !kitsuId) || !season || !episode) {
setSegments([]);
setIsLoading(false);
fetchedRef.current = false;
lastKeyRef.current = '';
return;
}
@ -39,23 +41,41 @@ export const useSkipSegments = ({
return;
}
// Clear stale intervals while resolving a new episode/key.
if (lastKeyRef.current !== key) {
setSegments([]);
fetchedRef.current = false;
}
lastKeyRef.current = key;
fetchedRef.current = true;
setIsLoading(true);
let cancelled = false;
const fetchSegments = async () => {
try {
const intervals = await introService.getSkipTimes(imdbId, season, episode, malId, kitsuId);
// Ignore stale responses from old requests.
if (cancelled || lastKeyRef.current !== key) return;
setSegments(intervals);
fetchedRef.current = true;
} catch (error) {
if (cancelled || lastKeyRef.current !== key) return;
logger.error('[useSkipSegments] Error fetching skip data:', error);
setSegments([]);
// Keep this key retryable on transient failures.
fetchedRef.current = false;
} finally {
if (cancelled || lastKeyRef.current !== key) return;
setIsLoading(false);
}
};
fetchSegments();
return () => {
cancelled = true;
};
}, [imdbId, type, season, episode, malId, kitsuId, enabled]);
const getActiveSegment = (currentTime: number) => {
@ -64,7 +84,12 @@ export const useSkipSegments = ({
);
};
const outroSegment = segments.find(s => ['ed', 'outro', 'mixed-ed'].includes(s.type));
const outroSegment = segments
.filter(s => ['ed', 'outro', 'mixed-ed'].includes(s.type))
.reduce<SkipInterval | null>((latest, interval) => {
if (!latest || interval.endTime > latest.endTime) return interval;
return latest;
}, null);
return {
segments,

View file

@ -23,6 +23,7 @@ interface SkipIntroButtonProps {
episode?: number;
malId?: string;
kitsuId?: string;
skipIntervals?: SkipInterval[] | null;
currentTime: number;
onSkip: (endTime: number) => void;
controlsVisible?: boolean;
@ -36,6 +37,7 @@ export const SkipIntroButton: React.FC<SkipIntroButtonProps> = ({
episode,
malId,
kitsuId,
skipIntervals: externalSkipIntervals,
currentTime,
onSkip,
controlsVisible = false,
@ -47,15 +49,17 @@ export const SkipIntroButton: React.FC<SkipIntroButtonProps> = ({
const skipIntroEnabled = settings.skipIntroEnabled;
const { segments: skipIntervals } = useSkipSegments({
const { segments: fetchedSkipIntervals } = useSkipSegments({
imdbId,
type,
season,
episode,
malId,
kitsuId,
enabled: skipIntroEnabled
// Allow parent components to provide pre-fetched intervals to avoid duplicate requests.
enabled: skipIntroEnabled && !externalSkipIntervals
});
const skipIntervals = externalSkipIntervals ?? fetchedSkipIntervals;
// State
const [currentInterval, setCurrentInterval] = useState<SkipInterval | null>(null);
@ -293,4 +297,4 @@ const styles = StyleSheet.create({
},
});
export default SkipIntroButton;
export default SkipIntroButton;