Skip to content

Add detection button for multiple detections from one bout#910

Open
benjifriedman wants to merge 1 commit intomainfrom
add-detection-button
Open

Add detection button for multiple detections from one bout#910
benjifriedman wants to merge 1 commit intomainfrom
add-detection-button

Conversation

@benjifriedman
Copy link
Contributor

@benjifriedman benjifriedman commented Aug 20, 2025

Summary by CodeRabbit

  • New Features

    • Add detection directly from video via an “Add detection” button.
    • Detection dialog pre-fills precise timestamp based on current playback position.
    • Displays current listener count to provide live context.
  • Improvements

    • Playback state is now accurately reflected in the UI (play/pause/waiting/error).
    • More reliable timing by calculating offsets relative to the stream’s playlist timestamp.
    • Smoother interaction between video playback and detection creation for fewer interruptions.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Aug 20, 2025

Walkthrough

Integrated Video.js player state and event syncing, computed playback offset from playlist timestamp, derived listener presence count, and wired a new DetectionDialog for adding detections using feed, timing, and player state. Added two conditional “Add detection” UI blocks when a feed stream exists. Updated imports and hooks.

Changes

Cohort / File(s) Summary of changes
Detection integration & player sync
ui/src/components/Bouts/BoutPage.tsx
Added Video.js player reference/state and isPlaying sync via event listeners; derived playlistTimestampNum and offset function; integrated useFeedPresence to compute listenerCount; introduced DetectionDialog wired with feed, timestamp, player time, and presence; rendered two conditional “Add detection” UI blocks; updated imports/types.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant BoutPage
  participant VideoJS as Video.js Player
  participant Presence as FeedPresence
  participant Dialog as DetectionDialog

  Note over BoutPage: On mount or player ready
  BoutPage->>VideoJS: store player ref via setPlayerControls
  VideoJS-->>BoutPage: events (playing/pause/waiting/error)
  BoutPage->>BoutPage: update isPlaying from player.paused()

  Note over BoutPage: Feed stream available
  BoutPage->>BoutPage: compute playlistTimestampNum
  BoutPage->>BoutPage: getOffsetSeconds() from player currentTime + playlist timestamp

  BoutPage->>Presence: useFeedPresence(feedId)
  Presence-->>BoutPage: metas.length -> listenerCount

  User->>BoutPage: Click "Add detection"
  BoutPage->>Dialog: Open with { feed, playlistTimestampNum, isPlaying, getPlayerTime=getOffsetSeconds, listenerCount }
  Dialog-->>BoutPage: Submit/close (detection added or canceled)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

A bunny taps the play, ears perked for cues,
Streams hum along while timelines fuse.
With a sniff of presence, counts appear,
“Add detection” pops—so crystal clear.
Hops mark offsets, seconds align,
Carrots for code, and playback fine.
Thump—ship it, right on time!

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch add-detection-button

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@paulcretu paulcretu temporarily deployed to orcasite-pr-910 August 20, 2025 05:31 Inactive
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (2)
ui/src/components/Bouts/BoutPage.tsx (2)

173-191: Prefer 'play' event over 'playing' for Video.js; minor robustness tweak

Video.js reliably emits 'play' and 'pause'; 'playing' may not consistently fire across sources/tech. Also, minor cleanups: use 'onPlay' name for parity and guard against already-disposed players (rare, but cheap).

Apply this diff:

-    const onPlaying = () => setIsPlaying(true);
+    const onPlay = () => setIsPlaying(true);
     const onPause = () => setIsPlaying(false);
     const onWaiting = () => setIsPlaying(false);
     const onError = () => setIsPlaying(false);
-    player.on("playing", onPlaying);
+    player.on("play", onPlay);
     player.on("pause", onPause);
     player.on("waiting", onWaiting);
     player.on("error", onError);
     return () => {
-      player.off("playing", onPlaying);
+      player.off("play", onPlay);
       player.off("pause", onPause);
       player.off("waiting", onWaiting);
       player.off("error", onError);
     };

652-681: UX consideration: gating submission by isPlaying may block valid reports

