From 962ca74101815b8f25a106c4f8a6528cf79eb904 Mon Sep 17 00:00:00 2001 From: Isaac <> Date: Fri, 7 Feb 2025 15:22:47 +0400 Subject: [PATCH] [WIP] Conference calls --- .../Sources/PresentationCallManager.swift | 1 + .../Sources/ViewControllerComponent.swift | 3 +- .../Navigation/NavigationController.swift | 16 +- .../Sources/CallKitIntegration.swift | 18 ++ .../Components/MediaStreamComponent.swift | 3 + .../Sources/PresentationCall.swift | 221 +++++++++++++----- .../Sources/PresentationCallManager.swift | 33 ++- .../Sources/PresentationGroupCall.swift | 9 +- .../Sources/VideoChatMicButtonComponent.swift | 5 + .../VideoChatParticipantAvatarComponent.swift | 5 + .../Sources/VideoChatScreen.swift | 28 ++- .../Sources/VoiceChatController.swift | 5 + .../Sources/State/CallSessionManager.swift | 48 ++-- .../Sources/SharedAccountContext.swift | 151 ++++++------ .../Sources/OngoingCallThreadLocalContext.mm | 4 + 15 files changed, 395 insertions(+), 155 deletions(-) diff --git a/submodules/AccountContext/Sources/PresentationCallManager.swift b/submodules/AccountContext/Sources/PresentationCallManager.swift index 45a7d000ac..da4700798b 100644 --- a/submodules/AccountContext/Sources/PresentationCallManager.swift +++ b/submodules/AccountContext/Sources/PresentationCallManager.swift @@ -414,6 +414,7 @@ public protocol PresentationGroupCall: AnyObject { var isStream: Bool { get } var isConference: Bool { get } + var conferenceSource: CallSessionInternalId? { get } var encryptionKeyValue: Data? { get } var audioOutputState: Signal<([AudioSessionOutput], AudioSessionOutput?), NoError> { get } diff --git a/submodules/Components/ViewControllerComponent/Sources/ViewControllerComponent.swift b/submodules/Components/ViewControllerComponent/Sources/ViewControllerComponent.swift index 049c3feff7..b8955192e2 100644 --- a/submodules/Components/ViewControllerComponent/Sources/ViewControllerComponent.swift +++ b/submodules/Components/ViewControllerComponent/Sources/ViewControllerComponent.swift @@ -236,7 +236,7 @@ open class ViewControllerComponentContainer: ViewController { private let context: AccountContext private var theme: Theme - private let component: AnyComponent + public private(set) var component: AnyComponent private var presentationDataDisposable: Disposable? public private(set) var validLayout: ContainerViewLayout? @@ -387,6 +387,7 @@ open class ViewControllerComponentContainer: ViewController { } public func updateComponent(component: AnyComponent, transition: ComponentTransition) { + self.component = component self.node.updateComponent(component: component, transition: transition) } } diff --git a/submodules/Display/Source/Navigation/NavigationController.swift b/submodules/Display/Source/Navigation/NavigationController.swift index 535d333d26..e2bbb32347 100644 --- a/submodules/Display/Source/Navigation/NavigationController.swift +++ b/submodules/Display/Source/Navigation/NavigationController.swift @@ -811,7 +811,11 @@ open class NavigationController: UINavigationController, ContainableController, modalContainer.update(layout: modalContainer.isFlat ? globalOverlayLayout : layout, controllers: navigationLayout.modal[i].controllers, coveredByModalTransition: effectiveModalTransition, transition: containerTransition) if modalContainer.supernode == nil && modalContainer.isReady { - if let previousModalContainer = previousModalContainer { + if let previousModalContainer { + assert(previousModalContainer.supernode != nil) + } + + if let previousModalContainer, previousModalContainer.supernode != nil { self.displayNode.insertSubnode(modalContainer, belowSubnode: previousModalContainer) } else if let inCallStatusBar = self.inCallStatusBar { self.displayNode.insertSubnode(modalContainer, belowSubnode: inCallStatusBar) @@ -1601,6 +1605,16 @@ open class NavigationController: UINavigationController, ContainableController, } public func setViewControllers(_ viewControllers: [UIViewController], animated: Bool, completion: @escaping () -> Void) { + let requestedViewControllers = viewControllers + var viewControllers: [UIViewController] = [] + for controller in requestedViewControllers { + if !viewControllers.contains(where: { $0 === controller }) { + viewControllers.append(controller) + } else { + assert(true) + } + } + for i in 0 ..< viewControllers.count { guard let controller = viewControllers[i] as? ViewController else { continue diff --git a/submodules/TelegramCallsUI/Sources/CallKitIntegration.swift b/submodules/TelegramCallsUI/Sources/CallKitIntegration.swift index 2efdb56331..2e67ed442b 100644 --- a/submodules/TelegramCallsUI/Sources/CallKitIntegration.swift +++ b/submodules/TelegramCallsUI/Sources/CallKitIntegration.swift @@ -94,6 +94,10 @@ public final class CallKitIntegration { public func applyVoiceChatOutputMode(outputMode: AudioSessionOutputMode) { sharedProviderDelegate?.applyVoiceChatOutputMode(outputMode: outputMode) } + + public func updateCallIsConference(uuid: UUID) { + sharedProviderDelegate?.updateCallIsConference(uuid: uuid) + } } @available(iOSApplicationExtension 10.0, iOS 10.0, *) @@ -276,6 +280,20 @@ class CallKitProviderDelegate: NSObject, CXProviderDelegate { self.provider.reportOutgoingCall(with: uuid, connectedAt: date) } + func updateCallIsConference(uuid: UUID) { + let update = CXCallUpdate() + let handle = CXHandle(type: .generic, value: "\(uuid)") + update.remoteHandle = handle + //TODO:localize + update.localizedCallerName = "Group Call" + update.supportsHolding = false + update.supportsGrouping = false + update.supportsUngrouping = false + update.supportsDTMF = false + + self.provider.reportCall(with: uuid, updated: update) + } + func providerDidReset(_ provider: CXProvider) { Logger.shared.log("CallKitIntegration", "providerDidReset") diff --git a/submodules/TelegramCallsUI/Sources/Components/MediaStreamComponent.swift b/submodules/TelegramCallsUI/Sources/Components/MediaStreamComponent.swift index 7910f6c09e..be21e8c204 100644 --- a/submodules/TelegramCallsUI/Sources/Components/MediaStreamComponent.swift +++ b/submodules/TelegramCallsUI/Sources/Components/MediaStreamComponent.swift @@ -1038,6 +1038,9 @@ public final class MediaStreamComponentController: ViewControllerComponentContai fatalError("init(coder:) has not been implemented") } + public func updateCall(call: VideoChatCall) { + } + override public func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) diff --git a/submodules/TelegramCallsUI/Sources/PresentationCall.swift b/submodules/TelegramCallsUI/Sources/PresentationCall.swift index d8dcb4679c..a9568f69ae 100644 --- a/submodules/TelegramCallsUI/Sources/PresentationCall.swift +++ b/submodules/TelegramCallsUI/Sources/PresentationCall.swift @@ -325,6 +325,8 @@ public final class PresentationCallImpl: PresentationCall { private var localVideoEndpointId: String? private var remoteVideoEndpointId: String? + private var isMovedToConference: Bool = false + init( context: AccountContext, audioSession: ManagedAudioSession, @@ -541,6 +543,89 @@ public final class PresentationCallImpl: PresentationCall { } } + public func resetAsMovedToConference() { + if self.isMovedToConference { + return + } + self.isMovedToConference = true + + self.sharedAudioContext = nil + self.sessionState = nil + self.callContextState = nil + self.ongoingContext = nil + self.ongoingContextStateDisposable?.dispose() + self.ongoingContextStateDisposable = nil + self.ongoingContextIsFailedDisposable?.dispose() + self.ongoingContextIsFailedDisposable = nil + self.ongoingContextIsDroppedDisposable?.dispose() + self.ongoingContextIsDroppedDisposable = nil + self.didDropCall = false + self.requestedVideoAspect = nil + self.reception = nil + self.receptionDisposable?.dispose() + self.receptionDisposable = nil + self.audioLevelDisposable?.dispose() + self.audioLevelDisposable = nil + self.reportedIncomingCall = false + self.batteryLevelDisposable?.dispose() + self.batteryLevelDisposable = nil + self.callWasActive = false + self.shouldPresentCallRating = false + self.previousVideoState = nil + self.previousRemoteVideoState = nil + self.previousRemoteAudioState = nil + self.previousRemoteBatteryLevel = nil + self.sessionStateDisposable?.dispose() + self.sessionStateDisposable = nil + self.activeTimestamp = nil + self.audioSessionControl = nil + self.audioSessionDisposable?.dispose() + self.audioSessionDisposable = nil + self.audioSessionShouldBeActiveDisposable?.dispose() + self.audioSessionShouldBeActiveDisposable = nil + self.audioSessionActiveDisposable?.dispose() + self.audioSessionActiveDisposable = nil + self.isAudioSessionActive = false + self.currentTone = nil + + self.dropCallKitCallTimer?.invalidate() + self.dropCallKitCallTimer = nil + + self.droppedCall = true + + self.videoCapturer = nil + + self.screencastBufferServerContext = nil + self.screencastCapturer = nil + self.isScreencastActive = false + + if let proximityManagerIndex = self.proximityManagerIndex { + DeviceProximityManager.shared().remove(proximityManagerIndex) + self.proximityManagerIndex = nil + } + + self.screencastFramesDisposable.set(nil) + self.screencastAudioDataDisposable.set(nil) + self.screencastStateDisposable.set(nil) + + self.conferenceCallImpl = nil + + self.conferenceCallDisposable?.dispose() + self.conferenceCallDisposable = nil + + self.upgradedToConferenceCompletions.removeAll() + + self.waitForConferenceCallReadyDisposable?.dispose() + self.waitForConferenceCallReadyDisposable = nil + + self.pendingInviteToConferencePeerIds.removeAll() + + self.localVideoEndpointId = nil + self.remoteVideoEndpointId = nil + + self.callKitIntegration?.updateCallIsConference(uuid: self.internalId) + } + func internal_markAsCanBeRemoved() { if !self.didSetCanBeRemoved { self.didSetCanBeRemoved = true @@ -549,6 +634,9 @@ public final class PresentationCallImpl: PresentationCall { } private func updateSessionState(sessionState: CallSession, callContextState: OngoingCallContextState?, reception: Int32?, audioSessionControl: ManagedAudioSessionControl?) { + if self.isMovedToConference { + return + } self.reception = reception if let ongoingContext = self.ongoingContext { @@ -730,11 +818,11 @@ public final class PresentationCallImpl: PresentationCall { presentationState = PresentationCallState(state: .terminated(id, reason, self.callWasActive && (options.contains(.reportRating) || self.shouldPresentCallRating)), videoState: mappedVideoState, remoteVideoState: .inactive, remoteAudioState: mappedRemoteAudioState, remoteBatteryLevel: mappedRemoteBatteryLevel) case let .requesting(ringing, _): presentationState = PresentationCallState(state: .requesting(ringing), videoState: mappedVideoState, remoteVideoState: mappedRemoteVideoState, remoteAudioState: mappedRemoteAudioState, remoteBatteryLevel: mappedRemoteBatteryLevel) - case let .active(_, _, keyVisualHash, _, _, _, _, _, _), let .switchedToConference(_, keyVisualHash, _): + case let .active(_, _, keyVisualHash, _, _, _, _, _, _, _), let .switchedToConference(_, keyVisualHash, _): self.callWasActive = true var isConference = false - if case let .active(_, _, _, _, _, _, _, _, conferenceCall) = sessionState.state { + if case let .active(_, _, _, _, _, _, _, _, conferenceCall, _) = sessionState.state { isConference = conferenceCall != nil } else if case .switchedToConference = sessionState.state { isConference = true @@ -774,8 +862,8 @@ public final class PresentationCallImpl: PresentationCall { var conferenceCallData: (key: Data, keyVisualHash: Data, conferenceCall: GroupCallReference)? var conferenceFromCallId: CallId? switch sessionState.state { - case let .active(id, key, keyVisualHash, _, _, _, _, _, conferenceCall): - if let conferenceCall { + case let .active(id, key, keyVisualHash, _, _, _, _, _, conferenceCall, isIncomingConference): + if let conferenceCall, !isIncomingConference { conferenceFromCallId = id conferenceCallData = (key, keyVisualHash, conferenceCall) } @@ -789,6 +877,10 @@ public final class PresentationCallImpl: PresentationCall { if self.conferenceCallDisposable == nil { self.conferenceCallDisposable = EmptyDisposable + #if DEBUG + print("Switching to conference call with encryption key: \(key.base64EncodedString())") + #endif + let conferenceCall = PresentationGroupCallImpl( accountContext: self.context, audioSession: self.audioSession, @@ -810,6 +902,7 @@ public final class PresentationCallImpl: PresentationCall { isStream: false, encryptionKey: (key, 1), conferenceFromCallId: conferenceFromCallId, + conferenceSourceId: self.internalId, isConference: true, sharedAudioContext: self.sharedAudioContext ) @@ -917,7 +1010,7 @@ public final class PresentationCallImpl: PresentationCall { if let _ = audioSessionControl { self.audioSessionShouldBeActive.set(true) } - case let .active(id, key, _, connections, maxLayer, version, customParameters, allowsP2P, _): + case let .active(id, key, _, connections, maxLayer, version, customParameters, allowsP2P, _, _): self.audioSessionShouldBeActive.set(true) if conferenceCallData != nil { @@ -1044,7 +1137,7 @@ public final class PresentationCallImpl: PresentationCall { } var isConference = false - if case let .active(_, _, _, _, _, _, _, _, conferenceCall) = sessionState.state { + if case let .active(_, _, _, _, _, _, _, _, conferenceCall, _) = sessionState.state { isConference = conferenceCall != nil } else if case .switchedToConference = sessionState.state { isConference = true @@ -1072,59 +1165,10 @@ public final class PresentationCallImpl: PresentationCall { } } - private func requestMediaChannelDescriptions(ssrcs: Set, completion: @escaping ([OngoingGroupCallContext.MediaChannelDescription]) -> Void) -> Disposable { - /*func extractMediaChannelDescriptions(remainingSsrcs: inout Set, participants: [GroupCallParticipantsContext.Participant], into result: inout [OngoingGroupCallContext.MediaChannelDescription]) { - for participant in participants { - guard let audioSsrc = participant.ssrc else { - continue - } - - if remainingSsrcs.contains(audioSsrc) { - remainingSsrcs.remove(audioSsrc) - - result.append(OngoingGroupCallContext.MediaChannelDescription( - kind: .audio, - audioSsrc: audioSsrc, - videoDescription: nil - )) - } - - if let screencastSsrc = participant.presentationDescription?.audioSsrc { - if remainingSsrcs.contains(screencastSsrc) { - remainingSsrcs.remove(screencastSsrc) - - result.append(OngoingGroupCallContext.MediaChannelDescription( - kind: .audio, - audioSsrc: screencastSsrc, - videoDescription: nil - )) - } - } - } - } - - var remainingSsrcs = ssrcs - var result: [OngoingGroupCallContext.MediaChannelDescription] = [] - - if let membersValue = self.membersValue { - extractMediaChannelDescriptions(remainingSsrcs: &remainingSsrcs, participants: membersValue.participants, into: &result) - } - - if !remainingSsrcs.isEmpty, let callInfo = self.internalState.callInfo { - return (self.accountContext.engine.calls.getGroupCallParticipants(callId: callInfo.id, accessHash: callInfo.accessHash, offset: "", ssrcs: Array(remainingSsrcs), limit: 100, sortAscending: callInfo.sortAscending) - |> deliverOnMainQueue).start(next: { state in - extractMediaChannelDescriptions(remainingSsrcs: &remainingSsrcs, participants: state.participants, into: &result) - - completion(result) - }) - } else { - completion(result) - return EmptyDisposable - }*/ - return EmptyDisposable - } - private func updateTone(_ state: PresentationCallState, callContextState: OngoingCallContextState?, previous: CallSession?) { + if self.isMovedToConference { + return + } var tone: PresentationCallTone? if let callContextState = callContextState, case .reconnecting = callContextState.state { if !self.isVideo { @@ -1174,16 +1218,25 @@ public final class PresentationCallImpl: PresentationCall { } private func updateIsAudioSessionActive(_ value: Bool) { + if self.isMovedToConference { + return + } if self.isAudioSessionActive != value { self.isAudioSessionActive = value } } public func answer() { + if self.isMovedToConference { + return + } self.answer(fromCallKitAction: false) } func answer(fromCallKitAction: Bool) { + if self.isMovedToConference { + return + } let (presentationData, present, openSettings) = self.getDeviceAccessData() DeviceAccess.authorizeAccess(to: .microphone(.voiceCall), presentationData: presentationData, present: { c, a in @@ -1234,6 +1287,9 @@ public final class PresentationCallImpl: PresentationCall { } public func hangUp() -> Signal { + if self.isMovedToConference { + return .single(true) + } let debugLogValue = Promise() self.callSessionManager.drop(internalId: self.internalId, reason: .hangUp, debugLog: debugLogValue.get()) self.ongoingContext?.stop(debugLogValue: debugLogValue) @@ -1242,22 +1298,34 @@ public final class PresentationCallImpl: PresentationCall { } public func rejectBusy() { + if self.isMovedToConference { + return + } self.callSessionManager.drop(internalId: self.internalId, reason: .busy, debugLog: .single(nil)) let debugLog = Promise() self.ongoingContext?.stop(debugLogValue: debugLog) } public func toggleIsMuted() { + if self.isMovedToConference { + return + } self.setIsMuted(!self.isMutedValue) } public func setIsMuted(_ value: Bool) { + if self.isMovedToConference { + return + } self.isMutedValue = value self.isMutedPromise.set(self.isMutedValue) self.ongoingContext?.setIsMuted(self.isMutedValue) } public func requestVideo() { + if self.isMovedToConference { + return + } if self.videoCapturer == nil { let videoCapturer = OngoingCallVideoCapturer() self.videoCapturer = videoCapturer @@ -1270,6 +1338,9 @@ public final class PresentationCallImpl: PresentationCall { } public func requestVideo(capturer: OngoingCallVideoCapturer) { + if self.isMovedToConference { + return + } if self.videoCapturer == nil { self.videoCapturer = capturer } @@ -1281,11 +1352,17 @@ public final class PresentationCallImpl: PresentationCall { } public func setRequestedVideoAspect(_ aspect: Float) { + if self.isMovedToConference { + return + } self.requestedVideoAspect = aspect self.ongoingContext?.setRequestedVideoAspect(aspect) } public func disableVideo() { + if self.isMovedToConference { + return + } if let _ = self.videoCapturer { self.videoCapturer = nil if let ongoingContext = self.ongoingContext { @@ -1295,6 +1372,9 @@ public final class PresentationCallImpl: PresentationCall { } private func resetScreencastContext() { + if self.isMovedToConference { + return + } let basePath = self.context.sharedContext.basePath + "/broadcast-coordination" let screencastBufferServerContext = IpcGroupCallBufferAppContext(basePath: basePath) self.screencastBufferServerContext = screencastBufferServerContext @@ -1332,6 +1412,9 @@ public final class PresentationCallImpl: PresentationCall { } private func requestScreencast() { + if self.isMovedToConference { + return + } self.disableVideo() if let screencastCapturer = self.screencastCapturer { @@ -1343,6 +1426,9 @@ public final class PresentationCallImpl: PresentationCall { } func disableScreencast(reset: Bool = true) { + if self.isMovedToConference { + return + } if self.isScreencastActive { if let _ = self.videoCapturer { self.videoCapturer = nil @@ -1357,10 +1443,16 @@ public final class PresentationCallImpl: PresentationCall { } public func setOutgoingVideoIsPaused(_ isPaused: Bool) { + if self.isMovedToConference { + return + } self.videoCapturer?.setIsVideoEnabled(!isPaused) } public func upgradeToConference(invitePeerIds: [EnginePeer.Id], completion: @escaping (PresentationGroupCall) -> Void) -> Disposable { + if self.isMovedToConference { + return EmptyDisposable + } if let conferenceCall = self.conferenceCall { completion(conferenceCall) return EmptyDisposable @@ -1385,6 +1477,9 @@ public final class PresentationCallImpl: PresentationCall { } public func setCurrentAudioOutput(_ output: AudioSessionOutput) { + if self.isMovedToConference { + return + } if let sharedAudioContext = self.sharedAudioContext { sharedAudioContext.setCurrentAudioOutput(output) return @@ -1416,6 +1511,9 @@ public final class PresentationCallImpl: PresentationCall { } func video(isIncoming: Bool) -> Signal? { + if self.isMovedToConference { + return nil + } if isIncoming { if let ongoingContext = self.ongoingContext { return ongoingContext.video(isIncoming: isIncoming) @@ -1430,6 +1528,10 @@ public final class PresentationCallImpl: PresentationCall { } public func makeOutgoingVideoView(completion: @escaping (PresentationCallVideoView?) -> Void) { + if self.isMovedToConference { + completion(nil) + return + } if self.videoCapturer == nil { let videoCapturer = OngoingCallVideoCapturer() self.videoCapturer = videoCapturer @@ -1504,6 +1606,9 @@ public final class PresentationCallImpl: PresentationCall { } public func switchVideoCamera() { + if self.isMovedToConference { + return + } self.useFrontCamera = !self.useFrontCamera self.videoCapturer?.switchVideoInput(isFront: self.useFrontCamera) } diff --git a/submodules/TelegramCallsUI/Sources/PresentationCallManager.swift b/submodules/TelegramCallsUI/Sources/PresentationCallManager.swift index 71be61b833..7dfa00cb44 100644 --- a/submodules/TelegramCallsUI/Sources/PresentationCallManager.swift +++ b/submodules/TelegramCallsUI/Sources/PresentationCallManager.swift @@ -62,6 +62,7 @@ public final class PresentationCallManagerImpl: PresentationCallManager { private let removeCurrentCallDisposable = MetaDisposable() private let removeCurrentGroupCallDisposable = MetaDisposable() private var callToConferenceDisposable: Disposable? + private var isConferenceReadyDisposable: Disposable? private var currentUpgradedToConferenceCallId: CallSessionInternalId? private var currentGroupCallValue: VideoChatCall? @@ -251,7 +252,19 @@ public final class PresentationCallManagerImpl: PresentationCallManager { } endCallImpl = { [weak self] uuid in - if let strongSelf = self, let currentCall = strongSelf.currentCall { + guard let self else { + return .single(false) + } + + if let currentGroupCall = self.currentGroupCall { + switch currentGroupCall { + case let .conferenceSource(conferenceSource): + return conferenceSource.hangUp() + case let .group(groupCall): + return groupCall.leave(terminateIfPossible: false) + } + } + if let currentCall = self.currentCall { return currentCall.hangUp() } else { return .single(false) @@ -300,6 +313,7 @@ public final class PresentationCallManagerImpl: PresentationCallManager { self.proxyServerDisposable?.dispose() self.callSettingsDisposable?.dispose() self.callToConferenceDisposable?.dispose() + self.isConferenceReadyDisposable?.dispose() } private func ringingStatesUpdated(_ ringingStates: [(AccountContext, Peer, CallSessionRingingState, Bool, NetworkType)], enableCallKit: Bool) { @@ -334,7 +348,7 @@ public final class PresentationCallManagerImpl: PresentationCallManager { internalId: firstState.2.id, peerId: firstState.2.peerId, isOutgoing: false, - isIncomingConference: firstState.2.isConference, + isIncomingConference: firstState.2.isIncomingConference, peer: EnginePeer(firstState.1), proxyServer: strongSelf.proxyServer, auxiliaryServers: [], @@ -687,13 +701,21 @@ public final class PresentationCallManagerImpl: PresentationCallManager { } if self.currentGroupCallValue != value { + if case let .group(groupCall) = self.currentGroupCallValue, let conferenceSourceId = groupCall.conferenceSource { + groupCall.accountContext.account.callSessionManager.drop(internalId: conferenceSourceId, reason: .hangUp, debugLog: .single(nil)) + (groupCall as! PresentationGroupCallImpl).callKitIntegration?.dropCall(uuid: conferenceSourceId) + } + self.currentGroupCallValue = value + self.isConferenceReadyDisposable?.dispose() + self.isConferenceReadyDisposable = nil + if let value { switch value { case let .conferenceSource(conferenceSource): - self.callToConferenceDisposable?.dispose() - self.callToConferenceDisposable = (conferenceSource.conferenceState + self.isConferenceReadyDisposable?.dispose() + self.isConferenceReadyDisposable = (conferenceSource.conferenceState |> filter { value in if let value, case .ready = value { return true @@ -709,6 +731,7 @@ public final class PresentationCallManagerImpl: PresentationCallManager { guard let groupCall = conferenceSource.conferenceCall else { return } + (conferenceSource as! PresentationCallImpl).resetAsMovedToConference() self.updateCurrentGroupCall(.group(groupCall)) }) @@ -826,6 +849,7 @@ public final class PresentationCallManagerImpl: PresentationCallManager { isStream: false, encryptionKey: nil, conferenceFromCallId: nil, + conferenceSourceId: nil, isConference: false, sharedAudioContext: nil ) @@ -1052,6 +1076,7 @@ public final class PresentationCallManagerImpl: PresentationCallManager { isStream: initialCall.isStream ?? false, encryptionKey: nil, conferenceFromCallId: nil, + conferenceSourceId: nil, isConference: false, sharedAudioContext: nil ) diff --git a/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift b/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift index 436835c834..abdae27842 100644 --- a/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift +++ b/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift @@ -770,7 +770,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { public let account: Account public let accountContext: AccountContext private let audioSession: ManagedAudioSession - private let callKitIntegration: CallKitIntegration? + public let callKitIntegration: CallKitIntegration? public var isIntegratedWithCallKit: Bool { return self.callKitIntegration != nil } @@ -1057,6 +1057,11 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { } } + private let conferenceSourceId: CallSessionInternalId? + public var conferenceSource: CallSessionInternalId? { + return self.conferenceSourceId + } + var internal_isRemoteConnected = Promise() private var internal_isRemoteConnectedDisposable: Disposable? @@ -1081,6 +1086,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { isStream: Bool, encryptionKey: (key: Data, fingerprint: Int64)?, conferenceFromCallId: CallId?, + conferenceSourceId: CallSessionInternalId?, isConference: Bool, sharedAudioContext: SharedCallAudioContext? ) { @@ -1109,6 +1115,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { self.hasScreencast = false self.isStream = isStream self.conferenceFromCallId = conferenceFromCallId + self.conferenceSourceId = conferenceSourceId self.isConference = isConference self.encryptionKey = encryptionKey diff --git a/submodules/TelegramCallsUI/Sources/VideoChatMicButtonComponent.swift b/submodules/TelegramCallsUI/Sources/VideoChatMicButtonComponent.swift index 2460da8ea5..80c340ef08 100644 --- a/submodules/TelegramCallsUI/Sources/VideoChatMicButtonComponent.swift +++ b/submodules/TelegramCallsUI/Sources/VideoChatMicButtonComponent.swift @@ -326,6 +326,11 @@ final class VideoChatMicButtonComponent: Component { let previousComponent = self.component self.component = component + if let previousComponent, previousComponent.call != component.call { + self.audioLevelDisposable?.dispose() + self.audioLevelDisposable = nil + } + let alphaTransition: ComponentTransition = transition.animation.isImmediate ? .immediate : .easeInOut(duration: 0.2) let titleText: String diff --git a/submodules/TelegramCallsUI/Sources/VideoChatParticipantAvatarComponent.swift b/submodules/TelegramCallsUI/Sources/VideoChatParticipantAvatarComponent.swift index e538a7179b..2eede132d0 100644 --- a/submodules/TelegramCallsUI/Sources/VideoChatParticipantAvatarComponent.swift +++ b/submodules/TelegramCallsUI/Sources/VideoChatParticipantAvatarComponent.swift @@ -230,6 +230,11 @@ final class VideoChatParticipantAvatarComponent: Component { let previousComponent = self.component self.component = component + if let previousComponent, previousComponent.call != component.call { + self.audioLevelDisposable?.dispose() + self.audioLevelDisposable = nil + } + let avatarNode: AvatarNode if let current = self.avatarNode { avatarNode = current diff --git a/submodules/TelegramCallsUI/Sources/VideoChatScreen.swift b/submodules/TelegramCallsUI/Sources/VideoChatScreen.swift index 3077cd803e..ee64db2fcc 100644 --- a/submodules/TelegramCallsUI/Sources/VideoChatScreen.swift +++ b/submodules/TelegramCallsUI/Sources/VideoChatScreen.swift @@ -163,6 +163,16 @@ final class VideoChatScreenComponent: Component { } static func ==(lhs: VideoChatScreenComponent, rhs: VideoChatScreenComponent) -> Bool { + if lhs === rhs { + return true + } + if lhs.initialData !== rhs.initialData { + return false + } + if lhs.initialCall != rhs.initialCall { + return false + } + return true } @@ -1082,7 +1092,12 @@ final class VideoChatScreenComponent: Component { self.callState = component.initialData.callState } - var call = self.currentCall ?? component.initialCall + var call: VideoChatCall + if let previousComponent = self.component, previousComponent.initialCall != component.initialCall { + call = component.initialCall + } else { + call = self.currentCall ?? component.initialCall + } if case let .conferenceSource(conferenceSource) = call, let conferenceCall = conferenceSource.conferenceCall, conferenceSource.conferenceStateValue == .ready { call = .group(conferenceCall) } @@ -2517,6 +2532,17 @@ final class VideoChatScreenV2Impl: ViewControllerComponentContainer, VoiceChatCo self.idleTimerExtensionDisposable?.dispose() } + func updateCall(call: VideoChatCall) { + self.call = call + if let component = self.component.wrapped as? VideoChatScreenComponent { + // This is only to clear the reference to regular call + self.updateComponent(component: AnyComponent(VideoChatScreenComponent( + initialData: component.initialData, + initialCall: call + )), transition: .immediate) + } + } + override public func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) diff --git a/submodules/TelegramCallsUI/Sources/VoiceChatController.swift b/submodules/TelegramCallsUI/Sources/VoiceChatController.swift index 60658434bb..edeb62ba96 100644 --- a/submodules/TelegramCallsUI/Sources/VoiceChatController.swift +++ b/submodules/TelegramCallsUI/Sources/VoiceChatController.swift @@ -248,6 +248,8 @@ public protocol VoiceChatController: ViewController { var onViewDidAppear: (() -> Void)? { get set } var onViewDidDisappear: (() -> Void)? { get set } + func updateCall(call: VideoChatCall) + func dismiss(closing: Bool, manual: Bool) } @@ -6938,6 +6940,9 @@ final class VoiceChatControllerImpl: ViewController, VoiceChatController { } } + func updateCall(call: VideoChatCall) { + } + override public func loadDisplayNode() { self.displayNode = Node(controller: self, sharedContext: self.sharedContext, call: self.callImpl) diff --git a/submodules/TelegramCore/Sources/State/CallSessionManager.swift b/submodules/TelegramCore/Sources/State/CallSessionManager.swift index 51d4f5a6dc..94929eac4a 100644 --- a/submodules/TelegramCore/Sources/State/CallSessionManager.swift +++ b/submodules/TelegramCore/Sources/State/CallSessionManager.swift @@ -79,7 +79,7 @@ enum CallSessionInternalState { case requesting(a: Data, conferenceCall: GroupCallReference?, disposable: Disposable) case requested(id: Int64, accessHash: Int64, a: Data, gA: Data, config: SecretChatEncryptionConfig, remoteConfirmationTimestamp: Int32?, conferenceCall: GroupCallReference?) case confirming(id: Int64, accessHash: Int64, key: Data, keyId: Int64, keyVisualHash: Data, conferenceCall: GroupCallReference?, disposable: Disposable) - case active(id: Int64, accessHash: Int64, beginTimestamp: Int32, key: Data, keyId: Int64, keyVisualHash: Data, connections: CallSessionConnectionSet, maxLayer: Int32, version: String, customParameters: String?, allowsP2P: Bool, conferenceCall: GroupCallReference?) + case active(id: Int64, accessHash: Int64, beginTimestamp: Int32, key: Data, keyId: Int64, keyVisualHash: Data, connections: CallSessionConnectionSet, maxLayer: Int32, version: String, customParameters: String?, allowsP2P: Bool, conferenceCall: GroupCallReference?, willSwitchToConference: Bool) case switchedToConference(key: Data, keyVisualHash: Data, conferenceCall: GroupCallReference) case dropping(reason: CallSessionTerminationReason, disposable: Disposable) case terminated(id: Int64?, accessHash: Int64?, reason: CallSessionTerminationReason, reportRating: Bool, sendDebugLogs: Bool) @@ -98,7 +98,7 @@ enum CallSessionInternalState { return id case let .confirming(id, _, _, _, _, _, _): return id - case let .active(id, _, _, _, _, _, _, _, _, _, _, _): + case let .active(id, _, _, _, _, _, _, _, _, _, _, _, _): return id case .switchedToConference: return nil @@ -137,7 +137,7 @@ public struct CallSessionRingingState: Equatable { public let peerId: PeerId public let isVideo: Bool public let isVideoPossible: Bool - public let isConference: Bool + public let isIncomingConference: Bool } public enum DropCallReason { @@ -166,7 +166,7 @@ public enum CallSessionState { case ringing case accepting case requesting(ringing: Bool, conferenceCall: GroupCallReference?) - case active(id: CallId, key: Data, keyVisualHash: Data, connections: CallSessionConnectionSet, maxLayer: Int32, version: String, customParameters: String?, allowsP2P: Bool, conferenceCall: GroupCallReference?) + case active(id: CallId, key: Data, keyVisualHash: Data, connections: CallSessionConnectionSet, maxLayer: Int32, version: String, customParameters: String?, allowsP2P: Bool, conferenceCall: GroupCallReference?, willSwitchToConference: Bool) case switchedToConference(key: Data, keyVisualHash: Data, conferenceCall: GroupCallReference) case dropping(reason: CallSessionTerminationReason) case terminated(id: CallId?, reason: CallSessionTerminationReason, options: CallTerminationOptions) @@ -183,8 +183,8 @@ public enum CallSessionState { self = .requesting(ringing: true, conferenceCall: conferenceCall) case let .requested(_, _, _, _, _, remoteConfirmationTimestamp, conferenceCall): self = .requesting(ringing: remoteConfirmationTimestamp != nil, conferenceCall: conferenceCall) - case let .active(id, accessHash, _, key, _, keyVisualHash, connections, maxLayer, version, customParameters, allowsP2P, conferenceCall): - self = .active(id: CallId(id: id, accessHash: accessHash), key: key, keyVisualHash: keyVisualHash, connections: connections, maxLayer: maxLayer, version: version, customParameters: customParameters, allowsP2P: allowsP2P, conferenceCall: conferenceCall) + case let .active(id, accessHash, _, key, _, keyVisualHash, connections, maxLayer, version, customParameters, allowsP2P, conferenceCall, willSwitchToConference): + self = .active(id: CallId(id: id, accessHash: accessHash), key: key, keyVisualHash: keyVisualHash, connections: connections, maxLayer: maxLayer, version: version, customParameters: customParameters, allowsP2P: allowsP2P, conferenceCall: conferenceCall, willSwitchToConference: willSwitchToConference) case let .dropping(reason, _): self = .dropping(reason: reason) case let .terminated(id, accessHash, reason, reportRating, sendDebugLogs): @@ -335,7 +335,7 @@ private func parseConnectionSet(primary: Api.PhoneConnection, alternative: [Api. private final class CallSessionContext { let peerId: PeerId let isOutgoing: Bool - let isConference: Bool + let isIncomingConference: Bool var type: CallSession.CallType var isVideoPossible: Bool let pendingConference: (conference: GroupCallReference, encryptionKey: Data)? @@ -355,10 +355,10 @@ private final class CallSessionContext { } } - init(peerId: PeerId, isOutgoing: Bool, isConference: Bool, type: CallSession.CallType, isVideoPossible: Bool, pendingConference: (conference: GroupCallReference, encryptionKey: Data)?, state: CallSessionInternalState) { + init(peerId: PeerId, isOutgoing: Bool, isIncomingConference: Bool, type: CallSession.CallType, isVideoPossible: Bool, pendingConference: (conference: GroupCallReference, encryptionKey: Data)?, state: CallSessionInternalState) { self.peerId = peerId self.isOutgoing = isOutgoing - self.isConference = isConference + self.isIncomingConference = isIncomingConference self.type = type self.isVideoPossible = isVideoPossible self.pendingConference = pendingConference @@ -555,7 +555,7 @@ private final class CallSessionManagerContext { peerId: context.peerId, isVideo: context.type == .video, isVideoPossible: context.isVideoPossible, - isConference: context.isConference + isIncomingConference: context.isIncomingConference )) } } @@ -599,7 +599,7 @@ private final class CallSessionManagerContext { //#endif let internalId = CallSessionManager.getStableIncomingUUID(stableId: stableId) - let context = CallSessionContext(peerId: peerId, isOutgoing: false, isConference: conferenceCall != nil, type: isVideo ? .video : .audio, isVideoPossible: isVideoPossible, pendingConference: nil, state: .ringing(id: stableId, accessHash: accessHash, gAHash: gAHash, b: b, versions: versions, conferenceCall: conferenceCall)) + let context = CallSessionContext(peerId: peerId, isOutgoing: false, isIncomingConference: conferenceCall != nil, type: isVideo ? .video : .audio, isVideoPossible: isVideoPossible, pendingConference: nil, state: .ringing(id: stableId, accessHash: accessHash, gAHash: gAHash, b: b, versions: versions, conferenceCall: conferenceCall)) self.contexts[internalId] = context let queue = self.queue @@ -653,7 +653,7 @@ private final class CallSessionManagerContext { case let .accepting(id, accessHash, _, _, _, disposable): dropData = (id, accessHash, .abort) disposable.dispose() - case let .active(id, accessHash, beginTimestamp, _, _, _, _, _, _, _, _, _): + case let .active(id, accessHash, beginTimestamp, _, _, _, _, _, _, _, _, _, _): let duration = max(0, Int32(CFAbsoluteTimeGetCurrent()) - beginTimestamp) let internalReason: DropCallSessionReason switch reason { @@ -755,7 +755,7 @@ private final class CallSessionManagerContext { var dropData: (CallSessionStableId, Int64)? let isVideo = context.type == .video switch context.state { - case let .active(id, accessHash, _, _, _, _, _, _, _, _, _, _): + case let .active(id, accessHash, _, _, _, _, _, _, _, _, _, _, _): dropData = (id, accessHash) default: break @@ -807,7 +807,7 @@ private final class CallSessionManagerContext { strongSelf.contextUpdated(internalId: internalId) case let .call(config, gA, timestamp, connections, maxLayer, version, customParameters, allowsP2P, conferenceCall): if let (key, keyId, keyVisualHash) = strongSelf.makeSessionEncryptionKey(config: config, gAHash: gAHash, b: b, gA: gA) { - context.state = .active(id: id, accessHash: accessHash, beginTimestamp: timestamp, key: key, keyId: keyId, keyVisualHash: keyVisualHash, connections: connections, maxLayer: maxLayer, version: version, customParameters: customParameters, allowsP2P: allowsP2P, conferenceCall: conferenceCall) + context.state = .active(id: id, accessHash: accessHash, beginTimestamp: timestamp, key: key, keyId: keyId, keyVisualHash: keyVisualHash, connections: connections, maxLayer: maxLayer, version: version, customParameters: customParameters, allowsP2P: allowsP2P, conferenceCall: conferenceCall, willSwitchToConference: context.isIncomingConference) strongSelf.contextUpdated(internalId: internalId) } else { strongSelf.drop(internalId: internalId, reason: .disconnect, debugLog: .single(nil)) @@ -828,7 +828,7 @@ private final class CallSessionManagerContext { func sendSignalingData(internalId: CallSessionInternalId, data: Data) { if let context = self.contexts[internalId] { switch context.state { - case let .active(id, accessHash, _, _, _, _, _, _, _, _, _, _): + case let .active(id, accessHash, _, _, _, _, _, _, _, _, _, _, _): context.signalingDisposables.add(self.network.request(Api.functions.phone.sendSignalingData(peer: .inputPhoneCall(id: id, accessHash: accessHash), data: Buffer(data: data))).start()) default: break @@ -844,7 +844,7 @@ private final class CallSessionManagerContext { var idAndAccessHash: (id: Int64, accessHash: Int64)? switch context.state { - case let .active(id, accessHash, _, _, _, _, _, _, _, _, _, conferenceCall): + case let .active(id, accessHash, _, _, _, _, _, _, _, _, _, conferenceCall, _): if conferenceCall != nil { return } @@ -864,8 +864,8 @@ private final class CallSessionManagerContext { } if let result { switch context.state { - case let .active(id, accessHash, beginTimestamp, key, keyId, keyVisualHash, connections, maxLayer, version, customParameters, allowsP2P, _): - context.state = .active(id: id, accessHash: accessHash, beginTimestamp: beginTimestamp, key: key, keyId: keyId, keyVisualHash: keyVisualHash, connections: connections, maxLayer: maxLayer, version: version, customParameters: customParameters, allowsP2P: allowsP2P, conferenceCall: result) + case let .active(id, accessHash, beginTimestamp, key, keyId, keyVisualHash, connections, maxLayer, version, customParameters, allowsP2P, _, _): + context.state = .active(id: id, accessHash: accessHash, beginTimestamp: beginTimestamp, key: key, keyId: keyId, keyVisualHash: keyVisualHash, connections: connections, maxLayer: maxLayer, version: version, customParameters: customParameters, allowsP2P: allowsP2P, conferenceCall: result, willSwitchToConference: false) self.contextUpdated(internalId: internalId) default: break @@ -977,7 +977,7 @@ private final class CallSessionManagerContext { disposable.dispose() context.state = .terminated(id: id, accessHash: accessHash, reason: parsedReason, reportRating: reportRating, sendDebugLogs: sendDebugLogs) self.contextUpdated(internalId: internalId) - case let .active(id, accessHash, _, _, _, _, _, _, _, _, _, conferenceCall): + case let .active(id, accessHash, _, _, _, _, _, _, _, _, _, conferenceCall, _): if let conferenceCall, case let .phoneCallDiscardReasonAllowGroupCall(encryptedGroupKey) = reason { context.state = .switchedToConference(key: encryptedGroupKey.makeData(), keyVisualHash: MTSha256(encryptedGroupKey.makeData()), conferenceCall: conferenceCall) } else { @@ -1016,8 +1016,8 @@ private final class CallSessionManagerContext { switch context.state { case .accepting, .dropping, .requesting, .ringing, .terminated, .requested, .switchedToConference: break - case let .active(id, accessHash, beginTimestamp, key, keyId, keyVisualHash, connections, maxLayer, version, customParameters, allowsP2P, _): - context.state = .active(id: id, accessHash: accessHash, beginTimestamp: beginTimestamp, key: key, keyId: keyId, keyVisualHash: keyVisualHash, connections: connections, maxLayer: maxLayer, version: version, customParameters: customParameters, allowsP2P: allowsP2P, conferenceCall: conferenceCall.flatMap(GroupCallReference.init)) + case let .active(id, accessHash, beginTimestamp, key, keyId, keyVisualHash, connections, maxLayer, version, customParameters, allowsP2P, _, _): + context.state = .active(id: id, accessHash: accessHash, beginTimestamp: beginTimestamp, key: key, keyId: keyId, keyVisualHash: keyVisualHash, connections: connections, maxLayer: maxLayer, version: version, customParameters: customParameters, allowsP2P: allowsP2P, conferenceCall: conferenceCall.flatMap(GroupCallReference.init), willSwitchToConference: context.isIncomingConference) self.contextUpdated(internalId: internalId) case let .awaitingConfirmation(_, accessHash, gAHash, b, config): if let (key, calculatedKeyId, keyVisualHash) = self.makeSessionEncryptionKey(config: config, gAHash: gAHash, b: b, gA: gAOrB.makeData()) { @@ -1036,7 +1036,7 @@ private final class CallSessionManagerContext { let isVideoPossible = self.videoVersions().contains(where: { versions.contains($0) }) context.isVideoPossible = isVideoPossible - context.state = .active(id: id, accessHash: accessHash, beginTimestamp: startDate, key: key, keyId: calculatedKeyId, keyVisualHash: keyVisualHash, connections: parseConnectionSet(primary: connections.first!, alternative: Array(connections[1...])), maxLayer: maxLayer, version: versions[0], customParameters: customParametersValue, allowsP2P: allowsP2P, conferenceCall: conferenceCall.flatMap(GroupCallReference.init)) + context.state = .active(id: id, accessHash: accessHash, beginTimestamp: startDate, key: key, keyId: calculatedKeyId, keyVisualHash: keyVisualHash, connections: parseConnectionSet(primary: connections.first!, alternative: Array(connections[1...])), maxLayer: maxLayer, version: versions[0], customParameters: customParametersValue, allowsP2P: allowsP2P, conferenceCall: conferenceCall.flatMap(GroupCallReference.init), willSwitchToConference: context.isIncomingConference) self.contextUpdated(internalId: internalId) } else { self.drop(internalId: internalId, reason: .disconnect, debugLog: .single(nil)) @@ -1063,7 +1063,7 @@ private final class CallSessionManagerContext { let isVideoPossible = self.videoVersions().contains(where: { versions.contains($0) }) context.isVideoPossible = isVideoPossible - context.state = .active(id: id, accessHash: accessHash, beginTimestamp: startDate, key: key, keyId: keyId, keyVisualHash: keyVisualHash, connections: parseConnectionSet(primary: connections.first!, alternative: Array(connections[1...])), maxLayer: maxLayer, version: versions[0], customParameters: customParametersValue, allowsP2P: allowsP2P, conferenceCall: conferenceCall.flatMap(GroupCallReference.init)) + context.state = .active(id: id, accessHash: accessHash, beginTimestamp: startDate, key: key, keyId: keyId, keyVisualHash: keyVisualHash, connections: parseConnectionSet(primary: connections.first!, alternative: Array(connections[1...])), maxLayer: maxLayer, version: versions[0], customParameters: customParametersValue, allowsP2P: allowsP2P, conferenceCall: conferenceCall.flatMap(GroupCallReference.init), willSwitchToConference: context.isIncomingConference) self.contextUpdated(internalId: internalId) } else { self.drop(internalId: internalId, reason: .disconnect, debugLog: .single(nil)) @@ -1173,7 +1173,7 @@ private final class CallSessionManagerContext { let randomStatus = SecRandomCopyBytes(nil, 256, aBytes.assumingMemoryBound(to: UInt8.self)) let a = Data(bytesNoCopy: aBytes, count: 256, deallocator: .free) if randomStatus == 0 { - self.contexts[internalId] = CallSessionContext(peerId: peerId, isOutgoing: true, isConference: conferenceCall != nil, type: isVideo ? .video : .audio, isVideoPossible: enableVideo || isVideo, pendingConference: conferenceCall, state: .requesting(a: a, conferenceCall: conferenceCall?.conference, disposable: (requestCallSession(postbox: self.postbox, network: self.network, peerId: peerId, a: a, maxLayer: self.maxLayer, versions: self.filteredVersions(enableVideo: true), isVideo: isVideo, conferenceCall: conferenceCall?.conference) |> deliverOn(queue)).start(next: { [weak self] result in + self.contexts[internalId] = CallSessionContext(peerId: peerId, isOutgoing: true, isIncomingConference: false, type: isVideo ? .video : .audio, isVideoPossible: enableVideo || isVideo, pendingConference: conferenceCall, state: .requesting(a: a, conferenceCall: conferenceCall?.conference, disposable: (requestCallSession(postbox: self.postbox, network: self.network, peerId: peerId, a: a, maxLayer: self.maxLayer, versions: self.filteredVersions(enableVideo: true), isVideo: isVideo, conferenceCall: conferenceCall?.conference) |> deliverOn(queue)).start(next: { [weak self] result in if let strongSelf = self, let context = strongSelf.contexts[internalId] { if case .requesting = context.state { switch result { diff --git a/submodules/TelegramUI/Sources/SharedAccountContext.swift b/submodules/TelegramUI/Sources/SharedAccountContext.swift index a98781aa0a..468ed6c5cc 100644 --- a/submodules/TelegramUI/Sources/SharedAccountContext.swift +++ b/submodules/TelegramUI/Sources/SharedAccountContext.swift @@ -183,6 +183,7 @@ public final class SharedAccountContextImpl: SharedAccountContext { public var currentGroupCallController: ViewController? { return self.groupCallController } + private var hasGroupCallOnScreenValue: Bool = false private let hasGroupCallOnScreenPromise = Promise(false) public var hasGroupCallOnScreen: Signal { return self.hasGroupCallOnScreenPromise.get() @@ -1182,15 +1183,14 @@ public final class SharedAccountContextImpl: SharedAccountContext { self.notificationController?.setBlocking(nil) } - - self.callStateDisposable?.dispose() - self.callStateDisposable = nil } self.currentCall = call let beginDisplayingCallStatusBar = Promise() + var shouldResetGroupCallOnScreen = true + var transitioningToConferenceCallController: CallController? if let call, case let .group(groupCall) = call, case let .conferenceSource(conferenceSource) = groupCall, let callController = self.callController, callController.call === conferenceSource { transitioningToConferenceCallController = callController @@ -1198,8 +1198,7 @@ public final class SharedAccountContextImpl: SharedAccountContext { callController.dismissWithoutAnimation() } self.callController = nil - } else { - self.hasGroupCallOnScreenPromise.set(.single(false)) + shouldResetGroupCallOnScreen = false } if let callController = self.callController { @@ -1207,10 +1206,27 @@ public final class SharedAccountContextImpl: SharedAccountContext { callController.dismiss() } if let groupCallController = self.groupCallController { + if case let .group(groupCall) = call, case let .group(groupCall) = groupCall, let conferenceSourceId = groupCall.conferenceSource { + if case let .conferenceSource(conferenceSource) = groupCallController.call, conferenceSource.internalId == conferenceSourceId { + groupCallController.updateCall(call: .group(groupCall)) + + self.updateInCallStatusBarData(hasGroupCallOnScreen: self.hasGroupCallOnScreenValue) + + return + } + } + self.groupCallController = nil groupCallController.dismiss() } + if shouldResetGroupCallOnScreen { + self.hasGroupCallOnScreenPromise.set(.single(false)) + } + + self.callStateDisposable?.dispose() + self.callStateDisposable = nil + if case let .call(call) = call { let callController = CallController(sharedContext: self, account: call.context.account, call: call, easyDebugAccess: !GlobalExperimentalSettings.isAppStoreBuild) self.callController = callController @@ -1282,36 +1298,36 @@ public final class SharedAccountContextImpl: SharedAccountContext { )) }) } - } - - self.awaitingCallConnectionDisposable = (call.state - |> filter { state in - switch state.state { - case .ringing: - return false - case .terminating, .terminated: - return false - default: - return true - } - } - |> take(1) - |> deliverOnMainQueue).start(next: { [weak self, weak callController] _ in - guard let self, let callController, self.callController === callController else { - return - } - self.notificationController?.setBlocking(nil) - self.callPeerDisposable?.dispose() - self.callPeerDisposable = nil - - thisCallIsOnScreenPromise.set(true) - if useFlatModalCallsPresentation(context: callController.call.context) { - (self.mainWindow?.viewController as? NavigationController)?.pushViewController(callController) - } else { - self.mainWindow?.present(callController, on: .calls) + self.awaitingCallConnectionDisposable = (call.state + |> filter { state in + switch state.state { + case .ringing: + return false + case .terminating, .terminated: + return false + default: + return true + } } - }) + |> take(1) + |> deliverOnMainQueue).start(next: { [weak self, weak callController] _ in + guard let self, let callController, self.callController === callController else { + return + } + self.notificationController?.setBlocking(nil) + + self.callPeerDisposable?.dispose() + self.callPeerDisposable = nil + + thisCallIsOnScreenPromise.set(true) + if useFlatModalCallsPresentation(context: callController.call.context) { + (self.mainWindow?.viewController as? NavigationController)?.pushViewController(callController) + } else { + self.mainWindow?.present(callController, on: .calls) + } + }) + } beginDisplayingCallStatusBar.set(call.state |> filter { state in @@ -1389,40 +1405,11 @@ public final class SharedAccountContextImpl: SharedAccountContext { guard let self else { return } - - var statusBarContent: CallStatusBarNodeImpl.Content? - if !hasGroupCallOnScreen, let currentCall = self.currentCall { - switch currentCall { - case let .call(call): - statusBarContent = .call(self, call.context.account, call) - case let .group(groupCall): - switch groupCall { - case let .conferenceSource(conferenceSource): - statusBarContent = .call(self, conferenceSource.context.account, conferenceSource) - case let .group(groupCall): - statusBarContent = .groupCall(self, groupCall.account, groupCall) - } - } - } - - var resolvedCallStatusBarNode: CallStatusBarNodeImpl? - if let statusBarContent { - if let current = self.currentCallStatusBarNode { - resolvedCallStatusBarNode = current - } else { - resolvedCallStatusBarNode = CallStatusBarNodeImpl() - self.currentCallStatusBarNode = resolvedCallStatusBarNode - } - resolvedCallStatusBarNode?.update(content: statusBarContent) - } else { - self.currentCallStatusBarNode = nil - } - - if let navigationController = self.mainWindow?.viewController as? NavigationController { - navigationController.setForceInCallStatusBar(resolvedCallStatusBarNode) - } + self.hasGroupCallOnScreenValue = hasGroupCallOnScreen + self.updateInCallStatusBarData(hasGroupCallOnScreen: hasGroupCallOnScreen) }) } else { + self.hasGroupCallOnScreenValue = false self.currentCallStatusBarNode = nil if let navigationController = self.mainWindow?.viewController as? NavigationController { navigationController.setForceInCallStatusBar(nil) @@ -1430,6 +1417,40 @@ public final class SharedAccountContextImpl: SharedAccountContext { } } + private func updateInCallStatusBarData(hasGroupCallOnScreen: Bool) { + var statusBarContent: CallStatusBarNodeImpl.Content? + if !hasGroupCallOnScreen, let currentCall = self.currentCall { + switch currentCall { + case let .call(call): + statusBarContent = .call(self, call.context.account, call) + case let .group(groupCall): + switch groupCall { + case let .conferenceSource(conferenceSource): + statusBarContent = .call(self, conferenceSource.context.account, conferenceSource) + case let .group(groupCall): + statusBarContent = .groupCall(self, groupCall.account, groupCall) + } + } + } + + var resolvedCallStatusBarNode: CallStatusBarNodeImpl? + if let statusBarContent { + if let current = self.currentCallStatusBarNode { + resolvedCallStatusBarNode = current + } else { + resolvedCallStatusBarNode = CallStatusBarNodeImpl() + self.currentCallStatusBarNode = resolvedCallStatusBarNode + } + resolvedCallStatusBarNode?.update(content: statusBarContent) + } else { + self.currentCallStatusBarNode = nil + } + + if let navigationController = self.mainWindow?.viewController as? NavigationController { + navigationController.setForceInCallStatusBar(resolvedCallStatusBarNode) + } + } + private func presentControllerWithCurrentCall() { /*guard let call = self.call else { return diff --git a/submodules/TgVoipWebrtc/Sources/OngoingCallThreadLocalContext.mm b/submodules/TgVoipWebrtc/Sources/OngoingCallThreadLocalContext.mm index c5bc182627..afceae0862 100644 --- a/submodules/TgVoipWebrtc/Sources/OngoingCallThreadLocalContext.mm +++ b/submodules/TgVoipWebrtc/Sources/OngoingCallThreadLocalContext.mm @@ -2275,6 +2275,10 @@ isConference:(bool)isConference { auto encryptionKeyValue = std::make_shared>(); memcpy(encryptionKeyValue->data(), encryptionKey.bytes, encryptionKey.length); + #if DEBUG + NSLog(@"Encryption key: %@", [encryptionKey base64EncodedStringWithOptions:0]); + #endif + mappedEncryptionKey = tgcalls::EncryptionKey(encryptionKeyValue, true); }