Video call improvements

This commit is contained in:
Ali 2020-08-07 19:16:55 +04:00
parent 497ca256a6
commit c5da0bc464
20 changed files with 3807 additions and 3776 deletions

View File

@ -215,7 +215,9 @@
"PUSH_AUTH_REGION" = "New login|from unrecognized device %1$@, location: %2$@";
"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_VIDEO_CALL_MISSED" = "%1$@|You missed a video call";
"PUSH_MESSAGE_GAME_SCORE" = "%1$@ scored %3$@ in game %2$@";
"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.CallInProgressMessage" = "Finish call with %1$@ and start a new one with %2$@?";
"Call.ExternalCallInProgressMessage" = "Please finish the current call first.";
"Call.Message" = "Message";

View File

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

View File

@ -286,17 +286,22 @@ public final class CallListController: ViewController {
} else {
let presentationData = strongSelf.presentationData
let _ = (strongSelf.context.account.postbox.transaction { transaction -> (Peer?, Peer?) in
return (transaction.getPeer(peerId), transaction.getPeer(currentPeerId))
} |> deliverOnMainQueue).start(next: { [weak self] peer, current in
if let strongSelf = self, let peer = peer, let current = current {
return (transaction.getPeer(peerId), currentPeerId.flatMap(transaction.getPeer))
} |> deliverOnMainQueue).start(next: { [weak self] peer, current in
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: {
if let strongSelf = self {
let _ = strongSelf.context.sharedContext.callManager?.requestCall(context: strongSelf.context, peerId: peerId, isVideo: isVideo, endCurrentIfAny: true)
began?()
}
})]), 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))
}
})
}
})
}
} else {
began?()

View File

@ -130,6 +130,21 @@ func contactContextMenuItems(context: AccountContext, peerId: PeerId, contactsCo
} else {
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
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))
}
|> 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)
})]), in: .window(.root))
}
})
})*/
}
}
}
@ -155,6 +170,21 @@ func contactContextMenuItems(context: AccountContext, peerId: PeerId, contactsCo
} else {
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
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))
}
|> 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)
})]), in: .window(.root))
}
})
})*/
}
}
}

View File

