diff --git a/ui/src/components/Bouts/BoutPage.tsx b/ui/src/components/Bouts/BoutPage.tsx index 94a5854b2..de5834b61 100644 --- a/ui/src/components/Bouts/BoutPage.tsx +++ b/ui/src/components/Bouts/BoutPage.tsx @@ -47,6 +47,8 @@ import SpectrogramTimeline, { SpectrogramControls, } from "@/components/Bouts/SpectrogramTimeline"; import { BoutPlayer, PlayerControls } from "@/components/Player/BoutPlayer"; +import DetectionDialog from "@/components/Player/DetectionDialog"; +import type { VideoJSPlayer } from "@/components/Player/VideoJS"; import { AudioCategory, BoutQuery, @@ -60,6 +62,7 @@ import { useUpdateBoutMutation, } from "@/graphql/generated"; import { useAudioImageUpdatedSubscription } from "@/hooks/useAudioImageUpdatedSubscription"; +import useFeedPresence from "@/hooks/useFeedPresence"; import { roundToNearest } from "@/utils/time"; import CopyToClipboardButton from "../CopyToClipboard"; @@ -101,10 +104,15 @@ export default function BoutPage({ [], ); const playerControls = useRef(); - const setPlayerControls = useCallback( - (controls: PlayerControls) => (playerControls.current = controls), - [], - ); + const [videoJsPlayer, setVideoJsPlayer] = useState< + VideoJSPlayer | undefined + >(); + const [isPlaying, setIsPlaying] = useState(false); + const setPlayerControls = useCallback((controls: PlayerControls) => { + playerControls.current = controls; + setVideoJsPlayer(controls.player); + setIsPlaying(!controls.paused()); + }, []); const spectrogramControls = useRef(); const [cachedPlayerTime, setCachedPlayerTime] = useState(targetTime); @@ -162,6 +170,26 @@ export default function BoutPage({ max: Date; }>({ min: timelineStartTime, max: timelineEndTime }); + // Track player play/pause state from the embedded Video.js player + useEffect(() => { + const player = videoJsPlayer; + if (!player) return; + const onPlaying = () => setIsPlaying(true); + const onPause = () => setIsPlaying(false); + const onWaiting = () => setIsPlaying(false); + const onError = () => setIsPlaying(false); + player.on("playing", onPlaying); + player.on("pause", onPause); + player.on("waiting", onWaiting); + player.on("error", onError); + return () => { + player.off("playing", onPlaying); + player.off("pause", onPause); + player.off("waiting", onWaiting); + player.off("error", onError); + }; + }, [videoJsPlayer]); + const expandTimelineStart = useCallback(() => { setTimelineStartTime((timelineStartTime) => roundToNearest( @@ -214,6 +242,14 @@ export default function BoutPage({ [feedStreamQueryResult], ); const feedStream = feedStreams[0]; + const playlistTimestampNum = feedStream + ? Number(feedStream.playlistTimestamp) + : undefined; + const getOffsetSeconds = () => + feedStream && playerTime.current + ? playerTime.current.valueOf() / 1000 - + Number(feedStream.playlistTimestamp) + : undefined; const detections = detectionQueryResult.data?.detections?.results ?? []; @@ -237,6 +273,15 @@ export default function BoutPage({ ({ id }) => id, ); + // Listener count via Presence (same approach as Player.tsx) + const feedPresence = useFeedPresence(feed.slug); + const listenerCount: number = Array.isArray( + (feedPresence as Record | undefined)?.metas, + ) + ? ((feedPresence as Record)?.metas?.length ?? + 0) + : 0; + const [boutForm, setBoutForm] = useState<{ errors: Record; isSaving: boolean; @@ -604,6 +649,36 @@ export default function BoutPage({ )} + {feedStream && ( + + + Add detection + + + + + + + + + + )}