Skip to content

fix: align webapp with updated livekit tracks lifetime#316

Merged
iparaskev merged 3 commits into
mainfrom
fix_call_web_app
May 18, 2026
Merged

fix: align webapp with updated livekit tracks lifetime#316
iparaskev merged 3 commits into
mainfrom
fix_call_web_app

Conversation

@iparaskev
Copy link
Copy Markdown
Contributor

@iparaskev iparaskev commented May 17, 2026

Summary by CodeRabbit

  • Bug Fixes

    • Fixed video rendering for odd-width streams (chroma plane alignment)
  • Improvements

    • Mute/unmute now preserves published tracks, improving continuity
    • Remote audio playback and screen-share handling made more reliable
    • Media controls and screen-share sync updated for more consistent behavior
  • New Features

    • Reworked dropdown/select UI with a polished, accessible select component

Review Change Stack

@iparaskev iparaskev requested a review from konsalex as a code owner May 17, 2026 19:47
@netlify
Copy link
Copy Markdown

netlify Bot commented May 17, 2026

Deploy Preview for hoppdocs ready!

Name Link
🔨 Latest commit 42203ba
🔍 Latest deploy log https://app.netlify.com/projects/hoppdocs/deploys/6a0b82435566210009531d98
😎 Deploy Preview https://deploy-preview-316--hoppdocs.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 17, 2026

📝 Walkthrough

Walkthrough

Fixes YUV 4:2:0 chroma sizing via ceiling division for odd widths; adds Radix-based Select UI and dependency; refactors Room.tsx to use useIsMuted, extracts remote audio, and changes local mic/camera toggles to mute/unmute.

Changes

Video and Media Handling Improvements

Layer / File(s) Summary
YUV chroma sizing with ceiling division
core/src/graphics/yuv_renderer.rs, core/src/livekit/video.rs
YuvPipeline::ensure_textures, YuvPipeline::upload_yuv, and VideoBuffer::copy_from_i420 now compute U/V texture and buffer dimensions using div_ceil(2) instead of truncating division, correctly handling odd-width video frames.
Radix Select UI and dependency
web-app/src/components/ui/select.tsx, web-app/package.json
Adds new Select component wrappers around Radix primitives (SelectTrigger, SelectContent, SelectItem, etc.) and adds @radix-ui/react-icons to dependencies for component icons.
Mute-based media control and participant layout refactor
web-app/src/pages/Room.tsx
Components now use useIsMuted hook for camera/mic state; ParticipantCard and RemoteAudio are updated for mute-aware rendering; device corner <select>s replaced with the new Select; MediaControls mutes/unmutes local tracks instead of unpublishing, and screen-share syncing dependencies updated.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Possibly related PRs

  • gethopp/hopp#302: Both PRs modify the YUV GPU upload/rendering pipeline in core/src/graphics/yuv_renderer.rs—this PR adjusts U/V texture sizing via ceiling division; the referenced PR rewires the staging/upload path.

Poem

