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

This commit is contained in:
Ilya Laktyushin 2020-08-07 21:09:36 +03:00
commit 575d98dd1f
21 changed files with 3845 additions and 3778 deletions

View File

@ -4,7 +4,7 @@
"NSLocationWhenInUseUsageDescription" = "When you send your location to your friends, Telegram needs access to show them a map."; "NSLocationWhenInUseUsageDescription" = "When you send your location to your friends, Telegram needs access to show them a map.";
"NSLocationAlwaysAndWhenInUseUsageDescription" = "When you choose to share your Live Location with friends in a chat, Telegram needs background access to your location to keep them updated for the duration of the live sharing."; "NSLocationAlwaysAndWhenInUseUsageDescription" = "When you choose to share your Live Location with friends in a chat, Telegram needs background access to your location to keep them updated for the duration of the live sharing.";
"NSLocationAlwaysUsageDescription" = "When you choose to share your live location with friends in a chat, Telegram needs background access to your location to keep them updated for the duration of the live sharing. You also need this to send locations from an Apple Watch."; "NSLocationAlwaysUsageDescription" = "When you choose to share your live location with friends in a chat, Telegram needs background access to your location to keep them updated for the duration of the live sharing. You also need this to send locations from an Apple Watch.";
"NSCameraUsageDescription" = "We need this so that you can take and share photos and videos."; "NSCameraUsageDescription" = "We need this so that you can take and share photos and videos, as well as make video calls.";
"NSPhotoLibraryUsageDescription" = "We need this so that you can share photos and videos from your photo library."; "NSPhotoLibraryUsageDescription" = "We need this so that you can share photos and videos from your photo library.";
"NSPhotoLibraryAddUsageDescription" = "We need this so that you can save photos and videos to your photo library."; "NSPhotoLibraryAddUsageDescription" = "We need this so that you can save photos and videos to your photo library.";
"NSMicrophoneUsageDescription" = "We need this so that you can record and share voice messages and videos with sound."; "NSMicrophoneUsageDescription" = "We need this so that you can record and share voice messages and videos with sound.";

View File

@ -215,7 +215,9 @@
"PUSH_AUTH_REGION" = "New login|from unrecognized device %1$@, location: %2$@"; "PUSH_AUTH_REGION" = "New login|from unrecognized device %1$@, location: %2$@";
"PUSH_PHONE_CALL_REQUEST" = "%1$@|is calling you!"; "PUSH_PHONE_CALL_REQUEST" = "%1$@|is calling you!";
"PUSH_VIDEO_CALL_REQUEST" = "%1$@|is calling you!";
"PUSH_PHONE_CALL_MISSED" = "%1$@|You missed a call"; "PUSH_PHONE_CALL_MISSED" = "%1$@|You missed a call";
"PUSH_VIDEO_CALL_MISSED" = "%1$@|You missed a video call";
"PUSH_MESSAGE_GAME_SCORE" = "%1$@ scored %3$@ in game %2$@"; "PUSH_MESSAGE_GAME_SCORE" = "%1$@ scored %3$@ in game %2$@";
"PUSH_MESSAGE_VIDEOS" = "%1$@ sent you %2$@ videos"; "PUSH_MESSAGE_VIDEOS" = "%1$@ sent you %2$@ videos";
@ -2472,6 +2474,7 @@ Unused sets are archived when you add more.";
"Call.CallInProgressTitle" = "Call in Progress"; "Call.CallInProgressTitle" = "Call in Progress";
"Call.CallInProgressMessage" = "Finish call with %1$@ and start a new one with %2$@?"; "Call.CallInProgressMessage" = "Finish call with %1$@ and start a new one with %2$@?";
"Call.ExternalCallInProgressMessage" = "Please finish the current call first.";
"Call.Message" = "Message"; "Call.Message" = "Message";
@ -3025,7 +3028,7 @@ Unused sets are archived when you add more.";
"InfoPlist.NSContactsUsageDescription" = "Telegram will continuously upload your contacts to its heavily encrypted cloud servers to let you connect with your friends across all your devices."; "InfoPlist.NSContactsUsageDescription" = "Telegram will continuously upload your contacts to its heavily encrypted cloud servers to let you connect with your friends across all your devices.";
"InfoPlist.NSLocationWhenInUseUsageDescription" = "When you send your location to your friends, Telegram needs access to show them a map."; "InfoPlist.NSLocationWhenInUseUsageDescription" = "When you send your location to your friends, Telegram needs access to show them a map.";
"InfoPlist.NSCameraUsageDescription" = "We need this so that you can take and share photos and videos."; "InfoPlist.NSCameraUsageDescription" = "We need this so that you can take and share photos and videos, as well as make video calls.";
"InfoPlist.NSPhotoLibraryUsageDescription" = "We need this so that you can share photos and videos from your photo library."; "InfoPlist.NSPhotoLibraryUsageDescription" = "We need this so that you can share photos and videos from your photo library.";
"InfoPlist.NSPhotoLibraryAddUsageDescription" = "We need this so that you can save photos and videos to your photo library."; "InfoPlist.NSPhotoLibraryAddUsageDescription" = "We need this so that you can save photos and videos to your photo library.";
"InfoPlist.NSMicrophoneUsageDescription" = "We need this so that you can record and share voice messages and videos with sound."; "InfoPlist.NSMicrophoneUsageDescription" = "We need this so that you can record and share voice messages and videos with sound.";

View File

@ -8,7 +8,7 @@ import TelegramAudio
public enum RequestCallResult { public enum RequestCallResult {
case requested case requested
case alreadyInProgress(PeerId) case alreadyInProgress(PeerId?)
} }
public struct CallAuxiliaryServer { public struct CallAuxiliaryServer {
@ -46,15 +46,20 @@ public struct PresentationCallState: Equatable {
public enum VideoState: Equatable { public enum VideoState: Equatable {
case notAvailable case notAvailable
case possible case inactive
case outgoingRequested
case incomingRequested(sendsVideo: Bool)
case active case active
case paused
} }
public enum RemoteVideoState: Equatable { public enum RemoteVideoState: Equatable {
case inactive case inactive
case active case active
case paused
}
public enum RemoteAudioState: Equatable {
case active
case muted
} }
public enum RemoteBatteryLevel: Equatable { public enum RemoteBatteryLevel: Equatable {
@ -65,12 +70,14 @@ public struct PresentationCallState: Equatable {
public var state: State public var state: State
public var videoState: VideoState public var videoState: VideoState
public var remoteVideoState: RemoteVideoState public var remoteVideoState: RemoteVideoState
public var remoteAudioState: RemoteAudioState
public var remoteBatteryLevel: RemoteBatteryLevel public var remoteBatteryLevel: RemoteBatteryLevel
public init(state: State, videoState: VideoState, remoteVideoState: RemoteVideoState, remoteBatteryLevel: RemoteBatteryLevel) { public init(state: State, videoState: VideoState, remoteVideoState: RemoteVideoState, remoteAudioState: RemoteAudioState, remoteBatteryLevel: RemoteBatteryLevel) {
self.state = state self.state = state
self.videoState = videoState self.videoState = videoState
self.remoteVideoState = remoteVideoState self.remoteVideoState = remoteVideoState
self.remoteAudioState = remoteAudioState
self.remoteBatteryLevel = remoteBatteryLevel self.remoteBatteryLevel = remoteBatteryLevel
} }
} }
@ -130,7 +137,7 @@ public protocol PresentationCall: class {
func toggleIsMuted() func toggleIsMuted()
func setIsMuted(_ value: Bool) func setIsMuted(_ value: Bool)
func requestVideo() func requestVideo()
func acceptVideo() func disableVideo()
func setOutgoingVideoIsPaused(_ isPaused: Bool) func setOutgoingVideoIsPaused(_ isPaused: Bool)
func switchVideoCamera() func switchVideoCamera()
func setCurrentAudioOutput(_ output: AudioSessionOutput) func setCurrentAudioOutput(_ output: AudioSessionOutput)

View File

@ -285,15 +285,20 @@ public final class CallListController: ViewController {
} else { } else {
let presentationData = strongSelf.presentationData let presentationData = strongSelf.presentationData
let _ = (strongSelf.context.account.postbox.transaction { transaction -> (Peer?, Peer?) in let _ = (strongSelf.context.account.postbox.transaction { transaction -> (Peer?, Peer?) in
return (transaction.getPeer(peerId), transaction.getPeer(currentPeerId)) return (transaction.getPeer(peerId), currentPeerId.flatMap(transaction.getPeer))
} |> deliverOnMainQueue).start(next: { [weak self] peer, current in } |> deliverOnMainQueue).start(next: { [weak self] peer, current in
if let strongSelf = self, let peer = peer, let current = current { if let strongSelf = self, let peer = peer {
if let current = current {
strongSelf.present(textAlertController(context: strongSelf.context, title: presentationData.strings.Call_CallInProgressTitle, text: presentationData.strings.Call_CallInProgressMessage(current.compactDisplayTitle, peer.compactDisplayTitle).0, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .genericAction, title: presentationData.strings.Common_OK, action: { strongSelf.present(textAlertController(context: strongSelf.context, title: presentationData.strings.Call_CallInProgressTitle, text: presentationData.strings.Call_CallInProgressMessage(current.compactDisplayTitle, peer.compactDisplayTitle).0, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .genericAction, title: presentationData.strings.Common_OK, action: {
if let strongSelf = self { if let strongSelf = self {
let _ = strongSelf.context.sharedContext.callManager?.requestCall(context: strongSelf.context, peerId: peerId, isVideo: isVideo, endCurrentIfAny: true) let _ = strongSelf.context.sharedContext.callManager?.requestCall(context: strongSelf.context, peerId: peerId, isVideo: isVideo, endCurrentIfAny: true)
began?() began?()
} }
})]), in: .window(.root)) })]), in: .window(.root))
} else {
strongSelf.present(textAlertController(context: strongSelf.context, title: presentationData.strings.Call_CallInProgressTitle, text: presentationData.strings.Call_ExternalCallInProgressMessage, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_OK, action: {
})]), in: .window(.root))
}
} }
}) })
} }

View File

@ -130,6 +130,21 @@ func contactContextMenuItems(context: AccountContext, peerId: PeerId, contactsCo
} else { } else {
let presentationData = context.sharedContext.currentPresentationData.with { $0 } let presentationData = context.sharedContext.currentPresentationData.with { $0 }
let _ = (context.account.postbox.transaction { transaction -> (Peer?, Peer?) in let _ = (context.account.postbox.transaction { transaction -> (Peer?, Peer?) in
return (transaction.getPeer(peerId), currentPeerId.flatMap(transaction.getPeer))
} |> deliverOnMainQueue).start(next: { [weak contactsController] peer, current in
if let contactsController = contactsController, let peer = peer {
if let current = current {
contactsController.present(textAlertController(context: context, title: presentationData.strings.Call_CallInProgressTitle, text: presentationData.strings.Call_CallInProgressMessage(current.compactDisplayTitle, peer.compactDisplayTitle).0, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .genericAction, title: presentationData.strings.Common_OK, action: {
let _ = context.sharedContext.callManager?.requestCall(context: context, peerId: peerId, isVideo: false, endCurrentIfAny: true)
})]), in: .window(.root))
} else {
contactsController.present(textAlertController(context: context, title: presentationData.strings.Call_CallInProgressTitle, text: presentationData.strings.Call_ExternalCallInProgressMessage, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_OK, action: {
})]), in: .window(.root))
}
}
})
/*let _ = (context.account.postbox.transaction { transaction -> (Peer?, Peer?) in
return (transaction.getPeer(peerId), transaction.getPeer(currentPeerId)) return (transaction.getPeer(peerId), transaction.getPeer(currentPeerId))
} }
|> deliverOnMainQueue).start(next: { [weak contactsController] peer, current in |> deliverOnMainQueue).start(next: { [weak contactsController] peer, current in
@ -138,7 +153,7 @@ func contactContextMenuItems(context: AccountContext, peerId: PeerId, contactsCo
let _ = context.sharedContext.callManager?.requestCall(context: context, peerId: peerId, isVideo: false, endCurrentIfAny: true) let _ = context.sharedContext.callManager?.requestCall(context: context, peerId: peerId, isVideo: false, endCurrentIfAny: true)
})]), in: .window(.root)) })]), in: .window(.root))
} }
}) })*/
} }
} }
} }
@ -155,6 +170,21 @@ func contactContextMenuItems(context: AccountContext, peerId: PeerId, contactsCo
} else { } else {
let presentationData = context.sharedContext.currentPresentationData.with { $0 } let presentationData = context.sharedContext.currentPresentationData.with { $0 }
let _ = (context.account.postbox.transaction { transaction -> (Peer?, Peer?) in let _ = (context.account.postbox.transaction { transaction -> (Peer?, Peer?) in
return (transaction.getPeer(peerId), currentPeerId.flatMap(transaction.getPeer))
} |> deliverOnMainQueue).start(next: { [weak contactsController] peer, current in
if let contactsController = contactsController, let peer = peer {
if let current = current {
contactsController.present(textAlertController(context: context, title: presentationData.strings.Call_CallInProgressTitle, text: presentationData.strings.Call_CallInProgressMessage(current.compactDisplayTitle, peer.compactDisplayTitle).0, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .genericAction, title: presentationData.strings.Common_OK, action: {
let _ = context.sharedContext.callManager?.requestCall(context: context, peerId: peerId, isVideo: true, endCurrentIfAny: true)
})]), in: .window(.root))
} else {
contactsController.present(textAlertController(context: context, title: presentationData.strings.Call_CallInProgressTitle, text: presentationData.strings.Call_ExternalCallInProgressMessage, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_OK, action: {
})]), in: .window(.root))
}
}
})
/*let _ = (context.account.postbox.transaction { transaction -> (Peer?, Peer?) in
return (transaction.getPeer(peerId), transaction.getPeer(currentPeerId)) return (transaction.getPeer(peerId), transaction.getPeer(currentPeerId))
} }
|> deliverOnMainQueue).start(next: { [weak contactsController] peer, current in |> deliverOnMainQueue).start(next: { [weak contactsController] peer, current in
@ -163,7 +193,7 @@ func contactContextMenuItems(context: AccountContext, peerId: PeerId, contactsCo
let _ = context.sharedContext.callManager?.requestCall(context: context, peerId: peerId, isVideo: true, endCurrentIfAny: true) let _ = context.sharedContext.callManager?.requestCall(context: context, peerId: peerId, isVideo: true, endCurrentIfAny: true)
})]), in: .window(.root)) })]), in: .window(.root))
} }
}) })*/
} }
} }
} }

View File

@ -884,12 +884,17 @@ public func deviceContactInfoController(context: AccountContext, subject: Device
} else { } else {
let presentationData = context.sharedContext.currentPresentationData.with { $0 } let presentationData = context.sharedContext.currentPresentationData.with { $0 }
let _ = (context.account.postbox.transaction { transaction -> (Peer?, Peer?) in let _ = (context.account.postbox.transaction { transaction -> (Peer?, Peer?) in
return (transaction.getPeer(user.id), transaction.getPeer(currentPeerId)) return (transaction.getPeer(user.id), currentPeerId.flatMap(transaction.getPeer))
} |> deliverOnMainQueue).start(next: { peer, current in } |> deliverOnMainQueue).start(next: { peer, current in
if let peer = peer, let current = current { if let peer = peer {
if let current = current {
presentControllerImpl?(textAlertController(context: context, title: presentationData.strings.Call_CallInProgressTitle, text: presentationData.strings.Call_CallInProgressMessage(current.compactDisplayTitle, peer.compactDisplayTitle).0, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .genericAction, title: presentationData.strings.Common_OK, action: { presentControllerImpl?(textAlertController(context: context, title: presentationData.strings.Call_CallInProgressTitle, text: presentationData.strings.Call_CallInProgressMessage(current.compactDisplayTitle, peer.compactDisplayTitle).0, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .genericAction, title: presentationData.strings.Common_OK, action: {
let _ = context.sharedContext.callManager?.requestCall(context: context, peerId: peer.id, isVideo: false, endCurrentIfAny: true) let _ = context.sharedContext.callManager?.requestCall(context: context, peerId: user.id, isVideo: false, endCurrentIfAny: true)
})]), nil) })]), nil)
} else {
presentControllerImpl?(textAlertController(context: context, title: presentationData.strings.Call_CallInProgressTitle, text: presentationData.strings.Call_ExternalCallInProgressMessage, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_OK, action: {
})]), nil)
}
} }
}) })
} }

View File

