[WIP] Conference calls

This commit is contained in:
Isaac 2025-02-06 21:12:05 +04:00
parent 0648ffbb2a
commit 3e74304640
17 changed files with 1290 additions and 423 deletions

View File

@ -498,9 +498,42 @@ public enum VideoChatCall: Equatable {
}
}
public extension VideoChatCall {
var accountContext: AccountContext {
switch self {
case let .group(group):
return group.accountContext
case let .conferenceSource(conferenceSource):
return conferenceSource.context
}
}
}
public enum PresentationCurrentCall: Equatable {
case call(PresentationCall)
case group(VideoChatCall)
public static func ==(lhs: PresentationCurrentCall, rhs: PresentationCurrentCall) -> Bool {
switch lhs {
case let .call(lhsCall):
if case let .call(rhsCall) = rhs, lhsCall === rhsCall {
return true
} else {
return false
}
case let .group(lhsCall):
if case let .group(rhsCall) = rhs, lhsCall == rhsCall {
return true
} else {
return false
}
}
}
}
public protocol PresentationCallManager: AnyObject {
var currentCallSignal: Signal<PresentationCall?, NoError> { get }
var currentGroupCallSignal: Signal<PresentationGroupCall?, NoError> { get }
var currentGroupCallSignal: Signal<VideoChatCall?, NoError> { get }
var hasActiveCall: Bool { get }
var hasActiveGroupCall: Bool { get }

View File

@ -466,7 +466,10 @@ final class CallListControllerNode: ASDisplayNode {
if let callManager = context.sharedContext.callManager {
currentGroupCallPeerId = callManager.currentGroupCallSignal
|> map { call -> EnginePeer.Id? in
call?.peerId
guard case let .group(call) = call else {
return nil
}
return call.peerId
}
|> distinctUntilChanged
} else {

View File

@ -77,12 +77,34 @@ private final class GlobalOverlayContainerParent: ASDisplayNode {
}
private final class NavigationControllerNode: ASDisplayNode {
private final class View: UIView {
private var scheduledWithLayout: (() -> Void)?
func schedule(layout f: @escaping () -> Void) {
self.scheduledWithLayout = f
self.setNeedsLayout()
}
override func layoutSubviews() {
super.layoutSubviews()
if let scheduledWithLayout = self.scheduledWithLayout {
self.scheduledWithLayout = nil
scheduledWithLayout()
}
}
}
private weak var controller: NavigationController?
init(controller: NavigationController) {
self.controller = controller
super.init()
self.setViewBlock({
return View(frame: CGRect())
})
}
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
@ -100,6 +122,10 @@ private final class NavigationControllerNode: ASDisplayNode {
}
return false
}
func schedule(layout f: @escaping () -> Void) {
(self.view as? View)?.schedule(layout: f)
}
}
public protocol NavigationControllerDropContentItem: AnyObject {
@ -376,6 +402,9 @@ open class NavigationController: UINavigationController, ContainableController,
self.loadView()
}
self.validLayout = layout
self.scheduledLayoutTransitionRequest = nil
self.updateContainers(layout: layout, transition: transition)
}
@ -1805,18 +1834,11 @@ open class NavigationController: UINavigationController, ContainableController,
return nil
}
private func scheduleAfterLayout(_ f: @escaping () -> Void) {
(self.view as? UITracingLayerView)?.schedule(layout: {
f()
})
self.view.setNeedsLayout()
}
private func scheduleLayoutTransitionRequest(_ transition: ContainedViewLayoutTransition) {
let requestId = self.scheduledLayoutTransitionRequestId
self.scheduledLayoutTransitionRequestId += 1
self.scheduledLayoutTransitionRequest = (requestId, transition)
(self.view as? UITracingLayerView)?.schedule(layout: { [weak self] in
(self.displayNode as? NavigationControllerNode)?.schedule(layout: { [weak self] in
if let strongSelf = self {
if let (currentRequestId, currentRequestTransition) = strongSelf.scheduledLayoutTransitionRequest, currentRequestId == requestId {
strongSelf.scheduledLayoutTransitionRequest = nil
@ -1869,7 +1891,8 @@ open class NavigationController: UINavigationController, ContainableController,
}
if let layout = self.validLayout {
inCallStatusBar.updateState(statusBar: nil, withSafeInsets: !layout.safeInsets.top.isZero, inCallNode: forceInCallStatusBar, animated: false)
self.containerLayoutUpdated(layout, transition: transition)
self.scheduleLayoutTransitionRequest(transition)
//self.containerLayoutUpdated(layout, transition: transition)
} else {
self.updateInCallStatusBarState = forceInCallStatusBar
}
@ -1879,7 +1902,9 @@ open class NavigationController: UINavigationController, ContainableController,
inCallStatusBar?.removeFromSupernode()
})
if let layout = self.validLayout {
self.containerLayoutUpdated(layout, transition: transition)
let _ = layout
self.scheduleLayoutTransitionRequest(transition)
//self.containerLayoutUpdated(layout, transition: transition)
}
}
}

View File

@ -260,10 +260,13 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder {
case let .peer(peerId):
let currentGroupCall: Signal<PresentationGroupCall?, NoError> = callManager.currentGroupCallSignal
|> distinctUntilChanged(isEqual: { lhs, rhs in
return lhs?.internalId == rhs?.internalId
return lhs == rhs
})
|> map { call -> PresentationGroupCall? in
guard let call = call, call.peerId == peerId && call.account.peerId == context.account.peerId else {
guard case let .group(call) = call else {
return nil
}
guard call.peerId == peerId && call.account.peerId == context.account.peerId else {
return nil
}
return call

View File

@ -62,6 +62,15 @@ final class SharedCallAudioContext {
audioSessionControl.setOutputMode(.custom(self.currentAudioOutputValue))
audioSessionControl.setup(synchronous: true)
}
let audioSessionActive: Signal<Bool, NoError>
if let callKitIntegration = self.callKitIntegration {
audioSessionActive = callKitIntegration.audioSessionActive
} else {
audioSessionControl.activate({ _ in })
audioSessionActive = .single(true)
}
self.isAudioSessionActivePromise.set(audioSessionActive)
}
}
}, deactivate: { [weak self] _ in
@ -130,7 +139,7 @@ final class SharedCallAudioContext {
guard let self else {
return
}
let _ = self
self.audioDevice?.setIsAudioSessionActive(value)
})
}

View File

@ -61,9 +61,11 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
private var currentCallDisposable = MetaDisposable()
private let removeCurrentCallDisposable = MetaDisposable()
private let removeCurrentGroupCallDisposable = MetaDisposable()
private var callToConferenceDisposable: Disposable?
private var currentUpgradedToConferenceCallId: CallSessionInternalId?
private var currentGroupCallValue: PresentationGroupCallImpl?
private var currentGroupCall: PresentationGroupCallImpl? {
private var currentGroupCallValue: VideoChatCall?
private var currentGroupCall: VideoChatCall? {
return self.currentGroupCallValue
}
@ -95,8 +97,8 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
return self.currentCallPromise.get()
}
private let currentGroupCallPromise = Promise<PresentationGroupCall?>(nil)
public var currentGroupCallSignal: Signal<PresentationGroupCall?, NoError> {
private let currentGroupCallPromise = Promise<VideoChatCall?>(nil)
public var currentGroupCallSignal: Signal<VideoChatCall?, NoError> {
return self.currentGroupCallPromise.get()
}
@ -297,16 +299,23 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
self.startCallDisposable.dispose()
self.proxyServerDisposable?.dispose()
self.callSettingsDisposable?.dispose()
self.callToConferenceDisposable?.dispose()
}
private func ringingStatesUpdated(_ ringingStates: [(AccountContext, Peer, CallSessionRingingState, Bool, NetworkType)], enableCallKit: Bool) {
if let firstState = ringingStates.first {
if self.currentCall == nil && self.currentGroupCall == nil {
self.currentCallDisposable.set((combineLatest(firstState.0.account.postbox.preferencesView(keys: [PreferencesKeys.voipConfiguration, PreferencesKeys.appConfiguration]) |> take(1), accountManager.sharedData(keys: [SharedDataKeys.autodownloadSettings, ApplicationSpecificSharedDataKeys.experimentalUISettings]) |> take(1))
self.currentCallDisposable.set((combineLatest(
firstState.0.account.postbox.preferencesView(keys: [PreferencesKeys.voipConfiguration, PreferencesKeys.appConfiguration]) |> take(1),
accountManager.sharedData(keys: [SharedDataKeys.autodownloadSettings, ApplicationSpecificSharedDataKeys.experimentalUISettings]) |> take(1)
)
|> deliverOnMainQueue).start(next: { [weak self] preferences, sharedData in
guard let strongSelf = self else {
return
}
if strongSelf.currentUpgradedToConferenceCallId == firstState.2.id {
return
}
let configuration = preferences.values[PreferencesKeys.voipConfiguration]?.get(VoipConfiguration.self) ?? .defaultValue
let autodownloadSettings = sharedData.entries[SharedDataKeys.autodownloadSettings]?.get(AutodownloadSettings.self) ?? .defaultSettings
@ -338,18 +347,6 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
preferredVideoCodec: experimentalSettings.preferredVideoCodec
)
strongSelf.updateCurrentCall(call)
strongSelf.currentCallPromise.set(.single(call))
strongSelf.hasActivePersonalCallsPromise.set(true)
strongSelf.removeCurrentCallDisposable.set((call.canBeRemoved
|> deliverOnMainQueue).start(next: { [weak self, weak call] value in
if value, let strongSelf = self, let call = call {
if strongSelf.currentCall === call {
strongSelf.updateCurrentCall(nil)
strongSelf.currentCallPromise.set(.single(nil))
strongSelf.hasActivePersonalCallsPromise.set(false)
}
}
}))
}))
} else {
for (context, _, state, _, _) in ringingStates {
@ -370,14 +367,17 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
alreadyInCallWithPeerId = call.peerId
} else if let currentGroupCall = self.currentGroupCallValue {
alreadyInCall = true
alreadyInCallWithPeerId = currentGroupCall.peerId
switch currentGroupCall {
case let .conferenceSource(conferenceSource):
alreadyInCallWithPeerId = conferenceSource.peerId
case let .group(groupCall):
alreadyInCallWithPeerId = groupCall.peerId
}
} else {
if #available(iOS 10.0, *) {
if CXCallObserver().calls.contains(where: { $0.hasEnded == false }) {
alreadyInCall = true
}
}
}
if alreadyInCall, !endCurrentIfAny {
return .alreadyInProgress(alreadyInCallWithPeerId)
@ -456,12 +456,22 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
begin()
}))
} else if let currentGroupCall = self.currentGroupCallValue {
self.startCallDisposable.set((currentGroupCall.leave(terminateIfPossible: false)
switch currentGroupCall {
case let .conferenceSource(conferenceSource):
self.startCallDisposable.set((conferenceSource.hangUp()
|> filter { $0 }
|> take(1)
|> deliverOnMainQueue).start(next: { _ in
begin()
}))
case let .group(groupCall):
self.startCallDisposable.set((groupCall.leave(terminateIfPossible: false)
|> filter { $0 }
|> take(1)
|> deliverOnMainQueue).start(next: { _ in
begin()
}))
}
} else {
begin()
}
@ -478,12 +488,22 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
begin()
}))
} else if let currentGroupCall = self.currentGroupCallValue {
self.startCallDisposable.set((currentGroupCall.leave(terminateIfPossible: false)
switch currentGroupCall {
case let .conferenceSource(conferenceSource):
self.startCallDisposable.set((conferenceSource.hangUp()
|> filter { $0 }
|> take(1)
|> deliverOnMainQueue).start(next: { _ in
begin()
}))
case let .group(groupCall):
self.startCallDisposable.set((groupCall.leave(terminateIfPossible: false)
|> filter { $0 }
|> take(1)
|> deliverOnMainQueue).start(next: { _ in
begin()
}))
}
} else {
begin()
}
@ -540,9 +560,18 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
return context.account.callSessionManager.request(peerId: peerId, isVideo: isVideo, enableVideo: isVideoPossible, conferenceCall: nil, internalId: internalId)
}
return (combineLatest(queue: .mainQueue(), request, networkType |> take(1), context.account.postbox.peerView(id: peerId) |> map { peerView -> Bool in
return (combineLatest(queue: .mainQueue(),
request,
networkType |> take(1),
context.account.postbox.peerView(id: peerId)
|> map { peerView -> Bool in
return peerView.peerIsContact
} |> take(1), context.account.postbox.preferencesView(keys: [PreferencesKeys.voipConfiguration, PreferencesKeys.appConfiguration]) |> take(1), accountManager.sharedData(keys: [SharedDataKeys.autodownloadSettings, ApplicationSpecificSharedDataKeys.experimentalUISettings]) |> take(1), areVideoCallsAvailable)
}
|> take(1),
context.account.postbox.preferencesView(keys: [PreferencesKeys.voipConfiguration, PreferencesKeys.appConfiguration]) |> take(1),
accountManager.sharedData(keys: [SharedDataKeys.autodownloadSettings, ApplicationSpecificSharedDataKeys.experimentalUISettings]) |> take(1),
areVideoCallsAvailable
)
|> deliverOnMainQueue
|> beforeNext { internalId, currentNetworkType, isContact, preferences, sharedData, areVideoCallsAvailable in
if let strongSelf = self, accessEnabled {
@ -586,18 +615,6 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
preferredVideoCodec: experimentalSettings.preferredVideoCodec
)
strongSelf.updateCurrentCall(call)
strongSelf.currentCallPromise.set(.single(call))
strongSelf.hasActivePersonalCallsPromise.set(true)
strongSelf.removeCurrentCallDisposable.set((call.canBeRemoved
|> deliverOnMainQueue).start(next: { [weak call] value in
if value, let strongSelf = self, let call = call {
if strongSelf.currentCall === call {
strongSelf.updateCurrentCall(nil)
strongSelf.currentCallPromise.set(.single(nil))
strongSelf.hasActivePersonalCallsPromise.set(false)
}
}
}))
}
})
|> mapToSignal { value -> Signal<Bool, NoError> in
@ -613,23 +630,126 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
self.resumeMedia = self.isMediaPlaying()
}
if self.currentCallValue !== value {
self.currentCallValue = value
self.callToConferenceDisposable?.dispose()
self.callToConferenceDisposable = nil
self.currentUpgradedToConferenceCallId = nil
if let currentCallValue = self.currentCallValue {
self.callToConferenceDisposable = (currentCallValue.conferenceState
|> filter { conferenceState in
return conferenceState != nil
}
|> take(1)
|> deliverOnMainQueue).startStrict(next: { [weak self, weak currentCallValue] _ in
guard let self, let currentCallValue, self.currentCallValue === currentCallValue else {
return
}
self.currentUpgradedToConferenceCallId = currentCallValue.internalId
self.removeCurrentCallDisposable.set(nil)
self.updateCurrentGroupCall(.conferenceSource(currentCallValue))
self.updateCurrentCall(nil)
})
self.currentCallPromise.set(.single(currentCallValue))
self.hasActivePersonalCallsPromise.set(true)
self.removeCurrentCallDisposable.set((currentCallValue.canBeRemoved
|> deliverOnMainQueue).start(next: { [weak self, weak currentCallValue] value in
if value, let self, let currentCallValue {
if self.currentCall === currentCallValue {
self.updateCurrentCall(nil)
self.currentCallPromise.set(.single(nil))
self.hasActivePersonalCallsPromise.set(false)
}
}
}))
} else {
self.currentCallPromise.set(.single(nil))
self.hasActivePersonalCallsPromise.set(false)
}
}
if !wasEmpty && isEmpty && self.resumeMedia {
self.resumeMedia = false
self.resumeMediaPlayback()
}
}
private func updateCurrentGroupCall(_ value: PresentationGroupCallImpl?) {
private func updateCurrentGroupCall(_ value: VideoChatCall?) {
let wasEmpty = self.currentGroupCallValue == nil
let isEmpty = value == nil
if wasEmpty && !isEmpty {
self.resumeMedia = self.isMediaPlaying()
}
if self.currentGroupCallValue != value {
self.currentGroupCallValue = value
if let value {
switch value {
case let .conferenceSource(conferenceSource):
self.callToConferenceDisposable?.dispose()
self.callToConferenceDisposable = (conferenceSource.conferenceState
|> filter { value in
if let value, case .ready = value {
return true
} else {
return false
}
}
|> take(1)
|> deliverOnMainQueue).startStrict(next: { [weak self, weak conferenceSource] _ in
guard let self, let conferenceSource, self.currentGroupCallValue == .conferenceSource(conferenceSource) else {
return
}
guard let groupCall = conferenceSource.conferenceCall else {
return
}
self.updateCurrentGroupCall(.group(groupCall))
})
self.currentGroupCallPromise.set(.single(.conferenceSource(conferenceSource)))
self.hasActiveGroupCallsPromise.set(true)
self.removeCurrentGroupCallDisposable.set((conferenceSource.canBeRemoved
|> filter { $0 }
|> take(1)
|> deliverOnMainQueue).start(next: { [weak self, weak conferenceSource] value in
guard let self, let conferenceSource else {
return
}
if value {
if self.currentGroupCall == .conferenceSource(conferenceSource) {
self.updateCurrentGroupCall(nil)
}
}
}))
case let .group(groupCall):
self.currentGroupCallPromise.set(.single(.group(groupCall)))
self.hasActiveGroupCallsPromise.set(true)
self.removeCurrentGroupCallDisposable.set((groupCall.canBeRemoved
|> filter { $0 }
|> take(1)
|> deliverOnMainQueue).start(next: { [weak self, weak groupCall] value in
guard let self, let groupCall else {
return
}
if value {
if self.currentGroupCall == .group(groupCall) {
self.updateCurrentGroupCall(nil)
}
}
}))
}
} else {
self.currentGroupCallPromise.set(.single(nil))
self.hasActiveGroupCallsPromise.set(false)
}
}
if !wasEmpty && isEmpty && self.resumeMedia {
self.resumeMedia = false
self.resumeMediaPlayback()
@ -671,7 +791,7 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
)
|> deliverOnMainQueue
|> mapToSignal { [weak self, weak parentController] accessEnabled, peer -> Signal<Bool, NoError> in
guard let strongSelf = self else {
guard let self else {
return .single(false)
}
@ -684,11 +804,10 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
isChannel = true
}
if shouldUseV2VideoChatImpl(context: accountContext) {
if let parentController {
parentController.push(ScheduleVideoChatSheetScreen(
context: accountContext,
scheduleAction: { timestamp in
scheduleAction: { [weak self] timestamp in
guard let self else {
return
}
@ -712,67 +831,11 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
)
call.schedule(timestamp: timestamp)
self.updateCurrentGroupCall(call)
self.currentGroupCallPromise.set(.single(call))
self.hasActiveGroupCallsPromise.set(true)
self.removeCurrentGroupCallDisposable.set((call.canBeRemoved
|> filter { $0 }
|> take(1)
|> deliverOnMainQueue).start(next: { [weak self, weak call] value in
guard let self, let call else {
return
}
if value {
if self.currentGroupCall === call {
self.updateCurrentGroupCall(nil)
self.currentGroupCallPromise.set(.single(nil))
self.hasActiveGroupCallsPromise.set(false)
}
}
}))
self.updateCurrentGroupCall(.group(call))
}
))
}
return .single(true)
} else {
let call = PresentationGroupCallImpl(
accountContext: accountContext,
audioSession: strongSelf.audioSession,
callKitIntegration: nil,
getDeviceAccessData: strongSelf.getDeviceAccessData,
initialCall: nil,
internalId: internalId,
peerId: peerId,
isChannel: isChannel,
invite: nil,
joinAsPeerId: nil,
isStream: false,
encryptionKey: nil,
conferenceFromCallId: nil,
isConference: false,
sharedAudioContext: nil
)
strongSelf.updateCurrentGroupCall(call)
strongSelf.currentGroupCallPromise.set(.single(call))
strongSelf.hasActiveGroupCallsPromise.set(true)
strongSelf.removeCurrentGroupCallDisposable.set((call.canBeRemoved
|> filter { $0 }
|> take(1)
|> deliverOnMainQueue).start(next: { [weak call] value in
guard let strongSelf = self, let call = call else {
return
}
if value {
if strongSelf.currentGroupCall === call {
strongSelf.updateCurrentGroupCall(nil)
strongSelf.currentGroupCallPromise.set(.single(nil))
strongSelf.hasActiveGroupCallsPromise.set(false)
}
}
}))
}
return .single(true)
}
}
@ -787,15 +850,29 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
if let currentGroupCall = self.currentGroupCallValue {
if endCurrentIfAny {
let endSignal = currentGroupCall.leave(terminateIfPossible: false)
switch currentGroupCall {
case let .conferenceSource(conferenceSource):
self.startCallDisposable.set((conferenceSource.hangUp()
|> filter { $0 }
|> take(1)
|> deliverOnMainQueue
self.startCallDisposable.set(endSignal.start(next: { _ in
|> deliverOnMainQueue).start(next: { _ in
begin()
}))
case let .group(groupCall):
self.startCallDisposable.set((groupCall.leave(terminateIfPossible: false)
|> filter { $0 }
|> take(1)
|> deliverOnMainQueue).start(next: { _ in
begin()
}))
}
} else {
return .alreadyInProgress(currentGroupCall.peerId)
switch currentGroupCall {
case let .conferenceSource(conferenceSource):
return .alreadyInProgress(conferenceSource.peerId)
case let .group(groupCall):
return .alreadyInProgress(groupCall.peerId)
}
}
} else if let currentCall = self.currentCall {
if endCurrentIfAny {
@ -832,15 +909,29 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
if let currentGroupCall = self.currentGroupCallValue {
if endCurrentIfAny {
let endSignal = currentGroupCall.leave(terminateIfPossible: false)
switch currentGroupCall {
case let .conferenceSource(conferenceSource):
self.startCallDisposable.set((conferenceSource.hangUp()
|> filter { $0 }
|> take(1)
|> deliverOnMainQueue
self.startCallDisposable.set(endSignal.start(next: { _ in
|> deliverOnMainQueue).start(next: { _ in
begin()
}))
case let .group(groupCall):
self.startCallDisposable.set((groupCall.leave(terminateIfPossible: false)
|> filter { $0 }
|> take(1)
|> deliverOnMainQueue).start(next: { _ in
begin()
}))
}
} else {
return .alreadyInProgress(currentGroupCall.peerId)
switch currentGroupCall {
case let .conferenceSource(conferenceSource):
return .alreadyInProgress(conferenceSource.peerId)
case let .group(groupCall):
return .alreadyInProgress(groupCall.peerId)
}
}
} else if let currentCall = self.currentCall {
if endCurrentIfAny {
@ -919,6 +1010,29 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
return .single(false)
}
strongSelf.createGroupCall(
accountContext: accountContext,
peerId: peerId,
peer: peer,
initialCall: initialCall,
internalId: internalId,
invite: invite,
joinAsPeerId: joinAsPeerId
)
return .single(true)
}
}
private func createGroupCall(
accountContext: AccountContext,
peerId: EnginePeer.Id,
peer: EnginePeer?,
initialCall: EngineGroupCallDescription,
internalId: CallSessionInternalId,
invite: String?,
joinAsPeerId: EnginePeer.Id?
) {
var isChannel = false
if let peer = peer, case let .channel(channel) = peer, case .broadcast = channel.info {
isChannel = true
@ -926,9 +1040,9 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
let call = PresentationGroupCallImpl(
accountContext: accountContext,
audioSession: strongSelf.audioSession,
audioSession: self.audioSession,
callKitIntegration: nil,
getDeviceAccessData: strongSelf.getDeviceAccessData,
getDeviceAccessData: self.getDeviceAccessData,
initialCall: initialCall,
internalId: internalId,
peerId: peerId,
@ -941,26 +1055,6 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
isConference: false,
sharedAudioContext: nil
)
strongSelf.updateCurrentGroupCall(call)
strongSelf.currentGroupCallPromise.set(.single(call))
strongSelf.hasActiveGroupCallsPromise.set(true)
strongSelf.removeCurrentGroupCallDisposable.set((call.canBeRemoved
|> filter { $0 }
|> take(1)
|> deliverOnMainQueue).start(next: { [weak call] value in
guard let strongSelf = self, let call = call else {
return
}
if value {
if strongSelf.currentGroupCall === call {
strongSelf.updateCurrentGroupCall(nil)
strongSelf.currentGroupCallPromise.set(.single(nil))
strongSelf.hasActiveGroupCallsPromise.set(false)
}
}
}))
return .single(true)
}
self.updateCurrentGroupCall(.group(call))
}
}

View File

@ -1111,6 +1111,13 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
self.conferenceFromCallId = conferenceFromCallId
self.isConference = isConference
self.encryptionKey = encryptionKey
var sharedAudioContext = sharedAudioContext
if sharedAudioContext == nil && accountContext.sharedContext.immediateExperimentalUISettings.conferenceCalls {
let sharedAudioContextValue = SharedCallAudioContext(audioSession: audioSession, callKitIntegration: callKitIntegration)
sharedAudioContext = sharedAudioContextValue
}
self.sharedAudioContext = sharedAudioContext
if self.sharedAudioContext == nil && !accountContext.sharedContext.immediateExperimentalUISettings.liveStreamV2 {
@ -1906,7 +1913,6 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
var encryptionKey: Data?
encryptionKey = self.encryptionKey?.key
encryptionKey = nil
let contextAudioSessionActive: Signal<Bool, NoError>
if self.sharedAudioContext != nil {
@ -2875,18 +2881,18 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
let _ = (callManager.currentGroupCallSignal
|> take(1)
|> deliverOnMainQueue).start(next: { [weak self] call in
guard let strongSelf = self else {
guard let self else {
return
}
if let call = call, call !== strongSelf {
strongSelf.wasRemoved.set(.single(true))
if let call = call, call != .group(self) {
self.wasRemoved.set(.single(true))
return
}
strongSelf.beginTone(tone: .groupLeft)
self.beginTone(tone: .groupLeft)
Queue.mainQueue().after(1.0, {
strongSelf.wasRemoved.set(.single(true))
self.wasRemoved.set(.single(true))
})
})
}

View File

@ -25,15 +25,6 @@ import LegacyComponents
import TooltipUI
extension VideoChatCall {
var accountContext: AccountContext {
switch self {
case let .group(group):
return group.accountContext
case let .conferenceSource(conferenceSource):
return conferenceSource.context
}
}
var myAudioLevelAndSpeaking: Signal<(Float, Bool), NoError> {
switch self {
case let .group(group):

View File

@ -98,7 +98,7 @@ public final class VoiceChatJoinScreen: ViewController {
currentGroupCall = callManager.currentGroupCallSignal
|> castError(GetCurrentGroupCallError.self)
|> mapToSignal { call -> Signal<(PresentationGroupCall, Int64, Bool)?, GetCurrentGroupCallError> in
if let call = call {
if case let .group(call) = call {
return call.summaryState
|> castError(GetCurrentGroupCallError.self)
|> map { state -> (PresentationGroupCall, Int64, Bool)? in

View File

@ -93,7 +93,7 @@ final class KeyEmojiView: HighlightTrackingButton {
for i in 0 ..< self.emojiViews.count {
let emojiView = self.emojiViews[i]
emojiView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
emojiView.layer.animatePosition(from: CGPoint(x: CGFloat(i) * 30.0, y: 0.0), to: CGPoint(), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
//emojiView.layer.animateScale(from: 0.2, to: 1.0, duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
}
}
@ -109,7 +109,7 @@ final class KeyEmojiView: HighlightTrackingButton {
}
private func update(params: Params, transition: ComponentTransition) -> CGSize {
let itemSpacing: CGFloat = 1.0
let itemSpacing: CGFloat = 0.0
var height: CGFloat = 0.0
var nextX = 0.0
@ -118,7 +118,7 @@ final class KeyEmojiView: HighlightTrackingButton {
nextX += itemSpacing
}
let emojiView = self.emojiViews[i]
let itemSize = emojiView.update(string: emoji[i], fontSize: params.isExpanded ? 40.0 : 16.0, fontWeight: 0.0, color: .white, constrainedWidth: 100.0, transition: transition)
let itemSize = emojiView.update(string: emoji[i], fontSize: params.isExpanded ? 40.0 : 15.0, fontWeight: 0.0, color: .white, constrainedWidth: 100.0, transition: transition)
if height == 0.0 {
height = itemSize.height
}
@ -144,7 +144,7 @@ func generateParabollicMotionKeyframes(from sourcePoint: CGPoint, to targetPosit
let numPoints: Int = Int(ceil(Double(UIScreen.main.maximumFramesPerSecond) * duration))
var keyframes: [CGPoint] = []
if abs(y1 - y3) < 5.0 && abs(x1 - x3) < 5.0 {
if abs(y1 - y3) < 5.0 || abs(x1 - x3) < 5.0 {
for rawI in 0 ..< numPoints {
let i = reverse ? (numPoints - 1 - rawI) : rawI
let ks = CGFloat(i) / CGFloat(numPoints - 1)

View File

@ -1241,8 +1241,8 @@ public final class PrivateCallScreen: OverlayMaskContainerView, AVPictureInPictu
let titleSize = self.titleView.update(
string: titleString,
fontSize: !havePrimaryVideo ? 28.0 : 17.0,
fontWeight: !havePrimaryVideo ? 0.0 : 0.25,
fontSize: 28.0,
fontWeight: 0.0,
color: .white,
constrainedWidth: params.size.width - 16.0 * 2.0,
transition: transition
@ -1301,7 +1301,11 @@ public final class PrivateCallScreen: OverlayMaskContainerView, AVPictureInPictu
if currentAreControlsHidden {
titleY = -8.0 - titleSize.height - statusSize.height
} else if havePrimaryVideo {
if case .active = params.state.lifecycleState {
titleY = params.insets.top + 2.0 + 54.0
} else {
titleY = params.insets.top + 2.0
}
} else {
titleY = collapsedAvatarFrame.maxY + 39.0
}
@ -1313,7 +1317,7 @@ public final class PrivateCallScreen: OverlayMaskContainerView, AVPictureInPictu
size: titleSize
)
transition.setFrame(view: self.titleView, frame: titleFrame)
genericAlphaTransition.setAlpha(view: self.titleView, alpha: (currentAreControlsHidden || self.isAnimatedOutToGroupCall) ? 0.0 : 1.0)
genericAlphaTransition.setAlpha(view: self.titleView, alpha: (currentAreControlsHidden || self.isAnimatedOutToGroupCall || (havePrimaryVideo && self.isEmojiKeyExpanded)) ? 0.0 : 1.0)
var emojiViewSizeValue: CGSize?
var emojiTransition = transition
@ -1392,15 +1396,10 @@ public final class PrivateCallScreen: OverlayMaskContainerView, AVPictureInPictu
}
}
var statusEmojiWidth: CGFloat = 0.0
let statusEmojiSpacing: CGFloat = 8.0
if !self.isEmojiKeyExpanded, let emojiViewSize = emojiViewSizeValue {
statusEmojiWidth = statusEmojiSpacing + emojiViewSize.width
}
let statusFrame = CGRect(
origin: CGPoint(
x: (params.size.width - statusSize.width - statusEmojiWidth) * 0.5,
y: titleFrame.maxY + (havePrimaryVideo ? 0.0 : 4.0)
x: (params.size.width - statusSize.width) * 0.5,
y: titleFrame.maxY + 4.0
),
size: statusSize
)
@ -1414,12 +1413,19 @@ public final class PrivateCallScreen: OverlayMaskContainerView, AVPictureInPictu
}
} else {
transition.setFrame(view: self.statusView, frame: statusFrame)
genericAlphaTransition.setAlpha(view: self.statusView, alpha: (currentAreControlsHidden || self.isAnimatedOutToGroupCall) ? 0.0 : 1.0)
genericAlphaTransition.setAlpha(view: self.statusView, alpha: (currentAreControlsHidden || self.isAnimatedOutToGroupCall || (havePrimaryVideo && self.isEmojiKeyExpanded)) ? 0.0 : 1.0)
}
if case .active = params.state.lifecycleState {
if let emojiView = self.emojiView, !self.isEmojiKeyExpanded, let emojiViewSize = emojiViewSizeValue {
let emojiViewFrame = CGRect(origin: CGPoint(x: statusFrame.maxX + statusEmojiSpacing, y: statusFrame.minY + floorToScreenPixels((statusFrame.height - emojiViewSize.height) * 0.5)), size: emojiViewSize)
let emojiViewY: CGFloat
if currentAreControlsHidden {
emojiViewY = -8.0 - emojiViewSize.height
} else {
emojiViewY = params.insets.top + 14.0
}
let emojiViewFrame = CGRect(origin: CGPoint(x: floor((params.size.width - emojiViewSize.width) * 0.5), y: emojiViewY), size: emojiViewSize)
if case let .curve(duration, curve) = transition.animation, emojiViewWasExpanded {
let distance = CGPoint(x: emojiViewFrame.midX - emojiView.center.x, y: emojiViewFrame.midY - emojiView.center.y)

View File

@ -163,17 +163,18 @@ public final class SharedAccountContextImpl: SharedAccountContext {
public var callManager: PresentationCallManager?
let hasInAppPurchases: Bool
private var callDisposable: Disposable?
private var callStateDisposable: Disposable?
private(set) var currentCallStatusBarNode: CallStatusBarNodeImpl?
private var callDisposable: Disposable?
private var groupCallDisposable: Disposable?
private var callController: CallController?
private var call: PresentationCall?
private var currentCall: PresentationCurrentCall?
public let hasOngoingCall = ValuePromise<Bool>(false)
private let callState = Promise<PresentationCallState?>(nil)
private var awaitingCallConnectionDisposable: Disposable?
private var callPeerDisposable: Disposable?
private var callIsConferenceDisposable: Disposable?
@ -182,7 +183,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
public var currentGroupCallController: ViewController? {
return self.groupCallController
}
private let hasGroupCallOnScreenPromise = ValuePromise<Bool>(false, ignoreRepeated: true)
private let hasGroupCallOnScreenPromise = Promise<Bool>(false)
public var hasGroupCallOnScreen: Signal<Bool, NoError> {
return self.hasGroupCallOnScreenPromise.get()
}
@ -806,7 +807,13 @@ public final class SharedAccountContextImpl: SharedAccountContext {
return
}
if call !== self.call {
if let call {
self.updateCurrentCall(call: .call(call))
} else if let current = self.currentCall, case .call = current {
self.updateCurrentCall(call: nil)
}
/*if call !== self.call {
let previousCall = self.call
self.call = call
@ -842,11 +849,6 @@ public final class SharedAccountContextImpl: SharedAccountContext {
self.callIsConferenceDisposable = nil
if let call {
if call.conferenceStateValue == nil && call.conferenceCall == nil {
self.callState.set(call.state
|> map(Optional.init))
}
self.hasOngoingCall.set(true)
setNotificationCall(call)
@ -862,14 +864,12 @@ public final class SharedAccountContextImpl: SharedAccountContext {
}
guard let callController = self.callController, callController.call === call else {
if self.callController == nil, call.conferenceStateValue != nil {
self.callState.set(.single(nil))
self.presentControllerWithCurrentCall()
self.notificationController?.setBlocking(nil)
}
return
}
if call.conferenceStateValue != nil {
self.callState.set(.single(nil))
self.presentControllerWithCurrentCall()
}
})
@ -932,55 +932,64 @@ public final class SharedAccountContextImpl: SharedAccountContext {
self.awaitingCallConnectionDisposable = nil
setNotificationCall(nil)
}
}
}*/
})
self.groupCallDisposable = (callManager.currentGroupCallSignal
|> deliverOnMainQueue).start(next: { [weak self] call in
if let strongSelf = self {
if call.flatMap(VideoChatCall.group) != strongSelf.groupCallController?.call {
strongSelf.groupCallController?.dismiss(closing: true, manual: false)
strongSelf.groupCallController = nil
strongSelf.hasOngoingCall.set(false)
guard let self else {
return
}
if let call {
self.updateCurrentCall(call: .group(call))
} else if let current = self.currentCall, case .group = current {
self.updateCurrentCall(call: nil)
}
/*if call.flatMap(VideoChatCall.group) != self.groupCallController?.call {
self.groupCallController?.dismiss(closing: true, manual: false)
self.groupCallController = nil
self.hasOngoingCall.set(false)
if let call = call, let navigationController = mainWindow.viewController as? NavigationController {
mainWindow.hostView.containerView.endEditing(true)
if call.isStream {
strongSelf.hasGroupCallOnScreenPromise.set(true)
self.hasGroupCallOnScreenPromise.set(true)
let groupCallController = MediaStreamComponentController(call: call)
groupCallController.onViewDidAppear = { [weak self] in
if let strongSelf = self {
strongSelf.hasGroupCallOnScreenPromise.set(true)
if let self {
self.hasGroupCallOnScreenPromise.set(true)
}
}
groupCallController.onViewDidDisappear = { [weak self] in
if let strongSelf = self {
strongSelf.hasGroupCallOnScreenPromise.set(false)
if let self {
self.hasGroupCallOnScreenPromise.set(false)
}
}
groupCallController.navigationPresentation = .flatModal
groupCallController.parentNavigationController = navigationController
strongSelf.groupCallController = groupCallController
self.groupCallController = groupCallController
navigationController.pushViewController(groupCallController)
} else {
strongSelf.hasGroupCallOnScreenPromise.set(true)
self.hasGroupCallOnScreenPromise.set(true)
let _ = (makeVoiceChatControllerInitialData(sharedContext: strongSelf, accountContext: call.accountContext, call: .group(call))
|> deliverOnMainQueue).start(next: { [weak strongSelf, weak navigationController] initialData in
guard let strongSelf, let navigationController else {
let _ = (makeVoiceChatControllerInitialData(sharedContext: self, accountContext: call.accountContext, call: .group(call))
|> deliverOnMainQueue).start(next: { [weak self, weak navigationController] initialData in
guard let self, let navigationController else {
return
}
let groupCallController = makeVoiceChatController(sharedContext: strongSelf, accountContext: call.accountContext, call: .group(call), initialData: initialData, sourceCallController: nil)
groupCallController.onViewDidAppear = { [weak strongSelf] in
if let strongSelf {
strongSelf.hasGroupCallOnScreenPromise.set(true)
let groupCallController = makeVoiceChatController(sharedContext: self, accountContext: call.accountContext, call: .group(call), initialData: initialData, sourceCallController: nil)
groupCallController.onViewDidAppear = { [weak self] in
if let self {
self.hasGroupCallOnScreenPromise.set(true)
}
}
groupCallController.onViewDidDisappear = { [weak strongSelf] in
if let strongSelf {
strongSelf.hasGroupCallOnScreenPromise.set(false)
groupCallController.onViewDidDisappear = { [weak self] in
if let self {
self.hasGroupCallOnScreenPromise.set(false)
}
}
groupCallController.navigationPresentation = .flatModal
@ -990,93 +999,18 @@ public final class SharedAccountContextImpl: SharedAccountContext {
})
}
strongSelf.hasOngoingCall.set(true)
self.hasOngoingCall.set(true)
} else {
strongSelf.hasOngoingCall.set(false)
}
}
}
})
let callSignal: Signal<(PresentationCall?, PresentationGroupCall?), NoError> = .single((nil, nil))
|> then(
callManager.currentCallSignal
|> deliverOnMainQueue
|> mapToSignal { call -> Signal<(PresentationCall?, PresentationGroupCall?), NoError> in
guard let call else {
return .single((nil, nil))
}
return call.state
|> map { [weak call] state -> (PresentationCall?, PresentationGroupCall?) in
guard let call else {
return (nil, nil)
}
switch state.state {
case .ringing:
return (nil, nil)
case .terminating, .terminated:
return (nil, nil)
default:
return (call, nil)
}
}
}
|> distinctUntilChanged(isEqual: { lhs, rhs in
return lhs.0 === rhs.0 && lhs.1 === rhs.1
})
)
let groupCallSignal: Signal<PresentationGroupCall?, NoError> = .single(nil)
|> then(
callManager.currentGroupCallSignal
)
self.callStateDisposable = combineLatest(queue: .mainQueue(),
callSignal,
groupCallSignal,
self.hasGroupCallOnScreenPromise.get()
).start(next: { [weak self] call, groupCall, hasGroupCallOnScreen in
if let strongSelf = self {
var (call, conferenceCall) = call
var groupCall = groupCall
if let conferenceCall {
call = nil
groupCall = conferenceCall
}
let statusBarContent: CallStatusBarNodeImpl.Content?
if let call, !hasGroupCallOnScreen {
statusBarContent = .call(strongSelf, call.context.account, call)
} else if let groupCall = groupCall, !hasGroupCallOnScreen {
statusBarContent = .groupCall(strongSelf, groupCall.account, groupCall)
} else {
statusBarContent = nil
}
var resolvedCallStatusBarNode: CallStatusBarNodeImpl?
if let statusBarContent = statusBarContent {
if let current = strongSelf.currentCallStatusBarNode {
resolvedCallStatusBarNode = current
} else {
resolvedCallStatusBarNode = CallStatusBarNodeImpl()
strongSelf.currentCallStatusBarNode = resolvedCallStatusBarNode
}
resolvedCallStatusBarNode?.update(content: statusBarContent)
} else {
strongSelf.currentCallStatusBarNode = nil
}
if let navigationController = strongSelf.mainWindow?.viewController as? NavigationController {
navigationController.setForceInCallStatusBar(resolvedCallStatusBarNode)
}
self.hasOngoingCall.set(false)
}
}*/
})
mainWindow.inCallNavigate = { [weak self] in
guard let strongSelf = self else {
guard let self else {
return
}
if let callController = strongSelf.callController {
if callController.isNodeLoaded {
if let callController = self.callController {
mainWindow.hostView.containerView.endEditing(true)
if callController.view.superview == nil {
if useFlatModalCallsPresentation(context: callController.call.context) {
@ -1087,16 +1021,13 @@ public final class SharedAccountContextImpl: SharedAccountContext {
} else {
callController.expandFromPipIfPossible()
}
}
} else if let groupCallController = strongSelf.groupCallController {
if groupCallController.isNodeLoaded {
} else if let groupCallController = self.groupCallController {
mainWindow.hostView.containerView.endEditing(true)
if groupCallController.view.superview == nil {
(mainWindow.viewController as? NavigationController)?.pushViewController(groupCallController)
}
}
}
}
} else {
self.callManager = nil
}
@ -1236,8 +1167,271 @@ public final class SharedAccountContextImpl: SharedAccountContext {
}
}
private func updateCurrentCall(call: PresentationCurrentCall?) {
if self.currentCall == call {
return
}
if let currentCall = self.currentCall {
if case .call = currentCall {
self.callPeerDisposable?.dispose()
self.callPeerDisposable = nil
self.awaitingCallConnectionDisposable?.dispose()
self.awaitingCallConnectionDisposable = nil
self.notificationController?.setBlocking(nil)
}
self.callStateDisposable?.dispose()
self.callStateDisposable = nil
}
self.currentCall = call
let beginDisplayingCallStatusBar = Promise<Void>()
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
if callController.navigationPresentation != .flatModal {
callController.dismissWithoutAnimation()
}
self.callController = nil
} else {
self.hasGroupCallOnScreenPromise.set(.single(false))
}
if let callController = self.callController {
self.callController = nil
callController.dismiss()
}
if let groupCallController = self.groupCallController {
self.groupCallController = nil
groupCallController.dismiss()
}
if case let .call(call) = call {
let callController = CallController(sharedContext: self, account: call.context.account, call: call, easyDebugAccess: !GlobalExperimentalSettings.isAppStoreBuild)
self.callController = callController
let thisCallIsOnScreenPromise = ValuePromise<Bool>(false, ignoreRepeated: true)
callController.restoreUIForPictureInPicture = { [weak self, weak callController] completion in
guard let self, let callController else {
completion(false)
return
}
if callController.window == nil {
if useFlatModalCallsPresentation(context: callController.call.context) {
(self.mainWindow?.viewController as? NavigationController)?.pushViewController(callController)
} else {
self.mainWindow?.present(callController, on: .calls)
}
}
completion(true)
}
callController.onViewDidAppear = {
thisCallIsOnScreenPromise.set(true)
}
callController.onViewDidDisappear = {
thisCallIsOnScreenPromise.set(false)
}
if call.isOutgoing {
self.mainWindow?.hostView.containerView.endEditing(true)
thisCallIsOnScreenPromise.set(true)
self.hasGroupCallOnScreenPromise.set(thisCallIsOnScreenPromise.get())
if useFlatModalCallsPresentation(context: callController.call.context) {
(self.mainWindow?.viewController as? NavigationController)?.pushViewController(callController)
} else {
self.mainWindow?.present(callController, on: .calls)
}
} else {
self.hasGroupCallOnScreenPromise.set(thisCallIsOnScreenPromise.get())
if !call.isIntegratedWithCallKit {
self.callPeerDisposable = (call.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: call.peerId))
|> deliverOnMainQueue).startStrict(next: { [weak self, weak call] peer in
guard let self, let call, let peer else {
return
}
if self.currentCall != .call(call) {
return
}
let presentationData = self.currentPresentationData.with({ $0 })
self.notificationController?.setBlocking(ChatCallNotificationItem(
context: call.context,
strings: presentationData.strings,
nameDisplayOrder: presentationData.nameDisplayOrder,
peer: peer,
isVideo: call.isVideo,
action: { [weak call] answerAction in
guard let call else {
return
}
if answerAction {
self.notificationController?.setBlocking(nil)
call.answer()
} else {
self.notificationController?.setBlocking(nil)
call.rejectBusy()
}
}
))
})
}
}
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
switch state.state {
case .ringing:
return false
case .terminating, .terminated:
return false
default:
return true
}
}
|> take(1)
|> map { _ -> Void in
return Void()
})
}
if case let .group(groupCall) = call {
let _ = (makeVoiceChatControllerInitialData(sharedContext: self, accountContext: groupCall.accountContext, call: groupCall)
|> deliverOnMainQueue).start(next: { [weak self, weak transitioningToConferenceCallController] initialData in
guard let self else {
return
}
guard let navigationController = self.mainWindow?.viewController as? NavigationController else {
return
}
let thisCallIsOnScreenPromise = ValuePromise<Bool>(false, ignoreRepeated: true)
let groupCallController = makeVoiceChatController(sharedContext: self, accountContext: groupCall.accountContext, call: groupCall, initialData: initialData, sourceCallController: transitioningToConferenceCallController)
groupCallController.onViewDidAppear = {
thisCallIsOnScreenPromise.set(true)
}
groupCallController.onViewDidDisappear = {
thisCallIsOnScreenPromise.set(false)
}
groupCallController.navigationPresentation = .flatModal
groupCallController.parentNavigationController = navigationController
self.groupCallController = groupCallController
self.mainWindow?.hostView.containerView.endEditing(true)
thisCallIsOnScreenPromise.set(true)
self.hasGroupCallOnScreenPromise.set(thisCallIsOnScreenPromise.get())
beginDisplayingCallStatusBar.set(.single(Void()))
if let transitioningToConferenceCallController {
transitioningToConferenceCallController.onViewDidAppear = nil
transitioningToConferenceCallController.onViewDidDisappear = nil
}
if let transitioningToConferenceCallController {
var viewControllers = navigationController.viewControllers
if let index = viewControllers.firstIndex(where: { $0 === transitioningToConferenceCallController }) {
viewControllers.insert(groupCallController, at: index)
navigationController.setViewControllers(viewControllers, animated: false)
viewControllers.remove(at: index + 1)
navigationController.setViewControllers(viewControllers, animated: false)
} else {
navigationController.pushViewController(groupCallController)
}
} else {
navigationController.pushViewController(groupCallController)
}
})
}
if self.currentCall != nil {
self.callStateDisposable = (combineLatest(queue: .mainQueue(),
self.hasGroupCallOnScreenPromise.get(),
beginDisplayingCallStatusBar.get()
)
|> deliverOnMainQueue).startStrict(next: { [weak self] hasGroupCallOnScreen, _ in
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)
}
})
} else {
self.currentCallStatusBarNode = nil
if let navigationController = self.mainWindow?.viewController as? NavigationController {
navigationController.setForceInCallStatusBar(nil)
}
}
}
private func presentControllerWithCurrentCall() {
guard let call = self.call else {
/*guard let call = self.call else {
return
}
@ -1360,7 +1554,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
} else {
self.mainWindow?.present(callController, on: .calls)
}
}
}*/
}
public func updateNotificationTokensRegistration() {

View File

@ -142,8 +142,16 @@ public final class SharedWakeupManager {
|> distinctUntilChanged
let hasActiveGroupCalls = (callManager?.currentGroupCallSignal ?? .single(nil))
|> map { call in
return call?.accountContext.account.id == account.id
|> map { call -> Bool in
guard let call else {
return false
}
switch call {
case let .conferenceSource(conferenceSource):
return conferenceSource.context.account.id == account.id
case let .group(groupCall):
return groupCall.accountContext.account.id == account.id
}
}
|> distinctUntilChanged

View File

@ -1328,6 +1328,7 @@ public final class OngoingCallContext {
self?.audioLevelPromise.set(.single(level))
}
if audioDevice == nil {
strongSelf.audioSessionActiveDisposable.set((audioSessionActive
|> deliverOn(queue)).start(next: { isActive in
guard let strongSelf = self else {
@ -1337,6 +1338,7 @@ public final class OngoingCallContext {
context.nativeSetIsAudioSessionActive(isActive: isActive)
}
}))
}
strongSelf.networkTypeDisposable = (updatedNetworkType
|> deliverOn(queue)).start(next: { networkType in

View File

@ -18,7 +18,6 @@
#else
#import "platform/darwin/VideoMetalView.h"
#import "platform/darwin/GLVideoView.h"
#import "platform/darwin/VideoSampleBufferView.h"
#import "platform/darwin/VideoCaptureView.h"
#import "platform/darwin/CustomExternalCapturer.h"

View File

@ -71,58 +71,537 @@ public:
virtual ~SharedAudioDeviceModule() = default;
public:
virtual rtc::scoped_refptr<webrtc::tgcalls_ios_adm::AudioDeviceModuleIOS> audioDeviceModule() = 0;
virtual rtc::scoped_refptr<tgcalls::WrappedAudioDeviceModule> audioDeviceModule() = 0;
virtual rtc::scoped_refptr<tgcalls::WrappedAudioDeviceModule> makeChildAudioDeviceModule() = 0;
virtual void start() = 0;
};
}
class WrappedAudioDeviceModuleIOS : public tgcalls::DefaultWrappedAudioDeviceModule, public webrtc::AudioTransport {
public:
WrappedAudioDeviceModuleIOS(webrtc::scoped_refptr<webrtc::AudioDeviceModule> impl) :
tgcalls::DefaultWrappedAudioDeviceModule(impl) {
}
virtual ~WrappedAudioDeviceModuleIOS() {
ActualStop();
}
virtual int32_t ActiveAudioLayer(AudioLayer *audioLayer) const override {
return 0;
}
void UpdateAudioCallback(webrtc::AudioTransport *previousAudioCallback, webrtc::AudioTransport *audioCallback) {
if (audioCallback == nil) {
if (_currentAudioTransport == previousAudioCallback) {
_currentAudioTransport = nil;
}
} else {
_currentAudioTransport = audioCallback;
}
}
virtual int32_t RegisterAudioCallback(webrtc::AudioTransport *audioCallback) override {
return 0;
}
virtual int32_t Init() override {
return 0;
}
virtual int32_t Terminate() override {
return 0;
}
virtual bool Initialized() const override {
return true;
}
virtual int16_t PlayoutDevices() override {
return 0;
}
virtual int16_t RecordingDevices() override {
return 0;
}
virtual int32_t PlayoutDeviceName(uint16_t index, char name[webrtc::kAdmMaxDeviceNameSize], char guid[webrtc::kAdmMaxGuidSize]) override {
return -1;
}
virtual int32_t RecordingDeviceName(uint16_t index, char name[webrtc::kAdmMaxDeviceNameSize], char guid[webrtc::kAdmMaxGuidSize]) override {
return -1;
}
virtual int32_t SetPlayoutDevice(uint16_t index) override {
return 0;
}
virtual int32_t SetPlayoutDevice(WindowsDeviceType device) override {
return 0;
}
virtual int32_t SetRecordingDevice(uint16_t index) override {
return 0;
}
virtual int32_t SetRecordingDevice(WindowsDeviceType device) override {
return 0;
}
virtual int32_t PlayoutIsAvailable(bool *available) override {
return 0;
}
virtual int32_t InitPlayout() override {
return 0;
}
virtual bool PlayoutIsInitialized() const override {
return true;
}
virtual int32_t RecordingIsAvailable(bool *available) override {
if (available) {
*available = true;
}
return 0;
}
virtual int32_t InitRecording() override {
return 0;
}
virtual bool RecordingIsInitialized() const override {
return true;
}
virtual int32_t StartPlayout() override {
return 0;
}
virtual int32_t StopPlayout() override {
return 0;
}
virtual bool Playing() const override {
return true;
}
virtual int32_t StartRecording() override {
return 0;
}
virtual int32_t StopRecording() override {
return 0;
}
virtual bool Recording() const override {
return true;
}
virtual int32_t InitSpeaker() override {
return 0;
}
virtual bool SpeakerIsInitialized() const override {
return true;
}
virtual int32_t InitMicrophone() override {
return 0;
}
virtual bool MicrophoneIsInitialized() const override {
return true;
}
virtual int32_t SpeakerVolumeIsAvailable(bool *available) override {
if (available) {
*available = false;
}
return 0;
}
virtual int32_t SetSpeakerVolume(uint32_t volume) override {
return 0;
}
virtual int32_t SpeakerVolume(uint32_t* volume) const override {
if (volume) {
*volume = 0;
}
return 0;
}
virtual int32_t MaxSpeakerVolume(uint32_t *maxVolume) const override {
if (maxVolume) {
*maxVolume = 0;
}
return 0;
}
virtual int32_t MinSpeakerVolume(uint32_t *minVolume) const override {
if (minVolume) {
*minVolume = 0;
}
return 0;
}
virtual int32_t MicrophoneVolumeIsAvailable(bool *available) override {
if (available) {
*available = false;
}
return 0;
}
virtual int32_t SetMicrophoneVolume(uint32_t volume) override {
return 0;
}
virtual int32_t MicrophoneVolume(uint32_t *volume) const override {
if (volume) {
*volume = 0;
}
return 0;
}
virtual int32_t MaxMicrophoneVolume(uint32_t *maxVolume) const override {
if (maxVolume) {
*maxVolume = 0;
}
return 0;
}
virtual int32_t MinMicrophoneVolume(uint32_t *minVolume) const override {
if (minVolume) {
*minVolume = 0;
}
return 0;
}
virtual int32_t SpeakerMuteIsAvailable(bool *available) override {
if (available) {
*available = false;
}
return 0;
}
virtual int32_t SetSpeakerMute(bool enable) override {
return 0;
}
virtual int32_t SpeakerMute(bool *enabled) const override {
if (enabled) {
*enabled = false;
}
return 0;
}
virtual int32_t MicrophoneMuteIsAvailable(bool *available) override {
if (available) {
*available = false;
}
return 0;
}
virtual int32_t SetMicrophoneMute(bool enable) override {
return 0;
}
virtual int32_t MicrophoneMute(bool *enabled) const override {
if (enabled) {
*enabled = false;
}
return 0;
}
virtual int32_t StereoPlayoutIsAvailable(bool *available) const override {
if (available) {
*available = false;
}
return 0;
}
virtual int32_t SetStereoPlayout(bool enable) override {
return 0;
}
virtual int32_t StereoPlayout(bool *enabled) const override {
if (enabled) {
*enabled = false;
}
return 0;
}
virtual int32_t StereoRecordingIsAvailable(bool *available) const override {
if (available) {
*available = false;
}
return 0;
}
virtual int32_t SetStereoRecording(bool enable) override {
return 0;
}
virtual int32_t StereoRecording(bool *enabled) const override {
if (enabled) {
*enabled = false;
}
return 0;
}
virtual int32_t PlayoutDelay(uint16_t* delayMS) const override {
if (delayMS) {
*delayMS = 0;
}
return 0;
}
virtual bool BuiltInAECIsAvailable() const override {
return true;
}
virtual bool BuiltInAGCIsAvailable() const override {
return true;
}
virtual bool BuiltInNSIsAvailable() const override {
return true;
}
virtual int32_t EnableBuiltInAEC(bool enable) override {
return 0;
}
virtual int32_t EnableBuiltInAGC(bool enable) override {
return 0;
}
virtual int32_t EnableBuiltInNS(bool enable) override {
return 0;
}
virtual int32_t GetPlayoutUnderrunCount() const override {
return 0;
}
virtual int GetPlayoutAudioParameters(webrtc::AudioParameters *params) const override {
return WrappedInstance()->GetPlayoutAudioParameters(params);
}
virtual int GetRecordAudioParameters(webrtc::AudioParameters *params) const override {
return WrappedInstance()->GetRecordAudioParameters(params);
}
public:
virtual int32_t RecordedDataIsAvailable(
const void* audioSamples,
size_t nSamples,
size_t nBytesPerSample,
size_t nChannels,
uint32_t samplesPerSec,
uint32_t totalDelayMS,
int32_t clockDrift,
uint32_t currentMicLevel,
bool keyPressed,
uint32_t& newMicLevel
) override {
if (_currentAudioTransport) {
return _currentAudioTransport->RecordedDataIsAvailable(
audioSamples,
nSamples,
nBytesPerSample,
nChannels,
samplesPerSec,
totalDelayMS,
clockDrift,
currentMicLevel,
keyPressed,
newMicLevel
);
} else {
return 0;
}
}
virtual int32_t RecordedDataIsAvailable(
const void *audioSamples,
size_t nSamples,
size_t nBytesPerSample,
size_t nChannels,
uint32_t samplesPerSec,
uint32_t totalDelayMS,
int32_t clockDrift,
uint32_t currentMicLevel,
bool keyPressed,
uint32_t& newMicLevel,
absl::optional<int64_t> estimatedCaptureTimeNS
) override {
if (_currentAudioTransport) {
return _currentAudioTransport->RecordedDataIsAvailable(
audioSamples,
nSamples,
nBytesPerSample,
nChannels,
samplesPerSec,
totalDelayMS,
clockDrift,
currentMicLevel,
keyPressed,
newMicLevel,
estimatedCaptureTimeNS
);
} else {
return 0;
}
}
// Implementation has to setup safe values for all specified out parameters.
virtual int32_t NeedMorePlayData(
size_t nSamples,
size_t nBytesPerSample,
size_t nChannels,
uint32_t samplesPerSec,
void* audioSamples,
size_t& nSamplesOut,
int64_t* elapsed_time_ms,
int64_t* ntp_time_ms
) override {
if (_currentAudioTransport) {
return _currentAudioTransport->NeedMorePlayData(
nSamples,
nBytesPerSample,
nChannels,
samplesPerSec,
audioSamples,
nSamplesOut,
elapsed_time_ms,
ntp_time_ms
);
} else {
nSamplesOut = 0;
return 0;
}
}
virtual void PullRenderData(
int bits_per_sample,
int sample_rate,
size_t number_of_channels,
size_t number_of_frames,
void* audio_data,
int64_t* elapsed_time_ms,
int64_t* ntp_time_ms
) override {
if (_currentAudioTransport) {
_currentAudioTransport->PullRenderData(
bits_per_sample,
sample_rate,
number_of_channels,
number_of_frames,
audio_data,
elapsed_time_ms,
ntp_time_ms
);
}
}
public:
virtual void Start() {
if (!_isStarted) {
_isStarted = true;
WrappedInstance()->Init();
WrappedInstance()->RegisterAudioCallback(this);
if (!WrappedInstance()->Playing()) {
WrappedInstance()->InitPlayout();
WrappedInstance()->StartPlayout();
WrappedInstance()->InitRecording();
WrappedInstance()->StartRecording();
}
}
}
virtual void Stop() override {
}
virtual void ActualStop() {
if (_isStarted) {
_isStarted = false;
WrappedInstance()->StopPlayout();
WrappedInstance()->StopRecording();
WrappedInstance()->Terminate();
}
}
private:
bool _isStarted = false;
webrtc::AudioTransport *_currentAudioTransport = nullptr;
};
class WrappedChildAudioDeviceModule : public tgcalls::DefaultWrappedAudioDeviceModule {
public:
WrappedChildAudioDeviceModule(webrtc::scoped_refptr<WrappedAudioDeviceModuleIOS> impl) :
tgcalls::DefaultWrappedAudioDeviceModule(impl) {
}
virtual ~WrappedChildAudioDeviceModule() {
}
virtual int32_t RegisterAudioCallback(webrtc::AudioTransport *audioCallback) override {
auto previousAudioCallback = _audioCallback;
_audioCallback = audioCallback;
((WrappedAudioDeviceModuleIOS *)WrappedInstance().get())->UpdateAudioCallback(previousAudioCallback, audioCallback);
return 0;
}
private:
webrtc::AudioTransport *_audioCallback = nullptr;
};
class SharedAudioDeviceModuleImpl: public tgcalls::SharedAudioDeviceModule {
public:
SharedAudioDeviceModuleImpl(bool disableAudioInput, bool enableSystemMute) {
RTC_DCHECK(tgcalls::StaticThreads::getThreads()->getWorkerThread()->IsCurrent());
_audioDeviceModule = rtc::make_ref_counted<webrtc::tgcalls_ios_adm::AudioDeviceModuleIOS>(false, disableAudioInput, enableSystemMute, disableAudioInput ? 2 : 1);
auto sourceDeviceModule = rtc::make_ref_counted<webrtc::tgcalls_ios_adm::AudioDeviceModuleIOS>(false, disableAudioInput, enableSystemMute, disableAudioInput ? 2 : 1);
_audioDeviceModule = rtc::make_ref_counted<WrappedAudioDeviceModuleIOS>(sourceDeviceModule);
}
virtual ~SharedAudioDeviceModuleImpl() override {
if (tgcalls::StaticThreads::getThreads()->getWorkerThread()->IsCurrent()) {
if (_audioDeviceModule->Playing()) {
_audioDeviceModule->StopPlayout();
_audioDeviceModule->StopRecording();
}
_audioDeviceModule->ActualStop();
_audioDeviceModule = nullptr;
} else {
tgcalls::StaticThreads::getThreads()->getWorkerThread()->BlockingCall([&]() {
if (_audioDeviceModule->Playing()) {
_audioDeviceModule->StopPlayout();
_audioDeviceModule->StopRecording();
}
_audioDeviceModule->ActualStop();
_audioDeviceModule = nullptr;
});
}
}
public:
virtual rtc::scoped_refptr<webrtc::tgcalls_ios_adm::AudioDeviceModuleIOS> audioDeviceModule() override {
virtual rtc::scoped_refptr<tgcalls::WrappedAudioDeviceModule> audioDeviceModule() override {
return _audioDeviceModule;
}
rtc::scoped_refptr<tgcalls::WrappedAudioDeviceModule> makeChildAudioDeviceModule() override {
return rtc::make_ref_counted<WrappedChildAudioDeviceModule>(_audioDeviceModule);
}
virtual void start() override {
RTC_DCHECK(tgcalls::StaticThreads::getThreads()->getWorkerThread()->IsCurrent());
_audioDeviceModule->Init();
if (!_audioDeviceModule->Playing()) {
_audioDeviceModule->InitPlayout();
//_audioDeviceModule->InitRecording();
if (_audioDeviceModule->PlayoutIsInitialized()) {
_audioDeviceModule->InternalStartPlayout();
}
//_audioDeviceModule->InternalStartRecording();
}
_audioDeviceModule->Start();
}
private:
rtc::scoped_refptr<webrtc::tgcalls_ios_adm::AudioDeviceModuleIOS> _audioDeviceModule;
rtc::scoped_refptr<WrappedAudioDeviceModuleIOS> _audioDeviceModule;
};
@implementation SharedCallAudioDevice {
@ -145,7 +624,8 @@ private:
- (void)setTone:(CallAudioTone * _Nullable)tone {
_audioDeviceModule->perform([tone](tgcalls::SharedAudioDeviceModule *audioDeviceModule) {
audioDeviceModule->audioDeviceModule()->setTone([tone asTone]);
//audioDeviceModule->audioDeviceModule()->setTone([tone asTone]);
//TODO:implement
});
}
@ -1303,6 +1783,13 @@ static void (*InternalVoipLoggingFunction)(NSString *) = NULL;
return resultModule;
}
},
.createWrappedAudioDeviceModule = [audioDeviceModule](webrtc::TaskQueueFactory *taskQueueFactory) -> rtc::scoped_refptr<tgcalls::WrappedAudioDeviceModule> {
if (audioDeviceModule) {
return audioDeviceModule->getSyncAssumingSameThread()->makeChildAudioDeviceModule();
} else {
return nullptr;
}
},
.directConnectionChannel = directConnectionChannel
});
_state = OngoingCallStateInitializing;
@ -2000,6 +2487,13 @@ isConference:(bool)isConference {
return resultModule;
}
},
.createWrappedAudioDeviceModule = [audioDeviceModule](webrtc::TaskQueueFactory *taskQueueFactory) -> rtc::scoped_refptr<tgcalls::WrappedAudioDeviceModule> {
if (audioDeviceModule) {
return audioDeviceModule->getSyncAssumingSameThread()->makeChildAudioDeviceModule();
} else {
return nullptr;
}
},
.onMutedSpeechActivityDetected = [weakSelf, queue](bool value) {
[queue dispatch:^{
__strong GroupCallThreadLocalContext *strongSelf = weakSelf;

@ -1 +1 @@
Subproject commit 421d23ead1cf8595fb72087a01d7b1452ffd2723
Subproject commit b07cb07fb9bcb97f745e74c27f8751e8bef1dfb3