Skip to content

Commit 2095a4f

Browse files
fix: handle screenshare failure gracefully
1 parent 8151952 commit 2095a4f

File tree

4 files changed

+57
-6
lines changed

4 files changed

+57
-6
lines changed

lib/src/voip/call_session.dart

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -592,7 +592,6 @@ class CallSession {
592592
await addLocalStream(stream, SDPStreamMetadataPurpose.Screenshare);
593593
return true;
594594
} catch (err) {
595-
fireCallEvent(CallStateChange.kError);
596595
return false;
597596
}
598597
} else {
@@ -1315,16 +1314,16 @@ class CallSession {
13151314
return await voip.delegate.mediaDevices.getUserMedia(mediaConstraints);
13161315
} catch (e) {
13171316
await _getUserMediaFailed(e);
1318-
rethrow;
13191317
}
1318+
return null;
13201319
}
13211320

13221321
Future<MediaStream?> _getDisplayMedia() async {
13231322
try {
13241323
return await voip.delegate.mediaDevices
13251324
.getDisplayMedia(UserMediaConstraints.screenMediaConstraints);
13261325
} catch (e) {
1327-
await _getUserMediaFailed(e);
1326+
await _getDisplayMediaFailed(e);
13281327
}
13291328
return null;
13301329
}
@@ -1493,6 +1492,18 @@ class CallSession {
14931492
);
14941493
}
14951494

1495+
Future<void> _getDisplayMediaFailed(dynamic err) async {
1496+
Logs().w('Failed to get display media - ending call ${err.toString()}');
1497+
fireCallEvent(CallStateChange.kError);
1498+
// We don't terminate the call here because the user might still want to stay
1499+
// on the call and try again later.
1500+
throw CallError(
1501+
CallErrorCode.displayMediaFailed,
1502+
'Failed to get display media',
1503+
err,
1504+
);
1505+
}
1506+
14961507
Future<void> onSelectAnswerReceived(String? selectedPartyId) async {
14971508
if (direction != CallDirection.kIncoming) {
14981509
Logs().w('Got select_answer for an outbound call: ignoring');

lib/src/voip/utils/types.dart

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,11 @@ enum CallErrorCode {
5050
/// the hardware isn't plugged in, or the user has explicitly denied access.
5151
userMediaFailed('user_media_failed'),
5252

53+
/// An error code when there is no local display to screenshare. This may be
54+
/// because the hardware isn't plugged in, or the user has explicitly denied
55+
/// access.
56+
displayMediaFailed('display_media_failed'),
57+
5358
/// Error code used when a call event failed to send
5459
/// because unknown devices were present in the room
5560
unknownDevice('unknown_device'),

test/calls_test.dart

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -635,6 +635,32 @@ void main() {
635635
}
636636
});
637637

638+
test('Call continues when getDisplayMedia fails', () async {
639+
final mockDelegate = MockWebRTCDelegate();
640+
mockDelegate.mediaDevices.throwOnGetDisplayMedia = true;
641+
voip = VoIP(matrix, mockDelegate);
642+
VoIP.customTxid = '1234';
643+
644+
final call = await voip.inviteToCall(
645+
room,
646+
CallType.kVoice,
647+
userId: '@alice:testing.com',
648+
);
649+
650+
// Attempt to share screen - should not throw or terminate call
651+
try {
652+
await call.setScreensharingEnabled(true);
653+
} catch (e) {
654+
fail('Screen sharing failure should be handled internally');
655+
}
656+
657+
expect(call.onCallEventChanged.value, CallStateChange.kError);
658+
expect(call.state, isNot(CallState.kEnded));
659+
expect(voip.currentCID, isNotNull);
660+
661+
await call.hangup(reason: CallErrorCode.userHangup);
662+
});
663+
638664
test('getFamedlyCallEvents sort order', () {
639665
room.setState(
640666
Event(

test/webrtc_stub.dart

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,10 @@ class MockWebRTCDelegate implements WebRTCDelegate {
5454
@override
5555
bool get isWeb => false;
5656

57+
final _mockMediaDevices = MockMediaDevices();
58+
5759
@override
58-
MediaDevices get mediaDevices => MockMediaDevices();
60+
MockMediaDevices get mediaDevices => _mockMediaDevices;
5961

6062
@override
6163
Future<void> playRingtone() async {
@@ -93,6 +95,8 @@ class MockEncryptionKeyProvider implements EncryptionKeyProvider {
9395
}
9496

9597
class MockMediaDevices implements MediaDevices {
98+
bool throwOnGetDisplayMedia = false;
99+
96100
@override
97101
Function(dynamic event)? ondevicechange;
98102

@@ -102,8 +106,13 @@ class MockMediaDevices implements MediaDevices {
102106
}
103107

104108
@override
105-
Future<MediaStream> getDisplayMedia(Map<String, dynamic> mediaConstraints) {
106-
throw UnimplementedError();
109+
Future<MediaStream> getDisplayMedia(
110+
Map<String, dynamic> mediaConstraints,
111+
) async {
112+
if (throwOnGetDisplayMedia) {
113+
throw Exception('mock exception while getting display media');
114+
}
115+
return MockMediaStream('', '');
107116
}
108117

109118
@override

0 commit comments

Comments
 (0)