Various improvements

This commit is contained in:
Isaac
2024-12-06 22:15:52 +08:00
parent 1abaeddfad
commit 4e964d4546
12 changed files with 570 additions and 1995 deletions

View File

@@ -57,14 +57,14 @@ public struct PresentationCallState: Equatable {
public enum VideoState: Equatable { public enum VideoState: Equatable {
case notAvailable case notAvailable
case inactive case inactive
case active(isScreencast: Bool) case active(isScreencast: Bool, endpointId: String)
case paused(isScreencast: Bool) case paused(isScreencast: Bool, endpointId: String)
} }
public enum RemoteVideoState: Equatable { public enum RemoteVideoState: Equatable {
case inactive case inactive
case active case active(endpointId: String)
case paused case paused(endpointId: String)
} }
public enum RemoteAudioState: Equatable { public enum RemoteAudioState: Equatable {

View File

@@ -105,7 +105,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
case disableReloginTokens(Bool) case disableReloginTokens(Bool)
case disableCallV2(Bool) case disableCallV2(Bool)
case experimentalCallMute(Bool) case experimentalCallMute(Bool)
case autoBenchmarkReflectors(Bool) case conferenceCalls(Bool)
case benchmarkReflectors case benchmarkReflectors
case enableLocalTranslation(Bool) case enableLocalTranslation(Bool)
case preferredVideoCodec(Int, String, String?, Bool) case preferredVideoCodec(Int, String, String?, Bool)
@@ -132,7 +132,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
return DebugControllerSection.web.rawValue return DebugControllerSection.web.rawValue
case .keepChatNavigationStack, .skipReadHistory, .dustEffect, .crashOnSlowQueries, .crashOnMemoryPressure: case .keepChatNavigationStack, .skipReadHistory, .dustEffect, .crashOnSlowQueries, .crashOnMemoryPressure:
return DebugControllerSection.experiments.rawValue return DebugControllerSection.experiments.rawValue
case .clearTips, .resetNotifications, .crash, .fillLocalSavedMessageCache, .resetDatabase, .resetDatabaseAndCache, .resetHoles, .resetTagHoles, .reindexUnread, .resetCacheIndex, .reindexCache, .resetBiometricsData, .optimizeDatabase, .photoPreview, .knockoutWallpaper, .storiesExperiment, .storiesJpegExperiment, .playlistPlayback, .enableQuickReactionSwitch, .experimentalCompatibility, .enableDebugDataDisplay, .rippleEffect, .browserExperiment, .localTranscription, .enableReactionOverrides, .restorePurchases, .disableReloginTokens, .disableCallV2, .experimentalCallMute, .autoBenchmarkReflectors, .benchmarkReflectors, .enableLocalTranslation: case .clearTips, .resetNotifications, .crash, .fillLocalSavedMessageCache, .resetDatabase, .resetDatabaseAndCache, .resetHoles, .resetTagHoles, .reindexUnread, .resetCacheIndex, .reindexCache, .resetBiometricsData, .optimizeDatabase, .photoPreview, .knockoutWallpaper, .storiesExperiment, .storiesJpegExperiment, .playlistPlayback, .enableQuickReactionSwitch, .experimentalCompatibility, .enableDebugDataDisplay, .rippleEffect, .browserExperiment, .localTranscription, .enableReactionOverrides, .restorePurchases, .disableReloginTokens, .disableCallV2, .experimentalCallMute, .conferenceCalls, .benchmarkReflectors, .enableLocalTranslation:
return DebugControllerSection.experiments.rawValue return DebugControllerSection.experiments.rawValue
case .logTranslationRecognition, .resetTranslationStates: case .logTranslationRecognition, .resetTranslationStates:
return DebugControllerSection.translation.rawValue return DebugControllerSection.translation.rawValue
@@ -249,7 +249,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
return 51 return 51
case .experimentalCallMute: case .experimentalCallMute:
return 52 return 52
case .autoBenchmarkReflectors: case .conferenceCalls:
return 53 return 53
case .benchmarkReflectors: case .benchmarkReflectors:
return 54 return 54
@@ -1345,12 +1345,12 @@ private enum DebugControllerEntry: ItemListNodeEntry {
}) })
}).start() }).start()
}) })
case let .autoBenchmarkReflectors(value): case let .conferenceCalls(value):
return ItemListSwitchItem(presentationData: presentationData, title: "Auto-Benchmark Reflectors [Restart]", value: value, sectionId: self.section, style: .blocks, updated: { value in return ItemListSwitchItem(presentationData: presentationData, title: "Conference [WIP]", value: value, sectionId: self.section, style: .blocks, updated: { value in
let _ = arguments.sharedContext.accountManager.transaction ({ transaction in let _ = arguments.sharedContext.accountManager.transaction ({ transaction in
transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in
var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings
settings.autoBenchmarkReflectors = value settings.conferenceCalls = value
return PreferencesEntry(settings) return PreferencesEntry(settings)
}) })
}).start() }).start()
@@ -1577,11 +1577,7 @@ private func debugControllerEntries(sharedContext: SharedAccountContext, present
entries.append(.disableCallV2(experimentalSettings.disableCallV2)) entries.append(.disableCallV2(experimentalSettings.disableCallV2))
entries.append(.experimentalCallMute(experimentalSettings.experimentalCallMute)) entries.append(.experimentalCallMute(experimentalSettings.experimentalCallMute))
var defaultAutoBenchmarkReflectors = false entries.append(.conferenceCalls(experimentalSettings.conferenceCalls))
if case .internal = sharedContext.applicationBindings.appBuildType {
defaultAutoBenchmarkReflectors = true
}
entries.append(.autoBenchmarkReflectors(experimentalSettings.autoBenchmarkReflectors ?? defaultAutoBenchmarkReflectors))
entries.append(.benchmarkReflectors) entries.append(.benchmarkReflectors)
entries.append(.enableLocalTranslation(experimentalSettings.enableLocalTranslation)) entries.append(.enableLocalTranslation(experimentalSettings.enableLocalTranslation))

View File

@@ -41,6 +41,193 @@ protocol CallControllerNodeProtocol: AnyObject {
} }
public final class CallController: ViewController { public final class CallController: ViewController {
public enum Call: Equatable {
case call(PresentationCall)
case groupCall(PresentationGroupCall)
public static func ==(lhs: Call, rhs: Call) -> Bool {
switch lhs {
case let .call(lhsCall):
if case let .call(rhsCall) = rhs {
return lhsCall === rhsCall
} else {
return false
}
case let .groupCall(lhsGroupCall):
if case let .groupCall(rhsGroupCall) = rhs {
return lhsGroupCall === rhsGroupCall
} else {
return false
}
}
}
public var context: AccountContext {
switch self {
case let .call(call):
return call.context
case let .groupCall(groupCall):
return groupCall.accountContext
}
}
public var peerId: EnginePeer.Id {
switch self {
case let .call(call):
return call.peerId
case let .groupCall(groupCall):
return groupCall.peerId
}
}
public func requestVideo() {
switch self {
case let .call(call):
call.requestVideo()
case let .groupCall(groupCall):
groupCall.requestVideo()
}
}
public func disableVideo() {
switch self {
case let .call(call):
call.disableVideo()
case let .groupCall(groupCall):
groupCall.disableVideo()
}
}
public func disableScreencast() {
switch self {
case let .call(call):
(call as? PresentationCallImpl)?.disableScreencast()
case let .groupCall(groupCall):
groupCall.disableScreencast()
}
}
public func switchVideoCamera() {
switch self {
case let .call(call):
call.switchVideoCamera()
case let .groupCall(groupCall):
groupCall.switchVideoCamera()
}
}
public func toggleIsMuted() {
switch self {
case let .call(call):
call.toggleIsMuted()
case let .groupCall(groupCall):
groupCall.toggleIsMuted()
}
}
public func setCurrentAudioOutput(_ output: AudioSessionOutput) {
switch self {
case let .call(call):
call.setCurrentAudioOutput(output)
case let .groupCall(groupCall):
groupCall.setCurrentAudioOutput(output)
}
}
public var isMuted: Signal<Bool, NoError> {
switch self {
case let .call(call):
return call.isMuted
case let .groupCall(groupCall):
return groupCall.isMuted
}
}
public var audioLevel: Signal<Float, NoError> {
switch self {
case let .call(call):
return call.audioLevel
case let .groupCall(groupCall):
var audioLevelId: UInt32?
return groupCall.audioLevels |> map { audioLevels -> Float in
var result: Float = 0
for item in audioLevels {
if let audioLevelId {
if item.1 == audioLevelId {
result = item.2
break
}
} else {
if item.1 != 0 {
audioLevelId = item.1
result = item.2
break
}
}
}
return result
}
}
}
public var isOutgoing: Bool {
switch self {
case let .call(call):
return call.isOutgoing
case .groupCall:
return false
}
}
public func makeOutgoingVideoView(completion: @escaping (PresentationCallVideoView?) -> Void) {
switch self {
case let .call(call):
call.makeOutgoingVideoView(completion: completion)
case let .groupCall(groupCall):
groupCall.makeOutgoingVideoView(requestClone: false, completion: { a, _ in
completion(a)
})
}
}
public var audioOutputState: Signal<([AudioSessionOutput], AudioSessionOutput?), NoError> {
switch self {
case let .call(call):
return call.audioOutputState
case let .groupCall(groupCall):
return groupCall.audioOutputState
}
}
public func debugInfo() -> Signal<(String, String), NoError> {
switch self {
case let .call(call):
return call.debugInfo()
case .groupCall:
return .single(("", ""))
}
}
public func answer() {
switch self {
case let .call(call):
call.answer()
case .groupCall:
break
}
}
public func hangUp() -> Signal<Bool, NoError> {
switch self {
case let .call(call):
return call.hangUp()
case let .groupCall(groupCall):
return groupCall.leave(terminateIfPossible: false)
}
}
}
private var controllerNode: CallControllerNodeProtocol { private var controllerNode: CallControllerNodeProtocol {
return self.displayNode as! CallControllerNodeProtocol return self.displayNode as! CallControllerNodeProtocol
} }
@@ -55,7 +242,7 @@ public final class CallController: ViewController {
private let sharedContext: SharedAccountContext private let sharedContext: SharedAccountContext
private let account: Account private let account: Account
public let call: PresentationCall public let call: CallController.Call
private let easyDebugAccess: Bool private let easyDebugAccess: Bool
private var presentationData: PresentationData private var presentationData: PresentationData
@@ -78,7 +265,10 @@ public final class CallController: ViewController {
public var restoreUIForPictureInPicture: ((@escaping (Bool) -> Void) -> Void)? public var restoreUIForPictureInPicture: ((@escaping (Bool) -> Void) -> Void)?
public init(sharedContext: SharedAccountContext, account: Account, call: PresentationCall, easyDebugAccess: Bool) { public var onViewDidAppear: (() -> Void)?
public var onViewDidDisappear: (() -> Void)?
public init(sharedContext: SharedAccountContext, account: Account, call: CallController.Call, easyDebugAccess: Bool) {
self.sharedContext = sharedContext self.sharedContext = sharedContext
self.account = account self.account = account
self.call = call self.call = call
@@ -103,10 +293,84 @@ public final class CallController: ViewController {
self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .portrait, compactSize: .portrait) self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .portrait, compactSize: .portrait)
self.disposable = (call.state switch call {
|> deliverOnMainQueue).start(next: { [weak self] callState in case let .call(call):
self?.callStateUpdated(callState) self.disposable = (call.state
}) |> deliverOnMainQueue).start(next: { [weak self] callState in
self?.callStateUpdated(callState)
})
case let .groupCall(groupCall):
let accountPeerId = groupCall.account.peerId
let videoEndpoints: Signal<(local: String?, remote: PresentationGroupCallRequestedVideo?), NoError> = groupCall.members
|> map { members -> (local: String?, remote: PresentationGroupCallRequestedVideo?) in
guard let members else {
return (nil, nil)
}
var local: String?
var remote: PresentationGroupCallRequestedVideo?
for participant in members.participants {
if let video = participant.requestedPresentationVideoChannel(minQuality: .thumbnail, maxQuality: .full) ?? participant.requestedVideoChannel(minQuality: .thumbnail, maxQuality: .full) {
if participant.peer.id == accountPeerId {
local = video.endpointId
} else {
if remote == nil {
remote = video
}
}
}
}
return (local, remote)
}
|> distinctUntilChanged(isEqual: { lhs, rhs in
return lhs == rhs
})
var startTimestamp: Double?
self.disposable = (combineLatest(queue: .mainQueue(),
groupCall.state,
videoEndpoints
)
|> deliverOnMainQueue).start(next: { [weak self] callState, videoEndpoints in
guard let self else {
return
}
let mappedState: PresentationCallState.State
switch callState.networkState {
case .connecting:
mappedState = .connecting(nil)
case .connected:
let timestamp = startTimestamp ?? CFAbsoluteTimeGetCurrent()
startTimestamp = timestamp
mappedState = .active(timestamp, nil, Data())
}
var mappedLocalVideoState: PresentationCallState.VideoState = .inactive
var mappedRemoteVideoState: PresentationCallState.RemoteVideoState = .inactive
if let local = videoEndpoints.local {
mappedLocalVideoState = .active(isScreencast: false, endpointId: local)
}
if let remote = videoEndpoints.remote {
mappedRemoteVideoState = .active(endpointId: remote.endpointId)
}
if case let .groupCall(groupCall) = self.call {
var requestedVideo: [PresentationGroupCallRequestedVideo] = []
if let remote = videoEndpoints.remote {
requestedVideo.append(remote)
}
groupCall.setRequestedVideoList(items: requestedVideo)
}
self.callStateUpdated(PresentationCallState(
state: mappedState,
videoState: mappedLocalVideoState,
remoteVideoState: mappedRemoteVideoState,
remoteAudioState: .active,
remoteBatteryLevel: .normal
))
})
}
self.callMutedDisposable = (call.isMuted self.callMutedDisposable = (call.isMuted
|> deliverOnMainQueue).start(next: { [weak self] value in |> deliverOnMainQueue).start(next: { [weak self] value in
@@ -148,26 +412,24 @@ public final class CallController: ViewController {
} }
override public func loadDisplayNode() { override public func loadDisplayNode() {
var useV2 = true let displayNode = CallControllerNodeV2(
if let data = self.call.context.currentAppConfiguration.with({ $0 }).data, let _ = data["ios_killswitch_disable_callui_v2"] { sharedContext: self.sharedContext,
useV2 = false account: self.account,
} presentationData: self.presentationData,
statusBar: self.statusBar,
debugInfo: self.call.debugInfo(),
easyDebugAccess: self.easyDebugAccess,
call: self.call
)
self.displayNode = displayNode
self.isContentsReady.set(displayNode.isReady.get())
if !useV2 { displayNode.restoreUIForPictureInPicture = { [weak self] completion in
self.displayNode = CallControllerNode(sharedContext: self.sharedContext, account: self.account, presentationData: self.presentationData, statusBar: self.statusBar, debugInfo: self.call.debugInfo(), shouldStayHiddenUntilConnection: !self.call.isOutgoing && self.call.isIntegratedWithCallKit, easyDebugAccess: self.easyDebugAccess, call: self.call) guard let self, let restoreUIForPictureInPicture = self.restoreUIForPictureInPicture else {
self.isContentsReady.set(.single(true)) completion(false)
} else { return
let displayNode = CallControllerNodeV2(sharedContext: self.sharedContext, account: self.account, presentationData: self.presentationData, statusBar: self.statusBar, debugInfo: self.call.debugInfo(), easyDebugAccess: self.easyDebugAccess, call: self.call)
self.displayNode = displayNode
self.isContentsReady.set(displayNode.isReady.get())
displayNode.restoreUIForPictureInPicture = { [weak self] completion in
guard let self, let restoreUIForPictureInPicture = self.restoreUIForPictureInPicture else {
completion(false)
return
}
restoreUIForPictureInPicture(completion)
} }
restoreUIForPictureInPicture(completion)
} }
self.displayNodeDidLoad() self.displayNodeDidLoad()
@@ -335,7 +597,11 @@ public final class CallController: ViewController {
self.presentingViewController?.dismiss(animated: false, completion: nil) self.presentingViewController?.dismiss(animated: false, completion: nil)
} }
self.peerDisposable = (combineLatest(self.account.postbox.peerView(id: self.account.peerId) |> take(1), self.account.postbox.peerView(id: self.call.peerId), self.sharedContext.activeAccountsWithInfo |> take(1)) self.peerDisposable = (combineLatest(queue: .mainQueue(),
self.account.postbox.peerView(id: self.account.peerId) |> take(1),
self.account.postbox.peerView(id: self.call.peerId),
self.sharedContext.activeAccountsWithInfo |> take(1)
)
|> deliverOnMainQueue).start(next: { [weak self] accountView, view, activeAccountsWithInfo in |> deliverOnMainQueue).start(next: { [weak self] accountView, view, activeAccountsWithInfo in
if let strongSelf = self { if let strongSelf = self {
if let accountPeer = accountView.peers[accountView.peerId], let peer = view.peers[view.peerId] { if let accountPeer = accountView.peers[accountView.peerId], let peer = view.peers[view.peerId] {
@@ -363,12 +629,16 @@ public final class CallController: ViewController {
} }
self.idleTimerExtensionDisposable.set(self.sharedContext.applicationBindings.pushIdleTimerExtension()) self.idleTimerExtensionDisposable.set(self.sharedContext.applicationBindings.pushIdleTimerExtension())
self.onViewDidAppear?()
} }
override public func viewDidDisappear(_ animated: Bool) { override public func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated) super.viewDidDisappear(animated)
self.idleTimerExtensionDisposable.set(nil) self.idleTimerExtensionDisposable.set(nil)
self.onViewDidDisappear?()
} }
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {

File diff suppressed because it is too large Load Diff

View File

@@ -32,7 +32,7 @@ final class CallControllerNodeV2: ViewControllerTracingNode, CallControllerNodeP
private let account: Account private let account: Account
private let presentationData: PresentationData private let presentationData: PresentationData
private let statusBar: StatusBar private let statusBar: StatusBar
private let call: PresentationCall private let call: CallController.Call
private let containerView: UIView private let containerView: UIView
private let callScreen: PrivateCallScreen private let callScreen: PrivateCallScreen
@@ -90,7 +90,7 @@ final class CallControllerNodeV2: ViewControllerTracingNode, CallControllerNodeP
statusBar: StatusBar, statusBar: StatusBar,
debugInfo: Signal<(String, String), NoError>, debugInfo: Signal<(String, String), NoError>,
easyDebugAccess: Bool, easyDebugAccess: Bool,
call: PresentationCall call: CallController.Call
) { ) {
self.sharedContext = sharedContext self.sharedContext = sharedContext
self.account = account self.account = account
@@ -172,9 +172,6 @@ final class CallControllerNodeV2: ViewControllerTracingNode, CallControllerNodeP
isRemoteBatteryLow: false, isRemoteBatteryLow: false,
isEnergySavingEnabled: !self.sharedContext.energyUsageSettings.fullTranslucency isEnergySavingEnabled: !self.sharedContext.energyUsageSettings.fullTranslucency
) )
if let peer = call.peer {
self.updatePeer(peer: peer)
}
self.isMicrophoneMutedDisposable = (call.isMuted self.isMicrophoneMutedDisposable = (call.isMuted
|> deliverOnMainQueue).startStrict(next: { [weak self] isMuted in |> deliverOnMainQueue).startStrict(next: { [weak self] isMuted in
@@ -308,9 +305,9 @@ final class CallControllerNodeV2: ViewControllerTracingNode, CallControllerNodeP
switch callState.state { switch callState.state {
case .active: case .active:
switch callState.videoState { switch callState.videoState {
case .active(let isScreencast), .paused(let isScreencast): case .active(let isScreencast, _), .paused(let isScreencast, _):
if isScreencast { if isScreencast {
(self.call as? PresentationCallImpl)?.disableScreencast() self.call.disableScreencast()
} else { } else {
self.call.disableVideo() self.call.disableVideo()
} }
@@ -489,12 +486,23 @@ final class CallControllerNodeV2: ViewControllerTracingNode, CallControllerNodeP
self.remoteVideo = nil self.remoteVideo = nil
default: default:
switch callState.videoState { switch callState.videoState {
case .active(let isScreencast), .paused(let isScreencast): case .active(let isScreencast, let endpointId), .paused(let isScreencast, let endpointId):
if isScreencast { if isScreencast {
self.localVideo = nil self.localVideo = nil
} else { } else {
if self.localVideo == nil, let call = self.call as? PresentationCallImpl, let videoStreamSignal = call.video(isIncoming: false) { if self.localVideo == nil {
self.localVideo = AdaptedCallVideoSource(videoStreamSignal: videoStreamSignal) switch self.call {
case let .call(call):
if let call = call as? PresentationCallImpl, let videoStreamSignal = call.video(isIncoming: false) {
self.localVideo = AdaptedCallVideoSource(videoStreamSignal: videoStreamSignal)
}
case let .groupCall(groupCall):
if let groupCall = groupCall as? PresentationGroupCallImpl {
if let videoStreamSignal = groupCall.video(endpointId: endpointId) {
self.localVideo = AdaptedCallVideoSource(videoStreamSignal: videoStreamSignal)
}
}
}
} }
} }
case .inactive, .notAvailable: case .inactive, .notAvailable:
@@ -502,9 +510,20 @@ final class CallControllerNodeV2: ViewControllerTracingNode, CallControllerNodeP
} }
switch callState.remoteVideoState { switch callState.remoteVideoState {
case .active, .paused: case .active(let endpointId), .paused(let endpointId):
if self.remoteVideo == nil, let call = self.call as? PresentationCallImpl, let videoStreamSignal = call.video(isIncoming: true) { if self.remoteVideo == nil {
self.remoteVideo = AdaptedCallVideoSource(videoStreamSignal: videoStreamSignal) switch self.call {
case let .call(call):
if let call = call as? PresentationCallImpl, let videoStreamSignal = call.video(isIncoming: true) {
self.remoteVideo = AdaptedCallVideoSource(videoStreamSignal: videoStreamSignal)
}
case let .groupCall(groupCall):
if let groupCall = groupCall as? PresentationGroupCallImpl {
if let videoStreamSignal = groupCall.video(endpointId: endpointId) {
self.remoteVideo = AdaptedCallVideoSource(videoStreamSignal: videoStreamSignal)
}
}
}
} }
case .inactive: case .inactive:
self.remoteVideo = nil self.remoteVideo = nil

View File

@@ -183,7 +183,7 @@ public final class PresentationCallImpl: PresentationCall {
self.isVideo = startWithVideo self.isVideo = startWithVideo
if self.isVideo { if self.isVideo {
self.videoCapturer = OngoingCallVideoCapturer() self.videoCapturer = OngoingCallVideoCapturer()
self.statePromise.set(PresentationCallState(state: isOutgoing ? .waiting : .ringing, videoState: .active(isScreencast: self.isScreencastActive), remoteVideoState: .inactive, remoteAudioState: .active, remoteBatteryLevel: .normal)) self.statePromise.set(PresentationCallState(state: isOutgoing ? .waiting : .ringing, videoState: .active(isScreencast: self.isScreencastActive, endpointId: ""), remoteVideoState: .inactive, remoteAudioState: .active, remoteBatteryLevel: .normal))
} else { } else {
self.statePromise.set(PresentationCallState(state: isOutgoing ? .waiting : .ringing, videoState: self.isVideoPossible ? .inactive : .notAvailable, remoteVideoState: .inactive, remoteAudioState: .active, remoteBatteryLevel: .normal)) self.statePromise.set(PresentationCallState(state: isOutgoing ? .waiting : .ringing, videoState: self.isVideoPossible ? .inactive : .notAvailable, remoteVideoState: .inactive, remoteAudioState: .active, remoteBatteryLevel: .normal))
} }
@@ -418,19 +418,19 @@ public final class PresentationCallImpl: PresentationCall {
case .notAvailable: case .notAvailable:
mappedVideoState = .notAvailable mappedVideoState = .notAvailable
case .active: case .active:
mappedVideoState = .active(isScreencast: self.isScreencastActive) mappedVideoState = .active(isScreencast: self.isScreencastActive, endpointId: "")
case .inactive: case .inactive:
mappedVideoState = .inactive mappedVideoState = .inactive
case .paused: case .paused:
mappedVideoState = .paused(isScreencast: self.isScreencastActive) mappedVideoState = .paused(isScreencast: self.isScreencastActive, endpointId: "")
} }
switch callContextState.remoteVideoState { switch callContextState.remoteVideoState {
case .inactive: case .inactive:
mappedRemoteVideoState = .inactive mappedRemoteVideoState = .inactive
case .active: case .active:
mappedRemoteVideoState = .active mappedRemoteVideoState = .active(endpointId: "")
case .paused: case .paused:
mappedRemoteVideoState = .paused mappedRemoteVideoState = .paused(endpointId: "")
} }
switch callContextState.remoteAudioState { switch callContextState.remoteAudioState {
case .active: case .active:
@@ -453,7 +453,7 @@ public final class PresentationCallImpl: PresentationCall {
mappedVideoState = previousVideoState mappedVideoState = previousVideoState
} else { } else {
if self.isVideo { if self.isVideo {
mappedVideoState = .active(isScreencast: self.isScreencastActive) mappedVideoState = .active(isScreencast: self.isScreencastActive, endpointId: "")
} else if self.isVideoPossible && sessionState.isVideoPossible { } else if self.isVideoPossible && sessionState.isVideoPossible {
mappedVideoState = .inactive mappedVideoState = .inactive
} else { } else {

View File

@@ -77,18 +77,18 @@ private final class FetchImpl {
let partRange: Range<Int64> let partRange: Range<Int64>
let fetchRange: Range<Int64> let fetchRange: Range<Int64>
let fetchedData: Data let fetchedData: Data
let decryptedData: Data let cleanData: Data
init( init(
partRange: Range<Int64>, partRange: Range<Int64>,
fetchRange: Range<Int64>, fetchRange: Range<Int64>,
fetchedData: Data, fetchedData: Data,
decryptedData: Data cleanData: Data
) { ) {
self.partRange = partRange self.partRange = partRange
self.fetchRange = fetchRange self.fetchRange = fetchRange
self.fetchedData = fetchedData self.fetchedData = fetchedData
self.decryptedData = decryptedData self.cleanData = cleanData
} }
} }
@@ -148,6 +148,48 @@ private final class FetchImpl {
case cdn(CdnData) case cdn(CdnData)
} }
private final class DecryptionState {
let aesKey: Data
var aesIv: Data
let decryptedSize: Int64
var offset: Int = 0
init(aesKey: Data, aesIv: Data, decryptedSize: Int64) {
self.aesKey = aesKey
self.aesIv = aesIv
self.decryptedSize = decryptedSize
}
func tryDecrypt(data: Data, offset: Int, loggingIdentifier: String) -> Data? {
if offset == self.offset {
var decryptedData = data
if self.decryptedSize == 0 {
Logger.shared.log("FetchV2", "\(loggingIdentifier): not decrypting part \(offset) ..< \(offset + data.count) (decryptedSize == 0)")
return nil
}
if decryptedData.count % 16 != 0 {
Logger.shared.log("FetchV2", "\(loggingIdentifier): not decrypting part \(offset) ..< \(offset + data.count) (decryptedData.count % 16 != 0)")
}
let decryptedDataCount = decryptedData.count
decryptedData.withUnsafeMutableBytes { rawBytes -> Void in
let bytes = rawBytes.baseAddress!.assumingMemoryBound(to: UInt8.self)
self.aesIv.withUnsafeMutableBytes { rawIv -> Void in
let iv = rawIv.baseAddress!.assumingMemoryBound(to: UInt8.self)
MTAesDecryptBytesInplaceAndModifyIv(bytes, decryptedDataCount, self.aesKey, iv)
}
}
if self.offset + decryptedData.count > self.decryptedSize {
decryptedData.count = Int(self.decryptedSize) - self.offset
}
self.offset += decryptedData.count
Logger.shared.log("FetchV2", "\(loggingIdentifier): decrypted part \(offset) ..< \(offset + data.count) (new offset is \(self.offset))")
return decryptedData
} else {
return nil
}
}
}
private final class FetchingState { private final class FetchingState {
let fetchLocation: FetchLocation let fetchLocation: FetchLocation
let partSize: Int64 let partSize: Int64
@@ -160,6 +202,7 @@ private final class FetchImpl {
var pendingParts: [PendingPart] = [] var pendingParts: [PendingPart] = []
var completedRanges = RangeSet<Int64>() var completedRanges = RangeSet<Int64>()
var decryptionState: DecryptionState?
var pendingReadyParts: [PendingReadyPart] = [] var pendingReadyParts: [PendingReadyPart] = []
var completedHashRanges = RangeSet<Int64>() var completedHashRanges = RangeSet<Int64>()
var pendingHashRanges: [PendingHashRange] = [] var pendingHashRanges: [PendingHashRange] = []
@@ -174,7 +217,8 @@ private final class FetchImpl {
maxPartSize: Int64, maxPartSize: Int64,
partAlignment: Int64, partAlignment: Int64,
partDivision: Int64, partDivision: Int64,
maxPendingParts: Int maxPendingParts: Int,
decryptionState: DecryptionState?
) { ) {
self.fetchLocation = fetchLocation self.fetchLocation = fetchLocation
self.partSize = partSize self.partSize = partSize
@@ -183,6 +227,7 @@ private final class FetchImpl {
self.partAlignment = partAlignment self.partAlignment = partAlignment
self.partDivision = partDivision self.partDivision = partDivision
self.maxPendingParts = maxPendingParts self.maxPendingParts = maxPendingParts
self.decryptionState = decryptionState
} }
deinit { deinit {
@@ -373,6 +418,12 @@ private final class FetchImpl {
if self.state == nil { if self.state == nil {
Logger.shared.log("FetchV2", "\(self.loggingIdentifier): initializing to .datacenter(\(self.datacenterId))") Logger.shared.log("FetchV2", "\(self.loggingIdentifier): initializing to .datacenter(\(self.datacenterId))")
var decryptionState: DecryptionState?
if let encryptionKey = self.encryptionKey, let decryptedSize = self.decryptedSize {
decryptionState = DecryptionState(aesKey: encryptionKey.aesKey, aesIv: encryptionKey.aesIv, decryptedSize: decryptedSize)
self.onNext(.reset)
}
self.state = .fetching(FetchingState( self.state = .fetching(FetchingState(
fetchLocation: .datacenter(self.datacenterId), fetchLocation: .datacenter(self.datacenterId),
partSize: self.defaultPartSize, partSize: self.defaultPartSize,
@@ -380,7 +431,8 @@ private final class FetchImpl {
maxPartSize: 1 * 1024 * 1024, maxPartSize: 1 * 1024 * 1024,
partAlignment: 4 * 1024, partAlignment: 4 * 1024,
partDivision: 1 * 1024 * 1024, partDivision: 1 * 1024 * 1024,
maxPendingParts: 6 maxPendingParts: 6,
decryptionState: decryptionState
)) ))
} }
guard let state = self.state else { guard let state = self.state else {
@@ -396,55 +448,75 @@ private final class FetchImpl {
do { do {
var removedPendingReadyPartIndices: [Int] = [] var removedPendingReadyPartIndices: [Int] = []
for i in 0 ..< state.pendingReadyParts.count { if let decryptionState = state.decryptionState {
let pendingReadyPart = state.pendingReadyParts[i] while true {
if state.completedHashRanges.isSuperset(of: RangeSet<Int64>(pendingReadyPart.fetchRange)) { var removedSomePendingReadyPart = false
removedPendingReadyPartIndices.append(i) for i in 0 ..< state.pendingReadyParts.count {
if removedPendingReadyPartIndices.contains(i) {
var checkOffset: Int64 = 0 continue
var checkFailed = false }
while checkOffset < pendingReadyPart.fetchedData.count { let pendingReadyPart = state.pendingReadyParts[i]
if let hashRange = state.hashRanges[pendingReadyPart.fetchRange.lowerBound + checkOffset] { if let resultData = decryptionState.tryDecrypt(data: pendingReadyPart.cleanData, offset: Int(pendingReadyPart.fetchRange.lowerBound), loggingIdentifier: self.loggingIdentifier) {
var clippedHashRange = hashRange.range removedPendingReadyPartIndices.append(i)
removedSomePendingReadyPart = true
if pendingReadyPart.fetchRange.lowerBound + Int64(pendingReadyPart.fetchedData.count) < clippedHashRange.lowerBound { self.commitPendingReadyPart(state: state, partRange: pendingReadyPart.partRange, fetchRange: pendingReadyPart.fetchRange, data: resultData)
Logger.shared.log("FetchV2", "\(self.loggingIdentifier): unable to check \(pendingReadyPart.fetchRange): data range \(clippedHashRange) out of bounds (0 ..< \(pendingReadyPart.fetchedData.count))")
checkFailed = true
break
}
clippedHashRange = clippedHashRange.lowerBound ..< min(clippedHashRange.upperBound, pendingReadyPart.fetchRange.lowerBound + Int64(pendingReadyPart.fetchedData.count))
let partLocalHashRange = (clippedHashRange.lowerBound - pendingReadyPart.fetchRange.lowerBound) ..< (clippedHashRange.upperBound - pendingReadyPart.fetchRange.lowerBound)
if partLocalHashRange.lowerBound < 0 || partLocalHashRange.upperBound > pendingReadyPart.fetchedData.count {
Logger.shared.log("FetchV2", "\(self.loggingIdentifier): unable to check \(pendingReadyPart.fetchRange): data range \(partLocalHashRange) out of bounds (0 ..< \(pendingReadyPart.fetchedData.count))")
checkFailed = true
break
}
let dataToHash = pendingReadyPart.decryptedData.subdata(in: Int(partLocalHashRange.lowerBound) ..< Int(partLocalHashRange.upperBound))
let localHash = MTSha256(dataToHash)
if localHash != hashRange.data {
Logger.shared.log("FetchV2", "\(self.loggingIdentifier): failed to verify \(pendingReadyPart.fetchRange): hash mismatch")
checkFailed = true
break
}
checkOffset += partLocalHashRange.upperBound - partLocalHashRange.lowerBound
} else {
Logger.shared.log("FetchV2", "\(self.loggingIdentifier): unable to find \(pendingReadyPart.fetchRange) hash range despite it being marked as ready")
checkFailed = true
break
} }
} }
if !checkFailed { if !removedSomePendingReadyPart {
self.commitPendingReadyPart(state: state, partRange: pendingReadyPart.partRange, fetchRange: pendingReadyPart.fetchRange, data: pendingReadyPart.decryptedData) break
} else { }
Logger.shared.log("FetchV2", "\(self.loggingIdentifier): unable to find \(pendingReadyPart.fetchRange) hash check failed") }
} else {
for i in 0 ..< state.pendingReadyParts.count {
let pendingReadyPart = state.pendingReadyParts[i]
if state.completedHashRanges.isSuperset(of: RangeSet<Int64>(pendingReadyPart.fetchRange)) {
removedPendingReadyPartIndices.append(i)
var checkOffset: Int64 = 0
var checkFailed = false
while checkOffset < pendingReadyPart.fetchedData.count {
if let hashRange = state.hashRanges[pendingReadyPart.fetchRange.lowerBound + checkOffset] {
var clippedHashRange = hashRange.range
if pendingReadyPart.fetchRange.lowerBound + Int64(pendingReadyPart.fetchedData.count) < clippedHashRange.lowerBound {
Logger.shared.log("FetchV2", "\(self.loggingIdentifier): unable to check \(pendingReadyPart.fetchRange): data range \(clippedHashRange) out of bounds (0 ..< \(pendingReadyPart.fetchedData.count))")
checkFailed = true
break
}
clippedHashRange = clippedHashRange.lowerBound ..< min(clippedHashRange.upperBound, pendingReadyPart.fetchRange.lowerBound + Int64(pendingReadyPart.fetchedData.count))
let partLocalHashRange = (clippedHashRange.lowerBound - pendingReadyPart.fetchRange.lowerBound) ..< (clippedHashRange.upperBound - pendingReadyPart.fetchRange.lowerBound)
if partLocalHashRange.lowerBound < 0 || partLocalHashRange.upperBound > pendingReadyPart.fetchedData.count {
Logger.shared.log("FetchV2", "\(self.loggingIdentifier): unable to check \(pendingReadyPart.fetchRange): data range \(partLocalHashRange) out of bounds (0 ..< \(pendingReadyPart.fetchedData.count))")
checkFailed = true
break
}
let dataToHash = pendingReadyPart.cleanData.subdata(in: Int(partLocalHashRange.lowerBound) ..< Int(partLocalHashRange.upperBound))
let localHash = MTSha256(dataToHash)
if localHash != hashRange.data {
Logger.shared.log("FetchV2", "\(self.loggingIdentifier): failed to verify \(pendingReadyPart.fetchRange): hash mismatch")
checkFailed = true
break
}
checkOffset += partLocalHashRange.upperBound - partLocalHashRange.lowerBound
} else {
Logger.shared.log("FetchV2", "\(self.loggingIdentifier): unable to find \(pendingReadyPart.fetchRange) hash range despite it being marked as ready")
checkFailed = true
break
}
}
if !checkFailed {
self.commitPendingReadyPart(state: state, partRange: pendingReadyPart.partRange, fetchRange: pendingReadyPart.fetchRange, data: pendingReadyPart.cleanData)
} else {
Logger.shared.log("FetchV2", "\(self.loggingIdentifier): unable to find \(pendingReadyPart.fetchRange) hash check failed")
}
} }
} }
} }
for index in removedPendingReadyPartIndices.reversed() { for index in removedPendingReadyPartIndices.sorted(by: >) {
state.pendingReadyParts.remove(at: index) state.pendingReadyParts.remove(at: index)
} }
} }
@@ -452,7 +524,9 @@ private final class FetchImpl {
var requiredHashRanges = RangeSet<Int64>() var requiredHashRanges = RangeSet<Int64>()
for pendingReadyPart in state.pendingReadyParts { for pendingReadyPart in state.pendingReadyParts {
//TODO:check if already have hashes //TODO:check if already have hashes
requiredHashRanges.formUnion(RangeSet<Int64>(pendingReadyPart.fetchRange)) if state.decryptionState == nil {
requiredHashRanges.formUnion(RangeSet<Int64>(pendingReadyPart.fetchRange))
}
} }
requiredHashRanges.subtract(state.completedHashRanges) requiredHashRanges.subtract(state.completedHashRanges)
for pendingHashRange in state.pendingHashRanges { for pendingHashRange in state.pendingHashRanges {
@@ -613,7 +687,8 @@ private final class FetchImpl {
maxPartSize: self.cdnPartSize * 2, maxPartSize: self.cdnPartSize * 2,
partAlignment: self.cdnPartSize, partAlignment: self.cdnPartSize,
partDivision: 1 * 1024 * 1024, partDivision: 1 * 1024 * 1024,
maxPendingParts: 6 maxPendingParts: 6,
decryptionState: nil
)) ))
self.update() self.update()
}, error: { [weak self] error in }, error: { [weak self] error in
@@ -661,7 +736,8 @@ private final class FetchImpl {
maxPartSize: self.defaultPartSize, maxPartSize: self.defaultPartSize,
partAlignment: 4 * 1024, partAlignment: 4 * 1024,
partDivision: 1 * 1024 * 1024, partDivision: 1 * 1024 * 1024,
maxPendingParts: 6 maxPendingParts: 6,
decryptionState: nil
)) ))
self.update() self.update()
@@ -819,7 +895,16 @@ private final class FetchImpl {
partRange: partRange, partRange: partRange,
fetchRange: fetchRange, fetchRange: fetchRange,
fetchedData: verifyPartHashData.fetchedData, fetchedData: verifyPartHashData.fetchedData,
decryptedData: data cleanData: data
))
} else if state.decryptionState != nil {
Logger.shared.log("FetchV2", "\(self.loggingIdentifier): stashing data part \(partRange) (aligned as \(fetchRange)) for decryption")
state.pendingReadyParts.append(FetchImpl.PendingReadyPart(
partRange: partRange,
fetchRange: fetchRange,
fetchedData: data,
cleanData: data
)) ))
} else { } else {
self.commitPendingReadyPart( self.commitPendingReadyPart(
@@ -837,7 +922,8 @@ private final class FetchImpl {
maxPartSize: self.cdnPartSize * 2, maxPartSize: self.cdnPartSize * 2,
partAlignment: self.cdnPartSize, partAlignment: self.cdnPartSize,
partDivision: 1 * 1024 * 1024, partDivision: 1 * 1024 * 1024,
maxPendingParts: 6 maxPendingParts: 6,
decryptionState: nil
)) ))
case let .cdnRefresh(cdnData, refreshToken): case let .cdnRefresh(cdnData, refreshToken):
self.state = .reuploadingToCdn(ReuploadingToCdnState( self.state = .reuploadingToCdn(ReuploadingToCdnState(

View File

@@ -1115,7 +1115,7 @@ func multipartFetch(
continueInBackground: Bool = false, continueInBackground: Bool = false,
useMainConnection: Bool = false useMainConnection: Bool = false
) -> Signal<MediaResourceDataFetchResult, MediaResourceDataFetchError> { ) -> Signal<MediaResourceDataFetchResult, MediaResourceDataFetchError> {
if network.useExperimentalFeatures, let _ = resource as? TelegramCloudMediaResource, !(resource is SecretFileMediaResource) { if network.useExperimentalFeatures, let _ = resource as? TelegramCloudMediaResource {
return multipartFetchV2( return multipartFetchV2(
accountPeerId: accountPeerId, accountPeerId: accountPeerId,
postbox: postbox, postbox: postbox,

View File

@@ -69,6 +69,7 @@ swift_library(
"//submodules/AppBundle", "//submodules/AppBundle",
"//submodules/UIKitRuntimeUtils", "//submodules/UIKitRuntimeUtils",
"//submodules/TelegramPresentationData", "//submodules/TelegramPresentationData",
"//submodules/Components/MultilineTextComponent",
], ],
visibility = [ visibility = [
"//visibility:public", "//visibility:public",

View File

@@ -317,6 +317,10 @@ public class ShareRootControllerImpl {
presentationDataPromise.set(.single(presentationData)) presentationDataPromise.set(.single(presentationData))
var immediatePeerId: PeerId? var immediatePeerId: PeerId?
#if DEBUG
// Xcode crashes
immediatePeerId = nil
#else
if #available(iOS 13.2, *), let sendMessageIntent = self.getExtensionContext()?.intent as? INSendMessageIntent { if #available(iOS 13.2, *), let sendMessageIntent = self.getExtensionContext()?.intent as? INSendMessageIntent {
if let contact = sendMessageIntent.recipients?.first, let handle = contact.customIdentifier, handle.hasPrefix("tg") { if let contact = sendMessageIntent.recipients?.first, let handle = contact.customIdentifier, handle.hasPrefix("tg") {
let string = handle.suffix(from: handle.index(handle.startIndex, offsetBy: 2)) let string = handle.suffix(from: handle.index(handle.startIndex, offsetBy: 2))
@@ -325,6 +329,7 @@ public class ShareRootControllerImpl {
} }
} }
} }
#endif
/*let account: Signal<(SharedAccountContextImpl, Account, [AccountWithInfo]), ShareAuthorizationError> = internalContext.sharedContext.accountManager.transaction { transaction -> (SharedAccountContextImpl, LoggingSettings) in /*let account: Signal<(SharedAccountContextImpl, Account, [AccountWithInfo]), ShareAuthorizationError> = internalContext.sharedContext.accountManager.transaction { transaction -> (SharedAccountContextImpl, LoggingSettings) in
return (internalContext.sharedContext, transaction.getSharedData(SharedDataKeys.loggingSettings)?.get(LoggingSettings.self) ?? LoggingSettings.defaultSettings) return (internalContext.sharedContext, transaction.getSharedData(SharedDataKeys.loggingSettings)?.get(LoggingSettings.self) ?? LoggingSettings.defaultSettings)

View File

@@ -885,48 +885,57 @@ public final class SharedAccountContextImpl: SharedAccountContext {
self.groupCallDisposable = (callManager.currentGroupCallSignal self.groupCallDisposable = (callManager.currentGroupCallSignal
|> deliverOnMainQueue).start(next: { [weak self] call in |> deliverOnMainQueue).start(next: { [weak self] call in
if let strongSelf = self { if let strongSelf = self {
if call !== strongSelf.groupCallController?.call { if strongSelf.immediateExperimentalUISettings.conferenceCalls {
strongSelf.groupCallController?.dismiss(closing: true, manual: false) let mappedCall = call.flatMap(CallController.Call.groupCall)
strongSelf.groupCallController = nil if mappedCall != strongSelf.callController?.call {
strongSelf.hasOngoingCall.set(false) strongSelf.callController?.dismiss()
strongSelf.callController = nil
if let call = call, let navigationController = mainWindow.viewController as? NavigationController { strongSelf.hasOngoingCall.set(false)
mainWindow.hostView.containerView.endEditing(true)
if call.isStream { if let call {
mainWindow.hostView.containerView.endEditing(true)
strongSelf.hasGroupCallOnScreenPromise.set(true) strongSelf.hasGroupCallOnScreenPromise.set(true)
let groupCallController = MediaStreamComponentController(call: call)
groupCallController.onViewDidAppear = { [weak self] in let callController = CallController(sharedContext: strongSelf, account: call.accountContext.account, call: .groupCall(call), easyDebugAccess: !GlobalExperimentalSettings.isAppStoreBuild)
if let strongSelf = self {
callController.onViewDidAppear = { [weak strongSelf] in
if let strongSelf {
strongSelf.hasGroupCallOnScreenPromise.set(true) strongSelf.hasGroupCallOnScreenPromise.set(true)
} }
} }
groupCallController.onViewDidDisappear = { [weak self] in callController.onViewDidDisappear = { [weak strongSelf] in
if let strongSelf = self { if let strongSelf {
strongSelf.hasGroupCallOnScreenPromise.set(false) strongSelf.hasGroupCallOnScreenPromise.set(false)
} }
} }
groupCallController.navigationPresentation = .flatModal strongSelf.callController = callController
groupCallController.parentNavigationController = navigationController strongSelf.mainWindow?.present(callController, on: .calls)
strongSelf.groupCallController = groupCallController
navigationController.pushViewController(groupCallController)
} else {
strongSelf.hasGroupCallOnScreenPromise.set(true)
let _ = (makeVoiceChatControllerInitialData(sharedContext: strongSelf, accountContext: call.accountContext, call: call) strongSelf.hasOngoingCall.set(true)
|> deliverOnMainQueue).start(next: { [weak strongSelf, weak navigationController] initialData in } else {
guard let strongSelf, let navigationController else { strongSelf.hasOngoingCall.set(false)
return }
} }
} else {
let groupCallController = makeVoiceChatController(sharedContext: strongSelf, accountContext: call.accountContext, call: call, initialData: initialData) if call !== strongSelf.groupCallController?.call {
groupCallController.onViewDidAppear = { [weak strongSelf] in strongSelf.groupCallController?.dismiss(closing: true, manual: false)
if let strongSelf { strongSelf.groupCallController = nil
strongSelf.hasOngoingCall.set(false)
if let call = call, let navigationController = mainWindow.viewController as? NavigationController {
mainWindow.hostView.containerView.endEditing(true)
if call.isStream {
strongSelf.hasGroupCallOnScreenPromise.set(true)
let groupCallController = MediaStreamComponentController(call: call)
groupCallController.onViewDidAppear = { [weak self] in
if let strongSelf = self {
strongSelf.hasGroupCallOnScreenPromise.set(true) strongSelf.hasGroupCallOnScreenPromise.set(true)
} }
} }
groupCallController.onViewDidDisappear = { [weak strongSelf] in groupCallController.onViewDidDisappear = { [weak self] in
if let strongSelf { if let strongSelf = self {
strongSelf.hasGroupCallOnScreenPromise.set(false) strongSelf.hasGroupCallOnScreenPromise.set(false)
} }
} }
@@ -934,12 +943,37 @@ public final class SharedAccountContextImpl: SharedAccountContext {
groupCallController.parentNavigationController = navigationController groupCallController.parentNavigationController = navigationController
strongSelf.groupCallController = groupCallController strongSelf.groupCallController = groupCallController
navigationController.pushViewController(groupCallController) navigationController.pushViewController(groupCallController)
}) } else {
} strongSelf.hasGroupCallOnScreenPromise.set(true)
let _ = (makeVoiceChatControllerInitialData(sharedContext: strongSelf, accountContext: call.accountContext, call: 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: call, initialData: initialData)
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) strongSelf.hasOngoingCall.set(true)
} else { } else {
strongSelf.hasOngoingCall.set(false) strongSelf.hasOngoingCall.set(false)
}
} }
} }
} }
@@ -1178,7 +1212,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
} }
if let currentCallController = self.callController { if let currentCallController = self.callController {
if currentCallController.call === call { if currentCallController.call == .call(call) {
self.navigateToCurrentCall() self.navigateToCurrentCall()
return return
} else { } else {
@@ -1188,7 +1222,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
} }
self.mainWindow?.hostView.containerView.endEditing(true) self.mainWindow?.hostView.containerView.endEditing(true)
let callController = CallController(sharedContext: self, account: call.context.account, call: call, easyDebugAccess: !GlobalExperimentalSettings.isAppStoreBuild) let callController = CallController(sharedContext: self, account: call.context.account, call: .call(call), easyDebugAccess: !GlobalExperimentalSettings.isAppStoreBuild)
self.callController = callController self.callController = callController
callController.restoreUIForPictureInPicture = { [weak self, weak callController] completion in callController.restoreUIForPictureInPicture = { [weak self, weak callController] completion in
guard let self, let callController else { guard let self, let callController else {

View File

@@ -62,6 +62,7 @@ public struct ExperimentalUISettings: Codable, Equatable {
public var dynamicStreaming: Bool public var dynamicStreaming: Bool
public var enableLocalTranslation: Bool public var enableLocalTranslation: Bool
public var autoBenchmarkReflectors: Bool? public var autoBenchmarkReflectors: Bool?
public var conferenceCalls: Bool
public static var defaultSettings: ExperimentalUISettings { public static var defaultSettings: ExperimentalUISettings {
return ExperimentalUISettings( return ExperimentalUISettings(
@@ -101,7 +102,8 @@ public struct ExperimentalUISettings: Codable, Equatable {
liveStreamV2: false, liveStreamV2: false,
dynamicStreaming: false, dynamicStreaming: false,
enableLocalTranslation: false, enableLocalTranslation: false,
autoBenchmarkReflectors: nil autoBenchmarkReflectors: nil,
conferenceCalls: false
) )
} }
@@ -142,7 +144,8 @@ public struct ExperimentalUISettings: Codable, Equatable {
liveStreamV2: Bool, liveStreamV2: Bool,
dynamicStreaming: Bool, dynamicStreaming: Bool,
enableLocalTranslation: Bool, enableLocalTranslation: Bool,
autoBenchmarkReflectors: Bool? autoBenchmarkReflectors: Bool?,
conferenceCalls: Bool
) { ) {
self.keepChatNavigationStack = keepChatNavigationStack self.keepChatNavigationStack = keepChatNavigationStack
self.skipReadHistory = skipReadHistory self.skipReadHistory = skipReadHistory
@@ -181,6 +184,7 @@ public struct ExperimentalUISettings: Codable, Equatable {
self.dynamicStreaming = dynamicStreaming self.dynamicStreaming = dynamicStreaming
self.enableLocalTranslation = enableLocalTranslation self.enableLocalTranslation = enableLocalTranslation
self.autoBenchmarkReflectors = autoBenchmarkReflectors self.autoBenchmarkReflectors = autoBenchmarkReflectors
self.conferenceCalls = conferenceCalls
} }
public init(from decoder: Decoder) throws { public init(from decoder: Decoder) throws {
@@ -223,6 +227,7 @@ public struct ExperimentalUISettings: Codable, Equatable {
self.dynamicStreaming = try container.decodeIfPresent(Bool.self, forKey: "dynamicStreaming_v2") ?? false self.dynamicStreaming = try container.decodeIfPresent(Bool.self, forKey: "dynamicStreaming_v2") ?? false
self.enableLocalTranslation = try container.decodeIfPresent(Bool.self, forKey: "enableLocalTranslation") ?? false self.enableLocalTranslation = try container.decodeIfPresent(Bool.self, forKey: "enableLocalTranslation") ?? false
self.autoBenchmarkReflectors = try container.decodeIfPresent(Bool.self, forKey: "autoBenchmarkReflectors") self.autoBenchmarkReflectors = try container.decodeIfPresent(Bool.self, forKey: "autoBenchmarkReflectors")
self.conferenceCalls = try container.decodeIfPresent(Bool.self, forKey: "conferenceCalls") ?? false
} }
public func encode(to encoder: Encoder) throws { public func encode(to encoder: Encoder) throws {
@@ -265,6 +270,7 @@ public struct ExperimentalUISettings: Codable, Equatable {
try container.encode(self.dynamicStreaming, forKey: "dynamicStreaming") try container.encode(self.dynamicStreaming, forKey: "dynamicStreaming")
try container.encode(self.enableLocalTranslation, forKey: "enableLocalTranslation") try container.encode(self.enableLocalTranslation, forKey: "enableLocalTranslation")
try container.encodeIfPresent(self.autoBenchmarkReflectors, forKey: "autoBenchmarkReflectors") try container.encodeIfPresent(self.autoBenchmarkReflectors, forKey: "autoBenchmarkReflectors")
try container.encodeIfPresent(self.conferenceCalls, forKey: "conferenceCalls")
} }
} }