Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios

This commit is contained in:
Ilya Laktyushin 2025-02-09 00:39:24 +04:00
commit 2dfc7e2b96
30 changed files with 1675 additions and 520 deletions

View File

@ -1 +1 @@
3a64b94cc76109006741756f85403c87
72ef725b9fffe38df9f94dfee99bb99b

View File

@ -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 }

View File

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

View File

@ -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)
}
}

View File

@ -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

View File

@ -77,12 +77,34 @@ private final class GlobalOverlayContainerParent: ASDisplayNode {
}
private final class NavigationControllerNode: ASDisplayNode {
private final class View: UIView {
private var scheduledWithLayout: (() -> Void)?
func schedule(layout f: @escaping () -> Void) {
self.scheduledWithLayout = f
self.setNeedsLayout()
}
override func layoutSubviews() {
super.layoutSubviews()
if let scheduledWithLayout = self.scheduledWithLayout {
self.scheduledWithLayout = nil
scheduledWithLayout()
}
}
}
private weak var controller: NavigationController?
init(controller: NavigationController) {
self.controller = controller
super.init()
self.setViewBlock({
return View(frame: CGRect())
})
}
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
@ -100,6 +122,10 @@ private final class NavigationControllerNode: ASDisplayNode {
}
return false
}
func schedule(layout f: @escaping () -> Void) {
(self.view as? View)?.schedule(layout: f)
}
}
public protocol NavigationControllerDropContentItem: AnyObject {
@ -376,6 +402,9 @@ open class NavigationController: UINavigationController, ContainableController,
self.loadView()
}
self.validLayout = layout
self.scheduledLayoutTransitionRequest = nil
self.updateContainers(layout: layout, transition: transition)
}
@ -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)
}
}
}

View File

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

View File

@ -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")

View File

@ -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)

View File

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

View File

@ -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))
}
}

View File

@ -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))
})
})
}

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

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

View File

@ -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 {

View File

@ -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
}
}
}
}

View File

@ -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")
}
}

View File

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

View File

@ -1241,8 +1241,8 @@ public final class PrivateCallScreen: OverlayMaskContainerView, AVPictureInPictu
let titleSize = self.titleView.update(
string: titleString,
fontSize: !havePrimaryVideo ? 28.0 : 17.0,
fontWeight: !havePrimaryVideo ? 0.0 : 0.25,
fontSize: 28.0,
fontWeight: 0.0,
color: .white,
constrainedWidth: params.size.width - 16.0 * 2.0,
transition: transition
@ -1301,7 +1301,11 @@ public final class PrivateCallScreen: OverlayMaskContainerView, AVPictureInPictu
if currentAreControlsHidden {
titleY = -8.0 - titleSize.height - statusSize.height
} else if havePrimaryVideo {
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)

View File

@ -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,

View File

@ -163,17 +163,18 @@ public final class SharedAccountContextImpl: SharedAccountContext {
public var callManager: PresentationCallManager?
let hasInAppPurchases: Bool
private var callDisposable: Disposable?
private var callStateDisposable: Disposable?
private(set) var currentCallStatusBarNode: CallStatusBarNodeImpl?
private var callDisposable: Disposable?
private var groupCallDisposable: Disposable?
private var callController: CallController?
private var call: PresentationCall?
private var currentCall: PresentationCurrentCall?
public let hasOngoingCall = ValuePromise<Bool>(false)
private let callState = Promise<PresentationCallState?>(nil)
private var awaitingCallConnectionDisposable: Disposable?
private var callPeerDisposable: Disposable?
private var callIsConferenceDisposable: Disposable?
@ -182,7 +183,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() {

View File

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

View File

@ -1328,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

View File

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

View File

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

View File

@ -1,6 +1,6 @@
{
"app": "11.7.1",
"xcode": "16.0",
"xcode": "16.2",
"bazel": "7.3.1:981f82a470bad1349322b6f51c9c6ffa0aa291dab1014fac411543c12e661dff",
"macos": "15.0"
"macos": "15"
}