pause on lifecycle item click and point click

This commit is contained in:
Josh Hawkins 2025-10-16 13:10:35 -05:00
parent 9599450cff
commit 4f3507b08e
6 changed files with 34 additions and 23 deletions

View File

@ -19,7 +19,7 @@ type ObjectTrackOverlayProps = {
videoWidth: number; videoWidth: number;
videoHeight: number; videoHeight: number;
className?: string; className?: string;
onSeekToTime?: (timestamp: number) => void; onSeekToTime?: (timestamp: number, play?: boolean) => void;
objectTimeline?: ObjectLifecycleSequence[]; objectTimeline?: ObjectLifecycleSequence[];
}; };
@ -227,7 +227,7 @@ export default function ObjectTrackOverlay({
const handlePointClick = useCallback( const handlePointClick = useCallback(
(timestamp: number) => { (timestamp: number) => {
onSeekToTime?.(timestamp); onSeekToTime?.(timestamp, false);
}, },
[onSeekToTime], [onSeekToTime],
); );

View File

@ -49,7 +49,7 @@ type HlsVideoPlayerProps = {
onPlayerLoaded?: () => void; onPlayerLoaded?: () => void;
onTimeUpdate?: (time: number) => void; onTimeUpdate?: (time: number) => void;
onPlaying?: () => void; onPlaying?: () => void;
onSeekToTime?: (timestamp: number) => void; onSeekToTime?: (timestamp: number, play?: boolean) => void;
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;
@ -328,9 +328,10 @@ export default function HlsVideoPlayer({
videoWidth={videoDimensions.width} videoWidth={videoDimensions.width}
videoHeight={videoDimensions.height} videoHeight={videoDimensions.height}
className="absolute inset-0 z-10" className="absolute inset-0 z-10"
onSeekToTime={(timestamp) => { onSeekToTime={(timestamp, play) => {
if (onSeekToTime) { if (onSeekToTime) {
onSeekToTime(timestamp); console.log("object track overlay seek", timestamp, play);
onSeekToTime(timestamp, play);
} }
}} }}
objectTimeline={selectedObjectTimeline} objectTimeline={selectedObjectTimeline}

View File

@ -111,6 +111,7 @@ export class DynamicVideoController {
if (play) { if (play) {
this.waitAndPlay(); this.waitAndPlay();
} else { } else {
console.log("called pause", Date.now());
this.playerController.pause(); this.playerController.pause();
} }
} else { } else {

View File

@ -32,7 +32,7 @@ type DynamicVideoPlayerProps = {
onControllerReady: (controller: DynamicVideoController) => void; onControllerReady: (controller: DynamicVideoController) => void;
onTimestampUpdate?: (timestamp: number) => void; onTimestampUpdate?: (timestamp: number) => void;
onClipEnded?: () => void; onClipEnded?: () => void;
onSeekToTime?: (timestamp: number) => void; onSeekToTime?: (timestamp: number, play?: boolean) => void;
setFullResolution: React.Dispatch<React.SetStateAction<VideoResolutionType>>; setFullResolution: React.Dispatch<React.SetStateAction<VideoResolutionType>>;
toggleFullscreen: () => void; toggleFullscreen: () => void;
containerRef?: React.MutableRefObject<HTMLDivElement | null>; containerRef?: React.MutableRefObject<HTMLDivElement | null>;
@ -267,7 +267,12 @@ export default function DynamicVideoPlayer({
onTimeUpdate={onTimeUpdate} onTimeUpdate={onTimeUpdate}
onPlayerLoaded={onPlayerLoaded} onPlayerLoaded={onPlayerLoaded}
onClipEnded={onValidateClipEnd} onClipEnded={onValidateClipEnd}
onSeekToTime={onSeekToTime} onSeekToTime={(timestamp, play) => {
if (onSeekToTime) {
console.log("hls player seek", timestamp, play);
onSeekToTime(timestamp, play);
}
}}
onPlaying={() => { onPlaying={() => {
if (isScrubbing) { if (isScrubbing) {
playerRef.current?.pause(); playerRef.current?.pause();

View File

@ -28,7 +28,8 @@ import { cn } from "@/lib/utils";
type DetailStreamProps = { type DetailStreamProps = {
reviewItems?: ReviewSegment[]; reviewItems?: ReviewSegment[];
currentTime: number; currentTime: number;
onSeek: (timestamp: number) => void; // `play` follows DynamicVideoController convention; pass `false` to pause
onSeek: (timestamp: number, play?: boolean) => void;
}; };
export default function DetailStream({ export default function DetailStream({
@ -398,7 +399,7 @@ function EventCollapsible({
event.id != selectedObjectId && event.id != selectedObjectId &&
(effectiveTime ?? 0) >= (event.start_time ?? 0) && (effectiveTime ?? 0) >= (event.start_time ?? 0) &&
(effectiveTime ?? 0) <= (event.end_time ?? event.start_time ?? 0) && (effectiveTime ?? 0) <= (event.end_time ?? event.start_time ?? 0) &&
"bg-secondary-highlight/80 outline-[1px] -outline-offset-[0.8px] outline-primary/40", "bg-secondary outline-[1px] -outline-offset-[0.8px] outline-primary/40",
)} )}
> >
<div className="flex w-full items-center justify-between"> <div className="flex w-full items-center justify-between">
@ -464,11 +465,12 @@ function EventCollapsible({
type LifecycleItemProps = { type LifecycleItemProps = {
event: ObjectLifecycleSequence; event: ObjectLifecycleSequence;
onSeek: (timestamp: number) => void;
isActive?: boolean; isActive?: boolean;
onSeek?: (timestamp: number, play?: boolean) => void;
play?: boolean;
}; };
function LifecycleItem({ event, isActive }: LifecycleItemProps) { function LifecycleItem({ event, isActive, onSeek }: LifecycleItemProps) {
const { t } = useTranslation("views/events"); const { t } = useTranslation("views/events");
const { data: config } = useSWR<FrigateConfig>("config"); const { data: config } = useSWR<FrigateConfig>("config");
@ -490,9 +492,13 @@ function LifecycleItem({ event, isActive }: LifecycleItemProps) {
return ( return (
<div <div
role="button"
onClick={() => onSeek?.(event.timestamp ?? 0, false)}
className={cn( className={cn(
"flex items-center gap-2 text-sm text-primary-variant", "flex cursor-pointer items-center gap-2 text-sm text-primary-variant",
isActive ? "text-white" : "duration-500", isActive
? "font-semibold text-primary dark:font-normal"
: "duration-500",
)} )}
> >
<div className="flex size-4 items-center justify-center"> <div className="flex size-4 items-center justify-center">
@ -542,14 +548,12 @@ function ObjectTimeline({
const isActive = const isActive =
Math.abs((effectiveTime ?? 0) - (event.timestamp ?? 0)) <= 0.5; Math.abs((effectiveTime ?? 0) - (event.timestamp ?? 0)) <= 0.5;
return ( return (
<div <LifecycleItem
key={`${event.timestamp}-${event.source_id ?? idx}`} key={`${event.timestamp}-${event.source_id ?? idx}`}
onClick={() => { event={event}
onSeek(event.timestamp); onSeek={onSeek}
}} isActive={isActive}
> />
<LifecycleItem event={event} onSeek={onSeek} isActive={isActive} />
</div>
); );
})} })}
</div> </div>

View File

@ -283,15 +283,15 @@ export function RecordingView({
]); ]);
const manuallySetCurrentTime = useCallback( const manuallySetCurrentTime = useCallback(
(time: number) => { (time: number, play: boolean = false) => {
if (!currentTimeRange) { if (!currentTimeRange) {
return; return;
} }
console.log("manually set time", time, play);
setCurrentTime(time); setCurrentTime(time);
if (currentTimeRange.after <= time && currentTimeRange.before >= time) { if (currentTimeRange.after <= time && currentTimeRange.before >= time) {
mainControllerRef.current?.seekToTimestamp(time, true); mainControllerRef.current?.seekToTimestamp(time, !play);
} else { } else {
updateSelectedSegment(time, true); updateSelectedSegment(time, true);
} }