mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-04-24 10:03:15 +00:00
fix: preserve Emby session reporting across stream switches
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
parent
8cf8036f0b
commit
73819585eb
3 changed files with 26 additions and 35 deletions
|
|
@ -712,7 +712,8 @@ const AndroidVideoPlayer: React.FC = () => {
|
||||||
streamProvider: newProvider,
|
streamProvider: newProvider,
|
||||||
streamName: newStreamName,
|
streamName: newStreamName,
|
||||||
headers: newStream.headers,
|
headers: newStream.headers,
|
||||||
availableStreams: availableStreams
|
availableStreams: availableStreams,
|
||||||
|
embyItemId: newStream.embyItemId ?? undefined,
|
||||||
});
|
});
|
||||||
}, 300);
|
}, 300);
|
||||||
};
|
};
|
||||||
|
|
@ -751,6 +752,7 @@ const AndroidVideoPlayer: React.FC = () => {
|
||||||
backdrop: backdrop || undefined,
|
backdrop: backdrop || undefined,
|
||||||
availableStreams: {},
|
availableStreams: {},
|
||||||
groupedEpisodes: groupedEpisodes,
|
groupedEpisodes: groupedEpisodes,
|
||||||
|
embyItemId: stream.embyItemId ?? undefined,
|
||||||
});
|
});
|
||||||
}, 300);
|
}, 300);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -714,7 +714,8 @@ const KSPlayerCore: React.FC = () => {
|
||||||
streamProvider: newProvider,
|
streamProvider: newProvider,
|
||||||
streamName: newStreamName,
|
streamName: newStreamName,
|
||||||
headers: newStream.headers,
|
headers: newStream.headers,
|
||||||
availableStreams: availableStreams
|
availableStreams: availableStreams,
|
||||||
|
embyItemId: newStream.embyItemId ?? undefined,
|
||||||
});
|
});
|
||||||
}, 100);
|
}, 100);
|
||||||
};
|
};
|
||||||
|
|
@ -767,6 +768,7 @@ const KSPlayerCore: React.FC = () => {
|
||||||
episodeId: ep.stremioId || `${id}:${ep.season_number}:${ep.episode_number} `,
|
episodeId: ep.stremioId || `${id}:${ep.season_number}:${ep.episode_number} `,
|
||||||
imdbId: imdbId ?? undefined,
|
imdbId: imdbId ?? undefined,
|
||||||
backdrop: backdrop || undefined,
|
backdrop: backdrop || undefined,
|
||||||
|
embyItemId: stream.embyItemId ?? undefined,
|
||||||
});
|
});
|
||||||
}, 100);
|
}, 100);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -20,28 +20,40 @@ export const useEmbySession = (
|
||||||
currentTime: number,
|
currentTime: number,
|
||||||
paused: boolean
|
paused: boolean
|
||||||
) => {
|
) => {
|
||||||
const hasStartedRef = useRef(false);
|
|
||||||
const progressTimerRef = useRef<ReturnType<typeof setInterval> | null>(null);
|
const progressTimerRef = useRef<ReturnType<typeof setInterval> | null>(null);
|
||||||
|
|
||||||
// Keep a ref for the latest values to avoid stale closures in the interval
|
// Keep a ref for the latest values to avoid stale closures in the interval
|
||||||
const currentTimeRef = useRef(currentTime);
|
const currentTimeRef = useRef(currentTime);
|
||||||
const pausedRef = useRef(paused);
|
const pausedRef = useRef(paused);
|
||||||
const embyItemIdRef = useRef(embyItemId);
|
|
||||||
|
|
||||||
useEffect(() => { currentTimeRef.current = currentTime; }, [currentTime]);
|
useEffect(() => { currentTimeRef.current = currentTime; }, [currentTime]);
|
||||||
useEffect(() => { pausedRef.current = paused; }, [paused]);
|
useEffect(() => { pausedRef.current = paused; }, [paused]);
|
||||||
useEffect(() => { embyItemIdRef.current = embyItemId; }, [embyItemId]);
|
|
||||||
|
|
||||||
// Report playback start once when embyItemId becomes available
|
// Session lifecycle: start when embyItemId becomes available, stop when it
|
||||||
|
// changes or the component unmounts. The cleanup closure captures the
|
||||||
|
// embyItemId that was active when the effect ran, so the correct session is
|
||||||
|
// always stopped — even if the ref/prop has already moved to a new value.
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!embyItemId) return;
|
if (!embyItemId) return;
|
||||||
if (hasStartedRef.current) return;
|
|
||||||
|
|
||||||
hasStartedRef.current = true;
|
|
||||||
embyService.reportPlaybackStart(embyItemId, currentTimeRef.current).catch((err) => {
|
embyService.reportPlaybackStart(embyItemId, currentTimeRef.current).catch((err) => {
|
||||||
logger.warn('[useEmbySession] reportPlaybackStart error:', err);
|
logger.warn('[useEmbySession] reportPlaybackStart error:', err);
|
||||||
});
|
});
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
|
return () => {
|
||||||
|
if (progressTimerRef.current) {
|
||||||
|
clearInterval(progressTimerRef.current);
|
||||||
|
progressTimerRef.current = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deferred so navigation can complete first
|
||||||
|
const positionAtStop = currentTimeRef.current;
|
||||||
|
setTimeout(() => {
|
||||||
|
embyService
|
||||||
|
.reportPlaybackStopped(embyItemId, positionAtStop)
|
||||||
|
.catch((err) => logger.warn('[useEmbySession] reportPlaybackStopped error:', err));
|
||||||
|
}, 0);
|
||||||
|
};
|
||||||
}, [embyItemId]);
|
}, [embyItemId]);
|
||||||
|
|
||||||
// Send progress every PROGRESS_INTERVAL_MS while playing; pause/resume sends an immediate report
|
// Send progress every PROGRESS_INTERVAL_MS while playing; pause/resume sends an immediate report
|
||||||
|
|
@ -59,10 +71,8 @@ export const useEmbySession = (
|
||||||
|
|
||||||
if (!paused) {
|
if (!paused) {
|
||||||
progressTimerRef.current = setInterval(() => {
|
progressTimerRef.current = setInterval(() => {
|
||||||
const itemId = embyItemIdRef.current;
|
|
||||||
if (!itemId) return;
|
|
||||||
embyService
|
embyService
|
||||||
.reportPlaybackProgress(itemId, currentTimeRef.current, pausedRef.current)
|
.reportPlaybackProgress(embyItemId, currentTimeRef.current, pausedRef.current)
|
||||||
.catch(() => {});
|
.catch(() => {});
|
||||||
}, PROGRESS_INTERVAL_MS);
|
}, PROGRESS_INTERVAL_MS);
|
||||||
}
|
}
|
||||||
|
|
@ -73,28 +83,5 @@ export const useEmbySession = (
|
||||||
progressTimerRef.current = null;
|
progressTimerRef.current = null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
// Re-run when paused state changes; embyItemId already guarded above
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, [embyItemId, paused]);
|
}, [embyItemId, paused]);
|
||||||
|
|
||||||
// Report stopped on component unmount (deferred so navigation can complete first)
|
|
||||||
useEffect(() => {
|
|
||||||
return () => {
|
|
||||||
const itemId = embyItemIdRef.current;
|
|
||||||
if (!itemId) return;
|
|
||||||
|
|
||||||
if (progressTimerRef.current) {
|
|
||||||
clearInterval(progressTimerRef.current);
|
|
||||||
progressTimerRef.current = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
embyService
|
|
||||||
.reportPlaybackStopped(itemId, currentTimeRef.current)
|
|
||||||
.catch((err) => logger.warn('[useEmbySession] reportPlaybackStopped error:', err));
|
|
||||||
}, 0);
|
|
||||||
};
|
|
||||||
// Only run cleanup on unmount
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, []);
|
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue