[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 { public protocol PresentationCallManager: AnyObject {
var currentCallSignal: Signal<PresentationCall?, NoError> { get } var currentCallSignal: Signal<PresentationCall?, NoError> { get }
var currentGroupCallSignal: Signal<PresentationGroupCall?, NoError> { get } var currentGroupCallSignal: Signal<VideoChatCall?, NoError> { get }
var hasActiveCall: Bool { get } var hasActiveCall: Bool { get }
var hasActiveGroupCall: Bool { get } var hasActiveGroupCall: Bool { get }

View File

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

View File

@ -77,12 +77,34 @@ private final class GlobalOverlayContainerParent: ASDisplayNode {
} }
private final class NavigationControllerNode: 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? private weak var controller: NavigationController?
init(controller: NavigationController) { init(controller: NavigationController) {
self.controller = controller self.controller = controller
super.init() super.init()
self.setViewBlock({
return View(frame: CGRect())
})
} }
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
@ -100,6 +122,10 @@ private final class NavigationControllerNode: ASDisplayNode {
} }
return false return false
} }
func schedule(layout f: @escaping () -> Void) {
(self.view as? View)?.schedule(layout: f)
}
} }
public protocol NavigationControllerDropContentItem: AnyObject { public protocol NavigationControllerDropContentItem: AnyObject {
@ -376,6 +402,9 @@ open class NavigationController: UINavigationController, ContainableController,
self.loadView() self.loadView()
} }
self.validLayout = layout self.validLayout = layout
self.scheduledLayoutTransitionRequest = nil
self.updateContainers(layout: layout, transition: transition) self.updateContainers(layout: layout, transition: transition)
} }
@ -1805,18 +1834,11 @@ open class NavigationController: UINavigationController, ContainableController,
return nil return nil
} }
private func scheduleAfterLayout(_ f: @escaping () -> Void) {
(self.view as? UITracingLayerView)?.schedule(layout: {
f()
})
self.view.setNeedsLayout()
}
private func scheduleLayoutTransitionRequest(_ transition: ContainedViewLayoutTransition) { private func scheduleLayoutTransitionRequest(_ transition: ContainedViewLayoutTransition) {
let requestId = self.scheduledLayoutTransitionRequestId let requestId = self.scheduledLayoutTransitionRequestId
self.scheduledLayoutTransitionRequestId += 1 self.scheduledLayoutTransitionRequestId += 1
self.scheduledLayoutTransitionRequest = (requestId, transition) 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 strongSelf = self {
if let (currentRequestId, currentRequestTransition) = strongSelf.scheduledLayoutTransitionRequest, currentRequestId == requestId { if let (currentRequestId, currentRequestTransition) = strongSelf.scheduledLayoutTransitionRequest, currentRequestId == requestId {
strongSelf.scheduledLayoutTransitionRequest = nil strongSelf.scheduledLayoutTransitionRequest = nil
@ -1869,7 +1891,8 @@ open class NavigationController: UINavigationController, ContainableController,
} }
if let layout = self.validLayout { if let layout = self.validLayout {
inCallStatusBar.updateState(statusBar: nil, withSafeInsets: !layout.safeInsets.top.isZero, inCallNode: forceInCallStatusBar, animated: false) 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 { } else {
self.updateInCallStatusBarState = forceInCallStatusBar self.updateInCallStatusBarState = forceInCallStatusBar
} }
@ -1879,7 +1902,9 @@ open class NavigationController: UINavigationController, ContainableController,
inCallStatusBar?.removeFromSupernode() inCallStatusBar?.removeFromSupernode()
}) })
if let layout = self.validLayout { 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): case let .peer(peerId):
let currentGroupCall: Signal<PresentationGroupCall?, NoError> = callManager.currentGroupCallSignal let currentGroupCall: Signal<PresentationGroupCall?, NoError> = callManager.currentGroupCallSignal
|> distinctUntilChanged(isEqual: { lhs, rhs in |> distinctUntilChanged(isEqual: { lhs, rhs in
return lhs?.internalId == rhs?.internalId return lhs == rhs
}) })
|> map { call -> PresentationGroupCall? in |> 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 nil
} }
return call return call

