Merge branch 'td/glare' into 'main'

fix: glare

Closes famedly/company/product-management#80

See merge request famedly/company/frontend/famedlysdk!1161
This commit is contained in:
td 2023-01-23 06:48:09 +00:00
commit 441df7851b
3 changed files with 65 additions and 6 deletions

View File

@ -172,4 +172,10 @@ Usually there are four media streams in a 1v1 call, which are
They can be get by the methods of `CallSession`. the `newCall.onCallStreamsChanged` event is fired when these streams are added or removed.
When the media stream changes, we can change the UI display according to the priority.
`remoteScreenSharingStream` always needs to be displayed first, followed by `remoteUserMediaStream`
`remoteScreenSharingStream` always needs to be displayed first, followed by `remoteUserMediaStream`
### Glare
Short note on a difference between canHandleNewCall and glare stuff -
- canHandleNewCall should be set to false only if the client can see a connected call. This prevents any other user's call and fires a handleMissedCall.
- glare stuff on the other hand tries to handle collision of invites, if both clients get a invite from one another at the same time, one of the invites should be discarded. (Ideally the other one should be answered by default, but for now we just decided to let it ring to avoid unexpected answers.)

View File

@ -422,6 +422,38 @@ class CallSession {
Future<void> initWithInvite(CallType type, RTCSessionDescription offer,
SDPStreamMetadata? metadata, int lifetime, bool isGroupCall) async {
// glare fixes
final prevCallId = voip.incomingCallRoomId[room.id];
if (prevCallId != null) {
// This is probably an outbound call, but we already have a incoming invite, so let's terminate it.
final prevCall = voip.calls[prevCallId];
if (prevCall != null) {
if (prevCall.inviteOrAnswerSent) {
Logs().d('[glare] invite or answer sent, lex compare now');
if (callId.compareTo(prevCall.callId) > 0) {
Logs().d(
'[glare] new call $callId needs to be canceled because the older one ${prevCall.callId} has a smaller lex');
await hangup();
return;
} else {
Logs().d(
'[glare] nice, lex of newer call $callId is smaller auto accept this here');
/// These fixes do not work all the time because sometimes the code
/// is at an unrecoverable stage (invite already sent when we were
/// checking if we want to send a invite), so commented out answering
/// automatically to prevent unknown cases
// await answer();
// return;
}
} else {
Logs().d(
'[glare] ${prevCall.callId} was still preparing prev call, nvm now cancel it');
await prevCall.hangup();
}
}
}
await _preparePeerConnection();
if (metadata != null) {
_updateRemoteSDPStreamMetadata(metadata);
@ -1039,8 +1071,10 @@ class CallSession {
if (shouldEmit) {
setCallState(CallState.kEnded);
}
if (callId != voip.currentCID) return;
voip.currentCID = null;
voip.calls.remove(callId);
voip.incomingCallRoomId.removeWhere((key, value) => value == callId);
await cleanUp();
if (shouldEmit) {
onCallHangup.add(this);
@ -1099,10 +1133,20 @@ class CallSession {
..transferee = false;
final metadata = _getLocalSDPStreamMetadata();
if (state == CallState.kCreateOffer) {
Logs().d('[glare] new invite sent about to be called');
await sendInviteToCall(
room, callId, Timeouts.lifetimeMs, localPartyId, null, offer.sdp!,
capabilities: callCapabilities, metadata: metadata);
// just incase we ended the call but already sent the invite
if (state == CallState.kEnded) {
await hangup(CallErrorCode.Replaced, false);
setCallState(CallState.kEnded);
return;
}
inviteOrAnswerSent = true;
Logs().d('[glare] set callid because new invite sent');
voip.incomingCallRoomId[room.id] = callId;
setCallState(CallState.kInviteSent);
inviteTimer = Timer(Duration(seconds: Timeouts.callTimeoutSec), () {

View File

@ -23,6 +23,10 @@ abstract class WebRTCDelegate {
void handleGroupCallEnded(GroupCall groupCall);
bool get isBackgroud;
bool get isWeb;
/// This should be set to false if any calls in the client are in kConnected
/// state. If another room tries to call you during a connected call this fires
/// a handleMissedCall
bool get canHandleNewCall => true;
}
@ -38,12 +42,12 @@ class VoIP {
final Client client;
final WebRTCDelegate delegate;
final StreamController<GroupCall> onIncomingGroupCall = StreamController();
void _handleEvent(
Event event,
Function(String roomId, String senderId, Map<String, dynamic> content)
func) =>
func(event.roomId!, event.senderId, event.content);
Map<String, String> incomingCallRoomId = {};
VoIP(this.client, this.delegate) : super() {
client.onCallInvite.stream
@ -163,6 +167,9 @@ class VoIP {
final String? deviceId = content['device_id'];
final call = calls[callId];
Logs().d(
'[glare] got new call ${content.tryGet('call_id')} and currently room id is mapped to ${incomingCallRoomId.tryGet(roomId)}');
if (call != null && call.state == CallState.kEnded) {
// Session already exist.
Logs().v('[VOIP] onCallInvite: Session [$callId] already exist.');
@ -214,11 +221,10 @@ class VoIP {
newCall.remoteUser = await room.requestUser(senderId);
newCall.opponentDeviceId = deviceId;
newCall.opponentSessionId = content['sender_session_id'];
if (!delegate.canHandleNewCall &&
(confId == null || confId != currentGroupCID)) {
Logs().v(
'[VOIP] onCallInvite: Unable to handle new calls, maybe user is busy.');
'[glare] [VOIP] onCallInvite: Unable to handle new calls, maybe user is busy.');
await newCall.reject(reason: 'busy', shouldEmit: false);
delegate.handleMissedCall(newCall);
return;
@ -246,7 +252,7 @@ class VoIP {
currentCID = callId;
// Popup CallingPage for incoming call.
if (!delegate.isBackgroud && confId == null) {
if (!delegate.isBackgroud && confId == null && !newCall.callHasEnded) {
delegate.handleNewCall(newCall);
}
@ -341,7 +347,9 @@ class VoIP {
} else {
Logs().v('[VOIP] onCallHangup: Session [$callId] not found!');
}
currentCID = null;
if (callId == currentCID) {
currentCID = null;
}
}
Future<void> onCallReject(
@ -537,6 +545,7 @@ class VoIP {
return Null as CallSession;
}
final callId = 'cid${DateTime.now().millisecondsSinceEpoch}';
incomingCallRoomId[roomId] = callId;
final opts = CallOptions()
..callId = callId
..type = type