🐰 When chroma math would lop a stray pixel wide,
div_ceil hops in so no U or V will hide.
Mute flows through hooks, audio finds its place,
Selects pick devices with a cleaner face.
Hopp hops on—pixels and voices all in grace.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 60.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title focuses on 'aligning webapp with updated livekit tracks lifetime,' which matches the major webapp refactoring in Room.tsx to mute-aware rendering and local track state management, plus YUV rendering fixes for video processing.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix_call_web_app

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

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@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: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@web-app/src/pages/Room.tsx`:
- Around line 561-573: RemoteAudio currently grabs the first audio publication
but forces Track.Source.Microphone in the AudioTrack ref, causing a mismatch
when the first publication isn't a microphone (e.g., screen-share audio). Update
RemoteAudio to find the audio publication whose source is actually
Track.Source.Microphone (e.g., iterate
participant.audioTrackPublications.values() and select pub where
pub.track?.source === Track.Source.Microphone or pub.source ===
Track.Source.Microphone), use the same source when calling useIsMuted, and pass
that matching publication and its source into AudioTrack.trackRef so the
component can resolve the correct remote microphone track.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4e6c7a2a-5aec-4d73-901c-54adb2b8b3b9

📥 Commits

Reviewing files that changed from the base of the PR and between 6a823c2 and efa72e5.

📒 Files selected for processing (3)
  • core/src/graphics/yuv_renderer.rs
  • core/src/livekit/video.rs
  • web-app/src/pages/Room.tsx

Comment on lines +561 to +573
function RemoteAudio({ participant }: { participant: Participant }) {
const isMicMuted = useIsMuted({ participant, source: Track.Source.Microphone });
const audioPub = Array.from(participant.audioTrackPublications.values())[0];

if (!audioPub || isMicMuted) return null;

return (
<AudioTrack
trackRef={{
participant,
source: Track.Source.Microphone,
publication: audioPub,
}}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

For livekit-client v2.16.1, can Participant.audioTrackPublicationsinclude non-microphone sources (e.g., screen-share audio)? For@livekit/components-reactv2.9.17, inAudioTrack, what happens if trackRef.sourceisMicrophonebuttrackRef.publication.source differs?

💡 Result:

For LiveKit Client SDK v2.16.1, the Participant.audioTrackPublications property is a collection of all audio track publications for a participant [1]. While it is commonly used for microphone tracks, it is not strictly limited to them; it includes any published audio track, including those from other sources like screen-share audio, provided they are published as audio tracks [1][2]. Regarding @livekit/components-react v2.9.17, the AudioTrack component uses the useMediaTrackBySourceOrName hook to resolve the track to be rendered [3][4]. When you provide a trackRef, the component relies on this hook to find the appropriate publication [3][4]. If the trackRef.source is set to Microphone but the actual publication source differs (e.g., it is a screen-share audio track), the hook will typically fail to find a matching publication for that specific source, resulting in the component not rendering the track or rendering nothing, as it specifically filters for the source provided in the trackRef [3][5][4]. The component does not automatically fallback to other sources if the specified source does not match the publication's source [3][4].

Citations:


Select microphone publication explicitly in RemoteAudio.

RemoteAudio picks the first audio publication, but then forces source to microphone. Participant.audioTrackPublications can include non-microphone audio sources (e.g., screen-share audio). When trackRef.source (Microphone) does not match the actual publication source, the AudioTrack component fails to resolve the track, resulting in missing remote audio.

Proposed fix
 function RemoteAudio({ participant }: { participant: Participant }) {
   const isMicMuted = useIsMuted({ participant, source: Track.Source.Microphone });
-  const audioPub = Array.from(participant.audioTrackPublications.values())[0];
+  const audioPub = Array.from(participant.audioTrackPublications.values()).find(
+    (pub) => pub.source === Track.Source.Microphone,
+  );
 
   if (!audioPub || isMicMuted) return null;
 
   return (
     <AudioTrack
       trackRef={{
         participant,
         source: Track.Source.Microphone,
         publication: audioPub,
       }}
       volume={1.0}
     />
   );
 }
📝 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
function RemoteAudio({ participant }: { participant: Participant }) {
const isMicMuted = useIsMuted({ participant, source: Track.Source.Microphone });
const audioPub = Array.from(participant.audioTrackPublications.values())[0];
if (!audioPub || isMicMuted) return null;
return (
<AudioTrack
trackRef={{
participant,
source: Track.Source.Microphone,
publication: audioPub,
}}
function RemoteAudio({ participant }: { participant: Participant }) {
const isMicMuted = useIsMuted({ participant, source: Track.Source.Microphone });
const audioPub = Array.from(participant.audioTrackPublications.values()).find(
(pub) => pub.source === Track.Source.Microphone,
);
if (!audioPub || isMicMuted) return null;
return (
<AudioTrack
trackRef={{
participant,
source: Track.Source.Microphone,
publication: audioPub,
}}
volume={1.0}
/>
);
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web-app/src/pages/Room.tsx` around lines 561 - 573, RemoteAudio currently
grabs the first audio publication but forces Track.Source.Microphone in the
AudioTrack ref, causing a mismatch when the first publication isn't a microphone
(e.g., screen-share audio). Update RemoteAudio to find the audio publication
whose source is actually Track.Source.Microphone (e.g., iterate
participant.audioTrackPublications.values() and select pub where
pub.track?.source === Track.Source.Microphone or pub.source ===
Track.Source.Microphone), use the same source when calling useIsMuted, and pass
that matching publication and its source into AudioTrack.trackRef so the
component can resolve the correct remote microphone track.

Copy link
Copy Markdown
Contributor

@konsalex konsalex left a comment

Choose a reason for hiding this comment

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

wrong review

Copy link
Copy Markdown

@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.

🧹 Nitpick comments (1)
web-app/src/components/ui/select.tsx (1)

27-27: ⚡ Quick win

Prefer cn utility for consistency.

The rest of the file uses the cn utility for className composition, but this line uses clsx directly. Using cn would maintain consistency across the component.

♻️ Proposed fix for consistency
-      <ChevronDownIcon className={clsx("size-4 opacity-70", iconClassName)} />
+      <ChevronDownIcon className={cn("size-4 opacity-70", iconClassName)} />
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web-app/src/components/ui/select.tsx` at line 27, Replace the direct use of
clsx on the ChevronDownIcon with the project's cn utility for class composition
to keep consistency; update the JSX inside the Select component where
ChevronDownIcon is rendered (the className currently built with clsx("size-4
opacity-70", iconClassName)) to use cn("size-4 opacity-70", iconClassName) and
ensure the cn import is present at top of the file if missing.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@web-app/src/components/ui/select.tsx`:
- Line 27: Replace the direct use of clsx on the ChevronDownIcon with the
project's cn utility for class composition to keep consistency; update the JSX
inside the Select component where ChevronDownIcon is rendered (the className
currently built with clsx("size-4 opacity-70", iconClassName)) to use cn("size-4
opacity-70", iconClassName) and ensure the cn import is present at top of the
file if missing.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 268d657f-83d0-4465-a809-8df44f42a2b8

📥 Commits

Reviewing files that changed from the base of the PR and between efa72e5 and 42203ba.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (3)
  • web-app/package.json
  • web-app/src/components/ui/select.tsx
  • web-app/src/pages/Room.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • web-app/src/pages/Room.tsx

@iparaskev iparaskev merged commit 6ef5fe0 into main May 18, 2026
21 checks passed
@iparaskev iparaskev deleted the fix_call_web_app branch May 18, 2026 22:04
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.

2 participants