@ -880,7 +880,7 @@ public func userInfoController(context: AccountContext, peerId: PeerId, mode: Pe
} else { } else {
let presentationData = context.sharedContext.currentPresentationData.with { $0 } let presentationData = context.sharedContext.currentPresentationData.with { $0 }
let _ = (context.account.postbox.transaction { transaction -> (Peer?, Peer?) in let _ = (context.account.postbox.transaction { transaction -> (Peer?, Peer?) in
return (transaction.getPeer(peer.id), transaction.getPeer(currentPeerId)) return (transaction.getPeer(peer.id), currentPeerId.flatMap(transaction.getPeer))
} |> deliverOnMainQueue).start(next: { peer, current in } |> deliverOnMainQueue).start(next: { peer, current in
if let peer = peer, let current = current { if let peer = peer, let current = current {
presentControllerImpl?(textAlertController(context: context, title: presentationData.strings.Call_CallInProgressTitle, text: presentationData.strings.Call_CallInProgressMessage(current.compactDisplayTitle, peer.compactDisplayTitle).0, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .genericAction, title: presentationData.strings.Common_OK, action: { presentControllerImpl?(textAlertController(context: context, title: presentationData.strings.Call_CallInProgressTitle, text: presentationData.strings.Call_CallInProgressMessage(current.compactDisplayTitle, peer.compactDisplayTitle).0, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .genericAction, title: presentationData.strings.Common_OK, action: {

View File

@ -22,7 +22,6 @@ protocol CallControllerNodeProtocol: class {
var beginAudioOuputSelection: ((Bool) -> Void)? { get set } var beginAudioOuputSelection: ((Bool) -> Void)? { get set }
var acceptCall: (() -> Void)? { get set } var acceptCall: (() -> Void)? { get set }
var endCall: (() -> Void)? { get set } var endCall: (() -> Void)? { get set }
var setIsVideoPaused: ((Bool) -> Void)? { get set }
var back: (() -> Void)? { get set } var back: (() -> Void)? { get set }
var presentCallRating: ((CallId) -> Void)? { get set } var presentCallRating: ((CallId) -> Void)? { get set }
var present: ((ViewController) -> Void)? { get set } var present: ((ViewController) -> Void)? { get set }
@ -222,10 +221,6 @@ public final class CallController: ViewController {
let _ = self?.call.hangUp() let _ = self?.call.hangUp()
} }
self.controllerNode.setIsVideoPaused = { [weak self] isPaused in
self?.call.setOutgoingVideoIsPaused(isPaused)
}
self.controllerNode.back = { [weak self] in self.controllerNode.back = { [weak self] in
let _ = self?.dismiss() let _ = self?.dismiss()
} }

View File

@ -21,12 +21,12 @@ enum CallControllerButtonsSpeakerMode: Equatable {
} }
enum CallControllerButtonsMode: Equatable { enum CallControllerButtonsMode: Equatable {
enum VideoState: Equatable { struct VideoState: Equatable {
case notAvailable var isAvailable: Bool
case possible(isEnabled: Bool, isInitializing: Bool) var isCameraActive: Bool
case outgoingRequested(isInitializing: Bool) var canChangeStatus: Bool
case incomingRequested(sendsVideo: Bool) var hasVideo: Bool
case active var isInitializingCamera: Bool
} }
case active(speakerMode: CallControllerButtonsSpeakerMode, hasAudioRouteMenu: Bool, videoState: VideoState) case active(speakerMode: CallControllerButtonsSpeakerMode, hasAudioRouteMenu: Bool, videoState: VideoState)
@ -96,7 +96,6 @@ final class CallControllerButtonsNode: ASDisplayNode {
private var validLayout: (CGFloat, CGFloat)? private var validLayout: (CGFloat, CGFloat)?
var isMuted = false var isMuted = false
var isCameraPaused = false
var acceptOrEnd: (() -> Void)? var acceptOrEnd: (() -> Void)?
var decline: (() -> Void)? var decline: (() -> Void)?
@ -189,16 +188,8 @@ final class CallControllerButtonsNode: ASDisplayNode {
case .outgoingRinging: case .outgoingRinging:
mappedState = .outgoingRinging mappedState = .outgoingRinging
case let .active(_, _, videoStateValue): case let .active(_, _, videoStateValue):
switch videoStateValue {
case let .incomingRequested(sendsVideo):
mappedState = .active mappedState = .active
videoState = .incomingRequested(sendsVideo: sendsVideo) videoState = videoStateValue
case let .outgoingRequested(isInitializing):
mappedState = .active
videoState = .outgoingRequested(isInitializing: isInitializing)
case .active, .possible, .notAvailable:
mappedState = .active
}
} }
var buttons: [PlacedButton] = [] var buttons: [PlacedButton] = []
@ -226,22 +217,21 @@ final class CallControllerButtonsNode: ASDisplayNode {
} }
} }
switch videoState { if videoState.isAvailable {
case .active, .possible, .incomingRequested, .outgoingRequested:
let isCameraActive: Bool let isCameraActive: Bool
let isCameraEnabled: Bool let isCameraEnabled: Bool
let isCameraInitializing: Bool let isCameraInitializing: Bool
if case let .possible(value, isInitializing) = videoState { if videoState.hasVideo {
isCameraActive = false isCameraActive = videoState.isCameraActive
isCameraEnabled = value isCameraEnabled = videoState.canChangeStatus
isCameraInitializing = isInitializing isCameraInitializing = videoState.isInitializingCamera
} else { } else {
isCameraActive = !self.isCameraPaused isCameraActive = false
isCameraEnabled = true isCameraEnabled = videoState.canChangeStatus
isCameraInitializing = false isCameraInitializing = videoState.isInitializingCamera
} }
topButtons.append(.enableCamera(isCameraActive, false, isCameraInitializing)) topButtons.append(.enableCamera(isCameraActive, false, isCameraInitializing))
if case .possible = videoState { if !videoState.hasVideo {
topButtons.append(.mute(self.isMuted)) topButtons.append(.mute(self.isMuted))
topButtons.append(.soundOutput(soundOutput)) topButtons.append(.soundOutput(soundOutput))
} else { } else {
@ -252,7 +242,7 @@ final class CallControllerButtonsNode: ASDisplayNode {
} }
topButtons.append(.switchCamera(isCameraActive && !isCameraInitializing)) topButtons.append(.switchCamera(isCameraActive && !isCameraInitializing))
} }
case .notAvailable: } else {
topButtons.append(.mute(self.isMuted)) topButtons.append(.mute(self.isMuted))
topButtons.append(.soundOutput(soundOutput)) topButtons.append(.soundOutput(soundOutput))
} }
@ -286,23 +276,18 @@ final class CallControllerButtonsNode: ASDisplayNode {
height = largeButtonSize + topBottomSpacing + largeButtonSize + max(bottomInset + 32.0, 46.0) height = largeButtonSize + topBottomSpacing + largeButtonSize + max(bottomInset + 32.0, 46.0)
case .active: case .active:
switch videoState { if videoState.hasVideo {
case .active, .incomingRequested, .outgoingRequested:
let isCameraActive: Bool let isCameraActive: Bool
let isCameraEnabled: Bool let isCameraEnabled: Bool
var isCameraInitializing: Bool let isCameraInitializing: Bool
if case .incomingRequested = videoState { if videoState.hasVideo {
isCameraActive = false isCameraActive = videoState.isCameraActive
isCameraEnabled = true isCameraEnabled = videoState.canChangeStatus
isCameraInitializing = false isCameraInitializing = videoState.isInitializingCamera
} else if case let .possible(value, isInitializing) = videoState {
isCameraActive = false
isCameraEnabled = value
isCameraInitializing = isInitializing
} else { } else {
isCameraActive = !self.isCameraPaused isCameraActive = false
isCameraEnabled = true isCameraEnabled = videoState.canChangeStatus
isCameraInitializing = false isCameraInitializing = videoState.isInitializingCamera
} }
var topButtons: [ButtonDescription] = [] var topButtons: [ButtonDescription] = []
@ -326,10 +311,6 @@ final class CallControllerButtonsNode: ASDisplayNode {
} }
} }
if case let .outgoingRequested(isInitializing) = videoState {
isCameraInitializing = isInitializing
}
topButtons.append(.enableCamera(isCameraActive, isCameraEnabled, isCameraInitializing)) topButtons.append(.enableCamera(isCameraActive, isCameraEnabled, isCameraInitializing))
if hasAudioRouteMenu { if hasAudioRouteMenu {
topButtons.append(.soundOutput(soundOutput)) topButtons.append(.soundOutput(soundOutput))
@ -350,21 +331,21 @@ final class CallControllerButtonsNode: ASDisplayNode {
} }
height = smallButtonSize + max(bottomInset + 19.0, 46.0) height = smallButtonSize + max(bottomInset + 19.0, 46.0)
case .notAvailable, .possible: } else {
var topButtons: [ButtonDescription] = [] var topButtons: [ButtonDescription] = []
var bottomButtons: [ButtonDescription] = [] var bottomButtons: [ButtonDescription] = []
let isCameraActive: Bool let isCameraActive: Bool
let isCameraEnabled: Bool let isCameraEnabled: Bool
var isCameraInitializing: Bool let isCameraInitializing: Bool
if case let .possible(value, isInitializing) = videoState { if videoState.hasVideo {
isCameraActive = false isCameraActive = videoState.isCameraActive
isCameraEnabled = value isCameraEnabled = videoState.canChangeStatus
isCameraInitializing = isInitializing isCameraInitializing = videoState.isInitializingCamera
} else { } else {
isCameraActive = false isCameraActive = false
isCameraEnabled = true isCameraEnabled = videoState.canChangeStatus
isCameraInitializing = false isCameraInitializing = videoState.isInitializingCamera
} }
let soundOutput: ButtonDescription.SoundOutput let soundOutput: ButtonDescription.SoundOutput

View File

@ -302,12 +302,16 @@ final class CallControllerNode: ViewControllerTracingNode, CallControllerNodePro
private let imageNode: TransformImageNode private let imageNode: TransformImageNode
private let dimNode: ASImageNode private let dimNode: ASImageNode
private var candidateIncomingVideoNodeValue: CallVideoNode?
private var incomingVideoNodeValue: CallVideoNode? private var incomingVideoNodeValue: CallVideoNode?
private var incomingVideoViewRequested: Bool = false private var incomingVideoViewRequested: Bool = false
private var candidateOutgoingVideoNodeValue: CallVideoNode? private var candidateOutgoingVideoNodeValue: CallVideoNode?
private var outgoingVideoNodeValue: CallVideoNode? private var outgoingVideoNodeValue: CallVideoNode?
private var outgoingVideoViewRequested: Bool = false private var outgoingVideoViewRequested: Bool = false
private var removedMinimizedVideoNodeValue: CallVideoNode?
private var removedExpandedVideoNodeValue: CallVideoNode?
private var isRequestingVideo: Bool = false private var isRequestingVideo: Bool = false
private var animateRequestedVideoOnce: Bool = false private var animateRequestedVideoOnce: Bool = false
@ -354,7 +358,6 @@ final class CallControllerNode: ViewControllerTracingNode, CallControllerNodePro
var beginAudioOuputSelection: ((Bool) -> Void)? var beginAudioOuputSelection: ((Bool) -> Void)?
var acceptCall: (() -> Void)? var acceptCall: (() -> Void)?
var endCall: (() -> Void)? var endCall: (() -> Void)?
var setIsVideoPaused: ((Bool) -> Void)?
var back: (() -> Void)? var back: (() -> Void)?
var presentCallRating: ((CallId) -> Void)? var presentCallRating: ((CallId) -> Void)?
var callEnded: ((Bool) -> Void)? var callEnded: ((Bool) -> Void)?
@ -447,6 +450,7 @@ final class CallControllerNode: ViewControllerTracingNode, CallControllerNodePro
self.containerNode.addSubnode(self.statusNode) self.containerNode.addSubnode(self.statusNode)
self.containerNode.addSubnode(self.videoPausedNode) self.containerNode.addSubnode(self.videoPausedNode)
self.containerNode.addSubnode(self.buttonsNode) self.containerNode.addSubnode(self.buttonsNode)
self.containerNode.addSubnode(self.toastNode)
self.containerNode.addSubnode(self.keyButtonNode) self.containerNode.addSubnode(self.keyButtonNode)
self.containerNode.addSubnode(self.backButtonArrowNode) self.containerNode.addSubnode(self.backButtonArrowNode)
self.containerNode.addSubnode(self.backButtonNode) self.containerNode.addSubnode(self.backButtonNode)
@ -465,12 +469,7 @@ final class CallControllerNode: ViewControllerTracingNode, CallControllerNodePro
} }
switch callState.state { switch callState.state {
case .active, .connecting, .reconnecting: case .active, .connecting, .reconnecting:
switch callState.videoState {
case .incomingRequested:
strongSelf.call.acceptVideo()
default:
strongSelf.endCall?() strongSelf.endCall?()
}
case .requesting: case .requesting:
strongSelf.endCall?() strongSelf.endCall?()
case .ringing: case .ringing:
@ -493,32 +492,20 @@ final class CallControllerNode: ViewControllerTracingNode, CallControllerNodePro
if strongSelf.outgoingVideoNodeValue == nil { if strongSelf.outgoingVideoNodeValue == nil {
let proceed = { let proceed = {
switch callState.videoState { switch callState.videoState {
case .possible: case .inactive:
strongSelf.isRequestingVideo = true strongSelf.isRequestingVideo = true
strongSelf.updateButtonsMode() strongSelf.updateButtonsMode()
default: default:
break break
} }
switch callState.videoState {
case .incomingRequested:
strongSelf.call.acceptVideo()
default:
strongSelf.call.requestVideo() strongSelf.call.requestVideo()
} }
}
strongSelf.present?(textAlertController(sharedContext: strongSelf.sharedContext, title: nil, text: strongSelf.presentationData.strings.Call_CameraConfirmationText, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Call_CameraConfirmationConfirm, action: { strongSelf.present?(textAlertController(sharedContext: strongSelf.sharedContext, title: nil, text: strongSelf.presentationData.strings.Call_CameraConfirmationText, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Call_CameraConfirmationConfirm, action: {
proceed() proceed()
})])) })]))
} else { } else {
strongSelf.isVideoPaused = !strongSelf.isVideoPaused strongSelf.call.disableVideo()
strongSelf.outgoingVideoNodeValue?.updateIsBlurred(isBlurred: strongSelf.isVideoPaused)
strongSelf.buttonsNode.isCameraPaused = strongSelf.isVideoPaused
strongSelf.setIsVideoPaused?(strongSelf.isVideoPaused)
if let (layout, navigationBarHeight) = strongSelf.validLayout {
strongSelf.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .animated(duration: 0.3, curve: .easeInOut))
}
} }
default: default:
break break
@ -526,9 +513,10 @@ final class CallControllerNode: ViewControllerTracingNode, CallControllerNodePro
} }
self.buttonsNode.rotateCamera = { [weak self] in self.buttonsNode.rotateCamera = { [weak self] in
guard let strongSelf = self else { guard let strongSelf = self, !strongSelf.areUserActionsDisabledNow() else {
return return
} }
strongSelf.disableActionsUntilTimestamp = CACurrentMediaTime() + 1.0
if let outgoingVideoNode = strongSelf.outgoingVideoNodeValue, let (layout, _) = strongSelf.validLayout { if let outgoingVideoNode = strongSelf.outgoingVideoNodeValue, let (layout, _) = strongSelf.validLayout {
outgoingVideoNode.flip(withBackground: outgoingVideoNode.frame.width == layout.size.width) outgoingVideoNode.flip(withBackground: outgoingVideoNode.frame.width == layout.size.width)
} }
@ -543,6 +531,14 @@ final class CallControllerNode: ViewControllerTracingNode, CallControllerNodePro
self.keyButtonNode.addTarget(self, action: #selector(self.keyPressed), forControlEvents: .touchUpInside) self.keyButtonNode.addTarget(self, action: #selector(self.keyPressed), forControlEvents: .touchUpInside)
self.backButtonNode.addTarget(self, action: #selector(self.backPressed), forControlEvents: .touchUpInside) self.backButtonNode.addTarget(self, action: #selector(self.backPressed), forControlEvents: .touchUpInside)
if !shouldStayHiddenUntilConnection && call.isVideo && call.isOutgoing {
self.containerNode.alpha = 0.0
Queue.mainQueue().after(1.0, { [weak self] in
self?.containerNode.alpha = 1.0
self?.animateIn()
})
}
} }
func displayCameraTooltip() { func displayCameraTooltip() {
@ -616,7 +612,7 @@ final class CallControllerNode: ViewControllerTracingNode, CallControllerNodePro
} }
private func setupAudioOutputs() { private func setupAudioOutputs() {
if self.outgoingVideoNodeValue != nil || self.candidateOutgoingVideoNodeValue != nil { if self.outgoingVideoNodeValue != nil || self.incomingVideoNodeValue != nil || self.candidateOutgoingVideoNodeValue != nil || self.candidateIncomingVideoNodeValue != nil {
if let audioOutputState = self.audioOutputState, let currentOutput = audioOutputState.currentOutput { if let audioOutputState = self.audioOutputState, let currentOutput = audioOutputState.currentOutput {
switch currentOutput { switch currentOutput {
case .headphones: case .headphones:
@ -636,21 +632,39 @@ final class CallControllerNode: ViewControllerTracingNode, CallControllerNodePro
let statusValue: CallControllerStatusValue let statusValue: CallControllerStatusValue
var statusReception: Int32? var statusReception: Int32?
switch callState.videoState { switch callState.remoteVideoState {
case .active, .incomingRequested(true): case .active, .paused:
if !self.incomingVideoViewRequested { if !self.incomingVideoViewRequested {
self.incomingVideoViewRequested = true self.incomingVideoViewRequested = true
let delayUntilInitialized = true
self.call.makeIncomingVideoView(completion: { [weak self] incomingVideoView in self.call.makeIncomingVideoView(completion: { [weak self] incomingVideoView in
guard let strongSelf = self else { guard let strongSelf = self else {
return return
} }
if let incomingVideoView = incomingVideoView { if let incomingVideoView = incomingVideoView {
let incomingVideoNode = CallVideoNode(videoView: incomingVideoView, assumeReadyAfterTimeout: false, isReadyUpdated: { incomingVideoView.view.backgroundColor = .black
guard let strongSelf = self else { incomingVideoView.view.clipsToBounds = true
let applyNode: () -> Void = {
guard let strongSelf = self, let incomingVideoNode = strongSelf.candidateIncomingVideoNodeValue else {
return return
} }
if let (layout, navigationBarHeight) = strongSelf.validLayout { strongSelf.candidateIncomingVideoNodeValue = nil
strongSelf.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .animated(duration: 0.5, curve: .spring))
strongSelf.incomingVideoNodeValue = incomingVideoNode
if let expandedVideoNode = strongSelf.expandedVideoNode {
strongSelf.minimizedVideoNode = expandedVideoNode
}
strongSelf.expandedVideoNode = incomingVideoNode
strongSelf.containerNode.insertSubnode(incomingVideoNode, belowSubnode: strongSelf.dimNode)
strongSelf.updateButtonsMode(transition: .animated(duration: 0.4, curve: .spring))
}
let incomingVideoNode = CallVideoNode(videoView: incomingVideoView, assumeReadyAfterTimeout: false, isReadyUpdated: {
if delayUntilInitialized {
Queue.mainQueue().after(0.1, {
applyNode()
})
} }
}, orientationUpdated: { }, orientationUpdated: {
guard let strongSelf = self else { guard let strongSelf = self else {
@ -661,21 +675,38 @@ final class CallControllerNode: ViewControllerTracingNode, CallControllerNodePro
} }
}, isFlippedUpdated: { _ in }, isFlippedUpdated: { _ in
}) })
strongSelf.incomingVideoNodeValue = incomingVideoNode strongSelf.candidateIncomingVideoNodeValue = incomingVideoNode
strongSelf.expandedVideoNode = incomingVideoNode strongSelf.setupAudioOutputs()
strongSelf.containerNode.insertSubnode(incomingVideoNode, belowSubnode: strongSelf.dimNode)
if let (layout, navigationBarHeight) = strongSelf.validLayout { if !delayUntilInitialized {
strongSelf.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .animated(duration: 0.5, curve: .spring)) applyNode()
} }
} }
}) })
} }
default: case .inactive:
break self.candidateIncomingVideoNodeValue = nil
if let incomingVideoNodeValue = self.incomingVideoNodeValue {
if self.minimizedVideoNode == incomingVideoNodeValue {
self.minimizedVideoNode = nil
self.removedMinimizedVideoNodeValue = incomingVideoNodeValue
}
if self.expandedVideoNode == incomingVideoNodeValue {
self.expandedVideoNode = nil
self.removedExpandedVideoNodeValue = incomingVideoNodeValue
if let minimizedVideoNode = self.minimizedVideoNode {
self.expandedVideoNode = minimizedVideoNode
self.minimizedVideoNode = nil
}
}
self.incomingVideoNodeValue = nil
self.incomingVideoViewRequested = false
}
} }
switch callState.videoState { switch callState.videoState {
case .active, .outgoingRequested, .incomingRequested(false): case .active, .paused:
if !self.outgoingVideoViewRequested { if !self.outgoingVideoViewRequested {
self.outgoingVideoViewRequested = true self.outgoingVideoViewRequested = true
let delayUntilInitialized = self.isRequestingVideo let delayUntilInitialized = self.isRequestingVideo
@ -700,10 +731,11 @@ final class CallControllerNode: ViewControllerTracingNode, CallControllerNodePro
} }
strongSelf.outgoingVideoNodeValue = outgoingVideoNode strongSelf.outgoingVideoNodeValue = outgoingVideoNode
strongSelf.minimizedVideoNode = outgoingVideoNode
if let expandedVideoNode = strongSelf.expandedVideoNode { if let expandedVideoNode = strongSelf.expandedVideoNode {
strongSelf.minimizedVideoNode = outgoingVideoNode
strongSelf.containerNode.insertSubnode(outgoingVideoNode, aboveSubnode: expandedVideoNode) strongSelf.containerNode.insertSubnode(outgoingVideoNode, aboveSubnode: expandedVideoNode)
} else { } else {
strongSelf.expandedVideoNode = outgoingVideoNode
strongSelf.containerNode.insertSubnode(outgoingVideoNode, belowSubnode: strongSelf.dimNode) strongSelf.containerNode.insertSubnode(outgoingVideoNode, belowSubnode: strongSelf.dimNode)
} }
strongSelf.updateButtonsMode(transition: .animated(duration: 0.4, curve: .spring)) strongSelf.updateButtonsMode(transition: .animated(duration: 0.4, curve: .spring))
@ -750,12 +782,28 @@ final class CallControllerNode: ViewControllerTracingNode, CallControllerNodePro
if !delayUntilInitialized { if !delayUntilInitialized {
applyNode() applyNode()
} }
strongSelf.setupAudioOutputs()
} }
}) })
} }
default: case .notAvailable, .inactive:
break self.candidateOutgoingVideoNodeValue = nil
if let outgoingVideoNodeValue = self.outgoingVideoNodeValue {
if self.minimizedVideoNode == outgoingVideoNodeValue {
self.minimizedVideoNode = nil
self.removedMinimizedVideoNodeValue = outgoingVideoNodeValue
}
if self.expandedVideoNode == self.outgoingVideoNodeValue {
self.expandedVideoNode = nil
self.removedExpandedVideoNodeValue = outgoingVideoNodeValue
if let minimizedVideoNode = self.minimizedVideoNode {
self.expandedVideoNode = minimizedVideoNode
self.minimizedVideoNode = nil
}
}
self.outgoingVideoNodeValue = nil
self.outgoingVideoViewRequested = false
}
} }
if let incomingVideoNode = self.incomingVideoNodeValue { if let incomingVideoNode = self.incomingVideoNodeValue {
@ -765,7 +813,7 @@ final class CallControllerNode: ViewControllerTracingNode, CallControllerNodePro
default: default:
let isActive: Bool let isActive: Bool
switch callState.remoteVideoState { switch callState.remoteVideoState {
case .inactive: case .inactive, .paused:
isActive = false isActive = false
case .active: case .active:
isActive = true isActive = true
@ -842,8 +890,7 @@ final class CallControllerNode: ViewControllerTracingNode, CallControllerNodePro
self.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .immediate) self.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .immediate)
} }
} }
switch callState.videoState {
case .notAvailable, .active, .possible, .outgoingRequested:
statusValue = .timer({ value in statusValue = .timer({ value in
if isReconnecting { if isReconnecting {
return strings.Call_StatusConnecting return strings.Call_StatusConnecting
@ -851,16 +898,8 @@ final class CallControllerNode: ViewControllerTracingNode, CallControllerNodePro
return value return value
} }
}, timestamp) }, timestamp)
if case .active = callState.state {
statusReception = reception statusReception = reception
case .incomingRequested:
var text: String
text = self.presentationData.strings.Call_IncomingVideoCall
if !self.statusNode.subtitle.isEmpty {
text += "\n\(self.statusNode.subtitle)"
}
statusValue = .text(string: text, displayLogo: false)
/*case .outgoingRequested:
statusValue = .text(string: self.presentationData.strings.Call_StatusRequesting, displayLogo: false)*/
} }
} }
if self.shouldStayHiddenUntilConnection { if self.shouldStayHiddenUntilConnection {
@ -900,7 +939,7 @@ final class CallControllerNode: ViewControllerTracingNode, CallControllerNodePro
if self.incomingVideoViewRequested && !self.outgoingVideoViewRequested && !self.displayedCameraTooltip { if self.incomingVideoViewRequested && !self.outgoingVideoViewRequested && !self.displayedCameraTooltip {
self.displayedCameraTooltip = true self.displayedCameraTooltip = true
Queue.mainQueue().after(1.0) { Queue.mainQueue().after(2.0) {
self.displayCameraTooltip() self.displayCameraTooltip()
} }
} }
@ -937,6 +976,7 @@ final class CallControllerNode: ViewControllerTracingNode, CallControllerNodePro
self.dimNode.backgroundColor = color self.dimNode.backgroundColor = color
self.dimNode.image = image self.dimNode.image = image
} }
self.statusNode.isHidden = !visible
} }
} }
@ -950,7 +990,7 @@ final class CallControllerNode: ViewControllerTracingNode, CallControllerNodePro
var mode: CallControllerButtonsSpeakerMode = .none var mode: CallControllerButtonsSpeakerMode = .none
var hasAudioRouteMenu: Bool = false var hasAudioRouteMenu: Bool = false
if let (availableOutputs, maybeCurrentOutput) = self.audioOutputState, let currentOutput = maybeCurrentOutput { if let (availableOutputs, maybeCurrentOutput) = self.audioOutputState, let currentOutput = maybeCurrentOutput {
hasAudioRouteMenu = availableOutputs.count >= 2 hasAudioRouteMenu = availableOutputs.count > 2
switch currentOutput { switch currentOutput {
case .builtin: case .builtin:
mode = .builtin mode = .builtin
@ -972,29 +1012,16 @@ final class CallControllerNode: ViewControllerTracingNode, CallControllerNodePro
mode = .none mode = .none
} }
} }
let mappedVideoState: CallControllerButtonsMode.VideoState var mappedVideoState = CallControllerButtonsMode.VideoState(isAvailable: false, isCameraActive: self.outgoingVideoNodeValue != nil, canChangeStatus: false, hasVideo: self.outgoingVideoNodeValue != nil || self.incomingVideoNodeValue != nil, isInitializingCamera: self.isRequestingVideo)
switch callState.videoState { switch callState.videoState {
case .notAvailable: case .notAvailable:
mappedVideoState = .notAvailable
case .possible:
var isEnabled = false
switch callState.state {
case .active:
isEnabled = true
default:
break break
} case .inactive:
mappedVideoState = .possible(isEnabled: isEnabled, isInitializing: false) mappedVideoState.isAvailable = true
case .outgoingRequested: mappedVideoState.canChangeStatus = true
if self.outgoingVideoNodeValue != nil { case .active, .paused:
mappedVideoState = .outgoingRequested(isInitializing: self.isRequestingVideo) mappedVideoState.isAvailable = true
} else { mappedVideoState.canChangeStatus = true
mappedVideoState = .possible(isEnabled: true, isInitializing: self.isRequestingVideo)
}
case let .incomingRequested(sendsVideo):
mappedVideoState = .incomingRequested(sendsVideo: sendsVideo)
case .active:
mappedVideoState = .active
} }
switch callState.state { switch callState.state {
@ -1021,6 +1048,7 @@ final class CallControllerNode: ViewControllerTracingNode, CallControllerNodePro
} }
func animateIn() { func animateIn() {
if !self.containerNode.alpha.isZero {
var bounds = self.bounds var bounds = self.bounds
bounds.origin = CGPoint() bounds.origin = CGPoint()
self.bounds = bounds self.bounds = bounds
@ -1034,6 +1062,7 @@ final class CallControllerNode: ViewControllerTracingNode, CallControllerNodePro
self.containerNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) self.containerNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
} }
} }
}
func animateOut(completion: @escaping () -> Void) { func animateOut(completion: @escaping () -> Void) {
self.statusBar.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false) self.statusBar.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false)
@ -1063,6 +1092,7 @@ final class CallControllerNode: ViewControllerTracingNode, CallControllerNodePro
uiDisplayTransition *= 1.0 - self.pictureInPictureTransitionFraction uiDisplayTransition *= 1.0 - self.pictureInPictureTransitionFraction
let buttonsHeight: CGFloat = self.buttonsNode.bounds.height let buttonsHeight: CGFloat = self.buttonsNode.bounds.height
let toastHeight: CGFloat = self.toastNode.bounds.height
var fullInsets = layout.insets(options: .statusBar) var fullInsets = layout.insets(options: .statusBar)
@ -1072,7 +1102,7 @@ final class CallControllerNode: ViewControllerTracingNode, CallControllerNodePro
cleanInsets.right = 20.0 cleanInsets.right = 20.0
fullInsets.top += 44.0 + 8.0 fullInsets.top += 44.0 + 8.0
fullInsets.bottom = buttonsHeight + 27.0 fullInsets.bottom = buttonsHeight + toastHeight + 27.0
fullInsets.left = 20.0 fullInsets.left = 20.0
fullInsets.right = 20.0 fullInsets.right = 20.0
@ -1225,7 +1255,7 @@ final class CallControllerNode: ViewControllerTracingNode, CallControllerNodePro
let videoPausedSize = self.videoPausedNode.updateLayout(CGSize(width: layout.size.width - 16.0, height: 100.0)) let videoPausedSize = self.videoPausedNode.updateLayout(CGSize(width: layout.size.width - 16.0, height: 100.0))
transition.updateFrame(node: self.videoPausedNode, frame: CGRect(origin: CGPoint(x: floor((layout.size.width - videoPausedSize.width) / 2.0), y: floor((layout.size.height - videoPausedSize.height) / 2.0)), size: videoPausedSize)) transition.updateFrame(node: self.videoPausedNode, frame: CGRect(origin: CGPoint(x: floor((layout.size.width - videoPausedSize.width) / 2.0), y: floor((layout.size.height - videoPausedSize.height) / 2.0)), size: videoPausedSize))
transition.updateFrame(node: self.toastNode, frame: CGRect(origin: CGPoint(x: 0.0, y: buttonsOriginY), size: CGSize(width: layout.size.width, height: buttonsHeight))) transition.updateFrame(node: self.toastNode, frame: CGRect(origin: CGPoint(x: 0.0, y: buttonsOriginY - toastHeight), size: CGSize(width: layout.size.width, height: toastHeight)))
transition.updateFrame(node: self.buttonsNode, frame: CGRect(origin: CGPoint(x: 0.0, y: buttonsOriginY), size: CGSize(width: layout.size.width, height: buttonsHeight))) transition.updateFrame(node: self.buttonsNode, frame: CGRect(origin: CGPoint(x: 0.0, y: buttonsOriginY), size: CGSize(width: layout.size.width, height: buttonsHeight)))
transition.updateAlpha(node: self.buttonsNode, alpha: overlayAlpha) transition.updateAlpha(node: self.buttonsNode, alpha: overlayAlpha)
@ -1233,15 +1263,66 @@ final class CallControllerNode: ViewControllerTracingNode, CallControllerNodePro
let previewVideoFrame = self.calculatePreviewVideoRect(layout: layout, navigationHeight: navigationBarHeight) let previewVideoFrame = self.calculatePreviewVideoRect(layout: layout, navigationHeight: navigationBarHeight)
if let removedMinimizedVideoNodeValue = self.removedMinimizedVideoNodeValue {
self.removedMinimizedVideoNodeValue = nil
if transition.isAnimated {
removedMinimizedVideoNodeValue.layer.animateScale(from: 1.0, to: 0.1, duration: 0.3, removeOnCompletion: false)
removedMinimizedVideoNodeValue.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { [weak removedMinimizedVideoNodeValue] _ in
removedMinimizedVideoNodeValue?.removeFromSupernode()
})
} else {
removedMinimizedVideoNodeValue.removeFromSupernode()
}
}
if let expandedVideoNode = self.expandedVideoNode { if let expandedVideoNode = self.expandedVideoNode {
var expandedVideoTransition = transition var expandedVideoTransition = transition
if expandedVideoNode.frame.isEmpty || self.disableAnimationForExpandedVideoOnce { if expandedVideoNode.frame.isEmpty || self.disableAnimationForExpandedVideoOnce {
expandedVideoTransition = .immediate expandedVideoTransition = .immediate
self.disableAnimationForExpandedVideoOnce = false self.disableAnimationForExpandedVideoOnce = false
} }
if let removedExpandedVideoNodeValue = self.removedExpandedVideoNodeValue {
self.removedExpandedVideoNodeValue = nil
expandedVideoTransition.updateFrame(node: expandedVideoNode, frame: fullscreenVideoFrame, completion: { [weak removedExpandedVideoNodeValue] _ in
removedExpandedVideoNodeValue?.removeFromSupernode()
})
} else {
expandedVideoTransition.updateFrame(node: expandedVideoNode, frame: fullscreenVideoFrame) expandedVideoTransition.updateFrame(node: expandedVideoNode, frame: fullscreenVideoFrame)
expandedVideoNode.updateLayout(size: expandedVideoNode.frame.size, cornerRadius: 0.0, transition: expandedVideoTransition)
} }
expandedVideoNode.updateLayout(size: expandedVideoNode.frame.size, cornerRadius: 0.0, transition: expandedVideoTransition)
if self.animateRequestedVideoOnce {
self.animateRequestedVideoOnce = false
if expandedVideoNode === self.outgoingVideoNodeValue {
let videoButtonFrame = self.buttonsNode.videoButtonFrame().flatMap { frame -> CGRect in
return self.buttonsNode.view.convert(frame, to: self.view)
}
if let previousVideoButtonFrame = previousVideoButtonFrame, let videoButtonFrame = videoButtonFrame {
expandedVideoNode.animateRadialMask(from: previousVideoButtonFrame, to: videoButtonFrame)
}
}
}
} else {
if let removedExpandedVideoNodeValue = self.removedExpandedVideoNodeValue {
self.removedExpandedVideoNodeValue = nil
if transition.isAnimated {
removedExpandedVideoNodeValue.layer.animateScale(from: 1.0, to: 0.1, duration: 0.3, removeOnCompletion: false)
removedExpandedVideoNodeValue.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { [weak removedExpandedVideoNodeValue] _ in
removedExpandedVideoNodeValue?.removeFromSupernode()
})
} else {
removedExpandedVideoNodeValue.removeFromSupernode()
}
}
}
if let minimizedVideoNode = self.minimizedVideoNode { if let minimizedVideoNode = self.minimizedVideoNode {
var minimizedVideoTransition = transition var minimizedVideoTransition = transition
var didAppear = false var didAppear = false
@ -1249,7 +1330,6 @@ final class CallControllerNode: ViewControllerTracingNode, CallControllerNodePro
minimizedVideoTransition = .immediate minimizedVideoTransition = .immediate
didAppear = true didAppear = true
} }
if let expandedVideoNode = self.expandedVideoNode, expandedVideoNode.isReady {
if self.minimizedVideoDraggingPosition == nil { if self.minimizedVideoDraggingPosition == nil {
if let animationForExpandedVideoSnapshotView = self.animationForExpandedVideoSnapshotView { if let animationForExpandedVideoSnapshotView = self.animationForExpandedVideoSnapshotView {
self.containerNode.view.addSubview(animationForExpandedVideoSnapshotView) self.containerNode.view.addSubview(animationForExpandedVideoSnapshotView)
@ -1262,25 +1342,12 @@ final class CallControllerNode: ViewControllerTracingNode, CallControllerNodePro
self.animationForExpandedVideoSnapshotView = nil self.animationForExpandedVideoSnapshotView = nil
} }
minimizedVideoTransition.updateFrame(node: minimizedVideoNode, frame: previewVideoFrame) minimizedVideoTransition.updateFrame(node: minimizedVideoNode, frame: previewVideoFrame)
minimizedVideoNode.updateLayout(size: minimizedVideoNode.frame.size, cornerRadius: interpolate(from: 14.0, to: 24.0, value: self.pictureInPictureTransitionFraction), transition: minimizedVideoTransition) minimizedVideoNode.updateLayout(size: previewVideoFrame.size, cornerRadius: interpolate(from: 14.0, to: 24.0, value: self.pictureInPictureTransitionFraction), transition: minimizedVideoTransition)
if transition.isAnimated && didAppear { if transition.isAnimated && didAppear {
minimizedVideoNode.layer.animateSpring(from: 0.1 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.5) minimizedVideoNode.layer.animateSpring(from: 0.1 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.5)
} }
} }
} else {
minimizedVideoNode.frame = fullscreenVideoFrame
minimizedVideoNode.updateLayout(size: layout.size, cornerRadius: 0.0, transition: minimizedVideoTransition)
if self.animateRequestedVideoOnce {
self.animateRequestedVideoOnce = false
let videoButtonFrame = self.buttonsNode.videoButtonFrame().flatMap { frame -> CGRect in
return self.buttonsNode.view.convert(frame, to: self.view)
}
if let previousVideoButtonFrame = previousVideoButtonFrame, let videoButtonFrame = videoButtonFrame {
minimizedVideoNode.animateRadialMask(from: previousVideoButtonFrame, to: videoButtonFrame)
}
}
}
self.animationForExpandedVideoSnapshotView = nil self.animationForExpandedVideoSnapshotView = nil
} }

