Skip to content

Conversation

Brazol
Copy link
Contributor

@Brazol Brazol commented Aug 20, 2025

part of FLU-232
resolves FLU-205

This PR moves call stats from CallState to StatsReporter and adds additional battery and thermal state tracking.

Summary by CodeRabbit

  • New Features

    • Battery and thermal monitoring added with new charts and a battery-drain summary.
    • Consolidated metrics reporter surfaces environment info (SFU / SDK / WebRTC) and aggregated call metrics.
  • Improvements

    • Unified metrics view now shows latency history, jitter, bitrate, resolution and codec details.
    • Metrics loading displays a spinner when data is unavailable.
  • User Experience

    • New headers, icons and descriptive text improve diagnostics readability.

@Brazol Brazol requested a review from a team as a code owner August 20, 2025 11:57
Copy link

coderabbitai bot commented Aug 20, 2025

Walkthrough

Replaces legacy per-connection stats with a StatsReporter/CallMetrics pipeline: Call exposes call.statsReporter, session creates and wires StatsReporter, CallState drops publisher/subscriber/local/latency fields, dogfooding UI consumes CallMetrics and adds battery/thermal charts and UI bindings.

Changes

Cohort / File(s) Summary
Dogfooding UI: Call stats screen
dogfooding/lib/screens/call_stats_screen.dart
Rewired UI to consume StreamBuilder<CallMetrics?> from call.statsReporter?.stream (with initialData); added null-safe rendering; replaced legacy subscriberStats/publisherStats/localStats paths with subscriber/publisher/clientEnvironment; added thermal & battery sections and battery-drain calculation; updated latency/codec/bitrate/resolution/environment displays.
Dogfooding UI: New charts
dogfooding/lib/screens/stats_battery_chart.dart, dogfooding/lib/screens/stats_thermal_chart.dart
Added StatsBatteryChart (line chart) and StatsThermalChart (bar chart) using fl_chart; map history lists to chart data, apply gradients/interpolation, configure axes/titles, and style visuals.
Core models & metrics types
packages/stream_video/lib/src/models/call_stats.dart
Renamed CallStatsPeerConnectionStatsBundle, renamed LocalStatsClientEnvironment, and added CallMetrics aggregating publisher/subscriber, clientEnvironment, latency/battery/thermal histories, and initialBatteryLevel.
Stats reporter & pipeline
packages/stream_video/lib/src/call/stats/stats_reporter.dart
Reimplemented StatsReporter as StateNotifier<CallMetrics?>; adds currentMetrics; run/collectStats now emit publisher/subscriber bundles; periodically samples battery/thermal and appends histories; processes bundles into CallMetrics.
Call API & session integration
packages/stream_video/lib/src/call/call.dart, packages/stream_video/lib/src/call/session/call_session.dart, packages/stream_video/CHANGELOG.md
Added Call.statsReporter getter; changed Call.stats payload to publisher/subscriber bundle shape; session constructs ClientEnvironment, instantiates/stores StatsReporter after rtcManager creation and wires metrics emission; CHANGELOG documents breaking changes.
Lifecycle & state cleanup
packages/stream_video/lib/src/call/state/mixins/state_lifecycle_mixin.dart, packages/stream_video/lib/src/call_state.dart
Removed publisherStats, subscriberStats, localStats, and latencyHistory from CallState, lifecycle methods, and copyWith/props; deleted lifecycleCallStats and related update sites.
Diagnostics & Flutter integration
packages/stream_video_flutter/lib/src/call_screen/call_diagnostics_content/call_diagnostics_content.dart
Updated diagnostics subscription to new publisherStatsBundle/subscriberStatsBundle types; adjusted internal fields and subscription typing; preserved rendering using .printable.
Tracing / trace utilities & factory
packages/stream_video/lib/src/call/stats/tracer.dart, packages/stream_video/lib/src/call/stats/trace_record.dart, packages/stream_video/lib/src/call/session/call_session_factory.dart
Added Tracer.traceMultiple, TraceRecord.copyWith, and makeCallSession param leftoverTraceRecords (replayed via traceMultiple); added tracing instrumentation across join/reconnect/SFU events and replay of leftover traces.
Tests / helpers
packages/stream_video/test/src/call/call_test_helpers.dart
Test helpers stub getTrace on CallSession to return empty TraceSlice, enabling trace-dependent code paths in tests.

Sequence Diagram(s)

sequenceDiagram
  participant UI as CallStatsScreen
  participant Call as Call
  participant Session as CallSession
  participant SR as StatsReporter
  participant RTC as RTCManager
  participant Device as Device

  UI->>Call: subscribe to statsReporter?.stream (initialData: currentMetrics)
  Call-->>UI: emits CallMetrics updates

  Note over Session,SR: Session builds StatsReporter after RTC manager creation
  Session->>SR: instantiate StatsReporter(clientEnvironment, rtcManager)
  Session-->>Call: expose statsReporter

  loop periodic ticks
    SR->>RTC: collect publisher/subscriber PC bundles
    SR->>SR: process bundles -> update CallMetrics (latency, bitrate, jitter, codecs, resolution)
    alt every 10th tick
      SR->>Device: sample battery & thermal
      Device-->>SR: battery%, thermal severity
      SR->>SR: append to battery/thermal histories
    end
    SR-->>Call: publish updated metrics
    Call-->>UI: UI renders metrics & charts
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Suggested reviewers

  • xsahil03x

Poem

A rabbit peers at metrics bright,
Bundles hopping through the night.
Battery ticks and thermal bars,
Charts that sparkle like small stars.
StatsReporter hums — hop, hop, delight! 🐇📈

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings, 2 inconclusive)
Check name Status Explanation Resolution
Out of Scope Changes Check ⚠️ Warning In addition to the stats migration and battery/thermal tracking called out by FLU-205 and FLU-232, the PR introduces extensive tracing enhancements (new trace tags and methods in CallSession, tracer.traceMultiple, TraceRecord.copyWith, call_session_factory leftoverTraceRecords handling, SFU event tracing, reconnection instrumentation) that fall outside the linked issues’ scope focused solely on stats handling. Please separate the tracing and instrumentation enhancements into a distinct PR or clearly document how these changes relate to the stats objectives, ensuring that this PR remains focused on the call stats migration and battery/thermal metrics as defined by the linked issues.
Description Check ⚠️ Warning The current description only references linked issues and gives a high-level summary of moving call stats to StatsReporter and adding battery and thermal tracking but omits most required template sections such as the goal, detailed implementation steps, testing instructions, and UI changes. Please update the description to follow the repository template by adding a “🎯 Goal” section explaining the rationale, “🛠 Implementation details” outlining the key code changes, “🧪 Testing” instructions, and any relevant “🎨 UI Changes” if applicable.
Title Check ❓ Inconclusive The title “chore(llc): call stats improvements” generically references “call stats” but does not capture the specific primary changes such as migrating stats to StatsReporter and adding battery and thermal tracking, making it too vague to clearly convey the main intent of the PR. Please refine the title to succinctly highlight the core change, for example “migrate call stats to StatsReporter and add battery and thermal tracking,” to clearly communicate the primary updates.
Linked Issues Check ❓ Inconclusive The PR removes the legacy CallState stats fields (publisherStats, subscriberStats, localStats, latencyHistory), adds battery drain and thermal history tracking via StatsReporter and updates the UI charts accordingly, addressing most requirements from FLU-205 and FLU-232; however, it is not clear from the provided summaries whether stat collection is now gated to active listeners or that a single pc.getStats() invocation is used for both SFU and local stats. Please verify and document that stats collection is activated only when there is at least one listener on the stream and that the implementation uses a single pc.getStats() call to consolidate both local and SFU stats collection per the linked issue requirements.
✅ Passed checks (1 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch chore/call-stats-improvements

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • 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 1e99baa and 30f8752.

📒 Files selected for processing (1)
  • dogfooding/lib/screens/call_stats_screen.dart (3 hunks)
⏰ 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). (6)
  • GitHub Check: stream_video_noise_cancellation
  • GitHub Check: stream_video_push_notification
  • GitHub Check: build
  • GitHub Check: analyze
  • GitHub Check: stream_video_flutter
  • GitHub Check: stream_video
