Merge branch 'td/asyncmesh' into 'main'

fix: make group call stuff async, let clients await what they need

See merge request famedly/company/frontend/famedlysdk!1244
This commit is contained in:
td 2023-03-03 15:03:32 +00:00
commit b776e8f9df
5 changed files with 177 additions and 156 deletions

View File

@ -508,10 +508,10 @@ class CallSession {
});
}
void answerWithStreams(List<WrappedMediaStream> callFeeds) {
Future<void> answerWithStreams(List<WrappedMediaStream> callFeeds) async {
if (inviteOrAnswerSent) return;
Logs().d('nswering call $callId');
gotCallFeedsForAnswer(callFeeds);
await gotCallFeedsForAnswer(callFeeds);
}
void replacedBy(CallSession newCall) {
@ -782,13 +782,14 @@ class CallSession {
if (stream == null) {
return false;
}
stream.getTracks().forEach((track) {
for (final track in stream.getTracks()) {
// screen sharing should only have 1 video track anyway, so this only
// fires once
track.onEnded = () {
setScreensharingEnabled(false);
track.onEnded = () async {
await setScreensharingEnabled(false);
};
});
}
await addLocalStream(stream, SDPStreamMetadataPurpose.Screenshare);
return true;
} catch (err) {
@ -1082,7 +1083,7 @@ class CallSession {
return;
}
// stop play ringtone
voip.delegate.stopRingtone();
await voip.delegate.stopRingtone();
if (direction == CallDirection.kIncoming) {
setCallState(CallState.kCreateAnswer);
@ -1132,7 +1133,7 @@ class CallSession {
///
Future<void> reject({String? reason, bool shouldEmit = true}) async {
// stop play ringtone
voip.delegate.stopRingtone();
await voip.delegate.stopRingtone();
if (state != CallState.kRinging && state != CallState.kFledgling) {
Logs().e('[VOIP] Call must be in \'ringing|fledgling\' state to reject!');
return;
@ -1147,7 +1148,7 @@ class CallSession {
Future<void> hangup([String? reason, bool suppressEvent = false]) async {
// stop play ringtone
voip.delegate.stopRingtone();
await voip.delegate.stopRingtone();
await terminate(
CallParty.kLocal, reason ?? CallErrorCode.UserHangup, !suppressEvent);
@ -1185,7 +1186,7 @@ class CallSession {
ringingTimer = null;
try {
voip.delegate.stopRingtone();
await voip.delegate.stopRingtone();
} catch (e) {
// maybe rigntone never started (group calls) or has been stopped already
Logs().d('stopping ringtone failed ', e);
@ -1209,10 +1210,10 @@ class CallSession {
await cleanUp();
if (shouldEmit) {
onCallHangup.add(this);
voip.delegate.handleCallEnded(this);
await voip.delegate.handleCallEnded(this);
fireCallEvent(CallEvent.kHangup);
if ((party == CallParty.kRemote && missedCall)) {
voip.delegate.handleMissedCall(this);
await voip.delegate.handleMissedCall(this);
}
}
}
@ -1402,9 +1403,9 @@ class CallSession {
localUserMediaStream!.isVideoMuted()) ||
remoteOnHold;
_setTracksEnabled(localUserMediaStream?.stream!.getAudioTracks() ?? [],
_setTracksEnabled(localUserMediaStream?.stream?.getAudioTracks() ?? [],
!micShouldBeMuted);
_setTracksEnabled(localUserMediaStream?.stream!.getVideoTracks() ?? [],
_setTracksEnabled(localUserMediaStream?.stream?.getVideoTracks() ?? [],
!vidShouldBeMuted);
await sendSDPStreamMetadataChanged(
@ -1420,10 +1421,12 @@ class CallSession {
SDPStreamMetadata _getLocalSDPStreamMetadata() {
final sdpStreamMetadatas = <String, SDPStreamPurpose>{};
for (final wpstream in getLocalStreams) {
sdpStreamMetadatas[wpstream.stream!.id] = SDPStreamPurpose(
purpose: wpstream.purpose,
audio_muted: wpstream.audioMuted,
video_muted: wpstream.videoMuted);
if (wpstream.stream != null) {
sdpStreamMetadatas[wpstream.stream!.id] = SDPStreamPurpose(
purpose: wpstream.purpose,
audio_muted: wpstream.audioMuted,
video_muted: wpstream.videoMuted);
}
}
final metadata = SDPStreamMetadata(sdpStreamMetadatas);
Logs().v('Got local SDPStreamMetadata ${metadata.toJson().toString()}');
@ -1482,18 +1485,18 @@ class CallSession {
'sdpSemantics': 'unified-plan'
};
final pc = await voip.delegate.createPeerConnection(configuration);
pc.onTrack = (RTCTrackEvent event) {
pc.onTrack = (RTCTrackEvent event) async {
if (event.streams.isNotEmpty) {
final stream = event.streams[0];
_addRemoteStream(stream);
stream.getTracks().forEach((track) {
track.onEnded = () {
await _addRemoteStream(stream);
for (final track in stream.getTracks()) {
track.onEnded = () async {
if (stream.getTracks().isEmpty) {
Logs().d('[VOIP] detected a empty stream, removing it');
_removeStream(stream);
await _removeStream(stream);
}
};
});
}
}
};
return pc;

View File

@ -381,7 +381,7 @@ class GroupCall {
localUserMediaStream = newStream;
await localUserMediaStream!.initialize();
addUserMediaStream(newStream);
await addUserMediaStream(newStream);
setState(GroupCallState.LocalCallFeedInitialized);
@ -430,7 +430,7 @@ class GroupCall {
_callSubscription = voip.onIncomingCall.stream.listen(onIncomingCall);
for (final call in calls) {
onIncomingCall(call);
await onIncomingCall(call);
}
// Set up participants for the members currently in the room.
@ -438,26 +438,26 @@ class GroupCall {
final memberStateEvents = await getAllMemberStateEvents();
memberStateEvents.forEach((stateEvent) {
onMemberStateChanged(stateEvent);
});
for (final memberState in memberStateEvents) {
await onMemberStateChanged(memberState);
}
onActiveSpeakerLoop();
voip.currentGroupCID = groupCallId;
voip.delegate.handleNewGroupCall(this);
await voip.delegate.handleNewGroupCall(this);
}
Future<void> dispose() async {
if (localUserMediaStream != null) {
removeUserMediaStream(localUserMediaStream!);
await removeUserMediaStream(localUserMediaStream!);
localUserMediaStream = null;
}
if (localScreenshareStream != null) {
await stopMediaStream(localScreenshareStream!.stream);
removeScreenshareStream(localScreenshareStream!);
await removeScreenshareStream(localScreenshareStream!);
localScreenshareStream = null;
localDesktopCapturerSourceId = null;
}
@ -467,9 +467,10 @@ class GroupCall {
await removeMemberStateEvent();
final callsCopy = calls.toList();
callsCopy.forEach((element) {
removeCall(element, CallErrorCode.UserHangup);
});
for (final call in callsCopy) {
await removeCall(call, CallErrorCode.UserHangup);
}
activeSpeaker = null;
activeSpeakerLoopTimeout?.cancel();
@ -480,7 +481,7 @@ class GroupCall {
await dispose();
setState(GroupCallState.LocalCallFeedUninitialized);
voip.currentGroupCID = null;
voip.delegate.handleGroupCallEnded(this);
await voip.delegate.handleGroupCallEnded(this);
final justLeftGroupCall = voip.groupCalls.tryGet<GroupCall>(room.id);
// terminate group call if empty
if (justLeftGroupCall != null &&
@ -510,7 +511,7 @@ class GroupCall {
});
Logs().d('[VOIP] Group call $groupCallId was killed');
}
voip.delegate.handleGroupCallEnded(this);
await voip.delegate.handleGroupCallEnded(this);
setState(GroupCallState.Ended);
}
@ -540,9 +541,9 @@ class GroupCall {
setTracksEnabled(localUserMediaStream!.stream!.getAudioTracks(), !muted);
}
calls.forEach((call) {
call.setMicrophoneMuted(muted);
});
for (final call in calls) {
await call.setMicrophoneMuted(muted);
}
onGroupCallEvent.add(GroupCallEvent.LocalMuteStateChanged);
return true;
@ -558,9 +559,9 @@ class GroupCall {
setTracksEnabled(localUserMediaStream!.stream!.getVideoTracks(), !muted);
}
calls.forEach((call) {
call.setLocalVideoMuted(muted);
});
for (final call in calls) {
await call.setLocalVideoMuted(muted);
}
onGroupCallEvent.add(GroupCallEvent.LocalMuteStateChanged);
return true;
@ -580,13 +581,13 @@ class GroupCall {
try {
Logs().v('Asking for screensharing permissions...');
final stream = await _getDisplayMedia();
stream.getTracks().forEach((track) {
track.onEnded = () {
// screen sharing should only have 1 video track anyway, so this only
// fires once
setScreensharingEnabled(false, '');
for (final track in stream.getTracks()) {
// screen sharing should only have 1 video track anyway, so this only
// fires once
track.onEnded = () async {
await setScreensharingEnabled(false, '');
};
});
}
Logs().v(
'Screensharing permissions granted. Setting screensharing enabled on all calls');
localDesktopCapturerSourceId = desktopCapturerSourceId;
@ -607,12 +608,11 @@ class GroupCall {
await localScreenshareStream!.initialize();
onGroupCallEvent.add(GroupCallEvent.LocalScreenshareStateChanged);
calls.forEach((call) async {
for (final call in calls) {
await call.addLocalStream(
await localScreenshareStream!.stream!.clone(),
localScreenshareStream!.purpose);
});
}
await sendMemberStateEvent();
@ -625,11 +625,12 @@ class GroupCall {
return false;
}
} else {
calls.forEach((call) {
call.removeLocalStream(call.localScreenSharingStream!);
});
for (final call in calls) {
await call.removeLocalStream(call.localScreenSharingStream!);
}
await stopMediaStream(localScreenshareStream?.stream);
removeScreenshareStream(localScreenshareStream!);
await removeScreenshareStream(localScreenshareStream!);
localScreenshareStream = null;
localDesktopCapturerSourceId = null;
await sendMemberStateEvent();
@ -642,7 +643,7 @@ class GroupCall {
return localScreenshareStream != null;
}
void onIncomingCall(CallSession newCall) {
Future<void> onIncomingCall(CallSession newCall) async {
// The incoming calls may be for another room, which we will ignore.
if (newCall.room.id != room.id) {
return;
@ -656,7 +657,7 @@ class GroupCall {
if (newCall.groupCallId == null || newCall.groupCallId != groupCallId) {
Logs().v(
'Incoming call with groupCallId ${newCall.groupCallId} ignored because it doesn\'t match the current group call');
newCall.reject();
await newCall.reject();
return;
}
@ -671,12 +672,12 @@ class GroupCall {
// Check if the user calling has an existing call and use this call instead.
if (existingCall != null) {
replaceCall(existingCall, newCall);
await replaceCall(existingCall, newCall);
} else {
addCall(newCall);
await addCall(newCall);
}
newCall.answerWithStreams(getLocalStreams());
await newCall.answerWithStreams(getLocalStreams());
}
Future<void> sendMemberStateEvent() async {
@ -782,7 +783,7 @@ class GroupCall {
room.id, EventTypes.GroupCallMemberPrefix, localUserId, content);
}
void onMemberStateChanged(MatrixEvent event) async {
Future<void> onMemberStateChanged(MatrixEvent event) async {
// The member events may be received for another room, which we will ignore.
if (event.roomId != room.id) {
return;
@ -892,7 +893,7 @@ class GroupCall {
await newCall.placeCallWithStreams(
getLocalStreams(), requestScreenshareFeed);
addCall(newCall);
await addCall(newCall);
}
Future<IGroupCallRoomMemberDevice?> getDeviceForMember(String userId) async {
@ -929,13 +930,14 @@ class GroupCall {
return null;
}
void addCall(CallSession call) {
Future<void> addCall(CallSession call) async {
calls.add(call);
initCall(call);
await initCall(call);
onGroupCallEvent.add(GroupCallEvent.CallsChanged);
}
void replaceCall(CallSession existingCall, CallSession replacementCall) {
Future<void> replaceCall(
CallSession existingCall, CallSession replacementCall) async {
final existingCallIndex =
calls.indexWhere((element) => element == existingCall);
@ -946,15 +948,15 @@ class GroupCall {
calls.removeAt(existingCallIndex);
calls.add(replacementCall);
disposeCall(existingCall, CallErrorCode.Replaced);
initCall(replacementCall);
await disposeCall(existingCall, CallErrorCode.Replaced);
await initCall(replacementCall);
onGroupCallEvent.add(GroupCallEvent.CallsChanged);
}
/// Removes a peer call from group calls.
void removeCall(CallSession call, String hangupReason) {
disposeCall(call, hangupReason);
Future<void> removeCall(CallSession call, String hangupReason) async {
await disposeCall(call, hangupReason);
calls.removeWhere((element) => call.callId == element.callId);
@ -962,7 +964,7 @@ class GroupCall {
}
/// init a peer call from group calls.
void initCall(CallSession call) {
Future<void> initCall(CallSession call) async {
final opponentMemberId = call.opponentDeviceId;
if (opponentMemberId == null) {
@ -972,17 +974,17 @@ class GroupCall {
call.onCallStateChanged.stream
.listen(((event) => onCallStateChanged(call, event)));
call.onCallReplaced.stream.listen((CallSession newCall) {
replaceCall(call, newCall);
call.onCallReplaced.stream.listen((CallSession newCall) async {
await replaceCall(call, newCall);
});
call.onCallStreamsChanged.stream.listen((call) {
call.tryRemoveStopedStreams();
onStreamsChanged(call);
call.onCallStreamsChanged.stream.listen((call) async {
await call.tryRemoveStopedStreams();
await onStreamsChanged(call);
});
call.onCallHangup.stream.listen((event) {
onCallHangup(call);
call.onCallHangup.stream.listen((event) async {
await onCallHangup(call);
});
call.onStreamAdd.stream.listen((stream) {
@ -998,7 +1000,7 @@ class GroupCall {
});
}
void disposeCall(CallSession call, String hangupReason) {
Future<void> disposeCall(CallSession call, String hangupReason) async {
final opponentMemberId = call.opponentDeviceId;
if (opponentMemberId == null) {
@ -1012,19 +1014,19 @@ class GroupCall {
}
if (call.state != CallState.kEnded) {
call.hangup(hangupReason, false);
await call.hangup(hangupReason, false);
}
final usermediaStream = getUserMediaStreamByUserId(opponentMemberId);
if (usermediaStream != null) {
removeUserMediaStream(usermediaStream);
await removeUserMediaStream(usermediaStream);
}
final screenshareStream = getScreenshareStreamByUserId(opponentMemberId);
if (screenshareStream != null) {
removeScreenshareStream(screenshareStream);
await removeScreenshareStream(screenshareStream);
}
}
@ -1032,7 +1034,7 @@ class GroupCall {
return call.remoteUser?.id ?? call.invitee;
}
void onStreamsChanged(CallSession call) {
Future<void> onStreamsChanged(CallSession call) async {
final opponentMemberId = getCallUserId(call);
if (opponentMemberId == null) {
@ -1045,13 +1047,14 @@ class GroupCall {
if (remoteStreamChanged) {
if (currentUserMediaStream == null && remoteUsermediaStream != null) {
addUserMediaStream(remoteUsermediaStream);
await addUserMediaStream(remoteUsermediaStream);
} else if (currentUserMediaStream != null &&
remoteUsermediaStream != null) {
replaceUserMediaStream(currentUserMediaStream, remoteUsermediaStream);
await replaceUserMediaStream(
currentUserMediaStream, remoteUsermediaStream);
} else if (currentUserMediaStream != null &&
remoteUsermediaStream == null) {
removeUserMediaStream(currentUserMediaStream);
await removeUserMediaStream(currentUserMediaStream);
}
}
@ -1067,38 +1070,38 @@ class GroupCall {
addScreenshareStream(remoteScreensharingStream);
} else if (currentScreenshareStream != null &&
remoteScreensharingStream != null) {
replaceScreenshareStream(
await replaceScreenshareStream(
currentScreenshareStream, remoteScreensharingStream);
} else if (currentScreenshareStream != null &&
remoteScreensharingStream == null) {
removeScreenshareStream(currentScreenshareStream);
await removeScreenshareStream(currentScreenshareStream);
}
}
onGroupCallFeedsChanged.add(this);
}
void onCallStateChanged(CallSession call, CallState state) {
Future<void> onCallStateChanged(CallSession call, CallState state) async {
final audioMuted = localUserMediaStream?.isAudioMuted() ?? true;
if (call.localUserMediaStream != null &&
call.isMicrophoneMuted != audioMuted) {
call.setMicrophoneMuted(audioMuted);
await call.setMicrophoneMuted(audioMuted);
}
final videoMuted = localUserMediaStream?.isVideoMuted() ?? true;
if (call.localUserMediaStream != null &&
call.isLocalVideoMuted != videoMuted) {
call.setLocalVideoMuted(videoMuted);
await call.setLocalVideoMuted(videoMuted);
}
}
void onCallHangup(CallSession call) {
Future<void> onCallHangup(CallSession call) async {
if (call.hangupReason == CallErrorCode.Replaced) {
return;
}
onStreamsChanged(call);
removeCall(call, call.hangupReason!);
await onStreamsChanged(call);
await removeCall(call, call.hangupReason!);
}
WrappedMediaStream? getUserMediaStreamByUserId(String userId) {
@ -1109,7 +1112,7 @@ class GroupCall {
return null;
}
void addUserMediaStream(WrappedMediaStream stream) {
Future<void> addUserMediaStream(WrappedMediaStream stream) async {
userMediaStreams.add(stream);
//callFeed.measureVolumeActivity(true);
onStreamAdd.add(stream);
@ -1132,7 +1135,7 @@ class GroupCall {
onGroupCallEvent.add(GroupCallEvent.UserMediaStreamsChanged);
}
void removeUserMediaStream(WrappedMediaStream stream) {
Future<void> removeUserMediaStream(WrappedMediaStream stream) async {
final streamIndex =
userMediaStreams.indexWhere((stream) => stream.userId == stream.userId);
@ -1145,8 +1148,8 @@ class GroupCall {
onStreamRemoved.add(stream);
if (stream.isLocal()) {
stream.disposeRenderer();
stopMediaStream(stream.stream);
await stream.disposeRenderer();
await stopMediaStream(stream.stream);
}
onGroupCallEvent.add(GroupCallEvent.UserMediaStreamsChanged);
@ -1240,7 +1243,7 @@ class GroupCall {
onGroupCallEvent.add(GroupCallEvent.ScreenshareStreamsChanged);
}
void removeScreenshareStream(WrappedMediaStream stream) {
Future<void> removeScreenshareStream(WrappedMediaStream stream) async {
final streamIndex = screenshareStreams
.indexWhere((stream) => stream.userId == stream.userId);
@ -1254,8 +1257,8 @@ class GroupCall {
onStreamRemoved.add(stream);
if (stream.isLocal()) {
stream.disposeRenderer();
stopMediaStream(stream.stream);
await stream.disposeRenderer();
await stopMediaStream(stream.stream);
}
onGroupCallEvent.add(GroupCallEvent.ScreenshareStreamsChanged);

View File

@ -3,11 +3,23 @@ import 'dart:async';
import 'package:random_string/random_string.dart';
import 'package:webrtc_interface/webrtc_interface.dart';
import 'package:matrix/matrix.dart';
Future<void> stopMediaStream(MediaStream? stream) async {
stream?.getTracks().forEach((element) async {
await element.stop();
});
await stream?.dispose();
if (stream != null) {
for (final track in stream.getTracks()) {
try {
await track.stop();
} catch (e) {
Logs().e('[VOIP] stopping track ${track.id} failed', e);
}
}
try {
await stream.dispose();
} catch (e) {
Logs().e('[VOIP] disposing stream ${stream.id} failed', e);
}
}
}
void setTracksEnabled(List<MediaStreamTrack> tracks, bool enabled) {

View File

@ -14,13 +14,13 @@ abstract class WebRTCDelegate {
Map<String, dynamic> configuration,
[Map<String, dynamic> constraints = const {}]);
VideoRenderer createRenderer();
void playRingtone();
void stopRingtone();
void handleNewCall(CallSession session);
void handleCallEnded(CallSession session);
void handleMissedCall(CallSession session);
void handleNewGroupCall(GroupCall groupCall);
void handleGroupCallEnded(GroupCall groupCall);
Future<void> playRingtone();
Future<void> stopRingtone();
Future<void> handleNewCall(CallSession session);
Future<void> handleCallEnded(CallSession session);
Future<void> handleMissedCall(CallSession session);
Future<void> handleNewGroupCall(GroupCall groupCall);
Future<void> handleGroupCallEnded(GroupCall groupCall);
bool get isWeb;
/// This should be set to false if any calls in the client are in kConnected
@ -71,13 +71,13 @@ class VoIP {
.listen((event) => _handleEvent(event, onAssertedIdentityReceived));
client.onRoomState.stream.listen(
(event) {
(event) async {
if ([
EventTypes.GroupCallPrefix,
EventTypes.GroupCallMemberPrefix,
].contains(event.type)) {
Logs().v('[VOIP] onRoomState: type ${event.toJson()}.');
onRoomStateChanged(event);
await onRoomStateChanged(event);
}
},
);
@ -237,7 +237,7 @@ class VoIP {
Logs().v(
'[VOIP] onCallInvite: Unable to handle new calls, maybe user is busy.');
await newCall.reject(reason: CallErrorCode.UserBusy, shouldEmit: false);
delegate.handleMissedCall(newCall);
await delegate.handleMissedCall(newCall);
return;
}
@ -254,7 +254,7 @@ class VoIP {
/// Autoplay on firefox still needs interaction, without which all notifications
/// could be blocked.
if (confId == null) {
delegate.playRingtone();
await delegate.playRingtone();
}
await newCall.initWithInvite(
@ -264,7 +264,7 @@ class VoIP {
// Popup CallingPage for incoming call.
if (confId == null && !newCall.callHasEnded) {
delegate.handleNewCall(newCall);
await delegate.handleNewCall(newCall);
}
onIncomingCall.add(newCall);
@ -281,7 +281,7 @@ class VoIP {
if (senderId == client.userID) {
// Ignore messages to yourself.
if (!call.answeredByUs) {
delegate.stopRingtone();
await delegate.stopRingtone();
}
if (call.state == CallState.kRinging) {
call.onAnsweredElsewhere();
@ -333,7 +333,7 @@ class VoIP {
Future<void> onCallHangup(String roomId, String _ /*senderId unused*/,
Map<String, dynamic> content) async {
// stop play ringtone, if this is an incoming call
delegate.stopRingtone();
await delegate.stopRingtone();
Logs().v('[VOIP] onCallHangup => ${content.toString()}');
final String callId = content['call_id'];
final call = calls[callId];
@ -676,14 +676,16 @@ class VoIP {
Future<void> createGroupCallForRoom(Room room) async {
final events = await client.getRoomState(room.id);
events.sort((a, b) => a.originServerTs.compareTo(b.originServerTs));
events.forEach((element) async {
if (element.type == EventTypes.GroupCallPrefix) {
if (element.content['m.terminated'] != null) {
for (final event in events) {
if (event.type == EventTypes.GroupCallPrefix) {
if (event.content['m.terminated'] != null) {
return;
}
await createGroupCallFromRoomStateEvent(element);
await createGroupCallFromRoomStateEvent(event);
}
});
}
return;
}
@ -733,12 +735,12 @@ class VoIP {
onIncomingGroupCall.add(groupCall);
if (emitHandleNewGroupCall) {
delegate.handleNewGroupCall(groupCall);
await delegate.handleNewGroupCall(groupCall);
}
return groupCall;
}
void onRoomStateChanged(MatrixEvent event) {
Future<void> onRoomStateChanged(MatrixEvent event) async {
final eventType = event.type;
final roomId = event.roomId;
if (eventType == EventTypes.GroupCallPrefix) {
@ -746,11 +748,11 @@ class VoIP {
final content = event.content;
final currentGroupCall = groupCalls[groupCallId];
if (currentGroupCall == null && content['m.terminated'] == null) {
createGroupCallFromRoomStateEvent(event);
await createGroupCallFromRoomStateEvent(event);
} else if (currentGroupCall != null &&
currentGroupCall.groupCallId == groupCallId) {
if (content['m.terminated'] != null) {
currentGroupCall.terminate(emitStateEvent: false);
await currentGroupCall.terminate(emitStateEvent: false);
} else if (content['m.type'] != currentGroupCall.type) {
// TODO: Handle the callType changing when the room state changes
Logs().w(
@ -767,7 +769,7 @@ class VoIP {
if (groupCall == null) {
return;
}
groupCall.onMemberStateChanged(event);
await groupCall.onMemberStateChanged(event);
}
}

View File

@ -89,37 +89,38 @@ extension GroupCallUtils on Room {
final copyGroupCallIds =
states.tryGetMap<String, Event>(EventTypes.GroupCallPrefix);
if (copyGroupCallIds == null) return;
copyGroupCallIds.forEach(
(groupCallId, groupCallEvent) async {
if (groupCallEvent.content.tryGet('m.intent') == 'm.room') return;
if (!groupCallEvent.content.containsKey('m.terminated')) {
Logs().i('found non terminated group call with id $groupCallId');
// call is not empty but check for stale participants (gone offline)
// with expire_ts
bool callExpired = true; // assume call is expired
final callMemberEvents =
states.tryGetMap<String, Event>(EventTypes.GroupCallMemberPrefix);
if (callMemberEvents != null) {
for (var i = 0; i < callMemberEvents.length; i++) {
final groupCallMemberEventMap =
callMemberEvents.entries.toList()[i];
for (final groupCall in copyGroupCallIds.entries) {
final groupCallId = groupCall.key;
final groupCallEvent = groupCall.value;
final groupCallMemberEvent = groupCallMemberEventMap.value;
callExpired =
callMemberStateIsExpired(groupCallMemberEvent, groupCallId);
// no need to iterate further even if one participant says call isn't expired
if (!callExpired) break;
}
}
if (groupCallEvent.content.tryGet('m.intent') == 'm.room') return;
if (!groupCallEvent.content.containsKey('m.terminated')) {
Logs().i('found non terminated group call with id $groupCallId');
// call is not empty but check for stale participants (gone offline)
// with expire_ts
bool callExpired = true; // assume call is expired
final callMemberEvents =
states.tryGetMap<String, Event>(EventTypes.GroupCallMemberPrefix);
if (callMemberEvents != null) {
for (var i = 0; i < callMemberEvents.length; i++) {
final groupCallMemberEventMap =
callMemberEvents.entries.toList()[i];
if (callExpired) {
Logs().i(
'Group call with only expired timestamps detected, terminating');
await sendGroupCallTerminateEvent(groupCallId);
final groupCallMemberEvent = groupCallMemberEventMap.value;
callExpired =
callMemberStateIsExpired(groupCallMemberEvent, groupCallId);
// no need to iterate further even if one participant says call isn't expired
if (!callExpired) break;
}
}
},
);
if (callExpired) {
Logs().i(
'Group call with only expired timestamps detected, terminating');
await sendGroupCallTerminateEvent(groupCallId);
}
}
}
}
/// returns the event_id if successful