From ecad9c86b353418ea3f700287e4367b70b581a6d Mon Sep 17 00:00:00 2001 From: Christophe Deschamps Date: Fri, 10 Jun 2022 16:50:34 +0200 Subject: [PATCH] Incoming calls from Conference serveur handling - (CallKit) --- Classes/Swift/CallManager.swift | 40 +++++++++++- Classes/Swift/Voip/Theme/VoipTexts.swift | 4 +- Classes/Swift/Voip/ViewModels/CallData.swift | 64 ++++++++++++++++--- .../ActiveCallOrConferenceView.swift | 2 +- 4 files changed, 96 insertions(+), 14 deletions(-) diff --git a/Classes/Swift/CallManager.swift b/Classes/Swift/CallManager.swift index 322574a9d..c12826d17 100644 --- a/Classes/Swift/CallManager.swift +++ b/Classes/Swift/CallManager.swift @@ -206,6 +206,15 @@ import AVFoundation Log.directLog(BCTBX_LOG_MESSAGE, text: "Voice recording in progress, stopping it befoce accepting the call.") chatView.stopVoiceRecording() } + + if (call.callLog?.wasConference() == true) { + // Prevent incoming group call to start in audio only layout + // Do the same as the conference waiting room + callParams.videoEnabled = true + callParams.videoDirection = Core.get().videoActivationPolicy?.automaticallyInitiate == true ? .SendRecv : .RecvOnly + Log.i("[Context] Enabling video on call params to prevent audio-only layout when answering") + } + try call.acceptWithParams(params: callParams) } catch { Log.directLog(BCTBX_LOG_ERROR, text: "accept call failed \(error)") @@ -430,6 +439,11 @@ import AVFoundation CallManager.instance().endCallkit = false } } + + func isConferenceCall(call:Call) -> Bool { + let remoteAddress = call.remoteAddress?.asStringUriOnly() + return remoteAddress?.contains("focus") == true || remoteAddress?.contains("audiovideo") == true + } func onCallStateChanged(core: Core, call: Call, state cstate: Call.State, message: String) { let callLog = call.callLog @@ -452,9 +466,31 @@ import AVFoundation switch cstate { case .IncomingReceived: - let addr = call.remoteAddress; - let displayName = FastAddressBook.displayName(for: addr?.getCobject) ?? "Unknown" + let addr = call.remoteAddress + var displayName = "" + let isConference = isConferenceCall(call: call) + let isEarlyConference = isConference && CallsViewModel.shared.currentCallData.value??.isConferenceCall.value != true // Conference info not be received yet. + if (isConference) { + if (isEarlyConference) { + displayName = VoipTexts.conference_incoming_title + } else { + displayName = "\(VoipTexts.conference_incoming_title): \(CallsViewModel.shared.currentCallData.value??.remoteConferenceSubject.value ?? "") (\(CallsViewModel.shared.currentCallData.value??.conferenceParticipantsCountLabel.value ?? ""))" + } + } else { + displayName = FastAddressBook.displayName(for: addr?.getCobject) ?? "Unknown" + } + if (CallManager.callKitEnabled()) { + if (isEarlyConference) { + CallsViewModel.shared.currentCallData.readCurrentAndObserve { _ in + let uuid = CallManager.instance().providerDelegate.uuids["\(callId!)"] + if (uuid != nil) { + displayName = "\(VoipTexts.conference_incoming_title): \(CallsViewModel.shared.currentCallData.value??.remoteConferenceSubject.value ?? "") (\(CallsViewModel.shared.currentCallData.value??.conferenceParticipantsCountLabel.value ?? ""))" + CallManager.instance().providerDelegate.updateCall(uuid: uuid!, handle: addr!.asStringUriOnly(), hasVideo: video, displayName: displayName) + } + } + } + let uuid = CallManager.instance().providerDelegate.uuids["\(callId!)"] if (uuid != nil) { // Tha app is now registered, updated the call already existed. diff --git a/Classes/Swift/Voip/Theme/VoipTexts.swift b/Classes/Swift/Voip/Theme/VoipTexts.swift index bad54b4ab..303d1aa2e 100644 --- a/Classes/Swift/Voip/Theme/VoipTexts.swift +++ b/Classes/Swift/Voip/Theme/VoipTexts.swift @@ -111,10 +111,10 @@ import UIKit static let conference_info_confirm_removal_delete = NSLocalizedString("Delete",comment:"") static let conference_last_user = NSLocalizedString("All other participants have left the group call",comment:"") static let conference_first_to_join = NSLocalizedString("You're the first to join the group call",comment:"") + static let conference_incoming_title = NSLocalizedString("Incoming group call",comment:"") + static let conference_participants_title = NSLocalizedString("%d participants",comment:"") - - // Call Stats static let call_stats_audio = "Audio" diff --git a/Classes/Swift/Voip/ViewModels/CallData.swift b/Classes/Swift/Voip/ViewModels/CallData.swift index b83c9742f..c70a5cc8b 100644 --- a/Classes/Swift/Voip/ViewModels/CallData.swift +++ b/Classes/Swift/Voip/ViewModels/CallData.swift @@ -32,6 +32,11 @@ class CallData { let isRemotelyRecorded = MutableLiveData() let isInRemoteConference = MutableLiveData() let remoteConferenceSubject = MutableLiveData() + let isConferenceCall = MediatorLiveData() + let conferenceParticipants = MutableLiveData<[Address]>() + let conferenceParticipantsCountLabel = MutableLiveData() + let callKitConferenceLabel = MutableLiveData() + let isOutgoing = MutableLiveData() let isIncoming = MutableLiveData() let callState = MutableLiveData() @@ -58,6 +63,14 @@ class CallData { } ) call.addDelegate(delegate: callDelegate!) + + remoteConferenceSubject.readCurrentAndObserve { _ in + self.isConferenceCall.value = self.remoteConferenceSubject.value?.count ?? 0 > 0 || self.conferenceParticipants.value?.count ?? 0 > 0 + } + conferenceParticipants.readCurrentAndObserve { _ in + self.isConferenceCall.value = self.remoteConferenceSubject.value?.count ?? 0 > 0 || self.conferenceParticipants.value?.count ?? 0 > 0 + } + update() } @@ -86,13 +99,12 @@ class CallData { isPaused.value = isCallPaused() isRemotelyPaused.value = isCallRemotelyPaused() canBePaused.value = canCallBePaused() - let conference = call.conference - isInRemoteConference.value = conference != nil || isCallingAConference() - if (conference != nil) { - remoteConferenceSubject.value = ConferenceViewModel.getConferenceSubject(conference: conference!) - } + + updateConferenceInfo() + isOutgoing.value = isOutGoing() isIncoming.value = isInComing() + if (call.mediaInProgress()) { DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(1)) { self.update() @@ -103,6 +115,43 @@ class CallData { callState.value = call.state } + private func updateConferenceInfo() { + let conference = call.conference + isInRemoteConference.value = conference != nil + if (conference != nil) { + Log.d("[Call] Found conference attached to call") + remoteConferenceSubject.value = ConferenceViewModel.getConferenceSubject(conference: conference!) + Log.d("[Call] Found conference related to this call with subject \(remoteConferenceSubject.value)") + var participantsList:[Address] = [] + conference?.participantList.forEach { + $0.address.map {participantsList.append($0)} + } + conferenceParticipants.value = participantsList + conferenceParticipantsCountLabel.value = VoipTexts.conference_participants_title.replacingOccurrences(of:"%d",with:String(participantsList.count)) + } else { + if let conferenceAddress = getConferenceAddress(call: call), let conferenceInfo = Core.get().findConferenceInformationFromUri(uri:conferenceAddress) { + Log.d("[Call] Found matching conference info with subject: \(conferenceInfo.subject)") + remoteConferenceSubject.value = conferenceInfo.subject + var participantsList:[Address] = [] + conferenceInfo.participants.forEach { + participantsList.append($0) + } + // Add organizer if not in participants list + if let organizer = conferenceInfo.organizer { + if (participantsList.filter { $0.weakEqual(address2: organizer) }.first == nil) { + participantsList.insert(organizer, at:0) + } + conferenceParticipants.value = participantsList + conferenceParticipantsCountLabel.value = VoipTexts.conference_participants_title.replacingOccurrences(of:"%d",with:String(participantsList.count)) + } + } + } + } + + func getConferenceAddress(call: Call) -> Address? { + let remoteContact = call.remoteContact + return call.dir == .Incoming ? (remoteContact != nil ? Core.get().interpretUrl(url: remoteContact) : nil) : call.remoteAddress + } func sendDTMF(dtmf:String) { enteredDTMF.value = enteredDTMF.value! + dtmf @@ -150,8 +199,5 @@ class CallData { isPaused.value = isCallPaused() } - func isCallingAConference() -> Bool { - return CallManager.getAppData(call: call.getCobject!)?.isConference == true - } - + } diff --git a/Classes/Swift/Voip/Views/CompositeViewControllers/ActiveCallOrConferenceView.swift b/Classes/Swift/Voip/Views/CompositeViewControllers/ActiveCallOrConferenceView.swift index 44add5d0b..edba6f0e9 100644 --- a/Classes/Swift/Voip/Views/CompositeViewControllers/ActiveCallOrConferenceView.swift +++ b/Classes/Swift/Voip/Views/CompositeViewControllers/ActiveCallOrConferenceView.swift @@ -328,7 +328,7 @@ import linphonesw if (data?.isOutgoing.value == true || data?.isIncoming.value == true) { PhoneMainView.instance().popView(self.compositeViewDescription()) } else { - if (data!.isCallingAConference()) { + if (data!.isInRemoteConference.value == true) { PhoneMainView.instance().pop(toView: self.compositeViewDescription()) } else { PhoneMainView.instance().changeCurrentView(self.compositeViewDescription())