🔇 Additional comments (7)
dogfooding/lib/screens/call_stats_screen.dart (7)

30-34: LGTM!

Appropriate null-state handling with a loading indicator.


39-43: Battery drain calculation properly handles null.

The null-safe battery drain calculation correctly checks for both initialBatteryLevel availability and non-empty batteryLevelHistory before computing the drain. This addresses the previous review concern.


106-177: Well-structured thermal and battery tracking sections.

The new thermal state and battery level sections are properly implemented with appropriate:

  • Icon indicators (whatshot, battery_full)
  • Descriptive text
  • Chart widgets (StatsThermalChart, StatsBatteryChart)
  • Proper data binding to state.thermalStatusHistory and state.batteryLevelHistory

Note: The previous duplicate latency section issue has been resolved.


171-174: Battery drain display correctly handles null.

The display properly shows "N/A" when battery drain cannot be calculated, addressing the previous review concern.


7-9: Imports for new chart widgets are validstats_battery_chart.dart and stats_thermal_chart.dart both exist under dogfooding/lib/screens/ and define class StatsBatteryChart and class StatsThermalChart, so the imports are correct.


267-278: ClientEnvironment on CallMetrics verified — no change required.
CallMetrics exposes clientEnvironment and ClientEnvironment defines sfu, sdkVersion, and webRtcVersion (packages/stream_video/lib/src/models/call_stats.dart); the usages in dogfooding/lib/screens/call_stats_screen.dart are valid.


203-266: CallMetrics structure verified All accessed properties (latency, jitterInMs, bitrateKbps, resolution, videoCodec) are defined as nullable on PeerConnectionStats (used by CallMetrics.publisher/subscriber), and all null‐safety (?. and ??) is correctly applied.


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

codecov bot commented Aug 20, 2025

Codecov Report

❌ Patch coverage is 6.71642% with 125 lines in your changes missing coverage. Please review.
✅ Project coverage is 4.80%. Comparing base (98f3623) to head (30f8752).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
...tream_video/lib/src/call/stats/stats_reporter.dart 0.00% 52 Missing ⚠️
packages/stream_video/lib/src/call/call.dart 24.24% 25 Missing ⚠️
...ckages/stream_video/lib/src/models/call_stats.dart 5.00% 19 Missing ⚠️
...tream_video/lib/src/call/session/call_session.dart 0.00% 12 Missing ⚠️
.../stream_video/lib/src/call/stats/trace_record.dart 0.00% 6 Missing ⚠️
...deo/lib/src/call/session/call_session_factory.dart 0.00% 5 Missing ⚠️
...ckages/stream_video/lib/src/call/stats/tracer.dart 0.00% 3 Missing ⚠️
..._diagnostics_content/call_diagnostics_content.dart 0.00% 3 Missing ⚠️
Additional details and impacted files
@@           Coverage Diff            @@
##            main   #1053      +/-   ##
========================================
- Coverage   4.83%   4.80%   -0.04%     
========================================
  Files        591     591              
  Lines      40324   40395      +71     
========================================
- Hits        1951    1941      -10     
- Misses     38373   38454      +81     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

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: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/stream_video_flutter/lib/src/call_screen/call_diagnostics_content/call_diagnostics_content.dart (1)

54-57: Fix: dispose() must be synchronous and cancel subscriptions before super.dispose()

Overriding dispose() as async returns Future, which does not match State.dispose() signature (must be void). Also cancel subscriptions before calling super.dispose() to avoid setState after dispose race.

-  @override
-  Future<void> dispose() async {
-    super.dispose();
-    await _subscription?.cancel();
-  }
+  @override
+  void dispose() {
+    // Cancel before super.dispose() to prevent events from hitting a dead State.
+    _subscription?.cancel();
+    super.dispose();
+  }
🧹 Nitpick comments (11)
packages/stream_video/CHANGELOG.md (1)

6-15: Unify unordered list style to pass markdownlint (MD004).

Sub-list items under “Breaking changes” use dashes, while the configured style is asterisk. Align to asterisks to satisfy MD004.

-  - The record field names have changed and the element types are different.
+  * The record field names have changed and the element types are different.
-  - Use `call.stats` for periodic WebRTC stats updates, or
-  - Access `call.statsReporter?.currentMetrics` for the latest aggregated metrics instead.
+  * Use `call.stats` for periodic WebRTC stats updates, or
+  * Access `call.statsReporter?.currentMetrics` for the latest aggregated metrics instead.
dogfooding/lib/screens/stats_battery_chart.dart (2)

58-69: Make X-axis dynamic and guard against empty history

Hard-coding maxX: 20 will clip data for longer histories and mis-scale shorter ones. Also consider clamping Y to [0, 100] and handling empty lists gracefully.

-          minX: 0,
-          maxX: 20,
+          minX: 0,
+          maxX: batteryLevelHistory.isEmpty
+              ? 0
+              : (batteryLevelHistory.length - 1).toDouble(),
@@
-              spots: batteryLevelHistory.indexed
-                  .map<FlSpot>(
-                    (m) => FlSpot(m.$1.toDouble(), m.$2.toDouble()),
-                  )
-                  .toList(),
+              spots: batteryLevelHistory.indexed
+                  .map<FlSpot>((m) => FlSpot(
+                        m.$1.toDouble(),
+                        m.$2.clamp(0, 100).toDouble(),
+                      ))
+                  .toList(),

Optional: if you want a sliding window of N points (e.g., 21), slice the tail and rescale X from 0..windowLen-1 for consistent spacing.


17-35: Minor: remove unused vertical grid interval when vertical lines are disabled

verticalInterval has no effect with drawVerticalLine: false.

-            drawVerticalLine: false,
-            drawHorizontalLine: true,
-            verticalInterval: 1,
+            drawVerticalLine: false,
+            drawHorizontalLine: true,
dogfooding/lib/screens/stats_thermal_chart.dart (2)

74-79: Nit: background bar height aligns better with chart max

You add +1 to the data bars to ensure visibility; align the background rod with maxY for consistency.

