Skip to content

Commit 8287d9e

Browse files
authored
feat(stats): local call stats added + stats documentation (#601)
* local call stats added + stats documentation * typos * tweaks
1 parent 0a66a73 commit 8287d9e

File tree

11 files changed

+143
-51
lines changed

11 files changed

+143
-51
lines changed
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
---
2+
title: Call Statistics
3+
slug: /call-statistics
4+
description: How to listen and leverage call statistics
5+
---
6+
7+
There are two sources of statistics you can use to monitor the performance of your calls:
8+
- `Call.stats` stream that provides real-time webRTC statistics
9+
- `CallState` properties that contain some of the webRTC statistics processed and more useful for display purposes
10+
11+
## Stats stream
12+
13+
If you want to tap in directly into the stream of webRTC stats you can do this with `stats` steam inside `Call` object. It provides a stream of [`CallStats`](https://github.com/GetStream/stream-video-flutter/blob/main/packages/stream_video/lib/src/models/call_stats.dart) objects for publisher and subscriber connections.
14+
We provide those statistics in three ways for ease of use:
15+
16+
* `raw` - raw stats as they come from the webRTC
17+
* `printable` - representation of the stats that can be easily printed to the console or as a block of text
18+
* `stats` - webRTC stats but in a structured form
19+
20+
## CallState properties
21+
22+
You can also access more processed data with useful information about the call. This data is available in [`CallState`](https://github.com/GetStream/stream-video-flutter/blob/main/packages/stream_video/lib/src/call_state.dart) object. Here are some of the properties that you can use:
23+
24+
`publisherStats` and `subscriberStats` - objects that contain the following data:
25+
* `latency` - the time it takes to deliver the data between the server and the app
26+
* `jitterInMs` - the variation in the delay of receiving packets of data over a network
27+
* `bitrateKbps` - the rate at which data is transmitted from the app to the server (publisher) or from the server to the app (subscriber)
28+
29+
`localStats` - object that contain the following data:
30+
* `sfu` - the server we are connected to
31+
* `sdkVersion` - the version of the Stream SDK
32+
* `webRtcVersion` - the version of the webRTC
33+
34+
`latencyHistory` - array of latency values for the last 10 seconds
35+
36+
## Example usage
37+
38+
You can check the example of the stats screen in our [demo app](https://github.com/GetStream/stream-video-flutter/blob/main/dogfooding/lib/screens/call_stats_screen.dart)
39+
40+
![Sample Call Stats screen](../assets/core_concepts/stats_screen.jpeg)
41+
145 KB
Loading

dogfooding/lib/screens/call_screen.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ class _CallScreenState extends State<CallScreen> {
124124

125125
@override
126126
Widget build(BuildContext context) {
127+
// ignore: deprecated_member_use
127128
return WillPopScope(
128129
onWillPop: () async {
129130
return !Navigator.of(context).userGestureInProgress;

dogfooding/lib/screens/call_stats_screen.dart

Lines changed: 31 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ class CallStatsScreen extends StatelessWidget {
111111
const Padding(
112112
padding: EdgeInsets.symmetric(horizontal: 16.0),
113113
child: Text(
114-
'Very high latency values may reduce call quality, cause lag, and make the call less enjoyable.',
114+
'Review the key data points below to assess call performance.',
115115
style: TextStyle(color: Colors.white),
116116
),
117117
),
@@ -131,8 +131,16 @@ class CallStatsScreen extends StatelessWidget {
131131
value: state.publisherStats?.jitterInMs,
132132
),
133133
StatsItem(
134-
title: 'Publish quality drop reason',
135-
value: state.publisherStats?.qualityDropReason,
134+
title: 'Region',
135+
value: state.localStats?.sfu,
136+
),
137+
StatsItem(
138+
title: 'SDK Version',
139+
value: state.localStats?.sdkVersion,
140+
),
141+
StatsItem(
142+
title: 'WebRTC Version',
143+
value: state.localStats?.webRtcVersion,
136144
),
137145
StatsItem(
138146
title: 'Publish bitrate',
@@ -205,24 +213,28 @@ class StatsItem extends StatelessWidget {
205213
padding: const EdgeInsets.all(16),
206214
child: Row(
207215
children: [
208-
Column(
209-
crossAxisAlignment: CrossAxisAlignment.start,
210-
children: [
211-
Text(
212-
title,
213-
style: theme.textTheme.footnote.apply(
214-
color: AppColorPalette.secondaryText,
216+
Expanded(
217+
child: Column(
218+
crossAxisAlignment: CrossAxisAlignment.start,
219+
children: [
220+
Text(
221+
title,
222+
style: theme.textTheme.footnote.apply(
223+
color: AppColorPalette.secondaryText,
224+
),
215225
),
216-
),
217-
const SizedBox(height: 4),
218-
Text(
219-
value ?? '',
220-
style: theme.textTheme.title3,
221-
),
222-
],
226+
const SizedBox(height: 4),
227+
Text(
228+
value ?? '',
229+
style: theme.textTheme.title3,
230+
),
231+
],
232+
),
223233
),
224-
const Spacer(),
225-
if (trailing != null) trailing!,
234+
if (trailing != null) ...[
235+
const Spacer(),
236+
trailing!,
237+
],
226238
],
227239
),
228240
);

packages/stream_video/CHANGELOG.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,14 @@
1+
## 0.3.3
2+
3+
* Added `StreamCallType` class that replaces depricated String `type` parameter
4+
* Exapanded `CallStats` class with more structured WebRTC statistics as `stats` field
5+
* Changed `raw` statistics in `CallStats` to be of a Map<Stirng, dynamic> type
6+
* Added `publisherStats`, `subsciberStats` and `latencyHistory` to the `CallState` that hold some of the processed statistcs
7+
8+
Bug fixes
9+
* Fixes incoming call behavior when both CallKit and Stream incoming screen component is used
10+
* Fixes the issue on Android that caused missed call notification when ringing with reused call id
11+
112
## 0.3.2
213

314
🐞 Fixed

packages/stream_video/lib/src/call/call.dart

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import 'package:meta/meta.dart';
99
import 'package:rxdart/rxdart.dart';
1010

1111
import '../../stream_video.dart';
12+
import '../../version.g.dart';
1213
import '../action/internal/lifecycle_action.dart';
1314
import '../coordinator/models/coordinator_models.dart';
1415
import '../errors/video_error_composer.dart';
@@ -600,8 +601,19 @@ class Call {
600601
}),
601602
);
602603

603-
_stateManager
604-
.lifecycleCallSessionStart(CallSessionStart(session.sessionId));
604+
var localStats = state.value.localStats ?? LocalStats.empty();
605+
localStats = localStats.copyWith(
606+
sfu: session.config.sfuUrl,
607+
sdkVersion: streamVideoVersion,
608+
webRtcVersion:
609+
CurrentPlatform.isAndroid ? androidWebRTCVersion : iosWebRTCVersion,
610+
);
611+
612+
_stateManager.lifecycleCallSessionStart(
613+
CallSessionStart(session.sessionId),
614+
localStats: localStats,
615+
);
616+
605617
final result = await session.start();
606618
_logger.v(() => '[startSession] completed: $result');
607619
return result;

packages/stream_video/lib/src/call/state/mixins/state_lifecycle_mixin.dart

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,9 @@ mixin StateLifecycleMixin on StateNotifier<CallState> {
6565
DisconnectReason.ended(),
6666
),
6767
sessionId: '',
68+
localStats: LocalStats.empty(),
69+
publisherStats: PeerConnectionStats.empty(),
70+
subscriberStats: PeerConnectionStats.empty(),
6871
);
6972
}
7073

@@ -185,6 +188,9 @@ mixin StateLifecycleMixin on StateNotifier<CallState> {
185188
),
186189
sessionId: '',
187190
callParticipants: const [],
191+
localStats: LocalStats.empty(),
192+
publisherStats: PeerConnectionStats.empty(),
193+
subscriberStats: PeerConnectionStats.empty(),
188194
);
189195
}
190196

@@ -197,6 +203,9 @@ mixin StateLifecycleMixin on StateNotifier<CallState> {
197203
const DisconnectReason.timeout(),
198204
),
199205
sessionId: '',
206+
localStats: LocalStats.empty(),
207+
publisherStats: PeerConnectionStats.empty(),
208+
subscriberStats: PeerConnectionStats.empty(),
200209
);
201210
}
202211

@@ -226,15 +235,20 @@ mixin StateLifecycleMixin on StateNotifier<CallState> {
226235
status: CallStatus.disconnected(
227236
DisconnectReason.failure(stage.error),
228237
),
238+
localStats: LocalStats.empty(),
239+
publisherStats: PeerConnectionStats.empty(),
240+
subscriberStats: PeerConnectionStats.empty(),
229241
);
230242
}
231243

232244
void lifecycleCallSessionStart(
233-
CallSessionStart action,
234-
) {
245+
CallSessionStart action, {
246+
LocalStats? localStats,
247+
}) {
235248
_logger.d(() => '[lifecycleCallSessionStart] state: $state');
236249
state = state.copyWith(
237250
sessionId: action.sessionId,
251+
localStats: localStats,
238252
//status: CallStatus.connecting(),
239253
);
240254
}
@@ -260,16 +274,14 @@ mixin StateLifecycleMixin on StateNotifier<CallState> {
260274
required List<int> latencyHistory,
261275
PeerConnectionStats? publisherStats,
262276
PeerConnectionStats? subscriberStats,
263-
LocalStats? localStats,
264277
}) {
265278
_logger.d(
266279
() =>
267-
'[lifecycleCallStats] publisherStats: $publisherStats, subscriberStats: $subscriberStats, localStats: $localStats, state: $state',
280+
'[lifecycleCallStats] publisherStats: $publisherStats, subscriberStats: $subscriberStats, state: $state',
268281
);
269282
state = state.copyWith(
270283
publisherStats: publisherStats,
271284
subscriberStats: subscriberStats,
272-
localStats: localStats,
273285
latencyHistory: latencyHistory,
274286
);
275287
}

packages/stream_video/lib/src/models/call_stats.dart

Lines changed: 23 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -164,48 +164,48 @@ class MediaStatsInfo {
164164
@immutable
165165
class LocalStats {
166166
const LocalStats({
167-
required this.resolution,
168-
required this.availableResolutions,
169-
required this.maxResolution,
170167
required this.sfu,
171-
required this.os,
172168
required this.sdkVersion,
173-
required this.deviceModel,
169+
required this.webRtcVersion,
174170
});
175171

172+
factory LocalStats.empty() => const LocalStats(
173+
sfu: '',
174+
sdkVersion: '',
175+
webRtcVersion: '',
176+
);
177+
176178
final String sfu;
177-
final String os;
178179
final String sdkVersion;
179-
final String deviceModel;
180-
final String? resolution;
181-
final List<String>? availableResolutions;
182-
final String? maxResolution;
180+
final String webRtcVersion;
183181

184182
@override
185183
String toString() {
186-
return 'LocalStats{resolution: $resolution, availableResolutions: $availableResolutions, maxResolution: $maxResolution, sfu: $sfu, os: $os, sdkVersion: $sdkVersion, deviceModel: $deviceModel}';
184+
return 'LocalStats{sfu: $sfu, sdkVersion: $sdkVersion, webRtcVersion: $webRtcVersion}';
185+
}
186+
187+
LocalStats copyWith({
188+
String? sfu,
189+
String? sdkVersion,
190+
String? webRtcVersion,
191+
}) {
192+
return LocalStats(
193+
sfu: sfu ?? this.sfu,
194+
sdkVersion: sdkVersion ?? this.sdkVersion,
195+
webRtcVersion: webRtcVersion ?? this.webRtcVersion,
196+
);
187197
}
188198

189199
@override
190200
bool operator ==(Object other) =>
191201
identical(this, other) ||
192202
other is LocalStats &&
193203
runtimeType == other.runtimeType &&
194-
resolution == other.resolution &&
195-
availableResolutions == other.availableResolutions &&
196-
maxResolution == other.maxResolution &&
197204
sfu == other.sfu &&
198-
os == other.os &&
199205
sdkVersion == other.sdkVersion &&
200-
deviceModel == other.deviceModel;
206+
webRtcVersion == other.webRtcVersion;
201207

202208
@override
203209
int get hashCode =>
204-
resolution.hashCode ^
205-
availableResolutions.hashCode ^
206-
maxResolution.hashCode ^
207-
sfu.hashCode ^
208-
os.hashCode ^
209-
sdkVersion.hashCode ^
210-
deviceModel.hashCode;
210+
sfu.hashCode ^ sdkVersion.hashCode ^ webRtcVersion.hashCode;
211211
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
// This file is generated. Do not manually edit.
22
/// Current package version.
33
const String streamVideoVersion = '0.3.2';
4+
const String androidWebRTCVersion = 'libwebrtc-m114.5735.05';
5+
const String iosWebRTCVersion = 'libwebrtc-m114.5735.08';

packages/stream_video_flutter/lib/src/call_participants/call_participants.dart

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,9 @@ class _StreamCallParticipantsState extends State<StreamCallParticipants> {
116116
super.didUpdateWidget(oldWidget);
117117

118118
if (!const ListEquality<CallParticipantState>().equals(
119-
widget.participants.toList(), oldWidget.participants.toList())) {
119+
widget.participants.toList(),
120+
oldWidget.participants.toList(),
121+
)) {
120122
_recalculateParticipants();
121123
}
122124
}

0 commit comments

Comments
 (0)