Skip to content

Conversation

@maxkahan
Copy link
Collaborator

@maxkahan maxkahan commented Jan 30, 2026

Summary

  • Add video frame tracking to supplement aiortc's missing frame metrics (dimensions, frame count, encode/decode time, FPS)
  • Fix release workflow job failures

Changes

Frame Tracking

  • VideoFrameTracker: Wraps subscriber video tracks to capture frame metrics through recv() interception
  • BufferedMediaTrack: Enhanced with frame tracking for publisher video
  • StatsTracer: Injects tracked frame stats into RTP stats before reporting
  • Stats interval parameterized via DEFAULT_STATS_INTERVAL_MS instead of hardcoded values

CI

  • Fix release.yml causing job failures

Test plan

  • test_frame_tracking.py - 5 tests covering frame tracking and stats injection
  • Lint passes
  • Manual verification: frame metrics (Frame Rate, Frame Width, Frame Height, Encode Time Share) now appear in dashboard

Summary by CodeRabbit

  • New Features

    • Video frame tracking for publish/subscribe with per-frame metrics (counts, dimensions, processing/encode/decode times) and FPS reporting.
    • Stats reporting accepts a configurable sampling interval and can attach frame trackers for richer video metrics.
  • Bug Fixes

    • Defensive handling to ensure stats flow continues if frame-stat collection fails.
  • Tests

    • New comprehensive unit tests covering frame tracking and stats injection.
  • Chores

    • CI now installs all extras and dev dependencies during setup.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Jan 30, 2026

📝 Walkthrough

Walkthrough

Adds video frame tracking and per-frame metrics by wrapping video MediaStreamTracks with VideoFrameTracker and surfacing metrics via StatsTracer; also updates CI release workflow to pass extra install flags to python-uv-setup.

Changes

Cohort / File(s) Summary
Release Workflow Configuration
.github/workflows/release.yml
Added with: sync-flags: "--all-extras --dev" to python-uv-setup action in two places to pass extra install flags.
Video Frame Tracking & Utilities
getstream/video/rtc/track_util.py
Added VideoFrameTracker and per-frame metrics in BufferedMediaTrack (frames_processed, frame dimensions, total_processing_time_ms), plus _update_frame_stats() and get_frame_stats().
Peer Connection Integration
getstream/video/rtc/pc.py, getstream/video/rtc/peer_connection.py
Wraps video MediaStreamTrack instances with VideoFrameTracker, stores wrappers in video_frame_trackers, uses wrapped tracks for subscriptions/PCM emission, initializes StatsTracer with DEFAULT_STATS_INTERVAL_MS/1000, and sets publisher frame tracker for relayed video.
Statistics Reporting
getstream/video/rtc/stats_tracer.py
Expanded StatsTracer to accept interval_s, added _frame_tracker and set_frame_tracker(), injects per-track frame metrics into outbound/inbound RTP stats (framesSent/framesDecoded, dimensions, total encode/decode time, FPS) with defensive error handling.
Tests
tests/rtc/test_frame_tracking.py
New comprehensive tests for VideoFrameTracker, BufferedMediaTrack, and StatsTracer frame-stats injection, plus integration checks with a PeerConnection and mocked video frames.

Sequence Diagram

sequenceDiagram
    participant MediaTrack as MediaStreamTrack
    participant VFT as VideoFrameTracker
    participant PC as PeerConnection
    participant ST as StatsTracer
    participant Reporter as Stats Report

    MediaTrack->>VFT: wrap track on add
    VFT->>VFT: recv() updates frame metrics (dims, timing)
    PC->>ST: init(interval_s)
    PC->>ST: set_frame_tracker(VFT)
    ST->>ST: periodic collect
    ST->>VFT: get_frame_stats()
    VFT-->>ST: return frame metrics
    ST->>Reporter: emit RTP stats with frame metrics
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐇 I count each frame with nimble feet,

Pixels and times in tidy beat.
Wrapped and tracked, the video sings,
Stats hop out on streaming wings. 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 62.79% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Send frame info to SFU' directly aligns with the main objective of the PR, which is to add video frame tracking and send frame metrics to the SFU (Selective Forwarding Unit) for dashboard display.

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

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch add-frame-info

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

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

