Skip to content

Commit 9ae2384

Browse files
authored
Merge pull request #1956 from famedly/karthi/register-listener-callback
fix: BREAKING! missed initial updates for stream listener callbacks in P2P & mesh calls
2 parents 4249cf1 + f3e2559 commit 9ae2384

File tree

5 files changed

+127
-34
lines changed

5 files changed

+127
-34
lines changed

lib/src/voip/README.md

+18-7
Original file line numberDiff line numberDiff line change
@@ -119,14 +119,25 @@ class MyVoipApp implements WebRTCDelegate {
119119
VideoRenderer createRenderer() => RTCVideoRenderer();
120120
121121
@override
122-
void playRingtone(){
122+
Future<void> playRingtone() async {
123123
// play ringtone
124124
}
125-
void stopRingtone() {
125+
Future<void> stopRingtone() async {
126126
// stop ringtone
127127
}
128128
129-
void handleNewCall(CallSession session) {
129+
Future<void> registerListeners(CallSession session) async {
130+
// register all listeners here
131+
session.onCallStreamsChanged.stream.listen((CallStateChange event) async {});
132+
session.onCallReplaced.stream.listen((CallStateChange event) async {});
133+
session.onCallHangupNotifierForGroupCalls.stream.listen((CallStateChange event) async {});
134+
session.onCallStateChanged.stream.listen((CallStateChange event) async {});
135+
session.onCallEventChanged.stream.listen((CallStateChange event) async {});
136+
session.onStreamAdd.stream.listen((CallStateChange event) async {});
137+
session.onStreamRemoved.stream.listen((CallStateChange event) async {});
138+
}
139+
140+
Future<void> handleNewCall(CallSession session) async {
130141
// handle new call incoming or outgoing
131142
switch(session.direction) {
132143
case CallDirection.kIncoming:
@@ -138,7 +149,7 @@ class MyVoipApp implements WebRTCDelegate {
138149
}
139150
}
140151
141-
void handleCallEnded(CallSession session) {
152+
Future<void> handleCallEnded(CallSession session) async {
142153
// handle call ended by local or remote
143154
}
144155
}
@@ -170,7 +181,7 @@ newCall.onCallStateChanged.stream.listen((state) {
170181
/// Then you can pop up the incoming call window at MyVoipApp.handleNewCall.
171182
class MyVoipApp implements WebRTCDelegate {
172183
...
173-
void handleNewCall(CallSession session) {
184+
Future<void> handleNewCall(CallSession session) async {
174185
switch(session.direction) {
175186
case CallDirection.kOutgoing:
176187
// show outgoing call window
@@ -185,13 +196,13 @@ newCall.hangup();
185196

186197
### 4.Answer a incoming call
187198

188-
When a new incoming call comes in, handleNewCall will be called, and the answering interface can pop up at this time, and use `onCallStateChanged` to listen to the call state.
199+
When a new incoming call comes in, registerListeners will be called right before handleNewCall is called, and the answering interface can pop up at this time, and use `onCallStateChanged` to listen to the call state.
189200

190201
The incoming call window need display `answer` and `reject` buttons, by calling `newCall.answer();` or `newCall.reject();` to decide whether to connect the call.
191202

192203
```dart
193204
...
194-
void handleNewCall(CallSession newCall) {
205+
Future<void> registerListeners(CallSession newCall) async {
195206
switch(newCall.direction) {
196207
case CallDirection.kIncoming:
197208
/// show incoming call window

lib/src/voip/backend/mesh_backend.dart

+87-24
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,11 @@ class MeshBackend extends CallBackend {
2020
/// participant:volume
2121
final Map<CallParticipant, double> _audioLevelsMap = {};
2222

23-
StreamSubscription<CallSession>? _callSubscription;
23+
/// The stream is used to prepare for incoming peer calls like registering listeners
24+
StreamSubscription<CallSession>? _callSetupSubscription;
25+
26+
/// The stream is used to signal the start of an incoming peer call
27+
StreamSubscription<CallSession>? _callStartSubscription;
2428

2529
Timer? _activeSpeakerLoopTimeout;
2630

@@ -109,14 +113,32 @@ class MeshBackend extends CallBackend {
109113
);
110114
}
111115

116+
/// Register listeners for a peer call to use for the group calls, that is
117+
/// needed before even call is added to `_callSessions`.
118+
/// We do this here for onStreamAdd and onStreamRemoved to make sure we don't
119+
/// miss any events that happen before the call is completely started.
120+
void _registerListenersBeforeCallAdd(CallSession call) {
121+
call.onStreamAdd.stream.listen((stream) {
122+
if (!stream.isLocal()) {
123+
onStreamAdd.add(stream);
124+
}
125+
});
126+
127+
call.onStreamRemoved.stream.listen((stream) {
128+
if (!stream.isLocal()) {
129+
onStreamRemoved.add(stream);
130+
}
131+
});
132+
}
133+
112134
Future<void> _addCall(GroupCallSession groupCall, CallSession call) async {
113135
_callSessions.add(call);
114-
await _initCall(groupCall, call);
136+
_initCall(groupCall, call);
115137
groupCall.onGroupCallEvent.add(GroupCallStateChange.callsChanged);
116138
}
117139

118140
/// init a peer call from group calls.
119-
Future<void> _initCall(GroupCallSession groupCall, CallSession call) async {
141+
void _initCall(GroupCallSession groupCall, CallSession call) {
120142
if (call.remoteUserId == null) {
121143
throw MatrixSDKVoipException(
122144
'Cannot init call without proper invitee user and device Id',
@@ -141,18 +163,6 @@ class MeshBackend extends CallBackend {
141163
call.onCallHangupNotifierForGroupCalls.stream.listen((event) async {
142164
await _onCallHangup(groupCall, call);
143165
});
144-
145-
call.onStreamAdd.stream.listen((stream) {
146-
if (!stream.isLocal()) {
147-
onStreamAdd.add(stream);
148-
}
149-
});
150-
151-
call.onStreamRemoved.stream.listen((stream) {
152-
if (!stream.isLocal()) {
153-
onStreamRemoved.add(stream);
154-
}
155-
});
156166
}
157167

158168
Future<void> _replaceCall(
@@ -171,7 +181,8 @@ class MeshBackend extends CallBackend {
171181
_callSessions.add(replacementCall);
172182

173183
await _disposeCall(groupCall, existingCall, CallErrorCode.replaced);
174-
await _initCall(groupCall, replacementCall);
184+
_registerListenersBeforeCallAdd(replacementCall);
185+
_initCall(groupCall, replacementCall);
175186

176187
groupCall.onGroupCallEvent.add(GroupCallStateChange.callsChanged);
177188
}
@@ -657,7 +668,49 @@ class MeshBackend extends CallBackend {
657668
return;
658669
}
659670

660-
Future<void> _onIncomingCall(
671+
void _onIncomingCallInMeshSetup(
672+
GroupCallSession groupCall,
673+
CallSession newCall,
674+
) {
675+
// The incoming calls may be for another room, which we will ignore.
676+
if (newCall.room.id != groupCall.room.id) return;
677+
678+
if (newCall.state != CallState.kRinging) {
679+
Logs().v(
680+
'[_onIncomingCallInMeshSetup] Incoming call no longer in ringing state. Ignoring.',
681+
);
682+
return;
683+
}
684+
685+
if (newCall.groupCallId == null ||
686+
newCall.groupCallId != groupCall.groupCallId) {
687+
Logs().v(
688+
'[_onIncomingCallInMeshSetup] Incoming call with groupCallId ${newCall.groupCallId} ignored because it doesn\'t match the current group call',
689+
);
690+
return;
691+
}
692+
693+
final existingCall = _getCallForParticipant(
694+
groupCall,
695+
CallParticipant(
696+
groupCall.voip,
697+
userId: newCall.remoteUserId!,
698+
deviceId: newCall.remoteDeviceId,
699+
),
700+
);
701+
702+
// if it's an existing call, `_registerListenersForCall` will be called in
703+
// `_replaceCall` that is used in `_onIncomingCallStart`.
704+
if (existingCall != null) return;
705+
706+
Logs().v(
707+
'[_onIncomingCallInMeshSetup] GroupCallSession: incoming call from: ${newCall.remoteUserId}${newCall.remoteDeviceId}${newCall.remotePartyId}',
708+
);
709+
710+
_registerListenersBeforeCallAdd(newCall);
711+
}
712+
713+
Future<void> _onIncomingCallInMeshStart(
661714
GroupCallSession groupCall,
662715
CallSession newCall,
663716
) async {
@@ -667,14 +720,16 @@ class MeshBackend extends CallBackend {
667720
}
668721

669722
if (newCall.state != CallState.kRinging) {
670-
Logs().w('Incoming call no longer in ringing state. Ignoring.');
723+
Logs().v(
724+
'[_onIncomingCallInMeshStart] Incoming call no longer in ringing state. Ignoring.',
725+
);
671726
return;
672727
}
673728

674729
if (newCall.groupCallId == null ||
675730
newCall.groupCallId != groupCall.groupCallId) {
676731
Logs().v(
677-
'Incoming call with groupCallId ${newCall.groupCallId} ignored because it doesn\'t match the current group call',
732+
'[_onIncomingCallInMeshStart] Incoming call with groupCallId ${newCall.groupCallId} ignored because it doesn\'t match the current group call',
678733
);
679734
await newCall.reject();
680735
return;
@@ -694,7 +749,7 @@ class MeshBackend extends CallBackend {
694749
}
695750

696751
Logs().v(
697-
'GroupCallSession: incoming call from: ${newCall.remoteUserId}${newCall.remoteDeviceId}${newCall.remotePartyId}',
752+
'[_onIncomingCallInMeshStart] GroupCallSession: incoming call from: ${newCall.remoteUserId}${newCall.remoteDeviceId}${newCall.remotePartyId}',
698753
);
699754

700755
// Check if the user calling has an existing call and use this call instead.
@@ -800,7 +855,8 @@ class MeshBackend extends CallBackend {
800855

801856
_activeSpeaker = null;
802857
_activeSpeakerLoopTimeout?.cancel();
803-
await _callSubscription?.cancel();
858+
await _callSetupSubscription?.cancel();
859+
await _callStartSubscription?.cancel();
804860
}
805861

806862
@override
@@ -826,11 +882,16 @@ class MeshBackend extends CallBackend {
826882
GroupCallSession groupCall,
827883
) async {
828884
for (final call in _callSessions) {
829-
await _onIncomingCall(groupCall, call);
885+
_onIncomingCallInMeshSetup(groupCall, call);
886+
await _onIncomingCallInMeshStart(groupCall, call);
830887
}
831888

832-
_callSubscription = groupCall.voip.onIncomingCall.stream.listen(
833-
(newCall) => _onIncomingCall(groupCall, newCall),
889+
_callSetupSubscription = groupCall.voip.onIncomingCallSetup.stream.listen(
890+
(newCall) => _onIncomingCallInMeshSetup(groupCall, newCall),
891+
);
892+
893+
_callStartSubscription = groupCall.voip.onIncomingCallStart.stream.listen(
894+
(newCall) => _onIncomingCallInMeshStart(groupCall, newCall),
834895
);
835896

836897
_onActiveSpeakerLoop(groupCall);
@@ -883,6 +944,8 @@ class MeshBackend extends CallBackend {
883944
// party id set to when answered
884945
newCall.remoteSessionId = mem.membershipId;
885946

947+
_registerListenersBeforeCallAdd(newCall);
948+
886949
await newCall.placeCallWithStreams(
887950
_getLocalStreams(),
888951
requestScreenSharing: mem.feeds?.any(

lib/src/voip/models/webrtc_delegate.dart

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ abstract class WebRTCDelegate {
1111
]);
1212
Future<void> playRingtone();
1313
Future<void> stopRingtone();
14+
Future<void> registerListeners(CallSession session);
1415
Future<void> handleNewCall(CallSession session);
1516
Future<void> handleCallEnded(CallSession session);
1617
Future<void> handleMissedCall(CallSession session);

lib/src/voip/voip.dart

+16-3
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,13 @@ class VoIP {
3636
Map<VoipId, GroupCallSession> get groupCalls => _groupCalls;
3737
final Map<VoipId, GroupCallSession> _groupCalls = {};
3838

39-
final CachedStreamController<CallSession> onIncomingCall =
39+
/// The stream is used to prepare for incoming peer calls in a mesh call
40+
/// For example, registering listeners
41+
final CachedStreamController<CallSession> onIncomingCallSetup =
42+
CachedStreamController();
43+
44+
/// The stream is used to signal the start of an incoming peer call in a mesh call
45+
final CachedStreamController<CallSession> onIncomingCallStart =
4046
CachedStreamController();
4147

4248
VoipId? currentCID;
@@ -479,6 +485,12 @@ class VoIP {
479485
// by terminate.
480486
currentCID = VoipId(roomId: room.id, callId: callId);
481487

488+
if (confId == null) {
489+
await delegate.registerListeners(newCall);
490+
} else {
491+
onIncomingCallSetup.add(newCall);
492+
}
493+
482494
await newCall.initWithInvite(
483495
callType,
484496
offer,
@@ -493,8 +505,7 @@ class VoIP {
493505
}
494506

495507
if (confId != null) {
496-
// the stream is used to monitor incoming peer calls in a mesh call
497-
onIncomingCall.add(newCall);
508+
onIncomingCallStart.add(newCall);
498509
}
499510
}
500511

@@ -768,6 +779,8 @@ class VoIP {
768779
newCall.remoteUserId = userId;
769780
newCall.remoteDeviceId = deviceId;
770781

782+
await delegate.registerListeners(newCall);
783+
771784
currentCID = VoipId(roomId: roomId, callId: callId);
772785
await newCall.initOutboundCall(type).then((_) {
773786
delegate.handleNewCall(newCall);

test/webrtc_stub.dart

+5
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@ class MockWebRTCDelegate implements WebRTCDelegate {
1515
]) async =>
1616
MockRTCPeerConnection();
1717

18+
@override
19+
Future<void> registerListeners(CallSession session) async {
20+
Logs().i('registerListeners called in MockWebRTCDelegate');
21+
}
22+
1823
@override
1924
Future<void> handleCallEnded(CallSession session) async {
2025
Logs().i('handleCallEnded called in MockWebRTCDelegate');

0 commit comments

Comments
 (0)