diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index e851479dba..4269334c82 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -23,8 +23,6 @@ internal: - export PATH=`gem environment gemdir`/bin:$PATH - python3 -u build-system/Make/Make.py remote-build --darwinContainers="$DARWIN_CONTAINERS" --darwinContainersHost="$DARWIN_CONTAINERS_HOST" --cacheHost="$TELEGRAM_BAZEL_CACHE_HOST" --configurationPath="build-system/appcenter-configuration.json" --gitCodesigningRepository="$TELEGRAM_GIT_CODESIGNING_REPOSITORY" --gitCodesigningType=adhoc --configuration=release_arm64 - python3 -u build-system/Make/DeployToAppCenter.py --configuration="$TELEGRAM_PRIVATE_DATA_PATH/appcenter-configurations/appcenter-internal.json" --ipa="build/artifacts/Telegram.ipa" --dsyms="build/artifacts/Telegram.DSYMs.zip" - - rm -rf build - - python3 -u build-system/Make/Make.py remote-build --darwinContainers="$DARWIN_CONTAINERS" --darwinContainersHost="$DARWIN_CONTAINERS_HOST" --cacheHost="$TELEGRAM_BAZEL_CACHE_HOST" --configurationPath="build-system/appstore-configuration.json" --gitCodesigningRepository="$TELEGRAM_GIT_CODESIGNING_REPOSITORY" --gitCodesigningType=appstore --configuration=release_arm64 environment: name: internal artifacts: @@ -33,7 +31,7 @@ internal: - build/artifacts expire_in: 1 week -deploy_internal_testflight: +internal_testflight: tags: - ios_internal stage: deploy @@ -42,6 +40,7 @@ deploy_internal_testflight: except: - tags script: + - python3 -u build-system/Make/Make.py remote-build --darwinContainers="$DARWIN_CONTAINERS" --darwinContainersHost="$DARWIN_CONTAINERS_HOST" --cacheHost="$TELEGRAM_BAZEL_CACHE_HOST" --configurationPath="build-system/appstore-configuration.json" --gitCodesigningRepository="$TELEGRAM_GIT_CODESIGNING_REPOSITORY" --gitCodesigningType=appstore --configuration=release_arm64 - python3 -u build-system/Make/Make.py remote-deploy-testflight --darwinContainers="$DARWIN_CONTAINERS" --darwinContainersHost="$DARWIN_CONTAINERS_HOST" --ipa="build/artifacts/Telegram.ipa" --dsyms="build/artifacts/Telegram.DSYMs.zip" environment: name: testflight_llc diff --git a/Random.txt b/Random.txt index de55a1122f..c69e7057c2 100644 --- a/Random.txt +++ b/Random.txt @@ -1 +1 @@ -72ef725b9fffe38df9f94dfee99bb99b +a9952f2d5730b71bcafb90f6fc0d0bed diff --git a/submodules/AccountContext/Sources/PresentationCallManager.swift b/submodules/AccountContext/Sources/PresentationCallManager.swift index da4700798b..4237e9bdcf 100644 --- a/submodules/AccountContext/Sources/PresentationCallManager.swift +++ b/submodules/AccountContext/Sources/PresentationCallManager.swift @@ -401,6 +401,22 @@ public extension GroupCallParticipantsContext.Participant { } } +public struct PresentationGroupCallInvitedPeer: Equatable { + public enum State { + case requesting + case ringing + case connecting + } + + public var id: EnginePeer.Id + public var state: State? + + public init(id: EnginePeer.Id, state: State?) { + self.id = id + self.state = state + } +} + public protocol PresentationGroupCall: AnyObject { var account: Account { get } var accountContext: AccountContext { get } @@ -468,7 +484,7 @@ public protocol PresentationGroupCall: AnyObject { func invitePeer(_ peerId: EnginePeer.Id) -> Bool func removedPeer(_ peerId: EnginePeer.Id) - var invitedPeers: Signal<[EnginePeer.Id], NoError> { get } + var invitedPeers: Signal<[PresentationGroupCallInvitedPeer], NoError> { get } var inviteLinks: Signal { get } diff --git a/submodules/TelegramCallsUI/Sources/CallControllerNodeV2.swift b/submodules/TelegramCallsUI/Sources/CallControllerNodeV2.swift index 39fac600d0..73d57e9c3c 100644 --- a/submodules/TelegramCallsUI/Sources/CallControllerNodeV2.swift +++ b/submodules/TelegramCallsUI/Sources/CallControllerNodeV2.swift @@ -470,7 +470,7 @@ final class CallControllerNodeV2: ViewControllerTracingNode, CallControllerNodeP } case .busy: mappedReason = .busy - case .hungUp: + case .hungUp, .switchedToConference: if self.callStartTimestamp != nil { mappedReason = .hangUp } else { @@ -687,12 +687,12 @@ final class CallControllerNodeV2: ViewControllerTracingNode, CallControllerNodeP func animateOutToGroupChat(completion: @escaping () -> Void) -> CallController.AnimateOutToGroupChat { self.callScreen.animateOutToGroupChat(completion: completion) - let takenIncomingVideoLayer = self.callScreen.takeIncomingVideoLayer() + let takeSource = self.callScreen.takeIncomingVideoLayer() return CallController.AnimateOutToGroupChat( containerView: self.containerView, - incomingPeerId: self.call.peerId, - incomingVideoLayer: takenIncomingVideoLayer?.0, - incomingVideoPlaceholder: takenIncomingVideoLayer?.1 + incomingPeerId: (takeSource?.1 ?? true) ? self.call.peerId : self.call.context.account.peerId, + incomingVideoLayer: takeSource?.0.0, + incomingVideoPlaceholder: takeSource?.0.1 ) } diff --git a/submodules/TelegramCallsUI/Sources/LegacyCallControllerNode.swift b/submodules/TelegramCallsUI/Sources/LegacyCallControllerNode.swift index ceb75af373..9b5caf3cc0 100644 --- a/submodules/TelegramCallsUI/Sources/LegacyCallControllerNode.swift +++ b/submodules/TelegramCallsUI/Sources/LegacyCallControllerNode.swift @@ -225,7 +225,7 @@ final class LegacyCallControllerNode: ASDisplayNode, CallControllerNodeProtocol switch type { case .busy: statusValue = .text(self.presentationData.strings.Call_StatusBusy) - case .hungUp, .missed: + case .hungUp, .missed, .switchedToConference: statusValue = .text(self.presentationData.strings.Call_StatusEnded) } case .error: diff --git a/submodules/TelegramCallsUI/Sources/PresentationCall.swift b/submodules/TelegramCallsUI/Sources/PresentationCall.swift index a9568f69ae..ba2670c36c 100644 --- a/submodules/TelegramCallsUI/Sources/PresentationCall.swift +++ b/submodules/TelegramCallsUI/Sources/PresentationCall.swift @@ -909,7 +909,7 @@ public final class PresentationCallImpl: PresentationCall { self.conferenceCallImpl = conferenceCall conferenceCall.upgradedConferenceCall = self - conferenceCall.setInvitedPeers(self.pendingInviteToConferencePeerIds) + conferenceCall.setConferenceInvitedPeers(self.pendingInviteToConferencePeerIds) for peerId in self.pendingInviteToConferencePeerIds { let _ = conferenceCall.invitePeer(peerId) } @@ -990,8 +990,6 @@ public final class PresentationCallImpl: PresentationCall { return } - self.ongoingContext?.stop(debugLogValue: Promise()) - self.ongoingContext = nil self.ongoingContextStateDisposable?.dispose() self.conferenceStateValue = .ready @@ -1197,6 +1195,8 @@ public final class PresentationCallImpl: PresentationCall { tone = .busy case .hungUp, .missed: tone = .ended + case .switchedToConference: + tone = nil } case .error: tone = .failed diff --git a/submodules/TelegramCallsUI/Sources/PresentationCallManager.swift b/submodules/TelegramCallsUI/Sources/PresentationCallManager.swift index 7dfa00cb44..ca6722a9d1 100644 --- a/submodules/TelegramCallsUI/Sources/PresentationCallManager.swift +++ b/submodules/TelegramCallsUI/Sources/PresentationCallManager.swift @@ -731,7 +731,7 @@ public final class PresentationCallManagerImpl: PresentationCallManager { guard let groupCall = conferenceSource.conferenceCall else { return } - (conferenceSource as! PresentationCallImpl).resetAsMovedToConference() + (groupCall as! PresentationGroupCallImpl).moveConferenceCall(source: conferenceSource) self.updateCurrentGroupCall(.group(groupCall)) }) diff --git a/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift b/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift index abdae27842..a8690336b1 100644 --- a/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift +++ b/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift @@ -601,6 +601,10 @@ private final class ScreencastEmbeddedIPCContext: ScreencastIPCContext { } private final class PendingConferenceInvitationContext { + enum State { + case ringing + } + private let callSessionManager: CallSessionManager private var requestDisposable: Disposable? private var stateDisposable: Disposable? @@ -608,7 +612,7 @@ private final class PendingConferenceInvitationContext { private var didNotifyEnded: Bool = false - init(callSessionManager: CallSessionManager, groupCall: GroupCallReference, encryptionKey: Data, peerId: PeerId, onEnded: @escaping () -> Void) { + init(callSessionManager: CallSessionManager, groupCall: GroupCallReference, encryptionKey: Data, peerId: PeerId, onStateUpdated: @escaping (State) -> Void, onEnded: @escaping (Bool) -> Void) { self.callSessionManager = callSessionManager self.requestDisposable = (callSessionManager.request(peerId: peerId, isVideo: false, enableVideo: true, conferenceCall: (groupCall, encryptionKey)) @@ -624,10 +628,14 @@ private final class PendingConferenceInvitationContext { return } switch state.state { - case .dropping, .terminated: + case let .requesting(ringing, _): + if ringing { + onStateUpdated(.ringing) + } + case let .dropping(reason), let .terminated(_, reason, _): if !self.didNotifyEnded { self.didNotifyEnded = true - onEnded() + onEnded(reason == .ended(.switchedToConference)) } default: break @@ -972,15 +980,15 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { return self.membersPromise.get() } - private var invitedPeersValue: [EnginePeer.Id] = [] { + private var invitedPeersValue: [PresentationGroupCallInvitedPeer] = [] { didSet { if self.invitedPeersValue != oldValue { self.inivitedPeersPromise.set(self.invitedPeersValue) } } } - private let inivitedPeersPromise = ValuePromise<[EnginePeer.Id]>([]) - public var invitedPeers: Signal<[EnginePeer.Id], NoError> { + private let inivitedPeersPromise = ValuePromise<[PresentationGroupCallInvitedPeer]>([]) + public var invitedPeers: Signal<[PresentationGroupCallInvitedPeer], NoError> { return self.inivitedPeersPromise.get() } @@ -1062,14 +1070,13 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { return self.conferenceSourceId } - var internal_isRemoteConnected = Promise() - private var internal_isRemoteConnectedDisposable: Disposable? - public var onMutedSpeechActivityDetected: ((Bool) -> Void)? let debugLog = Promise() public weak var upgradedConferenceCall: PresentationCallImpl? + public var pendingDisconnedUpgradedConferenceCall: PresentationCallImpl? + private var pendingDisconnedUpgradedConferenceCallTimer: Foundation.Timer? private var conferenceInvitationContexts: [PeerId: PendingConferenceInvitationContext] = [:] init( @@ -1120,9 +1127,16 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { self.encryptionKey = encryptionKey var sharedAudioContext = sharedAudioContext - if sharedAudioContext == nil && accountContext.sharedContext.immediateExperimentalUISettings.conferenceCalls { - let sharedAudioContextValue = SharedCallAudioContext(audioSession: audioSession, callKitIntegration: callKitIntegration) - sharedAudioContext = sharedAudioContextValue + if sharedAudioContext == nil { + var useSharedAudio = true + if let data = self.accountContext.currentAppConfiguration.with({ $0 }).data, data["ios_killswitch_group_shared_audio"] != nil { + useSharedAudio = false + } + + if useSharedAudio { + let sharedAudioContextValue = SharedCallAudioContext(audioSession: audioSession, callKitIntegration: callKitIntegration) + sharedAudioContext = sharedAudioContextValue + } } self.sharedAudioContext = sharedAudioContext @@ -1428,14 +1442,10 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { } self.audioOutputStateDisposable?.dispose() - self.removedChannelMembersDisposable?.dispose() - self.peerUpdatesSubscription?.dispose() - self.screencastStateDisposable?.dispose() - - self.internal_isRemoteConnectedDisposable?.dispose() + self.pendingDisconnedUpgradedConferenceCallTimer?.invalidate() } private func switchToTemporaryParticipantsContext(sourceContext: GroupCallParticipantsContext?, oldMyPeerId: PeerId) { @@ -1529,7 +1539,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { topParticipants.append(participant) } - if let index = updatedInvitedPeers.firstIndex(of: participant.peer.id) { + if let index = updatedInvitedPeers.firstIndex(where: { $0.id == participant.peer.id }) { updatedInvitedPeers.remove(at: index) didUpdateInvitedPeers = true } @@ -1986,6 +1996,8 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { } lastTimestamp = CFAbsoluteTimeGetCurrent() self.hasActiveIncomingDataValue = true + + self.activateIncomingAudioIfNeeded() }) self.hasActiveIncomingDataTimer?.invalidate() @@ -2002,15 +2014,6 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { }) self.signalBarsPromise.set(callContext.signalBars) - - self.internal_isRemoteConnectedDisposable = (self.internal_isRemoteConnected.get() - |> distinctUntilChanged - |> deliverOnMainQueue).startStrict(next: { [weak callContext] isRemoteConnected in - guard let callContext else { - return - } - callContext.addRemoteConnectedEvent(isRemoteConntected: isRemoteConnected) - }) } } @@ -2645,7 +2648,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { } } - if let index = updatedInvitedPeers.firstIndex(of: participant.peer.id) { + if let index = updatedInvitedPeers.firstIndex(where: { $0.id == participant.peer.id }) { updatedInvitedPeers.remove(at: index) didUpdateInvitedPeers = true } @@ -2741,6 +2744,12 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { } } + private func activateIncomingAudioIfNeeded() { + if let genericCallContext = self.genericCallContext, case let .call(groupCall) = genericCallContext { + groupCall.activateIncomingAudio() + } + } + 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 { @@ -3643,42 +3652,68 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { if conferenceInvitationContexts[peerId] != nil { return false } - var onEnded: (() -> Void)? + var onStateUpdated: ((PendingConferenceInvitationContext.State) -> Void)? + var onEnded: ((Bool) -> Void)? var didEndAlready = false let invitationContext = PendingConferenceInvitationContext( callSessionManager: self.accountContext.account.callSessionManager, groupCall: GroupCallReference(id: initialCall.id, accessHash: initialCall.accessHash), encryptionKey: encryptionKey.key, peerId: peerId, - onEnded: { + onStateUpdated: { state in + onStateUpdated?(state) + }, + onEnded: { success in didEndAlready = true - onEnded?() + onEnded?(success) } ) if !didEndAlready { conferenceInvitationContexts[peerId] = invitationContext - if !self.invitedPeersValue.contains(peerId) { - self.invitedPeersValue.append(peerId) + if !self.invitedPeersValue.contains(where: { $0.id == peerId }) { + self.invitedPeersValue.append(PresentationGroupCallInvitedPeer(id: peerId, state: .requesting)) } - onEnded = { [weak self, weak invitationContext] in + onStateUpdated = { [weak self] state in + guard let self else { + return + } + if let index = self.invitedPeersValue.firstIndex(where: { $0.id == peerId }) { + var invitedPeer = self.invitedPeersValue[index] + switch state { + case .ringing: + invitedPeer.state = .ringing + } + self.invitedPeersValue[index] = invitedPeer + } + } + onEnded = { [weak self, weak invitationContext] success in guard let self, let invitationContext else { return } if self.conferenceInvitationContexts[peerId] === invitationContext { self.conferenceInvitationContexts.removeValue(forKey: peerId) - self.invitedPeersValue.removeAll(where: { $0 == peerId }) + + if success { + if let index = self.invitedPeersValue.firstIndex(where: { $0.id == peerId }) { + var invitedPeer = self.invitedPeersValue[index] + invitedPeer.state = .connecting + self.invitedPeersValue[index] = invitedPeer + } + } else { + self.invitedPeersValue.removeAll(where: { $0.id == peerId }) + } } } } return false } else { - guard let callInfo = self.internalState.callInfo, !self.invitedPeersValue.contains(peerId) else { + guard let callInfo = self.internalState.callInfo, !self.invitedPeersValue.contains(where: { $0.id == peerId }) else { return false } var updatedInvitedPeers = self.invitedPeersValue - updatedInvitedPeers.insert(peerId, at: 0) + updatedInvitedPeers.insert(PresentationGroupCallInvitedPeer(id: peerId, state: nil), at: 0) self.invitedPeersValue = updatedInvitedPeers let _ = self.accountContext.engine.calls.inviteToGroupCall(callId: callInfo.id, accessHash: callInfo.accessHash, peerId: peerId).start() @@ -3687,13 +3722,15 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { } } - func setInvitedPeers(_ peerIds: [PeerId]) { - self.invitedPeersValue = peerIds + func setConferenceInvitedPeers(_ peerIds: [PeerId]) { + self.invitedPeersValue = peerIds.map { + PresentationGroupCallInvitedPeer(id: $0, state: .requesting) + } } public func removedPeer(_ peerId: PeerId) { var updatedInvitedPeers = self.invitedPeersValue - updatedInvitedPeers.removeAll(where: { $0 == peerId}) + updatedInvitedPeers.removeAll(where: { $0.id == peerId}) self.invitedPeersValue = updatedInvitedPeers } @@ -3859,6 +3896,26 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { } |> runOn(.mainQueue()) } + + func moveConferenceCall(source: PresentationCall) { + guard let source = source as? PresentationCallImpl else { + return + } + + self.pendingDisconnedUpgradedConferenceCall?.resetAsMovedToConference() + self.pendingDisconnedUpgradedConferenceCall = source + + self.pendingDisconnedUpgradedConferenceCallTimer?.invalidate() + self.pendingDisconnedUpgradedConferenceCallTimer = Foundation.Timer.scheduledTimer(withTimeInterval: 5.0, repeats: false, block: { [weak self] _ in + guard let self else { + return + } + if let pendingDisconnedUpgradedConferenceCall = self.pendingDisconnedUpgradedConferenceCall { + self.pendingDisconnedUpgradedConferenceCall = nil + pendingDisconnedUpgradedConferenceCall.resetAsMovedToConference() + } + }) + } } private protocol ScreencastContext: AnyObject { diff --git a/submodules/TelegramCallsUI/Sources/VideoChatParticipantVideoComponent.swift b/submodules/TelegramCallsUI/Sources/VideoChatParticipantVideoComponent.swift index dcb9cf08bd..70ed81c1ac 100644 --- a/submodules/TelegramCallsUI/Sources/VideoChatParticipantVideoComponent.swift +++ b/submodules/TelegramCallsUI/Sources/VideoChatParticipantVideoComponent.swift @@ -428,6 +428,15 @@ final class VideoChatParticipantVideoComponent: Component { alphaTransition.setAlpha(view: titleView, alpha: controlsAlpha) } + var previousVideoDescription: GroupCallParticipantsContext.Participant.VideoDescription? + if let previousComponent { + if previousComponent.isMyPeer && previousComponent.isPresentation { + previousVideoDescription = nil + } else { + previousVideoDescription = previousComponent.maxVideoQuality == 0 ? nil : (previousComponent.isPresentation ? previousComponent.participant.presentationDescription : previousComponent.participant.videoDescription) + } + } + let videoDescription: GroupCallParticipantsContext.Participant.VideoDescription? if component.isMyPeer && component.isPresentation { videoDescription = nil @@ -506,8 +515,13 @@ final class VideoChatParticipantVideoComponent: Component { } let videoLayer: PrivateCallVideoLayer + var resetVideoSource = false if let current = self.videoLayer { videoLayer = current + + if let previousVideoDescription, previousVideoDescription.endpointId != videoDescription.endpointId { + resetVideoSource = true + } } else { videoLayer = PrivateCallVideoLayer() self.videoLayer = videoLayer @@ -517,6 +531,10 @@ final class VideoChatParticipantVideoComponent: Component { videoLayer.blurredLayer.opacity = 0.0 + resetVideoSource = true + } + + if resetVideoSource { if let input = component.call.video(endpointId: videoDescription.endpointId) { let videoSource = AdaptedCallVideoSource(videoStreamSignal: input) self.videoSource = videoSource diff --git a/submodules/TelegramCallsUI/Sources/VideoChatParticipantsComponent.swift b/submodules/TelegramCallsUI/Sources/VideoChatParticipantsComponent.swift index c73cdc8fa7..0a37c4681c 100644 --- a/submodules/TelegramCallsUI/Sources/VideoChatParticipantsComponent.swift +++ b/submodules/TelegramCallsUI/Sources/VideoChatParticipantsComponent.swift @@ -128,7 +128,7 @@ final class VideoChatParticipantsComponent: Component { let call: VideoChatCall let participants: Participants? - let invitedPeers: [EnginePeer] + let invitedPeers: [VideoChatScreenComponent.InvitedPeer] let speakingParticipants: Set let expandedVideoState: ExpandedVideoState? let maxVideoQuality: Int @@ -148,7 +148,7 @@ final class VideoChatParticipantsComponent: Component { init( call: VideoChatCall, participants: Participants?, - invitedPeers: [EnginePeer], + invitedPeers: [VideoChatScreenComponent.InvitedPeer], speakingParticipants: Set, expandedVideoState: ExpandedVideoState?, maxVideoQuality: Int, @@ -1259,10 +1259,20 @@ final class VideoChatParticipantsComponent: Component { ) } else { let invitedPeer = component.invitedPeers[i - self.listParticipants.count] - participantPeerId = invitedPeer.id + participantPeerId = invitedPeer.peer.id let subtitle: PeerListItemComponent.Subtitle - subtitle = PeerListItemComponent.Subtitle(text: component.strings.VoiceChat_StatusInvited, color: .neutral) + //TODO:localize + switch invitedPeer.state { + case .none: + subtitle = PeerListItemComponent.Subtitle(text: component.strings.VoiceChat_StatusInvited, color: .neutral) + case .connecting: + subtitle = PeerListItemComponent.Subtitle(text: "connecting...", color: .neutral) + case .requesting: + subtitle = PeerListItemComponent.Subtitle(text: "requesting...", color: .neutral) + case .ringing: + subtitle = PeerListItemComponent.Subtitle(text: "ringing...", color: .neutral) + } peerItemComponent = PeerListItemComponent( context: component.call.accountContext, @@ -1270,15 +1280,15 @@ final class VideoChatParticipantsComponent: Component { strings: component.strings, style: .generic, sideInset: 0.0, - title: invitedPeer.displayTitle(strings: component.strings, displayOrder: .firstLast), + title: invitedPeer.peer.displayTitle(strings: component.strings, displayOrder: .firstLast), avatarComponent: AnyComponent(VideoChatParticipantAvatarComponent( call: component.call, - peer: invitedPeer, + peer: invitedPeer.peer, myPeerId: component.participants?.myPeerId ?? component.call.accountContext.account.peerId, isSpeaking: false, theme: component.theme )), - peer: invitedPeer, + peer: invitedPeer.peer, subtitle: subtitle, subtitleAccessory: .none, presence: nil, diff --git a/submodules/TelegramCallsUI/Sources/VideoChatScreen.swift b/submodules/TelegramCallsUI/Sources/VideoChatScreen.swift index ee64db2fcc..12d60fa618 100644 --- a/submodules/TelegramCallsUI/Sources/VideoChatScreen.swift +++ b/submodules/TelegramCallsUI/Sources/VideoChatScreen.swift @@ -190,6 +190,16 @@ final class VideoChatScreenComponent: Component { self.scrollView = scrollView } } + + struct InvitedPeer: Equatable { + var peer: EnginePeer + var state: PresentationGroupCallInvitedPeer.State? + + init(peer: EnginePeer, state: PresentationGroupCallInvitedPeer.State?) { + self.peer = peer + self.state = state + } + } final class View: UIView, UIGestureRecognizerDelegate { let containerView: UIView @@ -242,7 +252,7 @@ final class VideoChatScreenComponent: Component { var members: PresentationGroupCallMembers? var membersDisposable: Disposable? - var invitedPeers: [EnginePeer] = [] + var invitedPeers: [InvitedPeer] = [] var invitedPeersDisposable: Disposable? var speakingParticipantPeers: [EnginePeer] = [] @@ -323,8 +333,13 @@ final class VideoChatScreenComponent: Component { } var expandedPeer: (id: EnginePeer.Id, isPresentation: Bool)? - if let animateOutData, animateOutData.incomingVideoLayer != nil { - if let members = self.members, let participant = members.participants.first(where: { $0.peer.id == animateOutData.incomingPeerId }) { + if let animateOutData, animateOutData.incomingVideoLayer != nil, let members = self.members { + if let participant = members.participants.first(where: { $0.peer.id == animateOutData.incomingPeerId }) { + if let _ = participant.videoDescription { + expandedPeer = (participant.peer.id, false) + self.expandedParticipantsVideoState = VideoChatParticipantsComponent.ExpandedVideoState(mainParticipant: VideoChatParticipantsComponent.VideoParticipantKey(id: participant.peer.id, isPresentation: false), isMainParticipantPinned: false, isUIHidden: true) + } + } else if let participant = members.participants.first(where: { $0.peer.id == sourceCallController.call.context.account.peerId }) { if let _ = participant.videoDescription { expandedPeer = (participant.peer.id, false) self.expandedParticipantsVideoState = VideoChatParticipantsComponent.ExpandedVideoState(mainParticipant: VideoChatParticipantsComponent.VideoParticipantKey(id: participant.peer.id, isPresentation: false), isMainParticipantPinned: false, isUIHidden: true) @@ -969,7 +984,7 @@ final class VideoChatScreenComponent: Component { } } - static func groupCallStateForConferenceSource(conferenceSource: PresentationCall) -> Signal<(state: PresentationGroupCallState, invitedPeers: [EnginePeer]), NoError> { + static func groupCallStateForConferenceSource(conferenceSource: PresentationCall) -> Signal<(state: PresentationGroupCallState, invitedPeers: [InvitedPeer]), NoError> { let invitedPeers = conferenceSource.context.engine.data.subscribe( EngineDataList((conferenceSource as! PresentationCallImpl).pendingInviteToConferencePeerIds.map { TelegramEngine.EngineData.Item.Peer.Peer(id: $0) }) ) @@ -982,7 +997,7 @@ final class VideoChatScreenComponent: Component { conferenceSource.isMuted, invitedPeers ) - |> mapToSignal { state, isMuted, invitedPeers -> Signal<(state: PresentationGroupCallState, invitedPeers: [EnginePeer]), NoError> in + |> mapToSignal { state, isMuted, invitedPeers -> Signal<(state: PresentationGroupCallState, invitedPeers: [VideoChatScreenComponent.InvitedPeer]), NoError> in let mappedNetworkState: PresentationGroupCallState.NetworkState switch state.state { case .active: @@ -1007,7 +1022,12 @@ final class VideoChatScreenComponent: Component { isVideoWatchersLimitReached: false ) - return .single((callState, invitedPeers.compactMap({ $0 }))) + return .single((callState, invitedPeers.compactMap({ peer -> VideoChatScreenComponent.InvitedPeer? in + guard let peer else { + return nil + } + return VideoChatScreenComponent.InvitedPeer(peer: peer, state: .requesting) + }))) } } @@ -1023,10 +1043,18 @@ final class VideoChatScreenComponent: Component { var participants: [GroupCallParticipantsContext.Participant] = [] let (myPeer, remotePeer) = peers if let myPeer { + var myVideoDescription: GroupCallParticipantsContext.Participant.VideoDescription? + switch state.videoState { + case .active: + myVideoDescription = GroupCallParticipantsContext.Participant.VideoDescription(endpointId: "temp-local", ssrcGroups: [], audioSsrc: nil, isPaused: false) + default: + break + } + participants.append(GroupCallParticipantsContext.Participant( peer: myPeer._asPeer(), ssrc: nil, - videoDescription: nil, + videoDescription: myVideoDescription, presentationDescription: nil, joinTimestamp: 0, raiseHandRating: nil, @@ -1040,10 +1068,18 @@ final class VideoChatScreenComponent: Component { )) } if let remotePeer { + var remoteVideoDescription: GroupCallParticipantsContext.Participant.VideoDescription? + switch state.remoteVideoState { + case .active: + remoteVideoDescription = GroupCallParticipantsContext.Participant.VideoDescription(endpointId: "temp-remote", ssrcGroups: [], audioSsrc: nil, isPaused: false) + default: + break + } + participants.append(GroupCallParticipantsContext.Participant( peer: remotePeer._asPeer(), ssrc: nil, - videoDescription: nil, + videoDescription: remoteVideoDescription, presentationDescription: nil, joinTimestamp: 0, raiseHandRating: nil, @@ -1087,7 +1123,7 @@ final class VideoChatScreenComponent: Component { self.members = component.initialData.members self.invitedPeers = component.initialData.invitedPeers if let members = self.members { - self.invitedPeers.removeAll(where: { invitedPeer in members.participants.contains(where: { $0.peer.id == invitedPeer.id }) }) + self.invitedPeers.removeAll(where: { invitedPeer in members.participants.contains(where: { $0.peer.id == invitedPeer.peer.id }) }) } self.callState = component.initialData.callState } @@ -1128,7 +1164,7 @@ final class VideoChatScreenComponent: Component { self.members = members if let members { - self.invitedPeers.removeAll(where: { invitedPeer in members.participants.contains(where: { $0.peer.id == invitedPeer.id }) }) + self.invitedPeers.removeAll(where: { invitedPeer in members.participants.contains(where: { $0.peer.id == invitedPeer.peer.id }) }) } if let members, let expandedParticipantsVideoState = self.expandedParticipantsVideoState, !expandedParticipantsVideoState.isUIHidden { @@ -1234,10 +1270,16 @@ final class VideoChatScreenComponent: Component { self.invitedPeersDisposable = (groupCall.invitedPeers |> mapToSignal { invitedPeers in return accountContext.engine.data.get( - EngineDataList(invitedPeers.map({ TelegramEngine.EngineData.Item.Peer.Peer(id: $0) })) + EngineDataMap(invitedPeers.map({ TelegramEngine.EngineData.Item.Peer.Peer(id: $0.id) })) ) - |> map { peers in - return peers.compactMap { $0 } + |> map { peers -> [InvitedPeer] in + var result: [InvitedPeer] = [] + for invitedPeer in invitedPeers { + if let maybePeer = peers[invitedPeer.id], let peer = maybePeer { + result.append(InvitedPeer(peer: peer, state: invitedPeer.state)) + } + } + return result } } |> deliverOnMainQueue).startStrict(next: { [weak self] invitedPeers in @@ -1247,7 +1289,7 @@ final class VideoChatScreenComponent: Component { var invitedPeers = invitedPeers if let members { - invitedPeers.removeAll(where: { invitedPeer in members.participants.contains(where: { $0.peer.id == invitedPeer.id }) }) + invitedPeers.removeAll(where: { invitedPeer in members.participants.contains(where: { $0.peer.id == invitedPeer.peer.id }) }) } if self.invitedPeers != invitedPeers { @@ -2461,13 +2503,13 @@ final class VideoChatScreenV2Impl: ViewControllerComponentContainer, VoiceChatCo let peer: EnginePeer? let members: PresentationGroupCallMembers? let callState: PresentationGroupCallState - let invitedPeers: [EnginePeer] + let invitedPeers: [VideoChatScreenComponent.InvitedPeer] init( peer: EnginePeer?, members: PresentationGroupCallMembers?, callState: PresentationGroupCallState, - invitedPeers: [EnginePeer] + invitedPeers: [VideoChatScreenComponent.InvitedPeer] ) { self.peer = peer self.members = members @@ -2633,7 +2675,7 @@ final class VideoChatScreenV2Impl: ViewControllerComponentContainer, VoiceChatCo let accountContext = groupCall.accountContext let invitedPeers = groupCall.invitedPeers |> take(1) |> mapToSignal { invitedPeers in return accountContext.engine.data.get( - EngineDataList(invitedPeers.map({ TelegramEngine.EngineData.Item.Peer.Peer(id: $0) })) + EngineDataList(invitedPeers.map(\.id).map({ TelegramEngine.EngineData.Item.Peer.Peer(id: $0) })) ) } return combineLatest( @@ -2647,7 +2689,12 @@ final class VideoChatScreenV2Impl: ViewControllerComponentContainer, VoiceChatCo peer: peer, members: members, callState: callState, - invitedPeers: invitedPeers.compactMap { $0 } + invitedPeers: invitedPeers.compactMap { peer -> VideoChatScreenComponent.InvitedPeer? in + guard let peer else { + return nil + } + return VideoChatScreenComponent.InvitedPeer(peer: peer, state: nil) + } ) } case let .conferenceSource(conferenceSource): diff --git a/submodules/TelegramCallsUI/Sources/VoiceChatController.swift b/submodules/TelegramCallsUI/Sources/VoiceChatController.swift index edeb62ba96..be28d3b3af 100644 --- a/submodules/TelegramCallsUI/Sources/VoiceChatController.swift +++ b/submodules/TelegramCallsUI/Sources/VoiceChatController.swift @@ -1891,7 +1891,8 @@ final class VoiceChatControllerImpl: ViewController, VoiceChatController { self.updateDecorationsColors() let invitedPeers: Signal<[EnginePeer], NoError> = self.call.invitedPeers - |> mapToSignal { ids -> Signal<[EnginePeer], NoError> in + |> mapToSignal { peers -> Signal<[EnginePeer], NoError> in + let ids = peers.map(\.id) return context.engine.data.get(EngineDataList( ids.map(TelegramEngine.EngineData.Item.Peer.Peer.init) )) diff --git a/submodules/TelegramCore/Sources/State/CallSessionManager.swift b/submodules/TelegramCore/Sources/State/CallSessionManager.swift index 94929eac4a..6f88c27a2d 100644 --- a/submodules/TelegramCore/Sources/State/CallSessionManager.swift +++ b/submodules/TelegramCore/Sources/State/CallSessionManager.swift @@ -19,6 +19,7 @@ public enum CallSessionEndedType { case hungUp case busy case missed + case switchedToConference } public enum CallSessionTerminationReason: Equatable { @@ -709,7 +710,7 @@ private final class CallSessionManagerContext { case .missed: mappedReason = .ended(.missed) case .switchToConference: - mappedReason = .ended(.hungUp) + mappedReason = .ended(.switchedToConference) } context.state = .dropping(reason: mappedReason, disposable: (dropCallSession(network: self.network, addUpdates: self.addUpdates, stableId: id, accessHash: accessHash, isVideo: isVideo, reason: reason) |> deliverOn(self.queue)).start(next: { [weak self] reportRating, sendDebugLogs in @@ -763,7 +764,7 @@ private final class CallSessionManagerContext { if let (id, accessHash) = dropData { self.contextIdByStableId.removeValue(forKey: id) - context.state = .dropping(reason: .ended(.hungUp), disposable: (dropCallSession(network: self.network, addUpdates: self.addUpdates, stableId: id, accessHash: accessHash, isVideo: isVideo, reason: .switchToConference(encryptedGroupKey: encryptedGroupKey)) + context.state = .dropping(reason: .ended(.switchedToConference), disposable: (dropCallSession(network: self.network, addUpdates: self.addUpdates, stableId: id, accessHash: accessHash, isVideo: isVideo, reason: .switchToConference(encryptedGroupKey: encryptedGroupKey)) |> deliverOn(self.queue)).start(next: { [weak self] reportRating, sendDebugLogs in if let strongSelf = self { if let context = strongSelf.contexts[internalId] { @@ -962,11 +963,15 @@ private final class CallSessionManagerContext { case .phoneCallDiscardReasonDisconnect: parsedReason = .error(.disconnected) case .phoneCallDiscardReasonHangup: - parsedReason = .ended(.hungUp) + if context.pendingConference != nil { + parsedReason = .ended(.switchedToConference) + } else { + parsedReason = .ended(.hungUp) + } case .phoneCallDiscardReasonMissed: parsedReason = .ended(.missed) case .phoneCallDiscardReasonAllowGroupCall: - parsedReason = .ended(.hungUp) + parsedReason = .ended(.switchedToConference) } } else { parsedReason = .ended(.hungUp) diff --git a/submodules/TelegramUI/Components/Calls/CallScreen/Sources/PrivateCallScreen.swift b/submodules/TelegramUI/Components/Calls/CallScreen/Sources/PrivateCallScreen.swift index 54e6187304..8681522eed 100644 --- a/submodules/TelegramUI/Components/Calls/CallScreen/Sources/PrivateCallScreen.swift +++ b/submodules/TelegramUI/Components/Calls/CallScreen/Sources/PrivateCallScreen.swift @@ -503,21 +503,27 @@ public final class PrivateCallScreen: OverlayMaskContainerView, AVPictureInPictu self.update(transition: .easeInOut(duration: 0.25)) } - public func takeIncomingVideoLayer() -> (CALayer, VideoSource.Output?)? { - var remoteVideoContainerKey: VideoContainerView.Key? + public func takeIncomingVideoLayer() -> ((CALayer, VideoSource.Output?), Bool)? { + var activeVideoSources: [(VideoContainerView.Key, Bool)] = [] if self.swapLocalAndRemoteVideo { + if let _ = self.activeLocalVideoSource { + activeVideoSources.append((.background, false)) + } if let _ = self.activeRemoteVideoSource { - remoteVideoContainerKey = .foreground + activeVideoSources.append((.foreground, true)) } } else { if let _ = self.activeRemoteVideoSource { - remoteVideoContainerKey = .background + activeVideoSources.append((.background, true)) + } + if let _ = self.activeLocalVideoSource { + activeVideoSources.append((.foreground, false)) } } - if let remoteVideoContainerKey, let videoContainerView = self.videoContainerViews.first(where: { $0.key == remoteVideoContainerKey }) { + if let videoSource = activeVideoSources.first, let videoContainerView = self.videoContainerViews.first(where: { $0.key == videoSource.0 }) { videoContainerView.videoContainerLayerTaken = true - return (videoContainerView.videoContainerLayer, videoContainerView.currentVideoOutput) + return ((videoContainerView.videoContainerLayer, videoContainerView.currentVideoOutput), videoSource.1) } return nil diff --git a/submodules/TelegramVoip/Sources/GroupCallContext.swift b/submodules/TelegramVoip/Sources/GroupCallContext.swift index 5acfdbef45..303bc5fbda 100644 --- a/submodules/TelegramVoip/Sources/GroupCallContext.swift +++ b/submodules/TelegramVoip/Sources/GroupCallContext.swift @@ -1087,10 +1087,8 @@ public final class OngoingGroupCallContext { } - func addRemoteConnectedEvent(isRemoteConntected: Bool) { - #if os(iOS) - self.context.addRemoteConnectedEvent(isRemoteConntected) - #endif + func activateIncomingAudio() { + self.context.activateIncomingAudio() } } @@ -1314,9 +1312,9 @@ public final class OngoingGroupCallContext { } } - public func addRemoteConnectedEvent(isRemoteConntected: Bool) { + public func activateIncomingAudio() { self.impl.with { impl in - impl.addRemoteConnectedEvent(isRemoteConntected: isRemoteConntected) + impl.activateIncomingAudio() } } } diff --git a/submodules/TgVoipWebrtc/PublicHeaders/TgVoipWebrtc/OngoingCallThreadLocalContext.h b/submodules/TgVoipWebrtc/PublicHeaders/TgVoipWebrtc/OngoingCallThreadLocalContext.h index 3135e0dc31..d08d031a79 100644 --- a/submodules/TgVoipWebrtc/PublicHeaders/TgVoipWebrtc/OngoingCallThreadLocalContext.h +++ b/submodules/TgVoipWebrtc/PublicHeaders/TgVoipWebrtc/OngoingCallThreadLocalContext.h @@ -453,7 +453,7 @@ isConference:(bool)isConference; - (void)getStats:(void (^ _Nonnull)(OngoingGroupCallStats * _Nonnull))completion; -- (void)addRemoteConnectedEvent:(bool)isRemoteConnected; +- (void)activateIncomingAudio; @end diff --git a/submodules/TgVoipWebrtc/Sources/OngoingCallThreadLocalContext.mm b/submodules/TgVoipWebrtc/Sources/OngoingCallThreadLocalContext.mm index afceae0862..1f971cbfaf 100644 --- a/submodules/TgVoipWebrtc/Sources/OngoingCallThreadLocalContext.mm +++ b/submodules/TgVoipWebrtc/Sources/OngoingCallThreadLocalContext.mm @@ -66,6 +66,29 @@ namespace tgcalls { +class WrappedChildAudioDeviceModuleControl { +public: + WrappedChildAudioDeviceModuleControl() { + } + + virtual ~WrappedChildAudioDeviceModuleControl() { + _mutex.Lock(); + _mutex.Unlock(); + } + +public: + void setActive() { + _mutex.Lock(); + + + + _mutex.Unlock(); + } + +private: + webrtc::Mutex _mutex; +}; + class SharedAudioDeviceModule { public: virtual ~SharedAudioDeviceModule() = default; @@ -93,12 +116,15 @@ public: } void UpdateAudioCallback(webrtc::AudioTransport *previousAudioCallback, webrtc::AudioTransport *audioCallback) { - if (audioCallback == nil) { - if (_currentAudioTransport == previousAudioCallback) { - _currentAudioTransport = nil; + if (audioCallback) { + _audioTransports.push_back(audioCallback); + } else if (previousAudioCallback) { + for (size_t i = 0; i < _audioTransports.size(); i++) { + if (_audioTransports[i] == previousAudioCallback) { + _audioTransports.erase(_audioTransports.begin() + i); + break; + } } - } else { - _currentAudioTransport = audioCallback; } } @@ -409,19 +435,26 @@ public: bool keyPressed, uint32_t& newMicLevel ) override { - if (_currentAudioTransport) { - return _currentAudioTransport->RecordedDataIsAvailable( - audioSamples, - nSamples, - nBytesPerSample, - nChannels, - samplesPerSec, - totalDelayMS, - clockDrift, - currentMicLevel, - keyPressed, - newMicLevel - ); + if (!_audioTransports.empty()) { + for (size_t i = 0; i < _audioTransports.size(); i++) { + auto result = _audioTransports[_audioTransports.size() - 1]->RecordedDataIsAvailable( + audioSamples, + nSamples, + nBytesPerSample, + nChannels, + samplesPerSec, + totalDelayMS, + clockDrift, + currentMicLevel, + keyPressed, + newMicLevel + ); + if (i == _audioTransports.size() - 1) { + return result; + } + } + + return 0; } else { return 0; } @@ -440,20 +473,26 @@ public: uint32_t& newMicLevel, absl::optional estimatedCaptureTimeNS ) override { - if (_currentAudioTransport) { - return _currentAudioTransport->RecordedDataIsAvailable( - audioSamples, - nSamples, - nBytesPerSample, - nChannels, - samplesPerSec, - totalDelayMS, - clockDrift, - currentMicLevel, - keyPressed, - newMicLevel, - estimatedCaptureTimeNS - ); + if (!_audioTransports.empty()) { + for (size_t i = 0; i < _audioTransports.size(); i++) { + auto result = _audioTransports[_audioTransports.size() - 1]->RecordedDataIsAvailable( + audioSamples, + nSamples, + nBytesPerSample, + nChannels, + samplesPerSec, + totalDelayMS, + clockDrift, + currentMicLevel, + keyPressed, + newMicLevel, + estimatedCaptureTimeNS + ); + if (i == _audioTransports.size() - 1) { + return result; + } + } + return 0; } else { return 0; } @@ -470,8 +509,8 @@ public: int64_t* elapsed_time_ms, int64_t* ntp_time_ms ) override { - if (_currentAudioTransport) { - return _currentAudioTransport->NeedMorePlayData( + if (!_audioTransports.empty()) { + return _audioTransports[_audioTransports.size() - 1]->NeedMorePlayData( nSamples, nBytesPerSample, nChannels, @@ -496,8 +535,8 @@ public: int64_t* elapsed_time_ms, int64_t* ntp_time_ms ) override { - if (_currentAudioTransport) { - _currentAudioTransport->PullRenderData( + if (!_audioTransports.empty()) { + _audioTransports[_audioTransports.size() - 1]->PullRenderData( bits_per_sample, sample_rate, number_of_channels, @@ -540,7 +579,7 @@ public: private: bool _isStarted = false; - webrtc::AudioTransport *_currentAudioTransport = nullptr; + std::vector _audioTransports; }; class WrappedChildAudioDeviceModule : public tgcalls::DefaultWrappedAudioDeviceModule { @@ -556,13 +595,28 @@ public: auto previousAudioCallback = _audioCallback; _audioCallback = audioCallback; - ((WrappedAudioDeviceModuleIOS *)WrappedInstance().get())->UpdateAudioCallback(previousAudioCallback, audioCallback); + if (_isActive) { + ((WrappedAudioDeviceModuleIOS *)WrappedInstance().get())->UpdateAudioCallback(previousAudioCallback, audioCallback); + } return 0; } +public: + void setIsActive() { + if (_isActive) { + return; + } + _isActive = true; + + if (_audioCallback) { + ((WrappedAudioDeviceModuleIOS *)WrappedInstance().get())->UpdateAudioCallback(nullptr, _audioCallback); + } + } + private: webrtc::AudioTransport *_audioCallback = nullptr; + bool _isActive = false; }; class SharedAudioDeviceModuleImpl: public tgcalls::SharedAudioDeviceModule { @@ -1785,7 +1839,9 @@ static void (*InternalVoipLoggingFunction)(NSString *) = NULL; }, .createWrappedAudioDeviceModule = [audioDeviceModule](webrtc::TaskQueueFactory *taskQueueFactory) -> rtc::scoped_refptr { if (audioDeviceModule) { - return audioDeviceModule->getSyncAssumingSameThread()->makeChildAudioDeviceModule(); + auto result = audioDeviceModule->getSyncAssumingSameThread()->makeChildAudioDeviceModule(); + ((WrappedChildAudioDeviceModule *)result.get())->setIsActive(); + return result; } else { return nullptr; } @@ -2493,7 +2549,9 @@ isConference:(bool)isConference { }, .createWrappedAudioDeviceModule = [audioDeviceModule](webrtc::TaskQueueFactory *taskQueueFactory) -> rtc::scoped_refptr { if (audioDeviceModule) { - return audioDeviceModule->getSyncAssumingSameThread()->makeChildAudioDeviceModule(); + auto result = audioDeviceModule->getSyncAssumingSameThread()->makeChildAudioDeviceModule(); + ((WrappedChildAudioDeviceModule *)result.get())->setIsActive(); + return result; } else { return nullptr; } @@ -2835,10 +2893,7 @@ isConference:(bool)isConference { } } -- (void)addRemoteConnectedEvent:(bool)isRemoteConnected { - if (_instance) { - _instance->internal_addCustomNetworkEvent(isRemoteConnected); - } +- (void)activateIncomingAudio { } @end