-                      backDrawRodData: BackgroundBarChartRodData(
+                      backDrawRodData: BackgroundBarChartRodData(
                         show: true,
-                        toY: _maxSeverity.toDouble(),
+                        toY: (_maxSeverity + 1).toDouble(),
                         color: Colors.white.withOpacity(0.04),
                       ),

32-50: Optional: map severity ticks to labels for readability

Right axis is numeric; consider custom getTitlesWidget to map 0..6 to severity names (e.g., none/light/moderate/severe/critical/emergency/shutdown) if available from your thermal model.

packages/stream_video/lib/src/call/call.dart (2)

866-873: Trace payload: prefer serializable names for enum-like fields

If pc.type is an enum (e.g., StreamPeerType), pass a string (name) instead of the enum object to avoid inconsistent serialization in traces.

-          _session?.trace('pc_reconnection_needed', {
-            'peerConnectionId': pc.type,
+          _session?.trace('pc_reconnection_needed', {
+            'peerConnectionId': pc.type.name,
             'reconnectionStrategy': strategy.name,
           });

1342-1345: Consider moving 'call_reconnect' trace inside the reconnect lock

Tracing before acquiring the lock can produce duplicate or misleading events if a concurrent reconnection slips in between the lock check and acquisition.

-    _session?.trace('call_reconnect', {
-      'strategy': strategy.name,
-    });
+    // Move this trace inside the _callReconnectLock.synchronized block after setting _reconnectStrategy

Add this inside the synchronized block, right after setting _reconnectStrategy:

_session?.trace('call_reconnect', {
  'strategy': _reconnectStrategy.name,
});
dogfooding/lib/screens/call_stats_screen.dart (2)

43-45: Clamp battery consumption to avoid negative values when charging

If the device charged during the call, batteryDrained can be negative. Clamp to zero for a clearer UX.

-          final batteryDrained = (state.initialBatteryLevel ?? 0) -
-              (state.batteryLevelHistory.lastOrNull ?? 0);
+          final batteryDrained = max(
+            0,
+            (state.initialBatteryLevel ?? 0) -
+                (state.batteryLevelHistory.lastOrNull ?? 0),
+          );

212-213: Prefer null over 0 for unknown values to render “--” consistently

Passing 0 implies a measured value. Let the widget show “--” when latency is not available.

-                              value: state.publisher?.latency ?? 0,
+                              value: state.publisher?.latency,
packages/stream_video/lib/src/models/call_stats.dart (2)

2-2: Remove unused thermal import

thermal.dart is not referenced in this file.

-import 'package:thermal/thermal.dart';

10-41: Fix toString class name and consider deep equality for lists

  • toString still says CallStats; update to PeerConnectionStatsBundle.
  • Equality on List fields (stats/raw) uses reference equality. If structural equality is desired, use DeepCollectionEquality.
   @override
   String toString() {
-    return 'CallStats{peerType: $peerType, printable: $printable, raw: $raw}';
+    return 'PeerConnectionStatsBundle{peerType: $peerType, printable: $printable, raw: $raw}';
   }

Optional (if structural equality matters here):

  • Add import 'package:collection/collection.dart';
  • Replace stats == other.stats and raw == other.raw with const DeepCollectionEquality().equals(stats, other.stats) and same for raw.
📜 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 b8214d8 and 26d9832.

📒 Files selected for processing (11)
  • dogfooding/lib/screens/call_stats_screen.dart (7 hunks)
  • dogfooding/lib/screens/stats_battery_chart.dart (1 hunks)
  • dogfooding/lib/screens/stats_thermal_chart.dart (1 hunks)
  • packages/stream_video/CHANGELOG.md (1 hunks)
  • packages/stream_video/lib/src/call/call.dart (8 hunks)
  • packages/stream_video/lib/src/call/session/call_session.dart (5 hunks)
  • packages/stream_video/lib/src/call/state/mixins/state_lifecycle_mixin.dart (0 hunks)
  • packages/stream_video/lib/src/call/stats/stats_reporter.dart (8 hunks)
  • packages/stream_video/lib/src/call_state.dart (0 hunks)
  • packages/stream_video/lib/src/models/call_stats.dart (6 hunks)
  • packages/stream_video_flutter/lib/src/call_screen/call_diagnostics_content/call_diagnostics_content.dart (2 hunks)
💤 Files with no reviewable changes (2)
  • packages/stream_video/lib/src/call_state.dart
  • packages/stream_video/lib/src/call/state/mixins/state_lifecycle_mixin.dart
🧰 Additional context used
🪛 markdownlint-cli2 (0.17.2)
packages/stream_video/CHANGELOG.md

11-11: Unordered list style
Expected: asterisk; Actual: dash

(MD004, ul-style)


13-13: Unordered list style
Expected: asterisk; Actual: dash

(MD004, ul-style)


14-14: Unordered list style
Expected: asterisk; Actual: dash

(MD004, ul-style)

🔇 Additional comments (28)
packages/stream_video/CHANGELOG.md (2)

18-20: LGTM: Clear API addition notes (StatsReporter and new metrics)


21-22: LGTM: “Changed” section correctly cross-refers to breaking changes

packages/stream_video_flutter/lib/src/call_screen/call_diagnostics_content/call_diagnostics_content.dart (5)

30-31: Type migration to PeerConnectionStatsBundle looks correct

Publisher stats field updated to the new bundle type without logic change.


33-34: Type migration to PeerConnectionStatsBundle looks correct

Subscriber stats field updated to the new bundle type without logic change.


36-41: Updated stats stream record type is correct and matches new Call.stats payload

The StreamSubscription type reflects the new record shape with publisherStatsBundle/subscriberStatsBundle.


47-49: Correct mapping to new record fields

Assignments pull from stats.publisherStatsBundle/subscriberStatsBundle as expected.


137-141: Propagated type changes to child widget props are consistent

Publisher/subscriber fields now use PeerConnectionStatsBundle?.

packages/stream_video/lib/src/call/session/call_session.dart (4)

38-38: Import of stats_reporter.dart is appropriate for new StatsReporter wiring


143-146: Trace passthrough is a nice convenience

Simple passthrough keeps existing tracing semantics.


414-423: Environment capture LGTM; webRTC version empty on non-Android/iOS is acceptable

Good capture of SDK and SFU info. If/when web/mac/windows versions are derivable, we can enrich later.


739-744: Confirm nullability of usedCodec is handled downstream

usedCodec can be null until initial metrics are available. Verify rtcManager.onPublishQualityChanged handles null gracefully; otherwise, consider falling back to previous codec or skipping the update until populated.

packages/stream_video/lib/src/call/call.dart (9)

304-304: Nice: Call exposes StatsReporter cleanly

Adding a getter for the session’s StatsReporter is a good, minimal surface to let clients consume metrics directly.


1192-1206: LGTM: Wiring session.statsReporter.run into the Call.stats emitter

Good reuse of the session-owned StatsReporter and proper subscription lifecycle via _subscriptions. The SharedEmitter now emits the new tuple type, matching the model changes.


1271-1275: Good addition: trace SFU socket disconnection details

Adding closeCode and closeReason to traces will help correlate reconnection flows.


1284-1287: Good addition: trace SFU socket failure with error

Lightweight diagnostics that will pay off when debugging.


1291-1293: Good addition: trace SFU go-away events

Capturing the go-away reason clarifies migrations at the telemetry layer.


1305-1307: Good addition: trace SFU errors with message

Capturing SFU error text is useful and appropriately scoped.


1411-1414: LGTM: trace reconnect success including strategy

Useful signal to confirm which strategy resolved the reconnection.


1421-1425: LGTM: trace reconnect failure details

Capturing the error string alongside strategy will aid backend telemetry correlation.


310-320: Stats API consumers OK

I searched for legacy CallStats and old field names; the only hits are in protobuf-generated code and the SFU stats‐reporter mapping (which correctly maps the new record fields to protobuf strings). There are no remaining references to CallStats or to stats.publisherStats/stats.subscriberStats in application or UI code—every consumer now uses publisherStatsBundle and subscriberStatsBundle as intended.

dogfooding/lib/screens/call_stats_screen.dart (4)

111-114: LGTM: latency chart switches to new metrics source

Using state.latencyHistory aligns with the new CallMetrics surface.


241-249: LGTM: bitrate rendering using new fields

Bindings to publisher/subscriber bitrateKbps match CallMetrics.


259-267: LGTM: resolution and codecs from new publisher/subscriber stats

Stringifying resolution and codecs via the new stats shape is correct.


273-282: LGTM: environment fields switched to ClientEnvironment

Region/Sdk/WebRTC versions now read from clientEnvironment; aligns with PR goals.

packages/stream_video/lib/src/models/call_stats.dart (2)

43-86: LGTM: CallMetrics shape cleanly encapsulates histories and environment

The new CallMetrics model fits the UI/telemetry requirements and keeps publisher/subscriber details optional.


238-285: LGTM: ClientEnvironment replaces LocalStats

Naming and fields are clearer and directly actionable in the UI.

packages/stream_video/lib/src/call/stats/stats_reporter.dart (2)

35-42: Non-blocking processing is fine here

Returning the raw bundle stream while processing metrics asynchronously is reasonable; the Run stream stays timely, UI can read metrics from the separate state stream.


73-86: LGTM: stats processing aligns with new bundle shape

Deriving outbound media metrics from publisher and inbound from subscriber is correct and consistent with the new PeerConnectionStatsBundle.

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: 0

♻️ Duplicate comments (2)
packages/stream_video/lib/src/call/session/call_session.dart (1)

121-122: Duplicate comment: StatsReporter lifecycle management needed

packages/stream_video/lib/src/call/stats/stats_reporter.dart (1)

18-28: StatsReporter must expose a broadcast stream and clean up resources

The dogfooding call stats screen relies on call.statsReporter?.stream (see dogfooding/lib/screens/call_stats_screen.dart), so StatsReporter needs to:

  • Expose a Stream<CallMetrics?> that updates whenever state changes
  • Properly close resources by overriding dispose()

Locations to update:

  • packages/stream_video/lib/src/call/stats/stats_reporter.dart (around lines 18–28)

Suggested diff:

 class StatsReporter extends StateNotifier<CallMetrics?> {
   StatsReporter({
     required this.rtcManager,
     required this.clientEnvironment,
   }) : super(null);

   final RtcManager rtcManager;
   final ClientEnvironment clientEnvironment;

+  // Broadcast controller to emit metrics updates
+  late final _streamController = StreamController<CallMetrics?>.broadcast();
+
   CallMetrics? get currentMetrics => state;
+  Stream<CallMetrics?> get stream => _streamController.stream;

+  @override
+  set state(CallMetrics? value) {
+    super.state = value;
+    _streamController.add(value);
+  }
+
+  @override
+  void dispose() {
+    _streamController.close();
+    super.dispose();
+  }
 }
🧹 Nitpick comments (2)
packages/stream_video/CHANGELOG.md (1)

4-8: Inconsistent list style detected - consider fixing.

Static analysis flagged inconsistent unordered list styles (mixing dashes and asterisks). While not critical, consider using consistent bullet points for better readability.

-  - The record field names have changed and the element types are different.
+  * The record field names have changed and the element types are different.
packages/stream_video/lib/src/call/stats/stats_reporter.dart (1)

191-229: Platform guard implemented but could be more robust

The platform checks for battery and thermal monitoring are good, but the error handling could be more specific about which platforms and errors are expected.

      try {
        if (batteryCheckAvailable) {
          batteryLevel = await Battery().batteryLevel;
          batteryLevelHistory = <int>[
            ...state?.batteryLevelHistory.reversed.take(49).toList().reversed ??
                [],
            batteryLevel,
          ];
        }
-      } catch (_) {
-        // Ignore battery read failures
+      } on MissingPluginException catch (_) {
+        // Platform doesn't support battery monitoring
+      } catch (e) {
+        // Log unexpected battery read failures for debugging
+        // Ignore to prevent crashes
      }

Similar improvement for thermal status error handling.

📜 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 26d9832 and 75df819.

📒 Files selected for processing (5)
  • packages/stream_video/CHANGELOG.md (1 hunks)
  • packages/stream_video/lib/src/call/call.dart (8 hunks)
  • packages/stream_video/lib/src/call/session/call_session.dart (6 hunks)
  • packages/stream_video/lib/src/call/stats/stats_reporter.dart (7 hunks)
  • packages/stream_video_flutter/lib/src/call_screen/call_diagnostics_content/call_diagnostics_content.dart (2 hunks)
🧰 Additional context used
🪛 markdownlint-cli2 (0.17.2)
packages/stream_video/CHANGELOG.md

8-8: Unordered list style
Expected: asterisk; Actual: dash

(MD004, ul-style)


10-10: Unordered list style
Expected: asterisk; Actual: dash

(MD004, ul-style)


11-11: Unordered list style
Expected: asterisk; Actual: dash

(MD004, ul-style)

⏰ 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). (7)
  • GitHub Check: stream_video_push_notification
  • GitHub Check: stream_video_noise_cancellation
  • GitHub Check: stream_video_flutter
  • GitHub Check: stream_video_screen_sharing
  • GitHub Check: stream_video
  • GitHub Check: analyze
  • GitHub Check: build
🔇 Additional comments (28)
packages/stream_video_flutter/lib/src/call_screen/call_diagnostics_content/call_diagnostics_content.dart (5)

30-33: LGTM! Type update aligns with model refactoring.

The field type changes from CallStats? to PeerConnectionStatsBundle? correctly align with the broader refactoring of stats models in this PR.


36-40: LGTM! Stream subscription type correctly updated.

The subscription type update to use the new record structure with PeerConnectionStatsBundle fields properly aligns with the changes in the stats emitter.


47-48: LGTM! Assignment correctly uses new field names.

The assignments now correctly reference publisherStatsBundle and subscriberStatsBundle from the stats record, matching the updated emission structure.


56-56: Verify dispose order doesn't cause issues.

The dispose order was changed to call super.dispose() after the subscription cancellation. This is generally safer as it ensures cleanup happens before parent disposal, but verify this doesn't cause any timing issues.


137-140: LGTM! Field types updated consistently.

The _CallStatsContent class field types are correctly updated to use PeerConnectionStatsBundle?, maintaining consistency with the data model changes.

packages/stream_video/CHANGELOG.md (2)

10-11: Well-documented breaking changes with clear migration path.

The breaking changes are clearly documented with specific field names and types, making it easy for users to understand what needs to be updated. The migration guidance to use call.stats or call.statsReporter?.currentMetrics is helpful.


14-15: Excellent addition of new functionality.

The addition of StatsReporter with consolidated metrics and battery/thermal tracking significantly enhances the monitoring capabilities of the SDK.

packages/stream_video/lib/src/call/session/call_session.dart (5)

38-38: LGTM! Import added for StatsReporter integration.

The import correctly brings the StatsReporter into scope for the session integration.


143-146: LGTM! Trace method provides useful debugging capability.

The trace method provides a clean passthrough to the internal tracer, allowing external components to add trace information.


414-428: LGTM! Environment construction and StatsReporter initialization.

The ClientEnvironment construction correctly captures SFU URL, SDK version, and platform-specific WebRTC versions. The StatsReporter initialization with the rtcManager and environment is properly positioned after rtcManager creation.


566-568: LGTM! StatsReporter disposal properly implemented.

The StatsReporter disposal is correctly placed in the close method with proper null safety. This addresses the lifecycle concern mentioned in previous reviews.


743-743: LGTM! Updated to use new metrics structure.

The codec lookup now correctly uses the new statsReporter?.currentMetrics?.publisher?.videoCodec path instead of the previous structure.

packages/stream_video/lib/src/call/call.dart (11)

304-304: LGTM! StatsReporter exposure provides clean public API.

The public getter correctly exposes the session's StatsReporter, providing access to consolidated metrics.


310-319: LGTM! Stats stream type correctly updated.

The stats stream now correctly emits the new record structure with PeerConnectionStatsBundle types, aligning with the model refactoring.


866-872: Excellent tracing addition for debugging reconnection issues.

The trace calls for peer connection reconnection events provide valuable debugging information, including the peer connection ID and reconnection strategy.


1192-1206: LGTM! Stats reporter integration correctly wired.

The integration properly checks for statsReporter availability, subscribes to its run stream with the configured interval, and emits the stats to the public _stats stream.


1271-1274: LGTM! SFU socket event tracing enhances debugging.

The trace calls for SFU socket disconnection events provide valuable debugging information with close codes and reasons.


1284-1286: LGTM! SFU socket failure tracing.

The trace call for socket failure events correctly captures error information for debugging.


1290-1292: LGTM! SFU go-away event tracing.

The trace call correctly captures the go-away reason for debugging SFU migration events.


1305-1307: LGTM! SFU error event tracing.

The trace call properly captures SFU error information for debugging reconnection strategies.


1342-1344: LGTM! Reconnection attempt tracing.

The trace call at the start of reconnection attempts provides valuable debugging information with the strategy being used.


1411-1413: LGTM! Reconnection success tracing.

The trace call for successful reconnection provides closure on debugging reconnection flows.


1421-1425: LGTM! Reconnection failure tracing with error details.

The trace call for reconnection failures correctly captures both the strategy and error information for comprehensive debugging.

packages/stream_video/lib/src/call/stats/stats_reporter.dart (5)

3-6: LGTM! Dependencies added for enhanced metrics collection.

The addition of battery_plus, state_notifier, and thermal dependencies correctly supports the new battery and thermal monitoring capabilities.


29-42: LGTM! Run method correctly returns new bundle structure.

The run method properly returns the new record structure with PeerConnectionStatsBundle types and correctly processes stats with tick information.


45-72: LGTM! Stats collection updated to new bundle structure.

The collectStats method correctly constructs and returns PeerConnectionStatsBundle objects instead of the previous CallStats structure.


74-83: LGTM! Stats processing signature correctly updated.

The _processStats method signature correctly accepts the new bundle structure and tick information.


231-248: LGTM! State update logic correctly constructs CallMetrics.

The state update logic properly handles both existing state updates via copyWith and new state creation with all required fields including the new battery and thermal histories.

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: 2

🧹 Nitpick comments (3)
packages/stream_video/lib/src/call/call.dart (3)

1347-1350: Move 'call_reconnect' trace inside the reconnect lock to avoid duplicate starts

Between the locked check and synchronized block, multiple callers can still race and emit duplicate “call_reconnect” entries. Emit inside the lock to represent an actual attempt.

Apply:

-    _session?.trace('call_reconnect', {
-      'strategy': strategy.name,
-    });
-
-    await _callReconnectLock.synchronized(() async {
+    await _callReconnectLock.synchronized(() async {
+      _session?.trace('call_reconnect', {
+        'strategy': strategy.name,
+      });
       _reconnectStrategy = strategy;

1526-1551: Fix typos in log messages: “Awailable” → “Available”

Purely cosmetic but noisy in logs.

Apply:

-    _logger.v(() => '[awaitNetworkAwailable] starting timer');
+    _logger.v(() => '[awaitNetworkAvailable] starting timer');
-      _logger.w(() => '[awaitNetworkAwailable] too late for fast reconnect');
+      _logger.w(() => '[awaitNetworkAvailable] too late for fast reconnect');
-        _logger.w(() => '[awaitNetworkAwailable] timeout');
+        _logger.w(() => '[awaitNetworkAvailable] timeout');
-      _logger.w(() => '[awaitNetworkAwailable] call was left');
+      _logger.w(() => '[awaitNetworkAvailable] call was left');

2660-2663: User-facing message mentions “video” while checking audio permission

Minor copy fix to avoid confusing developers.

Apply:

-      return Result.error('Missing permission to send video');
+      return Result.error('Missing permission to send audio');
📜 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 75df819 and 898f193.

📒 Files selected for processing (1)
  • packages/stream_video/lib/src/call/call.dart (9 hunks)
⏰ 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). (8)
  • GitHub Check: stream_video_screen_sharing
  • GitHub Check: stream_video
  • GitHub Check: stream_video_push_notification
  • GitHub Check: stream_video_flutter
  • GitHub Check: stream_video_noise_cancellation
  • GitHub Check: build
  • GitHub Check: analyze
  • GitHub Check: analyze_legacy_version
🔇 Additional comments (9)
packages/stream_video/lib/src/call/call.dart (9)

304-304: Public getter for StatsReporter looks good

Exposing the session-backed reporter via a nullable getter is reasonable and keeps the surface minimal.


752-755: Trace added for join migration source — LGTM

Capturing the SFU name on forced migration will help triage join instability.


872-877: PC reconnection-needed trace — LGTM

Emitting peerConnectionId and strategy is useful; callback delegates to existing reconnect flow.


1276-1280: Trace on sfu_socket_disconnected — LGTM

Close code and reason captured; aligns with reconnection behavior.


1290-1292: Trace on sfu_socket_failed — LGTM


1296-1298: Trace on sfu_go_away — LGTM


1311-1313: Trace on sfu_error — LGTM


1426-1430: Reconnect failed trace — LGTM

Unrecoverable case captures strategy and error before exiting.


310-320: Stats API migration verified All downstream usages reference publisherStatsBundle/subscriberStatsBundle on PeerConnectionStatsBundle; no remaining CallStats, publisherStats, or subscriberStats references found.

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: 0

♻️ Duplicate comments (2)
packages/stream_video/lib/src/call/call.dart (2)

1418-1421: Only trace reconnect success when actually connected

Currently emitted unconditionally; can produce false positives. Guard on connected state (or the actual method result).

Apply:

-          _session?.trace('call_reconnect_success', {
-            'strategy': _reconnectStrategy.name,
-          });
+          if (state.value.status is CallStatusConnected) {
+            _session?.trace('call_reconnect_success', {
+              'strategy': _reconnectStrategy.name,
+            });
+          }

1198-1212: Gate StatsReporter.run() on active listeners; start/stop on first/last subscriber

This currently always subscribes when the session starts, which conflicts with the PR objective “Process stats only when there is at least one active listener.” Please bridge lazily and cancel when the last listener unsubscribes.

Proposed approach (adapt emitter API names as needed):

-    if (session.statsReporter != null) {
-      _subscriptions.add(
-        _idSessionStats,
-        session.statsReporter!
-            .run(
-          interval:
-              _stateManager.callState.preferences.callStatsReportingInterval,
-        )
-            .listen(
-          (stats) {
-            _stats.emit(stats);
-          },
-        ),
-      );
-    }
+    if (session.statsReporter != null) {
+      // Create/cancel subscription based on listener count.
+      StreamSubscription? _statsSub;
+      void _startIfNeeded() {
+        if (_statsSub != null) return;
+        _statsSub = session.statsReporter!
+            .run(
+              interval: _stateManager
+                  .callState.preferences.callStatsReportingInterval,
+            )
+            .listen(_stats.emit);
+        _subscriptions.add(_idSessionStats, _statsSub!);
+      }
+      void _stopIfIdle() {
+        if (!_stats.hasListeners) {
+          _subscriptions.cancel(_idSessionStats);
+          _statsSub = null;
+        }
+      }
+      // Hook into emitter listener lifecycle.
+      _stats.onListen(_startIfNeeded);
+      _stats.onCancel(_stopIfIdle);
+      // If someone already listens, start immediately.
+      if (_stats.hasListeners) _startIfNeeded();
+    }

If SharedEmitter/MutableSharedEmitterImpl lacks onListen/onCancel/hasListeners, please add them by delegating the underlying subject/stream controller.

Run to confirm available hooks:

#!/bin/bash
rg -nP --type=dart -C3 'class\s+MutableSharedEmitterImpl\b|class\s+SharedEmitter\b'
rg -nP --type=dart -C2 '\b(hasListeners|onListen|onCancel|listenerCount|onFirstListener|onZeroListeners)\b'
🧹 Nitpick comments (8)
packages/stream_video/lib/src/call/call.dart (5)

310-320: Public API emits a named record; consider a small value type for forward-compat

Using a record in the public surface is fine, but adding/removing fields later becomes a source/binary compatibility risk for downstream destructuring. Consider introducing a tiny value class (e.g., CallPeerConnectionStats) and return that via SharedEmitter, or mark this API @experimental.


751-755: Enrich join-migration trace with attempt and callCid

Adds helpful signal for diagnosing repeated failures and correlating events.

Apply:

-              _session?.trace('call_join_migrate', {
-                'migrateFrom': sfuName,
-              });
+              _session?.trace('call_join_migrate', {
+                'migrateFrom': sfuName,
+                'attempt': attempt,
+                'callCid': callCid.value,
+              });

1277-1314: SFU socket/error traces: include stable identifiers for better correlation

Consider adding sessionId and sfuName to each trace, and for failures include a machine-parseable errorCode when available. This improves cross-system joins.

Apply (illustrative):

-        _session?.trace('sfu_socket_disconnected', {
-          'closeCode': sfuEvent.reason.closeCode,
-          'closeReason': sfuEvent.reason.closeReason,
-        });
+        _session?.trace('sfu_socket_disconnected', {
+          'sessionId': _session?.sessionId,
+          'sfuName': _session?.config.sfuName,
+          'closeCode': sfuEvent.reason.closeCode,
+          'closeReason': sfuEvent.reason.closeReason,
+        });

Repeat similarly for sfu_socket_failed, sfu_go_away, and sfu_error.


1369-1376: Add attempt counters to call_reconnect trace

You already track attempt locally; include it in the trace for observability.

Apply:

-        _session?.trace('call_reconnect', {
-          'strategy': _reconnectStrategy.name,
-        });
+        _session?.trace('call_reconnect', {
+          'strategy': _reconnectStrategy.name,
+          'attempt': attempt,
+        });

1429-1432: Include structured error data in reconnect_failed trace

Add error type/category/status code to help aggregation (e.g., OpenApiError.apiError.code).

Example:

-              _session?.trace('call_reconnect_failed', {
-                'strategy': _reconnectStrategy.name,
-                'error': error.toString(),
-              });
+              _session?.trace('call_reconnect_failed', {
+                'strategy': _reconnectStrategy.name,
+                'error': error.toString(),
+                'errorType': error.runtimeType.toString(),
+                'statusCode': (error is OpenApiError) ? error.apiError.code : null,
+              });
packages/stream_video/lib/src/call/stats/trace_record.dart (1)

40-52: Enable clearing fields to null in copyWith (current impl prevents setting null).

Using ?? means copyWith(id: null) won’t clear the field—it keeps the old value. If you ever need to null-out id/data, switch to a sentinel-based approach.

Apply within this method:

-  TraceRecord copyWith({
-    String? tag,
-    String? id,
-    dynamic data,
-    int? timestamp,
-  }) {
-    return TraceRecord(
-      tag: tag ?? this.tag,
-      id: id ?? this.id,
-      data: data ?? this.data,
-      timestamp: timestamp ?? this.timestamp,
-    );
-  }
+  TraceRecord copyWith({
+    Object? tag = _u,
+    Object? id = _u,
+    Object? data = _u,
+    Object? timestamp = _u,
+  }) {
+    return TraceRecord(
+      tag: tag != _u ? tag as String : this.tag,
+      id: id != _u ? id as String? : this.id,
+      data: data != _u ? data : this.data,
+      timestamp: timestamp != _u ? timestamp as int : this.timestamp,
+    );
+  }

Add this once inside the class (outside the method):

static const Object _u = Object();
packages/stream_video/lib/src/call/stats/tracer.dart (1)

29-33: Accept Iterable to avoid unnecessary list allocations.

Let traceMultiple take Iterable<TraceRecord> so callers can pass mapped iterables without toList().

-  void traceMultiple(List<TraceRecord> entries) {
+  void traceMultiple(Iterable<TraceRecord> entries) {
     if (!_enabled) return;
     _buffer.addAll(entries);
   }
packages/stream_video/lib/src/call/session/call_session_factory.dart (1)

36-37: Prefer Iterable for flexibility and fewer copies.

Align parameter type with a batched API and callers that may pass mapped views; keeps the default the same.

-    List<TraceRecord> leftoverTraceRecords = const [],
+    Iterable<TraceRecord> leftoverTraceRecords = const [],
📜 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 898f193 and 53ff7a8.

📒 Files selected for processing (4)
  • packages/stream_video/lib/src/call/call.dart (9 hunks)
  • packages/stream_video/lib/src/call/session/call_session_factory.dart (3 hunks)
  • packages/stream_video/lib/src/call/stats/trace_record.dart (1 hunks)
  • packages/stream_video/lib/src/call/stats/tracer.dart (1 hunks)
⏰ 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). (6)
  • GitHub Check: build
  • GitHub Check: analyze
  • GitHub Check: stream_video_flutter
  • GitHub Check: stream_video_noise_cancellation
  • GitHub Check: stream_video
  • GitHub Check: stream_video_push_notification
🔇 Additional comments (4)
packages/stream_video/lib/src/call/call.dart (2)

304-305: Expose statsReporter: LGTM

Getter is correctly nullable and scoped to the active session.


871-878: Reconnection-needed instrumentation and leftover trace carryover: LGTM

Carrying leftoverTraceRecords and tracing pc_reconnection_needed with strategy and peerConnectionId will help field triage.

packages/stream_video/lib/src/call/session/call_session_factory.dart (2)

9-9: Import looks correct.


59-69: Fix ‘create’ trace payload and guard previous‐session ID

  • Change trace('create', {'url': sfuName}) to use sfuUrl (trace('create', {'url': sfuUrl})).
  • In traceMultiple, only override r.id when sessionSeq > 0 (e.g. r.copyWith(id: '${sessionSeq-1}')) and leave it untouched otherwise.

Copy link
Contributor

@renefloor renefloor left a comment

Choose a reason for hiding this comment

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

Let's release this as breaking change together with the callkit change, so we don't have to release a breaking change only for stats.

Would be good to add some unit tests, I think statsReporter should be very doable for testing.

# Conflicts:
#	dogfooding/lib/screens/call_stats_screen.dart
#	packages/stream_video/CHANGELOG.md
#	packages/stream_video/lib/src/call/call.dart
#	packages/stream_video/lib/src/call/stats/stats_reporter.dart
#	packages/stream_video/lib/src/call_state.dart
#	packages/stream_video/lib/src/models/call_stats.dart
#	packages/stream_video_flutter/lib/src/call_screen/call_diagnostics_content/call_diagnostics_content.dart
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

♻️ Duplicate comments (4)
packages/stream_video/lib/src/call/session/call_session_factory.dart (1)

36-36: Document the leftoverTraceRecords parameter.

As noted in a previous review, the purpose and expected content of leftoverTraceRecords is not immediately clear. Add a doc comment explaining that this parameter carries trace records from a previous session into the new session context.

     required StatsOptions statsOptions,
     required StreamVideo streamVideo,
     ClientPublishOptions? clientPublishOptions,
+    /// Trace records from a previous session to be carried forward into the new session.
+    /// Each record will be re-emitted with an id of `sessionSeq - 1`.
     List<TraceRecord> leftoverTraceRecords = const [],
   }) async {
packages/stream_video/lib/src/call/call.dart (2)

1250-1266: Verify: Stats collection still runs unconditionally (doesn't match PR objective).

Per FLU-205 objective: "Process stats only when someone listens to the stats stream." Currently, session.statsReporter.run(...) is always subscribed when the session starts, regardless of whether _stats has active listeners.

To align with the PR objective, consider:

  • Starting the reporter only when _stats gains its first subscriber
  • Canceling when the last listener unsubscribes
  • Reattaching on subsequent listeners

Check if SharedEmitter / MutableSharedEmitterImpl exposes listener hooks:

#!/bin/bash
# Search for listener management APIs in SharedEmitter
rg -nP --type=dart -C3 'class\s+(MutableSharedEmitterImpl|SharedEmitter)\b' | head -50

# Check for onListen/onCancel/hasListeners methods
rg -nP --type=dart -C2 '\b(onListen|onCancel|hasListeners|listenerCount)\b' packages/stream_video/lib/src/shared_emitter.dart

1474-1477: Verify: Reconnect success traced unconditionally (may yield false positives).

The call_reconnect_success trace is emitted after the reconnect switch, even if the underlying methods returned without throwing but didn't actually connect. This can produce false positives.

Consider guarding on actual connection state before tracing success, or use the result from the reconnect methods.

Apply this diff to guard the success trace:

-          _session?.trace('call_reconnect_success', {
-            'strategy': _reconnectStrategy.name,
-          });
+          if (state.value.status is CallStatusConnected) {
+            _session?.trace('call_reconnect_success', {
+              'strategy': _reconnectStrategy.name,
+            });
+          }
packages/stream_video/lib/src/call/stats/stats_reporter.dart (1)

18-28: Suggest: Expose a public stream getter for external consumers.

The dogfooding UI and other consumers expect to read call.statsReporter?.stream, but StatsReporter currently only exposes currentMetrics. Consider adding a broadcast Stream<CallMetrics?> and forwarding state changes.

Apply this pattern:

 class StatsReporter extends StateNotifier<CallMetrics?> {
   StatsReporter({
     required this.rtcManager,
     required this.clientEnvironment,
   }) : super(null);

   final RtcManager rtcManager;
   final ClientEnvironment clientEnvironment;
+  final _metricsController = StreamController<CallMetrics?>.broadcast();

   CallMetrics? get currentMetrics => state;
+  Stream<CallMetrics?> get stream => _metricsController.stream;

+  @override
+  set state(CallMetrics? value) {
+    super.state = value;
+    _metricsController.add(value);
+  }
+
+  @override
+  void dispose() {
+    _metricsController.close();
+    super.dispose();
+  }
🧹 Nitpick comments (5)
packages/stream_video/CHANGELOG.md (3)

14-23: Make migration explicit (old/new names, types, listener gating).

Great high‑level notes, but devs need copy‑pasteable guidance. Please add:

  • Explicit mapping: CallStats → PeerConnectionStatsBundle; record fields publisherStats → publisherStatsBundle, subscriberStats → subscriberStatsBundle.
  • Public types: CallMetrics for currentMetrics; LocalStats → ClientEnvironment.
  • Note: stats are processed only when there’s at least one listener to statsReporter.stream.

Suggested changelog patch:

 * `Call.stats` payload changed. It now emits
   `({ PeerConnectionStatsBundle publisherStatsBundle, PeerConnectionStatsBundle subscriberStatsBundle })`
   instead of the previous
   `({ CallStats publisherStats, CallStats subscriberStats })`.
   - The record field names have changed and the element types are different.
+  - Migration example (Dart):
+    ```dart
+    // Before
+    final stats = await call.stats.first;
+    final pub = stats.publisherStats;
+    final sub = stats.subscriberStats;
+    // After
+    final stats = await call.stats.first;
+    final pub = stats.publisherStatsBundle;
+    final sub = stats.subscriberStatsBundle;
+    ```
 * Stats-related fields were removed from `CallState` (e.g., `publisherStats`, `subscriberStats`, `latencyHistory`).
-  - Use `call.stats` for periodic WebRTC stats updates, or
-  - Access `call.statsReporter?.currentMetrics` for the latest aggregated metrics instead.
+  - Use `call.stats` for periodic WebRTC stats updates, or
+  - Access `call.statsReporter?.currentMetrics` (type: `CallMetrics`) for the latest aggregated metrics.
+  - Internal refactor: `LocalStats` renamed to `ClientEnvironment`.
+  - Performance: stats are computed/emitted only when there is at least one active listener to `call.statsReporter?.stream`.

24-27: Clarify currentMetrics shape and safe access.

Name the type and list key fields so consumers know what to expect. Add a short usage snippet.

 * `StatsReporter` is now exposed on `Call` as `call.statsReporter`, providing `currentMetrics` — a consolidated view of publisher/subscriber WebRTC quality, client environment, and rolling histories (latency, battery level, thermal status).
-* Battery level and device thermal status are now tracked and available via `call.statsReporter?.currentMetrics`.
+* `currentMetrics` type: `CallMetrics` with (non‑exhaustive): 
+  `publisherStatsBundle`, `subscriberStatsBundle`, `latencyHistory`,
+  `batteryLevelHistory`, `thermalStatusHistory`, `initialBatteryLevel`,
+  `clientEnvironment`.
+* Battery level and device thermal status are now tracked and available via `call.statsReporter?.currentMetrics`.
+* Example:
+  ```dart
+  final metrics = call.statsReporter?.currentMetrics;
+  final battery = metrics?.batteryLevelHistory.lastOrNull;
+  final thermal = metrics?.thermalStatusHistory.lastOrNull;
+  ```

28-30: Call out single getStats() consolidation and cadence.

Under “Changed”, note the internal consolidation and confirm emission cadence hasn’t changed to avoid surprises.

 * `Call.stats` continues to emit periodically, but the record field names/types changed as noted under breaking changes.
+* Stats collection is consolidated into a single `RTCPeerConnection.getStats()` call for local + SFU data.
+* Emission cadence is unchanged (verify if different, document the new interval).
dogfooding/lib/screens/stats_battery_chart.dart (1)

50-51: Consider dynamic maxX based on data length.

The chart's maxX is hardcoded to 20, but batteryLevelHistory can be any length. If the history contains more than 20 points, data will be compressed; if fewer, empty space will appear. Consider setting maxX dynamically:

           minX: 0,
-          maxX: 20,
+          maxX: (batteryLevelHistory.length - 1).toDouble().clamp(1, 20),
           minY: 0,

This caps the visible window at 20 points for readability while adapting to shorter histories.

packages/stream_video/lib/src/call/stats/stats_reporter.dart (1)

200-239: Battery and thermal reads correctly guarded, but consider dependency injection for testing.

Platform checks and try/catch blocks now prevent crashes on unsupported platforms. However, for testability, consider injecting Battery and Thermal instances in the constructor (as noted in a past comment). This would allow mocking in tests.

Based on learnings

Example:

 class StatsReporter extends StateNotifier<CallMetrics?> {
   StatsReporter({
     required this.rtcManager,
     required this.clientEnvironment,
+    Battery? battery,
+    Thermal? thermal,
-  }) : super(null);
+  }) : _battery = battery ?? Battery(),
+       _thermal = thermal ?? Thermal(),
+       super(null);

   final RtcManager rtcManager;
   final ClientEnvironment clientEnvironment;
+  final Battery _battery;
+  final Thermal _thermal;

   ...

   // In _processStats:
-  batteryLevel = await Battery().batteryLevel;
+  batteryLevel = await _battery.batteryLevel;

-  final thermalStatus = await Thermal().thermalStatus;
+  final thermalStatus = await _thermal.thermalStatus;
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • 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 53ff7a8 and 4be211f.

📒 Files selected for processing (12)
  • dogfooding/lib/screens/call_stats_screen.dart (3 hunks)
  • dogfooding/lib/screens/stats_battery_chart.dart (1 hunks)
  • dogfooding/lib/screens/stats_thermal_chart.dart (1 hunks)
  • packages/stream_video/CHANGELOG.md (1 hunks)
  • packages/stream_video/lib/src/call/call.dart (9 hunks)
  • packages/stream_video/lib/src/call/session/call_session.dart (6 hunks)
  • packages/stream_video/lib/src/call/session/call_session_factory.dart (3 hunks)
  • packages/stream_video/lib/src/call/state/mixins/state_lifecycle_mixin.dart (0 hunks)
  • packages/stream_video/lib/src/call/stats/stats_reporter.dart (8 hunks)
  • packages/stream_video/lib/src/call_state.dart (0 hunks)
  • packages/stream_video/lib/src/models/call_stats.dart (6 hunks)
  • packages/stream_video_flutter/lib/src/call_screen/call_diagnostics_content/call_diagnostics_content.dart (2 hunks)
💤 Files with no reviewable changes (2)
  • packages/stream_video/lib/src/call_state.dart
  • packages/stream_video/lib/src/call/state/mixins/state_lifecycle_mixin.dart
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/stream_video/lib/src/call/session/call_session.dart
⏰ 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). (7)
  • GitHub Check: build
  • GitHub Check: analyze
  • GitHub Check: stream_video_screen_sharing
  • GitHub Check: stream_video_push_notification
  • GitHub Check: stream_video_noise_cancellation
  • GitHub Check: stream_video
  • GitHub Check: stream_video_flutter
🔇 Additional comments (13)
packages/stream_video_flutter/lib/src/call_screen/call_diagnostics_content/call_diagnostics_content.dart (1)

56-59: Improved dispose order.

Canceling the subscription before calling super.dispose() is the correct pattern and prevents potential issues with ongoing async operations.

dogfooding/lib/screens/call_stats_screen.dart (1)

167-202: New thermal and battery charts integration looks good.

The integration of StatsThermalChart and StatsBatteryChart with the new CallMetrics data model is well-structured. The battery drain calculation correctly handles null values, and both charts are properly bound to their respective history fields.

dogfooding/lib/screens/stats_thermal_chart.dart (1)

70-70: Confirmed Flutter SDK constraint covers withValues API
The project’s pubspec.yaml requires Flutter “>=3.32.0”, which is above the 3.27 version that introduced withValues. No changes are needed.

packages/stream_video/lib/src/call/call.dart (3)

311-311: LGTM! StatsReporter public getter added.

Correctly exposes the session's stats reporter for external consumers (UI/dogfooding).


790-793: LGTM! Migration trace added.

Correctly logs migration attempts for debugging.


910-917: LGTM! Trace continuity and reconnection instrumentation.

Carrying forward trace records and logging reconnection events ensures complete diagnostic context across session boundaries.

packages/stream_video/lib/src/models/call_stats.dart (3)

9-40: LGTM! PeerConnectionStatsBundle correctly renamed and extended.

The rename from CallStats to PeerConnectionStatsBundle clarifies the scope, and the new stats field provides raw stats access. Equality and hashCode are correctly updated.


42-85: LGTM! CallMetrics aggregates all stats and history.

Clean design that consolidates publisher/subscriber stats, environment, and temporal histories (latency, battery, thermal) in one immutable class. The copyWith method supports efficient state updates.


237-283: LGTM! ClientEnvironment renamed and enhanced.

The rename from LocalStats to ClientEnvironment better describes the data, and the added empty() factory, copyWith, equality, and hashCode follow Dart conventions.

packages/stream_video/lib/src/call/stats/stats_reporter.dart (4)

29-45: LGTM! Stream API correctly returns bundles with tick counter.

The run method now returns a stream of PeerConnectionStatsBundle records and passes the tick to _processStats for periodic battery/thermal sampling.


47-77: LGTM! collectStats correctly constructs PeerConnectionStatsBundle.

Properly handles nullable publisher and creates bundles with all required fields (peerType, stats, printable, raw).


79-195: LGTM! Stats processing correctly updated for bundle types.

Safe null handling (state?.latencyHistory), correct extraction of codec/resolution/jitter from the new PeerConnectionStatsBundle structure.


241-259: LGTM! State update correctly uses copyWith or creates new CallMetrics.

Immutable state management pattern correctly applied. All fields (publisher, subscriber, clientEnvironment, histories, initialBatteryLevel) are properly included.

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

♻️ Duplicate comments (1)
packages/stream_video/lib/src/call/stats/stats_reporter.dart (1)

18-35: Missing stream getter causes compilation failure in UI.

The dogfooding UI (dogfooding/lib/screens/call_stats_screen.dart:25) references call.statsReporter?.stream, but StatsReporter does not expose a Stream<CallMetrics?> property. This will cause a compilation error.

Add a broadcast stream that mirrors state changes:

 class StatsReporter extends StateNotifier<CallMetrics?> {
   StatsReporter({
     required this.rtcManager,
     required this.clientEnvironment,
     Battery? battery,
     Thermal? thermal,
-  }) : super(null) {
+  })  : _metricsController = StreamController<CallMetrics?>.broadcast(),
+        super(null) {
     _battery = battery ?? Battery();
     _thermal = thermal ?? Thermal();
   }
 
   final RtcManager rtcManager;
   final ClientEnvironment clientEnvironment;
 
+  final StreamController<CallMetrics?> _metricsController;
   late Battery _battery;
   late Thermal _thermal;
 
   CallMetrics? get currentMetrics => state;
+  Stream<CallMetrics?> get stream => _metricsController.stream;
+
+  @override
+  set state(CallMetrics? value) {
+    super.state = value;
+    _metricsController.add(value);
+  }
+
+  @override
+  void dispose() {
+    _metricsController.close();
+    super.dispose();
+  }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • 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 fe795a7 and 1e99baa.

📒 Files selected for processing (3)
  • dogfooding/lib/screens/call_stats_screen.dart (3 hunks)
  • packages/stream_video/lib/src/call/session/call_session_factory.dart (5 hunks)
  • packages/stream_video/lib/src/call/stats/stats_reporter.dart (8 hunks)
⏰ 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). (7)
  • GitHub Check: build
  • GitHub Check: analyze
  • GitHub Check: stream_video_push_notification
  • GitHub Check: stream_video
  • GitHub Check: stream_video_noise_cancellation
  • GitHub Check: stream_video_flutter
  • GitHub Check: analyze_legacy_version
🔇 Additional comments (8)
packages/stream_video/lib/src/call/session/call_session_factory.dart (3)

1-1: Import added to support max function.

The dart:math import is correctly added to support the max(0, sessionSeq - 1) clamping operation used in the trace ID calculation at line 81.


27-41: Documentation clearly explains leftoverTraceRecords parameter.

The updated documentation explains the purpose of leftoverTraceRecords and its relationship to previous sessions. This addresses the previous review comment requesting better documentation.


77-85: Negative trace ID issue resolved with max clamping.

The max(0, sessionSeq - 1) expression at line 81 correctly prevents negative trace IDs when sessionSeq is 0, addressing the concern raised in the previous review.

dogfooding/lib/screens/call_stats_screen.dart (2)

30-34: LGTM! Appropriate null handling.

The loading state is correctly guarded with a null check and displays a progress indicator while metrics are unavailable.


104-277: LGTM! New monitoring sections are well structured.

The thermal state, battery level, and call performance sections are properly implemented with appropriate charts and metrics. Note: A past review incorrectly flagged lines 104-138 as a duplicate of the latency section, but these are distinct new sections for thermal and battery monitoring.

packages/stream_video/lib/src/call/stats/stats_reporter.dart (3)

19-27: LGTM! Dependency injection enables testing.

Battery and Thermal are now injectable via optional constructor parameters, making the class testable. This addresses the previous review feedback.

Based on past review comments


208-247: LGTM! Proper platform checks and error handling for battery/thermal.

The battery and thermal readings are now guarded with platform availability checks and wrapped in try-catch blocks to prevent crashes on unsupported platforms or transient failures. This addresses the previous review feedback.

Based on past review comments


249-266: LGTM! State update logic correctly preserves initialBatteryLevel.

The state update properly uses copyWith for existing state and creates a new CallMetrics when state is null, ensuring initialBatteryLevel is captured only once at the start of the call.

@Brazol Brazol merged commit 0623534 into main Oct 1, 2025
14 of 15 checks passed
@Brazol Brazol deleted the chore/call-stats-improvements branch October 1, 2025 14:08
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