View File

@ -94,11 +94,12 @@ final class CallControllerToastContainerNode: ASDisplayNode {
toasts.append(.battery) toasts.append(.battery)
} }
var transitions: [ToastDescription.Key: (ContainedViewLayoutTransition, CGFloat, Bool)] = [:]
var validKeys: [ToastDescription.Key] = [] var validKeys: [ToastDescription.Key] = []
for toast in toasts { for toast in toasts {
validKeys.append(toast.key) validKeys.append(toast.key)
var toastTransition = transition var toastTransition = transition
var animateToastIn = false var animateIn = false
let toastNode: CallControllerToastItemNode let toastNode: CallControllerToastItemNode
if let current = self.toastNodes[toast.key] { if let current = self.toastNodes[toast.key] {
toastNode = current toastNode = current
@ -107,41 +108,37 @@ final class CallControllerToastContainerNode: ASDisplayNode {
self.toastNodes[toast.key] = toastNode self.toastNodes[toast.key] = toastNode
self.addSubnode(toastNode) self.addSubnode(toastNode)
toastTransition = .immediate toastTransition = .immediate
animateToastIn = transition.isAnimated animateIn = transition.isAnimated
} }
let toastContent: CallControllerToastItemNode.Content let toastContent: CallControllerToastItemNode.Content
let toastText: String
switch toast { switch toast {
case .camera: case .camera:
toastContent = CallControllerToastItemNode.Content( toastContent = CallControllerToastItemNode.Content(
key: .camera,
image: .camera, image: .camera,
text: strings.Call_CameraOff(self.title).0 text: strings.Call_CameraOff(self.title).0
) )
case .microphone: case .microphone:
toastContent = CallControllerToastItemNode.Content( toastContent = CallControllerToastItemNode.Content(
key: .microphone,
image: .microphone, image: .microphone,
text: strings.Call_MicrophoneOff(self.title).0 text: strings.Call_MicrophoneOff(self.title).0
) )
case .mute: case .mute:
toastContent = CallControllerToastItemNode.Content( toastContent = CallControllerToastItemNode.Content(
key: .mute,
image: .microphone, image: .microphone,
text: strings.Call_YourMicrophoneOff text: strings.Call_YourMicrophoneOff
) )
case .battery: case .battery:
toastContent = CallControllerToastItemNode.Content( toastContent = CallControllerToastItemNode.Content(
key: .battery,
image: .battery, image: .battery,
text: strings.Call_BatteryLow(self.title).0 text: strings.Call_BatteryLow(self.title).0
) )
} }
let toastHeight = toastNode.update(width: width, content: buttonContent, text: buttonText, transition: buttonTransition) let toastHeight = toastNode.update(width: width, content: toastContent, transition: toastTransition)
let toastFrame = CGRect(x: 0.0, y: 0.0, width: 100.0, height: 20.0) transitions[toast.key] = (toastTransition, toastHeight, animateIn)
toastTransition.updateFrame(node: toastNode, frame: toastFrame)
height += toastHeight +
if animateToastIn {
toastNode.animateIn()
}
} }
var removedKeys: [ToastDescription.Key] = [] var removedKeys: [ToastDescription.Key] = []
@ -161,6 +158,25 @@ final class CallControllerToastContainerNode: ASDisplayNode {
self.toastNodes.removeValue(forKey: key) self.toastNodes.removeValue(forKey: key)
} }
guard let subnodes = self.subnodes else {
return 0.0
}
for case let toastNode as CallControllerToastItemNode in subnodes.reversed() {
if let content = toastNode.currentContent, let (transition, toastHeight, animateIn) = transitions[content.key] {
transition.updateFrame(node: toastNode, frame: CGRect(x: 0.0, y: height, width: width, height: toastHeight))
height += toastHeight + spacing
if animateIn {
toastNode.animateIn()
}
}
}
if height > 0.0 {
height -= spacing
}
height += bottomSpacing
return height return height
} }
@ -177,7 +193,7 @@ final class CallControllerToastContainerNode: ASDisplayNode {
} }
} }
final class CallControllerToastItemNode: ASDisplayNode { private class CallControllerToastItemNode: ASDisplayNode {
struct Content: Equatable { struct Content: Equatable {
enum Image { enum Image {
case camera case camera
@ -185,52 +201,63 @@ final class CallControllerToastItemNode: ASDisplayNode {
case battery case battery
} }
var key: ToastDescription.Key
var image: Image var image: Image
var text: String var text: String
init(image: Image, text: String) { init(key: ToastDescription.Key, image: Image, text: String) {
self.key = key
self.image = image self.image = image
self.text = text self.text = text
} }
} }
let clipNode: ASDisplayNode
let effectView: UIVisualEffectView let effectView: UIVisualEffectView
let iconNode: ASImageNode let iconNode: ASImageNode
let textNode: ImmediateTextNode let textNode: ImmediateTextNode
private(set) var currentContent: Content? private(set) var currentContent: Content?
private(set) var currentWidth: CGFloat? private(set) var currentWidth: CGFloat?
private(set) var currentHeight: CGFloat?
override init() { override init() {
self.clipNode = ASDisplayNode()
self.clipNode.clipsToBounds = true
self.clipNode.layer.cornerRadius = 14.0
if #available(iOS 13.0, *) {
self.clipNode.layer.cornerCurve = .continuous
}
self.effectView = UIVisualEffectView() self.effectView = UIVisualEffectView()
self.effectView.effect = UIBlurEffect(style: .light) self.effectView.effect = UIBlurEffect(style: .light)
self.effectView.layer.cornerRadius = 16.0
self.effectView.clipsToBounds = true
self.effectView.isUserInteractionEnabled = false self.effectView.isUserInteractionEnabled = false
self.iconNode = ASImageNode() self.iconNode = ASImageNode()
self.iconNode.displaysAsynchronously = false self.iconNode.displaysAsynchronously = false
self.iconNode.displayWithoutProcessing = true self.iconNode.displayWithoutProcessing = true
self.iconNode.contentMode = .center
self.textNode = ImmediateTextNode() self.textNode = ImmediateTextNode()
self.textNode.maximumNumberOfLines = 2
self.textNode.displaysAsynchronously = false self.textNode.displaysAsynchronously = false
self.textNode.isUserInteractionEnabled = false self.textNode.isUserInteractionEnabled = false
super.init() super.init()
self.view.addSubview(self.effectView) self.addSubnode(self.clipNode)
self.addSubnode(self.iconNode) self.clipNode.view.addSubview(self.effectView)
self.addSubnode(self.textNode) self.clipNode.addSubnode(self.iconNode)
self.clipNode.addSubnode(self.textNode)
} }
func update(width: CGFloat, content: Content, transition: ContainedViewLayoutTransition) -> CGFloat { func update(width: CGFloat, content: Content, transition: ContainedViewLayoutTransition) -> CGFloat {
let inset: CGFloat = 24.0 let inset: CGFloat = 32.0
self.currentWidth = size.width if self.currentContent != content || self.currentWidth != width {
if self.currentContent != content {
let previousContent = self.currentContent let previousContent = self.currentContent
self.currentContent = content self.currentContent = content
self.currentWidth = width
var image: UIImage? var image: UIImage?
switch content.image { switch content.image {
@ -250,20 +277,25 @@ final class CallControllerToastItemNode: ASDisplayNode {
} }
if previousContent?.text != content.text { if previousContent?.text != content.text {
let textSize = self.textNode.updateLayout(CGSize(width: size.width - inset * 2.0, height: 100.0)) self.textNode.attributedText = NSAttributedString(string: content.text, font: Font.regular(17.0), textColor: .white)
let textFrame = CGRect(origin: CGPoint(x: floor((size.width - textSize.width) / 2.0), y: size.height), size: textSize)
if previousContent?.text.isEmpty ?? true { let iconSize = CGSize(width: 44.0, height: 28.0)
self.textNode.frame = textFrame let iconSpacing: CGFloat = 2.0
if transition.isAnimated { let textSize = self.textNode.updateLayout(CGSize(width: width - inset * 2.0 - iconSize.width - iconSpacing, height: 100.0))
self.textNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15)
} let backgroundSize = CGSize(width: iconSize.width + iconSpacing + textSize.width + 6.0 * 2.0, height: max(28.0, textSize.height + 4.0 * 2.0))
} else { let backgroundFrame = CGRect(origin: CGPoint(x: floor((width - backgroundSize.width) / 2.0), y: 0.0), size: backgroundSize)
transition.updateFrameAdditiveToCenter(node: self.textNode, frame: textFrame)
transition.updateFrame(node: self.clipNode, frame: backgroundFrame)
transition.updateFrame(view: self.effectView, frame: CGRect(origin: CGPoint(), size: backgroundFrame.size))
self.iconNode.frame = CGRect(origin: CGPoint(), size: iconSize)
self.textNode.frame = CGRect(origin: CGPoint(x: iconSize.width + iconSpacing, y: 4.0), size: textSize)
self.currentHeight = backgroundSize.height
} }
} }
} return self.currentHeight ?? 28.0
return 28.0
} }
func animateIn() { func animateIn() {

View File

@ -14,75 +14,6 @@ import LocalizedPeerData
import PhotoResources import PhotoResources
import CallsEmoji import CallsEmoji
private final class IncomingVideoNode: ASDisplayNode {
private let videoView: UIView
private var effectView: UIVisualEffectView?
private var isBlurred: Bool = false
init(videoView: UIView) {
self.videoView = videoView
super.init()
self.view.addSubview(self.videoView)
}
func updateLayout(size: CGSize) {
self.videoView.frame = CGRect(origin: CGPoint(), size: size)
}
func updateIsBlurred(isBlurred: Bool) {
if self.isBlurred == isBlurred {
return
}
self.isBlurred = isBlurred
if isBlurred {
if self.effectView == nil {
let effectView = UIVisualEffectView()
self.effectView = effectView
effectView.frame = self.videoView.frame
self.view.addSubview(effectView)
}
UIView.animate(withDuration: 0.3, animations: {
self.effectView?.effect = UIBlurEffect(style: .dark)
})
} else if let effectView = self.effectView {
UIView.animate(withDuration: 0.3, animations: {
effectView.effect = nil
})
}
}
}
private final class OutgoingVideoNode: ASDisplayNode {
private let videoView: UIView
private let switchCameraButton: HighlightableButtonNode
private let switchCamera: () -> Void
init(videoView: UIView, switchCamera: @escaping () -> Void) {
self.videoView = videoView
self.switchCameraButton = HighlightableButtonNode()
self.switchCamera = switchCamera
super.init()
self.view.addSubview(self.videoView)
self.addSubnode(self.switchCameraButton)
self.switchCameraButton.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside)
}
@objc private func buttonPressed() {
self.switchCamera()
}
func updateLayout(size: CGSize, isExpanded: Bool, transition: ContainedViewLayoutTransition) {
transition.updateFrame(view: self.videoView, frame: CGRect(origin: CGPoint(), size: size))
transition.updateCornerRadius(layer: self.videoView.layer, cornerRadius: isExpanded ? 0.0 : 16.0)
self.switchCameraButton.frame = CGRect(origin: CGPoint(), size: size)
}
}
final class LegacyCallControllerNode: ASDisplayNode, CallControllerNodeProtocol { final class LegacyCallControllerNode: ASDisplayNode, CallControllerNodeProtocol {
private let sharedContext: SharedAccountContext private let sharedContext: SharedAccountContext
private let account: Account private let account: Account
@ -100,14 +31,9 @@ final class LegacyCallControllerNode: ASDisplayNode, CallControllerNodeProtocol
private let imageNode: TransformImageNode private let imageNode: TransformImageNode
private let dimNode: ASDisplayNode private let dimNode: ASDisplayNode
private var incomingVideoNode: IncomingVideoNode?
private var incomingVideoViewRequested: Bool = false
private var outgoingVideoNode: OutgoingVideoNode?
private var outgoingVideoViewRequested: Bool = false
private let backButtonArrowNode: ASImageNode private let backButtonArrowNode: ASImageNode
private let backButtonNode: HighlightableButtonNode private let backButtonNode: HighlightableButtonNode
private let statusNode: LegacyCallControllerStatusNode private let statusNode: LegacyCallControllerStatusNode
private let videoPausedNode: ImmediateTextNode
private let buttonsNode: LegacyCallControllerButtonsNode private let buttonsNode: LegacyCallControllerButtonsNode
private var keyPreviewNode: CallControllerKeyPreviewNode? private var keyPreviewNode: CallControllerKeyPreviewNode?
@ -134,13 +60,12 @@ final class LegacyCallControllerNode: ASDisplayNode, CallControllerNodeProtocol
var beginAudioOuputSelection: ((Bool) -> Void)? var beginAudioOuputSelection: ((Bool) -> Void)?
var acceptCall: (() -> Void)? var acceptCall: (() -> Void)?
var endCall: (() -> Void)? var endCall: (() -> Void)?
var toggleVideo: (() -> Void)? var setIsVideoPaused: ((Bool) -> Void)?
var back: (() -> Void)? var back: (() -> Void)?
var presentCallRating: ((CallId) -> Void)? var presentCallRating: ((CallId) -> Void)?
var callEnded: ((Bool) -> Void)? var callEnded: ((Bool) -> Void)?
var dismissedInteractively: (() -> Void)? var dismissedInteractively: (() -> Void)?
var present: ((ViewController) -> Void)? var present: ((ViewController) -> Void)?
var setIsVideoPaused: ((Bool) -> Void)?
init(sharedContext: SharedAccountContext, account: Account, presentationData: PresentationData, statusBar: StatusBar, debugInfo: Signal<(String, String), NoError>, shouldStayHiddenUntilConnection: Bool = false, easyDebugAccess: Bool, call: PresentationCall) { init(sharedContext: SharedAccountContext, account: Account, presentationData: PresentationData, statusBar: StatusBar, debugInfo: Signal<(String, String), NoError>, shouldStayHiddenUntilConnection: Bool = false, easyDebugAccess: Bool, call: PresentationCall) {
self.sharedContext = sharedContext self.sharedContext = sharedContext
@ -171,9 +96,6 @@ final class LegacyCallControllerNode: ASDisplayNode, CallControllerNodeProtocol
self.statusNode = LegacyCallControllerStatusNode() self.statusNode = LegacyCallControllerStatusNode()
self.videoPausedNode = ImmediateTextNode()
self.videoPausedNode.alpha = 0.0
self.buttonsNode = LegacyCallControllerButtonsNode(strings: self.presentationData.strings) self.buttonsNode = LegacyCallControllerButtonsNode(strings: self.presentationData.strings)
self.keyButtonNode = HighlightableButtonNode() self.keyButtonNode = HighlightableButtonNode()
@ -208,7 +130,6 @@ final class LegacyCallControllerNode: ASDisplayNode, CallControllerNodeProtocol
self.containerNode.addSubnode(self.imageNode) self.containerNode.addSubnode(self.imageNode)
self.containerNode.addSubnode(self.dimNode) self.containerNode.addSubnode(self.dimNode)
self.containerNode.addSubnode(self.statusNode) self.containerNode.addSubnode(self.statusNode)
self.containerNode.addSubnode(self.videoPausedNode)
self.containerNode.addSubnode(self.buttonsNode) self.containerNode.addSubnode(self.buttonsNode)
self.containerNode.addSubnode(self.keyButtonNode) self.containerNode.addSubnode(self.keyButtonNode)
self.containerNode.addSubnode(self.backButtonArrowNode) self.containerNode.addSubnode(self.backButtonArrowNode)
@ -230,10 +151,6 @@ final class LegacyCallControllerNode: ASDisplayNode, CallControllerNodeProtocol
self?.acceptCall?() self?.acceptCall?()
} }
self.buttonsNode.rotateCamera = { [weak self] in
self?.call.switchVideoCamera()
}
self.keyButtonNode.addTarget(self, action: #selector(self.keyPressed), forControlEvents: .touchUpInside) self.keyButtonNode.addTarget(self, action: #selector(self.keyPressed), forControlEvents: .touchUpInside)
self.backButtonNode.addTarget(self, action: #selector(self.backPressed), forControlEvents: .touchUpInside) self.backButtonNode.addTarget(self, action: #selector(self.backPressed), forControlEvents: .touchUpInside)
@ -270,8 +187,6 @@ final class LegacyCallControllerNode: ASDisplayNode, CallControllerNodeProtocol
} }
} }
self.videoPausedNode.attributedText = NSAttributedString(string: self.presentationData.strings.Call_RemoteVideoPaused(peer.compactDisplayTitle).0, font: Font.regular(17.0), textColor: .white)
if let (layout, navigationBarHeight) = self.validLayout { if let (layout, navigationBarHeight) = self.validLayout {
self.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .immediate) self.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .immediate)
} }
@ -291,84 +206,6 @@ final class LegacyCallControllerNode: ASDisplayNode, CallControllerNodeProtocol
let statusValue: LegacyCallControllerStatusValue let statusValue: LegacyCallControllerStatusValue
var statusReception: Int32? var statusReception: Int32?
switch callState.videoState {
case .active:
if !self.incomingVideoViewRequested {
self.incomingVideoViewRequested = true
self.call.makeIncomingVideoView(completion: { [weak self] incomingVideoView in
guard let strongSelf = self else {
return
}
if let incomingVideoView = incomingVideoView {
strongSelf.setCurrentAudioOutput?(.speaker)
let incomingVideoNode = IncomingVideoNode(videoView: incomingVideoView.view)
strongSelf.incomingVideoNode = incomingVideoNode
strongSelf.containerNode.insertSubnode(incomingVideoNode, aboveSubnode: strongSelf.dimNode)
strongSelf.statusNode.isHidden = true
if let (layout, navigationBarHeight) = strongSelf.validLayout {
strongSelf.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .immediate)
}
}
})
}
default:
break
}
switch callState.videoState {
case .active, .outgoingRequested:
if !self.outgoingVideoViewRequested {
self.outgoingVideoViewRequested = true
self.call.makeOutgoingVideoView(completion: { [weak self] outgoingVideoView in
guard let strongSelf = self else {
return
}
if let outgoingVideoView = outgoingVideoView?.view {
outgoingVideoView.backgroundColor = .black
outgoingVideoView.clipsToBounds = true
outgoingVideoView.layer.cornerRadius = 16.0
strongSelf.setCurrentAudioOutput?(.speaker)
let outgoingVideoNode = OutgoingVideoNode(videoView: outgoingVideoView, switchCamera: {
guard let strongSelf = self else {
return
}
strongSelf.call.switchVideoCamera()
})
strongSelf.outgoingVideoNode = outgoingVideoNode
if let incomingVideoNode = strongSelf.incomingVideoNode {
strongSelf.containerNode.insertSubnode(outgoingVideoNode, aboveSubnode: incomingVideoNode)
} else {
strongSelf.containerNode.insertSubnode(outgoingVideoNode, aboveSubnode: strongSelf.dimNode)
}
if let (layout, navigationBarHeight) = strongSelf.validLayout {
strongSelf.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .immediate)
}
}
})
}
default:
break
}
if let incomingVideoNode = self.incomingVideoNode {
let isActive: Bool
switch callState.remoteVideoState {
case .inactive:
isActive = false
case .active:
isActive = true
}
incomingVideoNode.updateIsBlurred(isBlurred: !isActive)
if isActive != self.videoPausedNode.alpha.isZero {
if isActive {
self.videoPausedNode.alpha = 0.0
self.videoPausedNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3)
} else {
self.videoPausedNode.alpha = 1.0
self.videoPausedNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
}
}
}
switch callState.state { switch callState.state {
case .waiting, .connecting: case .waiting, .connecting:
statusValue = .text(self.presentationData.strings.Call_StatusConnecting) statusValue = .text(self.presentationData.strings.Call_StatusConnecting)
@ -592,33 +429,10 @@ final class LegacyCallControllerNode: ASDisplayNode, CallControllerNodeProtocol
let statusHeight = self.statusNode.updateLayout(constrainedWidth: layout.size.width, transition: transition) let statusHeight = self.statusNode.updateLayout(constrainedWidth: layout.size.width, transition: transition)
transition.updateFrame(node: self.statusNode, frame: CGRect(origin: CGPoint(x: 0.0, y: statusOffset), size: CGSize(width: layout.size.width, height: statusHeight))) transition.updateFrame(node: self.statusNode, frame: CGRect(origin: CGPoint(x: 0.0, y: statusOffset), size: CGSize(width: layout.size.width, height: statusHeight)))
let videoPausedSize = self.videoPausedNode.updateLayout(CGSize(width: layout.size.width - 16.0, height: 100.0))
transition.updateFrame(node: self.videoPausedNode, frame: CGRect(origin: CGPoint(x: floor((layout.size.width - videoPausedSize.width) / 2.0), y: floor((layout.size.height - videoPausedSize.height) / 2.0)), size: videoPausedSize))
self.buttonsNode.updateLayout(constrainedWidth: layout.size.width, transition: transition) self.buttonsNode.updateLayout(constrainedWidth: layout.size.width, transition: transition)
let buttonsOriginY: CGFloat = layout.size.height - (buttonsOffset - 40.0) - buttonsHeight - layout.intrinsicInsets.bottom let buttonsOriginY: CGFloat = layout.size.height - (buttonsOffset - 40.0) - buttonsHeight - layout.intrinsicInsets.bottom
transition.updateFrame(node: self.buttonsNode, frame: CGRect(origin: CGPoint(x: 0.0, y: buttonsOriginY), size: CGSize(width: layout.size.width, height: buttonsHeight))) transition.updateFrame(node: self.buttonsNode, frame: CGRect(origin: CGPoint(x: 0.0, y: buttonsOriginY), size: CGSize(width: layout.size.width, height: buttonsHeight)))
var outgoingVideoTransition = transition
if let incomingVideoNode = self.incomingVideoNode {
if incomingVideoNode.frame.width.isZero, let outgoingVideoNode = self.outgoingVideoNode, !outgoingVideoNode.frame.width.isZero, !transition.isAnimated {
outgoingVideoTransition = .animated(duration: 0.3, curve: .easeInOut)
}
incomingVideoNode.frame = CGRect(origin: CGPoint(), size: layout.size)
incomingVideoNode.updateLayout(size: layout.size)
}
if let outgoingVideoNode = self.outgoingVideoNode {
if self.incomingVideoNode == nil {
outgoingVideoNode.frame = CGRect(origin: CGPoint(), size: layout.size)
outgoingVideoNode.updateLayout(size: layout.size, isExpanded: true, transition: transition)
} else {
let outgoingSize = layout.size.aspectFitted(CGSize(width: 200.0, height: 200.0))
let outgoingFrame = CGRect(origin: CGPoint(x: layout.size.width - 16.0 - outgoingSize.width, y: buttonsOriginY - 32.0 - outgoingSize.height), size: outgoingSize)
outgoingVideoTransition.updateFrame(node: outgoingVideoNode, frame: outgoingFrame)
outgoingVideoNode.updateLayout(size: outgoingFrame.size, isExpanded: false, transition: outgoingVideoTransition)
}
}
let keyTextSize = self.keyButtonNode.frame.size let keyTextSize = self.keyButtonNode.frame.size
transition.updateFrame(node: self.keyButtonNode, frame: CGRect(origin: CGPoint(x: layout.size.width - keyTextSize.width - 8.0, y: navigationOffset + 8.0), size: keyTextSize)) transition.updateFrame(node: self.keyButtonNode, frame: CGRect(origin: CGPoint(x: layout.size.width - keyTextSize.width - 8.0, y: navigationOffset + 8.0), size: keyTextSize))

View File

@ -190,7 +190,11 @@ public final class PresentationCallImpl: PresentationCall {
private var callWasActive = false private var callWasActive = false
private var shouldPresentCallRating = false private var shouldPresentCallRating = false
private var videoWasActive = false
private var previousVideoState: PresentationCallState.VideoState?
private var previousRemoteVideoState: PresentationCallState.RemoteVideoState?
private var previousRemoteAudioState: PresentationCallState.RemoteAudioState?
private var previousRemoteBatteryLevel: PresentationCallState.RemoteBatteryLevel?
private var sessionStateDisposable: Disposable? private var sessionStateDisposable: Disposable?
@ -291,9 +295,9 @@ public final class PresentationCallImpl: PresentationCall {
self.enableHighBitrateVideoCalls = enableHighBitrateVideoCalls self.enableHighBitrateVideoCalls = enableHighBitrateVideoCalls
if self.isVideo { if self.isVideo {
self.videoCapturer = OngoingCallVideoCapturer() self.videoCapturer = OngoingCallVideoCapturer()
self.statePromise.set(PresentationCallState(state: isOutgoing ? .waiting : .ringing, videoState: .outgoingRequested, remoteVideoState: .active, remoteBatteryLevel: .normal)) self.statePromise.set(PresentationCallState(state: isOutgoing ? .waiting : .ringing, videoState: .active, remoteVideoState: .inactive, remoteAudioState: .active, remoteBatteryLevel: .normal))
} else { } else {
self.statePromise.set(PresentationCallState(state: isOutgoing ? .waiting : .ringing, videoState: self.isVideoPossible ? .possible : .notAvailable, remoteAudioState: .active, remoteBatteryLevel: .normal)) self.statePromise.set(PresentationCallState(state: isOutgoing ? .waiting : .ringing, videoState: self.isVideoPossible ? .inactive : .notAvailable, remoteVideoState: .inactive, remoteAudioState: .active, remoteBatteryLevel: .normal))
} }
self.serializedData = serializedData self.serializedData = serializedData
@ -376,7 +380,7 @@ public final class PresentationCallImpl: PresentationCall {
audioSessionActive = callKitIntegration.audioSessionActive audioSessionActive = callKitIntegration.audioSessionActive
|> filter { $0 } |> filter { $0 }
|> timeout(2.0, queue: Queue.mainQueue(), alternate: Signal { subscriber in |> timeout(2.0, queue: Queue.mainQueue(), alternate: Signal { subscriber in
if let strongSelf = self, let audioSessionControl = strongSelf.audioSessionControl { if let strongSelf = self, let _ = strongSelf.audioSessionControl {
//audioSessionControl.activate({ _ in }) //audioSessionControl.activate({ _ in })
} }
subscriber.putNext(true) subscriber.putNext(true)
@ -458,26 +462,32 @@ public final class PresentationCallImpl: PresentationCall {
let mappedVideoState: PresentationCallState.VideoState let mappedVideoState: PresentationCallState.VideoState
let mappedRemoteVideoState: PresentationCallState.RemoteVideoState let mappedRemoteVideoState: PresentationCallState.RemoteVideoState
let mappedRemoteAudioState: PresentationCallState.RemoteAudioState
let mappedRemoteBatteryLevel: PresentationCallState.RemoteBatteryLevel let mappedRemoteBatteryLevel: PresentationCallState.RemoteBatteryLevel
if let callContextState = callContextState { if let callContextState = callContextState {
switch callContextState.videoState { switch callContextState.videoState {
case .notAvailable: case .notAvailable:
mappedVideoState = .notAvailable mappedVideoState = .notAvailable
case .possible:
mappedVideoState = .possible
case .outgoingRequested:
mappedVideoState = .outgoingRequested
case let .incomingRequested(sendsVideo):
mappedVideoState = .incomingRequested(sendsVideo: sendsVideo)
case .active: case .active:
mappedVideoState = .active mappedVideoState = .active
self.videoWasActive = true case .inactive:
mappedVideoState = .inactive
case .paused:
mappedVideoState = .paused
} }
switch callContextState.remoteVideoState { switch callContextState.remoteVideoState {
case .inactive: case .inactive:
mappedRemoteVideoState = .inactive mappedRemoteVideoState = .inactive
case .active: case .active:
mappedRemoteVideoState = .active mappedRemoteVideoState = .active
case .paused:
mappedRemoteVideoState = .paused
}
switch callContextState.remoteAudioState {
case .active:
mappedRemoteAudioState = .active
case .muted:
mappedRemoteAudioState = .muted
} }
switch callContextState.remoteBatteryLevel { switch callContextState.remoteBatteryLevel {
case .normal: case .normal:
@ -485,25 +495,38 @@ public final class PresentationCallImpl: PresentationCall {
case .low: case .low:
mappedRemoteBatteryLevel = .low mappedRemoteBatteryLevel = .low
} }
self.previousVideoState = mappedVideoState
self.previousRemoteVideoState = mappedRemoteVideoState
self.previousRemoteAudioState = mappedRemoteAudioState
self.previousRemoteBatteryLevel = mappedRemoteBatteryLevel
} else {
if let previousVideoState = self.previousVideoState {
mappedVideoState = previousVideoState
} else { } else {
if self.isVideo { if self.isVideo {
mappedVideoState = .outgoingRequested mappedVideoState = .active
} else if self.isVideoPossible { } else if self.isVideoPossible {
mappedVideoState = .possible mappedVideoState = .inactive
} else { } else {
mappedVideoState = .notAvailable mappedVideoState = .notAvailable
} }
if self.videoWasActive {
mappedRemoteVideoState = .active
} else {
mappedRemoteVideoState = .inactive
} }
mappedRemoteVideoState = .inactive
if let previousRemoteAudioState = self.previousRemoteAudioState {
mappedRemoteAudioState = previousRemoteAudioState
} else {
mappedRemoteAudioState = .active
}
if let previousRemoteBatteryLevel = self.previousRemoteBatteryLevel {
mappedRemoteBatteryLevel = previousRemoteBatteryLevel
} else {
mappedRemoteBatteryLevel = .normal mappedRemoteBatteryLevel = .normal
} }
}
switch sessionState.state { switch sessionState.state {
case .ringing: case .ringing:
presentationState = PresentationCallState(state: .ringing, videoState: mappedVideoState, remoteVideoState: mappedRemoteVideoState, remoteBatteryLevel: mappedRemoteBatteryLevel) presentationState = PresentationCallState(state: .ringing, videoState: mappedVideoState, remoteVideoState: mappedRemoteVideoState, remoteAudioState: mappedRemoteAudioState, remoteBatteryLevel: mappedRemoteBatteryLevel)
if previous == nil || previousControl == nil { if previous == nil || previousControl == nil {
if !self.reportedIncomingCall { if !self.reportedIncomingCall {
self.reportedIncomingCall = true self.reportedIncomingCall = true
@ -530,19 +553,19 @@ public final class PresentationCallImpl: PresentationCall {
} }
case .accepting: case .accepting:
self.callWasActive = true self.callWasActive = true
presentationState = PresentationCallState(state: .connecting(nil), videoState: mappedVideoState, remoteVideoState: mappedRemoteVideoState, remoteBatteryLevel: mappedRemoteBatteryLevel) presentationState = PresentationCallState(state: .connecting(nil), videoState: mappedVideoState, remoteVideoState: mappedRemoteVideoState, remoteAudioState: mappedRemoteAudioState, remoteBatteryLevel: mappedRemoteBatteryLevel)
case .dropping: case .dropping:
presentationState = PresentationCallState(state: .terminating, videoState: mappedVideoState, remoteVideoState: mappedRemoteVideoState, remoteBatteryLevel: mappedRemoteBatteryLevel) presentationState = PresentationCallState(state: .terminating, videoState: mappedVideoState, remoteVideoState: .inactive, remoteAudioState: mappedRemoteAudioState, remoteBatteryLevel: mappedRemoteBatteryLevel)
case let .terminated(id, reason, options): case let .terminated(id, reason, options):
presentationState = PresentationCallState(state: .terminated(id, reason, self.callWasActive && (options.contains(.reportRating) || self.shouldPresentCallRating)), videoState: mappedVideoState, remoteVideoState: mappedRemoteVideoState, remoteBatteryLevel: mappedRemoteBatteryLevel) 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): case let .requesting(ringing):
presentationState = PresentationCallState(state: .requesting(ringing), videoState: mappedVideoState, remoteVideoState: mappedRemoteVideoState, remoteBatteryLevel: mappedRemoteBatteryLevel) presentationState = PresentationCallState(state: .requesting(ringing), videoState: mappedVideoState, remoteVideoState: mappedRemoteVideoState, remoteAudioState: mappedRemoteAudioState, remoteBatteryLevel: mappedRemoteBatteryLevel)
case let .active(_, _, keyVisualHash, _, _, _, _): case let .active(_, _, keyVisualHash, _, _, _, _):
self.callWasActive = true self.callWasActive = true
if let callContextState = callContextState { if let callContextState = callContextState {
switch callContextState.state { switch callContextState.state {
case .initializing: case .initializing:
presentationState = PresentationCallState(state: .connecting(keyVisualHash), videoState: mappedVideoState, remoteVideoState: mappedRemoteVideoState, remoteBatteryLevel: mappedRemoteBatteryLevel) presentationState = PresentationCallState(state: .connecting(keyVisualHash), videoState: mappedVideoState, remoteVideoState: mappedRemoteVideoState, remoteAudioState: mappedRemoteAudioState, remoteBatteryLevel: mappedRemoteBatteryLevel)
case .failed: case .failed:
presentationState = nil presentationState = nil
self.callSessionManager.drop(internalId: self.internalId, reason: .disconnect, debugLog: .single(nil)) self.callSessionManager.drop(internalId: self.internalId, reason: .disconnect, debugLog: .single(nil))
@ -554,7 +577,7 @@ public final class PresentationCallImpl: PresentationCall {
timestamp = CFAbsoluteTimeGetCurrent() timestamp = CFAbsoluteTimeGetCurrent()
self.activeTimestamp = timestamp self.activeTimestamp = timestamp
} }
presentationState = PresentationCallState(state: .active(timestamp, reception, keyVisualHash), videoState: mappedVideoState, remoteVideoState: mappedRemoteVideoState, remoteBatteryLevel: mappedRemoteBatteryLevel) presentationState = PresentationCallState(state: .active(timestamp, reception, keyVisualHash), videoState: mappedVideoState, remoteVideoState: mappedRemoteVideoState, remoteAudioState: mappedRemoteAudioState, remoteBatteryLevel: mappedRemoteBatteryLevel)
case .reconnecting: case .reconnecting:
let timestamp: Double let timestamp: Double
if let activeTimestamp = self.activeTimestamp { if let activeTimestamp = self.activeTimestamp {
@ -563,10 +586,10 @@ public final class PresentationCallImpl: PresentationCall {
timestamp = CFAbsoluteTimeGetCurrent() timestamp = CFAbsoluteTimeGetCurrent()
self.activeTimestamp = timestamp self.activeTimestamp = timestamp
} }
presentationState = PresentationCallState(state: .reconnecting(timestamp, reception, keyVisualHash), videoState: mappedVideoState, remoteVideoState: mappedRemoteVideoState, remoteBatteryLevel: mappedRemoteBatteryLevel) presentationState = PresentationCallState(state: .reconnecting(timestamp, reception, keyVisualHash), videoState: mappedVideoState, remoteVideoState: mappedRemoteVideoState, remoteAudioState: mappedRemoteAudioState, remoteBatteryLevel: mappedRemoteBatteryLevel)
} }
} else { } else {
presentationState = PresentationCallState(state: .connecting(keyVisualHash), videoState: mappedVideoState, remoteVideoState: mappedRemoteVideoState, remoteBatteryLevel: mappedRemoteBatteryLevel) presentationState = PresentationCallState(state: .connecting(keyVisualHash), videoState: mappedVideoState, remoteVideoState: mappedRemoteVideoState, remoteAudioState: mappedRemoteAudioState, remoteBatteryLevel: mappedRemoteBatteryLevel)
} }
} }
@ -665,7 +688,9 @@ public final class PresentationCallImpl: PresentationCall {
private func updateTone(_ state: PresentationCallState, callContextState: OngoingCallContextState?, previous: CallSession?) { private func updateTone(_ state: PresentationCallState, callContextState: OngoingCallContextState?, previous: CallSession?) {
var tone: PresentationCallTone? var tone: PresentationCallTone?
if let callContextState = callContextState, case .reconnecting = callContextState.state { if let callContextState = callContextState, case .reconnecting = callContextState.state {
if !self.isVideo {
tone = .connecting tone = .connecting
}
} else if let previous = previous { } else if let previous = previous {
switch previous.state { switch previous.state {
case .accepting, .active, .dropping, .requesting: case .accepting, .active, .dropping, .requesting:
@ -674,8 +699,10 @@ public final class PresentationCallImpl: PresentationCall {
if case .requesting = previous.state { if case .requesting = previous.state {
tone = .ringing tone = .ringing
} else { } else {
if !self.isVideo {
tone = .connecting tone = .connecting
} }
}
case .requesting(true): case .requesting(true):
tone = .ringing tone = .ringing
case let .terminated(_, reason, _): case let .terminated(_, reason, _):
@ -789,13 +816,10 @@ public final class PresentationCallImpl: PresentationCall {
} }
} }
public func acceptVideo() { public func disableVideo() {
if self.videoCapturer == nil { if let _ = self.videoCapturer {
let videoCapturer = OngoingCallVideoCapturer() self.videoCapturer = nil
self.videoCapturer = videoCapturer self.ongoingContext?.disableVideo()
}
if let videoCapturer = self.videoCapturer {
self.ongoingContext?.acceptVideo(videoCapturer)
} }
} }

View File

@ -10,6 +10,7 @@ import TelegramAudio
import TelegramVoip import TelegramVoip
import TelegramUIPreferences import TelegramUIPreferences
import AccountContext import AccountContext
import CallKit
private func callKitIntegrationIfEnabled(_ integration: CallKitIntegration?, settings: VoiceCallSettings?) -> CallKitIntegration? { private func callKitIntegrationIfEnabled(_ integration: CallKitIntegration?, settings: VoiceCallSettings?) -> CallKitIntegration? {
let enabled = settings?.enableSystemIntegration ?? true let enabled = settings?.enableSystemIntegration ?? true
@ -342,8 +343,22 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
public func requestCall(context: AccountContext, peerId: PeerId, isVideo: Bool, endCurrentIfAny: Bool) -> RequestCallResult { public func requestCall(context: AccountContext, peerId: PeerId, isVideo: Bool, endCurrentIfAny: Bool) -> RequestCallResult {
let account = context.account let account = context.account
if let call = self.currentCall, !endCurrentIfAny { var alreadyInCall: Bool = false
return .alreadyInProgress(call.peerId) var alreadyInCallWithPeerId: PeerId?
if let call = self.currentCall {
alreadyInCall = true
alreadyInCallWithPeerId = call.peerId
} else {
if #available(iOS 10.0, *) {
if CXCallObserver().calls.contains(where: { $0.hasEnded == false }) {
alreadyInCall = true
}
}
}
if alreadyInCall, !endCurrentIfAny {
return .alreadyInProgress(alreadyInCallWithPeerId)
} }
if let _ = callKitIntegrationIfEnabled(self.callKitIntegration, settings: self.callSettings) { if let _ = callKitIntegrationIfEnabled(self.callKitIntegration, settings: self.callSettings) {
let begin: () -> Void = { [weak self] in let begin: () -> Void = { [weak self] in
@ -460,12 +475,12 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
return .single(false) return .single(false)
} }
let request = account.postbox.transaction { transaction -> VideoCallsConfiguration in let request = account.postbox.transaction { transaction -> (VideoCallsConfiguration, CachedUserData?) in
let appConfiguration: AppConfiguration = transaction.getPreferencesEntry(key: PreferencesKeys.appConfiguration) as? AppConfiguration ?? AppConfiguration.defaultValue let appConfiguration: AppConfiguration = transaction.getPreferencesEntry(key: PreferencesKeys.appConfiguration) as? AppConfiguration ?? AppConfiguration.defaultValue
return VideoCallsConfiguration(appConfiguration: appConfiguration) return (VideoCallsConfiguration(appConfiguration: appConfiguration), transaction.getPeerCachedData(peerId: peerId) as? CachedUserData)
} }
|> mapToSignal { callsConfiguration -> Signal<CallSessionInternalId, NoError> in |> mapToSignal { callsConfiguration, cachedUserData -> Signal<CallSessionInternalId, NoError> in
let isVideoPossible: Bool var isVideoPossible: Bool
switch callsConfiguration.videoCallsSupport { switch callsConfiguration.videoCallsSupport {
case .disabled: case .disabled:
isVideoPossible = isVideo isVideoPossible = isVideo
@ -474,15 +489,23 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
case .onlyVideo: case .onlyVideo:
isVideoPossible = isVideo isVideoPossible = isVideo
} }
if let cachedUserData = cachedUserData, cachedUserData.videoCallsAvailable {
} else {
isVideoPossible = false
}
return account.callSessionManager.request(peerId: peerId, isVideo: isVideo, enableVideo: isVideoPossible, internalId: internalId) return account.callSessionManager.request(peerId: peerId, isVideo: isVideo, enableVideo: isVideoPossible, internalId: internalId)
} }
let cachedUserData = account.postbox.transaction { transaction -> CachedUserData? in
return transaction.getPeerCachedData(peerId: peerId) as? CachedUserData
}
return (combineLatest(queue: .mainQueue(), request, networkType |> take(1), account.postbox.peerView(id: peerId) |> map { peerView -> Bool in return (combineLatest(queue: .mainQueue(), request, networkType |> take(1), account.postbox.peerView(id: peerId) |> map { peerView -> Bool in
return peerView.peerIsContact return peerView.peerIsContact
} |> take(1), account.postbox.preferencesView(keys: [PreferencesKeys.voipConfiguration, ApplicationSpecificPreferencesKeys.voipDerivedState, PreferencesKeys.appConfiguration]) |> take(1), accountManager.sharedData(keys: [SharedDataKeys.autodownloadSettings, ApplicationSpecificSharedDataKeys.experimentalUISettings]) |> take(1)) } |> take(1), account.postbox.preferencesView(keys: [PreferencesKeys.voipConfiguration, ApplicationSpecificPreferencesKeys.voipDerivedState, PreferencesKeys.appConfiguration]) |> take(1), accountManager.sharedData(keys: [SharedDataKeys.autodownloadSettings, ApplicationSpecificSharedDataKeys.experimentalUISettings]) |> take(1), cachedUserData)
|> deliverOnMainQueue |> deliverOnMainQueue
|> beforeNext { internalId, currentNetworkType, isContact, preferences, sharedData in |> beforeNext { internalId, currentNetworkType, isContact, preferences, sharedData, cachedUserData in
if let strongSelf = self, accessEnabled { if let strongSelf = self, accessEnabled {
if let currentCall = strongSelf.currentCall { if let currentCall = strongSelf.currentCall {
currentCall.rejectBusy() currentCall.rejectBusy()
@ -494,7 +517,7 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
let appConfiguration = preferences.values[PreferencesKeys.appConfiguration] as? AppConfiguration ?? AppConfiguration.defaultValue let appConfiguration = preferences.values[PreferencesKeys.appConfiguration] as? AppConfiguration ?? AppConfiguration.defaultValue
let callsConfiguration = VideoCallsConfiguration(appConfiguration: appConfiguration) let callsConfiguration = VideoCallsConfiguration(appConfiguration: appConfiguration)
let isVideoPossible: Bool var isVideoPossible: Bool
switch callsConfiguration.videoCallsSupport { switch callsConfiguration.videoCallsSupport {
case .disabled: case .disabled:
isVideoPossible = isVideo isVideoPossible = isVideo
@ -503,6 +526,10 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
case .onlyVideo: case .onlyVideo:
isVideoPossible = isVideo isVideoPossible = isVideo
} }
if let cachedUserData = cachedUserData, cachedUserData.videoCallsAvailable {
} else {
isVideoPossible = false
}
let experimentalSettings = sharedData.entries[ApplicationSpecificSharedDataKeys.experimentalUISettings] as? ExperimentalUISettings ?? .defaultSettings let experimentalSettings = sharedData.entries[ApplicationSpecificSharedDataKeys.experimentalUISettings] as? ExperimentalUISettings ?? .defaultSettings

View File

@ -1226,6 +1226,23 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
} else { } else {
let presentationData = context.sharedContext.currentPresentationData.with { $0 } let presentationData = context.sharedContext.currentPresentationData.with { $0 }
let _ = (context.account.postbox.transaction { transaction -> (Peer?, Peer?) in let _ = (context.account.postbox.transaction { transaction -> (Peer?, Peer?) in
return (transaction.getPeer(peerId), currentPeerId.flatMap(transaction.getPeer))
} |> deliverOnMainQueue).start(next: { [weak self] peer, current in
if let peer = peer {
if let strongSelf = self, let current = current {
strongSelf.present(textAlertController(context: strongSelf.context, title: presentationData.strings.Call_CallInProgressTitle, text: presentationData.strings.Call_CallInProgressMessage(current.compactDisplayTitle, peer.compactDisplayTitle).0, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .genericAction, title: presentationData.strings.Common_OK, action: {
if let strongSelf = self {
let _ = strongSelf.context.sharedContext.callManager?.requestCall(context: context, peerId: peerId, isVideo: isVideo, endCurrentIfAny: true)
}
})]), in: .window(.root))
} else {
strongSelf.present(textAlertController(context: strongSelf.context, title: presentationData.strings.Call_CallInProgressTitle, text: presentationData.strings.Call_ExternalCallInProgressMessage, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_OK, action: {
})]), in: .window(.root))
}
}
})
/*let _ = (context.account.postbox.transaction { transaction -> (Peer?, Peer?) in
return (transaction.getPeer(peer.id), transaction.getPeer(currentPeerId)) return (transaction.getPeer(peer.id), transaction.getPeer(currentPeerId))
} }
|> deliverOnMainQueue).start(next: { peer, current in |> deliverOnMainQueue).start(next: { peer, current in
@ -1234,7 +1251,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
let _ = context.sharedContext.callManager?.requestCall(context: context, peerId: peer.id, isVideo: isVideo, endCurrentIfAny: true) let _ = context.sharedContext.callManager?.requestCall(context: context, peerId: peer.id, isVideo: isVideo, endCurrentIfAny: true)
})]), in: .window(.root)) })]), in: .window(.root))
} }
}) })*/
} }
} }
}) })

View File

@ -3158,7 +3158,25 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
if currentPeerId == peer.id { if currentPeerId == peer.id {
self.context.sharedContext.navigateToCurrentCall() self.context.sharedContext.navigateToCurrentCall()
} else { } else {
let presentationData = self.presentationData
let _ = (self.context.account.postbox.transaction { transaction -> (Peer?, Peer?) in let _ = (self.context.account.postbox.transaction { transaction -> (Peer?, Peer?) in
return (transaction.getPeer(peer.id), currentPeerId.flatMap(transaction.getPeer))
} |> deliverOnMainQueue).start(next: { [weak self] peer, current in
if let peer = peer {
if let strongSelf = self, let current = current {
strongSelf.controller?.present(textAlertController(context: strongSelf.context, title: presentationData.strings.Call_CallInProgressTitle, text: presentationData.strings.Call_CallInProgressMessage(current.compactDisplayTitle, peer.compactDisplayTitle).0, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .genericAction, title: presentationData.strings.Common_OK, action: {
if let strongSelf = self {
let _ = strongSelf.context.sharedContext.callManager?.requestCall(context: strongSelf.context, peerId: peer.id, isVideo: isVideo, endCurrentIfAny: true)
}
})]), in: .window(.root))
} else if let strongSelf = self {
strongSelf.controller?.present(textAlertController(context: strongSelf.context, title: presentationData.strings.Call_CallInProgressTitle, text: presentationData.strings.Call_ExternalCallInProgressMessage, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_OK, action: {
})]), in: .window(.root))
}
}
})
/*let _ = (self.context.account.postbox.transaction { transaction -> (Peer?, Peer?) in
return (transaction.getPeer(peer.id), transaction.getPeer(currentPeerId)) return (transaction.getPeer(peer.id), transaction.getPeer(currentPeerId))
} }
|> deliverOnMainQueue).start(next: { [weak self] peer, current in |> deliverOnMainQueue).start(next: { [weak self] peer, current in
@ -3173,7 +3191,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
let _ = strongSelf.context.sharedContext.callManager?.requestCall(context: strongSelf.context, peerId: peer.id, isVideo: isVideo, endCurrentIfAny: true) let _ = strongSelf.context.sharedContext.callManager?.requestCall(context: strongSelf.context, peerId: peer.id, isVideo: isVideo, endCurrentIfAny: true)
})]), in: .window(.root)) })]), in: .window(.root))
} }
}) })*/
} }
} }
} }

View File

@ -108,15 +108,20 @@ public struct OngoingCallContextState: Equatable {
public enum VideoState: Equatable { public enum VideoState: Equatable {
case notAvailable case notAvailable
case possible case inactive
case outgoingRequested
case incomingRequested(sendsVideo: Bool)
case active case active
case paused
} }
public enum RemoteVideoState: Equatable { public enum RemoteVideoState: Equatable {
case inactive case inactive
case active case active
case paused
}
public enum RemoteAudioState: Equatable {
case active
case muted
} }
public enum RemoteBatteryLevel: Equatable { public enum RemoteBatteryLevel: Equatable {
@ -127,6 +132,7 @@ public struct OngoingCallContextState: Equatable {
public let state: State public let state: State
public let videoState: VideoState public let videoState: VideoState
public let remoteVideoState: RemoteVideoState public let remoteVideoState: RemoteVideoState
public let remoteAudioState: RemoteAudioState
public let remoteBatteryLevel: RemoteBatteryLevel public let remoteBatteryLevel: RemoteBatteryLevel
} }
@ -257,7 +263,7 @@ private protocol OngoingCallThreadLocalContextProtocol: class {
func nativeSetNetworkType(_ type: NetworkType) func nativeSetNetworkType(_ type: NetworkType)
func nativeSetIsMuted(_ value: Bool) func nativeSetIsMuted(_ value: Bool)
func nativeRequestVideo(_ capturer: OngoingCallVideoCapturer) func nativeRequestVideo(_ capturer: OngoingCallVideoCapturer)
func nativeAcceptVideo(_ capturer: OngoingCallVideoCapturer) func nativeDisableVideo()
func nativeStop(_ completion: @escaping (String?, Int64, Int64, Int64, Int64) -> Void) func nativeStop(_ completion: @escaping (String?, Int64, Int64, Int64, Int64) -> Void)
func nativeBeginTermination() func nativeBeginTermination()
func nativeDebugInfo() -> String func nativeDebugInfo() -> String
@ -292,7 +298,7 @@ extension OngoingCallThreadLocalContext: OngoingCallThreadLocalContextProtocol {
func nativeRequestVideo(_ capturer: OngoingCallVideoCapturer) { func nativeRequestVideo(_ capturer: OngoingCallVideoCapturer) {
} }
func nativeAcceptVideo(_ capturer: OngoingCallVideoCapturer) { func nativeDisableVideo() {
} }
func nativeSwitchVideoCamera() { func nativeSwitchVideoCamera() {
@ -371,8 +377,8 @@ extension OngoingCallThreadLocalContextWebrtc: OngoingCallThreadLocalContextProt
self.requestVideo(capturer.impl) self.requestVideo(capturer.impl)
} }
func nativeAcceptVideo(_ capturer: OngoingCallVideoCapturer) { func nativeDisableVideo() {
self.acceptVideo(capturer.impl) self.disableVideo()
} }
func nativeDebugInfo() -> String { func nativeDebugInfo() -> String {
@ -580,15 +586,13 @@ public final class OngoingCallContext {
filteredConnections.append(mapped) filteredConnections.append(mapped)
} }
} }
let primaryConnection = filteredConnections.first!
let restConnections = Array(filteredConnections[1...])
let context = OngoingCallThreadLocalContextWebrtc(version: version, queue: OngoingCallThreadLocalContextQueueImpl(queue: queue), proxy: voipProxyServer, networkType: ongoingNetworkTypeForTypeWebrtc(initialNetworkType), dataSaving: ongoingDataSavingForTypeWebrtc(dataSaving), derivedState: derivedState.data, key: key, isOutgoing: isOutgoing, primaryConnection: primaryConnection, alternativeConnections: restConnections, maxLayer: maxLayer, allowP2P: allowP2P, logPath: logPath, sendSignalingData: { [weak callSessionManager] data in let context = OngoingCallThreadLocalContextWebrtc(version: version, queue: OngoingCallThreadLocalContextQueueImpl(queue: queue), proxy: voipProxyServer, networkType: ongoingNetworkTypeForTypeWebrtc(initialNetworkType), dataSaving: ongoingDataSavingForTypeWebrtc(dataSaving), derivedState: derivedState.data, key: key, isOutgoing: isOutgoing, connections: filteredConnections, maxLayer: maxLayer, allowP2P: allowP2P, logPath: logPath, sendSignalingData: { [weak callSessionManager] data in
callSessionManager?.sendSignalingData(internalId: internalId, data: data) callSessionManager?.sendSignalingData(internalId: internalId, data: data)
}, videoCapturer: video?.impl, preferredAspectRatio: Float(preferredAspectRatio), enableHighBitrateVideoCalls: enableHighBitrateVideoCalls) }, videoCapturer: video?.impl, preferredAspectRatio: Float(preferredAspectRatio), enableHighBitrateVideoCalls: enableHighBitrateVideoCalls)
strongSelf.contextRef = Unmanaged.passRetained(OngoingCallThreadLocalContextHolder(context)) strongSelf.contextRef = Unmanaged.passRetained(OngoingCallThreadLocalContextHolder(context))
context.stateChanged = { [weak callSessionManager] state, videoState, remoteVideoState, remoteBatteryLevel, aspectRatio in context.stateChanged = { [weak callSessionManager] state, videoState, remoteVideoState, remoteAudioState, remoteBatteryLevel, _ in
queue.async { queue.async {
guard let strongSelf = self else { guard let strongSelf = self else {
return return
@ -596,16 +600,12 @@ public final class OngoingCallContext {
let mappedState = OngoingCallContextState.State(state) let mappedState = OngoingCallContextState.State(state)
let mappedVideoState: OngoingCallContextState.VideoState let mappedVideoState: OngoingCallContextState.VideoState
switch videoState { switch videoState {
case .possible: case .inactive:
mappedVideoState = .possible mappedVideoState = .inactive
case .incomingRequested:
mappedVideoState = .incomingRequested(sendsVideo: false)
case .incomingRequestedAndActive:
mappedVideoState = .incomingRequested(sendsVideo: true)
case .outgoingRequested:
mappedVideoState = .outgoingRequested
case .active: case .active:
mappedVideoState = .active mappedVideoState = .active
case .paused:
mappedVideoState = .paused
@unknown default: @unknown default:
mappedVideoState = .notAvailable mappedVideoState = .notAvailable
} }
@ -615,9 +615,20 @@ public final class OngoingCallContext {
mappedRemoteVideoState = .inactive mappedRemoteVideoState = .inactive
case .active: case .active:
mappedRemoteVideoState = .active mappedRemoteVideoState = .active
case .paused:
mappedRemoteVideoState = .paused
@unknown default: @unknown default:
mappedRemoteVideoState = .inactive mappedRemoteVideoState = .inactive
} }
let mappedRemoteAudioState: OngoingCallContextState.RemoteAudioState
switch remoteAudioState {
case .active:
mappedRemoteAudioState = .active
case .muted:
mappedRemoteAudioState = .muted
@unknown default:
mappedRemoteAudioState = .active
}
let mappedRemoteBatteryLevel: OngoingCallContextState.RemoteBatteryLevel let mappedRemoteBatteryLevel: OngoingCallContextState.RemoteBatteryLevel
switch remoteBatteryLevel { switch remoteBatteryLevel {
case .normal: case .normal:
@ -631,9 +642,10 @@ public final class OngoingCallContext {
strongSelf.didReportCallAsVideo = true strongSelf.didReportCallAsVideo = true
callSessionManager?.updateCallType(internalId: internalId, type: .video) callSessionManager?.updateCallType(internalId: internalId, type: .video)
} }
strongSelf.contextState.set(.single(OngoingCallContextState(state: mappedState, videoState: mappedVideoState, remoteVideoState: mappedRemoteVideoState, remoteBatteryLevel: mappedRemoteBatteryLevel))) strongSelf.contextState.set(.single(OngoingCallContextState(state: mappedState, videoState: mappedVideoState, remoteVideoState: mappedRemoteVideoState, remoteAudioState: mappedRemoteAudioState, remoteBatteryLevel: mappedRemoteBatteryLevel)))
} }
} }
strongSelf.receptionPromise.set(.single(4))
context.signalBarsChanged = { signalBars in context.signalBarsChanged = { signalBars in
self?.receptionPromise.set(.single(signalBars)) self?.receptionPromise.set(.single(signalBars))
} }
@ -658,7 +670,7 @@ public final class OngoingCallContext {
strongSelf.contextRef = Unmanaged.passRetained(OngoingCallThreadLocalContextHolder(context)) strongSelf.contextRef = Unmanaged.passRetained(OngoingCallThreadLocalContextHolder(context))
context.stateChanged = { state in context.stateChanged = { state in
self?.contextState.set(.single(OngoingCallContextState(state: OngoingCallContextState.State(state), videoState: .notAvailable, remoteVideoState: .inactive, remoteBatteryLevel: .normal))) self?.contextState.set(.single(OngoingCallContextState(state: OngoingCallContextState.State(state), videoState: .notAvailable, remoteVideoState: .inactive, remoteAudioState: .active, remoteBatteryLevel: .normal)))
} }
context.signalBarsChanged = { signalBars in context.signalBarsChanged = { signalBars in
self?.receptionPromise.set(.single(signalBars)) self?.receptionPromise.set(.single(signalBars))
@ -760,9 +772,9 @@ public final class OngoingCallContext {
} }
} }
public func acceptVideo(_ capturer: OngoingCallVideoCapturer) { public func disableVideo() {
self.withContext { context in self.withContext { context in
context.nativeAcceptVideo(capturer) context.nativeDisableVideo()
} }
} }

View File

@ -32,16 +32,20 @@ typedef NS_ENUM(int32_t, OngoingCallStateWebrtc) {
}; };
typedef NS_ENUM(int32_t, OngoingCallVideoStateWebrtc) { typedef NS_ENUM(int32_t, OngoingCallVideoStateWebrtc) {
OngoingCallVideoStatePossible, OngoingCallVideoStateInactive,
OngoingCallVideoStateOutgoingRequested, OngoingCallVideoStateActive,
OngoingCallVideoStateIncomingRequested, OngoingCallVideoStatePaused
OngoingCallVideoStateIncomingRequestedAndActive,
OngoingCallVideoStateActive
}; };
typedef NS_ENUM(int32_t, OngoingCallRemoteVideoStateWebrtc) { typedef NS_ENUM(int32_t, OngoingCallRemoteVideoStateWebrtc) {
OngoingCallRemoteVideoStateInactive, OngoingCallRemoteVideoStateInactive,
OngoingCallRemoteVideoStateActive OngoingCallRemoteVideoStateActive,
OngoingCallRemoteVideoStatePaused
};
typedef NS_ENUM(int32_t, OngoingCallRemoteAudioStateWebrtc) {
OngoingCallRemoteAudioStateMuted,
OngoingCallRemoteAudioStateActive,
}; };
typedef NS_ENUM(int32_t, OngoingCallRemoteBatteryLevelWebrtc) { typedef NS_ENUM(int32_t, OngoingCallRemoteBatteryLevelWebrtc) {
@ -116,10 +120,10 @@ typedef NS_ENUM(int32_t, OngoingCallDataSavingWebrtc) {
+ (int32_t)maxLayer; + (int32_t)maxLayer;
+ (NSArray<NSString *> * _Nonnull)versionsWithIncludeReference:(bool)includeReference; + (NSArray<NSString *> * _Nonnull)versionsWithIncludeReference:(bool)includeReference;
@property (nonatomic, copy) void (^ _Nullable stateChanged)(OngoingCallStateWebrtc, OngoingCallVideoStateWebrtc, OngoingCallRemoteVideoStateWebrtc, OngoingCallRemoteBatteryLevelWebrtc, float); @property (nonatomic, copy) void (^ _Nullable stateChanged)(OngoingCallStateWebrtc, OngoingCallVideoStateWebrtc, OngoingCallRemoteVideoStateWebrtc, OngoingCallRemoteAudioStateWebrtc, OngoingCallRemoteBatteryLevelWebrtc, float);
@property (nonatomic, copy) void (^ _Nullable signalBarsChanged)(int32_t); @property (nonatomic, copy) void (^ _Nullable signalBarsChanged)(int32_t);
- (instancetype _Nonnull)initWithVersion:(NSString * _Nonnull)version queue:(id<OngoingCallThreadLocalContextQueueWebrtc> _Nonnull)queue proxy:(VoipProxyServerWebrtc * _Nullable)proxy networkType:(OngoingCallNetworkTypeWebrtc)networkType dataSaving:(OngoingCallDataSavingWebrtc)dataSaving derivedState:(NSData * _Nonnull)derivedState key:(NSData * _Nonnull)key isOutgoing:(bool)isOutgoing primaryConnection:(OngoingCallConnectionDescriptionWebrtc * _Nonnull)primaryConnection alternativeConnections:(NSArray<OngoingCallConnectionDescriptionWebrtc *> * _Nonnull)alternativeConnections maxLayer:(int32_t)maxLayer allowP2P:(BOOL)allowP2P logPath:(NSString * _Nonnull)logPath sendSignalingData:(void (^ _Nonnull)(NSData * _Nonnull))sendSignalingData videoCapturer:(OngoingCallThreadLocalContextVideoCapturer * _Nullable)videoCapturer preferredAspectRatio:(float)preferredAspectRatio enableHighBitrateVideoCalls:(bool)enableHighBitrateVideoCalls; - (instancetype _Nonnull)initWithVersion:(NSString * _Nonnull)version queue:(id<OngoingCallThreadLocalContextQueueWebrtc> _Nonnull)queue proxy:(VoipProxyServerWebrtc * _Nullable)proxy networkType:(OngoingCallNetworkTypeWebrtc)networkType dataSaving:(OngoingCallDataSavingWebrtc)dataSaving derivedState:(NSData * _Nonnull)derivedState key:(NSData * _Nonnull)key isOutgoing:(bool)isOutgoing connections:(NSArray<OngoingCallConnectionDescriptionWebrtc *> * _Nonnull)connections maxLayer:(int32_t)maxLayer allowP2P:(BOOL)allowP2P logPath:(NSString * _Nonnull)logPath sendSignalingData:(void (^ _Nonnull)(NSData * _Nonnull))sendSignalingData videoCapturer:(OngoingCallThreadLocalContextVideoCapturer * _Nullable)videoCapturer preferredAspectRatio:(float)preferredAspectRatio enableHighBitrateVideoCalls:(bool)enableHighBitrateVideoCalls;
- (void)beginTermination; - (void)beginTermination;
- (void)stop:(void (^_Nullable)(NSString * _Nullable debugLog, int64_t bytesSentWifi, int64_t bytesReceivedWifi, int64_t bytesSentMobile, int64_t bytesReceivedMobile))completion; - (void)stop:(void (^_Nullable)(NSString * _Nullable debugLog, int64_t bytesSentWifi, int64_t bytesReceivedWifi, int64_t bytesSentMobile, int64_t bytesReceivedMobile))completion;
@ -134,7 +138,7 @@ typedef NS_ENUM(int32_t, OngoingCallDataSavingWebrtc) {
- (void)setNetworkType:(OngoingCallNetworkTypeWebrtc)networkType; - (void)setNetworkType:(OngoingCallNetworkTypeWebrtc)networkType;
- (void)makeIncomingVideoView:(void (^_Nonnull)(UIView<OngoingCallThreadLocalContextWebrtcVideoView> * _Nullable))completion; - (void)makeIncomingVideoView:(void (^_Nonnull)(UIView<OngoingCallThreadLocalContextWebrtcVideoView> * _Nullable))completion;
- (void)requestVideo:(OngoingCallThreadLocalContextVideoCapturer * _Nullable)videoCapturer; - (void)requestVideo:(OngoingCallThreadLocalContextVideoCapturer * _Nullable)videoCapturer;
- (void)acceptVideo:(OngoingCallThreadLocalContextVideoCapturer * _Nullable)videoCapturer; - (void)disableVideo;
- (void)addSignalingData:(NSData * _Nonnull)data; - (void)addSignalingData:(NSData * _Nonnull)data;
@end @end

View File

@ -146,7 +146,7 @@
} }
- (void)setIsVideoEnabled:(bool)isVideoEnabled { - (void)setIsVideoEnabled:(bool)isVideoEnabled {
_interface->setIsVideoEnabled(isVideoEnabled); _interface->setState(isVideoEnabled ? tgcalls::VideoState::Active : tgcalls::VideoState::Paused);
} }
- (std::shared_ptr<tgcalls::VideoCaptureInterface>)getInterface { - (std::shared_ptr<tgcalls::VideoCaptureInterface>)getInterface {
@ -161,14 +161,14 @@
remoteRenderer.videoContentMode = UIViewContentModeScaleAspectFill; remoteRenderer.videoContentMode = UIViewContentModeScaleAspectFill;
std::shared_ptr<rtc::VideoSinkInterface<webrtc::VideoFrame>> sink = [remoteRenderer getSink]; std::shared_ptr<rtc::VideoSinkInterface<webrtc::VideoFrame>> sink = [remoteRenderer getSink];
interface->setVideoOutput(sink); interface->setOutput(sink);
completion(remoteRenderer); completion(remoteRenderer);
} else { } else {
GLVideoView *remoteRenderer = [[GLVideoView alloc] initWithFrame:CGRectZero]; GLVideoView *remoteRenderer = [[GLVideoView alloc] initWithFrame:CGRectZero];
std::shared_ptr<rtc::VideoSinkInterface<webrtc::VideoFrame>> sink = [remoteRenderer getSink]; std::shared_ptr<rtc::VideoSinkInterface<webrtc::VideoFrame>> sink = [remoteRenderer getSink];
interface->setVideoOutput(sink); interface->setOutput(sink);
completion(remoteRenderer); completion(remoteRenderer);
} }
@ -214,6 +214,7 @@
bool _connectedOnce; bool _connectedOnce;
OngoingCallRemoteBatteryLevelWebrtc _remoteBatteryLevel; OngoingCallRemoteBatteryLevelWebrtc _remoteBatteryLevel;
OngoingCallRemoteVideoStateWebrtc _remoteVideoState; OngoingCallRemoteVideoStateWebrtc _remoteVideoState;
OngoingCallRemoteAudioStateWebrtc _remoteAudioState;
OngoingCallVideoOrientationWebrtc _remoteVideoOrientation; OngoingCallVideoOrientationWebrtc _remoteVideoOrientation;
__weak UIView<OngoingCallThreadLocalContextWebrtcVideoViewImpl> *_currentRemoteVideoRenderer; __weak UIView<OngoingCallThreadLocalContextWebrtcVideoViewImpl> *_currentRemoteVideoRenderer;
OngoingCallThreadLocalContextVideoCapturer *_videoCapturer; OngoingCallThreadLocalContextVideoCapturer *_videoCapturer;
@ -227,7 +228,7 @@
} }
- (void)controllerStateChanged:(tgcalls::State)state videoState:(OngoingCallVideoStateWebrtc)videoState; - (void)controllerStateChanged:(tgcalls::State)state;
- (void)signalBarsChanged:(int32_t)signalBars; - (void)signalBarsChanged:(int32_t)signalBars;
@end @end
@ -306,7 +307,7 @@ static void (*InternalVoipLoggingFunction)(NSString *) = NULL;
} }
} }
- (instancetype _Nonnull)initWithVersion:(NSString * _Nonnull)version queue:(id<OngoingCallThreadLocalContextQueueWebrtc> _Nonnull)queue proxy:(VoipProxyServerWebrtc * _Nullable)proxy networkType:(OngoingCallNetworkTypeWebrtc)networkType dataSaving:(OngoingCallDataSavingWebrtc)dataSaving derivedState:(NSData * _Nonnull)derivedState key:(NSData * _Nonnull)key isOutgoing:(bool)isOutgoing primaryConnection:(OngoingCallConnectionDescriptionWebrtc * _Nonnull)primaryConnection alternativeConnections:(NSArray<OngoingCallConnectionDescriptionWebrtc *> * _Nonnull)alternativeConnections maxLayer:(int32_t)maxLayer allowP2P:(BOOL)allowP2P logPath:(NSString * _Nonnull)logPath sendSignalingData:(void (^)(NSData * _Nonnull))sendSignalingData videoCapturer:(OngoingCallThreadLocalContextVideoCapturer * _Nullable)videoCapturer preferredAspectRatio:(float)preferredAspectRatio enableHighBitrateVideoCalls:(bool)enableHighBitrateVideoCalls { - (instancetype _Nonnull)initWithVersion:(NSString * _Nonnull)version queue:(id<OngoingCallThreadLocalContextQueueWebrtc> _Nonnull)queue proxy:(VoipProxyServerWebrtc * _Nullable)proxy networkType:(OngoingCallNetworkTypeWebrtc)networkType dataSaving:(OngoingCallDataSavingWebrtc)dataSaving derivedState:(NSData * _Nonnull)derivedState key:(NSData * _Nonnull)key isOutgoing:(bool)isOutgoing connections:(NSArray<OngoingCallConnectionDescriptionWebrtc *> * _Nonnull)connections maxLayer:(int32_t)maxLayer allowP2P:(BOOL)allowP2P logPath:(NSString * _Nonnull)logPath sendSignalingData:(void (^)(NSData * _Nonnull))sendSignalingData videoCapturer:(OngoingCallThreadLocalContextVideoCapturer * _Nullable)videoCapturer preferredAspectRatio:(float)preferredAspectRatio enableHighBitrateVideoCalls:(bool)enableHighBitrateVideoCalls {
self = [super init]; self = [super init];
if (self != nil) { if (self != nil) {
_version = version; _version = version;
@ -324,12 +325,12 @@ static void (*InternalVoipLoggingFunction)(NSString *) = NULL;
_sendSignalingData = [sendSignalingData copy]; _sendSignalingData = [sendSignalingData copy];
_videoCapturer = videoCapturer; _videoCapturer = videoCapturer;
if (videoCapturer != nil) { if (videoCapturer != nil) {
_videoState = OngoingCallVideoStateOutgoingRequested; _videoState = OngoingCallVideoStateActive;
_remoteVideoState = OngoingCallRemoteVideoStateActive;
} else { } else {
_videoState = OngoingCallVideoStatePossible; _videoState = OngoingCallVideoStateInactive;
_remoteVideoState = OngoingCallRemoteVideoStateActive;
} }
_remoteVideoState = OngoingCallRemoteVideoStateInactive;
_remoteAudioState = OngoingCallRemoteAudioStateActive;
_remoteVideoOrientation = OngoingCallVideoOrientation0; _remoteVideoOrientation = OngoingCallVideoOrientation0;
@ -347,8 +348,6 @@ static void (*InternalVoipLoggingFunction)(NSString *) = NULL;
proxyValue = std::unique_ptr<tgcalls::Proxy>(proxyObject); proxyValue = std::unique_ptr<tgcalls::Proxy>(proxyObject);
} }
NSArray<OngoingCallConnectionDescriptionWebrtc *> *connections = [@[primaryConnection] arrayByAddingObjectsFromArray:alternativeConnections];
std::vector<tgcalls::RtcServer> parsedRtcServers; std::vector<tgcalls::RtcServer> parsedRtcServers;
for (OngoingCallConnectionDescriptionWebrtc *connection in connections) { for (OngoingCallConnectionDescriptionWebrtc *connection in connections) {
if (connection.hasStun) { if (connection.hasStun) {
@ -382,7 +381,7 @@ static void (*InternalVoipLoggingFunction)(NSString *) = NULL;
.enableNS = true, .enableNS = true,
.enableAGC = true, .enableAGC = true,
.enableCallUpgrade = false, .enableCallUpgrade = false,
.logPath = logPath.length == 0 ? "" : std::string(logPath.UTF8String), .logPath = "", //logPath.length == 0 ? "" : std::string(logPath.UTF8String),
.maxApiLayer = [OngoingCallThreadLocalContextWebrtc maxLayer], .maxApiLayer = [OngoingCallThreadLocalContextWebrtc maxLayer],
.preferredAspectRatio = preferredAspectRatio, .preferredAspectRatio = preferredAspectRatio,
.enableHighBitrateVideo = enableHighBitrateVideoCalls .enableHighBitrateVideo = enableHighBitrateVideoCalls
@ -408,30 +407,11 @@ static void (*InternalVoipLoggingFunction)(NSString *) = NULL;
.initialNetworkType = callControllerNetworkTypeForType(networkType), .initialNetworkType = callControllerNetworkTypeForType(networkType),
.encryptionKey = encryptionKey, .encryptionKey = encryptionKey,
.videoCapture = [_videoCapturer getInterface], .videoCapture = [_videoCapturer getInterface],
.stateUpdated = [weakSelf, queue](tgcalls::State state, tgcalls::VideoState videoState) { .stateUpdated = [weakSelf, queue](tgcalls::State state) {
[queue dispatch:^{ [queue dispatch:^{
__strong OngoingCallThreadLocalContextWebrtc *strongSelf = weakSelf; __strong OngoingCallThreadLocalContextWebrtc *strongSelf = weakSelf;
if (strongSelf) { if (strongSelf) {
OngoingCallVideoStateWebrtc mappedVideoState; [strongSelf controllerStateChanged:state];
switch (videoState) {
case tgcalls::VideoState::Possible:
mappedVideoState = OngoingCallVideoStatePossible;
break;
case tgcalls::VideoState::OutgoingRequested:
mappedVideoState = OngoingCallVideoStateOutgoingRequested;
break;
case tgcalls::VideoState::IncomingRequested:
mappedVideoState = OngoingCallVideoStateIncomingRequested;
break;
case tgcalls::VideoState::IncomingRequestedAndActive:
mappedVideoState = OngoingCallVideoStateIncomingRequestedAndActive;
break;
case tgcalls::VideoState::Active:
mappedVideoState = OngoingCallVideoStateActive;
break;
}
[strongSelf controllerStateChanged:state videoState:mappedVideoState];
} }
}]; }];
}, },
@ -446,20 +426,42 @@ static void (*InternalVoipLoggingFunction)(NSString *) = NULL;
} }
}]; }];
}, },
.remoteVideoIsActiveUpdated = [weakSelf, queue](bool isActive) { .remoteMediaStateUpdated = [weakSelf, queue](tgcalls::AudioState audioState, tgcalls::VideoState videoState) {
[queue dispatch:^{ [queue dispatch:^{
__strong OngoingCallThreadLocalContextWebrtc *strongSelf = weakSelf; __strong OngoingCallThreadLocalContextWebrtc *strongSelf = weakSelf;
if (strongSelf) { if (strongSelf) {
OngoingCallRemoteAudioStateWebrtc remoteAudioState;
OngoingCallRemoteVideoStateWebrtc remoteVideoState; OngoingCallRemoteVideoStateWebrtc remoteVideoState;
if (isActive) { switch (audioState) {
remoteVideoState = OngoingCallRemoteVideoStateActive; case tgcalls::AudioState::Muted:
} else { remoteAudioState = OngoingCallRemoteAudioStateMuted;
remoteVideoState = OngoingCallRemoteVideoStateInactive; break;
case tgcalls::AudioState::Active:
remoteAudioState = OngoingCallRemoteAudioStateActive;
break;
default:
remoteAudioState = OngoingCallRemoteAudioStateMuted;
break;
} }
if (strongSelf->_remoteVideoState != remoteVideoState) { switch (videoState) {
case tgcalls::VideoState::Inactive:
remoteVideoState = OngoingCallRemoteVideoStateInactive;
break;
case tgcalls::VideoState::Paused:
remoteVideoState = OngoingCallRemoteVideoStatePaused;
break;
case tgcalls::VideoState::Active:
remoteVideoState = OngoingCallRemoteVideoStateActive;
break;
default:
remoteVideoState = OngoingCallRemoteVideoStateInactive;
break;
}
if (strongSelf->_remoteVideoState != remoteVideoState || strongSelf->_remoteAudioState != remoteAudioState) {
strongSelf->_remoteVideoState = remoteVideoState; strongSelf->_remoteVideoState = remoteVideoState;
strongSelf->_remoteAudioState = remoteAudioState;
if (strongSelf->_stateChanged) { if (strongSelf->_stateChanged) {
strongSelf->_stateChanged(strongSelf->_state, strongSelf->_videoState, strongSelf->_remoteVideoState, strongSelf->_remoteBatteryLevel, strongSelf->_remotePreferredAspectRatio); strongSelf->_stateChanged(strongSelf->_state, strongSelf->_videoState, strongSelf->_remoteVideoState, strongSelf->_remoteAudioState, strongSelf->_remoteBatteryLevel, strongSelf->_remotePreferredAspectRatio);
} }
} }
} }
@ -478,7 +480,7 @@ static void (*InternalVoipLoggingFunction)(NSString *) = NULL;
if (strongSelf->_remoteBatteryLevel != remoteBatteryLevel) { if (strongSelf->_remoteBatteryLevel != remoteBatteryLevel) {
strongSelf->_remoteBatteryLevel = remoteBatteryLevel; strongSelf->_remoteBatteryLevel = remoteBatteryLevel;
if (strongSelf->_stateChanged) { if (strongSelf->_stateChanged) {
strongSelf->_stateChanged(strongSelf->_state, strongSelf->_videoState, strongSelf->_remoteVideoState, strongSelf->_remoteBatteryLevel, strongSelf->_remotePreferredAspectRatio); strongSelf->_stateChanged(strongSelf->_state, strongSelf->_videoState, strongSelf->_remoteVideoState, strongSelf->_remoteAudioState, strongSelf->_remoteBatteryLevel, strongSelf->_remotePreferredAspectRatio);
} }
} }
} }
@ -490,7 +492,7 @@ static void (*InternalVoipLoggingFunction)(NSString *) = NULL;
if (strongSelf) { if (strongSelf) {
strongSelf->_remotePreferredAspectRatio = value; strongSelf->_remotePreferredAspectRatio = value;
if (strongSelf->_stateChanged) { if (strongSelf->_stateChanged) {
strongSelf->_stateChanged(strongSelf->_state, strongSelf->_videoState, strongSelf->_remoteVideoState, strongSelf->_remoteBatteryLevel, strongSelf->_remotePreferredAspectRatio); strongSelf->_stateChanged(strongSelf->_state, strongSelf->_videoState, strongSelf->_remoteVideoState, strongSelf->_remoteAudioState, strongSelf->_remoteBatteryLevel, strongSelf->_remotePreferredAspectRatio);
} }
} }
}]; }];
@ -507,7 +509,7 @@ static void (*InternalVoipLoggingFunction)(NSString *) = NULL;
}); });
_state = OngoingCallStateInitializing; _state = OngoingCallStateInitializing;
_signalBars = -1; _signalBars = 4;
} }
return self; return self;
} }
@ -581,7 +583,7 @@ static void (*InternalVoipLoggingFunction)(NSString *) = NULL;
} }
} }
- (void)controllerStateChanged:(tgcalls::State)state videoState:(OngoingCallVideoStateWebrtc)videoState { - (void)controllerStateChanged:(tgcalls::State)state {
OngoingCallStateWebrtc callState = OngoingCallStateInitializing; OngoingCallStateWebrtc callState = OngoingCallStateInitializing;
switch (state) { switch (state) {
case tgcalls::State::Established: case tgcalls::State::Established:
@ -597,12 +599,11 @@ static void (*InternalVoipLoggingFunction)(NSString *) = NULL;
break; break;
} }
if (_state != callState || _videoState != videoState) { if (_state != callState) {
_state = callState; _state = callState;
_videoState = videoState;
if (_stateChanged) { if (_stateChanged) {
_stateChanged(_state, _videoState, _remoteVideoState, _remoteBatteryLevel, _remotePreferredAspectRatio); _stateChanged(_state, _videoState, _remoteVideoState, _remoteAudioState, _remoteBatteryLevel, _remotePreferredAspectRatio);
} }
} }
} }
@ -654,7 +655,11 @@ static void (*InternalVoipLoggingFunction)(NSString *) = NULL;
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
if ([VideoMetalView isSupported]) { if ([VideoMetalView isSupported]) {
VideoMetalView *remoteRenderer = [[VideoMetalView alloc] initWithFrame:CGRectZero]; VideoMetalView *remoteRenderer = [[VideoMetalView alloc] initWithFrame:CGRectZero];
remoteRenderer.videoContentMode = UIViewContentModeScaleAspectFit; #if TARGET_OS_IPHONE
remoteRenderer.videoContentMode = UIViewContentModeScaleAspectFill;
#else
remoteRenderer.videoContentMode = UIViewContentModeScaleAspect;
#endif
std::shared_ptr<rtc::VideoSinkInterface<webrtc::VideoFrame>> sink = [remoteRenderer getSink]; std::shared_ptr<rtc::VideoSinkInterface<webrtc::VideoFrame>> sink = [remoteRenderer getSink];
__strong OngoingCallThreadLocalContextWebrtc *strongSelf = weakSelf; __strong OngoingCallThreadLocalContextWebrtc *strongSelf = weakSelf;
@ -685,15 +690,29 @@ static void (*InternalVoipLoggingFunction)(NSString *) = NULL;
- (void)requestVideo:(OngoingCallThreadLocalContextVideoCapturer * _Nullable)videoCapturer { - (void)requestVideo:(OngoingCallThreadLocalContextVideoCapturer * _Nullable)videoCapturer {
if (_tgVoip && _videoCapturer == nil) { if (_tgVoip && _videoCapturer == nil) {
_videoCapturer = videoCapturer; _videoCapturer = videoCapturer;
_tgVoip->requestVideo([_videoCapturer getInterface]); _tgVoip->setVideoCapture([_videoCapturer getInterface]);
_videoState = OngoingCallVideoStateActive;
if (_stateChanged) {
_stateChanged(_state, _videoState, _remoteVideoState, _remoteAudioState, _remoteBatteryLevel, _remotePreferredAspectRatio);
}
} }
} }
- (void)acceptVideo:(OngoingCallThreadLocalContextVideoCapturer * _Nullable)videoCapturer { - (void)disableVideo {
if (_tgVoip && _videoCapturer == nil) { if (_tgVoip) {
_videoCapturer = videoCapturer; _videoCapturer = nil;
_tgVoip->requestVideo([_videoCapturer getInterface]); _tgVoip->setVideoCapture(nullptr);
_videoState = OngoingCallVideoStateInactive;
if (_stateChanged) {
_stateChanged(_state, _videoState, _remoteVideoState, _remoteAudioState, _remoteBatteryLevel, _remotePreferredAspectRatio);
} }
} }
}
- (void)remotePrefferedAspectRatioUpdated:(float)remotePrefferedAspectRatio {
}
@end @end