🤖 Fix all issues with AI agents
In `@getstream/video/rtc/peer_connection.py`:
- Line 61: The long StatsTracer instantiations (e.g., the subscriber_stats
assignment using StatsTracer(self.subscriber_pc, "subscriber",
DEFAULT_STATS_INTERVAL_MS / 1000) and the analogous publisher_stats call) exceed
formatter limits; reformat by breaking the constructor call across multiple
lines using implicit line continuation (wrap the arguments inside parentheses)
or one-per-line arguments so each line is under the formatter width, e.g. put
StatsTracer( on its own line and then each argument on its own line before the
closing parenthesis; update both the subscriber_stats and the publisher_stats
(StatsTracer) instantiations accordingly.

In `@getstream/video/rtc/stats_tracer.py`:
- Around line 142-144: Guard the FPS division against zero/negative intervals in
the FPS calculation sites inside the stats tracer (where interval_s is used
around the FPS math in the code paths referenced at lines ~626-631 and ~667-671
and called from _inject_frame_stats). Modify the FPS computation to check
interval_s > 0 before dividing (e.g., if interval_s <= 0 then set fps = 0 or
skip/update safely) or clamp interval_s = max(interval_s, epsilon) so the
division cannot raise ZeroDivisionError; apply the same change to both locations
where interval_s is used to compute fps.

In `@getstream/video/rtc/track_util.py`:
- Around line 2305-2310: The type error comes from assigning non-int frame
dimensions to self.frame_width / self.frame_height; update the assignment in the
block that checks hasattr(frame, "width") and hasattr(frame, "height") (the
place updating self.frames_processed, self.frame_width, self.frame_height,
self.total_processing_time_ms) to cast dimensions to int (e.g., use
int(frame.width) and int(frame.height)) so the attributes are assigned as ints
and satisfy static typing.

Copy link

@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

🤖 Fix all issues with AI agents
In `@tests/rtc/test_frame_tracking.py`:
- Line 6: Remove the unittest.mock.Mock import and replace all usages of Mock in
tests/rtc/test_frame_tracking.py (including the block around lines 105-134) with
a tiny concrete stub fixture: define a small class or pytest fixture (e.g.,
class StubClock / fixture stub_clock) that implements only the
attributes/methods the tests read (timestamps, advance(), now(), etc.), register
it as a pytest fixture, and update tests to use the fixture or instantiate the
stub directly instead of creating Mock() objects; ensure the stub provides
deterministic behavior and the same interface the tests expect so assertions
remain unchanged.
🧹 Nitpick comments (1)
tests/rtc/test_frame_tracking.py (1)

16-103: Use a fixture to inject MockVideoTrack into tests.

This keeps setup DRY and aligns with pytest fixture usage for shared construction. As per coding guidelines, "Use fixtures to inject objects in tests".

♻️ Proposed refactor
 class MockVideoTrack:
     """Mock video track for testing frame trackers."""
@@
     def stop(self):
         self._ended = True
 
+@pytest.fixture
+def make_video_track():
+    def _make(frames, delay_ms: float = 0):
+        return MockVideoTrack(frames, delay_ms=delay_ms)
+    return _make
+
 class TestVideoFrameTracker:
     """Tests for VideoFrameTracker (subscriber video track wrapper)."""
 
     `@pytest.mark.asyncio`
-    async def test_tracks_frames_and_dimensions(self):
+    async def test_tracks_frames_and_dimensions(self, make_video_track):
@@
-        tracker = VideoFrameTracker(MockVideoTrack(frames, delay_ms=5))
+        tracker = VideoFrameTracker(make_video_track(frames, delay_ms=5))
@@
-    async def test_handles_track_end(self):
+    async def test_handles_track_end(self, make_video_track):
@@
-        tracker = VideoFrameTracker(MockVideoTrack([create_video_frame()]))
+        tracker = VideoFrameTracker(make_video_track([create_video_frame()]))
@@
 class TestBufferedMediaTrackFrameTracking:
     """Tests for BufferedMediaTrack frame tracking (publisher video)."""
 
     `@pytest.mark.asyncio`
-    async def test_tracks_frames_through_recv(self):
+    async def test_tracks_frames_through_recv(self, make_video_track):
@@
-        buffered = BufferedMediaTrack(MockVideoTrack(frames))
+        buffered = BufferedMediaTrack(make_video_track(frames))

import asyncio
import pytest
import av
from unittest.mock import Mock
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Avoid unittest.mock.Mock here; replace with a tiny stub fixture.

This keeps the test pure and avoids mock objects. As per coding guidelines, "Do not use mocks or mock objects in tests unless directly requested".

🧩 Proposed fix
-import av
-from unittest.mock import Mock
+import av
@@
 class TestBufferedMediaTrackFrameTracking:
     """Tests for BufferedMediaTrack frame tracking (publisher video)."""
@@
         assert stats["frameHeight"] == 120
 
 
+@pytest.fixture
+def frame_tracker():
+    class _FrameTracker:
+        def get_frame_stats(self):
+            return {
+                "framesSent": 100,
+                "frameWidth": 320,
+                "frameHeight": 240,
+                "totalEncodeTime": 0.5,
+            }
+
+    return _FrameTracker()
+
+
 class TestStatsTracerFrameInjection:
     """Tests for StatsTracer frame stats injection."""
 
     `@pytest.mark.asyncio`
-    async def test_injects_publisher_frame_stats(self):
+    async def test_injects_publisher_frame_stats(self, frame_tracker):
@@
-            tracer.set_frame_tracker(
-                Mock(
-                    get_frame_stats=lambda: {
-                        "framesSent": 100,
-                        "frameWidth": 320,
-                        "frameHeight": 240,
-                        "totalEncodeTime": 0.5,
-                    }
-                )
-            )
+            tracer.set_frame_tracker(frame_tracker)

Also applies to: 105-134

🤖 Prompt for AI Agents
In `@tests/rtc/test_frame_tracking.py` at line 6, Remove the unittest.mock.Mock
import and replace all usages of Mock in tests/rtc/test_frame_tracking.py
(including the block around lines 105-134) with a tiny concrete stub fixture:
define a small class or pytest fixture (e.g., class StubClock / fixture
stub_clock) that implements only the attributes/methods the tests read
(timestamps, advance(), now(), etc.), register it as a pytest fixture, and
update tests to use the fixture or instantiate the stub directly instead of
creating Mock() objects; ensure the stub provides deterministic behavior and the
same interface the tests expect so assertions remain unchanged.

@maxkahan maxkahan merged commit f8af642 into main Jan 30, 2026
10 of 13 checks passed
@maxkahan maxkahan deleted the add-frame-info branch January 30, 2026 13:36
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