@ -884,14 +884,19 @@ public func deviceContactInfoController(context: AccountContext, subject: Device
} else {
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
let _ = (context.account.postbox.transaction { transaction -> (Peer?, Peer?) in
return (transaction.getPeer(user.id), transaction.getPeer(currentPeerId))
} |> deliverOnMainQueue).start(next: { peer, current in
if let peer = peer, let current = current {
return (transaction.getPeer(user.id), currentPeerId.flatMap(transaction.getPeer))
} |> deliverOnMainQueue).start(next: { peer, current in
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: {
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)
} 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 {
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
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
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: {

View File

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

View File

@ -15,12 +15,12 @@ enum CallControllerButtonsSpeakerMode {
}
enum CallControllerButtonsMode: Equatable {
enum VideoState: Equatable {
case notAvailable
case possible(isEnabled: Bool, isInitializing: Bool)
case outgoingRequested(isInitializing: Bool)
case incomingRequested(sendsVideo: Bool)
case active
struct VideoState: Equatable {
var isAvailable: Bool
var isCameraActive: Bool
var canChangeStatus: Bool
var hasVideo: Bool
var isInitializingCamera: Bool
}
case active(speakerMode: CallControllerButtonsSpeakerMode, videoState: VideoState)
@ -88,7 +88,6 @@ final class CallControllerButtonsNode: ASDisplayNode {
private var validLayout: (CGFloat, CGFloat)?
var isMuted = false
var isCameraPaused = false
var acceptOrEnd: (() -> Void)?
var decline: (() -> Void)?
@ -179,16 +178,8 @@ final class CallControllerButtonsNode: ASDisplayNode {
case .outgoingRinging:
mappedState = .outgoingRinging
case let .active(_, videoStateValue):
switch videoStateValue {
case let .incomingRequested(sendsVideo):
mappedState = .active
videoState = .incomingRequested(sendsVideo: sendsVideo)
case let .outgoingRequested(isInitializing):
mappedState = .active
videoState = .outgoingRequested(isInitializing: isInitializing)
case .active, .possible, .notAvailable:
mappedState = .active
}
mappedState = .active
videoState = videoStateValue
}
var buttons: [PlacedButton] = []
@ -209,28 +200,27 @@ final class CallControllerButtonsNode: ASDisplayNode {
soundOutput = .bluetooth
}
switch videoState {
case .active, .possible, .incomingRequested, .outgoingRequested:
if videoState.isAvailable {
let isCameraActive: Bool
let isCameraEnabled: Bool
let isCameraInitializing: Bool
if case let .possible(value, isInitializing) = videoState {
isCameraActive = false
isCameraEnabled = value
isCameraInitializing = isInitializing
if videoState.hasVideo {
isCameraActive = videoState.isCameraActive
isCameraEnabled = videoState.canChangeStatus
isCameraInitializing = videoState.isInitializingCamera
} else {
isCameraActive = !self.isCameraPaused
isCameraEnabled = true
isCameraInitializing = false
isCameraActive = false
isCameraEnabled = videoState.canChangeStatus
isCameraInitializing = videoState.isInitializingCamera
}
topButtons.append(.enableCamera(isCameraActive, isCameraEnabled, isCameraInitializing))
topButtons.append(.mute(self.isMuted))
if case .possible = videoState {
topButtons.append(.soundOutput(soundOutput))
} else {
if videoState.hasVideo {
topButtons.append(.switchCamera)
} else {
topButtons.append(.soundOutput(soundOutput))
}
case .notAvailable:
} else {
topButtons.append(.mute(self.isMuted))
topButtons.append(.soundOutput(soundOutput))
}
@ -264,23 +254,18 @@ final class CallControllerButtonsNode: ASDisplayNode {
height = largeButtonSize + topBottomSpacing + largeButtonSize + max(bottomInset + 32.0, 46.0)
case .active:
switch videoState {
case .active, .incomingRequested, .outgoingRequested:
if videoState.hasVideo {
let isCameraActive: Bool
let isCameraEnabled: Bool
var isCameraInitializing: Bool
if case .incomingRequested = videoState {
isCameraActive = false
isCameraEnabled = true
isCameraInitializing = false
} else if case let .possible(value, isInitializing) = videoState {
isCameraActive = false
isCameraEnabled = value
isCameraInitializing = isInitializing
let isCameraInitializing: Bool
if videoState.hasVideo {
isCameraActive = videoState.isCameraActive
isCameraEnabled = videoState.canChangeStatus
isCameraInitializing = videoState.isInitializingCamera
} else {
isCameraActive = !self.isCameraPaused
isCameraEnabled = true
isCameraInitializing = false
isCameraActive = false
isCameraEnabled = videoState.canChangeStatus
isCameraInitializing = videoState.isInitializingCamera
}
var topButtons: [ButtonDescription] = []
@ -297,10 +282,6 @@ final class CallControllerButtonsNode: ASDisplayNode {
soundOutput = .bluetooth
}
if case let .outgoingRequested(isInitializing) = videoState {
isCameraInitializing = isInitializing
}
topButtons.append(.enableCamera(isCameraActive, isCameraEnabled, isCameraInitializing))
topButtons.append(.mute(isMuted))
topButtons.append(.switchCamera)
@ -317,21 +298,21 @@ final class CallControllerButtonsNode: ASDisplayNode {
}
height = smallButtonSize + max(bottomInset + 19.0, 46.0)
case .notAvailable, .possible:
} else {
var topButtons: [ButtonDescription] = []
var bottomButtons: [ButtonDescription] = []
let isCameraActive: Bool
let isCameraEnabled: Bool
var isCameraInitializing: Bool
if case let .possible(value, isInitializing) = videoState {
isCameraActive = false
isCameraEnabled = value
isCameraInitializing = isInitializing
let isCameraInitializing: Bool
if videoState.hasVideo {
isCameraActive = videoState.isCameraActive
isCameraEnabled = videoState.canChangeStatus
isCameraInitializing = videoState.isInitializingCamera
} else {
isCameraActive = false
isCameraEnabled = true
isCameraInitializing = false
isCameraEnabled = videoState.canChangeStatus
isCameraInitializing = videoState.isInitializingCamera
}
let soundOutput: ButtonDescription.SoundOutput

View File

@ -274,12 +274,16 @@ final class CallControllerNode: ViewControllerTracingNode, CallControllerNodePro
private let imageNode: TransformImageNode
private let dimNode: ASDisplayNode
private var candidateIncomingVideoNodeValue: CallVideoNode?
private var incomingVideoNodeValue: CallVideoNode?
private var incomingVideoViewRequested: Bool = false
private var candidateOutgoingVideoNodeValue: CallVideoNode?
private var outgoingVideoNodeValue: CallVideoNode?
private var outgoingVideoViewRequested: Bool = false
private var removedMinimizedVideoNodeValue: CallVideoNode?
private var removedExpandedVideoNodeValue: CallVideoNode?
private var isRequestingVideo: Bool = false
private var animateRequestedVideoOnce: Bool = false
@ -323,7 +327,6 @@ final class CallControllerNode: ViewControllerTracingNode, CallControllerNodePro
var beginAudioOuputSelection: (() -> Void)?
var acceptCall: (() -> Void)?
var endCall: (() -> Void)?
var setIsVideoPaused: ((Bool) -> Void)?
var back: (() -> Void)?
var presentCallRating: ((CallId) -> Void)?
var callEnded: ((Bool) -> Void)?
@ -430,12 +433,7 @@ final class CallControllerNode: ViewControllerTracingNode, CallControllerNodePro
}
switch callState.state {
case .active, .connecting, .reconnecting:
switch callState.videoState {
case .incomingRequested:
strongSelf.call.acceptVideo()
default:
strongSelf.endCall?()
}
strongSelf.endCall?()
case .requesting:
strongSelf.endCall?()
case .ringing:
@ -457,27 +455,15 @@ final class CallControllerNode: ViewControllerTracingNode, CallControllerNodePro
case .active:
if strongSelf.outgoingVideoNodeValue == nil {
switch callState.videoState {
case .possible:
case .inactive:
strongSelf.isRequestingVideo = true
strongSelf.updateButtonsMode()
default:
break
}
switch callState.videoState {
case .incomingRequested:
strongSelf.call.acceptVideo()
default:
strongSelf.call.requestVideo()
}
strongSelf.call.requestVideo()
} else {
strongSelf.isVideoPaused = !strongSelf.isVideoPaused
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))
}
strongSelf.call.disableVideo()
}
default:
break
@ -499,6 +485,14 @@ final class CallControllerNode: ViewControllerTracingNode, CallControllerNodePro
self.keyButtonNode.addTarget(self, action: #selector(self.keyPressed), 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()
})
}
}
override func didLoad() {
@ -559,7 +553,7 @@ final class CallControllerNode: ViewControllerTracingNode, CallControllerNodePro
}
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 {
switch currentOutput {
case .headphones:
@ -579,21 +573,39 @@ final class CallControllerNode: ViewControllerTracingNode, CallControllerNodePro
let statusValue: CallControllerStatusValue
var statusReception: Int32?
switch callState.videoState {
case .active, .incomingRequested(true):
switch callState.remoteVideoState {
case .active, .paused:
if !self.incomingVideoViewRequested {
self.incomingVideoViewRequested = true
let delayUntilInitialized = true
self.call.makeIncomingVideoView(completion: { [weak self] incomingVideoView in
guard let strongSelf = self else {
return
}
if let incomingVideoView = incomingVideoView {
let incomingVideoNode = CallVideoNode(videoView: incomingVideoView, assumeReadyAfterTimeout: false, isReadyUpdated: {
guard let strongSelf = self else {
incomingVideoView.view.backgroundColor = .black
incomingVideoView.view.clipsToBounds = true
let applyNode: () -> Void = {
guard let strongSelf = self, let incomingVideoNode = strongSelf.candidateIncomingVideoNodeValue else {
return
}
if let (layout, navigationBarHeight) = strongSelf.validLayout {
strongSelf.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .animated(duration: 0.5, curve: .spring))
strongSelf.candidateIncomingVideoNodeValue = nil
strongSelf.incomingVideoNodeValue = incomingVideoNode
if let expandedVideoNode = strongSelf.expandedVideoNode {
strongSelf.minimizedVideoNode = expandedVideoNode
}
strongSelf.expandedVideoNode = incomingVideoNode
strongSelf.containerNode.insertSubnode(incomingVideoNode, aboveSubnode: 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: {
guard let strongSelf = self else {
@ -604,21 +616,39 @@ final class CallControllerNode: ViewControllerTracingNode, CallControllerNodePro
}
}, isFlippedUpdated: { _ in
})
strongSelf.incomingVideoNodeValue = incomingVideoNode
strongSelf.expandedVideoNode = incomingVideoNode
strongSelf.containerNode.insertSubnode(incomingVideoNode, aboveSubnode: strongSelf.dimNode)
if let (layout, navigationBarHeight) = strongSelf.validLayout {
strongSelf.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .animated(duration: 0.5, curve: .spring))
strongSelf.candidateIncomingVideoNodeValue = incomingVideoNode
strongSelf.setupAudioOutputs()
if !delayUntilInitialized {
applyNode()
}
}
})
}
default:
break
case .inactive:
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 {
case .active, .outgoingRequested, .incomingRequested(false):
case .active, .paused:
if !self.outgoingVideoViewRequested {
self.outgoingVideoViewRequested = true
let delayUntilInitialized = self.isRequestingVideo
@ -643,10 +673,11 @@ final class CallControllerNode: ViewControllerTracingNode, CallControllerNodePro
}
strongSelf.outgoingVideoNodeValue = outgoingVideoNode
strongSelf.minimizedVideoNode = outgoingVideoNode
if let expandedVideoNode = strongSelf.expandedVideoNode {
strongSelf.minimizedVideoNode = outgoingVideoNode
strongSelf.containerNode.insertSubnode(outgoingVideoNode, aboveSubnode: expandedVideoNode)
} else {
strongSelf.expandedVideoNode = outgoingVideoNode
strongSelf.containerNode.insertSubnode(outgoingVideoNode, aboveSubnode: strongSelf.dimNode)
}
strongSelf.updateButtonsMode(transition: .animated(duration: 0.4, curve: .spring))
@ -693,12 +724,28 @@ final class CallControllerNode: ViewControllerTracingNode, CallControllerNodePro
if !delayUntilInitialized {
applyNode()
}
strongSelf.setupAudioOutputs()
}
})
}
default:
break
case .notAvailable, .inactive:
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 {
@ -708,7 +755,7 @@ final class CallControllerNode: ViewControllerTracingNode, CallControllerNodePro
default:
let isActive: Bool
switch callState.remoteVideoState {
case .inactive:
case .inactive, .paused:
isActive = false
case .active:
isActive = true
@ -785,25 +832,16 @@ final class CallControllerNode: ViewControllerTracingNode, CallControllerNodePro
self.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .immediate)
}
}
switch callState.videoState {
case .notAvailable, .active, .possible, .outgoingRequested:
statusValue = .timer({ value in
if isReconnecting {
return strings.Call_StatusConnecting
} else {
return value
}
}, timestamp)
statusReception = reception
case .incomingRequested:
var text: String
text = self.presentationData.strings.Call_IncomingVideoCall
if !self.statusNode.subtitle.isEmpty {
text += "\n\(self.statusNode.subtitle)"
statusValue = .timer({ value in
if isReconnecting {
return strings.Call_StatusConnecting
} else {
return value
}
statusValue = .text(string: text, displayLogo: false)
/*case .outgoingRequested:
statusValue = .text(string: self.presentationData.strings.Call_StatusRequesting, displayLogo: false)*/
}, timestamp)
if case .active = callState.state {
statusReception = reception
}
}
if self.shouldStayHiddenUntilConnection {
@ -860,29 +898,16 @@ final class CallControllerNode: ViewControllerTracingNode, CallControllerNodePro
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 {
case .notAvailable:
mappedVideoState = .notAvailable
case .possible:
var isEnabled = false
switch callState.state {
case .active:
isEnabled = true
default:
break
}
mappedVideoState = .possible(isEnabled: isEnabled, isInitializing: false)
case .outgoingRequested:
if self.outgoingVideoNodeValue != nil {
mappedVideoState = .outgoingRequested(isInitializing: self.isRequestingVideo)
} else {
mappedVideoState = .possible(isEnabled: true, isInitializing: self.isRequestingVideo)
}
case let .incomingRequested(sendsVideo):
mappedVideoState = .incomingRequested(sendsVideo: sendsVideo)
case .active:
mappedVideoState = .active
break
case .inactive:
mappedVideoState.isAvailable = true
mappedVideoState.canChangeStatus = true
case .active, .paused:
mappedVideoState.isAvailable = true
mappedVideoState.canChangeStatus = true
}
switch callState.state {
@ -909,17 +934,19 @@ final class CallControllerNode: ViewControllerTracingNode, CallControllerNodePro
}
func animateIn() {
var bounds = self.bounds
bounds.origin = CGPoint()
self.bounds = bounds
self.layer.removeAnimation(forKey: "bounds")
self.statusBar.layer.removeAnimation(forKey: "opacity")
self.containerNode.layer.removeAnimation(forKey: "opacity")
self.containerNode.layer.removeAnimation(forKey: "scale")
self.statusBar.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
if !self.shouldStayHiddenUntilConnection {
self.containerNode.layer.animateScale(from: 1.04, to: 1.0, duration: 0.3)
self.containerNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
if !self.containerNode.alpha.isZero {
var bounds = self.bounds
bounds.origin = CGPoint()
self.bounds = bounds
self.layer.removeAnimation(forKey: "bounds")
self.statusBar.layer.removeAnimation(forKey: "opacity")
self.containerNode.layer.removeAnimation(forKey: "opacity")
self.containerNode.layer.removeAnimation(forKey: "scale")
self.statusBar.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
if !self.shouldStayHiddenUntilConnection {
self.containerNode.layer.animateScale(from: 1.04, to: 1.0, duration: 0.3)
self.containerNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
}
}
}
@ -1118,15 +1145,66 @@ final class CallControllerNode: ViewControllerTracingNode, CallControllerNodePro
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 {
var expandedVideoTransition = transition
if expandedVideoNode.frame.isEmpty || self.disableAnimationForExpandedVideoOnce {
expandedVideoTransition = .immediate
self.disableAnimationForExpandedVideoOnce = false
}
expandedVideoTransition.updateFrame(node: expandedVideoNode, frame: fullscreenVideoFrame)
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)
}
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 {
var minimizedVideoTransition = transition
var didAppear = false
@ -1134,38 +1212,24 @@ final class CallControllerNode: ViewControllerTracingNode, CallControllerNodePro
minimizedVideoTransition = .immediate
didAppear = true
}
if let expandedVideoNode = self.expandedVideoNode, expandedVideoNode.isReady {
if self.minimizedVideoDraggingPosition == nil {
if let animationForExpandedVideoSnapshotView = self.animationForExpandedVideoSnapshotView {
self.containerNode.view.addSubview(animationForExpandedVideoSnapshotView)
transition.updateAlpha(layer: animationForExpandedVideoSnapshotView.layer, alpha: 0.0, completion: { [weak animationForExpandedVideoSnapshotView] _ in
animationForExpandedVideoSnapshotView?.removeFromSuperview()
})
transition.updateTransformScale(layer: animationForExpandedVideoSnapshotView.layer, scale: previewVideoFrame.width / fullscreenVideoFrame.width)
transition.updatePosition(layer: animationForExpandedVideoSnapshotView.layer, position: CGPoint(x: previewVideoFrame.minX + previewVideoFrame.center.x / fullscreenVideoFrame.width * previewVideoFrame.width, y: previewVideoFrame.minY + previewVideoFrame.center.y / fullscreenVideoFrame.height * previewVideoFrame.height))
self.animationForExpandedVideoSnapshotView = nil
}
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)
if transition.isAnimated && didAppear {
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 self.minimizedVideoDraggingPosition == nil {
if let animationForExpandedVideoSnapshotView = self.animationForExpandedVideoSnapshotView {
self.containerNode.view.addSubview(animationForExpandedVideoSnapshotView)
transition.updateAlpha(layer: animationForExpandedVideoSnapshotView.layer, alpha: 0.0, completion: { [weak animationForExpandedVideoSnapshotView] _ in
animationForExpandedVideoSnapshotView?.removeFromSuperview()
})
transition.updateTransformScale(layer: animationForExpandedVideoSnapshotView.layer, scale: previewVideoFrame.width / fullscreenVideoFrame.width)
if let previousVideoButtonFrame = previousVideoButtonFrame, let videoButtonFrame = videoButtonFrame {
minimizedVideoNode.animateRadialMask(from: previousVideoButtonFrame, to: videoButtonFrame)
}
transition.updatePosition(layer: animationForExpandedVideoSnapshotView.layer, position: CGPoint(x: previewVideoFrame.minX + previewVideoFrame.center.x / fullscreenVideoFrame.width * previewVideoFrame.width, y: previewVideoFrame.minY + previewVideoFrame.center.y / fullscreenVideoFrame.height * previewVideoFrame.height))
self.animationForExpandedVideoSnapshotView = nil
}
minimizedVideoTransition.updateFrame(node: minimizedVideoNode, frame: previewVideoFrame)
minimizedVideoNode.updateLayout(size: previewVideoFrame.size, cornerRadius: interpolate(from: 14.0, to: 24.0, value: self.pictureInPictureTransitionFraction), transition: minimizedVideoTransition)
if transition.isAnimated && didAppear {
minimizedVideoNode.layer.animateSpring(from: 0.1 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.5)
}
}
self.animationForExpandedVideoSnapshotView = nil
}

View File

@ -14,75 +14,6 @@ import LocalizedPeerData
import PhotoResources
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 {
private let sharedContext: SharedAccountContext
private let account: Account
@ -100,14 +31,9 @@ final class LegacyCallControllerNode: ASDisplayNode, CallControllerNodeProtocol
private let imageNode: TransformImageNode
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 backButtonNode: HighlightableButtonNode
private let statusNode: LegacyCallControllerStatusNode
private let videoPausedNode: ImmediateTextNode
private let buttonsNode: LegacyCallControllerButtonsNode
private var keyPreviewNode: CallControllerKeyPreviewNode?
@ -134,12 +60,11 @@ final class LegacyCallControllerNode: ASDisplayNode, CallControllerNodeProtocol
var beginAudioOuputSelection: (() -> Void)?
var acceptCall: (() -> Void)?
var endCall: (() -> Void)?
var toggleVideo: (() -> Void)?
var setIsVideoPaused: ((Bool) -> Void)?
var back: (() -> Void)?
var presentCallRating: ((CallId) -> Void)?
var callEnded: ((Bool) -> Void)?
var dismissedInteractively: (() -> 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) {
self.sharedContext = sharedContext
@ -170,9 +95,6 @@ final class LegacyCallControllerNode: ASDisplayNode, CallControllerNodeProtocol
self.statusNode = LegacyCallControllerStatusNode()
self.videoPausedNode = ImmediateTextNode()
self.videoPausedNode.alpha = 0.0
self.buttonsNode = LegacyCallControllerButtonsNode(strings: self.presentationData.strings)
self.keyButtonNode = HighlightableButtonNode()
@ -207,7 +129,6 @@ final class LegacyCallControllerNode: ASDisplayNode, CallControllerNodeProtocol
self.containerNode.addSubnode(self.imageNode)
self.containerNode.addSubnode(self.dimNode)
self.containerNode.addSubnode(self.statusNode)
self.containerNode.addSubnode(self.videoPausedNode)
self.containerNode.addSubnode(self.buttonsNode)
self.containerNode.addSubnode(self.keyButtonNode)
self.containerNode.addSubnode(self.backButtonArrowNode)
@ -229,10 +150,6 @@ final class LegacyCallControllerNode: ASDisplayNode, CallControllerNodeProtocol
self?.acceptCall?()
}
self.buttonsNode.rotateCamera = { [weak self] in
self?.call.switchVideoCamera()
}
self.keyButtonNode.addTarget(self, action: #selector(self.keyPressed), forControlEvents: .touchUpInside)
self.backButtonNode.addTarget(self, action: #selector(self.backPressed), forControlEvents: .touchUpInside)
@ -269,8 +186,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 {
self.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .immediate)
}
@ -290,84 +205,6 @@ final class LegacyCallControllerNode: ASDisplayNode, CallControllerNodeProtocol
let statusValue: LegacyCallControllerStatusValue
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 {
case .waiting, .connecting:
statusValue = .text(self.presentationData.strings.Call_StatusConnecting)
@ -591,33 +428,10 @@ final class LegacyCallControllerNode: ASDisplayNode, CallControllerNodeProtocol
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)))
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)
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)))
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
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,10 @@ public final class PresentationCallImpl: PresentationCall {
private var callWasActive = 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 sessionStateDisposable: Disposable?
@ -291,9 +294,9 @@ public final class PresentationCallImpl: PresentationCall {
self.enableHighBitrateVideoCalls = enableHighBitrateVideoCalls
if self.isVideo {
self.videoCapturer = OngoingCallVideoCapturer()
self.statePromise.set(PresentationCallState(state: isOutgoing ? .waiting : .ringing, videoState: .outgoingRequested, remoteVideoState: .active))
self.statePromise.set(PresentationCallState(state: isOutgoing ? .waiting : .ringing, videoState: .active, remoteVideoState: .inactive, remoteAudioState: .active))
} else {
self.statePromise.set(PresentationCallState(state: isOutgoing ? .waiting : .ringing, videoState: self.isVideoPossible ? .possible : .notAvailable, remoteVideoState: .active))
self.statePromise.set(PresentationCallState(state: isOutgoing ? .waiting : .ringing, videoState: self.isVideoPossible ? .inactive : .notAvailable, remoteVideoState: .inactive, remoteAudioState: .active))
}
self.serializedData = serializedData
@ -376,7 +379,7 @@ public final class PresentationCallImpl: PresentationCall {
audioSessionActive = callKitIntegration.audioSessionActive
|> filter { $0 }
|> 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 })
}
subscriber.putNext(true)
@ -458,44 +461,59 @@ public final class PresentationCallImpl: PresentationCall {
let mappedVideoState: PresentationCallState.VideoState
let mappedRemoteVideoState: PresentationCallState.RemoteVideoState
let mappedRemoteAudioState: PresentationCallState.RemoteAudioState
if let callContextState = callContextState {
switch callContextState.videoState {
case .notAvailable:
mappedVideoState = .notAvailable
case .possible:
mappedVideoState = .possible
case .outgoingRequested:
mappedVideoState = .outgoingRequested
case let .incomingRequested(sendsVideo):
mappedVideoState = .incomingRequested(sendsVideo: sendsVideo)
case .active:
mappedVideoState = .active
self.videoWasActive = true
case .inactive:
mappedVideoState = .inactive
case .paused:
mappedVideoState = .paused
}
switch callContextState.remoteVideoState {
case .inactive:
mappedRemoteVideoState = .inactive
case .active:
mappedRemoteVideoState = .active
case .paused:
mappedRemoteVideoState = .paused
}
switch callContextState.remoteAudioState {
case .active:
mappedRemoteAudioState = .active
case .muted:
mappedRemoteAudioState = .muted
}
self.previousVideoState = mappedVideoState
self.previousRemoteVideoState = mappedRemoteVideoState
self.previousRemoteAudioState = mappedRemoteAudioState
} else {
if self.isVideo {
mappedVideoState = .outgoingRequested
} else if self.isVideoPossible {
mappedVideoState = .possible
if let previousVideoState = self.previousVideoState {
mappedVideoState = previousVideoState
} else {
mappedVideoState = .notAvailable
if self.isVideo {
mappedVideoState = .active
} else if self.isVideoPossible {
mappedVideoState = .inactive
} else {
mappedVideoState = .notAvailable
}
}
if self.videoWasActive {
mappedRemoteVideoState = .active
mappedRemoteVideoState = .inactive
if let previousRemoteAudioState = self.previousRemoteAudioState {
mappedRemoteAudioState = previousRemoteAudioState
} else {
mappedRemoteVideoState = .inactive
mappedRemoteAudioState = .active
}
}
switch sessionState.state {
case .ringing:
presentationState = PresentationCallState(state: .ringing, videoState: mappedVideoState, remoteVideoState: mappedRemoteVideoState)
presentationState = PresentationCallState(state: .ringing, videoState: mappedVideoState, remoteVideoState: mappedRemoteVideoState, remoteAudioState: mappedRemoteAudioState)
if previous == nil || previousControl == nil {
if !self.reportedIncomingCall {
self.reportedIncomingCall = true
@ -522,19 +540,19 @@ public final class PresentationCallImpl: PresentationCall {
}
case .accepting:
self.callWasActive = true
presentationState = PresentationCallState(state: .connecting(nil), videoState: mappedVideoState, remoteVideoState: mappedRemoteVideoState)
presentationState = PresentationCallState(state: .connecting(nil), videoState: mappedVideoState, remoteVideoState: mappedRemoteVideoState, remoteAudioState: mappedRemoteAudioState)
case .dropping:
presentationState = PresentationCallState(state: .terminating, videoState: mappedVideoState, remoteVideoState: mappedRemoteVideoState)
presentationState = PresentationCallState(state: .terminating, videoState: mappedVideoState, remoteVideoState: .inactive, remoteAudioState: mappedRemoteAudioState)
case let .terminated(id, reason, options):
presentationState = PresentationCallState(state: .terminated(id, reason, self.callWasActive && (options.contains(.reportRating) || self.shouldPresentCallRating)), videoState: mappedVideoState, remoteVideoState: mappedRemoteVideoState)
presentationState = PresentationCallState(state: .terminated(id, reason, self.callWasActive && (options.contains(.reportRating) || self.shouldPresentCallRating)), videoState: mappedVideoState, remoteVideoState: .inactive, remoteAudioState: mappedRemoteAudioState)
case let .requesting(ringing):
presentationState = PresentationCallState(state: .requesting(ringing), videoState: mappedVideoState, remoteVideoState: mappedRemoteVideoState)
presentationState = PresentationCallState(state: .requesting(ringing), videoState: mappedVideoState, remoteVideoState: mappedRemoteVideoState, remoteAudioState: mappedRemoteAudioState)
case let .active(_, _, keyVisualHash, _, _, _, _):
self.callWasActive = true
if let callContextState = callContextState {
switch callContextState.state {
case .initializing:
presentationState = PresentationCallState(state: .connecting(keyVisualHash), videoState: mappedVideoState, remoteVideoState: mappedRemoteVideoState)
presentationState = PresentationCallState(state: .connecting(keyVisualHash), videoState: mappedVideoState, remoteVideoState: mappedRemoteVideoState, remoteAudioState: mappedRemoteAudioState)
case .failed:
presentationState = nil
self.callSessionManager.drop(internalId: self.internalId, reason: .disconnect, debugLog: .single(nil))
@ -546,7 +564,7 @@ public final class PresentationCallImpl: PresentationCall {
timestamp = CFAbsoluteTimeGetCurrent()
self.activeTimestamp = timestamp
}
presentationState = PresentationCallState(state: .active(timestamp, reception, keyVisualHash), videoState: mappedVideoState, remoteVideoState: mappedRemoteVideoState)
presentationState = PresentationCallState(state: .active(timestamp, reception, keyVisualHash), videoState: mappedVideoState, remoteVideoState: mappedRemoteVideoState, remoteAudioState: mappedRemoteAudioState)
case .reconnecting:
let timestamp: Double
if let activeTimestamp = self.activeTimestamp {
@ -555,10 +573,10 @@ public final class PresentationCallImpl: PresentationCall {
timestamp = CFAbsoluteTimeGetCurrent()
self.activeTimestamp = timestamp
}
presentationState = PresentationCallState(state: .reconnecting(timestamp, reception, keyVisualHash), videoState: mappedVideoState, remoteVideoState: mappedRemoteVideoState)
presentationState = PresentationCallState(state: .reconnecting(timestamp, reception, keyVisualHash), videoState: mappedVideoState, remoteVideoState: mappedRemoteVideoState, remoteAudioState: mappedRemoteAudioState)
}
} else {
presentationState = PresentationCallState(state: .connecting(keyVisualHash), videoState: mappedVideoState, remoteVideoState: mappedRemoteVideoState)
presentationState = PresentationCallState(state: .connecting(keyVisualHash), videoState: mappedVideoState, remoteVideoState: mappedRemoteVideoState, remoteAudioState: mappedRemoteAudioState)
}
}
@ -650,7 +668,9 @@ public final class PresentationCallImpl: PresentationCall {
private func updateTone(_ state: PresentationCallState, callContextState: OngoingCallContextState?, previous: CallSession?) {
var tone: PresentationCallTone?
if let callContextState = callContextState, case .reconnecting = callContextState.state {
tone = .connecting
if !self.isVideo {
tone = .connecting
}
} else if let previous = previous {
switch previous.state {
case .accepting, .active, .dropping, .requesting:
@ -659,7 +679,9 @@ public final class PresentationCallImpl: PresentationCall {
if case .requesting = previous.state {
tone = .ringing
} else {
tone = .connecting
if !self.isVideo {
tone = .connecting
}
}
case .requesting(true):
tone = .ringing
@ -774,13 +796,10 @@ public final class PresentationCallImpl: PresentationCall {
}
}
public func acceptVideo() {
if self.videoCapturer == nil {
let videoCapturer = OngoingCallVideoCapturer()
self.videoCapturer = videoCapturer
}
if let videoCapturer = self.videoCapturer {
self.ongoingContext?.acceptVideo(videoCapturer)
public func disableVideo() {
if let _ = self.videoCapturer {
self.videoCapturer = nil
self.ongoingContext?.disableVideo()
}
}

View File

@ -10,6 +10,7 @@ import TelegramAudio
import TelegramVoip
import TelegramUIPreferences
import AccountContext
import CallKit
private func callKitIntegrationIfEnabled(_ integration: CallKitIntegration?, settings: VoiceCallSettings?) -> CallKitIntegration? {
let enabled = settings?.enableSystemIntegration ?? true
@ -341,9 +342,23 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
public func requestCall(context: AccountContext, peerId: PeerId, isVideo: Bool, endCurrentIfAny: Bool) -> RequestCallResult {
let account = context.account
var alreadyInCall: Bool = false
var alreadyInCallWithPeerId: PeerId?
if let call = self.currentCall, !endCurrentIfAny {
return .alreadyInProgress(call.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) {
let begin: () -> Void = { [weak self] in
@ -460,12 +475,12 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
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
return VideoCallsConfiguration(appConfiguration: appConfiguration)
return (VideoCallsConfiguration(appConfiguration: appConfiguration), transaction.getPeerCachedData(peerId: peerId) as? CachedUserData)
}
|> mapToSignal { callsConfiguration -> Signal<CallSessionInternalId, NoError> in
let isVideoPossible: Bool
|> mapToSignal { callsConfiguration, cachedUserData -> Signal<CallSessionInternalId, NoError> in
var isVideoPossible: Bool
switch callsConfiguration.videoCallsSupport {
case .disabled:
isVideoPossible = isVideo
@ -474,15 +489,23 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
case .onlyVideo:
isVideoPossible = isVideo
}
if let cachedUserData = cachedUserData, cachedUserData.videoCallsAvailable {
} else {
isVideoPossible = false
}
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 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
|> beforeNext { internalId, currentNetworkType, isContact, preferences, sharedData in
|> beforeNext { internalId, currentNetworkType, isContact, preferences, sharedData, cachedUserData in
if let strongSelf = self, accessEnabled {
if let currentCall = strongSelf.currentCall {
currentCall.rejectBusy()
@ -494,7 +517,7 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
let appConfiguration = preferences.values[PreferencesKeys.appConfiguration] as? AppConfiguration ?? AppConfiguration.defaultValue
let callsConfiguration = VideoCallsConfiguration(appConfiguration: appConfiguration)
let isVideoPossible: Bool
var isVideoPossible: Bool
switch callsConfiguration.videoCallsSupport {
case .disabled:
isVideoPossible = isVideo
@ -503,6 +526,10 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
case .onlyVideo:
isVideoPossible = isVideo
}
if let cachedUserData = cachedUserData, cachedUserData.videoCallsAvailable {
} else {
isVideoPossible = false
}
let experimentalSettings = sharedData.entries[ApplicationSpecificSharedDataKeys.experimentalUISettings] as? ExperimentalUISettings ?? .defaultSettings

View File

@ -1226,6 +1226,23 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
} else {
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
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))
}
|> 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)
})]), in: .window(.root))
}
})
})*/
}
}
})

View File

@ -3158,7 +3158,25 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
if currentPeerId == peer.id {
self.context.sharedContext.navigateToCurrentCall()
} else {
let presentationData = self.presentationData
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))
}
|> 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)
})]), in: .window(.root))
}
})
})*/
}
}
}

View File

@ -108,20 +108,26 @@ public struct OngoingCallContextState: Equatable {
public enum VideoState: Equatable {
case notAvailable
case possible
case outgoingRequested
case incomingRequested(sendsVideo: Bool)
case inactive
case active
case paused
}
public enum RemoteVideoState: Equatable {
case inactive
case active
case paused
}
public enum RemoteAudioState: Equatable {
case active
case muted
}
public let state: State
public let videoState: VideoState
public let remoteVideoState: RemoteVideoState
public let remoteAudioState: RemoteAudioState
}
private final class OngoingCallThreadLocalContextQueueImpl: NSObject, OngoingCallThreadLocalContextQueue, OngoingCallThreadLocalContextQueueWebrtc /*, OngoingCallThreadLocalContextQueueWebrtcCustom*/ {
@ -251,7 +257,7 @@ private protocol OngoingCallThreadLocalContextProtocol: class {
func nativeSetNetworkType(_ type: NetworkType)
func nativeSetIsMuted(_ value: Bool)
func nativeRequestVideo(_ capturer: OngoingCallVideoCapturer)
func nativeAcceptVideo(_ capturer: OngoingCallVideoCapturer)
func nativeDisableVideo()
func nativeStop(_ completion: @escaping (String?, Int64, Int64, Int64, Int64) -> Void)
func nativeBeginTermination()
func nativeDebugInfo() -> String
@ -286,7 +292,7 @@ extension OngoingCallThreadLocalContext: OngoingCallThreadLocalContextProtocol {
func nativeRequestVideo(_ capturer: OngoingCallVideoCapturer) {
}
func nativeAcceptVideo(_ capturer: OngoingCallVideoCapturer) {
func nativeDisableVideo() {
}
func nativeSwitchVideoCamera() {
@ -365,8 +371,8 @@ extension OngoingCallThreadLocalContextWebrtc: OngoingCallThreadLocalContextProt
self.requestVideo(capturer.impl)
}
func nativeAcceptVideo(_ capturer: OngoingCallVideoCapturer) {
self.acceptVideo(capturer.impl)
func nativeDisableVideo() {
self.disableVideo()
}
func nativeDebugInfo() -> String {
@ -574,15 +580,13 @@ public final class OngoingCallContext {
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)
}, videoCapturer: video?.impl, preferredAspectRatio: Float(preferredAspectRatio), enableHighBitrateVideoCalls: enableHighBitrateVideoCalls)
strongSelf.contextRef = Unmanaged.passRetained(OngoingCallThreadLocalContextHolder(context))
context.stateChanged = { [weak callSessionManager] state, videoState, remoteVideoState in
context.stateChanged = { [weak callSessionManager] state, videoState, remoteVideoState, remoteAudioState in
queue.async {
guard let strongSelf = self else {
return
@ -590,16 +594,12 @@ public final class OngoingCallContext {
let mappedState = OngoingCallContextState.State(state)
let mappedVideoState: OngoingCallContextState.VideoState
switch videoState {
case .possible:
mappedVideoState = .possible
case .incomingRequested:
mappedVideoState = .incomingRequested(sendsVideo: false)
case .incomingRequestedAndActive:
mappedVideoState = .incomingRequested(sendsVideo: true)
case .outgoingRequested:
mappedVideoState = .outgoingRequested
case .inactive:
mappedVideoState = .inactive
case .active:
mappedVideoState = .active
case .paused:
mappedVideoState = .paused
@unknown default:
mappedVideoState = .notAvailable
}
@ -609,16 +609,28 @@ public final class OngoingCallContext {
mappedRemoteVideoState = .inactive
case .active:
mappedRemoteVideoState = .active
case .paused:
mappedRemoteVideoState = .paused
@unknown default:
mappedRemoteVideoState = .inactive
}
let mappedRemoteAudioState: OngoingCallContextState.RemoteAudioState
switch remoteAudioState {
case .active:
mappedRemoteAudioState = .active
case .muted:
mappedRemoteAudioState = .muted
@unknown default:
mappedRemoteAudioState = .active
}
if case .active = mappedVideoState, !strongSelf.didReportCallAsVideo {
strongSelf.didReportCallAsVideo = true
callSessionManager?.updateCallType(internalId: internalId, type: .video)
}
strongSelf.contextState.set(.single(OngoingCallContextState(state: mappedState, videoState: mappedVideoState, remoteVideoState: mappedRemoteVideoState)))
strongSelf.contextState.set(.single(OngoingCallContextState(state: mappedState, videoState: mappedVideoState, remoteVideoState: mappedRemoteVideoState, remoteAudioState: mappedRemoteAudioState)))
}
}
strongSelf.receptionPromise.set(.single(4))
context.signalBarsChanged = { signalBars in
self?.receptionPromise.set(.single(signalBars))
}
@ -643,7 +655,7 @@ public final class OngoingCallContext {
strongSelf.contextRef = Unmanaged.passRetained(OngoingCallThreadLocalContextHolder(context))
context.stateChanged = { state in
self?.contextState.set(.single(OngoingCallContextState(state: OngoingCallContextState.State(state), videoState: .notAvailable, remoteVideoState: .inactive)))
self?.contextState.set(.single(OngoingCallContextState(state: OngoingCallContextState.State(state), videoState: .notAvailable, remoteVideoState: .inactive, remoteAudioState: .active)))
}
context.signalBarsChanged = { signalBars in
self?.receptionPromise.set(.single(signalBars))
@ -745,9 +757,9 @@ public final class OngoingCallContext {
}
}
public func acceptVideo(_ capturer: OngoingCallVideoCapturer) {
public func disableVideo() {
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) {
OngoingCallVideoStatePossible,
OngoingCallVideoStateOutgoingRequested,
OngoingCallVideoStateIncomingRequested,
OngoingCallVideoStateIncomingRequestedAndActive,
OngoingCallVideoStateActive
OngoingCallVideoStateInactive,
OngoingCallVideoStateActive,
OngoingCallVideoStatePaused
};
typedef NS_ENUM(int32_t, OngoingCallRemoteVideoStateWebrtc) {
OngoingCallRemoteVideoStateInactive,
OngoingCallRemoteVideoStateActive
OngoingCallRemoteVideoStateActive,
OngoingCallRemoteVideoStatePaused
};
typedef NS_ENUM(int32_t, OngoingCallRemoteAudioStateWebrtc) {
OngoingCallRemoteAudioStateMuted,
OngoingCallRemoteAudioStateActive,
};
typedef NS_ENUM(int32_t, OngoingCallVideoOrientationWebrtc) {
@ -111,10 +115,10 @@ typedef NS_ENUM(int32_t, OngoingCallDataSavingWebrtc) {
+ (int32_t)maxLayer;
+ (NSArray<NSString *> * _Nonnull)versionsWithIncludeReference:(bool)includeReference;
@property (nonatomic, copy) void (^ _Nullable stateChanged)(OngoingCallStateWebrtc, OngoingCallVideoStateWebrtc, OngoingCallRemoteVideoStateWebrtc);
@property (nonatomic, copy) void (^ _Nullable stateChanged)(OngoingCallStateWebrtc, OngoingCallVideoStateWebrtc, OngoingCallRemoteVideoStateWebrtc, OngoingCallRemoteAudioStateWebrtc);
@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)stop:(void (^_Nullable)(NSString * _Nullable debugLog, int64_t bytesSentWifi, int64_t bytesReceivedWifi, int64_t bytesSentMobile, int64_t bytesReceivedMobile))completion;
@ -129,7 +133,7 @@ typedef NS_ENUM(int32_t, OngoingCallDataSavingWebrtc) {
- (void)setNetworkType:(OngoingCallNetworkTypeWebrtc)networkType;
- (void)makeIncomingVideoView:(void (^_Nonnull)(UIView<OngoingCallThreadLocalContextWebrtcVideoView> * _Nullable))completion;
- (void)requestVideo:(OngoingCallThreadLocalContextVideoCapturer * _Nullable)videoCapturer;
- (void)acceptVideo:(OngoingCallThreadLocalContextVideoCapturer * _Nullable)videoCapturer;
- (void)disableVideo;
- (void)addSignalingData:(NSData * _Nonnull)data;
@end

View File

@ -144,7 +144,7 @@
}
- (void)setIsVideoEnabled:(bool)isVideoEnabled {
_interface->setIsVideoEnabled(isVideoEnabled);
_interface->setState(isVideoEnabled ? tgcalls::VideoState::Active : tgcalls::VideoState::Paused);
}
- (std::shared_ptr<tgcalls::VideoCaptureInterface>)getInterface {
@ -159,14 +159,14 @@
remoteRenderer.videoContentMode = UIViewContentModeScaleAspectFill;
std::shared_ptr<rtc::VideoSinkInterface<webrtc::VideoFrame>> sink = [remoteRenderer getSink];
interface->setVideoOutput(sink);
interface->setOutput(sink);
completion(remoteRenderer);
} else {
GLVideoView *remoteRenderer = [[GLVideoView alloc] initWithFrame:CGRectZero];
std::shared_ptr<rtc::VideoSinkInterface<webrtc::VideoFrame>> sink = [remoteRenderer getSink];
interface->setVideoOutput(sink);
interface->setOutput(sink);
completion(remoteRenderer);
}
@ -211,6 +211,7 @@
OngoingCallVideoStateWebrtc _videoState;
bool _connectedOnce;
OngoingCallRemoteVideoStateWebrtc _remoteVideoState;
OngoingCallRemoteAudioStateWebrtc _remoteAudioState;
OngoingCallVideoOrientationWebrtc _remoteVideoOrientation;
__weak UIView<OngoingCallThreadLocalContextWebrtcVideoViewImpl> *_currentRemoteVideoRenderer;
OngoingCallThreadLocalContextVideoCapturer *_videoCapturer;
@ -221,7 +222,7 @@
void (^_sendSignalingData)(NSData *);
}
- (void)controllerStateChanged:(tgcalls::State)state videoState:(OngoingCallVideoStateWebrtc)videoState;
- (void)controllerStateChanged:(tgcalls::State)state;
- (void)signalBarsChanged:(int32_t)signalBars;
@end
@ -300,7 +301,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];
if (self != nil) {
_version = version;
@ -317,12 +318,12 @@ static void (*InternalVoipLoggingFunction)(NSString *) = NULL;
_sendSignalingData = [sendSignalingData copy];
_videoCapturer = videoCapturer;
if (videoCapturer != nil) {
_videoState = OngoingCallVideoStateOutgoingRequested;
_remoteVideoState = OngoingCallRemoteVideoStateActive;
_videoState = OngoingCallVideoStateActive;
} else {
_videoState = OngoingCallVideoStatePossible;
_remoteVideoState = OngoingCallRemoteVideoStateActive;
_videoState = OngoingCallVideoStateInactive;
}
_remoteVideoState = OngoingCallRemoteVideoStateInactive;
_remoteAudioState = OngoingCallRemoteAudioStateActive;
_remoteVideoOrientation = OngoingCallVideoOrientation0;
@ -340,8 +341,6 @@ static void (*InternalVoipLoggingFunction)(NSString *) = NULL;
proxyValue = std::unique_ptr<tgcalls::Proxy>(proxyObject);
}
NSArray<OngoingCallConnectionDescriptionWebrtc *> *connections = [@[primaryConnection] arrayByAddingObjectsFromArray:alternativeConnections];
std::vector<tgcalls::RtcServer> parsedRtcServers;
for (OngoingCallConnectionDescriptionWebrtc *connection in connections) {
if (connection.hasStun) {
@ -375,7 +374,7 @@ static void (*InternalVoipLoggingFunction)(NSString *) = NULL;
.enableNS = true,
.enableAGC = true,
.enableCallUpgrade = false,
.logPath = logPath.length == 0 ? "" : std::string(logPath.UTF8String),
.logPath = "", //logPath.length == 0 ? "" : std::string(logPath.UTF8String),
.maxApiLayer = [OngoingCallThreadLocalContextWebrtc maxLayer],
.preferredAspectRatio = preferredAspectRatio,
.enableHighBitrateVideo = enableHighBitrateVideoCalls
@ -401,30 +400,11 @@ static void (*InternalVoipLoggingFunction)(NSString *) = NULL;
.initialNetworkType = callControllerNetworkTypeForType(networkType),
.encryptionKey = encryptionKey,
.videoCapture = [_videoCapturer getInterface],
.stateUpdated = [weakSelf, queue](tgcalls::State state, tgcalls::VideoState videoState) {
.stateUpdated = [weakSelf, queue](tgcalls::State state) {
[queue dispatch:^{
__strong OngoingCallThreadLocalContextWebrtc *strongSelf = weakSelf;
if (strongSelf) {
OngoingCallVideoStateWebrtc mappedVideoState;
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];
[strongSelf controllerStateChanged:state];
}
}];
},
@ -439,25 +419,55 @@ static void (*InternalVoipLoggingFunction)(NSString *) = NULL;
}
}];
},
.remoteVideoIsActiveUpdated = [weakSelf, queue](bool isActive) {
.remoteMediaStateUpdated = [weakSelf, queue](tgcalls::AudioState audioState, tgcalls::VideoState videoState) {
[queue dispatch:^{
__strong OngoingCallThreadLocalContextWebrtc *strongSelf = weakSelf;
if (strongSelf) {
OngoingCallRemoteAudioStateWebrtc remoteAudioState;
OngoingCallRemoteVideoStateWebrtc remoteVideoState;
if (isActive) {
remoteVideoState = OngoingCallRemoteVideoStateActive;
} else {
remoteVideoState = OngoingCallRemoteVideoStateInactive;
switch (audioState) {
case tgcalls::AudioState::Muted:
remoteAudioState = OngoingCallRemoteAudioStateMuted;
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->_remoteAudioState = remoteAudioState;
if (strongSelf->_stateChanged) {
strongSelf->_stateChanged(strongSelf->_state, strongSelf->_videoState, strongSelf->_remoteVideoState);
strongSelf->_stateChanged(strongSelf->_state, strongSelf->_videoState, strongSelf->_remoteVideoState, strongSelf->_remoteAudioState);
}
}
}
}];
},
.remotePrefferedAspectRatioUpdated = [weakSelf, queue](float value) {
[queue dispatch:^{
__strong OngoingCallThreadLocalContextWebrtc *strongSelf = weakSelf;
if (strongSelf) {
[strongSelf remotePrefferedAspectRatioUpdated:value];
}
}];
},
.signalingDataEmitted = [weakSelf, queue](const std::vector<uint8_t> &data) {
NSData *mappedData = [[NSData alloc] initWithBytes:data.data() length:data.size()];
[queue dispatch:^{
@ -470,7 +480,7 @@ static void (*InternalVoipLoggingFunction)(NSString *) = NULL;
});
_state = OngoingCallStateInitializing;
_signalBars = -1;
_signalBars = 4;
}
return self;
}
@ -544,7 +554,7 @@ static void (*InternalVoipLoggingFunction)(NSString *) = NULL;
}
}
- (void)controllerStateChanged:(tgcalls::State)state videoState:(OngoingCallVideoStateWebrtc)videoState {
- (void)controllerStateChanged:(tgcalls::State)state {
OngoingCallStateWebrtc callState = OngoingCallStateInitializing;
switch (state) {
case tgcalls::State::Established:
@ -560,12 +570,11 @@ static void (*InternalVoipLoggingFunction)(NSString *) = NULL;
break;
}
if (_state != callState || _videoState != videoState) {
if (_state != callState) {
_state = callState;
_videoState = videoState;
if (_stateChanged) {
_stateChanged(_state, _videoState, _remoteVideoState);
_stateChanged(_state, _videoState, _remoteVideoState, _remoteAudioState);
}
}
}
@ -647,15 +656,29 @@ static void (*InternalVoipLoggingFunction)(NSString *) = NULL;
- (void)requestVideo:(OngoingCallThreadLocalContextVideoCapturer * _Nullable)videoCapturer {
if (_tgVoip && _videoCapturer == nil) {
_videoCapturer = videoCapturer;
_tgVoip->requestVideo([_videoCapturer getInterface]);
_tgVoip->setVideoCapture([_videoCapturer getInterface]);
_videoState = OngoingCallVideoStateActive;
if (_stateChanged) {
_stateChanged(_state, _videoState, _remoteVideoState, _remoteAudioState);
}
}
}
- (void)acceptVideo:(OngoingCallThreadLocalContextVideoCapturer * _Nullable)videoCapturer {
if (_tgVoip && _videoCapturer == nil) {
_videoCapturer = videoCapturer;
_tgVoip->requestVideo([_videoCapturer getInterface]);
- (void)disableVideo {
if (_tgVoip) {
_videoCapturer = nil;
_tgVoip->setVideoCapture(nullptr);
_videoState = OngoingCallVideoStateInactive;
if (_stateChanged) {
_stateChanged(_state, _videoState, _remoteVideoState, _remoteAudioState);
}
}
}
- (void)remotePrefferedAspectRatioUpdated:(float)remotePrefferedAspectRatio {
}
@end

@ -1 +1 @@
Subproject commit 0806eae4d11e1fdc2f82402f2f49a3a2ff053077
Subproject commit 38246f9f2f3041626ff466bef3ce3f5ba32556cd