DetectionDialog requires isPlaying to be true to submit. That could prevent users from pausing to precisely locate and then report. If that’s intentional, ignore; otherwise consider allowing submission when paused (and just use the current playhead time).

Would you like a quick change to allow submission when paused but still capture the current offset accurately?

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between b8dcac8 and 06a21cb.

📒 Files selected for processing (1)
  • ui/src/components/Bouts/BoutPage.tsx (7 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
ui/src/components/Bouts/BoutPage.tsx (4)
ui/src/components/Player/VideoJS.tsx (1)
  • VideoJSPlayer (10-10)
ui/src/components/Player/BoutPlayer.tsx (1)
  • PlayerControls (18-24)
ui/src/hooks/useFeedPresence.ts (1)
  • useFeedPresence (11-38)
ui/src/components/Player/DetectionDialog.tsx (1)
  • DetectionDialog (28-260)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: test
🔇 Additional comments (3)
ui/src/components/Bouts/BoutPage.tsx (3)

50-52: Good addition: lazy type-only import for player type keeps bundle lean

Using a type-only import for VideoJSPlayer avoids bloating the client bundle. Importing DetectionDialog here is appropriate.


107-115: Storing player ref and deriving initial play state: LGTM

Persisting the Video.js player instance from PlayerControls and initializing isPlaying from controls.paused() is correct and keeps state in sync from the outset.


245-253: No unit mismatch—playlistTimestamp is in seconds.

The GraphQL schema defines playlistTimestamp as an Int (UTC Unix epoch in seconds), and playerTime.current.valueOf() / 1000 converts the Date’s millisecond value to seconds. Subtracting two second‐based values yields the correct offset in seconds. You can safely ignore the suggested diff.

Likely an incorrect or invalid review comment.

Comment on lines +276 to +284
// Listener count via Presence (same approach as Player.tsx)
const feedPresence = useFeedPresence(feed.slug);
const listenerCount: number = Array.isArray(
(feedPresence as Record<string, unknown[] | undefined> | undefined)?.metas,
)
? ((feedPresence as Record<string, unknown[] | undefined>)?.metas?.length ??
0)
: 0;

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Listener count derivation is broken: wrong identifier and wrong shape assumptions

  • useFeedPresence expects a feed ID but you pass feed.slug.
  • The hook returns an object with metas (from Phoenix Presence), not a Record keyed by metas. Current casting makes listenerCount always 0.

Fix both issues.

Apply this diff:

-  const feedPresence = useFeedPresence(feed.slug);
-  const listenerCount: number = Array.isArray(
-    (feedPresence as Record<string, unknown[] | undefined> | undefined)?.metas,
-  )
-    ? ((feedPresence as Record<string, unknown[] | undefined>)?.metas?.length ??
-      0)
-    : 0;
+  const feedPresence = useFeedPresence(feed.id) as { metas?: unknown[] } | undefined;
+  const listenerCount: number = Array.isArray(feedPresence?.metas)
+    ? feedPresence!.metas!.length
+    : 0;

If you'd like, I can also open a follow-up to correct the return type of useFeedPresence so consumers don’t need casts.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Listener count via Presence (same approach as Player.tsx)
const feedPresence = useFeedPresence(feed.slug);
const listenerCount: number = Array.isArray(
(feedPresence as Record<string, unknown[] | undefined> | undefined)?.metas,
)
? ((feedPresence as Record<string, unknown[] | undefined>)?.metas?.length ??
0)
: 0;
// Listener count via Presence (same approach as Player.tsx)
- const feedPresence = useFeedPresence(feed.slug);
- const listenerCount: number = Array.isArray(
- (feedPresence as Record<string, unknown[] | undefined> | undefined)?.metas,
- )
- ? ((feedPresence as Record<string, unknown[] | undefined>)?.metas?.length ??
- 0)
const feedPresence = useFeedPresence(feed.id) as { metas?: unknown[] } | undefined;
const listenerCount: number = Array.isArray(feedPresence?.metas)
? feedPresence!.metas!.length
: 0;
🤖 Prompt for AI Agents
In ui/src/components/Bouts/BoutPage.tsx around lines 276 to 284, the listener
count is computed incorrectly: pass feed.slug (should be feed.id) to
useFeedPresence and the code incorrectly assumes the hook returns a Record keyed
by metas, causing listenerCount to always be 0. Change the hook call to
useFeedPresence(feed.id), treat the return value as the Presence object (with an
optional metas array), and compute listenerCount as the length of presence.metas
if present (e.g., const listenerCount = Array.isArray(presence?.metas) ?
presence.metas.length : 0), removing the incorrect Record casts.

Comment on lines +663 to +669
<DetectionDialog
feed={feed}
timestamp={playlistTimestampNum}
isPlaying={isPlaying}
getPlayerTime={getOffsetSeconds}
listenerCount={listenerCount}
>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Prop uses the old timestamp variable; align with seconds-based value

If you adopt the seconds-based playlistTimestampSec above, wire it through here to keep DetectionDialog submissions consistent.

Apply this diff:

-                <DetectionDialog
+                <DetectionDialog
                   feed={feed}
-                  timestamp={playlistTimestampNum}
+                  timestamp={playlistTimestampSec}
                   isPlaying={isPlaying}
                   getPlayerTime={getOffsetSeconds}
                   listenerCount={listenerCount}
                 >
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<DetectionDialog
feed={feed}
timestamp={playlistTimestampNum}
isPlaying={isPlaying}
getPlayerTime={getOffsetSeconds}
listenerCount={listenerCount}
>
<DetectionDialog
feed={feed}
timestamp={playlistTimestampSec}
isPlaying={isPlaying}
getPlayerTime={getOffsetSeconds}
listenerCount={listenerCount}
>
🤖 Prompt for AI Agents
In ui/src/components/Bouts/BoutPage.tsx around lines 663 to 669, the
DetectionDialog prop 'timestamp' is still using the old seconds-based variable
playlistTimestampNum; replace it with the seconds-based variable
playlistTimestampSec so DetectionDialog receives the correct unit. Update the
prop to timestamp={playlistTimestampSec}, confirm the variable is defined in
scope and typed as seconds (number), and run type-checks/tests to ensure no
other callsites rely on the old variable name/unit.

Copy link
Contributor

@skanderm skanderm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this submission Benji! Looking through it, I'm realizing I should have been more specific about the context of what the feature needed. There's also a way that the DetectionDialog made some assumptions that don't apply fully here (i.e. that someone wants to make a submission while the audio is playing live), so we might actually want to create a new component for it.

Could you try this again with these notes?

  • Create a new BoutDetectionSubmission component
  • Have it take the params necessary to calculate the right playerOffset for the submitDetection mutation: playerTime (Date ref updated by the BoutPlayer), feedStream (to get playlistTimestamp). You can later calculate playerOffset by using subMilliseconds(playerTime.current, playlistTimestamp) / 1000. (The playerOffset is the time in seconds between the playlistTimestamp and the time of the detection. The playlistTimestamp is when the hydrophone node reboots every 6 or 24 hours and a new S3 bucket dir is created to upload 10s audio segments, so the offset tells us where in the new S3 dir is the detection).
  • Include a button that opens the dialog modal to let the user select the category and type in a description
  • Submit button in the modal should send the submitDetection submission with the playlistTimestamp from the feedStream, the feedId passed in also as a param to BoutDetectionSubmission, the playerOffset calculated earlier, a listenerCount set to 1 (because it's just the moderator listening/submitting the detection, and a category and description set during the modal.

All that should allow you to skip needing the VideoJSPlayer and the useEffect to set all the player hooks. Setting listenerCount to 1 also lets you skip the feedPresence stuff which was intended to give moderators a signal about how many people are listening live when they're submitting a detection, which doesn't apply here.

Please let me know if you have any questions, or if your dev setup is still missing stuff to make it easier to build this!

@benjifriedman
Copy link
Contributor Author

Thanks for the feedback @skanderm. I will make these changes in the pull request

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants