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;
|
onTimeUpdate?: (time: number) => void;
|
||||||
onPlaying?: () => void;
|
onPlaying?: () => void;
|
||||||
onSeekToTime?: (timestamp: number, play?: boolean) => void;
|
onSeekToTime?: (timestamp: number, play?: boolean) => void;
|
||||||
|
onGetAbsoluteTimestamp?: (playerSeconds: number) => number | undefined;
|
||||||
setFullResolution?: React.Dispatch<React.SetStateAction<VideoResolutionType>>;
|
setFullResolution?: React.Dispatch<React.SetStateAction<VideoResolutionType>>;
|
||||||
onUploadFrame?: (playTime: number) => Promise<AxiosResponse> | undefined;
|
onUploadFrame?: (playTime: number) => Promise<AxiosResponse> | undefined;
|
||||||
toggleFullscreen?: () => void;
|
toggleFullscreen?: () => void;
|
||||||
@ -76,6 +77,7 @@ export default function HlsVideoPlayer({
|
|||||||
onTimeUpdate,
|
onTimeUpdate,
|
||||||
onPlaying,
|
onPlaying,
|
||||||
onSeekToTime,
|
onSeekToTime,
|
||||||
|
onGetAbsoluteTimestamp,
|
||||||
setFullResolution,
|
setFullResolution,
|
||||||
onUploadFrame,
|
onUploadFrame,
|
||||||
toggleFullscreen,
|
toggleFullscreen,
|
||||||
@ -282,7 +284,7 @@ export default function HlsVideoPlayer({
|
|||||||
const getVideoTime = useCallback(() => {
|
const getVideoTime = useCallback(() => {
|
||||||
const currentTime = videoRef.current?.currentTime;
|
const currentTime = videoRef.current?.currentTime;
|
||||||
|
|
||||||
if (!currentTime) {
|
if (currentTime == undefined) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -323,7 +325,7 @@ export default function HlsVideoPlayer({
|
|||||||
onSeek={(diff) => {
|
onSeek={(diff) => {
|
||||||
const currentTime = videoRef.current?.currentTime;
|
const currentTime = videoRef.current?.currentTime;
|
||||||
|
|
||||||
if (!videoRef.current || !currentTime) {
|
if (!videoRef.current || currentTime == undefined) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -339,7 +341,7 @@ export default function HlsVideoPlayer({
|
|||||||
onUploadFrame={async () => {
|
onUploadFrame={async () => {
|
||||||
const frameTime = getVideoTime();
|
const frameTime = getVideoTime();
|
||||||
|
|
||||||
if (frameTime && onUploadFrame) {
|
if (frameTime != undefined && onUploadFrame) {
|
||||||
const resp = await onUploadFrame(frameTime);
|
const resp = await onUploadFrame(frameTime);
|
||||||
|
|
||||||
if (resp && resp.status == 200) {
|
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}
|
fullscreen={fullscreen}
|
||||||
toggleFullscreen={toggleFullscreen}
|
toggleFullscreen={toggleFullscreen}
|
||||||
containerRef={containerRef}
|
containerRef={containerRef}
|
||||||
@ -484,7 +515,7 @@ export default function HlsVideoPlayer({
|
|||||||
|
|
||||||
const frameTime = getVideoTime();
|
const frameTime = getVideoTime();
|
||||||
|
|
||||||
if (frameTime) {
|
if (frameTime != undefined) {
|
||||||
onTimeUpdate(frameTime);
|
onTimeUpdate(frameTime);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
|||||||
@ -406,6 +406,9 @@ export default function DynamicVideoPlayer({
|
|||||||
onSeekToTime(timestamp, play);
|
onSeekToTime(timestamp, play);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
onGetAbsoluteTimestamp={(playerSeconds) =>
|
||||||
|
controller ? controller.getProgress(playerSeconds) : undefined
|
||||||
|
}
|
||||||
onPlaying={() => {
|
onPlaying={() => {
|
||||||
if (isScrubbing) {
|
if (isScrubbing) {
|
||||||
playerRef.current?.pause();
|
playerRef.current?.pause();
|
||||||
|
|||||||
@ -257,6 +257,8 @@ export function RecordingView({
|
|||||||
const [scrubbing, setScrubbing] = useState(false);
|
const [scrubbing, setScrubbing] = useState(false);
|
||||||
const [currentTime, setCurrentTime] = useState<number>(startTime);
|
const [currentTime, setCurrentTime] = useState<number>(startTime);
|
||||||
const [playerTime, setPlayerTime] = useState(startTime);
|
const [playerTime, setPlayerTime] = useState(startTime);
|
||||||
|
const wasScrubbingRef = useRef(false);
|
||||||
|
const pendingScrubSeekTimeRef = useRef<number | null>(null);
|
||||||
|
|
||||||
const updateSelectedSegment = useCallback(
|
const updateSelectedSegment = useCallback(
|
||||||
(currentTime: number, updateStartTime: boolean) => {
|
(currentTime: number, updateStartTime: boolean) => {
|
||||||
@ -347,6 +349,17 @@ export function RecordingView({
|
|||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [currentTime, scrubbing]);
|
}, [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>({
|
const [fullResolution, setFullResolution] = useState<VideoResolutionType>({
|
||||||
width: 0,
|
width: 0,
|
||||||
height: 0,
|
height: 0,
|
||||||
@ -820,7 +833,19 @@ export function RecordingView({
|
|||||||
fullscreen={fullscreen}
|
fullscreen={fullscreen}
|
||||||
onTimestampUpdate={(timestamp) => {
|
onTimestampUpdate={(timestamp) => {
|
||||||
setPlayerTime(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);
|
setCurrentTime(timestamp);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setCurrentTime(timestamp);
|
||||||
|
}
|
||||||
|
|
||||||
Object.values(previewRefs.current ?? {}).forEach((prev) =>
|
Object.values(previewRefs.current ?? {}).forEach((prev) =>
|
||||||
prev.scrubToTimestamp(Math.floor(timestamp)),
|
prev.scrubToTimestamp(Math.floor(timestamp)),
|
||||||
);
|
);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user