Skip to content

Commit ebc5914

Browse files
authored
fix(llc): catch stats reporting exceptions (#1049)
* catch stats reporting exceptions * changelog
1 parent a2b51c1 commit ebc5914

File tree

2 files changed

+132
-120
lines changed

2 files changed

+132
-120
lines changed

packages/stream_video/CHANGELOG.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
1-
## Unregistered
1+
## Unreleased
22

3+
🐞 Fixed
4+
* Handled SFU stats reporting failures gracefully
5+
*
36
✅ Added
47
* Added option to configure android audio configuration when initializing `StreamVideo` instance by providing `androidAudioConfiguration` to `StreamVideoOptions`.
58

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

Lines changed: 128 additions & 119 deletions
Original file line numberDiff line numberDiff line change
@@ -92,146 +92,155 @@ class SfuStatsReporter {
9292
int? connectionTimeMs,
9393
SfuReconnectionStrategy? reconnectionStrategy,
9494
}) async {
95-
await _sfuStatsLock.synchronized(() async {
96-
final publisherStatsBundle =
97-
await callSession.rtcManager?.publisher?.getStats();
98-
final subscriberStatsBundle =
99-
await callSession.rtcManager?.subscriber.getStats();
100-
101-
if (publisherStatsBundle == null && subscriberStatsBundle == null) {
102-
return;
103-
}
104-
105-
final batterySaveModeAvailable = CurrentPlatform.isAndroid ||
106-
CurrentPlatform.isIos ||
107-
CurrentPlatform.isMacOS ||
108-
CurrentPlatform.isWindows;
109-
110-
bool? lowPowerMode;
111-
if (batterySaveModeAvailable) {
112-
try {
113-
lowPowerMode = await Battery().isInBatterySaveMode;
114-
} on PlatformException {
115-
_logger.d(() => 'Failed to get battery save mode from the device');
95+
try {
96+
await _sfuStatsLock.synchronized(() async {
97+
final publisherStatsBundle =
98+
await callSession.rtcManager?.publisher?.getStats();
99+
final subscriberStatsBundle =
100+
await callSession.rtcManager?.subscriber.getStats();
101+
102+
if (publisherStatsBundle == null && subscriberStatsBundle == null) {
103+
return;
116104
}
117-
}
118105

119-
sfu_models.AndroidState? androidState;
120-
sfu_models.AppleState? appleState;
121-
122-
final audioInputDevices = sfu_models.InputDevices(
123-
availableDevices: _availableAudioInputs,
124-
currentDevice: stateManager.callState.audioInputDevice?.label,
125-
isPermitted: stateManager.callState.audioInputDevice != null &&
126-
stateManager.callState.ownCapabilities
127-
.contains(CallPermission.sendAudio),
128-
);
106+
final batterySaveModeAvailable = CurrentPlatform.isAndroid ||
107+
CurrentPlatform.isIos ||
108+
CurrentPlatform.isMacOS ||
109+
CurrentPlatform.isWindows;
110+
111+
bool? lowPowerMode;
112+
if (batterySaveModeAvailable) {
113+
try {
114+
lowPowerMode = await Battery().isInBatterySaveMode;
115+
} on PlatformException {
116+
_logger.d(() => 'Failed to get battery save mode from the device');
117+
}
118+
}
129119

130-
final videoInputDevices = sfu_models.InputDevices(
131-
availableDevices: _availableVideoInputs,
132-
currentDevice: stateManager.callState.videoInputDevice?.label,
133-
isPermitted: stateManager.callState.videoInputDevice != null &&
134-
stateManager.callState.ownCapabilities
135-
.contains(CallPermission.sendVideo),
136-
);
120+
sfu_models.AndroidState? androidState;
121+
sfu_models.AppleState? appleState;
137122

138-
if (CurrentPlatform.isAndroid) {
139-
androidState = sfu_models.AndroidState(
140-
thermalState: _thermalStatus?.toAndroidThermalState(),
141-
isPowerSaverMode: lowPowerMode,
142-
);
143-
} else if (CurrentPlatform.isIos) {
144-
appleState = sfu_models.AppleState(
145-
thermalState: _thermalStatus?.toAppleThermalState(),
146-
isLowPowerModeEnabled: lowPowerMode,
123+
final audioInputDevices = sfu_models.InputDevices(
124+
availableDevices: _availableAudioInputs,
125+
currentDevice: stateManager.callState.audioInputDevice?.label,
126+
isPermitted: stateManager.callState.audioInputDevice != null &&
127+
stateManager.callState.ownCapabilities
128+
.contains(CallPermission.sendAudio),
147129
);
148-
}
149130

150-
List<PerformanceStats>? encodeStats;
151-
List<PerformanceStats>? decodeStats;
152-
final traces = <TraceSlice>[];
131+
final videoInputDevices = sfu_models.InputDevices(
132+
availableDevices: _availableVideoInputs,
133+
currentDevice: stateManager.callState.videoInputDevice?.label,
134+
isPermitted: stateManager.callState.videoInputDevice != null &&
135+
stateManager.callState.ownCapabilities
136+
.contains(CallPermission.sendVideo),
137+
);
153138

154-
if (statsOptions.enableRtcStats) {
155-
if (publisherStatsBundle != null) {
156-
await callSession.rtcManager?.publisher?.traceStats(
157-
publisherStatsBundle.rawStats,
139+
if (CurrentPlatform.isAndroid) {
140+
androidState = sfu_models.AndroidState(
141+
thermalState: _thermalStatus?.toAndroidThermalState(),
142+
isPowerSaverMode: lowPowerMode,
158143
);
159-
160-
encodeStats = callSession.rtcManager?.publisher?.getPerformanceStats(
161-
publisherStatsBundle.rtcStats,
162-
callSession.getTrackType,
144+
} else if (CurrentPlatform.isIos) {
145+
appleState = sfu_models.AppleState(
146+
thermalState: _thermalStatus?.toAppleThermalState(),
147+
isLowPowerModeEnabled: lowPowerMode,
163148
);
164149
}
165150

166-
if (subscriberStatsBundle != null) {
167-
await callSession.rtcManager?.subscriber.traceStats(
168-
subscriberStatsBundle.rawStats,
169-
);
151+
List<PerformanceStats>? encodeStats;
152+
List<PerformanceStats>? decodeStats;
153+
final traces = <TraceSlice>[];
154+
155+
if (statsOptions.enableRtcStats) {
156+
if (publisherStatsBundle != null) {
157+
await callSession.rtcManager?.publisher?.traceStats(
158+
publisherStatsBundle.rawStats,
159+
);
160+
161+
encodeStats =
162+
callSession.rtcManager?.publisher?.getPerformanceStats(
163+
publisherStatsBundle.rtcStats,
164+
callSession.getTrackType,
165+
);
166+
}
170167

171-
decodeStats = callSession.rtcManager?.subscriber.getPerformanceStats(
172-
subscriberStatsBundle.rtcStats,
173-
callSession.getTrackType,
174-
);
175-
}
168+
if (subscriberStatsBundle != null) {
169+
await callSession.rtcManager?.subscriber.traceStats(
170+
subscriberStatsBundle.rawStats,
171+
);
176172

177-
final subscriberTrace =
178-
callSession.rtcManager?.subscriber.tracer.take();
179-
final publisherTrace = callSession.rtcManager?.publisher?.tracer.take();
180-
final sessionTrace = callSession.getTrace();
173+
decodeStats =
174+
callSession.rtcManager?.subscriber.getPerformanceStats(
175+
subscriberStatsBundle.rtcStats,
176+
callSession.getTrackType,
177+
);
178+
}
181179

182-
traces.addAll([
183-
if (subscriberTrace != null) subscriberTrace,
184-
if (publisherTrace != null) publisherTrace,
185-
sessionTrace,
186-
]);
187-
}
180+
final subscriberTrace =
181+
callSession.rtcManager?.subscriber.tracer.take();
182+
final publisherTrace =
183+
callSession.rtcManager?.publisher?.tracer.take();
184+
final sessionTrace = callSession.getTrace();
185+
186+
traces.addAll([
187+
if (subscriberTrace != null) subscriberTrace,
188+
if (publisherTrace != null) publisherTrace,
189+
sessionTrace,
190+
]);
191+
}
188192

189-
try {
190-
final request = sfu.SendStatsRequest(
191-
sessionId: callSession.sessionId,
192-
publisherStats: publisherStatsBundle == null
193-
? null
194-
: jsonEncode(publisherStatsBundle.rawStats),
195-
subscriberStats: subscriberStatsBundle == null
196-
? null
197-
: jsonEncode(subscriberStatsBundle.rawStats),
198-
sdkVersion: streamVideoVersion,
199-
sdk: streamSdkName,
200-
android: androidState,
201-
apple: appleState,
202-
audioDevices: audioInputDevices,
203-
videoDevices: videoInputDevices,
204-
webrtcVersion: switch (CurrentPlatform.type) {
205-
PlatformType.android => androidWebRTCVersion,
206-
PlatformType.ios => iosWebRTCVersion,
207-
_ => null,
208-
},
209-
telemetry:
210-
_calculateTelemetry(connectionTimeMs, reconnectionStrategy),
211-
rtcStats:
212-
[...traces.expand((trace) => trace.snapshot)].toJsonString(),
213-
encodeStats: encodeStats,
214-
decodeStats: decodeStats,
215-
unifiedSessionId: unifiedSessionId,
216-
);
193+
try {
194+
final request = sfu.SendStatsRequest(
195+
sessionId: callSession.sessionId,
196+
publisherStats: publisherStatsBundle == null
197+
? null
198+
: jsonEncode(publisherStatsBundle.rawStats),
199+
subscriberStats: subscriberStatsBundle == null
200+
? null
201+
: jsonEncode(subscriberStatsBundle.rawStats),
202+
sdkVersion: streamVideoVersion,
203+
sdk: streamSdkName,
204+
android: androidState,
205+
apple: appleState,
206+
audioDevices: audioInputDevices,
207+
videoDevices: videoInputDevices,
208+
webrtcVersion: switch (CurrentPlatform.type) {
209+
PlatformType.android => androidWebRTCVersion,
210+
PlatformType.ios => iosWebRTCVersion,
211+
_ => null,
212+
},
213+
telemetry:
214+
_calculateTelemetry(connectionTimeMs, reconnectionStrategy),
215+
rtcStats:
216+
[...traces.expand((trace) => trace.snapshot)].toJsonString(),
217+
encodeStats: encodeStats,
218+
decodeStats: decodeStats,
219+
unifiedSessionId: unifiedSessionId,
220+
);
217221

218-
final result = await callSession.sfuClient.sendStats(
219-
request,
220-
);
222+
final result = await callSession.sfuClient.sendStats(
223+
request,
224+
);
221225

222-
if (result.isFailure) {
226+
if (result.isFailure) {
227+
for (final trace in traces) {
228+
trace.rollback();
229+
}
230+
}
231+
} catch (e) {
223232
for (final trace in traces) {
224233
trace.rollback();
225234
}
226-
}
227-
} catch (e) {
228-
for (final trace in traces) {
229-
trace.rollback();
230-
}
231235

232-
rethrow;
233-
}
234-
});
236+
rethrow;
237+
}
238+
});
239+
} catch (e) {
240+
_logger.v(
241+
() => 'Failed to send SFU stats: $e',
242+
);
243+
}
235244
}
236245

237246
sfu.Telemetry? _calculateTelemetry(

0 commit comments

Comments
 (0)