Files
MultiRoombaRover/perf/spectate-issues/002-multi-rover-webrtc-media-fanout.md
2026-06-12 01:01:53 -04:00

5.1 KiB

Issue 002: Multi-Rover WebRTC Media Fan-Out

Summary

/spectate mounts several RoverMediaPlayer instances at once. In the fresh runtime audit, Chromium reported 6 active RTCPeerConnections. That is a large baseline for low-end clients, especially while the page is also handling telemetry, snapshots, logs, chat, and SVG HUD maps.

Severity

High.

This may not show up as React mutations, but it consumes CPU in browser media pipelines, WebRTC negotiation, decoding, audio handling, and event dispatch. It also scales with the number of rovers.

Upstream Likelihood

Low to medium.

General RoverMediaPlayer improvements can help both / and /spectate, but the core problem is spectate-specific: /spectate intentionally displays multiple rover media players at once. The fix needs a spectator policy for active feeds.

Affected Files

  • webui/src/components/SpectateVideo/index.jsx
  • webui/src/components/RoverMediaPlayer/index.jsx
  • webui/src/hooks/useVideoRequests.js
  • webui/src/lib/whepPlayer.js
  • webui/src/spectate/SpectatorApp/components/RoverRow.jsx
  • server-side WHEP/video request handling, if feed quality/rate selection is added

Evidence

Artifact:

perf/results/2026-06-12T04-55-16-888Z-root-runtime/root-runtime-report.json

30s sample:

RTCPeerConnections: 6
JSEventListeners:   379

The listener list is dominated by normal media listeners:

audio canplay/ended/error/pause/play/stalled/waiting: 9 each
video media events: 3 each

That pattern fits several rover media players, likely with video and dedicated audio connections.

The same audit also saw:

TaskDuration:     28.48s
ScriptDuration:   12.11s
Long tasks:       81

The earlier 8x throttled /spectate profile saw:

Task delta:       49.13s
Script delta:     19.58s

Current Code Path

Each rover card mounts:

webui/src/components/SpectateVideo/index.jsx:17
<RoverMediaPlayer roverId={roverId} />

RoverMediaPlayer automatically creates video and audio request entries:

webui/src/components/RoverMediaPlayer/index.jsx
autoEntries = [
  video rover entry,
  optional dedicated audio entry,
]

It then requests sources through useVideoRequests, starts WHEP players, maintains restart timers, maintains unmute/audio retry timers, and optionally falls back to snapshots when no WHEP URL exists.

Why This Matters

Six peer connections on a powerful desktop may feel fine. On weak laptops and mobile devices, simultaneous WebRTC decode plus React UI churn can easily saturate the main thread or media threads. The visible symptom is not necessarily one obvious function in the CPU profile; it is degraded frame cadence, long tasks, and high browser-level media work.

Fix Strategies

Option A: Cap Active Live Feeds On Mobile

For mobile/low-end layouts:

  • one primary live WHEP feed
  • secondary rover cards use snapshots or lower-rate previews
  • promote a secondary rover to live only when selected/visible/focused

This is the highest-impact spectator-specific design change.

Option B: Pause Offscreen Feeds

Use IntersectionObserver around each SpectateVideo or RoverSpectatorCard:

  • start WHEP when the card is visible
  • stop WHEP when the card is far offscreen
  • keep a small grace period to avoid flapping while scrolling

This matters especially because portrait /spectate can scroll.

Option C: Disable Dedicated Audio For Non-Focused Rovers

Dedicated audio is useful, but not necessarily for every rover at the same time.

Possible policy:

  • audio enabled only for the selected/primary rover
  • audio disabled for muted secondary rovers
  • audio WHEP starts only after user interaction

This should reduce peer connections and audio retry timers.

Option D: Request Lower Quality For Spectator Secondary Feeds

If the server/media stack supports it, add spectator quality tiers:

primary:   normal FPS/resolution
secondary: low FPS/resolution or snapshot-only
mobile:    lower default bitrate/resolution

Option E: Stop Retry Loops When Hidden

When document visibility is hidden or a card is offscreen:

  • stop restart timers
  • stop audio retry intervals
  • avoid requesting fresh WHEP URLs

After the HUD/telemetry issue, add a spectator feed activity policy:

  1. Define "primary" versus "secondary" rover cards.
  2. Keep only primary feeds fully live on mobile.
  3. Use IntersectionObserver to avoid live playback for offscreen cards.
  4. Disable secondary audio by default.

Validation

Run:

CPU_THROTTLE=6 VIEWPORT=390x844 MOBILE=1 SAMPLE_MS=30000 \
  node perf/live-root-runtime-audit.mjs https://rover.otter.land/spectate perf/results

Expected improvements:

  • RTCPeerConnections should drop below 6 on mobile.
  • Media listener counts should drop.
  • TaskDuration should drop.
  • Frame p95/p99 should improve.
  • User-visible video should still start reliably for the primary rover.

Risks

  • Spectators may expect all feeds live all the time on desktop.
  • Switching feeds can introduce startup delay if WHEP is torn down too aggressively.
  • Audio policy needs to avoid surprising users. Make the focused rover's audio behavior clear and consistent.