View File

@ -62,6 +62,15 @@ final class SharedCallAudioContext {
audioSessionControl.setOutputMode(.custom(self.currentAudioOutputValue)) audioSessionControl.setOutputMode(.custom(self.currentAudioOutputValue))
audioSessionControl.setup(synchronous: true) 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 }, deactivate: { [weak self] _ in
@ -130,7 +139,7 @@ final class SharedCallAudioContext {
guard let self else { guard let self else {
return return
} }
let _ = self self.audioDevice?.setIsAudioSessionActive(value)
}) })
} }

View File

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

View File

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

View File

@ -24,16 +24,7 @@ import TelegramAudio
import LegacyComponents import LegacyComponents
import TooltipUI import TooltipUI
extension VideoChatCall { 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> { var myAudioLevelAndSpeaking: Signal<(Float, Bool), NoError> {
switch self { switch self {
case let .group(group): case let .group(group):

View File

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

View File

@ -93,7 +93,7 @@ final class KeyEmojiView: HighlightTrackingButton {
for i in 0 ..< self.emojiViews.count { for i in 0 ..< self.emojiViews.count {
let emojiView = self.emojiViews[i] let emojiView = self.emojiViews[i]
emojiView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3) 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 { private func update(params: Params, transition: ComponentTransition) -> CGSize {
let itemSpacing: CGFloat = 1.0 let itemSpacing: CGFloat = 0.0
var height: CGFloat = 0.0 var height: CGFloat = 0.0
var nextX = 0.0 var nextX = 0.0
@ -118,7 +118,7 @@ final class KeyEmojiView: HighlightTrackingButton {
nextX += itemSpacing nextX += itemSpacing
} }
let emojiView = self.emojiViews[i] 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 { if height == 0.0 {
height = itemSize.height height = itemSize.height
} }
@ -144,7 +144,7 @@ func generateParabollicMotionKeyframes(from sourcePoint: CGPoint, to targetPosit
let numPoints: Int = Int(ceil(Double(UIScreen.main.maximumFramesPerSecond) * duration)) let numPoints: Int = Int(ceil(Double(UIScreen.main.maximumFramesPerSecond) * duration))
var keyframes: [CGPoint] = [] 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 { for rawI in 0 ..< numPoints {
let i = reverse ? (numPoints - 1 - rawI) : rawI let i = reverse ? (numPoints - 1 - rawI) : rawI
let ks = CGFloat(i) / CGFloat(numPoints - 1) let ks = CGFloat(i) / CGFloat(numPoints - 1)

View File

@ -1241,8 +1241,8 @@ public final class PrivateCallScreen: OverlayMaskContainerView, AVPictureInPictu
let titleSize = self.titleView.update( let titleSize = self.titleView.update(
string: titleString, string: titleString,
fontSize: !havePrimaryVideo ? 28.0 : 17.0, fontSize: 28.0,
fontWeight: !havePrimaryVideo ? 0.0 : 0.25, fontWeight: 0.0,
color: .white, color: .white,
constrainedWidth: params.size.width - 16.0 * 2.0, constrainedWidth: params.size.width - 16.0 * 2.0,
transition: transition transition: transition
@ -1301,7 +1301,11 @@ public final class PrivateCallScreen: OverlayMaskContainerView, AVPictureInPictu
if currentAreControlsHidden { if currentAreControlsHidden {
titleY = -8.0 - titleSize.height - statusSize.height titleY = -8.0 - titleSize.height - statusSize.height
} else if havePrimaryVideo { } else if havePrimaryVideo {
titleY = params.insets.top + 2.0 if case .active = params.state.lifecycleState {
titleY = params.insets.top + 2.0 + 54.0
} else {
titleY = params.insets.top + 2.0
}
} else { } else {
titleY = collapsedAvatarFrame.maxY + 39.0 titleY = collapsedAvatarFrame.maxY + 39.0
} }
@ -1313,7 +1317,7 @@ public final class PrivateCallScreen: OverlayMaskContainerView, AVPictureInPictu
size: titleSize size: titleSize
) )
transition.setFrame(view: self.titleView, frame: titleFrame) 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 emojiViewSizeValue: CGSize?
var emojiTransition = transition 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( let statusFrame = CGRect(
origin: CGPoint( origin: CGPoint(
x: (params.size.width - statusSize.width - statusEmojiWidth) * 0.5, x: (params.size.width - statusSize.width) * 0.5,
y: titleFrame.maxY + (havePrimaryVideo ? 0.0 : 4.0) y: titleFrame.maxY + 4.0
), ),
size: statusSize size: statusSize
) )
@ -1414,12 +1413,19 @@ public final class PrivateCallScreen: OverlayMaskContainerView, AVPictureInPictu
} }
} else { } else {
transition.setFrame(view: self.statusView, frame: statusFrame) 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 case .active = params.state.lifecycleState {
if let emojiView = self.emojiView, !self.isEmojiKeyExpanded, let emojiViewSize = emojiViewSizeValue { 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 { if case let .curve(duration, curve) = transition.animation, emojiViewWasExpanded {
let distance = CGPoint(x: emojiViewFrame.midX - emojiView.center.x, y: emojiViewFrame.midY - emojiView.center.y) 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? public var callManager: PresentationCallManager?
let hasInAppPurchases: Bool let hasInAppPurchases: Bool
private var callDisposable: Disposable?
private var callStateDisposable: Disposable? private var callStateDisposable: Disposable?
private(set) var currentCallStatusBarNode: CallStatusBarNodeImpl? private(set) var currentCallStatusBarNode: CallStatusBarNodeImpl?
private var callDisposable: Disposable?
private var groupCallDisposable: Disposable? private var groupCallDisposable: Disposable?
private var callController: CallController? private var callController: CallController?
private var call: PresentationCall?
private var currentCall: PresentationCurrentCall?
public let hasOngoingCall = ValuePromise<Bool>(false) public let hasOngoingCall = ValuePromise<Bool>(false)
private let callState = Promise<PresentationCallState?>(nil)
private var awaitingCallConnectionDisposable: Disposable? private var awaitingCallConnectionDisposable: Disposable?
private var callPeerDisposable: Disposable? private var callPeerDisposable: Disposable?
private var callIsConferenceDisposable: Disposable? private var callIsConferenceDisposable: Disposable?
@ -182,7 +183,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
public var currentGroupCallController: ViewController? { public var currentGroupCallController: ViewController? {
return self.groupCallController return self.groupCallController
} }
private let hasGroupCallOnScreenPromise = ValuePromise<Bool>(false, ignoreRepeated: true) private let hasGroupCallOnScreenPromise = Promise<Bool>(false)
public var hasGroupCallOnScreen: Signal<Bool, NoError> { public var hasGroupCallOnScreen: Signal<Bool, NoError> {
return self.hasGroupCallOnScreenPromise.get() return self.hasGroupCallOnScreenPromise.get()
} }
@ -805,8 +806,14 @@ public final class SharedAccountContextImpl: SharedAccountContext {
guard let self else { guard let self else {
return 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 let previousCall = self.call
self.call = call self.call = call
@ -842,11 +849,6 @@ public final class SharedAccountContextImpl: SharedAccountContext {
self.callIsConferenceDisposable = nil self.callIsConferenceDisposable = nil
if let call { if let call {
if call.conferenceStateValue == nil && call.conferenceCall == nil {
self.callState.set(call.state
|> map(Optional.init))
}
self.hasOngoingCall.set(true) self.hasOngoingCall.set(true)
setNotificationCall(call) setNotificationCall(call)
@ -862,14 +864,12 @@ public final class SharedAccountContextImpl: SharedAccountContext {
} }
guard let callController = self.callController, callController.call === call else { guard let callController = self.callController, callController.call === call else {
if self.callController == nil, call.conferenceStateValue != nil { if self.callController == nil, call.conferenceStateValue != nil {
self.callState.set(.single(nil))
self.presentControllerWithCurrentCall() self.presentControllerWithCurrentCall()
self.notificationController?.setBlocking(nil) self.notificationController?.setBlocking(nil)
} }
return return
} }
if call.conferenceStateValue != nil { if call.conferenceStateValue != nil {
self.callState.set(.single(nil))
self.presentControllerWithCurrentCall() self.presentControllerWithCurrentCall()
} }
}) })
@ -932,168 +932,99 @@ public final class SharedAccountContextImpl: SharedAccountContext {
self.awaitingCallConnectionDisposable = nil self.awaitingCallConnectionDisposable = nil
setNotificationCall(nil) setNotificationCall(nil)
} }
} }*/
}) })
self.groupCallDisposable = (callManager.currentGroupCallSignal self.groupCallDisposable = (callManager.currentGroupCallSignal
|> deliverOnMainQueue).start(next: { [weak self] call in |> deliverOnMainQueue).start(next: { [weak self] call in
if let strongSelf = self { guard let self else {
if call.flatMap(VideoChatCall.group) != strongSelf.groupCallController?.call { return
strongSelf.groupCallController?.dismiss(closing: true, manual: false) }
strongSelf.groupCallController = nil
strongSelf.hasOngoingCall.set(false) 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 let call = call, let navigationController = mainWindow.viewController as? NavigationController { if call.isStream {
mainWindow.hostView.containerView.endEditing(true) self.hasGroupCallOnScreenPromise.set(true)
let groupCallController = MediaStreamComponentController(call: call)
groupCallController.onViewDidAppear = { [weak self] in
if let self {
self.hasGroupCallOnScreenPromise.set(true)
}
}
groupCallController.onViewDidDisappear = { [weak self] in
if let self {
self.hasGroupCallOnScreenPromise.set(false)
}
}
groupCallController.navigationPresentation = .flatModal
groupCallController.parentNavigationController = navigationController
self.groupCallController = groupCallController
navigationController.pushViewController(groupCallController)
} else {
self.hasGroupCallOnScreenPromise.set(true)
if call.isStream { let _ = (makeVoiceChatControllerInitialData(sharedContext: self, accountContext: call.accountContext, call: .group(call))
strongSelf.hasGroupCallOnScreenPromise.set(true) |> deliverOnMainQueue).start(next: { [weak self, weak navigationController] initialData in
let groupCallController = MediaStreamComponentController(call: call) guard let self, let navigationController else {
return
}
let groupCallController = makeVoiceChatController(sharedContext: self, accountContext: call.accountContext, call: .group(call), initialData: initialData, sourceCallController: nil)
groupCallController.onViewDidAppear = { [weak self] in groupCallController.onViewDidAppear = { [weak self] in
if let strongSelf = self { if let self {
strongSelf.hasGroupCallOnScreenPromise.set(true) self.hasGroupCallOnScreenPromise.set(true)
} }
} }
groupCallController.onViewDidDisappear = { [weak self] in groupCallController.onViewDidDisappear = { [weak self] in
if let strongSelf = self { if let self {
strongSelf.hasGroupCallOnScreenPromise.set(false) self.hasGroupCallOnScreenPromise.set(false)
} }
} }
groupCallController.navigationPresentation = .flatModal groupCallController.navigationPresentation = .flatModal
groupCallController.parentNavigationController = navigationController groupCallController.parentNavigationController = navigationController
strongSelf.groupCallController = groupCallController strongSelf.groupCallController = groupCallController
navigationController.pushViewController(groupCallController) navigationController.pushViewController(groupCallController)
} else { })
strongSelf.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 {
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)
}
}
groupCallController.onViewDidDisappear = { [weak strongSelf] in
if let strongSelf {
strongSelf.hasGroupCallOnScreenPromise.set(false)
}
}
groupCallController.navigationPresentation = .flatModal
groupCallController.parentNavigationController = navigationController
strongSelf.groupCallController = groupCallController
navigationController.pushViewController(groupCallController)
})
}
strongSelf.hasOngoingCall.set(true)
} else {
strongSelf.hasOngoingCall.set(false)
} }
}
} self.hasOngoingCall.set(true)
})
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 { } else {
statusBarContent = nil self.hasOngoingCall.set(false)
} }
}*/
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)
}
}
}) })
mainWindow.inCallNavigate = { [weak self] in mainWindow.inCallNavigate = { [weak self] in
guard let strongSelf = self else { guard let self else {
return return
} }
if let callController = strongSelf.callController { if let callController = self.callController {
if callController.isNodeLoaded { mainWindow.hostView.containerView.endEditing(true)
mainWindow.hostView.containerView.endEditing(true) if callController.view.superview == nil {
if callController.view.superview == nil { if useFlatModalCallsPresentation(context: callController.call.context) {
if useFlatModalCallsPresentation(context: callController.call.context) { (mainWindow.viewController as? NavigationController)?.pushViewController(callController)
(mainWindow.viewController as? NavigationController)?.pushViewController(callController)
} else {
mainWindow.present(callController, on: .calls)
}
} else { } else {
callController.expandFromPipIfPossible() mainWindow.present(callController, on: .calls)
} }
} else {
callController.expandFromPipIfPossible()
} }
} else if let groupCallController = strongSelf.groupCallController { } else if let groupCallController = self.groupCallController {
if groupCallController.isNodeLoaded { mainWindow.hostView.containerView.endEditing(true)
mainWindow.hostView.containerView.endEditing(true) if groupCallController.view.superview == nil {
if groupCallController.view.superview == nil { (mainWindow.viewController as? NavigationController)?.pushViewController(groupCallController)
(mainWindow.viewController as? NavigationController)?.pushViewController(groupCallController)
}
} }
} }
} }
@ -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() { private func presentControllerWithCurrentCall() {
guard let call = self.call else { /*guard let call = self.call else {
return return
} }
@ -1360,7 +1554,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
} else { } else {
self.mainWindow?.present(callController, on: .calls) self.mainWindow?.present(callController, on: .calls)
} }
} }*/
} }
public func updateNotificationTokensRegistration() { public func updateNotificationTokensRegistration() {

View File

@ -142,8 +142,16 @@ public final class SharedWakeupManager {
|> distinctUntilChanged |> distinctUntilChanged
let hasActiveGroupCalls = (callManager?.currentGroupCallSignal ?? .single(nil)) let hasActiveGroupCalls = (callManager?.currentGroupCallSignal ?? .single(nil))
|> map { call in |> map { call -> Bool in
return call?.accountContext.account.id == account.id 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 |> distinctUntilChanged

View File

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

View File

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

View File

@ -71,58 +71,537 @@ public:
virtual ~SharedAudioDeviceModule() = default; virtual ~SharedAudioDeviceModule() = default;
public: 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; 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 { class SharedAudioDeviceModuleImpl: public tgcalls::SharedAudioDeviceModule {
public: public:
SharedAudioDeviceModuleImpl(bool disableAudioInput, bool enableSystemMute) { SharedAudioDeviceModuleImpl(bool disableAudioInput, bool enableSystemMute) {
RTC_DCHECK(tgcalls::StaticThreads::getThreads()->getWorkerThread()->IsCurrent()); 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 { virtual ~SharedAudioDeviceModuleImpl() override {
if (tgcalls::StaticThreads::getThreads()->getWorkerThread()->IsCurrent()) { if (tgcalls::StaticThreads::getThreads()->getWorkerThread()->IsCurrent()) {
if (_audioDeviceModule->Playing()) { _audioDeviceModule->ActualStop();
_audioDeviceModule->StopPlayout();
_audioDeviceModule->StopRecording();
}
_audioDeviceModule = nullptr; _audioDeviceModule = nullptr;
} else { } else {
tgcalls::StaticThreads::getThreads()->getWorkerThread()->BlockingCall([&]() { tgcalls::StaticThreads::getThreads()->getWorkerThread()->BlockingCall([&]() {
if (_audioDeviceModule->Playing()) { _audioDeviceModule->ActualStop();
_audioDeviceModule->StopPlayout();
_audioDeviceModule->StopRecording();
}
_audioDeviceModule = nullptr; _audioDeviceModule = nullptr;
}); });
} }
} }
public: public:
virtual rtc::scoped_refptr<webrtc::tgcalls_ios_adm::AudioDeviceModuleIOS> audioDeviceModule() override { virtual rtc::scoped_refptr<tgcalls::WrappedAudioDeviceModule> audioDeviceModule() override {
return _audioDeviceModule; return _audioDeviceModule;
} }
rtc::scoped_refptr<tgcalls::WrappedAudioDeviceModule> makeChildAudioDeviceModule() override {
return rtc::make_ref_counted<WrappedChildAudioDeviceModule>(_audioDeviceModule);
}
virtual void start() override { virtual void start() override {
RTC_DCHECK(tgcalls::StaticThreads::getThreads()->getWorkerThread()->IsCurrent()); RTC_DCHECK(tgcalls::StaticThreads::getThreads()->getWorkerThread()->IsCurrent());
_audioDeviceModule->Init(); _audioDeviceModule->Start();
if (!_audioDeviceModule->Playing()) {
_audioDeviceModule->InitPlayout();
//_audioDeviceModule->InitRecording();
if (_audioDeviceModule->PlayoutIsInitialized()) {
_audioDeviceModule->InternalStartPlayout();
}
//_audioDeviceModule->InternalStartRecording();
}
} }
private: private:
rtc::scoped_refptr<webrtc::tgcalls_ios_adm::AudioDeviceModuleIOS> _audioDeviceModule; rtc::scoped_refptr<WrappedAudioDeviceModuleIOS> _audioDeviceModule;
}; };
@implementation SharedCallAudioDevice { @implementation SharedCallAudioDevice {
@ -145,7 +624,8 @@ private:
- (void)setTone:(CallAudioTone * _Nullable)tone { - (void)setTone:(CallAudioTone * _Nullable)tone {
_audioDeviceModule->perform([tone](tgcalls::SharedAudioDeviceModule *audioDeviceModule) { _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; return resultModule;
} }
}, },
.createWrappedAudioDeviceModule = [audioDeviceModule](webrtc::TaskQueueFactory *taskQueueFactory) -> rtc::scoped_refptr<tgcalls::WrappedAudioDeviceModule> {
if (audioDeviceModule) {
return audioDeviceModule->getSyncAssumingSameThread()->makeChildAudioDeviceModule();
} else {
return nullptr;
}
},
.directConnectionChannel = directConnectionChannel .directConnectionChannel = directConnectionChannel
}); });
_state = OngoingCallStateInitializing; _state = OngoingCallStateInitializing;
@ -2000,6 +2487,13 @@ isConference:(bool)isConference {
return resultModule; 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) { .onMutedSpeechActivityDetected = [weakSelf, queue](bool value) {
[queue dispatch:^{ [queue dispatch:^{
__strong GroupCallThreadLocalContext *strongSelf = weakSelf; __strong GroupCallThreadLocalContext *strongSelf = weakSelf;

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