mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-04-05 22:57:40 +03:00
History: fix snapshot timestamp and scrub-release seek behavior
(cherry picked from commit 7b6e26fdbbb878ec7443b29a335bff1990d0b9f5)
This commit is contained in:
parent
b668e802d9
commit
24b27dfa4a
@ -51,6 +51,7 @@ type HlsVideoPlayerProps = {
|
||||
onTimeUpdate?: (time: number) => void;
|
||||
onPlaying?: () => void;
|
||||
onSeekToTime?: (timestamp: number, play?: boolean) => void;
|
||||
onGetAbsoluteTimestamp?: (playerSeconds: number) => number | undefined;
|
||||
setFullResolution?: React.Dispatch<React.SetStateAction<VideoResolutionType>>;
|
||||
onUploadFrame?: (playTime: number) => Promise<AxiosResponse> | undefined;
|
||||
toggleFullscreen?: () => void;
|
||||
@ -76,6 +77,7 @@ export default function HlsVideoPlayer({
|
||||
onTimeUpdate,
|
||||
onPlaying,
|
||||
onSeekToTime,
|
||||
onGetAbsoluteTimestamp,
|
||||
setFullResolution,
|
||||
onUploadFrame,
|
||||
toggleFullscreen,
|
||||
@ -282,7 +284,7 @@ export default function HlsVideoPlayer({
|
||||
const getVideoTime = useCallback(() => {
|
||||
const currentTime = videoRef.current?.currentTime;
|
||||
|
||||
if (!currentTime) {
|
||||
if (currentTime == undefined) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@ -323,7 +325,7 @@ export default function HlsVideoPlayer({
|
||||
onSeek={(diff) => {
|
||||
const currentTime = videoRef.current?.currentTime;
|
||||
|
||||
if (!videoRef.current || !currentTime) {
|
||||
if (!videoRef.current || currentTime == undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -339,7 +341,7 @@ export default function HlsVideoPlayer({
|
||||
onUploadFrame={async () => {
|
||||
const frameTime = getVideoTime();
|
||||
|
||||
if (frameTime && onUploadFrame) {
|
||||
if (frameTime != undefined && onUploadFrame) {
|
||||
const resp = await onUploadFrame(frameTime);
|
||||
|
||||
if (resp && resp.status == 200) {
|
||||
@ -353,6 +355,35 @@ export default function HlsVideoPlayer({
|
||||
}
|
||||
}
|
||||
}}
|
||||
onSnapshot={async () => {
|
||||
const frameTime = getVideoTime();
|
||||
const snapshotTimestamp =
|
||||
frameTime != undefined
|
||||
? (onGetAbsoluteTimestamp?.(frameTime) ?? frameTime)
|
||||
: undefined;
|
||||
const result = await grabVideoSnapshot(videoRef.current);
|
||||
|
||||
if (result.success) {
|
||||
downloadSnapshot(
|
||||
result.data.dataUrl,
|
||||
generateSnapshotFilename(
|
||||
camera ?? "recording",
|
||||
snapshotTimestamp,
|
||||
),
|
||||
);
|
||||
toast.success(
|
||||
t("snapshot.downloadStarted", { ns: "views/live" }),
|
||||
{
|
||||
position: "top-center",
|
||||
},
|
||||
);
|
||||
} else {
|
||||
toast.error(t("snapshot.captureFailed", { ns: "views/live" }), {
|
||||
position: "top-center",
|
||||
});
|
||||
}
|
||||
}}
|
||||
snapshotTitle={t("snapshot.takeSnapshot", { ns: "views/live" })}
|
||||
fullscreen={fullscreen}
|
||||
toggleFullscreen={toggleFullscreen}
|
||||
containerRef={containerRef}
|
||||
@ -484,7 +515,7 @@ export default function HlsVideoPlayer({
|
||||
|
||||
const frameTime = getVideoTime();
|
||||
|
||||
if (frameTime) {
|
||||
if (frameTime != undefined) {
|
||||
onTimeUpdate(frameTime);
|
||||
}
|
||||
}}
|
||||
|
||||
@ -406,6 +406,9 @@ export default function DynamicVideoPlayer({
|
||||
onSeekToTime(timestamp, play);
|
||||
}
|
||||
}}
|
||||
onGetAbsoluteTimestamp={(playerSeconds) =>
|
||||
controller ? controller.getProgress(playerSeconds) : undefined
|
||||
}
|
||||
onPlaying={() => {
|
||||
if (isScrubbing) {
|
||||
playerRef.current?.pause();
|
||||
|
||||
@ -257,6 +257,8 @@ export function RecordingView({
|
||||
const [scrubbing, setScrubbing] = useState(false);
|
||||
const [currentTime, setCurrentTime] = useState<number>(startTime);
|
||||
const [playerTime, setPlayerTime] = useState(startTime);
|
||||
const wasScrubbingRef = useRef(false);
|
||||
const pendingScrubSeekTimeRef = useRef<number | null>(null);
|
||||
|
||||
const updateSelectedSegment = useCallback(
|
||||
(currentTime: number, updateStartTime: boolean) => {
|
||||
@ -347,6 +349,17 @@ export function RecordingView({
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [currentTime, scrubbing]);
|
||||
|
||||
useEffect(() => {
|
||||
// force an explicit seek when the user releases timeline scrubbing,
|
||||
// and guard against old player time updates overwriting the new target.
|
||||
if (wasScrubbingRef.current && !scrubbing) {
|
||||
pendingScrubSeekTimeRef.current = currentTime;
|
||||
mainControllerRef.current?.seekToTimestamp(currentTime, true);
|
||||
}
|
||||
|
||||
wasScrubbingRef.current = scrubbing;
|
||||
}, [scrubbing, currentTime]);
|
||||
|
||||
const [fullResolution, setFullResolution] = useState<VideoResolutionType>({
|
||||
width: 0,
|
||||
height: 0,
|
||||
@ -820,7 +833,19 @@ export function RecordingView({
|
||||
fullscreen={fullscreen}
|
||||
onTimestampUpdate={(timestamp) => {
|
||||
setPlayerTime(timestamp);
|
||||
|
||||
const pendingScrubSeekTime =
|
||||
pendingScrubSeekTimeRef.current;
|
||||
if (pendingScrubSeekTime != null) {
|
||||
// keep the scrubbed target time anchored until playback catches up.
|
||||
if (Math.abs(timestamp - pendingScrubSeekTime) <= 1) {
|
||||
pendingScrubSeekTimeRef.current = null;
|
||||
setCurrentTime(timestamp);
|
||||
}
|
||||
} else {
|
||||
setCurrentTime(timestamp);
|
||||
}
|
||||
|
||||
Object.values(previewRefs.current ?? {}).forEach((prev) =>
|
||||
prev.scrubToTimestamp(Math.floor(timestamp)),
|
||||
);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user