mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-10-09 03:20:48 +00:00
Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios
This commit is contained in:
commit
2dfc7e2b96
@ -1 +1 @@
|
||||
3a64b94cc76109006741756f85403c87
|
||||
72ef725b9fffe38df9f94dfee99bb99b
|
||||
|
@ -414,6 +414,7 @@ public protocol PresentationGroupCall: AnyObject {
|
||||
|
||||
var isStream: Bool { get }
|
||||
var isConference: Bool { get }
|
||||
var conferenceSource: CallSessionInternalId? { get }
|
||||
var encryptionKeyValue: Data? { get }
|
||||
|
||||
var audioOutputState: Signal<([AudioSessionOutput], AudioSessionOutput?), NoError> { get }
|
||||
@ -498,9 +499,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 }
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -236,7 +236,7 @@ open class ViewControllerComponentContainer: ViewController {
|
||||
|
||||
private let context: AccountContext
|
||||
private var theme: Theme
|
||||
private let component: AnyComponent<ViewControllerComponentContainer.Environment>
|
||||
public private(set) var component: AnyComponent<ViewControllerComponentContainer.Environment>
|
||||
|
||||
private var presentationDataDisposable: Disposable?
|
||||
public private(set) var validLayout: ContainerViewLayout?
|
||||
@ -387,6 +387,7 @@ open class ViewControllerComponentContainer: ViewController {
|
||||
}
|
||||
|
||||
public func updateComponent(component: AnyComponent<ViewControllerComponentContainer.Environment>, transition: ComponentTransition) {
|
||||
self.component = component
|
||||
self.node.updateComponent(component: component, transition: transition)
|
||||
}
|
||||
}
|
||||
|
@ -1372,7 +1372,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
||||
}).start()
|
||||
})
|
||||
case let .devRequests(value):
|
||||
return ItemListSwitchItem(presentationData: presentationData, title: "PlayerV2", value: value, sectionId: self.section, style: .blocks, updated: { value in
|
||||
return ItemListSwitchItem(presentationData: presentationData, title: "DevRequests", value: value, sectionId: self.section, style: .blocks, updated: { value in
|
||||
let _ = arguments.sharedContext.accountManager.transaction ({ transaction in
|
||||
transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in
|
||||
var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
@ -782,7 +811,11 @@ open class NavigationController: UINavigationController, ContainableController,
|
||||
modalContainer.update(layout: modalContainer.isFlat ? globalOverlayLayout : layout, controllers: navigationLayout.modal[i].controllers, coveredByModalTransition: effectiveModalTransition, transition: containerTransition)
|
||||
|
||||
if modalContainer.supernode == nil && modalContainer.isReady {
|
||||
if let previousModalContainer = previousModalContainer {
|
||||
if let previousModalContainer {
|
||||
assert(previousModalContainer.supernode != nil)
|
||||
}
|
||||
|
||||
if let previousModalContainer, previousModalContainer.supernode != nil {
|
||||
self.displayNode.insertSubnode(modalContainer, belowSubnode: previousModalContainer)
|
||||
} else if let inCallStatusBar = self.inCallStatusBar {
|
||||
self.displayNode.insertSubnode(modalContainer, belowSubnode: inCallStatusBar)
|
||||
@ -1572,6 +1605,16 @@ open class NavigationController: UINavigationController, ContainableController,
|
||||
}
|
||||
|
||||
public func setViewControllers(_ viewControllers: [UIViewController], animated: Bool, completion: @escaping () -> Void) {
|
||||
let requestedViewControllers = viewControllers
|
||||
var viewControllers: [UIViewController] = []
|
||||
for controller in requestedViewControllers {
|
||||
if !viewControllers.contains(where: { $0 === controller }) {
|
||||
viewControllers.append(controller)
|
||||
} else {
|
||||
assert(true)
|
||||
}
|
||||
}
|
||||
|
||||
for i in 0 ..< viewControllers.count {
|
||||
guard let controller = viewControllers[i] as? ViewController else {
|
||||
continue
|
||||
@ -1805,18 +1848,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 +1905,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 +1916,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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -94,6 +94,10 @@ public final class CallKitIntegration {
|
||||
public func applyVoiceChatOutputMode(outputMode: AudioSessionOutputMode) {
|
||||
sharedProviderDelegate?.applyVoiceChatOutputMode(outputMode: outputMode)
|
||||
}
|
||||
|
||||
public func updateCallIsConference(uuid: UUID) {
|
||||
sharedProviderDelegate?.updateCallIsConference(uuid: uuid)
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOSApplicationExtension 10.0, iOS 10.0, *)
|
||||
@ -276,6 +280,20 @@ class CallKitProviderDelegate: NSObject, CXProviderDelegate {
|
||||
self.provider.reportOutgoingCall(with: uuid, connectedAt: date)
|
||||
}
|
||||
|
||||
func updateCallIsConference(uuid: UUID) {
|
||||
let update = CXCallUpdate()
|
||||
let handle = CXHandle(type: .generic, value: "\(uuid)")
|
||||
update.remoteHandle = handle
|
||||
//TODO:localize
|
||||
update.localizedCallerName = "Group Call"
|
||||
update.supportsHolding = false
|
||||
update.supportsGrouping = false
|
||||
update.supportsUngrouping = false
|
||||
update.supportsDTMF = false
|
||||
|
||||
self.provider.reportCall(with: uuid, updated: update)
|
||||
}
|
||||
|
||||
func providerDidReset(_ provider: CXProvider) {
|
||||
Logger.shared.log("CallKitIntegration", "providerDidReset")
|
||||
|
||||
|
@ -1038,6 +1038,9 @@ public final class MediaStreamComponentController: ViewControllerComponentContai
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
public func updateCall(call: VideoChatCall) {
|
||||
}
|
||||
|
||||
override public func viewDidAppear(_ animated: Bool) {
|
||||
super.viewDidAppear(animated)
|
||||
|
||||
|
@ -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)
|
||||
})
|
||||
}
|
||||
|
||||
@ -316,6 +325,8 @@ public final class PresentationCallImpl: PresentationCall {
|
||||
private var localVideoEndpointId: String?
|
||||
private var remoteVideoEndpointId: String?
|
||||
|
||||
private var isMovedToConference: Bool = false
|
||||
|
||||
init(
|
||||
context: AccountContext,
|
||||
audioSession: ManagedAudioSession,
|
||||
@ -532,6 +543,89 @@ public final class PresentationCallImpl: PresentationCall {
|
||||
}
|
||||
}
|
||||
|
||||
public func resetAsMovedToConference() {
|
||||
if self.isMovedToConference {
|
||||
return
|
||||
}
|
||||
self.isMovedToConference = true
|
||||
|
||||
self.sharedAudioContext = nil
|
||||
self.sessionState = nil
|
||||
self.callContextState = nil
|
||||
self.ongoingContext = nil
|
||||
self.ongoingContextStateDisposable?.dispose()
|
||||
self.ongoingContextStateDisposable = nil
|
||||
self.ongoingContextIsFailedDisposable?.dispose()
|
||||
self.ongoingContextIsFailedDisposable = nil
|
||||
self.ongoingContextIsDroppedDisposable?.dispose()
|
||||
self.ongoingContextIsDroppedDisposable = nil
|
||||
self.didDropCall = false
|
||||
self.requestedVideoAspect = nil
|
||||
self.reception = nil
|
||||
self.receptionDisposable?.dispose()
|
||||
self.receptionDisposable = nil
|
||||
self.audioLevelDisposable?.dispose()
|
||||
self.audioLevelDisposable = nil
|
||||
self.reportedIncomingCall = false
|
||||
self.batteryLevelDisposable?.dispose()
|
||||
self.batteryLevelDisposable = nil
|
||||
self.callWasActive = false
|
||||
self.shouldPresentCallRating = false
|
||||
self.previousVideoState = nil
|
||||
self.previousRemoteVideoState = nil
|
||||
self.previousRemoteAudioState = nil
|
||||
self.previousRemoteBatteryLevel = nil
|
||||
self.sessionStateDisposable?.dispose()
|
||||
self.sessionStateDisposable = nil
|
||||
self.activeTimestamp = nil
|
||||
self.audioSessionControl = nil
|
||||
self.audioSessionDisposable?.dispose()
|
||||
self.audioSessionDisposable = nil
|
||||
self.audioSessionShouldBeActiveDisposable?.dispose()
|
||||
self.audioSessionShouldBeActiveDisposable = nil
|
||||
self.audioSessionActiveDisposable?.dispose()
|
||||
self.audioSessionActiveDisposable = nil
|
||||
self.isAudioSessionActive = false
|
||||
self.currentTone = nil
|
||||
|
||||
self.dropCallKitCallTimer?.invalidate()
|
||||
self.dropCallKitCallTimer = nil
|
||||
|
||||
self.droppedCall = true
|
||||
|
||||
self.videoCapturer = nil
|
||||
|
||||
self.screencastBufferServerContext = nil
|
||||
self.screencastCapturer = nil
|
||||
self.isScreencastActive = false
|
||||
|
||||
if let proximityManagerIndex = self.proximityManagerIndex {
|
||||
DeviceProximityManager.shared().remove(proximityManagerIndex)
|
||||
self.proximityManagerIndex = nil
|
||||
}
|
||||
|
||||
self.screencastFramesDisposable.set(nil)
|
||||
self.screencastAudioDataDisposable.set(nil)
|
||||
self.screencastStateDisposable.set(nil)
|
||||
|
||||
self.conferenceCallImpl = nil
|
||||
|
||||
self.conferenceCallDisposable?.dispose()
|
||||
self.conferenceCallDisposable = nil
|
||||
|
||||
self.upgradedToConferenceCompletions.removeAll()
|
||||
|
||||
self.waitForConferenceCallReadyDisposable?.dispose()
|
||||
self.waitForConferenceCallReadyDisposable = nil
|
||||
|
||||
self.pendingInviteToConferencePeerIds.removeAll()
|
||||
|
||||
self.localVideoEndpointId = nil
|
||||
self.remoteVideoEndpointId = nil
|
||||
|
||||
self.callKitIntegration?.updateCallIsConference(uuid: self.internalId)
|
||||
}
|
||||
|
||||
func internal_markAsCanBeRemoved() {
|
||||
if !self.didSetCanBeRemoved {
|
||||
self.didSetCanBeRemoved = true
|
||||
@ -540,6 +634,9 @@ public final class PresentationCallImpl: PresentationCall {
|
||||
}
|
||||
|
||||
private func updateSessionState(sessionState: CallSession, callContextState: OngoingCallContextState?, reception: Int32?, audioSessionControl: ManagedAudioSessionControl?) {
|
||||
if self.isMovedToConference {
|
||||
return
|
||||
}
|
||||
self.reception = reception
|
||||
|
||||
if let ongoingContext = self.ongoingContext {
|
||||
@ -721,11 +818,11 @@ public final class PresentationCallImpl: PresentationCall {
|
||||
presentationState = PresentationCallState(state: .terminated(id, reason, self.callWasActive && (options.contains(.reportRating) || self.shouldPresentCallRating)), videoState: mappedVideoState, remoteVideoState: .inactive, remoteAudioState: mappedRemoteAudioState, remoteBatteryLevel: mappedRemoteBatteryLevel)
|
||||
case let .requesting(ringing, _):
|
||||
presentationState = PresentationCallState(state: .requesting(ringing), videoState: mappedVideoState, remoteVideoState: mappedRemoteVideoState, remoteAudioState: mappedRemoteAudioState, remoteBatteryLevel: mappedRemoteBatteryLevel)
|
||||
case let .active(_, _, keyVisualHash, _, _, _, _, _, _), let .switchedToConference(_, keyVisualHash, _):
|
||||
case let .active(_, _, keyVisualHash, _, _, _, _, _, _, _), let .switchedToConference(_, keyVisualHash, _):
|
||||
self.callWasActive = true
|
||||
|
||||
var isConference = false
|
||||
if case let .active(_, _, _, _, _, _, _, _, conferenceCall) = sessionState.state {
|
||||
if case let .active(_, _, _, _, _, _, _, _, conferenceCall, _) = sessionState.state {
|
||||
isConference = conferenceCall != nil
|
||||
} else if case .switchedToConference = sessionState.state {
|
||||
isConference = true
|
||||
@ -765,8 +862,8 @@ public final class PresentationCallImpl: PresentationCall {
|
||||
var conferenceCallData: (key: Data, keyVisualHash: Data, conferenceCall: GroupCallReference)?
|
||||
var conferenceFromCallId: CallId?
|
||||
switch sessionState.state {
|
||||
case let .active(id, key, keyVisualHash, _, _, _, _, _, conferenceCall):
|
||||
if let conferenceCall {
|
||||
case let .active(id, key, keyVisualHash, _, _, _, _, _, conferenceCall, isIncomingConference):
|
||||
if let conferenceCall, !isIncomingConference {
|
||||
conferenceFromCallId = id
|
||||
conferenceCallData = (key, keyVisualHash, conferenceCall)
|
||||
}
|
||||
@ -780,6 +877,10 @@ public final class PresentationCallImpl: PresentationCall {
|
||||
if self.conferenceCallDisposable == nil {
|
||||
self.conferenceCallDisposable = EmptyDisposable
|
||||
|
||||
#if DEBUG
|
||||
print("Switching to conference call with encryption key: \(key.base64EncodedString())")
|
||||
#endif
|
||||
|
||||
let conferenceCall = PresentationGroupCallImpl(
|
||||
accountContext: self.context,
|
||||
audioSession: self.audioSession,
|
||||
@ -801,6 +902,7 @@ public final class PresentationCallImpl: PresentationCall {
|
||||
isStream: false,
|
||||
encryptionKey: (key, 1),
|
||||
conferenceFromCallId: conferenceFromCallId,
|
||||
conferenceSourceId: self.internalId,
|
||||
isConference: true,
|
||||
sharedAudioContext: self.sharedAudioContext
|
||||
)
|
||||
@ -908,7 +1010,7 @@ public final class PresentationCallImpl: PresentationCall {
|
||||
if let _ = audioSessionControl {
|
||||
self.audioSessionShouldBeActive.set(true)
|
||||
}
|
||||
case let .active(id, key, _, connections, maxLayer, version, customParameters, allowsP2P, _):
|
||||
case let .active(id, key, _, connections, maxLayer, version, customParameters, allowsP2P, _, _):
|
||||
self.audioSessionShouldBeActive.set(true)
|
||||
|
||||
if conferenceCallData != nil {
|
||||
@ -1035,7 +1137,7 @@ public final class PresentationCallImpl: PresentationCall {
|
||||
}
|
||||
|
||||
var isConference = false
|
||||
if case let .active(_, _, _, _, _, _, _, _, conferenceCall) = sessionState.state {
|
||||
if case let .active(_, _, _, _, _, _, _, _, conferenceCall, _) = sessionState.state {
|
||||
isConference = conferenceCall != nil
|
||||
} else if case .switchedToConference = sessionState.state {
|
||||
isConference = true
|
||||
@ -1063,59 +1165,10 @@ public final class PresentationCallImpl: PresentationCall {
|
||||
}
|
||||
}
|
||||
|
||||
private func requestMediaChannelDescriptions(ssrcs: Set<UInt32>, completion: @escaping ([OngoingGroupCallContext.MediaChannelDescription]) -> Void) -> Disposable {
|
||||
/*func extractMediaChannelDescriptions(remainingSsrcs: inout Set<UInt32>, participants: [GroupCallParticipantsContext.Participant], into result: inout [OngoingGroupCallContext.MediaChannelDescription]) {
|
||||
for participant in participants {
|
||||
guard let audioSsrc = participant.ssrc else {
|
||||
continue
|
||||
}
|
||||
|
||||
if remainingSsrcs.contains(audioSsrc) {
|
||||
remainingSsrcs.remove(audioSsrc)
|
||||
|
||||
result.append(OngoingGroupCallContext.MediaChannelDescription(
|
||||
kind: .audio,
|
||||
audioSsrc: audioSsrc,
|
||||
videoDescription: nil
|
||||
))
|
||||
}
|
||||
|
||||
if let screencastSsrc = participant.presentationDescription?.audioSsrc {
|
||||
if remainingSsrcs.contains(screencastSsrc) {
|
||||
remainingSsrcs.remove(screencastSsrc)
|
||||
|
||||
result.append(OngoingGroupCallContext.MediaChannelDescription(
|
||||
kind: .audio,
|
||||
audioSsrc: screencastSsrc,
|
||||
videoDescription: nil
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var remainingSsrcs = ssrcs
|
||||
var result: [OngoingGroupCallContext.MediaChannelDescription] = []
|
||||
|
||||
if let membersValue = self.membersValue {
|
||||
extractMediaChannelDescriptions(remainingSsrcs: &remainingSsrcs, participants: membersValue.participants, into: &result)
|
||||
}
|
||||
|
||||
if !remainingSsrcs.isEmpty, let callInfo = self.internalState.callInfo {
|
||||
return (self.accountContext.engine.calls.getGroupCallParticipants(callId: callInfo.id, accessHash: callInfo.accessHash, offset: "", ssrcs: Array(remainingSsrcs), limit: 100, sortAscending: callInfo.sortAscending)
|
||||
|> deliverOnMainQueue).start(next: { state in
|
||||
extractMediaChannelDescriptions(remainingSsrcs: &remainingSsrcs, participants: state.participants, into: &result)
|
||||
|
||||
completion(result)
|
||||
})
|
||||
} else {
|
||||
completion(result)
|
||||
return EmptyDisposable
|
||||
}*/
|
||||
return EmptyDisposable
|
||||
}
|
||||
|
||||
private func updateTone(_ state: PresentationCallState, callContextState: OngoingCallContextState?, previous: CallSession?) {
|
||||
if self.isMovedToConference {
|
||||
return
|
||||
}
|
||||
var tone: PresentationCallTone?
|
||||
if let callContextState = callContextState, case .reconnecting = callContextState.state {
|
||||
if !self.isVideo {
|
||||
@ -1165,16 +1218,25 @@ public final class PresentationCallImpl: PresentationCall {
|
||||
}
|
||||
|
||||
private func updateIsAudioSessionActive(_ value: Bool) {
|
||||
if self.isMovedToConference {
|
||||
return
|
||||
}
|
||||
if self.isAudioSessionActive != value {
|
||||
self.isAudioSessionActive = value
|
||||
}
|
||||
}
|
||||
|
||||
public func answer() {
|
||||
if self.isMovedToConference {
|
||||
return
|
||||
}
|
||||
self.answer(fromCallKitAction: false)
|
||||
}
|
||||
|
||||
func answer(fromCallKitAction: Bool) {
|
||||
if self.isMovedToConference {
|
||||
return
|
||||
}
|
||||
let (presentationData, present, openSettings) = self.getDeviceAccessData()
|
||||
|
||||
DeviceAccess.authorizeAccess(to: .microphone(.voiceCall), presentationData: presentationData, present: { c, a in
|
||||
@ -1225,6 +1287,9 @@ public final class PresentationCallImpl: PresentationCall {
|
||||
}
|
||||
|
||||
public func hangUp() -> Signal<Bool, NoError> {
|
||||
if self.isMovedToConference {
|
||||
return .single(true)
|
||||
}
|
||||
let debugLogValue = Promise<String?>()
|
||||
self.callSessionManager.drop(internalId: self.internalId, reason: .hangUp, debugLog: debugLogValue.get())
|
||||
self.ongoingContext?.stop(debugLogValue: debugLogValue)
|
||||
@ -1233,22 +1298,34 @@ public final class PresentationCallImpl: PresentationCall {
|
||||
}
|
||||
|
||||
public func rejectBusy() {
|
||||
if self.isMovedToConference {
|
||||
return
|
||||
}
|
||||
self.callSessionManager.drop(internalId: self.internalId, reason: .busy, debugLog: .single(nil))
|
||||
let debugLog = Promise<String?>()
|
||||
self.ongoingContext?.stop(debugLogValue: debugLog)
|
||||
}
|
||||
|
||||
public func toggleIsMuted() {
|
||||
if self.isMovedToConference {
|
||||
return
|
||||
}
|
||||
self.setIsMuted(!self.isMutedValue)
|
||||
}
|
||||
|
||||
public func setIsMuted(_ value: Bool) {
|
||||
if self.isMovedToConference {
|
||||
return
|
||||
}
|
||||
self.isMutedValue = value
|
||||
self.isMutedPromise.set(self.isMutedValue)
|
||||
self.ongoingContext?.setIsMuted(self.isMutedValue)
|
||||
}
|
||||
|
||||
public func requestVideo() {
|
||||
if self.isMovedToConference {
|
||||
return
|
||||
}
|
||||
if self.videoCapturer == nil {
|
||||
let videoCapturer = OngoingCallVideoCapturer()
|
||||
self.videoCapturer = videoCapturer
|
||||
@ -1261,6 +1338,9 @@ public final class PresentationCallImpl: PresentationCall {
|
||||
}
|
||||
|
||||
public func requestVideo(capturer: OngoingCallVideoCapturer) {
|
||||
if self.isMovedToConference {
|
||||
return
|
||||
}
|
||||
if self.videoCapturer == nil {
|
||||
self.videoCapturer = capturer
|
||||
}
|
||||
@ -1272,11 +1352,17 @@ public final class PresentationCallImpl: PresentationCall {
|
||||
}
|
||||
|
||||
public func setRequestedVideoAspect(_ aspect: Float) {
|
||||
if self.isMovedToConference {
|
||||
return
|
||||
}
|
||||
self.requestedVideoAspect = aspect
|
||||
self.ongoingContext?.setRequestedVideoAspect(aspect)
|
||||
}
|
||||
|
||||
public func disableVideo() {
|
||||
if self.isMovedToConference {
|
||||
return
|
||||
}
|
||||
if let _ = self.videoCapturer {
|
||||
self.videoCapturer = nil
|
||||
if let ongoingContext = self.ongoingContext {
|
||||
@ -1286,6 +1372,9 @@ public final class PresentationCallImpl: PresentationCall {
|
||||
}
|
||||
|
||||
private func resetScreencastContext() {
|
||||
if self.isMovedToConference {
|
||||
return
|
||||
}
|
||||
let basePath = self.context.sharedContext.basePath + "/broadcast-coordination"
|
||||
let screencastBufferServerContext = IpcGroupCallBufferAppContext(basePath: basePath)
|
||||
self.screencastBufferServerContext = screencastBufferServerContext
|
||||
@ -1323,6 +1412,9 @@ public final class PresentationCallImpl: PresentationCall {
|
||||
}
|
||||
|
||||
private func requestScreencast() {
|
||||
if self.isMovedToConference {
|
||||
return
|
||||
}
|
||||
self.disableVideo()
|
||||
|
||||
if let screencastCapturer = self.screencastCapturer {
|
||||
@ -1334,6 +1426,9 @@ public final class PresentationCallImpl: PresentationCall {
|
||||
}
|
||||
|
||||
func disableScreencast(reset: Bool = true) {
|
||||
if self.isMovedToConference {
|
||||
return
|
||||
}
|
||||
if self.isScreencastActive {
|
||||
if let _ = self.videoCapturer {
|
||||
self.videoCapturer = nil
|
||||
@ -1348,10 +1443,16 @@ public final class PresentationCallImpl: PresentationCall {
|
||||
}
|
||||
|
||||
public func setOutgoingVideoIsPaused(_ isPaused: Bool) {
|
||||
if self.isMovedToConference {
|
||||
return
|
||||
}
|
||||
self.videoCapturer?.setIsVideoEnabled(!isPaused)
|
||||
}
|
||||
|
||||
public func upgradeToConference(invitePeerIds: [EnginePeer.Id], completion: @escaping (PresentationGroupCall) -> Void) -> Disposable {
|
||||
if self.isMovedToConference {
|
||||
return EmptyDisposable
|
||||
}
|
||||
if let conferenceCall = self.conferenceCall {
|
||||
completion(conferenceCall)
|
||||
return EmptyDisposable
|
||||
@ -1376,6 +1477,9 @@ public final class PresentationCallImpl: PresentationCall {
|
||||
}
|
||||
|
||||
public func setCurrentAudioOutput(_ output: AudioSessionOutput) {
|
||||
if self.isMovedToConference {
|
||||
return
|
||||
}
|
||||
if let sharedAudioContext = self.sharedAudioContext {
|
||||
sharedAudioContext.setCurrentAudioOutput(output)
|
||||
return
|
||||
@ -1407,6 +1511,9 @@ public final class PresentationCallImpl: PresentationCall {
|
||||
}
|
||||
|
||||
func video(isIncoming: Bool) -> Signal<OngoingGroupCallContext.VideoFrameData, NoError>? {
|
||||
if self.isMovedToConference {
|
||||
return nil
|
||||
}
|
||||
if isIncoming {
|
||||
if let ongoingContext = self.ongoingContext {
|
||||
return ongoingContext.video(isIncoming: isIncoming)
|
||||
@ -1421,6 +1528,10 @@ public final class PresentationCallImpl: PresentationCall {
|
||||
}
|
||||
|
||||
public func makeOutgoingVideoView(completion: @escaping (PresentationCallVideoView?) -> Void) {
|
||||
if self.isMovedToConference {
|
||||
completion(nil)
|
||||
return
|
||||
}
|
||||
if self.videoCapturer == nil {
|
||||
let videoCapturer = OngoingCallVideoCapturer()
|
||||
self.videoCapturer = videoCapturer
|
||||
@ -1495,6 +1606,9 @@ public final class PresentationCallImpl: PresentationCall {
|
||||
}
|
||||
|
||||
public func switchVideoCamera() {
|
||||
if self.isMovedToConference {
|
||||
return
|
||||
}
|
||||
self.useFrontCamera = !self.useFrontCamera
|
||||
self.videoCapturer?.switchVideoInput(isFront: self.useFrontCamera)
|
||||
}
|
||||
|
@ -61,9 +61,12 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
|
||||
private var currentCallDisposable = MetaDisposable()
|
||||
private let removeCurrentCallDisposable = MetaDisposable()
|
||||
private let removeCurrentGroupCallDisposable = MetaDisposable()
|
||||
private var callToConferenceDisposable: Disposable?
|
||||
private var isConferenceReadyDisposable: 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 +98,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()
|
||||
}
|
||||
|
||||
@ -249,7 +252,19 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
|
||||
}
|
||||
|
||||
endCallImpl = { [weak self] uuid in
|
||||
if let strongSelf = self, let currentCall = strongSelf.currentCall {
|
||||
guard let self else {
|
||||
return .single(false)
|
||||
}
|
||||
|
||||
if let currentGroupCall = self.currentGroupCall {
|
||||
switch currentGroupCall {
|
||||
case let .conferenceSource(conferenceSource):
|
||||
return conferenceSource.hangUp()
|
||||
case let .group(groupCall):
|
||||
return groupCall.leave(terminateIfPossible: false)
|
||||
}
|
||||
}
|
||||
if let currentCall = self.currentCall {
|
||||
return currentCall.hangUp()
|
||||
} else {
|
||||
return .single(false)
|
||||
@ -297,16 +312,24 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
|
||||
self.startCallDisposable.dispose()
|
||||
self.proxyServerDisposable?.dispose()
|
||||
self.callSettingsDisposable?.dispose()
|
||||
self.callToConferenceDisposable?.dispose()
|
||||
self.isConferenceReadyDisposable?.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
|
||||
@ -325,7 +348,7 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
|
||||
internalId: firstState.2.id,
|
||||
peerId: firstState.2.peerId,
|
||||
isOutgoing: false,
|
||||
isIncomingConference: firstState.2.isConference,
|
||||
isIncomingConference: firstState.2.isIncomingConference,
|
||||
peer: EnginePeer(firstState.1),
|
||||
proxyServer: strongSelf.proxyServer,
|
||||
auxiliaryServers: [],
|
||||
@ -338,18 +361,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,12 +381,15 @@ 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 CXCallObserver().calls.contains(where: { $0.hasEnded == false }) {
|
||||
alreadyInCall = true
|
||||
}
|
||||
}
|
||||
|
||||
@ -456,12 +470,22 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
|
||||
begin()
|
||||
}))
|
||||
} else if let currentGroupCall = self.currentGroupCallValue {
|
||||
self.startCallDisposable.set((currentGroupCall.leave(terminateIfPossible: false)
|
||||
|> filter { $0 }
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).start(next: { _ in
|
||||
begin()
|
||||
}))
|
||||
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 +502,22 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
|
||||
begin()
|
||||
}))
|
||||
} else if let currentGroupCall = self.currentGroupCallValue {
|
||||
self.startCallDisposable.set((currentGroupCall.leave(terminateIfPossible: false)
|
||||
|> filter { $0 }
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).start(next: { _ in
|
||||
begin()
|
||||
}))
|
||||
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 +574,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 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)
|
||||
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
|
||||
)
|
||||
|> deliverOnMainQueue
|
||||
|> beforeNext { internalId, currentNetworkType, isContact, preferences, sharedData, areVideoCallsAvailable in
|
||||
if let strongSelf = self, accessEnabled {
|
||||
@ -586,18 +629,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,7 +644,48 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
|
||||
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 {
|
||||
self.resumeMedia = false
|
||||
@ -621,14 +693,85 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
self.currentGroupCallValue = value
|
||||
if self.currentGroupCallValue != value {
|
||||
if case let .group(groupCall) = self.currentGroupCallValue, let conferenceSourceId = groupCall.conferenceSource {
|
||||
groupCall.accountContext.account.callSessionManager.drop(internalId: conferenceSourceId, reason: .hangUp, debugLog: .single(nil))
|
||||
(groupCall as! PresentationGroupCallImpl).callKitIntegration?.dropCall(uuid: conferenceSourceId)
|
||||
}
|
||||
|
||||
self.currentGroupCallValue = value
|
||||
|
||||
self.isConferenceReadyDisposable?.dispose()
|
||||
self.isConferenceReadyDisposable = nil
|
||||
|
||||
if let value {
|
||||
switch value {
|
||||
case let .conferenceSource(conferenceSource):
|
||||
self.isConferenceReadyDisposable?.dispose()
|
||||
self.isConferenceReadyDisposable = (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
|
||||
}
|
||||
(conferenceSource as! PresentationCallImpl).resetAsMovedToConference()
|
||||
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
|
||||
@ -671,7 +814,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,93 +827,37 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
|
||||
isChannel = true
|
||||
}
|
||||
|
||||
if shouldUseV2VideoChatImpl(context: accountContext) {
|
||||
if let parentController {
|
||||
parentController.push(ScheduleVideoChatSheetScreen(
|
||||
context: accountContext,
|
||||
scheduleAction: { timestamp in
|
||||
guard let self else {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}))
|
||||
if let parentController {
|
||||
parentController.push(ScheduleVideoChatSheetScreen(
|
||||
context: accountContext,
|
||||
scheduleAction: { [weak self] timestamp in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
))
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
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,
|
||||
conferenceSourceId: nil,
|
||||
isConference: false,
|
||||
sharedAudioContext: nil
|
||||
)
|
||||
call.schedule(timestamp: timestamp)
|
||||
|
||||
self.updateCurrentGroupCall(.group(call))
|
||||
}
|
||||
if value {
|
||||
if strongSelf.currentGroupCall === call {
|
||||
strongSelf.updateCurrentGroupCall(nil)
|
||||
strongSelf.currentGroupCallPromise.set(.single(nil))
|
||||
strongSelf.hasActiveGroupCallsPromise.set(false)
|
||||
}
|
||||
}
|
||||
}))
|
||||
))
|
||||
}
|
||||
|
||||
return .single(true)
|
||||
@ -787,15 +874,29 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
|
||||
|
||||
if let currentGroupCall = self.currentGroupCallValue {
|
||||
if endCurrentIfAny {
|
||||
let endSignal = currentGroupCall.leave(terminateIfPossible: false)
|
||||
|> filter { $0 }
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue
|
||||
self.startCallDisposable.set(endSignal.start(next: { _ in
|
||||
begin()
|
||||
}))
|
||||
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 {
|
||||
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 +933,29 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
|
||||
|
||||
if let currentGroupCall = self.currentGroupCallValue {
|
||||
if endCurrentIfAny {
|
||||
let endSignal = currentGroupCall.leave(terminateIfPossible: false)
|
||||
|> filter { $0 }
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue
|
||||
self.startCallDisposable.set(endSignal.start(next: { _ in
|
||||
begin()
|
||||
}))
|
||||
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 {
|
||||
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,48 +1034,52 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
|
||||
return .single(false)
|
||||
}
|
||||
|
||||
var isChannel = false
|
||||
if let peer = peer, case let .channel(channel) = peer, case .broadcast = channel.info {
|
||||
isChannel = true
|
||||
}
|
||||
|
||||
let call = PresentationGroupCallImpl(
|
||||
strongSelf.createGroupCall(
|
||||
accountContext: accountContext,
|
||||
audioSession: strongSelf.audioSession,
|
||||
callKitIntegration: nil,
|
||||
getDeviceAccessData: strongSelf.getDeviceAccessData,
|
||||
peerId: peerId,
|
||||
peer: peer,
|
||||
initialCall: initialCall,
|
||||
internalId: internalId,
|
||||
peerId: peerId,
|
||||
isChannel: isChannel,
|
||||
invite: invite,
|
||||
joinAsPeerId: joinAsPeerId,
|
||||
isStream: initialCall.isStream ?? false,
|
||||
encryptionKey: nil,
|
||||
conferenceFromCallId: nil,
|
||||
isConference: false,
|
||||
sharedAudioContext: nil
|
||||
joinAsPeerId: joinAsPeerId
|
||||
)
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
conferenceSourceId: nil,
|
||||
isConference: false,
|
||||
sharedAudioContext: nil
|
||||
)
|
||||
self.updateCurrentGroupCall(.group(call))
|
||||
}
|
||||
}
|
||||
|
@ -770,7 +770,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
public let account: Account
|
||||
public let accountContext: AccountContext
|
||||
private let audioSession: ManagedAudioSession
|
||||
private let callKitIntegration: CallKitIntegration?
|
||||
public let callKitIntegration: CallKitIntegration?
|
||||
public var isIntegratedWithCallKit: Bool {
|
||||
return self.callKitIntegration != nil
|
||||
}
|
||||
@ -1057,6 +1057,11 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
}
|
||||
}
|
||||
|
||||
private let conferenceSourceId: CallSessionInternalId?
|
||||
public var conferenceSource: CallSessionInternalId? {
|
||||
return self.conferenceSourceId
|
||||
}
|
||||
|
||||
var internal_isRemoteConnected = Promise<Bool>()
|
||||
private var internal_isRemoteConnectedDisposable: Disposable?
|
||||
|
||||
@ -1081,6 +1086,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
isStream: Bool,
|
||||
encryptionKey: (key: Data, fingerprint: Int64)?,
|
||||
conferenceFromCallId: CallId?,
|
||||
conferenceSourceId: CallSessionInternalId?,
|
||||
isConference: Bool,
|
||||
sharedAudioContext: SharedCallAudioContext?
|
||||
) {
|
||||
@ -1109,8 +1115,16 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
self.hasScreencast = false
|
||||
self.isStream = isStream
|
||||
self.conferenceFromCallId = conferenceFromCallId
|
||||
self.conferenceSourceId = conferenceSourceId
|
||||
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 +1920,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 +2888,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))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
@ -326,6 +326,11 @@ final class VideoChatMicButtonComponent: Component {
|
||||
let previousComponent = self.component
|
||||
self.component = component
|
||||
|
||||
if let previousComponent, previousComponent.call != component.call {
|
||||
self.audioLevelDisposable?.dispose()
|
||||
self.audioLevelDisposable = nil
|
||||
}
|
||||
|
||||
let alphaTransition: ComponentTransition = transition.animation.isImmediate ? .immediate : .easeInOut(duration: 0.2)
|
||||
|
||||
let titleText: String
|
||||
|
@ -230,6 +230,11 @@ final class VideoChatParticipantAvatarComponent: Component {
|
||||
let previousComponent = self.component
|
||||
self.component = component
|
||||
|
||||
if let previousComponent, previousComponent.call != component.call {
|
||||
self.audioLevelDisposable?.dispose()
|
||||
self.audioLevelDisposable = nil
|
||||
}
|
||||
|
||||
let avatarNode: AvatarNode
|
||||
if let current = self.avatarNode {
|
||||
avatarNode = current
|
||||
|
@ -24,16 +24,7 @@ import TelegramAudio
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
extension VideoChatCall {
|
||||
var myAudioLevelAndSpeaking: Signal<(Float, Bool), NoError> {
|
||||
switch self {
|
||||
case let .group(group):
|
||||
@ -172,6 +163,16 @@ final class VideoChatScreenComponent: Component {
|
||||
}
|
||||
|
||||
static func ==(lhs: VideoChatScreenComponent, rhs: VideoChatScreenComponent) -> Bool {
|
||||
if lhs === rhs {
|
||||
return true
|
||||
}
|
||||
if lhs.initialData !== rhs.initialData {
|
||||
return false
|
||||
}
|
||||
if lhs.initialCall != rhs.initialCall {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
@ -1091,7 +1092,12 @@ final class VideoChatScreenComponent: Component {
|
||||
self.callState = component.initialData.callState
|
||||
}
|
||||
|
||||
var call = self.currentCall ?? component.initialCall
|
||||
var call: VideoChatCall
|
||||
if let previousComponent = self.component, previousComponent.initialCall != component.initialCall {
|
||||
call = component.initialCall
|
||||
} else {
|
||||
call = self.currentCall ?? component.initialCall
|
||||
}
|
||||
if case let .conferenceSource(conferenceSource) = call, let conferenceCall = conferenceSource.conferenceCall, conferenceSource.conferenceStateValue == .ready {
|
||||
call = .group(conferenceCall)
|
||||
}
|
||||
@ -2526,6 +2532,17 @@ final class VideoChatScreenV2Impl: ViewControllerComponentContainer, VoiceChatCo
|
||||
self.idleTimerExtensionDisposable?.dispose()
|
||||
}
|
||||
|
||||
func updateCall(call: VideoChatCall) {
|
||||
self.call = call
|
||||
if let component = self.component.wrapped as? VideoChatScreenComponent {
|
||||
// This is only to clear the reference to regular call
|
||||
self.updateComponent(component: AnyComponent(VideoChatScreenComponent(
|
||||
initialData: component.initialData,
|
||||
initialCall: call
|
||||
)), transition: .immediate)
|
||||
}
|
||||
}
|
||||
|
||||
override public func viewDidAppear(_ animated: Bool) {
|
||||
super.viewDidAppear(animated)
|
||||
|
||||
|
@ -248,6 +248,8 @@ public protocol VoiceChatController: ViewController {
|
||||
var onViewDidAppear: (() -> Void)? { get set }
|
||||
var onViewDidDisappear: (() -> Void)? { get set }
|
||||
|
||||
func updateCall(call: VideoChatCall)
|
||||
|
||||
func dismiss(closing: Bool, manual: Bool)
|
||||
}
|
||||
|
||||
@ -6938,6 +6940,9 @@ final class VoiceChatControllerImpl: ViewController, VoiceChatController {
|
||||
}
|
||||
}
|
||||
|
||||
func updateCall(call: VideoChatCall) {
|
||||
}
|
||||
|
||||
override public func loadDisplayNode() {
|
||||
self.displayNode = Node(controller: self, sharedContext: self.sharedContext, call: self.callImpl)
|
||||
|
||||
|
@ -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
|
||||
|
@ -79,7 +79,7 @@ enum CallSessionInternalState {
|
||||
case requesting(a: Data, conferenceCall: GroupCallReference?, disposable: Disposable)
|
||||
case requested(id: Int64, accessHash: Int64, a: Data, gA: Data, config: SecretChatEncryptionConfig, remoteConfirmationTimestamp: Int32?, conferenceCall: GroupCallReference?)
|
||||
case confirming(id: Int64, accessHash: Int64, key: Data, keyId: Int64, keyVisualHash: Data, conferenceCall: GroupCallReference?, disposable: Disposable)
|
||||
case active(id: Int64, accessHash: Int64, beginTimestamp: Int32, key: Data, keyId: Int64, keyVisualHash: Data, connections: CallSessionConnectionSet, maxLayer: Int32, version: String, customParameters: String?, allowsP2P: Bool, conferenceCall: GroupCallReference?)
|
||||
case active(id: Int64, accessHash: Int64, beginTimestamp: Int32, key: Data, keyId: Int64, keyVisualHash: Data, connections: CallSessionConnectionSet, maxLayer: Int32, version: String, customParameters: String?, allowsP2P: Bool, conferenceCall: GroupCallReference?, willSwitchToConference: Bool)
|
||||
case switchedToConference(key: Data, keyVisualHash: Data, conferenceCall: GroupCallReference)
|
||||
case dropping(reason: CallSessionTerminationReason, disposable: Disposable)
|
||||
case terminated(id: Int64?, accessHash: Int64?, reason: CallSessionTerminationReason, reportRating: Bool, sendDebugLogs: Bool)
|
||||
@ -98,7 +98,7 @@ enum CallSessionInternalState {
|
||||
return id
|
||||
case let .confirming(id, _, _, _, _, _, _):
|
||||
return id
|
||||
case let .active(id, _, _, _, _, _, _, _, _, _, _, _):
|
||||
case let .active(id, _, _, _, _, _, _, _, _, _, _, _, _):
|
||||
return id
|
||||
case .switchedToConference:
|
||||
return nil
|
||||
@ -137,7 +137,7 @@ public struct CallSessionRingingState: Equatable {
|
||||
public let peerId: PeerId
|
||||
public let isVideo: Bool
|
||||
public let isVideoPossible: Bool
|
||||
public let isConference: Bool
|
||||
public let isIncomingConference: Bool
|
||||
}
|
||||
|
||||
public enum DropCallReason {
|
||||
@ -166,7 +166,7 @@ public enum CallSessionState {
|
||||
case ringing
|
||||
case accepting
|
||||
case requesting(ringing: Bool, conferenceCall: GroupCallReference?)
|
||||
case active(id: CallId, key: Data, keyVisualHash: Data, connections: CallSessionConnectionSet, maxLayer: Int32, version: String, customParameters: String?, allowsP2P: Bool, conferenceCall: GroupCallReference?)
|
||||
case active(id: CallId, key: Data, keyVisualHash: Data, connections: CallSessionConnectionSet, maxLayer: Int32, version: String, customParameters: String?, allowsP2P: Bool, conferenceCall: GroupCallReference?, willSwitchToConference: Bool)
|
||||
case switchedToConference(key: Data, keyVisualHash: Data, conferenceCall: GroupCallReference)
|
||||
case dropping(reason: CallSessionTerminationReason)
|
||||
case terminated(id: CallId?, reason: CallSessionTerminationReason, options: CallTerminationOptions)
|
||||
@ -183,8 +183,8 @@ public enum CallSessionState {
|
||||
self = .requesting(ringing: true, conferenceCall: conferenceCall)
|
||||
case let .requested(_, _, _, _, _, remoteConfirmationTimestamp, conferenceCall):
|
||||
self = .requesting(ringing: remoteConfirmationTimestamp != nil, conferenceCall: conferenceCall)
|
||||
case let .active(id, accessHash, _, key, _, keyVisualHash, connections, maxLayer, version, customParameters, allowsP2P, conferenceCall):
|
||||
self = .active(id: CallId(id: id, accessHash: accessHash), key: key, keyVisualHash: keyVisualHash, connections: connections, maxLayer: maxLayer, version: version, customParameters: customParameters, allowsP2P: allowsP2P, conferenceCall: conferenceCall)
|
||||
case let .active(id, accessHash, _, key, _, keyVisualHash, connections, maxLayer, version, customParameters, allowsP2P, conferenceCall, willSwitchToConference):
|
||||
self = .active(id: CallId(id: id, accessHash: accessHash), key: key, keyVisualHash: keyVisualHash, connections: connections, maxLayer: maxLayer, version: version, customParameters: customParameters, allowsP2P: allowsP2P, conferenceCall: conferenceCall, willSwitchToConference: willSwitchToConference)
|
||||
case let .dropping(reason, _):
|
||||
self = .dropping(reason: reason)
|
||||
case let .terminated(id, accessHash, reason, reportRating, sendDebugLogs):
|
||||
@ -335,7 +335,7 @@ private func parseConnectionSet(primary: Api.PhoneConnection, alternative: [Api.
|
||||
private final class CallSessionContext {
|
||||
let peerId: PeerId
|
||||
let isOutgoing: Bool
|
||||
let isConference: Bool
|
||||
let isIncomingConference: Bool
|
||||
var type: CallSession.CallType
|
||||
var isVideoPossible: Bool
|
||||
let pendingConference: (conference: GroupCallReference, encryptionKey: Data)?
|
||||
@ -355,10 +355,10 @@ private final class CallSessionContext {
|
||||
}
|
||||
}
|
||||
|
||||
init(peerId: PeerId, isOutgoing: Bool, isConference: Bool, type: CallSession.CallType, isVideoPossible: Bool, pendingConference: (conference: GroupCallReference, encryptionKey: Data)?, state: CallSessionInternalState) {
|
||||
init(peerId: PeerId, isOutgoing: Bool, isIncomingConference: Bool, type: CallSession.CallType, isVideoPossible: Bool, pendingConference: (conference: GroupCallReference, encryptionKey: Data)?, state: CallSessionInternalState) {
|
||||
self.peerId = peerId
|
||||
self.isOutgoing = isOutgoing
|
||||
self.isConference = isConference
|
||||
self.isIncomingConference = isIncomingConference
|
||||
self.type = type
|
||||
self.isVideoPossible = isVideoPossible
|
||||
self.pendingConference = pendingConference
|
||||
@ -555,7 +555,7 @@ private final class CallSessionManagerContext {
|
||||
peerId: context.peerId,
|
||||
isVideo: context.type == .video,
|
||||
isVideoPossible: context.isVideoPossible,
|
||||
isConference: context.isConference
|
||||
isIncomingConference: context.isIncomingConference
|
||||
))
|
||||
}
|
||||
}
|
||||
@ -599,7 +599,7 @@ private final class CallSessionManagerContext {
|
||||
//#endif
|
||||
|
||||
let internalId = CallSessionManager.getStableIncomingUUID(stableId: stableId)
|
||||
let context = CallSessionContext(peerId: peerId, isOutgoing: false, isConference: conferenceCall != nil, type: isVideo ? .video : .audio, isVideoPossible: isVideoPossible, pendingConference: nil, state: .ringing(id: stableId, accessHash: accessHash, gAHash: gAHash, b: b, versions: versions, conferenceCall: conferenceCall))
|
||||
let context = CallSessionContext(peerId: peerId, isOutgoing: false, isIncomingConference: conferenceCall != nil, type: isVideo ? .video : .audio, isVideoPossible: isVideoPossible, pendingConference: nil, state: .ringing(id: stableId, accessHash: accessHash, gAHash: gAHash, b: b, versions: versions, conferenceCall: conferenceCall))
|
||||
self.contexts[internalId] = context
|
||||
let queue = self.queue
|
||||
|
||||
@ -653,7 +653,7 @@ private final class CallSessionManagerContext {
|
||||
case let .accepting(id, accessHash, _, _, _, disposable):
|
||||
dropData = (id, accessHash, .abort)
|
||||
disposable.dispose()
|
||||
case let .active(id, accessHash, beginTimestamp, _, _, _, _, _, _, _, _, _):
|
||||
case let .active(id, accessHash, beginTimestamp, _, _, _, _, _, _, _, _, _, _):
|
||||
let duration = max(0, Int32(CFAbsoluteTimeGetCurrent()) - beginTimestamp)
|
||||
let internalReason: DropCallSessionReason
|
||||
switch reason {
|
||||
@ -755,7 +755,7 @@ private final class CallSessionManagerContext {
|
||||
var dropData: (CallSessionStableId, Int64)?
|
||||
let isVideo = context.type == .video
|
||||
switch context.state {
|
||||
case let .active(id, accessHash, _, _, _, _, _, _, _, _, _, _):
|
||||
case let .active(id, accessHash, _, _, _, _, _, _, _, _, _, _, _):
|
||||
dropData = (id, accessHash)
|
||||
default:
|
||||
break
|
||||
@ -807,7 +807,7 @@ private final class CallSessionManagerContext {
|
||||
strongSelf.contextUpdated(internalId: internalId)
|
||||
case let .call(config, gA, timestamp, connections, maxLayer, version, customParameters, allowsP2P, conferenceCall):
|
||||
if let (key, keyId, keyVisualHash) = strongSelf.makeSessionEncryptionKey(config: config, gAHash: gAHash, b: b, gA: gA) {
|
||||
context.state = .active(id: id, accessHash: accessHash, beginTimestamp: timestamp, key: key, keyId: keyId, keyVisualHash: keyVisualHash, connections: connections, maxLayer: maxLayer, version: version, customParameters: customParameters, allowsP2P: allowsP2P, conferenceCall: conferenceCall)
|
||||
context.state = .active(id: id, accessHash: accessHash, beginTimestamp: timestamp, key: key, keyId: keyId, keyVisualHash: keyVisualHash, connections: connections, maxLayer: maxLayer, version: version, customParameters: customParameters, allowsP2P: allowsP2P, conferenceCall: conferenceCall, willSwitchToConference: context.isIncomingConference)
|
||||
strongSelf.contextUpdated(internalId: internalId)
|
||||
} else {
|
||||
strongSelf.drop(internalId: internalId, reason: .disconnect, debugLog: .single(nil))
|
||||
@ -828,7 +828,7 @@ private final class CallSessionManagerContext {
|
||||
func sendSignalingData(internalId: CallSessionInternalId, data: Data) {
|
||||
if let context = self.contexts[internalId] {
|
||||
switch context.state {
|
||||
case let .active(id, accessHash, _, _, _, _, _, _, _, _, _, _):
|
||||
case let .active(id, accessHash, _, _, _, _, _, _, _, _, _, _, _):
|
||||
context.signalingDisposables.add(self.network.request(Api.functions.phone.sendSignalingData(peer: .inputPhoneCall(id: id, accessHash: accessHash), data: Buffer(data: data))).start())
|
||||
default:
|
||||
break
|
||||
@ -844,7 +844,7 @@ private final class CallSessionManagerContext {
|
||||
|
||||
var idAndAccessHash: (id: Int64, accessHash: Int64)?
|
||||
switch context.state {
|
||||
case let .active(id, accessHash, _, _, _, _, _, _, _, _, _, conferenceCall):
|
||||
case let .active(id, accessHash, _, _, _, _, _, _, _, _, _, conferenceCall, _):
|
||||
if conferenceCall != nil {
|
||||
return
|
||||
}
|
||||
@ -864,8 +864,8 @@ private final class CallSessionManagerContext {
|
||||
}
|
||||
if let result {
|
||||
switch context.state {
|
||||
case let .active(id, accessHash, beginTimestamp, key, keyId, keyVisualHash, connections, maxLayer, version, customParameters, allowsP2P, _):
|
||||
context.state = .active(id: id, accessHash: accessHash, beginTimestamp: beginTimestamp, key: key, keyId: keyId, keyVisualHash: keyVisualHash, connections: connections, maxLayer: maxLayer, version: version, customParameters: customParameters, allowsP2P: allowsP2P, conferenceCall: result)
|
||||
case let .active(id, accessHash, beginTimestamp, key, keyId, keyVisualHash, connections, maxLayer, version, customParameters, allowsP2P, _, _):
|
||||
context.state = .active(id: id, accessHash: accessHash, beginTimestamp: beginTimestamp, key: key, keyId: keyId, keyVisualHash: keyVisualHash, connections: connections, maxLayer: maxLayer, version: version, customParameters: customParameters, allowsP2P: allowsP2P, conferenceCall: result, willSwitchToConference: false)
|
||||
self.contextUpdated(internalId: internalId)
|
||||
default:
|
||||
break
|
||||
@ -977,7 +977,7 @@ private final class CallSessionManagerContext {
|
||||
disposable.dispose()
|
||||
context.state = .terminated(id: id, accessHash: accessHash, reason: parsedReason, reportRating: reportRating, sendDebugLogs: sendDebugLogs)
|
||||
self.contextUpdated(internalId: internalId)
|
||||
case let .active(id, accessHash, _, _, _, _, _, _, _, _, _, conferenceCall):
|
||||
case let .active(id, accessHash, _, _, _, _, _, _, _, _, _, conferenceCall, _):
|
||||
if let conferenceCall, case let .phoneCallDiscardReasonAllowGroupCall(encryptedGroupKey) = reason {
|
||||
context.state = .switchedToConference(key: encryptedGroupKey.makeData(), keyVisualHash: MTSha256(encryptedGroupKey.makeData()), conferenceCall: conferenceCall)
|
||||
} else {
|
||||
@ -1016,8 +1016,8 @@ private final class CallSessionManagerContext {
|
||||
switch context.state {
|
||||
case .accepting, .dropping, .requesting, .ringing, .terminated, .requested, .switchedToConference:
|
||||
break
|
||||
case let .active(id, accessHash, beginTimestamp, key, keyId, keyVisualHash, connections, maxLayer, version, customParameters, allowsP2P, _):
|
||||
context.state = .active(id: id, accessHash: accessHash, beginTimestamp: beginTimestamp, key: key, keyId: keyId, keyVisualHash: keyVisualHash, connections: connections, maxLayer: maxLayer, version: version, customParameters: customParameters, allowsP2P: allowsP2P, conferenceCall: conferenceCall.flatMap(GroupCallReference.init))
|
||||
case let .active(id, accessHash, beginTimestamp, key, keyId, keyVisualHash, connections, maxLayer, version, customParameters, allowsP2P, _, _):
|
||||
context.state = .active(id: id, accessHash: accessHash, beginTimestamp: beginTimestamp, key: key, keyId: keyId, keyVisualHash: keyVisualHash, connections: connections, maxLayer: maxLayer, version: version, customParameters: customParameters, allowsP2P: allowsP2P, conferenceCall: conferenceCall.flatMap(GroupCallReference.init), willSwitchToConference: context.isIncomingConference)
|
||||
self.contextUpdated(internalId: internalId)
|
||||
case let .awaitingConfirmation(_, accessHash, gAHash, b, config):
|
||||
if let (key, calculatedKeyId, keyVisualHash) = self.makeSessionEncryptionKey(config: config, gAHash: gAHash, b: b, gA: gAOrB.makeData()) {
|
||||
@ -1036,7 +1036,7 @@ private final class CallSessionManagerContext {
|
||||
let isVideoPossible = self.videoVersions().contains(where: { versions.contains($0) })
|
||||
context.isVideoPossible = isVideoPossible
|
||||
|
||||
context.state = .active(id: id, accessHash: accessHash, beginTimestamp: startDate, key: key, keyId: calculatedKeyId, keyVisualHash: keyVisualHash, connections: parseConnectionSet(primary: connections.first!, alternative: Array(connections[1...])), maxLayer: maxLayer, version: versions[0], customParameters: customParametersValue, allowsP2P: allowsP2P, conferenceCall: conferenceCall.flatMap(GroupCallReference.init))
|
||||
context.state = .active(id: id, accessHash: accessHash, beginTimestamp: startDate, key: key, keyId: calculatedKeyId, keyVisualHash: keyVisualHash, connections: parseConnectionSet(primary: connections.first!, alternative: Array(connections[1...])), maxLayer: maxLayer, version: versions[0], customParameters: customParametersValue, allowsP2P: allowsP2P, conferenceCall: conferenceCall.flatMap(GroupCallReference.init), willSwitchToConference: context.isIncomingConference)
|
||||
self.contextUpdated(internalId: internalId)
|
||||
} else {
|
||||
self.drop(internalId: internalId, reason: .disconnect, debugLog: .single(nil))
|
||||
@ -1063,7 +1063,7 @@ private final class CallSessionManagerContext {
|
||||
let isVideoPossible = self.videoVersions().contains(where: { versions.contains($0) })
|
||||
context.isVideoPossible = isVideoPossible
|
||||
|
||||
context.state = .active(id: id, accessHash: accessHash, beginTimestamp: startDate, key: key, keyId: keyId, keyVisualHash: keyVisualHash, connections: parseConnectionSet(primary: connections.first!, alternative: Array(connections[1...])), maxLayer: maxLayer, version: versions[0], customParameters: customParametersValue, allowsP2P: allowsP2P, conferenceCall: conferenceCall.flatMap(GroupCallReference.init))
|
||||
context.state = .active(id: id, accessHash: accessHash, beginTimestamp: startDate, key: key, keyId: keyId, keyVisualHash: keyVisualHash, connections: parseConnectionSet(primary: connections.first!, alternative: Array(connections[1...])), maxLayer: maxLayer, version: versions[0], customParameters: customParametersValue, allowsP2P: allowsP2P, conferenceCall: conferenceCall.flatMap(GroupCallReference.init), willSwitchToConference: context.isIncomingConference)
|
||||
self.contextUpdated(internalId: internalId)
|
||||
} else {
|
||||
self.drop(internalId: internalId, reason: .disconnect, debugLog: .single(nil))
|
||||
@ -1173,7 +1173,7 @@ private final class CallSessionManagerContext {
|
||||
let randomStatus = SecRandomCopyBytes(nil, 256, aBytes.assumingMemoryBound(to: UInt8.self))
|
||||
let a = Data(bytesNoCopy: aBytes, count: 256, deallocator: .free)
|
||||
if randomStatus == 0 {
|
||||
self.contexts[internalId] = CallSessionContext(peerId: peerId, isOutgoing: true, isConference: conferenceCall != nil, type: isVideo ? .video : .audio, isVideoPossible: enableVideo || isVideo, pendingConference: conferenceCall, state: .requesting(a: a, conferenceCall: conferenceCall?.conference, disposable: (requestCallSession(postbox: self.postbox, network: self.network, peerId: peerId, a: a, maxLayer: self.maxLayer, versions: self.filteredVersions(enableVideo: true), isVideo: isVideo, conferenceCall: conferenceCall?.conference) |> deliverOn(queue)).start(next: { [weak self] result in
|
||||
self.contexts[internalId] = CallSessionContext(peerId: peerId, isOutgoing: true, isIncomingConference: false, type: isVideo ? .video : .audio, isVideoPossible: enableVideo || isVideo, pendingConference: conferenceCall, state: .requesting(a: a, conferenceCall: conferenceCall?.conference, disposable: (requestCallSession(postbox: self.postbox, network: self.network, peerId: peerId, a: a, maxLayer: self.maxLayer, versions: self.filteredVersions(enableVideo: true), isVideo: isVideo, conferenceCall: conferenceCall?.conference) |> deliverOn(queue)).start(next: { [weak self] result in
|
||||
if let strongSelf = self, let context = strongSelf.contexts[internalId] {
|
||||
if case .requesting = context.state {
|
||||
switch result {
|
||||
|
@ -187,7 +187,15 @@ func _internal_sendStarsReactionsInteractively(account: Account, messageId: Mess
|
||||
for attribute in attributes {
|
||||
if let attribute = attribute as? ReactionsMessageAttribute {
|
||||
if let myReaction = attribute.topPeers.first(where: { $0.isMy }) {
|
||||
resolvedPrivacy = myReaction.isAnonymous ? .anonymous : .default
|
||||
if myReaction.isAnonymous {
|
||||
resolvedPrivacy = .anonymous
|
||||
} else if myReaction.peerId == account.peerId {
|
||||
resolvedPrivacy = .default
|
||||
} else if let peerId = myReaction.peerId {
|
||||
resolvedPrivacy = .peer(peerId)
|
||||
} else {
|
||||
resolvedPrivacy = .anonymous
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -599,6 +599,6 @@ public final class PendingStarsReactionsMessageAttribute: MessageAttribute {
|
||||
encoder.encodeNil(forKey: "ap")
|
||||
}
|
||||
encoder.encodeInt32(self.count, forKey: "cnt")
|
||||
encoder.encodeCodable(self.privacy, forKey: "priv")
|
||||
encoder.encode(self.privacy, forKey: "priv")
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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 {
|
||||
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 {
|
||||
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)
|
||||
|
@ -822,6 +822,7 @@ private final class ChatSendStarsScreenComponent: Component {
|
||||
let context: AccountContext
|
||||
let peer: EnginePeer
|
||||
let myPeer: EnginePeer
|
||||
let defaultPrivacyPeer: ChatSendStarsScreenComponent.PrivacyPeer
|
||||
let channelsForPublicReaction: [EnginePeer]
|
||||
let messageId: EngineMessage.Id
|
||||
let maxAmount: Int
|
||||
@ -835,6 +836,7 @@ private final class ChatSendStarsScreenComponent: Component {
|
||||
context: AccountContext,
|
||||
peer: EnginePeer,
|
||||
myPeer: EnginePeer,
|
||||
defaultPrivacyPeer: ChatSendStarsScreenComponent.PrivacyPeer,
|
||||
channelsForPublicReaction: [EnginePeer],
|
||||
messageId: EngineMessage.Id,
|
||||
maxAmount: Int,
|
||||
@ -847,6 +849,7 @@ private final class ChatSendStarsScreenComponent: Component {
|
||||
self.context = context
|
||||
self.peer = peer
|
||||
self.myPeer = myPeer
|
||||
self.defaultPrivacyPeer = defaultPrivacyPeer
|
||||
self.channelsForPublicReaction = channelsForPublicReaction
|
||||
self.messageId = messageId
|
||||
self.maxAmount = maxAmount
|
||||
@ -985,7 +988,7 @@ private final class ChatSendStarsScreenComponent: Component {
|
||||
}
|
||||
}
|
||||
|
||||
private enum PrivacyPeer: Equatable {
|
||||
enum PrivacyPeer: Equatable {
|
||||
case account
|
||||
case anonymous
|
||||
case peer(EnginePeer)
|
||||
@ -1449,6 +1452,14 @@ private final class ChatSendStarsScreenComponent: Component {
|
||||
} else {
|
||||
self.privacyPeer = .account
|
||||
}
|
||||
} else {
|
||||
self.privacyPeer = component.defaultPrivacyPeer
|
||||
switch self.privacyPeer {
|
||||
case .anonymous, .account:
|
||||
break
|
||||
case let .peer(peer):
|
||||
self.currentMyPeer = peer
|
||||
}
|
||||
}
|
||||
|
||||
if let starsContext = component.context.starsContext {
|
||||
@ -2316,6 +2327,7 @@ public class ChatSendStarsScreen: ViewControllerComponentContainer {
|
||||
public final class InitialData {
|
||||
fileprivate let peer: EnginePeer
|
||||
fileprivate let myPeer: EnginePeer
|
||||
fileprivate let defaultPrivacyPeer: ChatSendStarsScreenComponent.PrivacyPeer
|
||||
fileprivate let channelsForPublicReaction: [EnginePeer]
|
||||
fileprivate let messageId: EngineMessage.Id
|
||||
fileprivate let balance: StarsAmount?
|
||||
@ -2326,6 +2338,7 @@ public class ChatSendStarsScreen: ViewControllerComponentContainer {
|
||||
fileprivate init(
|
||||
peer: EnginePeer,
|
||||
myPeer: EnginePeer,
|
||||
defaultPrivacyPeer: ChatSendStarsScreenComponent.PrivacyPeer,
|
||||
channelsForPublicReaction: [EnginePeer],
|
||||
messageId: EngineMessage.Id,
|
||||
balance: StarsAmount?,
|
||||
@ -2335,6 +2348,7 @@ public class ChatSendStarsScreen: ViewControllerComponentContainer {
|
||||
) {
|
||||
self.peer = peer
|
||||
self.myPeer = myPeer
|
||||
self.defaultPrivacyPeer = defaultPrivacyPeer
|
||||
self.channelsForPublicReaction = channelsForPublicReaction
|
||||
self.messageId = messageId
|
||||
self.balance = balance
|
||||
@ -2421,6 +2435,7 @@ public class ChatSendStarsScreen: ViewControllerComponentContainer {
|
||||
context: context,
|
||||
peer: initialData.peer,
|
||||
myPeer: initialData.myPeer,
|
||||
defaultPrivacyPeer: initialData.defaultPrivacyPeer,
|
||||
channelsForPublicReaction: initialData.channelsForPublicReaction,
|
||||
messageId: initialData.messageId,
|
||||
maxAmount: maxAmount,
|
||||
@ -2486,6 +2501,29 @@ public class ChatSendStarsScreen: ViewControllerComponentContainer {
|
||||
|
||||
let channelsForPublicReaction = context.engine.peers.channelsForPublicReaction(useLocalCache: true)
|
||||
|
||||
let defaultPrivacyPeer: Signal<ChatSendStarsScreenComponent.PrivacyPeer, NoError> = context.engine.data.get(
|
||||
TelegramEngine.EngineData.Item.Peer.StarsReactionDefaultPrivacy()
|
||||
)
|
||||
|> mapToSignal { defaultPrivacy -> Signal<ChatSendStarsScreenComponent.PrivacyPeer, NoError> in
|
||||
switch defaultPrivacy {
|
||||
case .anonymous:
|
||||
return .single(.anonymous)
|
||||
case .default:
|
||||
return .single(.account)
|
||||
case let .peer(peerId):
|
||||
return context.engine.data.get(
|
||||
TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)
|
||||
)
|
||||
|> map { peer -> ChatSendStarsScreenComponent.PrivacyPeer in
|
||||
if let peer {
|
||||
return .peer(peer)
|
||||
} else {
|
||||
return .anonymous
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return combineLatest(
|
||||
context.engine.data.get(
|
||||
TelegramEngine.EngineData.Item.Peer.Peer(id: peerId),
|
||||
@ -2493,9 +2531,10 @@ public class ChatSendStarsScreen: ViewControllerComponentContainer {
|
||||
EngineDataMap(allPeerIds.map(TelegramEngine.EngineData.Item.Peer.Peer.init(id:)))
|
||||
),
|
||||
balance,
|
||||
channelsForPublicReaction
|
||||
channelsForPublicReaction,
|
||||
defaultPrivacyPeer
|
||||
)
|
||||
|> map { peerAndTopPeerMap, balance, channelsForPublicReaction -> InitialData? in
|
||||
|> map { peerAndTopPeerMap, balance, channelsForPublicReaction, defaultPrivacyPeer -> InitialData? in
|
||||
let (peer, myPeer, topPeerMap) = peerAndTopPeerMap
|
||||
guard let peer, let myPeer else {
|
||||
return nil
|
||||
@ -2505,6 +2544,7 @@ public class ChatSendStarsScreen: ViewControllerComponentContainer {
|
||||
return InitialData(
|
||||
peer: peer,
|
||||
myPeer: myPeer,
|
||||
defaultPrivacyPeer: defaultPrivacyPeer,
|
||||
channelsForPublicReaction: channelsForPublicReaction,
|
||||
messageId: messageId,
|
||||
balance: balance,
|
||||
|
@ -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,8 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
||||
public var currentGroupCallController: ViewController? {
|
||||
return self.groupCallController
|
||||
}
|
||||
private let hasGroupCallOnScreenPromise = ValuePromise<Bool>(false, ignoreRepeated: true)
|
||||
private var hasGroupCallOnScreenValue: Bool = false
|
||||
private let hasGroupCallOnScreenPromise = Promise<Bool>(false)
|
||||
public var hasGroupCallOnScreen: Signal<Bool, NoError> {
|
||||
return self.hasGroupCallOnScreenPromise.get()
|
||||
}
|
||||
@ -805,8 +807,14 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
||||
guard let self else {
|
||||
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 +850,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 +865,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,168 +933,99 @@ 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 let call = call, let navigationController = mainWindow.viewController as? NavigationController {
|
||||
mainWindow.hostView.containerView.endEditing(true)
|
||||
if call.isStream {
|
||||
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 {
|
||||
strongSelf.hasGroupCallOnScreenPromise.set(true)
|
||||
let groupCallController = MediaStreamComponentController(call: call)
|
||||
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: self, accountContext: call.accountContext, call: .group(call), initialData: initialData, sourceCallController: nil)
|
||||
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
|
||||
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)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
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)
|
||||
|
||||
self.hasOngoingCall.set(true)
|
||||
} 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
|
||||
guard let strongSelf = self else {
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
if let callController = strongSelf.callController {
|
||||
if callController.isNodeLoaded {
|
||||
mainWindow.hostView.containerView.endEditing(true)
|
||||
if callController.view.superview == nil {
|
||||
if useFlatModalCallsPresentation(context: callController.call.context) {
|
||||
(mainWindow.viewController as? NavigationController)?.pushViewController(callController)
|
||||
} else {
|
||||
mainWindow.present(callController, on: .calls)
|
||||
}
|
||||
if let callController = self.callController {
|
||||
mainWindow.hostView.containerView.endEditing(true)
|
||||
if callController.view.superview == nil {
|
||||
if useFlatModalCallsPresentation(context: callController.call.context) {
|
||||
(mainWindow.viewController as? NavigationController)?.pushViewController(callController)
|
||||
} else {
|
||||
callController.expandFromPipIfPossible()
|
||||
mainWindow.present(callController, on: .calls)
|
||||
}
|
||||
} else {
|
||||
callController.expandFromPipIfPossible()
|
||||
}
|
||||
} else if let groupCallController = strongSelf.groupCallController {
|
||||
if groupCallController.isNodeLoaded {
|
||||
mainWindow.hostView.containerView.endEditing(true)
|
||||
if groupCallController.view.superview == nil {
|
||||
(mainWindow.viewController as? NavigationController)?.pushViewController(groupCallController)
|
||||
}
|
||||
} else if let groupCallController = self.groupCallController {
|
||||
mainWindow.hostView.containerView.endEditing(true)
|
||||
if groupCallController.view.superview == nil {
|
||||
(mainWindow.viewController as? NavigationController)?.pushViewController(groupCallController)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1236,8 +1168,291 @@ 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.currentCall = call
|
||||
|
||||
let beginDisplayingCallStatusBar = Promise<Void>()
|
||||
|
||||
var shouldResetGroupCallOnScreen = true
|
||||
|
||||
var transitioningToConferenceCallController: CallController?
|
||||
if let call, case let .group(groupCall) = call, case let .conferenceSource(conferenceSource) = groupCall, let callController = self.callController, callController.call === conferenceSource {
|
||||
transitioningToConferenceCallController = callController
|
||||
if callController.navigationPresentation != .flatModal {
|
||||
callController.dismissWithoutAnimation()
|
||||
}
|
||||
self.callController = nil
|
||||
shouldResetGroupCallOnScreen = false
|
||||
}
|
||||
|
||||
if let callController = self.callController {
|
||||
self.callController = nil
|
||||
callController.dismiss()
|
||||
}
|
||||
if let groupCallController = self.groupCallController {
|
||||
if case let .group(groupCall) = call, case let .group(groupCall) = groupCall, let conferenceSourceId = groupCall.conferenceSource {
|
||||
if case let .conferenceSource(conferenceSource) = groupCallController.call, conferenceSource.internalId == conferenceSourceId {
|
||||
groupCallController.updateCall(call: .group(groupCall))
|
||||
|
||||
self.updateInCallStatusBarData(hasGroupCallOnScreen: self.hasGroupCallOnScreenValue)
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
self.groupCallController = nil
|
||||
groupCallController.dismiss()
|
||||
}
|
||||
|
||||
if shouldResetGroupCallOnScreen {
|
||||
self.hasGroupCallOnScreenPromise.set(.single(false))
|
||||
}
|
||||
|
||||
self.callStateDisposable?.dispose()
|
||||
self.callStateDisposable = nil
|
||||
|
||||
if case let .call(call) = call {
|
||||
let callController = CallController(sharedContext: self, account: call.context.account, call: call, easyDebugAccess: !GlobalExperimentalSettings.isAppStoreBuild)
|
||||
self.callController = callController
|
||||
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
|
||||
}
|
||||
self.hasGroupCallOnScreenValue = hasGroupCallOnScreen
|
||||
self.updateInCallStatusBarData(hasGroupCallOnScreen: hasGroupCallOnScreen)
|
||||
})
|
||||
} else {
|
||||
self.hasGroupCallOnScreenValue = false
|
||||
self.currentCallStatusBarNode = nil
|
||||
if let navigationController = self.mainWindow?.viewController as? NavigationController {
|
||||
navigationController.setForceInCallStatusBar(nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func updateInCallStatusBarData(hasGroupCallOnScreen: Bool) {
|
||||
var statusBarContent: CallStatusBarNodeImpl.Content?
|
||||
if !hasGroupCallOnScreen, let currentCall = self.currentCall {
|
||||
switch currentCall {
|
||||
case let .call(call):
|
||||
statusBarContent = .call(self, call.context.account, call)
|
||||
case let .group(groupCall):
|
||||
switch groupCall {
|
||||
case let .conferenceSource(conferenceSource):
|
||||
statusBarContent = .call(self, conferenceSource.context.account, conferenceSource)
|
||||
case let .group(groupCall):
|
||||
statusBarContent = .groupCall(self, groupCall.account, groupCall)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var resolvedCallStatusBarNode: CallStatusBarNodeImpl?
|
||||
if let statusBarContent {
|
||||
if let current = self.currentCallStatusBarNode {
|
||||
resolvedCallStatusBarNode = current
|
||||
} else {
|
||||
resolvedCallStatusBarNode = CallStatusBarNodeImpl()
|
||||
self.currentCallStatusBarNode = resolvedCallStatusBarNode
|
||||
}
|
||||
resolvedCallStatusBarNode?.update(content: statusBarContent)
|
||||
} else {
|
||||
self.currentCallStatusBarNode = nil
|
||||
}
|
||||
|
||||
if let navigationController = self.mainWindow?.viewController as? NavigationController {
|
||||
navigationController.setForceInCallStatusBar(resolvedCallStatusBarNode)
|
||||
}
|
||||
}
|
||||
|
||||
private func presentControllerWithCurrentCall() {
|
||||
guard let call = self.call else {
|
||||
/*guard let call = self.call else {
|
||||
return
|
||||
}
|
||||
|
||||
@ -1360,7 +1575,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
||||
} else {
|
||||
self.mainWindow?.present(callController, on: .calls)
|
||||
}
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
||||
public func updateNotificationTokensRegistration() {
|
||||
|
@ -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
|
||||
|
||||
|
@ -1328,15 +1328,17 @@ public final class OngoingCallContext {
|
||||
self?.audioLevelPromise.set(.single(level))
|
||||
}
|
||||
|
||||
strongSelf.audioSessionActiveDisposable.set((audioSessionActive
|
||||
|> deliverOn(queue)).start(next: { isActive in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.withContext { context in
|
||||
context.nativeSetIsAudioSessionActive(isActive: isActive)
|
||||
}
|
||||
}))
|
||||
if audioDevice == nil {
|
||||
strongSelf.audioSessionActiveDisposable.set((audioSessionActive
|
||||
|> deliverOn(queue)).start(next: { isActive in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.withContext { context in
|
||||
context.nativeSetIsAudioSessionActive(isActive: isActive)
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
strongSelf.networkTypeDisposable = (updatedNetworkType
|
||||
|> deliverOn(queue)).start(next: { networkType in
|
||||
|
@ -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"
|
||||
|
@ -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;
|
||||
@ -1788,6 +2275,10 @@ isConference:(bool)isConference {
|
||||
auto encryptionKeyValue = std::make_shared<std::array<uint8_t, 256>>();
|
||||
memcpy(encryptionKeyValue->data(), encryptionKey.bytes, encryptionKey.length);
|
||||
|
||||
#if DEBUG
|
||||
NSLog(@"Encryption key: %@", [encryptionKey base64EncodedStringWithOptions:0]);
|
||||
#endif
|
||||
|
||||
mappedEncryptionKey = tgcalls::EncryptionKey(encryptionKeyValue, true);
|
||||
}
|
||||
|
||||
@ -2000,6 +2491,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
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"app": "11.7.1",
|
||||
"xcode": "16.0",
|
||||
"xcode": "16.2",
|
||||
"bazel": "7.3.1:981f82a470bad1349322b6f51c9c6ffa0aa291dab1014fac411543c12e661dff",
|
||||
"macos": "15.0"
|
||||
"macos": "15"
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user