Video improvements

This commit is contained in:
Ali 2020-06-23 01:42:33 +04:00
parent 74e9681c02
commit c0aa075f2e
62 changed files with 4841 additions and 4191 deletions

View File

@ -2289,7 +2289,9 @@ Unused sets are archived when you add more.";
"Notification.CallTimeFormat" = "%1$@ (%2$@)"; // 1 - type, 2 - duration "Notification.CallTimeFormat" = "%1$@ (%2$@)"; // 1 - type, 2 - duration
"Notification.CallOutgoing" = "Outgoing Call"; "Notification.CallOutgoing" = "Outgoing Call";
"Notification.VideoCallOutgoing" = "Outgoing Video Call";
"Notification.CallIncoming" = "Incoming Call"; "Notification.CallIncoming" = "Incoming Call";
"Notification.VideoCallIncoming" = "Incoming Video Call";
"Notification.CallMissed" = "Missed Call"; "Notification.CallMissed" = "Missed Call";
"Notification.CallCanceled" = "Cancelled Call"; "Notification.CallCanceled" = "Cancelled Call";
"Notification.CallOutgoingShort" = "Outgoing"; "Notification.CallOutgoingShort" = "Outgoing";
@ -5328,6 +5330,7 @@ Any member of this group will be able to see messages in the channel.";
"PeerInfo.ButtonMessage" = "Message"; "PeerInfo.ButtonMessage" = "Message";
"PeerInfo.ButtonDiscuss" = "Discuss"; "PeerInfo.ButtonDiscuss" = "Discuss";
"PeerInfo.ButtonCall" = "Call"; "PeerInfo.ButtonCall" = "Call";
"PeerInfo.ButtonVideoCall" = "Video Call";
"PeerInfo.ButtonMute" = "Mute"; "PeerInfo.ButtonMute" = "Mute";
"PeerInfo.ButtonUnmute" = "Unmute"; "PeerInfo.ButtonUnmute" = "Unmute";
"PeerInfo.ButtonMore" = "More"; "PeerInfo.ButtonMore" = "More";

View File

@ -30,7 +30,7 @@ public final class OpenChatMessageParams {
public let addToTransitionSurface: (UIView) -> Void public let addToTransitionSurface: (UIView) -> Void
public let openUrl: (String) -> Void public let openUrl: (String) -> Void
public let openPeer: (Peer, ChatControllerInteractionNavigateToPeer) -> Void public let openPeer: (Peer, ChatControllerInteractionNavigateToPeer) -> Void
public let callPeer: (PeerId) -> Void public let callPeer: (PeerId, Bool) -> Void
public let enqueueMessage: (EnqueueMessage) -> Void public let enqueueMessage: (EnqueueMessage) -> Void
public let sendSticker: ((FileMediaReference, ASDisplayNode, CGRect) -> Bool)? public let sendSticker: ((FileMediaReference, ASDisplayNode, CGRect) -> Bool)?
public let setupTemporaryHiddenMedia: (Signal<Any?, NoError>, Int, Media) -> Void public let setupTemporaryHiddenMedia: (Signal<Any?, NoError>, Int, Media) -> Void
@ -51,7 +51,7 @@ public final class OpenChatMessageParams {
addToTransitionSurface: @escaping (UIView) -> Void, addToTransitionSurface: @escaping (UIView) -> Void,
openUrl: @escaping (String) -> Void, openUrl: @escaping (String) -> Void,
openPeer: @escaping (Peer, ChatControllerInteractionNavigateToPeer) -> Void, openPeer: @escaping (Peer, ChatControllerInteractionNavigateToPeer) -> Void,
callPeer: @escaping (PeerId) -> Void, callPeer: @escaping (PeerId, Bool) -> Void,
enqueueMessage: @escaping (EnqueueMessage) -> Void, enqueueMessage: @escaping (EnqueueMessage) -> Void,
sendSticker: ((FileMediaReference, ASDisplayNode, CGRect) -> Bool)?, sendSticker: ((FileMediaReference, ASDisplayNode, CGRect) -> Bool)?,
setupTemporaryHiddenMedia: @escaping (Signal<Any?, NoError>, Int, Media) -> Void, setupTemporaryHiddenMedia: @escaping (Signal<Any?, NoError>, Int, Media) -> Void,

View File

@ -11,6 +11,27 @@ public enum RequestCallResult {
case alreadyInProgress(PeerId) case alreadyInProgress(PeerId)
} }
public struct CallAuxiliaryServer {
public enum Connection {
case stun
case turn(username: String, password: String)
}
public let host: String
public let port: Int
public let connection: Connection
public init(
host: String,
port: Int,
connection: Connection
) {
self.host = host
self.port = port
self.connection = connection
}
}
public struct PresentationCallState: Equatable { public struct PresentationCallState: Equatable {
public enum State: Equatable { public enum State: Equatable {
case waiting case waiting
@ -27,14 +48,22 @@ public struct PresentationCallState: Equatable {
case notAvailable case notAvailable
case available(Bool) case available(Bool)
case active case active
case activeOutgoing
}
public enum RemoteVideoState: Equatable {
case inactive
case active
} }
public var state: State public var state: State
public var videoState: VideoState public var videoState: VideoState
public var remoteVideoState: RemoteVideoState
public init(state: State, videoState: VideoState) { public init(state: State, videoState: VideoState, remoteVideoState: RemoteVideoState) {
self.state = state self.state = state
self.videoState = videoState self.videoState = videoState
self.remoteVideoState = remoteVideoState
} }
} }
@ -72,5 +101,5 @@ public protocol PresentationCall: class {
public protocol PresentationCallManager: class { public protocol PresentationCallManager: class {
var currentCallSignal: Signal<PresentationCall?, NoError> { get } var currentCallSignal: Signal<PresentationCall?, NoError> { get }
func requestCall(account: Account, peerId: PeerId, endCurrentIfAny: Bool) -> RequestCallResult func requestCall(account: Account, peerId: PeerId, isVideo: Bool, endCurrentIfAny: Bool) -> RequestCallResult
} }

View File

@ -135,7 +135,16 @@ class CallListCallItem: ListViewItem {
func selected(listView: ListView) { func selected(listView: ListView) {
listView.clearHighlightAnimated(true) listView.clearHighlightAnimated(true)
self.interaction.call(self.topMessage.id.peerId) var isVideo = false
for media in self.topMessage.media {
if let action = media as? TelegramMediaAction {
if case let .phoneCall(_, _, _, isVideoValue) = action.action {
break
isVideo = isVideoValue
}
}
}
self.interaction.call(self.topMessage.id.peerId, isVideo)
} }
static func mergeType(item: CallListCallItem, previousItem: ListViewItem?, nextItem: ListViewItem?) -> (first: Bool, last: Bool, firstWithHeader: Bool) { static func mergeType(item: CallListCallItem, previousItem: ListViewItem?, nextItem: ListViewItem?) -> (first: Bool, last: Bool, firstWithHeader: Bool) {
@ -237,7 +246,16 @@ class CallListCallItemNode: ItemListRevealOptionsItemNode {
guard let item = self?.layoutParams?.0 else { guard let item = self?.layoutParams?.0 else {
return false return false
} }
item.interaction.call(item.topMessage.id.peerId) var isVideo = false
for media in item.topMessage.media {
if let action = media as? TelegramMediaAction {
if case let .phoneCall(_, _, _, isVideoValue) = action.action {
break
isVideo = isVideoValue
}
}
}
item.interaction.call(item.topMessage.id.peerId, isVideo)
return true return true
} }
} }
@ -357,7 +375,7 @@ class CallListCallItemNode: ItemListRevealOptionsItemNode {
for message in item.messages { for message in item.messages {
inner: for media in message.media { inner: for media in message.media {
if let action = media as? TelegramMediaAction { if let action = media as? TelegramMediaAction {
if case let .phoneCall(_, discardReason, duration) = action.action { if case let .phoneCall(_, discardReason, duration, _) = action.action {
if message.flags.contains(.Incoming) { if message.flags.contains(.Incoming) {
hasIncoming = true hasIncoming = true

View File

@ -145,9 +145,9 @@ public final class CallListController: ViewController {
} }
override public func loadDisplayNode() { override public func loadDisplayNode() {
self.displayNode = CallListControllerNode(context: self.context, mode: self.mode, presentationData: self.presentationData, call: { [weak self] peerId in self.displayNode = CallListControllerNode(context: self.context, mode: self.mode, presentationData: self.presentationData, call: { [weak self] peerId, isVideo in
if let strongSelf = self { if let strongSelf = self {
strongSelf.call(peerId) strongSelf.call(peerId, isVideo: isVideo)
} }
}, openInfo: { [weak self] peerId, messages in }, openInfo: { [weak self] peerId, messages in
if let strongSelf = self { if let strongSelf = self {
@ -201,6 +201,10 @@ public final class CallListController: ViewController {
} }
@objc func callPressed() { @objc func callPressed() {
self.beginCallImpl(isVideo: false)
}
private func beginCallImpl(isVideo: Bool) {
let controller = self.context.sharedContext.makeContactSelectionController(ContactSelectionControllerParams(context: self.context, title: { $0.Calls_NewCall })) let controller = self.context.sharedContext.makeContactSelectionController(ContactSelectionControllerParams(context: self.context, title: { $0.Calls_NewCall }))
controller.navigationPresentation = .modal controller.navigationPresentation = .modal
self.createActionDisposable.set((controller.result self.createActionDisposable.set((controller.result
@ -208,7 +212,7 @@ public final class CallListController: ViewController {
|> deliverOnMainQueue).start(next: { [weak controller, weak self] peer in |> deliverOnMainQueue).start(next: { [weak controller, weak self] peer in
controller?.dismissSearch() controller?.dismissSearch()
if let strongSelf = self, let contactPeer = peer, case let .peer(peer, _, _) = contactPeer { if let strongSelf = self, let contactPeer = peer, case let .peer(peer, _, _) = contactPeer {
strongSelf.call(peer.id, began: { strongSelf.call(peer.id, isVideo: isVideo, began: {
if let strongSelf = self { if let strongSelf = self {
let _ = (strongSelf.context.sharedContext.hasOngoingCall.get() let _ = (strongSelf.context.sharedContext.hasOngoingCall.get()
|> filter { $0 } |> filter { $0 }
@ -257,7 +261,7 @@ public final class CallListController: ViewController {
} }
} }
private func call(_ peerId: PeerId, began: (() -> Void)? = nil) { private func call(_ peerId: PeerId, isVideo: Bool, began: (() -> Void)? = nil) {
self.peerViewDisposable.set((self.context.account.viewTracker.peerView(peerId) self.peerViewDisposable.set((self.context.account.viewTracker.peerView(peerId)
|> take(1) |> take(1)
|> deliverOnMainQueue).start(next: { [weak self] view in |> deliverOnMainQueue).start(next: { [weak self] view in
@ -273,7 +277,7 @@ public final class CallListController: ViewController {
return return
} }
let callResult = strongSelf.context.sharedContext.callManager?.requestCall(account: strongSelf.context.account, peerId: peerId, endCurrentIfAny: false) let callResult = strongSelf.context.sharedContext.callManager?.requestCall(account: strongSelf.context.account, peerId: peerId, isVideo: isVideo, endCurrentIfAny: false)
if let callResult = callResult { if let callResult = callResult {
if case let .alreadyInProgress(currentPeerId) = callResult { if case let .alreadyInProgress(currentPeerId) = callResult {
if currentPeerId == peerId { if currentPeerId == peerId {
@ -287,7 +291,7 @@ public final class CallListController: ViewController {
if let strongSelf = self, let peer = peer, let current = current { if let strongSelf = self, let peer = peer, 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(account: strongSelf.context.account, peerId: peerId, endCurrentIfAny: true) let _ = strongSelf.context.sharedContext.callManager?.requestCall(account: strongSelf.context.account, peerId: peerId, isVideo: isVideo, endCurrentIfAny: true)
began?() began?()
} }
})]), in: .window(.root)) })]), in: .window(.root))

View File

@ -59,12 +59,12 @@ private extension CallListViewEntry {
final class CallListNodeInteraction { final class CallListNodeInteraction {
let setMessageIdWithRevealedOptions: (MessageId?, MessageId?) -> Void let setMessageIdWithRevealedOptions: (MessageId?, MessageId?) -> Void
let call: (PeerId) -> Void let call: (PeerId, Bool) -> Void
let openInfo: (PeerId, [Message]) -> Void let openInfo: (PeerId, [Message]) -> Void
let delete: ([MessageId]) -> Void let delete: ([MessageId]) -> Void
let updateShowCallsTab: (Bool) -> Void let updateShowCallsTab: (Bool) -> Void
init(setMessageIdWithRevealedOptions: @escaping (MessageId?, MessageId?) -> Void, call: @escaping (PeerId) -> Void, openInfo: @escaping (PeerId, [Message]) -> Void, delete: @escaping ([MessageId]) -> Void, updateShowCallsTab: @escaping (Bool) -> Void) { init(setMessageIdWithRevealedOptions: @escaping (MessageId?, MessageId?) -> Void, call: @escaping (PeerId, Bool) -> Void, openInfo: @escaping (PeerId, [Message]) -> Void, delete: @escaping ([MessageId]) -> Void, updateShowCallsTab: @escaping (Bool) -> Void) {
self.setMessageIdWithRevealedOptions = setMessageIdWithRevealedOptions self.setMessageIdWithRevealedOptions = setMessageIdWithRevealedOptions
self.call = call self.call = call
self.openInfo = openInfo self.openInfo = openInfo
@ -190,14 +190,14 @@ final class CallListControllerNode: ASDisplayNode {
private let rightOverlayNode: ASDisplayNode private let rightOverlayNode: ASDisplayNode
private let emptyTextNode: ASTextNode private let emptyTextNode: ASTextNode
private let call: (PeerId) -> Void private let call: (PeerId, Bool) -> Void
private let openInfo: (PeerId, [Message]) -> Void private let openInfo: (PeerId, [Message]) -> Void
private let emptyStateUpdated: (Bool) -> Void private let emptyStateUpdated: (Bool) -> Void
private let emptyStatePromise = Promise<Bool>() private let emptyStatePromise = Promise<Bool>()
private let emptyStateDisposable = MetaDisposable() private let emptyStateDisposable = MetaDisposable()
init(context: AccountContext, mode: CallListControllerMode, presentationData: PresentationData, call: @escaping (PeerId) -> Void, openInfo: @escaping (PeerId, [Message]) -> Void, emptyStateUpdated: @escaping (Bool) -> Void) { init(context: AccountContext, mode: CallListControllerMode, presentationData: PresentationData, call: @escaping (PeerId, Bool) -> Void, openInfo: @escaping (PeerId, [Message]) -> Void, emptyStateUpdated: @escaping (Bool) -> Void) {
self.context = context self.context = context
self.mode = mode self.mode = mode
self.presentationData = presentationData self.presentationData = presentationData
@ -248,8 +248,8 @@ final class CallListControllerNode: ASDisplayNode {
} }
} }
} }
}, call: { [weak self] peerId in }, call: { [weak self] peerId, isVideo in
self?.call(peerId) self?.call(peerId, isVideo)
}, openInfo: { [weak self] peerId, messages in }, openInfo: { [weak self] peerId, messages in
self?.openInfo(peerId, messages) self?.openInfo(peerId, messages)
}, delete: { [weak self] messageIds in }, delete: { [weak self] messageIds in

View File

@ -189,7 +189,7 @@ public func chatListItemStrings(strings: PresentationStrings, nameDisplayOrder:
messageText = invoice.title messageText = invoice.title
case let action as TelegramMediaAction: case let action as TelegramMediaAction:
switch action.action { switch action.action {
case let .phoneCall(_, discardReason, _): case let .phoneCall(_, discardReason, _, _):
hideAuthor = !isPeerGroup hideAuthor = !isPeerGroup
let incoming = message.flags.contains(.Incoming) let incoming = message.flags.contains(.Incoming)
if let discardReason = discardReason { if let discardReason = discardReason {

View File

@ -111,11 +111,17 @@ func contactContextMenuItems(context: AccountContext, peerId: PeerId, contactsCo
if let user = peer as? TelegramUser, let cachedUserData = transaction.getPeerCachedData(peerId: peerId) as? CachedUserData, user.flags.contains(.isSupport) || cachedUserData.callsPrivate { if let user = peer as? TelegramUser, let cachedUserData = transaction.getPeerCachedData(peerId: peerId) as? CachedUserData, user.flags.contains(.isSupport) || cachedUserData.callsPrivate {
canCall = false canCall = false
} }
var canVideoCall = false
if canCall {
if context.sharedContext.immediateExperimentalUISettings.videoCalls {
canVideoCall = true
}
}
if canCall { if canCall {
items.append(.action(ContextMenuActionItem(text: strings.ContactList_Context_Call, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Call"), color: theme.contextMenu.primaryColor) }, action: { _, f in items.append(.action(ContextMenuActionItem(text: strings.ContactList_Context_Call, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Call"), color: theme.contextMenu.primaryColor) }, action: { _, f in
if let contactsController = contactsController { if let contactsController = contactsController {
let callResult = context.sharedContext.callManager?.requestCall(account: context.account, peerId: peerId, endCurrentIfAny: false) let callResult = context.sharedContext.callManager?.requestCall(account: context.account, peerId: peerId, isVideo: false, endCurrentIfAny: false)
if let callResult = callResult, case let .alreadyInProgress(currentPeerId) = callResult { if let callResult = callResult, case let .alreadyInProgress(currentPeerId) = callResult {
if currentPeerId == peerId { if currentPeerId == peerId {
context.sharedContext.navigateToCurrentCall() context.sharedContext.navigateToCurrentCall()
@ -127,7 +133,33 @@ func contactContextMenuItems(context: AccountContext, peerId: PeerId, contactsCo
|> deliverOnMainQueue).start(next: { [weak contactsController] peer, current in |> deliverOnMainQueue).start(next: { [weak contactsController] peer, current in
if let contactsController = contactsController, let peer = peer, let current = current { if let contactsController = contactsController, let peer = peer, 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: { 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(account: context.account, peerId: peerId, endCurrentIfAny: true) let _ = context.sharedContext.callManager?.requestCall(account: context.account, peerId: peerId, isVideo: false, endCurrentIfAny: true)
})]), in: .window(.root))
}
})
}
}
}
f(.default)
})))
}
if canVideoCall {
//TODO:localize
items.append(.action(ContextMenuActionItem(text: "Video Call", icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Call"), color: theme.contextMenu.primaryColor) }, action: { _, f in
if let contactsController = contactsController {
let callResult = context.sharedContext.callManager?.requestCall(account: context.account, peerId: peerId, isVideo: true, endCurrentIfAny: false)
if let callResult = callResult, case let .alreadyInProgress(currentPeerId) = callResult {
if currentPeerId == peerId {
context.sharedContext.navigateToCurrentCall()
} else {
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
let _ = (context.account.postbox.transaction { transaction -> (Peer?, Peer?) in
return (transaction.getPeer(peerId), transaction.getPeer(currentPeerId))
}
|> deliverOnMainQueue).start(next: { [weak contactsController] peer, current in
if let contactsController = contactsController, let peer = peer, 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(account: context.account, peerId: peerId, isVideo: true, endCurrentIfAny: true)
})]), in: .window(.root)) })]), in: .window(.root))
} }
}) })

View File

@ -544,6 +544,31 @@ public extension ContainedViewLayoutTransition {
} }
} }
func updateCornerRadius(layer: CALayer, cornerRadius: CGFloat, completion: ((Bool) -> Void)? = nil) {
if layer.cornerRadius.isEqual(to: cornerRadius) {
if let completion = completion {
completion(true)
}
return
}
switch self {
case .immediate:
layer.cornerRadius = cornerRadius
if let completion = completion {
completion(true)
}
case let .animated(duration, curve):
let previousCornerRadius = layer.cornerRadius
layer.cornerRadius = cornerRadius
layer.animate(from: NSNumber(value: Float(previousCornerRadius)), to: NSNumber(value: Float(cornerRadius)), keyPath: "cornerRadius", timingFunction: curve.timingFunction, duration: duration, mediaTimingFunction: curve.mediaTimingFunction, completion: { result in
if let completion = completion {
completion(result)
}
})
}
}
func animateTransformScale(node: ASDisplayNode, from fromScale: CGFloat, completion: ((Bool) -> Void)? = nil) { func animateTransformScale(node: ASDisplayNode, from fromScale: CGFloat, completion: ((Bool) -> Void)? = nil) {
let t = node.layer.transform let t = node.layer.transform
let currentScale = sqrt((t.m11 * t.m11) + (t.m12 * t.m12) + (t.m13 * t.m13)) let currentScale = sqrt((t.m11 * t.m11) + (t.m12 * t.m12) + (t.m13 * t.m13))

View File

@ -877,7 +877,7 @@ public func deviceContactInfoController(context: AccountContext, subject: Device
ActionSheetItemGroup(items: [ ActionSheetItemGroup(items: [
ActionSheetButtonItem(title: presentationData.strings.UserInfo_TelegramCall, action: { ActionSheetButtonItem(title: presentationData.strings.UserInfo_TelegramCall, action: {
dismissAction() dismissAction()
let callResult = context.sharedContext.callManager?.requestCall(account: context.account, peerId: user.id, endCurrentIfAny: false) let callResult = context.sharedContext.callManager?.requestCall(account: context.account, peerId: user.id, isVideo: false, endCurrentIfAny: false)
if let callResult = callResult, case let .alreadyInProgress(currentPeerId) = callResult { if let callResult = callResult, case let .alreadyInProgress(currentPeerId) = callResult {
if currentPeerId == user.id { if currentPeerId == user.id {
context.sharedContext.navigateToCurrentCall() context.sharedContext.navigateToCurrentCall()
@ -888,7 +888,7 @@ public func deviceContactInfoController(context: AccountContext, subject: Device
} |> 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: {
let _ = context.sharedContext.callManager?.requestCall(account: context.account, peerId: peer.id, endCurrentIfAny: true) let _ = context.sharedContext.callManager?.requestCall(account: context.account, peerId: peer.id, isVideo: false, endCurrentIfAny: true)
})]), nil) })]), nil)
} }
}) })

View File

@ -68,7 +68,7 @@ private func stringForCallType(message: Message, strings: PresentationStrings) -
switch media { switch media {
case let action as TelegramMediaAction: case let action as TelegramMediaAction:
switch action.action { switch action.action {
case let .phoneCall(_, discardReason, _): case let .phoneCall(_, discardReason, _, _):
let incoming = message.flags.contains(.Incoming) let incoming = message.flags.contains(.Incoming)
if let discardReason = discardReason { if let discardReason = discardReason {
switch discardReason { switch discardReason {

View File

@ -859,7 +859,7 @@ public func userInfoController(context: AccountContext, peerId: PeerId, mode: Pe
return .single((view, view.cachedData)) return .single((view, view.cachedData))
})) }))
let requestCallImpl: () -> Void = { let requestCallImpl: (Bool) -> Void = { isVideo in
let _ = (peerView.get() let _ = (peerView.get()
|> take(1) |> take(1)
|> deliverOnMainQueue).start(next: { view in |> deliverOnMainQueue).start(next: { view in
@ -873,7 +873,7 @@ public func userInfoController(context: AccountContext, peerId: PeerId, mode: Pe
return return
} }
let callResult = context.sharedContext.callManager?.requestCall(account: context.account, peerId: peer.id, endCurrentIfAny: false) let callResult = context.sharedContext.callManager?.requestCall(account: context.account, peerId: peer.id, isVideo: isVideo, endCurrentIfAny: false)
if let callResult = callResult, case let .alreadyInProgress(currentPeerId) = callResult { if let callResult = callResult, case let .alreadyInProgress(currentPeerId) = callResult {
if currentPeerId == peer.id { if currentPeerId == peer.id {
context.sharedContext.navigateToCurrentCall() context.sharedContext.navigateToCurrentCall()
@ -884,7 +884,7 @@ public func userInfoController(context: AccountContext, peerId: PeerId, mode: Pe
} |> 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: {
let _ = context.sharedContext.callManager?.requestCall(account: context.account, peerId: peer.id, endCurrentIfAny: true) let _ = context.sharedContext.callManager?.requestCall(account: context.account, peerId: peer.id, isVideo: isVideo, endCurrentIfAny: true)
})]), nil) })]), nil)
} }
}) })
@ -1111,7 +1111,7 @@ public func userInfoController(context: AccountContext, peerId: PeerId, mode: Pe
}, displayCopyContextMenu: { tag, phone in }, displayCopyContextMenu: { tag, phone in
displayCopyContextMenuImpl?(tag, phone) displayCopyContextMenuImpl?(tag, phone)
}, call: { }, call: {
requestCallImpl() requestCallImpl(false)
}, openCallMenu: { number in }, openCallMenu: { number in
let _ = (getUserPeer(postbox: context.account.postbox, peerId: peerId) let _ = (getUserPeer(postbox: context.account.postbox, peerId: peerId)
|> deliverOnMainQueue).start(next: { peer, _ in |> deliverOnMainQueue).start(next: { peer, _ in
@ -1125,7 +1125,7 @@ public func userInfoController(context: AccountContext, peerId: PeerId, mode: Pe
ActionSheetItemGroup(items: [ ActionSheetItemGroup(items: [
ActionSheetButtonItem(title: presentationData.strings.UserInfo_TelegramCall, action: { ActionSheetButtonItem(title: presentationData.strings.UserInfo_TelegramCall, action: {
dismissAction() dismissAction()
requestCallImpl() requestCallImpl(false)
}), }),
ActionSheetButtonItem(title: presentationData.strings.UserInfo_PhoneCall, action: { ActionSheetButtonItem(title: presentationData.strings.UserInfo_PhoneCall, action: {
dismissAction() dismissAction()

View File

@ -38,7 +38,7 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
case historyScreenshot case historyScreenshot
case messageAutoremoveTimeoutUpdated(Int32) case messageAutoremoveTimeoutUpdated(Int32)
case gameScore(gameId: Int64, score: Int32) case gameScore(gameId: Int64, score: Int32)
case phoneCall(callId: Int64, discardReason: PhoneCallDiscardReason?, duration: Int32?) case phoneCall(callId: Int64, discardReason: PhoneCallDiscardReason?, duration: Int32?, isVideo: Bool)
case paymentSent(currency: String, totalAmount: Int64) case paymentSent(currency: String, totalAmount: Int64)
case customText(text: String, entities: [MessageTextEntity]) case customText(text: String, entities: [MessageTextEntity])
case botDomainAccessGranted(domain: String) case botDomainAccessGranted(domain: String)
@ -80,7 +80,7 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
if let value = decoder.decodeOptionalInt32ForKey("dr") { if let value = decoder.decodeOptionalInt32ForKey("dr") {
discardReason = PhoneCallDiscardReason(rawValue: value) discardReason = PhoneCallDiscardReason(rawValue: value)
} }
self = .phoneCall(callId: decoder.decodeInt64ForKey("i", orElse: 0), discardReason: discardReason, duration: decoder.decodeInt32ForKey("d", orElse: 0)) self = .phoneCall(callId: decoder.decodeInt64ForKey("i", orElse: 0), discardReason: discardReason, duration: decoder.decodeInt32ForKey("d", orElse: 0), isVideo: decoder.decodeInt32ForKey("vc", orElse: 0) != 0)
case 15: case 15:
self = .paymentSent(currency: decoder.decodeStringForKey("currency", orElse: ""), totalAmount: decoder.decodeInt64ForKey("ta", orElse: 0)) self = .paymentSent(currency: decoder.decodeStringForKey("currency", orElse: ""), totalAmount: decoder.decodeInt64ForKey("ta", orElse: 0))
case 16: case 16:
@ -152,7 +152,7 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
encoder.encodeInt32(15, forKey: "_rawValue") encoder.encodeInt32(15, forKey: "_rawValue")
encoder.encodeString(currency, forKey: "currency") encoder.encodeString(currency, forKey: "currency")
encoder.encodeInt64(totalAmount, forKey: "ta") encoder.encodeInt64(totalAmount, forKey: "ta")
case let .phoneCall(callId, discardReason, duration): case let .phoneCall(callId, discardReason, duration, isVideo):
encoder.encodeInt32(14, forKey: "_rawValue") encoder.encodeInt32(14, forKey: "_rawValue")
encoder.encodeInt64(callId, forKey: "i") encoder.encodeInt64(callId, forKey: "i")
if let discardReason = discardReason { if let discardReason = discardReason {
@ -165,6 +165,7 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
} else { } else {
encoder.encodeNil(forKey: "d") encoder.encodeNil(forKey: "d")
} }
encoder.encodeInt32(isVideo ? 1 : 0, forKey: "vc")
case let .customText(text, entities): case let .customText(text, entities):
encoder.encodeInt32(16, forKey: "_rawValue") encoder.encodeInt32(16, forKey: "_rawValue")
encoder.encodeString(text, forKey: "text") encoder.encodeString(text, forKey: "text")

View File

@ -37,7 +37,7 @@ private func presentLiveLocationController(context: AccountContext, peerId: Peer
}, addToTransitionSurface: { _ in }, addToTransitionSurface: { _ in
}, openUrl: { _ in }, openUrl: { _ in
}, openPeer: { peer, navigation in }, openPeer: { peer, navigation in
}, callPeer: { _ in }, callPeer: { _, _ in
}, enqueueMessage: { _ in }, enqueueMessage: { _ in
}, sendSticker: nil, }, sendSticker: nil,
setupTemporaryHiddenMedia: { _, _, _ in setupTemporaryHiddenMedia: { _, _, _ in

View File

@ -33,8 +33,6 @@ final class CallControllerButtonsNode: ASDisplayNode {
private let endButton: CallControllerButtonNode private let endButton: CallControllerButtonNode
private let speakerButton: CallControllerButtonNode private let speakerButton: CallControllerButtonNode
private let videoButton: CallControllerButtonNode
private var mode: CallControllerButtonsMode? private var mode: CallControllerButtonsMode?
private var validLayout: CGFloat? private var validLayout: CGFloat?
@ -64,9 +62,6 @@ final class CallControllerButtonsNode: ASDisplayNode {
self.speakerButton = CallControllerButtonNode(type: .speaker, label: nil) self.speakerButton = CallControllerButtonNode(type: .speaker, label: nil)
self.speakerButton.alpha = 0.0 self.speakerButton.alpha = 0.0
self.videoButton = CallControllerButtonNode(type: .video, label: nil)
self.videoButton.alpha = 0.0
super.init() super.init()
self.addSubnode(self.acceptButton) self.addSubnode(self.acceptButton)
@ -74,14 +69,12 @@ final class CallControllerButtonsNode: ASDisplayNode {
self.addSubnode(self.muteButton) self.addSubnode(self.muteButton)
self.addSubnode(self.endButton) self.addSubnode(self.endButton)
self.addSubnode(self.speakerButton) self.addSubnode(self.speakerButton)
self.addSubnode(self.videoButton)
self.acceptButton.addTarget(self, action: #selector(self.buttonPressed(_:)), forControlEvents: .touchUpInside) self.acceptButton.addTarget(self, action: #selector(self.buttonPressed(_:)), forControlEvents: .touchUpInside)
self.declineButton.addTarget(self, action: #selector(self.buttonPressed(_:)), forControlEvents: .touchUpInside) self.declineButton.addTarget(self, action: #selector(self.buttonPressed(_:)), forControlEvents: .touchUpInside)
self.muteButton.addTarget(self, action: #selector(self.buttonPressed(_:)), forControlEvents: .touchUpInside) self.muteButton.addTarget(self, action: #selector(self.buttonPressed(_:)), forControlEvents: .touchUpInside)
self.endButton.addTarget(self, action: #selector(self.buttonPressed(_:)), forControlEvents: .touchUpInside) self.endButton.addTarget(self, action: #selector(self.buttonPressed(_:)), forControlEvents: .touchUpInside)
self.speakerButton.addTarget(self, action: #selector(self.buttonPressed(_:)), forControlEvents: .touchUpInside) self.speakerButton.addTarget(self, action: #selector(self.buttonPressed(_:)), forControlEvents: .touchUpInside)
self.videoButton.addTarget(self, action: #selector(self.buttonPressed(_:)), forControlEvents: .touchUpInside)
} }
func updateLayout(constrainedWidth: CGFloat, transition: ContainedViewLayoutTransition) { func updateLayout(constrainedWidth: CGFloat, transition: ContainedViewLayoutTransition) {
@ -122,10 +115,6 @@ final class CallControllerButtonsNode: ASDisplayNode {
for button in [self.muteButton, self.endButton, self.speakerButton] { for button in [self.muteButton, self.endButton, self.speakerButton] {
transition.updateFrame(node: button, frame: CGRect(origin: origin, size: buttonSize)) transition.updateFrame(node: button, frame: CGRect(origin: origin, size: buttonSize))
if button === self.endButton {
transition.updateFrame(node: self.videoButton, frame: CGRect(origin: CGPoint(x: origin.x, y: origin.y - buttonSize.height - 20.0), size: buttonSize))
}
origin.x += buttonSize.width + threeButtonSpacing origin.x += buttonSize.width + threeButtonSpacing
} }
@ -140,7 +129,7 @@ final class CallControllerButtonsNode: ASDisplayNode {
for button in [self.declineButton, self.acceptButton] { for button in [self.declineButton, self.acceptButton] {
button.alpha = 1.0 button.alpha = 1.0
} }
for button in [self.muteButton, self.endButton, self.speakerButton, self.videoButton] { for button in [self.muteButton, self.endButton, self.speakerButton] {
button.alpha = 0.0 button.alpha = 0.0
} }
case let .active(speakerMode, videoState): case let .active(speakerMode, videoState):
@ -171,7 +160,7 @@ final class CallControllerButtonsNode: ASDisplayNode {
self.endButton.alpha = 1.0 self.endButton.alpha = 1.0
} }
switch videoState { /*switch videoState {
case .notAvailable: case .notAvailable:
self.videoButton.alpha = 0.0 self.videoButton.alpha = 0.0
case let .available(isEnabled): case let .available(isEnabled):
@ -185,8 +174,7 @@ final class CallControllerButtonsNode: ASDisplayNode {
case .active: case .active:
self.videoButton.isUserInteractionEnabled = true self.videoButton.isUserInteractionEnabled = true
self.videoButton.alpha = 0.0 self.videoButton.alpha = 0.0
} }*/
if !self.declineButton.alpha.isZero { if !self.declineButton.alpha.isZero {
if animated { if animated {
@ -223,9 +211,9 @@ final class CallControllerButtonsNode: ASDisplayNode {
self.speaker?() self.speaker?()
} else if button === self.acceptButton { } else if button === self.acceptButton {
self.accept?() self.accept?()
} else if button === self.videoButton { }/* else if button === self.videoButton {
self.toggleVideo?() self.toggleVideo?()
} }*/
} }
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
@ -235,7 +223,7 @@ final class CallControllerButtonsNode: ASDisplayNode {
self.muteButton, self.muteButton,
self.endButton, self.endButton,
self.speakerButton, self.speakerButton,
self.videoButton //self.videoButton
] ]
for button in buttons { for button in buttons {
if let result = button.view.hitTest(self.view.convert(point, to: button.view), with: event) { if let result = button.view.hitTest(self.view.convert(point, to: button.view), with: event) {

View File

@ -16,6 +16,8 @@ import CallsEmoji
private final class IncomingVideoNode: ASDisplayNode { private final class IncomingVideoNode: ASDisplayNode {
private let videoView: UIView private let videoView: UIView
private var effectView: UIVisualEffectView?
private var isBlurred: Bool = false
init(videoView: UIView) { init(videoView: UIView) {
self.videoView = videoView self.videoView = videoView
@ -28,6 +30,29 @@ private final class IncomingVideoNode: ASDisplayNode {
func updateLayout(size: CGSize) { func updateLayout(size: CGSize) {
self.videoView.frame = CGRect(origin: CGPoint(), size: size) 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 final class OutgoingVideoNode: ASDisplayNode {
@ -51,8 +76,9 @@ private final class OutgoingVideoNode: ASDisplayNode {
self.switchCamera() self.switchCamera()
} }
func updateLayout(size: CGSize) { func updateLayout(size: CGSize, isExpanded: Bool, transition: ContainedViewLayoutTransition) {
self.videoView.frame = CGRect(origin: CGPoint(), size: size) 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) self.switchCameraButton.frame = CGRect(origin: CGPoint(), size: size)
} }
} }
@ -75,8 +101,9 @@ final class CallControllerNode: ASDisplayNode {
private let imageNode: TransformImageNode private let imageNode: TransformImageNode
private let dimNode: ASDisplayNode private let dimNode: ASDisplayNode
private var incomingVideoNode: IncomingVideoNode? private var incomingVideoNode: IncomingVideoNode?
private var incomingVideoViewRequested: Bool = false
private var outgoingVideoNode: OutgoingVideoNode? private var outgoingVideoNode: OutgoingVideoNode?
private var videoViewsRequested: Bool = false private var outgoingVideoViewRequested: Bool = false
private let backButtonArrowNode: ASImageNode private let backButtonArrowNode: ASImageNode
private let backButtonNode: HighlightableButtonNode private let backButtonNode: HighlightableButtonNode
private let statusNode: CallControllerStatusNode private let statusNode: CallControllerStatusNode
@ -256,8 +283,8 @@ final class CallControllerNode: ASDisplayNode {
switch callState.videoState { switch callState.videoState {
case .active: case .active:
if !self.videoViewsRequested { if !self.incomingVideoViewRequested {
self.videoViewsRequested = true self.incomingVideoViewRequested = 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
@ -273,7 +300,38 @@ final class CallControllerNode: ASDisplayNode {
} }
} }
}) })
}
if !self.outgoingVideoViewRequested {
self.outgoingVideoViewRequested = true
self.call.makeOutgoingVideoView(completion: { [weak self] outgoingVideoView in
guard let strongSelf = self else {
return
}
if let outgoingVideoView = outgoingVideoView {
outgoingVideoView.backgroundColor = .black
outgoingVideoView.clipsToBounds = true
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)
}
}
})
}
case .activeOutgoing:
if !self.outgoingVideoViewRequested {
self.outgoingVideoViewRequested = true
self.call.makeOutgoingVideoView(completion: { [weak self] outgoingVideoView in self.call.makeOutgoingVideoView(completion: { [weak self] outgoingVideoView in
guard let strongSelf = self else { guard let strongSelf = self else {
return return
@ -305,6 +363,17 @@ final class CallControllerNode: ASDisplayNode {
break break
} }
if let incomingVideoNode = self.incomingVideoNode {
let isActive: Bool
switch callState.remoteVideoState {
case .inactive:
isActive = false
case .active:
isActive = true
}
incomingVideoNode.updateIsBlurred(isBlurred: !isActive)
}
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)
@ -444,6 +513,8 @@ final class CallControllerNode: ASDisplayNode {
mappedVideoState = .available(true) mappedVideoState = .available(true)
case .active: case .active:
mappedVideoState = .active mappedVideoState = .active
case .activeOutgoing:
mappedVideoState = .active
} }
self.buttonsNode.updateMode(.active(speakerMode: mode, videoState: mappedVideoState)) self.buttonsNode.updateMode(.active(speakerMode: mode, videoState: mappedVideoState))
} }
@ -538,15 +609,24 @@ final class CallControllerNode: ASDisplayNode {
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 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.frame = CGRect(origin: CGPoint(), size: layout.size)
incomingVideoNode.updateLayout(size: layout.size) incomingVideoNode.updateLayout(size: layout.size)
} }
if let outgoingVideoNode = self.outgoingVideoNode { if let outgoingVideoNode = self.outgoingVideoNode {
let outgoingSize = layout.size.aspectFitted(CGSize(width: 200.0, height: 200.0)) if self.incomingVideoNode == nil {
let outgoingFrame = CGRect(origin: CGPoint(x: layout.size.width - 16.0 - outgoingSize.width, y: buttonsOriginY - 32.0 - outgoingSize.height), size: outgoingSize) outgoingVideoNode.frame = CGRect(origin: CGPoint(), size: layout.size)
outgoingVideoNode.frame = outgoingFrame outgoingVideoNode.updateLayout(size: layout.size, isExpanded: true, transition: transition)
outgoingVideoNode.updateLayout(size: outgoingFrame.size) } 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

View File

@ -15,13 +15,13 @@ public final class CallKitIntegration {
public static var isAvailable: Bool { public static var isAvailable: Bool {
#if targetEnvironment(simulator) #if targetEnvironment(simulator)
return false return false
#endif #else
if #available(iOSApplicationExtension 10.0, iOS 10.0, *) { if #available(iOSApplicationExtension 10.0, iOS 10.0, *) {
return Locale.current.regionCode?.lowercased() != "cn" return Locale.current.regionCode?.lowercased() != "cn"
} else { } else {
return false return false
} }
#endif
} }
private let audioSessionActivePromise = ValuePromise<Bool>(false, ignoreRepeated: true) private let audioSessionActivePromise = ValuePromise<Bool>(false, ignoreRepeated: true)
@ -29,7 +29,7 @@ public final class CallKitIntegration {
return self.audioSessionActivePromise.get() return self.audioSessionActivePromise.get()
} }
init?(startCall: @escaping (Account, UUID, String) -> Signal<Bool, NoError>, answerCall: @escaping (UUID) -> Void, endCall: @escaping (UUID) -> Signal<Bool, NoError>, setCallMuted: @escaping (UUID, Bool) -> Void, audioSessionActivationChanged: @escaping (Bool) -> Void) { init?(enableVideoCalls: Bool, startCall: @escaping (Account, UUID, String, Bool) -> Signal<Bool, NoError>, answerCall: @escaping (UUID) -> Void, endCall: @escaping (UUID) -> Signal<Bool, NoError>, setCallMuted: @escaping (UUID, Bool) -> Void, audioSessionActivationChanged: @escaping (Bool) -> Void) {
if !CallKitIntegration.isAvailable { if !CallKitIntegration.isAvailable {
return nil return nil
} }
@ -40,7 +40,7 @@ public final class CallKitIntegration {
if #available(iOSApplicationExtension 10.0, iOS 10.0, *) { if #available(iOSApplicationExtension 10.0, iOS 10.0, *) {
if sharedProviderDelegate == nil { if sharedProviderDelegate == nil {
sharedProviderDelegate = CallKitProviderDelegate() sharedProviderDelegate = CallKitProviderDelegate(enableVideoCalls: enableVideoCalls)
} }
(sharedProviderDelegate as? CallKitProviderDelegate)?.setup(audioSessionActivePromise: self.audioSessionActivePromise, startCall: startCall, answerCall: answerCall, endCall: endCall, setCallMuted: setCallMuted, audioSessionActivationChanged: audioSessionActivationChanged) (sharedProviderDelegate as? CallKitProviderDelegate)?.setup(audioSessionActivePromise: self.audioSessionActivePromise, startCall: startCall, answerCall: answerCall, endCall: endCall, setCallMuted: setCallMuted, audioSessionActivationChanged: audioSessionActivationChanged)
} else { } else {
@ -49,9 +49,9 @@ public final class CallKitIntegration {
#endif #endif
} }
func startCall(account: Account, peerId: PeerId, displayTitle: String) { func startCall(account: Account, peerId: PeerId, isVideo: Bool, displayTitle: String) {
if #available(iOSApplicationExtension 10.0, iOS 10.0, *) { if #available(iOSApplicationExtension 10.0, iOS 10.0, *) {
(sharedProviderDelegate as? CallKitProviderDelegate)?.startCall(account: account, peerId: peerId, displayTitle: displayTitle) (sharedProviderDelegate as? CallKitProviderDelegate)?.startCall(account: account, peerId: peerId, isVideo: isVideo, displayTitle: displayTitle)
self.donateIntent(peerId: peerId, displayTitle: displayTitle) self.donateIntent(peerId: peerId, displayTitle: displayTitle)
} }
} }
@ -68,9 +68,9 @@ public final class CallKitIntegration {
} }
} }
func reportIncomingCall(uuid: UUID, handle: String, displayTitle: String, completion: ((NSError?) -> Void)?) { func reportIncomingCall(uuid: UUID, handle: String, isVideo: Bool, displayTitle: String, completion: ((NSError?) -> Void)?) {
if #available(iOSApplicationExtension 10.0, iOS 10.0, *) { if #available(iOSApplicationExtension 10.0, iOS 10.0, *) {
(sharedProviderDelegate as? CallKitProviderDelegate)?.reportIncomingCall(uuid: uuid, handle: handle, displayTitle: displayTitle, completion: completion) (sharedProviderDelegate as? CallKitProviderDelegate)?.reportIncomingCall(uuid: uuid, handle: handle, isVideo: isVideo, displayTitle: displayTitle, completion: completion)
} }
} }
@ -102,7 +102,7 @@ class CallKitProviderDelegate: NSObject, CXProviderDelegate {
private var currentStartCallAccount: (UUID, Account)? private var currentStartCallAccount: (UUID, Account)?
private var startCall: ((Account, UUID, String) -> Signal<Bool, NoError>)? private var startCall: ((Account, UUID, String, Bool) -> Signal<Bool, NoError>)?
private var answerCall: ((UUID) -> Void)? private var answerCall: ((UUID) -> Void)?
private var endCall: ((UUID) -> Signal<Bool, NoError>)? private var endCall: ((UUID) -> Signal<Bool, NoError>)?
private var setCallMuted: ((UUID, Bool) -> Void)? private var setCallMuted: ((UUID, Bool) -> Void)?
@ -112,15 +112,15 @@ class CallKitProviderDelegate: NSObject, CXProviderDelegate {
fileprivate var audioSessionActivePromise: ValuePromise<Bool>? fileprivate var audioSessionActivePromise: ValuePromise<Bool>?
override init() { init(enableVideoCalls: Bool) {
self.provider = CXProvider(configuration: CallKitProviderDelegate.providerConfiguration) self.provider = CXProvider(configuration: CallKitProviderDelegate.providerConfiguration(enableVideoCalls: enableVideoCalls))
super.init() super.init()
self.provider.setDelegate(self, queue: nil) self.provider.setDelegate(self, queue: nil)
} }
func setup(audioSessionActivePromise: ValuePromise<Bool>, startCall: @escaping (Account, UUID, String) -> Signal<Bool, NoError>, answerCall: @escaping (UUID) -> Void, endCall: @escaping (UUID) -> Signal<Bool, NoError>, setCallMuted: @escaping (UUID, Bool) -> Void, audioSessionActivationChanged: @escaping (Bool) -> Void) { func setup(audioSessionActivePromise: ValuePromise<Bool>, startCall: @escaping (Account, UUID, String, Bool) -> Signal<Bool, NoError>, answerCall: @escaping (UUID) -> Void, endCall: @escaping (UUID) -> Signal<Bool, NoError>, setCallMuted: @escaping (UUID, Bool) -> Void, audioSessionActivationChanged: @escaping (Bool) -> Void) {
self.audioSessionActivePromise = audioSessionActivePromise self.audioSessionActivePromise = audioSessionActivePromise
self.startCall = startCall self.startCall = startCall
self.answerCall = answerCall self.answerCall = answerCall
@ -129,7 +129,7 @@ class CallKitProviderDelegate: NSObject, CXProviderDelegate {
self.audioSessionActivationChanged = audioSessionActivationChanged self.audioSessionActivationChanged = audioSessionActivationChanged
} }
static var providerConfiguration: CXProviderConfiguration { private static func providerConfiguration(enableVideoCalls: Bool) -> CXProviderConfiguration {
let providerConfiguration = CXProviderConfiguration(localizedName: "Telegram") let providerConfiguration = CXProviderConfiguration(localizedName: "Telegram")
providerConfiguration.supportsVideo = false providerConfiguration.supportsVideo = false
@ -166,14 +166,14 @@ class CallKitProviderDelegate: NSObject, CXProviderDelegate {
} }
func startCall(account: Account, peerId: PeerId, displayTitle: String) { func startCall(account: Account, peerId: PeerId, isVideo: Bool, displayTitle: String) {
let uuid = UUID() let uuid = UUID()
self.currentStartCallAccount = (uuid, account) self.currentStartCallAccount = (uuid, account)
let handle = CXHandle(type: .generic, value: "\(peerId.id)") let handle = CXHandle(type: .generic, value: "\(peerId.id)")
let startCallAction = CXStartCallAction(call: uuid, handle: handle) let startCallAction = CXStartCallAction(call: uuid, handle: handle)
startCallAction.contactIdentifier = displayTitle startCallAction.contactIdentifier = displayTitle
startCallAction.isVideo = false startCallAction.isVideo = isVideo
let transaction = CXTransaction(action: startCallAction) let transaction = CXTransaction(action: startCallAction)
self.requestTransaction(transaction, completion: { _ in self.requestTransaction(transaction, completion: { _ in
@ -189,7 +189,7 @@ class CallKitProviderDelegate: NSObject, CXProviderDelegate {
}) })
} }
func reportIncomingCall(uuid: UUID, handle: String, displayTitle: String, completion: ((NSError?) -> Void)?) { func reportIncomingCall(uuid: UUID, handle: String, isVideo: Bool, displayTitle: String, completion: ((NSError?) -> Void)?) {
let update = CXCallUpdate() let update = CXCallUpdate()
update.remoteHandle = CXHandle(type: .generic, value: handle) update.remoteHandle = CXHandle(type: .generic, value: handle)
update.localizedCallerName = displayTitle update.localizedCallerName = displayTitle
@ -197,6 +197,7 @@ class CallKitProviderDelegate: NSObject, CXProviderDelegate {
update.supportsGrouping = false update.supportsGrouping = false
update.supportsUngrouping = false update.supportsUngrouping = false
update.supportsDTMF = false update.supportsDTMF = false
update.hasVideo = isVideo
self.provider.reportNewIncomingCall(with: uuid, update: update, completion: { error in self.provider.reportNewIncomingCall(with: uuid, update: update, completion: { error in
completion?(error as NSError?) completion?(error as NSError?)
@ -222,7 +223,7 @@ class CallKitProviderDelegate: NSObject, CXProviderDelegate {
self.currentStartCallAccount = nil self.currentStartCallAccount = nil
let disposable = MetaDisposable() let disposable = MetaDisposable()
self.disposableSet.add(disposable) self.disposableSet.add(disposable)
disposable.set((startCall(account, action.callUUID, action.handle.value) disposable.set((startCall(account, action.callUUID, action.handle.value, action.isVideo)
|> deliverOnMainQueue |> deliverOnMainQueue
|> afterDisposed { [weak self, weak disposable] in |> afterDisposed { [weak self, weak disposable] in
if let strongSelf = self, let disposable = disposable { if let strongSelf = self, let disposable = disposable {

View File

@ -166,12 +166,14 @@ public final class PresentationCallImpl: PresentationCall {
public let internalId: CallSessionInternalId public let internalId: CallSessionInternalId
public let peerId: PeerId public let peerId: PeerId
public let isOutgoing: Bool public let isOutgoing: Bool
private var isVideo: Bool
public let peer: Peer? public let peer: Peer?
private let serializedData: String? private let serializedData: String?
private let dataSaving: VoiceCallDataSaving private let dataSaving: VoiceCallDataSaving
private let derivedState: VoipDerivedState private let derivedState: VoipDerivedState
private let proxyServer: ProxyServerSettings? private let proxyServer: ProxyServerSettings?
private let auxiliaryServers: [OngoingCallContext.AuxiliaryServer]
private let currentNetworkType: NetworkType private let currentNetworkType: NetworkType
private let updatedNetworkType: Signal<NetworkType, NoError> private let updatedNetworkType: Signal<NetworkType, NoError>
@ -188,7 +190,7 @@ public final class PresentationCallImpl: PresentationCall {
private var sessionStateDisposable: Disposable? private var sessionStateDisposable: Disposable?
private let statePromise = ValuePromise<PresentationCallState>(PresentationCallState(state: .waiting, videoState: .notAvailable), ignoreRepeated: true) private let statePromise = ValuePromise<PresentationCallState>(PresentationCallState(state: .waiting, videoState: .notAvailable, remoteVideoState: .inactive), ignoreRepeated: true)
public var state: Signal<PresentationCallState, NoError> { public var state: Signal<PresentationCallState, NoError> {
return self.statePromise.get() return self.statePromise.get()
} }
@ -231,16 +233,31 @@ public final class PresentationCallImpl: PresentationCall {
private var droppedCall = false private var droppedCall = false
private var dropCallKitCallTimer: SwiftSignalKit.Timer? private var dropCallKitCallTimer: SwiftSignalKit.Timer?
init(account: Account, audioSession: ManagedAudioSession, callSessionManager: CallSessionManager, callKitIntegration: CallKitIntegration?, serializedData: String?, dataSaving: VoiceCallDataSaving, derivedState: VoipDerivedState, getDeviceAccessData: @escaping () -> (presentationData: PresentationData, present: (ViewController, Any?) -> Void, openSettings: () -> Void), initialState: CallSession?, internalId: CallSessionInternalId, peerId: PeerId, isOutgoing: Bool, peer: Peer?, proxyServer: ProxyServerSettings?, currentNetworkType: NetworkType, updatedNetworkType: Signal<NetworkType, NoError>) { init(account: Account, audioSession: ManagedAudioSession, callSessionManager: CallSessionManager, callKitIntegration: CallKitIntegration?, serializedData: String?, dataSaving: VoiceCallDataSaving, derivedState: VoipDerivedState, getDeviceAccessData: @escaping () -> (presentationData: PresentationData, present: (ViewController, Any?) -> Void, openSettings: () -> Void), initialState: CallSession?, internalId: CallSessionInternalId, peerId: PeerId, isOutgoing: Bool, peer: Peer?, proxyServer: ProxyServerSettings?, auxiliaryServers: [CallAuxiliaryServer], currentNetworkType: NetworkType, updatedNetworkType: Signal<NetworkType, NoError>) {
self.account = account self.account = account
self.audioSession = audioSession self.audioSession = audioSession
self.callSessionManager = callSessionManager self.callSessionManager = callSessionManager
self.callKitIntegration = callKitIntegration self.callKitIntegration = callKitIntegration
self.getDeviceAccessData = getDeviceAccessData self.getDeviceAccessData = getDeviceAccessData
self.auxiliaryServers = auxiliaryServers.map { server -> OngoingCallContext.AuxiliaryServer in
let mappedConnection: OngoingCallContext.AuxiliaryServer.Connection
switch server.connection {
case .stun:
mappedConnection = .stun
case let .turn(username, password):
mappedConnection = .turn(username: username, password: password)
}
return OngoingCallContext.AuxiliaryServer(
host: server.host,
port: server.port,
connection: mappedConnection
)
}
self.internalId = internalId self.internalId = internalId
self.peerId = peerId self.peerId = peerId
self.isOutgoing = isOutgoing self.isOutgoing = isOutgoing
self.isVideo = initialState?.type == .video
self.peer = peer self.peer = peer
self.serializedData = serializedData self.serializedData = serializedData
@ -369,6 +386,9 @@ public final class PresentationCallImpl: PresentationCall {
} }
private func updateSessionState(sessionState: CallSession, callContextState: OngoingCallContextState?, reception: Int32?, audioSessionControl: ManagedAudioSessionControl?) { private func updateSessionState(sessionState: CallSession, callContextState: OngoingCallContextState?, reception: Int32?, audioSessionControl: ManagedAudioSessionControl?) {
if case .video = sessionState.type {
self.isVideo = true
}
let previous = self.sessionState let previous = self.sessionState
let previousControl = self.audioSessionControl let previousControl = self.audioSessionControl
self.sessionState = sessionState self.sessionState = sessionState
@ -400,13 +420,37 @@ public final class PresentationCallImpl: PresentationCall {
audioSessionControl.setup(synchronous: true) audioSessionControl.setup(synchronous: true)
} }
let mappedVideoState: PresentationCallState.VideoState
let mappedRemoteVideoState: PresentationCallState.RemoteVideoState
if let callContextState = callContextState {
switch callContextState.videoState {
case .notAvailable:
mappedVideoState = .notAvailable
case let .available(enabled):
mappedVideoState = .available(enabled)
case .active:
mappedVideoState = .active
case .activeOutgoing:
mappedVideoState = .activeOutgoing
}
switch callContextState.remoteVideoState {
case .inactive:
mappedRemoteVideoState = .inactive
case .active:
mappedRemoteVideoState = .active
}
} else {
mappedVideoState = .notAvailable
mappedRemoteVideoState = .inactive
}
switch sessionState.state { switch sessionState.state {
case .ringing: case .ringing:
presentationState = PresentationCallState(state: .ringing, videoState: .notAvailable) presentationState = PresentationCallState(state: .ringing, videoState: .notAvailable, remoteVideoState: .inactive)
if previous == nil || previousControl == nil { if previous == nil || previousControl == nil {
if !self.reportedIncomingCall { if !self.reportedIncomingCall {
self.reportedIncomingCall = true self.reportedIncomingCall = true
self.callKitIntegration?.reportIncomingCall(uuid: self.internalId, handle: "\(self.peerId.id)", displayTitle: self.peer?.debugDisplayTitle ?? "Unknown", completion: { [weak self] error in self.callKitIntegration?.reportIncomingCall(uuid: self.internalId, handle: "\(self.peerId.id)", isVideo: sessionState.type == .video, displayTitle: self.peer?.debugDisplayTitle ?? "Unknown", completion: { [weak self] error in
if let error = error { if let error = error {
if error.domain == "com.apple.CallKit.error.incomingcall" && (error.code == -3 || error.code == 3) { if error.domain == "com.apple.CallKit.error.incomingcall" && (error.code == -3 || error.code == 3) {
Logger.shared.log("PresentationCall", "reportIncomingCall device in DND mode") Logger.shared.log("PresentationCall", "reportIncomingCall device in DND mode")
@ -429,28 +473,19 @@ public final class PresentationCallImpl: PresentationCall {
} }
case .accepting: case .accepting:
self.callWasActive = true self.callWasActive = true
presentationState = PresentationCallState(state: .connecting(nil), videoState: .notAvailable) presentationState = PresentationCallState(state: .connecting(nil), videoState: .notAvailable, remoteVideoState: mappedRemoteVideoState)
case .dropping: case .dropping:
presentationState = PresentationCallState(state: .terminating, videoState: .notAvailable) presentationState = PresentationCallState(state: .terminating, videoState: .notAvailable, remoteVideoState: mappedRemoteVideoState)
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: .notAvailable) presentationState = PresentationCallState(state: .terminated(id, reason, self.callWasActive && (options.contains(.reportRating) || self.shouldPresentCallRating)), videoState: .notAvailable, remoteVideoState: mappedRemoteVideoState)
case let .requesting(ringing): case let .requesting(ringing):
presentationState = PresentationCallState(state: .requesting(ringing), videoState: .notAvailable) presentationState = PresentationCallState(state: .requesting(ringing), videoState: .notAvailable, remoteVideoState: mappedRemoteVideoState)
case let .active(_, _, keyVisualHash, _, _, _, _): case let .active(_, _, keyVisualHash, _, _, _, _):
self.callWasActive = true self.callWasActive = true
if let callContextState = callContextState { if let callContextState = callContextState {
let mappedVideoState: PresentationCallState.VideoState
switch callContextState.videoState {
case .notAvailable:
mappedVideoState = .notAvailable
case let .available(enabled):
mappedVideoState = .available(enabled)
case .active:
mappedVideoState = .active
}
switch callContextState.state { switch callContextState.state {
case .initializing: case .initializing:
presentationState = PresentationCallState(state: .connecting(keyVisualHash), videoState: mappedVideoState) presentationState = PresentationCallState(state: .connecting(keyVisualHash), videoState: mappedVideoState, remoteVideoState: mappedRemoteVideoState)
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))
@ -462,7 +497,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) presentationState = PresentationCallState(state: .active(timestamp, reception, keyVisualHash), videoState: mappedVideoState, remoteVideoState: mappedRemoteVideoState)
case .reconnecting: case .reconnecting:
let timestamp: Double let timestamp: Double
if let activeTimestamp = self.activeTimestamp { if let activeTimestamp = self.activeTimestamp {
@ -471,10 +506,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) presentationState = PresentationCallState(state: .reconnecting(timestamp, reception, keyVisualHash), videoState: mappedVideoState, remoteVideoState: mappedRemoteVideoState)
} }
} else { } else {
presentationState = PresentationCallState(state: .connecting(keyVisualHash), videoState: .notAvailable) presentationState = PresentationCallState(state: .connecting(keyVisualHash), videoState: .notAvailable, remoteVideoState: .inactive)
} }
} }
@ -488,7 +523,7 @@ public final class PresentationCallImpl: PresentationCall {
if let _ = audioSessionControl, !wasActive || previousControl == nil { if let _ = audioSessionControl, !wasActive || previousControl == nil {
let logName = "\(id.id)_\(id.accessHash)" let logName = "\(id.id)_\(id.accessHash)"
let ongoingContext = OngoingCallContext(account: account, callSessionManager: self.callSessionManager, internalId: self.internalId, proxyServer: proxyServer, initialNetworkType: self.currentNetworkType, updatedNetworkType: self.updatedNetworkType, serializedData: self.serializedData, dataSaving: dataSaving, derivedState: self.derivedState, key: key, isOutgoing: sessionState.isOutgoing, connections: connections, maxLayer: maxLayer, version: version, allowP2P: allowsP2P, audioSessionActive: self.audioSessionActive.get(), logName: logName) let ongoingContext = OngoingCallContext(account: account, callSessionManager: self.callSessionManager, internalId: self.internalId, proxyServer: proxyServer, auxiliaryServers: auxiliaryServers, initialNetworkType: self.currentNetworkType, updatedNetworkType: self.updatedNetworkType, serializedData: self.serializedData, dataSaving: dataSaving, derivedState: self.derivedState, key: key, isOutgoing: sessionState.isOutgoing, isVideo: sessionState.type == .video, connections: connections, maxLayer: maxLayer, version: version, allowP2P: allowsP2P, audioSessionActive: self.audioSessionActive.get(), logName: logName)
self.ongoingContext = ongoingContext self.ongoingContext = ongoingContext
self.debugInfoValue.set(ongoingContext.debugInfo()) self.debugInfoValue.set(ongoingContext.debugInfo())
@ -629,8 +664,26 @@ public final class PresentationCallImpl: PresentationCall {
return return
} }
if value { if value {
strongSelf.callSessionManager.accept(internalId: strongSelf.internalId) if strongSelf.isVideo {
strongSelf.callKitIntegration?.answerCall(uuid: strongSelf.internalId) DeviceAccess.authorizeAccess(to: .camera, presentationData: presentationData, present: { c, a in
present(c, a)
}, openSettings: {
openSettings()
}, { [weak self] value in
guard let strongSelf = self else {
return
}
if value {
strongSelf.callSessionManager.accept(internalId: strongSelf.internalId)
strongSelf.callKitIntegration?.answerCall(uuid: strongSelf.internalId)
} else {
let _ = strongSelf.hangUp().start()
}
})
} else {
strongSelf.callSessionManager.accept(internalId: strongSelf.internalId)
strongSelf.callKitIntegration?.answerCall(uuid: strongSelf.internalId)
}
} else { } else {
let _ = strongSelf.hangUp().start() let _ = strongSelf.hangUp().start()
} }

View File

@ -16,6 +16,47 @@ private func callKitIntegrationIfEnabled(_ integration: CallKitIntegration?, set
return enabled ? integration : nil return enabled ? integration : nil
} }
private func auxiliaryServers(appConfiguration: AppConfiguration) -> [CallAuxiliaryServer] {
guard let data = appConfiguration.data else {
return []
}
guard let servers = data["rtc_servers"] as? [[String: Any]] else {
return []
}
var result: [CallAuxiliaryServer] = []
for server in servers {
guard let host = server["host"] as? String else {
continue
}
guard let portString = server["port"] as? String else {
continue
}
guard let username = server["username"] as? String else {
continue
}
guard let password = server["password"] as? String else {
continue
}
guard let port = Int(portString) else {
continue
}
result.append(CallAuxiliaryServer(
host: host,
port: port,
connection: .stun
))
result.append(CallAuxiliaryServer(
host: host,
port: port,
connection: .turn(
username: username,
password: password
)
))
}
return result
}
private enum CurrentCall { private enum CurrentCall {
case none case none
case incomingRinging(CallSessionRingingState) case incomingRinging(CallSessionRingingState)
@ -79,7 +120,7 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
return OngoingCallContext.versions(includeExperimental: includeExperimental) return OngoingCallContext.versions(includeExperimental: includeExperimental)
} }
public init(accountManager: AccountManager, getDeviceAccessData: @escaping () -> (presentationData: PresentationData, present: (ViewController, Any?) -> Void, openSettings: () -> Void), isMediaPlaying: @escaping () -> Bool, resumeMediaPlayback: @escaping () -> Void, audioSession: ManagedAudioSession, activeAccounts: Signal<[Account], NoError>) { public init(accountManager: AccountManager, enableVideoCalls: Bool, getDeviceAccessData: @escaping () -> (presentationData: PresentationData, present: (ViewController, Any?) -> Void, openSettings: () -> Void), isMediaPlaying: @escaping () -> Bool, resumeMediaPlayback: @escaping () -> Void, audioSession: ManagedAudioSession, activeAccounts: Signal<[Account], NoError>) {
self.getDeviceAccessData = getDeviceAccessData self.getDeviceAccessData = getDeviceAccessData
self.accountManager = accountManager self.accountManager = accountManager
self.audioSession = audioSession self.audioSession = audioSession
@ -87,15 +128,15 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
self.isMediaPlaying = isMediaPlaying self.isMediaPlaying = isMediaPlaying
self.resumeMediaPlayback = resumeMediaPlayback self.resumeMediaPlayback = resumeMediaPlayback
var startCallImpl: ((Account, UUID, String) -> Signal<Bool, NoError>)? var startCallImpl: ((Account, UUID, String, Bool) -> Signal<Bool, NoError>)?
var answerCallImpl: ((UUID) -> Void)? var answerCallImpl: ((UUID) -> Void)?
var endCallImpl: ((UUID) -> Signal<Bool, NoError>)? var endCallImpl: ((UUID) -> Signal<Bool, NoError>)?
var setCallMutedImpl: ((UUID, Bool) -> Void)? var setCallMutedImpl: ((UUID, Bool) -> Void)?
var audioSessionActivationChangedImpl: ((Bool) -> Void)? var audioSessionActivationChangedImpl: ((Bool) -> Void)?
self.callKitIntegration = CallKitIntegration(startCall: { account, uuid, handle in self.callKitIntegration = CallKitIntegration(enableVideoCalls: enableVideoCalls, startCall: { account, uuid, handle, isVideo in
if let startCallImpl = startCallImpl { if let startCallImpl = startCallImpl {
return startCallImpl(account, uuid, handle) return startCallImpl(account, uuid, handle, isVideo)
} else { } else {
return .single(false) return .single(false)
} }
@ -169,9 +210,9 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
self?.ringingStatesUpdated(ringingStates, enableCallKit: enableCallKit) self?.ringingStatesUpdated(ringingStates, enableCallKit: enableCallKit)
}) })
startCallImpl = { [weak self] account, uuid, handle in startCallImpl = { [weak self] account, uuid, handle, isVideo in
if let strongSelf = self, let userId = Int32(handle) { if let strongSelf = self, let userId = Int32(handle) {
return strongSelf.startCall(account: account, peerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: userId), internalId: uuid) return strongSelf.startCall(account: account, peerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: userId), isVideo: isVideo, internalId: uuid)
|> take(1) |> take(1)
|> map { result -> Bool in |> map { result -> Bool in
return result return result
@ -245,7 +286,7 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
let semaphore = DispatchSemaphore(value: 0) let semaphore = DispatchSemaphore(value: 0)
var data: (PreferencesView, AccountSharedDataView, Peer?)? var data: (PreferencesView, AccountSharedDataView, Peer?)?
let _ = combineLatest( let _ = combineLatest(
account.postbox.preferencesView(keys: [PreferencesKeys.voipConfiguration, ApplicationSpecificPreferencesKeys.voipDerivedState]) account.postbox.preferencesView(keys: [PreferencesKeys.voipConfiguration, ApplicationSpecificPreferencesKeys.voipDerivedState, PreferencesKeys.appConfiguration])
|> take(1), |> take(1),
accountManager.sharedData(keys: [SharedDataKeys.autodownloadSettings]) accountManager.sharedData(keys: [SharedDataKeys.autodownloadSettings])
|> take(1), |> take(1),
@ -260,12 +301,13 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
if let (preferences, sharedData, maybePeer) = data, let peer = maybePeer { if let (preferences, sharedData, maybePeer) = data, let peer = maybePeer {
let configuration = preferences.values[PreferencesKeys.voipConfiguration] as? VoipConfiguration ?? .defaultValue let configuration = preferences.values[PreferencesKeys.voipConfiguration] as? VoipConfiguration ?? .defaultValue
let appConfiguration = preferences.values[PreferencesKeys.appConfiguration] as? AppConfiguration ?? AppConfiguration.defaultValue
let derivedState = preferences.values[ApplicationSpecificPreferencesKeys.voipDerivedState] as? VoipDerivedState ?? .default let derivedState = preferences.values[ApplicationSpecificPreferencesKeys.voipDerivedState] as? VoipDerivedState ?? .default
let autodownloadSettings = sharedData.entries[SharedDataKeys.autodownloadSettings] as? AutodownloadSettings ?? .defaultSettings let autodownloadSettings = sharedData.entries[SharedDataKeys.autodownloadSettings] as? AutodownloadSettings ?? .defaultSettings
let enableCallKit = true let enableCallKit = true
let call = PresentationCallImpl(account: account, audioSession: self.audioSession, callSessionManager: account.callSessionManager, callKitIntegration: enableCallKit ? callKitIntegrationIfEnabled(self.callKitIntegration, settings: self.callSettings) : nil, serializedData: configuration.serializedData, dataSaving: effectiveDataSaving(for: self.callSettings, autodownloadSettings: autodownloadSettings), derivedState: derivedState, getDeviceAccessData: self.getDeviceAccessData, initialState: callSession, internalId: ringingState.id, peerId: ringingState.peerId, isOutgoing: false, peer: peer, proxyServer: self.proxyServer, currentNetworkType: .none, updatedNetworkType: account.networkType) let call = PresentationCallImpl(account: account, audioSession: self.audioSession, callSessionManager: account.callSessionManager, callKitIntegration: enableCallKit ? callKitIntegrationIfEnabled(self.callKitIntegration, settings: self.callSettings) : nil, serializedData: configuration.serializedData, dataSaving: effectiveDataSaving(for: self.callSettings, autodownloadSettings: autodownloadSettings), derivedState: derivedState, getDeviceAccessData: self.getDeviceAccessData, initialState: callSession, internalId: ringingState.id, peerId: ringingState.peerId, isOutgoing: false, peer: peer, proxyServer: self.proxyServer, auxiliaryServers: auxiliaryServers(appConfiguration: appConfiguration), currentNetworkType: .none, updatedNetworkType: account.networkType)
self.updateCurrentCall(call) self.updateCurrentCall(call)
self.currentCallPromise.set(.single(call)) self.currentCallPromise.set(.single(call))
self.hasActiveCallsPromise.set(true) self.hasActiveCallsPromise.set(true)
@ -285,7 +327,7 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
private func ringingStatesUpdated(_ ringingStates: [(Account, Peer, CallSessionRingingState, Bool, NetworkType)], enableCallKit: Bool) { private func ringingStatesUpdated(_ ringingStates: [(Account, Peer, CallSessionRingingState, Bool, NetworkType)], enableCallKit: Bool) {
if let firstState = ringingStates.first { if let firstState = ringingStates.first {
if self.currentCall == nil { if self.currentCall == nil {
self.currentCallDisposable.set((combineLatest(firstState.0.postbox.preferencesView(keys: [PreferencesKeys.voipConfiguration, ApplicationSpecificPreferencesKeys.voipDerivedState]) |> take(1), accountManager.sharedData(keys: [SharedDataKeys.autodownloadSettings]) |> take(1)) self.currentCallDisposable.set((combineLatest(firstState.0.postbox.preferencesView(keys: [PreferencesKeys.voipConfiguration, ApplicationSpecificPreferencesKeys.voipDerivedState, PreferencesKeys.appConfiguration]) |> take(1), accountManager.sharedData(keys: [SharedDataKeys.autodownloadSettings]) |> take(1))
|> deliverOnMainQueue).start(next: { [weak self] preferences, sharedData in |> deliverOnMainQueue).start(next: { [weak self] preferences, sharedData in
guard let strongSelf = self else { guard let strongSelf = self else {
return return
@ -294,8 +336,9 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
let configuration = preferences.values[PreferencesKeys.voipConfiguration] as? VoipConfiguration ?? .defaultValue let configuration = preferences.values[PreferencesKeys.voipConfiguration] as? VoipConfiguration ?? .defaultValue
let derivedState = preferences.values[ApplicationSpecificPreferencesKeys.voipDerivedState] as? VoipDerivedState ?? .default let derivedState = preferences.values[ApplicationSpecificPreferencesKeys.voipDerivedState] as? VoipDerivedState ?? .default
let autodownloadSettings = sharedData.entries[SharedDataKeys.autodownloadSettings] as? AutodownloadSettings ?? .defaultSettings let autodownloadSettings = sharedData.entries[SharedDataKeys.autodownloadSettings] as? AutodownloadSettings ?? .defaultSettings
let appConfiguration = preferences.values[PreferencesKeys.appConfiguration] as? AppConfiguration ?? AppConfiguration.defaultValue
let call = PresentationCallImpl(account: firstState.0, audioSession: strongSelf.audioSession, callSessionManager: firstState.0.callSessionManager, callKitIntegration: enableCallKit ? callKitIntegrationIfEnabled(strongSelf.callKitIntegration, settings: strongSelf.callSettings) : nil, serializedData: configuration.serializedData, dataSaving: effectiveDataSaving(for: strongSelf.callSettings, autodownloadSettings: autodownloadSettings), derivedState: derivedState, getDeviceAccessData: strongSelf.getDeviceAccessData, initialState: nil, internalId: firstState.2.id, peerId: firstState.2.peerId, isOutgoing: false, peer: firstState.1, proxyServer: strongSelf.proxyServer, currentNetworkType: firstState.4, updatedNetworkType: firstState.0.networkType) let call = PresentationCallImpl(account: firstState.0, audioSession: strongSelf.audioSession, callSessionManager: firstState.0.callSessionManager, callKitIntegration: enableCallKit ? callKitIntegrationIfEnabled(strongSelf.callKitIntegration, settings: strongSelf.callSettings) : nil, serializedData: configuration.serializedData, dataSaving: effectiveDataSaving(for: strongSelf.callSettings, autodownloadSettings: autodownloadSettings), derivedState: derivedState, getDeviceAccessData: strongSelf.getDeviceAccessData, initialState: nil, internalId: firstState.2.id, peerId: firstState.2.peerId, isOutgoing: false, peer: firstState.1, proxyServer: strongSelf.proxyServer, auxiliaryServers: auxiliaryServers(appConfiguration: appConfiguration), currentNetworkType: firstState.4, updatedNetworkType: firstState.0.networkType)
strongSelf.updateCurrentCall(call) strongSelf.updateCurrentCall(call)
strongSelf.currentCallPromise.set(.single(call)) strongSelf.currentCallPromise.set(.single(call))
strongSelf.hasActiveCallsPromise.set(true) strongSelf.hasActiveCallsPromise.set(true)
@ -320,7 +363,7 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
} }
} }
public func requestCall(account: Account, peerId: PeerId, endCurrentIfAny: Bool) -> RequestCallResult { public func requestCall(account: Account, peerId: PeerId, isVideo: Bool, endCurrentIfAny: Bool) -> RequestCallResult {
if let call = self.currentCall, !endCurrentIfAny { if let call = self.currentCall, !endCurrentIfAny {
return .alreadyInProgress(call.peerId) return .alreadyInProgress(call.peerId)
} }
@ -337,8 +380,19 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
}, openSettings: { }, openSettings: {
openSettings() openSettings()
}, { value in }, { value in
subscriber.putNext(value) if isVideo {
subscriber.putCompletion() DeviceAccess.authorizeAccess(to: .camera, presentationData: presentationData, present: { c, a in
present(c, a)
}, openSettings: {
openSettings()
}, { value in
subscriber.putNext(value)
subscriber.putCompletion()
})
} else {
subscriber.putNext(value)
subscriber.putCompletion()
}
}) })
return EmptyDisposable return EmptyDisposable
} }
@ -357,7 +411,7 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
guard let strongSelf = self, let peer = peer else { guard let strongSelf = self, let peer = peer else {
return return
} }
strongSelf.callKitIntegration?.startCall(account: account, peerId: peerId, displayTitle: peer.debugDisplayTitle) strongSelf.callKitIntegration?.startCall(account: account, peerId: peerId, isVideo: isVideo, displayTitle: peer.debugDisplayTitle)
})) }))
} }
if let currentCall = self.currentCall { if let currentCall = self.currentCall {
@ -374,7 +428,7 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
guard let strongSelf = self else { guard let strongSelf = self else {
return return
} }
let _ = strongSelf.startCall(account: account, peerId: peerId).start() let _ = strongSelf.startCall(account: account, peerId: peerId, isVideo: isVideo).start()
} }
if let currentCall = self.currentCall { if let currentCall = self.currentCall {
self.startCallDisposable.set((currentCall.hangUp() self.startCallDisposable.set((currentCall.hangUp()
@ -388,7 +442,7 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
return .requested return .requested
} }
private func startCall(account: Account, peerId: PeerId, internalId: CallSessionInternalId = CallSessionInternalId()) -> Signal<Bool, NoError> { private func startCall(account: Account, peerId: PeerId, isVideo: Bool, internalId: CallSessionInternalId = CallSessionInternalId()) -> Signal<Bool, NoError> {
let (presentationData, present, openSettings) = self.getDeviceAccessData() let (presentationData, present, openSettings) = self.getDeviceAccessData()
let accessEnabledSignal: Signal<Bool, NoError> = Signal { subscriber in let accessEnabledSignal: Signal<Bool, NoError> = Signal { subscriber in
@ -397,8 +451,19 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
}, openSettings: { }, openSettings: {
openSettings() openSettings()
}, { value in }, { value in
subscriber.putNext(value) if isVideo {
subscriber.putCompletion() DeviceAccess.authorizeAccess(to: .camera, presentationData: presentationData, present: { c, a in
present(c, a)
}, openSettings: {
openSettings()
}, { value in
subscriber.putNext(value)
subscriber.putCompletion()
})
} else {
subscriber.putNext(value)
subscriber.putCompletion()
}
}) })
return EmptyDisposable return EmptyDisposable
} }
@ -411,9 +476,9 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
if !accessEnabled { if !accessEnabled {
return .single(false) return .single(false)
} }
return (combineLatest(queue: .mainQueue(), account.callSessionManager.request(peerId: peerId, internalId: internalId), networkType |> take(1), account.postbox.peerView(id: peerId) |> map { peerView -> Bool in return (combineLatest(queue: .mainQueue(), account.callSessionManager.request(peerId: peerId, isVideo: isVideo, internalId: internalId), 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]) |> take(1), accountManager.sharedData(keys: [SharedDataKeys.autodownloadSettings]) |> take(1)) } |> take(1), account.postbox.preferencesView(keys: [PreferencesKeys.voipConfiguration, ApplicationSpecificPreferencesKeys.voipDerivedState, PreferencesKeys.appConfiguration]) |> take(1), accountManager.sharedData(keys: [SharedDataKeys.autodownloadSettings]) |> take(1))
|> deliverOnMainQueue |> deliverOnMainQueue
|> beforeNext { internalId, currentNetworkType, isContact, preferences, sharedData in |> beforeNext { internalId, currentNetworkType, isContact, preferences, sharedData in
if let strongSelf = self, accessEnabled { if let strongSelf = self, accessEnabled {
@ -424,8 +489,9 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
let configuration = preferences.values[PreferencesKeys.voipConfiguration] as? VoipConfiguration ?? .defaultValue let configuration = preferences.values[PreferencesKeys.voipConfiguration] as? VoipConfiguration ?? .defaultValue
let derivedState = preferences.values[ApplicationSpecificPreferencesKeys.voipDerivedState] as? VoipDerivedState ?? .default let derivedState = preferences.values[ApplicationSpecificPreferencesKeys.voipDerivedState] as? VoipDerivedState ?? .default
let autodownloadSettings = sharedData.entries[SharedDataKeys.autodownloadSettings] as? AutodownloadSettings ?? .defaultSettings let autodownloadSettings = sharedData.entries[SharedDataKeys.autodownloadSettings] as? AutodownloadSettings ?? .defaultSettings
let appConfiguration = preferences.values[PreferencesKeys.appConfiguration] as? AppConfiguration ?? AppConfiguration.defaultValue
let call = PresentationCallImpl(account: account, audioSession: strongSelf.audioSession, callSessionManager: account.callSessionManager, callKitIntegration: callKitIntegrationIfEnabled(strongSelf.callKitIntegration, settings: strongSelf.callSettings), serializedData: configuration.serializedData, dataSaving: effectiveDataSaving(for: strongSelf.callSettings, autodownloadSettings: autodownloadSettings), derivedState: derivedState, getDeviceAccessData: strongSelf.getDeviceAccessData, initialState: nil, internalId: internalId, peerId: peerId, isOutgoing: true, peer: nil, proxyServer: strongSelf.proxyServer, currentNetworkType: currentNetworkType, updatedNetworkType: account.networkType) let call = PresentationCallImpl(account: account, audioSession: strongSelf.audioSession, callSessionManager: account.callSessionManager, callKitIntegration: callKitIntegrationIfEnabled(strongSelf.callKitIntegration, settings: strongSelf.callSettings), serializedData: configuration.serializedData, dataSaving: effectiveDataSaving(for: strongSelf.callSettings, autodownloadSettings: autodownloadSettings), derivedState: derivedState, getDeviceAccessData: strongSelf.getDeviceAccessData, initialState: nil, internalId: internalId, peerId: peerId, isOutgoing: true, peer: nil, proxyServer: strongSelf.proxyServer, auxiliaryServers: auxiliaryServers(appConfiguration: appConfiguration), currentNetworkType: currentNetworkType, updatedNetworkType: account.networkType)
strongSelf.updateCurrentCall(call) strongSelf.updateCurrentCall(call)
strongSelf.currentCallPromise.set(.single(call)) strongSelf.currentCallPromise.set(.single(call))
strongSelf.hasActiveCallsPromise.set(true) strongSelf.hasActiveCallsPromise.set(true)

View File

@ -1322,7 +1322,7 @@ public final class AccountViewTracker {
var lhsOther = false var lhsOther = false
inner: for media in lhs.media { inner: for media in lhs.media {
if let action = media as? TelegramMediaAction { if let action = media as? TelegramMediaAction {
if case let .phoneCall(_, discardReason, _) = action.action { if case let .phoneCall(_, discardReason, _, _) = action.action {
if lhs.flags.contains(.Incoming), let discardReason = discardReason, case .missed = discardReason { if lhs.flags.contains(.Incoming), let discardReason = discardReason, case .missed = discardReason {
lhsMissed = true lhsMissed = true
} else { } else {
@ -1336,7 +1336,7 @@ public final class AccountViewTracker {
var rhsOther = false var rhsOther = false
inner: for media in rhs.media { inner: for media in rhs.media {
if let action = media as? TelegramMediaAction { if let action = media as? TelegramMediaAction {
if case let .phoneCall(_, discardReason, _) = action.action { if case let .phoneCall(_, discardReason, _, _) = action.action {
if rhs.flags.contains(.Incoming), let discardReason = discardReason, case .missed = discardReason { if rhs.flags.contains(.Incoming), let discardReason = discardReason, case .missed = discardReason {
rhsMissed = true rhsMissed = true
} else { } else {

View File

@ -179,8 +179,14 @@ public enum CallSessionState {
} }
public struct CallSession { public struct CallSession {
public enum CallType {
case audio
case video
}
public let id: CallSessionInternalId public let id: CallSessionInternalId
public let isOutgoing: Bool public let isOutgoing: Bool
public let type: CallType
public let state: CallSessionState public let state: CallSessionState
} }
@ -211,6 +217,7 @@ private func parseConnectionSet(primary: Api.PhoneConnection, alternative: [Api.
private final class CallSessionContext { private final class CallSessionContext {
let peerId: PeerId let peerId: PeerId
let isOutgoing: Bool let isOutgoing: Bool
let type: CallSession.CallType
var state: CallSessionInternalState var state: CallSessionInternalState
let subscribers = Bag<(CallSession) -> Void>() let subscribers = Bag<(CallSession) -> Void>()
let signalingSubscribers = Bag<(Data) -> Void>() let signalingSubscribers = Bag<(Data) -> Void>()
@ -227,9 +234,10 @@ private final class CallSessionContext {
} }
} }
init(peerId: PeerId, isOutgoing: Bool, state: CallSessionInternalState) { init(peerId: PeerId, isOutgoing: Bool, type: CallSession.CallType, state: CallSessionInternalState) {
self.peerId = peerId self.peerId = peerId
self.isOutgoing = isOutgoing self.isOutgoing = isOutgoing
self.type = type
self.state = state self.state = state
} }
@ -311,7 +319,7 @@ private final class CallSessionManagerContext {
let index = context.subscribers.add { next in let index = context.subscribers.add { next in
subscriber.putNext(next) subscriber.putNext(next)
} }
subscriber.putNext(CallSession(id: internalId, isOutgoing: context.isOutgoing, state: CallSessionState(context))) subscriber.putNext(CallSession(id: internalId, isOutgoing: context.isOutgoing, type: context.type, state: CallSessionState(context)))
disposable.set(ActionDisposable { disposable.set(ActionDisposable {
queue.async { queue.async {
if let strongSelf = self, let context = strongSelf.contexts[internalId] { if let strongSelf = self, let context = strongSelf.contexts[internalId] {
@ -372,14 +380,14 @@ private final class CallSessionManagerContext {
private func contextUpdated(internalId: CallSessionInternalId) { private func contextUpdated(internalId: CallSessionInternalId) {
if let context = self.contexts[internalId] { if let context = self.contexts[internalId] {
let session = CallSession(id: internalId, isOutgoing: context.isOutgoing, state: CallSessionState(context)) let session = CallSession(id: internalId, isOutgoing: context.isOutgoing, type: context.type, state: CallSessionState(context))
for subscriber in context.subscribers.copyItems() { for subscriber in context.subscribers.copyItems() {
subscriber(session) subscriber(session)
} }
} }
} }
private func addIncoming(peerId: PeerId, stableId: CallSessionStableId, accessHash: Int64, timestamp: Int32, gAHash: Data, versions: [String]) -> CallSessionInternalId? { private func addIncoming(peerId: PeerId, stableId: CallSessionStableId, accessHash: Int64, timestamp: Int32, gAHash: Data, versions: [String], isVideo: Bool) -> CallSessionInternalId? {
if self.contextIdByStableId[stableId] != nil { if self.contextIdByStableId[stableId] != nil {
return nil return nil
} }
@ -390,7 +398,7 @@ private final class CallSessionManagerContext {
if randomStatus == 0 { if randomStatus == 0 {
let internalId = CallSessionInternalId() let internalId = CallSessionInternalId()
let context = CallSessionContext(peerId: peerId, isOutgoing: false, state: .ringing(id: stableId, accessHash: accessHash, gAHash: gAHash, b: b, versions: versions)) let context = CallSessionContext(peerId: peerId, isOutgoing: false, type: isVideo ? .video : .audio, state: .ringing(id: stableId, accessHash: accessHash, gAHash: gAHash, b: b, versions: versions))
self.contexts[internalId] = context self.contexts[internalId] = context
let queue = self.queue let queue = self.queue
context.acknowledgeIncomingCallDisposable.set(self.network.request(Api.functions.phone.receivedCall(peer: .inputPhoneCall(id: stableId, accessHash: accessHash))).start(error: { [weak self] _ in context.acknowledgeIncomingCallDisposable.set(self.network.request(Api.functions.phone.receivedCall(peer: .inputPhoneCall(id: stableId, accessHash: accessHash))).start(error: { [weak self] _ in
@ -414,6 +422,7 @@ private final class CallSessionManagerContext {
if let context = self.contexts[internalId] { if let context = self.contexts[internalId] {
var dropData: (CallSessionStableId, Int64, DropCallSessionReason)? var dropData: (CallSessionStableId, Int64, DropCallSessionReason)?
var wasRinging = false var wasRinging = false
let isVideo = context.type == .video
switch context.state { switch context.state {
case let .ringing(id, accessHash, _, _, _): case let .ringing(id, accessHash, _, _, _):
wasRinging = true wasRinging = true
@ -471,7 +480,7 @@ private final class CallSessionManagerContext {
if let (id, accessHash, reason) = dropData { if let (id, accessHash, reason) = dropData {
self.contextIdByStableId.removeValue(forKey: id) self.contextIdByStableId.removeValue(forKey: id)
context.state = .dropping((dropCallSession(network: self.network, addUpdates: self.addUpdates, stableId: id, accessHash: accessHash, reason: reason) context.state = .dropping((dropCallSession(network: self.network, addUpdates: self.addUpdates, stableId: id, accessHash: accessHash, isVideo: isVideo, reason: reason)
|> deliverOn(self.queue)).start(next: { [weak self] reportRating, sendDebugLogs in |> deliverOn(self.queue)).start(next: { [weak self] reportRating, sendDebugLogs in
if let strongSelf = self { if let strongSelf = self {
if let context = strongSelf.contexts[internalId] { if let context = strongSelf.contexts[internalId] {
@ -722,13 +731,14 @@ private final class CallSessionManagerContext {
} }
} }
case let .phoneCallRequested(flags, id, accessHash, date, adminId, _, gAHash, requestedProtocol): case let .phoneCallRequested(flags, id, accessHash, date, adminId, _, gAHash, requestedProtocol):
let isVideo = (flags & (1 << 5)) != 0
let versions: [String] let versions: [String]
switch requestedProtocol { switch requestedProtocol {
case let .phoneCallProtocol(_, _, _, libraryVersions): case let .phoneCallProtocol(_, _, _, libraryVersions):
versions = libraryVersions versions = libraryVersions
} }
if self.contextIdByStableId[id] == nil { if self.contextIdByStableId[id] == nil {
let internalId = self.addIncoming(peerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: adminId), stableId: id, accessHash: accessHash, timestamp: date, gAHash: gAHash.makeData(), versions: versions) let internalId = self.addIncoming(peerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: adminId), stableId: id, accessHash: accessHash, timestamp: date, gAHash: gAHash.makeData(), versions: versions, isVideo: isVideo)
if let internalId = internalId { if let internalId = internalId {
var resultRingingStateValue: CallSessionRingingState? var resultRingingStateValue: CallSessionRingingState?
for ringingState in self.ringingStatesValue() { for ringingState in self.ringingStatesValue() {
@ -738,7 +748,7 @@ private final class CallSessionManagerContext {
} }
} }
if let context = self.contexts[internalId] { if let context = self.contexts[internalId] {
let callSession = CallSession(id: internalId, isOutgoing: context.isOutgoing, state: CallSessionState(context)) let callSession = CallSession(id: internalId, isOutgoing: context.isOutgoing, type: context.type, state: CallSessionState(context))
if let resultRingingStateValue = resultRingingStateValue { if let resultRingingStateValue = resultRingingStateValue {
resultRingingState = (resultRingingStateValue, callSession) resultRingingState = (resultRingingStateValue, callSession)
} }
@ -802,12 +812,12 @@ private final class CallSessionManagerContext {
return (key, keyId, keyVisualHash) return (key, keyId, keyVisualHash)
} }
func request(peerId: PeerId, internalId: CallSessionInternalId) -> CallSessionInternalId? { func request(peerId: PeerId, internalId: CallSessionInternalId, isVideo: Bool) -> CallSessionInternalId? {
let aBytes = malloc(256)! let aBytes = malloc(256)!
let randomStatus = SecRandomCopyBytes(nil, 256, aBytes.assumingMemoryBound(to: UInt8.self)) let randomStatus = SecRandomCopyBytes(nil, 256, aBytes.assumingMemoryBound(to: UInt8.self))
let a = Data(bytesNoCopy: aBytes, count: 256, deallocator: .free) let a = Data(bytesNoCopy: aBytes, count: 256, deallocator: .free)
if randomStatus == 0 { if randomStatus == 0 {
self.contexts[internalId] = CallSessionContext(peerId: peerId, isOutgoing: true, state: .requesting(a: a, disposable: (requestCallSession(postbox: self.postbox, network: self.network, peerId: peerId, a: a, maxLayer: self.maxLayer, versions: self.versions) |> deliverOn(queue)).start(next: { [weak self] result in self.contexts[internalId] = CallSessionContext(peerId: peerId, isOutgoing: true, type: isVideo ? .video : .audio, state: .requesting(a: a, disposable: (requestCallSession(postbox: self.postbox, network: self.network, peerId: peerId, a: a, maxLayer: self.maxLayer, versions: self.versions, isVideo: isVideo) |> deliverOn(queue)).start(next: { [weak self] result in
if let strongSelf = self, let context = strongSelf.contexts[internalId] { if let strongSelf = self, let context = strongSelf.contexts[internalId] {
if case .requesting = context.state { if case .requesting = context.state {
switch result { switch result {
@ -900,12 +910,12 @@ public final class CallSessionManager {
} }
} }
public func request(peerId: PeerId, internalId: CallSessionInternalId = CallSessionInternalId()) -> Signal<CallSessionInternalId, NoError> { public func request(peerId: PeerId, isVideo: Bool, internalId: CallSessionInternalId = CallSessionInternalId()) -> Signal<CallSessionInternalId, NoError> {
return Signal { [weak self] subscriber in return Signal { [weak self] subscriber in
let disposable = MetaDisposable() let disposable = MetaDisposable()
self?.withContext { context in self?.withContext { context in
if let internalId = context.request(peerId: peerId, internalId: internalId) { if let internalId = context.request(peerId: peerId, internalId: internalId, isVideo: isVideo) {
subscriber.putNext(internalId) subscriber.putNext(internalId)
subscriber.putCompletion() subscriber.putCompletion()
} }
@ -1040,7 +1050,7 @@ private enum RequestCallSessionResult {
case failed(CallSessionError) case failed(CallSessionError)
} }
private func requestCallSession(postbox: Postbox, network: Network, peerId: PeerId, a: Data, maxLayer: Int32, versions: [String]) -> Signal<RequestCallSessionResult, NoError> { private func requestCallSession(postbox: Postbox, network: Network, peerId: PeerId, a: Data, maxLayer: Int32, versions: [String], isVideo: Bool) -> Signal<RequestCallSessionResult, NoError> {
return validatedEncryptionConfig(postbox: postbox, network: network) return validatedEncryptionConfig(postbox: postbox, network: network)
|> mapToSignal { config -> Signal<RequestCallSessionResult, NoError> in |> mapToSignal { config -> Signal<RequestCallSessionResult, NoError> in
return postbox.transaction { transaction -> Signal<RequestCallSessionResult, NoError> in return postbox.transaction { transaction -> Signal<RequestCallSessionResult, NoError> in
@ -1056,12 +1066,17 @@ private func requestCallSession(postbox: Postbox, network: Network, peerId: Peer
let gAHash = MTSha256(ga)! let gAHash = MTSha256(ga)!
return network.request(Api.functions.phone.requestCall(flags: 0, userId: inputUser, randomId: Int32(bitPattern: arc4random()), gAHash: Buffer(data: gAHash), protocol: .phoneCallProtocol(flags: (1 << 0) | (1 << 1), minLayer: minLayer, maxLayer: maxLayer, libraryVersions: versions))) var callFlags: Int32 = 0
if isVideo {
callFlags |= 1 << 0
}
return network.request(Api.functions.phone.requestCall(flags: callFlags, userId: inputUser, randomId: Int32(bitPattern: arc4random()), gAHash: Buffer(data: gAHash), protocol: .phoneCallProtocol(flags: (1 << 0) | (1 << 1), minLayer: minLayer, maxLayer: maxLayer, libraryVersions: versions)))
|> map { result -> RequestCallSessionResult in |> map { result -> RequestCallSessionResult in
switch result { switch result {
case let .phoneCall(phoneCall, _): case let .phoneCall(phoneCall, _):
switch phoneCall { switch phoneCall {
case let .phoneCallRequested(flags, id, accessHash, _, _, _, _, _): case let .phoneCallRequested(_, id, accessHash, _, _, _, _, _):
return .success(id: id, accessHash: accessHash, config: config, gA: ga, remoteConfirmationTimestamp: nil) return .success(id: id, accessHash: accessHash, config: config, gA: ga, remoteConfirmationTimestamp: nil)
case let .phoneCallWaiting(_, id, accessHash, _, _, _, _, receiveDate): case let .phoneCallWaiting(_, id, accessHash, _, _, _, _, receiveDate):
return .success(id: id, accessHash: accessHash, config: config, gA: ga, remoteConfirmationTimestamp: receiveDate) return .success(id: id, accessHash: accessHash, config: config, gA: ga, remoteConfirmationTimestamp: receiveDate)
@ -1118,7 +1133,7 @@ private enum DropCallSessionReason {
case missed case missed
} }
private func dropCallSession(network: Network, addUpdates: @escaping (Api.Updates) -> Void, stableId: CallSessionStableId, accessHash: Int64, reason: DropCallSessionReason) -> Signal<(Bool, Bool), NoError> { private func dropCallSession(network: Network, addUpdates: @escaping (Api.Updates) -> Void, stableId: CallSessionStableId, accessHash: Int64, isVideo: Bool, reason: DropCallSessionReason) -> Signal<(Bool, Bool), NoError> {
var mappedReason: Api.PhoneCallDiscardReason var mappedReason: Api.PhoneCallDiscardReason
var duration: Int32 = 0 var duration: Int32 = 0
switch reason { switch reason {
@ -1134,7 +1149,13 @@ private func dropCallSession(network: Network, addUpdates: @escaping (Api.Update
case .missed: case .missed:
mappedReason = .phoneCallDiscardReasonMissed mappedReason = .phoneCallDiscardReasonMissed
} }
return network.request(Api.functions.phone.discardCall(flags: 0, peer: Api.InputPhoneCall.inputPhoneCall(id: stableId, accessHash: accessHash), duration: duration, reason: mappedReason, connectionId: 0))
var callFlags: Int32 = 0
if isVideo {
callFlags |= 1 << 0
}
return network.request(Api.functions.phone.discardCall(flags: callFlags, peer: Api.InputPhoneCall.inputPhoneCall(id: stableId, accessHash: accessHash), duration: duration, reason: mappedReason, connectionId: 0))
|> map(Optional.init) |> map(Optional.init)
|> `catch` { _ -> Signal<Api.Updates?, NoError> in |> `catch` { _ -> Signal<Api.Updates?, NoError> in
return .single(nil) return .single(nil)

View File

@ -75,7 +75,7 @@ public func tagsForStoreMessage(incoming: Bool, attributes: [MessageAttribute],
tags.insert(.webPage) tags.insert(.webPage)
} else if let action = attachment as? TelegramMediaAction { } else if let action = attachment as? TelegramMediaAction {
switch action.action { switch action.action {
case let .phoneCall(_, discardReason, _): case let .phoneCall(_, discardReason, _, _):
globalTags.insert(.Calls) globalTags.insert(.Calls)
if incoming, let discardReason = discardReason, case .missed = discardReason { if incoming, let discardReason = discardReason, case .missed = discardReason {
globalTags.insert(.MissedCalls) globalTags.insert(.MissedCalls)

View File

@ -32,12 +32,13 @@ func telegramMediaActionFromApiAction(_ action: Api.MessageAction) -> TelegramMe
return TelegramMediaAction(action: .pinnedMessageUpdated) return TelegramMediaAction(action: .pinnedMessageUpdated)
case let .messageActionGameScore(gameId, score): case let .messageActionGameScore(gameId, score):
return TelegramMediaAction(action: .gameScore(gameId: gameId, score: score)) return TelegramMediaAction(action: .gameScore(gameId: gameId, score: score))
case let .messageActionPhoneCall(_, callId, reason, duration): case let .messageActionPhoneCall(flags, callId, reason, duration):
var discardReason: PhoneCallDiscardReason? var discardReason: PhoneCallDiscardReason?
if let reason = reason { if let reason = reason {
discardReason = PhoneCallDiscardReason(apiReason: reason) discardReason = PhoneCallDiscardReason(apiReason: reason)
} }
return TelegramMediaAction(action: .phoneCall(callId: callId, discardReason: discardReason, duration: duration)) let isVideo = (flags & (1 << 2)) != 0
return TelegramMediaAction(action: .phoneCall(callId: callId, discardReason: discardReason, duration: duration, isVideo: isVideo))
case .messageActionEmpty: case .messageActionEmpty:
return nil return nil
case let .messageActionPaymentSent(currency, totalAmount): case let .messageActionPaymentSent(currency, totalAmount):

View File

@ -371,7 +371,7 @@ public func universalServiceMessageString(presentationData: (PresentationTheme,
} else { } else {
attributedString = NSAttributedString(string: strings.Message_PaymentSent(formatCurrencyAmount(totalAmount, currency: currency)).0, font: titleFont, textColor: primaryTextColor) attributedString = NSAttributedString(string: strings.Message_PaymentSent(formatCurrencyAmount(totalAmount, currency: currency)).0, font: titleFont, textColor: primaryTextColor)
} }
case let .phoneCall(_, discardReason, _): case let .phoneCall(_, discardReason, _, _):
var titleString: String var titleString: String
let incoming: Bool let incoming: Bool
if message.flags.contains(.Incoming) { if message.flags.contains(.Incoming) {

View File

@ -141,6 +141,14 @@ protocol SupportedStartCallIntent {
@available(iOS 10.0, *) @available(iOS 10.0, *)
extension INStartAudioCallIntent: SupportedStartCallIntent {} extension INStartAudioCallIntent: SupportedStartCallIntent {}
protocol SupportedStartVideoCallIntent {
@available(iOS 10.0, *)
var contacts: [INPerson]? { get }
}
@available(iOS 10.0, *)
extension INStartVideoCallIntent: SupportedStartVideoCallIntent {}
private enum QueuedWakeup: Int32 { private enum QueuedWakeup: Int32 {
case call case call
case backgroundLocation case backgroundLocation
@ -1725,9 +1733,19 @@ final class SharedApplicationContext {
func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool { func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
if #available(iOS 10.0, *) { if #available(iOS 10.0, *) {
var startCallContacts: [INPerson]?
var startCallIsVideo = false
if let startCallIntent = userActivity.interaction?.intent as? SupportedStartCallIntent { if let startCallIntent = userActivity.interaction?.intent as? SupportedStartCallIntent {
startCallContacts = startCallIntent.contacts
startCallIsVideo = false
} else if let startCallIntent = userActivity.interaction?.intent as? SupportedStartVideoCallIntent {
startCallContacts = startCallIntent.contacts
startCallIsVideo = true
}
if let startCallContacts = startCallContacts {
let startCall: (Int32) -> Void = { userId in let startCall: (Int32) -> Void = { userId in
self.startCallWhenReady(accountId: nil, peerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: userId)) self.startCallWhenReady(accountId: nil, peerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: userId), isVideo: startCallIsVideo)
} }
func cleanPhoneNumber(_ text: String) -> String { func cleanPhoneNumber(_ text: String) -> String {
@ -1754,7 +1772,7 @@ final class SharedApplicationContext {
} }
} }
if let contact = startCallIntent.contacts?.first { if let contact = startCallContacts.first {
var processed = false var processed = false
if let handle = contact.customIdentifier, handle.hasPrefix("tg") { if let handle = contact.customIdentifier, handle.hasPrefix("tg") {
let string = handle.suffix(from: handle.index(handle.startIndex, offsetBy: 2)) let string = handle.suffix(from: handle.index(handle.startIndex, offsetBy: 2))
@ -1914,7 +1932,7 @@ final class SharedApplicationContext {
}) })
} }
private func startCallWhenReady(accountId: AccountRecordId?, peerId: PeerId) { private func startCallWhenReady(accountId: AccountRecordId?, peerId: PeerId, isVideo: Bool) {
let signal = self.sharedContextPromise.get() let signal = self.sharedContextPromise.get()
|> take(1) |> take(1)
|> mapToSignal { sharedApplicationContext -> Signal<AuthorizedApplicationContext, NoError> in |> mapToSignal { sharedApplicationContext -> Signal<AuthorizedApplicationContext, NoError> in
@ -1932,7 +1950,7 @@ final class SharedApplicationContext {
} }
self.openChatWhenReadyDisposable.set((signal self.openChatWhenReadyDisposable.set((signal
|> deliverOnMainQueue).start(next: { context in |> deliverOnMainQueue).start(next: { context in
context.startCall(peerId: peerId) context.startCall(peerId: peerId, isVideo: isVideo)
})) }))
} }

View File

@ -750,7 +750,7 @@ final class AuthorizedApplicationContext {
} }
} }
func startCall(peerId: PeerId) { func startCall(peerId: PeerId, isVideo: Bool) {
guard let appLockContext = self.context.sharedContext.appLockContext as? AppLockContextImpl else { guard let appLockContext = self.context.sharedContext.appLockContext as? AppLockContextImpl else {
return return
} }
@ -763,7 +763,7 @@ final class AuthorizedApplicationContext {
guard let strongSelf = self else { guard let strongSelf = self else {
return return
} }
let _ = strongSelf.context.sharedContext.callManager?.requestCall(account: strongSelf.context.account, peerId: peerId, endCurrentIfAny: false) let _ = strongSelf.context.sharedContext.callManager?.requestCall(account: strongSelf.context.account, peerId: peerId, isVideo: isVideo, endCurrentIfAny: false)
})) }))
} }

View File

@ -469,8 +469,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
self?.openUrl(url, concealed: false, message: nil) self?.openUrl(url, concealed: false, message: nil)
}, openPeer: { peer, navigation in }, openPeer: { peer, navigation in
self?.openPeer(peerId: peer.id, navigation: navigation, fromMessage: nil) self?.openPeer(peerId: peer.id, navigation: navigation, fromMessage: nil)
}, callPeer: { peerId in }, callPeer: { peerId, isVideo in
self?.controllerInteraction?.callPeer(peerId) self?.controllerInteraction?.callPeer(peerId, isVideo)
}, enqueueMessage: { message in }, enqueueMessage: { message in
self?.sendMessages([message]) self?.sendMessages([message])
}, sendSticker: canSendMessagesToChat(strongSelf.presentationInterfaceState) ? { fileReference, sourceNode, sourceRect in }, sendSticker: canSendMessagesToChat(strongSelf.presentationInterfaceState) ? { fileReference, sourceNode, sourceRect in
@ -1178,7 +1178,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
return self?.chatDisplayNode.reactionContainerNode return self?.chatDisplayNode.reactionContainerNode
}, presentGlobalOverlayController: { [weak self] controller, arguments in }, presentGlobalOverlayController: { [weak self] controller, arguments in
self?.presentInGlobalOverlay(controller, with: arguments) self?.presentInGlobalOverlay(controller, with: arguments)
}, callPeer: { [weak self] peerId in }, callPeer: { [weak self] peerId, isVideo in
if let strongSelf = self { if let strongSelf = self {
strongSelf.commitPurposefulAction() strongSelf.commitPurposefulAction()
@ -1199,7 +1199,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
return return
} }
let callResult = context.sharedContext.callManager?.requestCall(account: context.account, peerId: peer.id, endCurrentIfAny: false) let callResult = context.sharedContext.callManager?.requestCall(account: context.account, peerId: peer.id, isVideo: isVideo, endCurrentIfAny: false)
if let callResult = callResult, case let .alreadyInProgress(currentPeerId) = callResult { if let callResult = callResult, case let .alreadyInProgress(currentPeerId) = callResult {
if currentPeerId == peer.id { if currentPeerId == peer.id {
context.sharedContext.navigateToCurrentCall() context.sharedContext.navigateToCurrentCall()
@ -1211,7 +1211,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|> 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 {
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: {
let _ = context.sharedContext.callManager?.requestCall(account: context.account, peerId: peer.id, endCurrentIfAny: true) let _ = context.sharedContext.callManager?.requestCall(account: context.account, peerId: peer.id, isVideo: isVideo, endCurrentIfAny: true)
})]), in: .window(.root)) })]), in: .window(.root))
} }
}) })
@ -4254,9 +4254,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
self?.dismissPeerContactOptions() self?.dismissPeerContactOptions()
}, deleteChat: { [weak self] in }, deleteChat: { [weak self] in
self?.deleteChat(reportChatSpam: false) self?.deleteChat(reportChatSpam: false)
}, beginCall: { [weak self] in }, beginCall: { [weak self] isVideo in
if let strongSelf = self, case let .peer(peerId) = strongSelf.chatLocation { if let strongSelf = self, case let .peer(peerId) = strongSelf.chatLocation {
strongSelf.controllerInteraction?.callPeer(peerId) strongSelf.controllerInteraction?.callPeer(peerId, isVideo)
} }
}, toggleMessageStickerStarred: { [weak self] messageId in }, toggleMessageStickerStarred: { [weak self] messageId in
if let strongSelf = self, let message = strongSelf.chatDisplayNode.historyNode.messageInCurrentHistoryView(messageId) { if let strongSelf = self, let message = strongSelf.chatDisplayNode.historyNode.messageInCurrentHistoryView(messageId) {

View File

@ -90,7 +90,7 @@ public final class ChatControllerInteraction {
let chatControllerNode: () -> ASDisplayNode? let chatControllerNode: () -> ASDisplayNode?
let reactionContainerNode: () -> ReactionSelectionParentNode? let reactionContainerNode: () -> ReactionSelectionParentNode?
let presentGlobalOverlayController: (ViewController, Any?) -> Void let presentGlobalOverlayController: (ViewController, Any?) -> Void
let callPeer: (PeerId) -> Void let callPeer: (PeerId, Bool) -> Void
let longTap: (ChatControllerInteractionLongTapAction, Message?) -> Void let longTap: (ChatControllerInteractionLongTapAction, Message?) -> Void
let openCheckoutOrReceipt: (MessageId) -> Void let openCheckoutOrReceipt: (MessageId) -> Void
let openSearch: () -> Void let openSearch: () -> Void
@ -136,7 +136,7 @@ public final class ChatControllerInteraction {
var searchTextHighightState: (String, [MessageIndex])? var searchTextHighightState: (String, [MessageIndex])?
var seenOneTimeAnimatedMedia = Set<MessageId>() var seenOneTimeAnimatedMedia = Set<MessageId>()
init(openMessage: @escaping (Message, ChatControllerInteractionOpenMessageMode) -> Bool, openPeer: @escaping (PeerId?, ChatControllerInteractionNavigateToPeer, Message?) -> Void, openPeerMention: @escaping (String) -> Void, openMessageContextMenu: @escaping (Message, Bool, ASDisplayNode, CGRect, UIGestureRecognizer?) -> Void, openMessageContextActions: @escaping (Message, ASDisplayNode, CGRect, ContextGesture?) -> Void, navigateToMessage: @escaping (MessageId, MessageId) -> Void, tapMessage: ((Message) -> Void)?, clickThroughMessage: @escaping () -> Void, toggleMessagesSelection: @escaping ([MessageId], Bool) -> Void, sendCurrentMessage: @escaping (Bool) -> Void, sendMessage: @escaping (String) -> Void, sendSticker: @escaping (FileMediaReference, Bool, ASDisplayNode, CGRect) -> Bool, sendGif: @escaping (FileMediaReference, ASDisplayNode, CGRect) -> Bool, sendBotContextResultAsGif: @escaping (ChatContextResultCollection, ChatContextResult, ASDisplayNode, CGRect) -> Bool, requestMessageActionCallback: @escaping (MessageId, MemoryBuffer?, Bool) -> Void, requestMessageActionUrlAuth: @escaping (String, MessageId, Int32) -> Void, activateSwitchInline: @escaping (PeerId?, String) -> Void, openUrl: @escaping (String, Bool, Bool?, Message?) -> Void, shareCurrentLocation: @escaping () -> Void, shareAccountContact: @escaping () -> Void, sendBotCommand: @escaping (MessageId?, String) -> Void, openInstantPage: @escaping (Message, ChatMessageItemAssociatedData?) -> Void, openWallpaper: @escaping (Message) -> Void, openTheme: @escaping (Message) -> Void, openHashtag: @escaping (String?, String) -> Void, updateInputState: @escaping ((ChatTextInputState) -> ChatTextInputState) -> Void, updateInputMode: @escaping ((ChatInputMode) -> ChatInputMode) -> Void, openMessageShareMenu: @escaping (MessageId) -> Void, presentController: @escaping (ViewController, Any?) -> Void, navigationController: @escaping () -> NavigationController?, chatControllerNode: @escaping () -> ASDisplayNode?, reactionContainerNode: @escaping () -> ReactionSelectionParentNode?, presentGlobalOverlayController: @escaping (ViewController, Any?) -> Void, callPeer: @escaping (PeerId) -> Void, longTap: @escaping (ChatControllerInteractionLongTapAction, Message?) -> Void, openCheckoutOrReceipt: @escaping (MessageId) -> Void, openSearch: @escaping () -> Void, setupReply: @escaping (MessageId) -> Void, canSetupReply: @escaping (Message) -> ChatControllerInteractionSwipeAction, navigateToFirstDateMessage: @escaping(Int32) ->Void, requestRedeliveryOfFailedMessages: @escaping (MessageId) -> Void, addContact: @escaping (String) -> Void, rateCall: @escaping (Message, CallId) -> Void, requestSelectMessagePollOptions: @escaping (MessageId, [Data]) -> Void, requestOpenMessagePollResults: @escaping (MessageId, MediaId) -> Void, openAppStorePage: @escaping () -> Void, displayMessageTooltip: @escaping (MessageId, String, ASDisplayNode?, CGRect?) -> Void, seekToTimecode: @escaping (Message, Double, Bool) -> Void, scheduleCurrentMessage: @escaping () -> Void, sendScheduledMessagesNow: @escaping ([MessageId]) -> Void, editScheduledMessagesTime: @escaping ([MessageId]) -> Void, performTextSelectionAction: @escaping (UInt32, NSAttributedString, TextSelectionAction) -> Void, updateMessageLike: @escaping (MessageId, Bool) -> Void, openMessageReactions: @escaping (MessageId) -> Void, displaySwipeToReplyHint: @escaping () -> Void, dismissReplyMarkupMessage: @escaping (Message) -> Void, openMessagePollResults: @escaping (MessageId, Data) -> Void, openPollCreation: @escaping (Bool?) -> Void, displayPollSolution: @escaping (TelegramMediaPollResults.Solution, ASDisplayNode) -> Void, displayPsa: @escaping (String, ASDisplayNode) -> Void, displayDiceTooltip: @escaping (TelegramMediaDice) -> Void, animateDiceSuccess: @escaping () -> Void, requestMessageUpdate: @escaping (MessageId) -> Void, cancelInteractiveKeyboardGestures: @escaping () -> Void, automaticMediaDownloadSettings: MediaAutoDownloadSettings, pollActionState: ChatInterfacePollActionState, stickerSettings: ChatInterfaceStickerSettings) { init(openMessage: @escaping (Message, ChatControllerInteractionOpenMessageMode) -> Bool, openPeer: @escaping (PeerId?, ChatControllerInteractionNavigateToPeer, Message?) -> Void, openPeerMention: @escaping (String) -> Void, openMessageContextMenu: @escaping (Message, Bool, ASDisplayNode, CGRect, UIGestureRecognizer?) -> Void, openMessageContextActions: @escaping (Message, ASDisplayNode, CGRect, ContextGesture?) -> Void, navigateToMessage: @escaping (MessageId, MessageId) -> Void, tapMessage: ((Message) -> Void)?, clickThroughMessage: @escaping () -> Void, toggleMessagesSelection: @escaping ([MessageId], Bool) -> Void, sendCurrentMessage: @escaping (Bool) -> Void, sendMessage: @escaping (String) -> Void, sendSticker: @escaping (FileMediaReference, Bool, ASDisplayNode, CGRect) -> Bool, sendGif: @escaping (FileMediaReference, ASDisplayNode, CGRect) -> Bool, sendBotContextResultAsGif: @escaping (ChatContextResultCollection, ChatContextResult, ASDisplayNode, CGRect) -> Bool, requestMessageActionCallback: @escaping (MessageId, MemoryBuffer?, Bool) -> Void, requestMessageActionUrlAuth: @escaping (String, MessageId, Int32) -> Void, activateSwitchInline: @escaping (PeerId?, String) -> Void, openUrl: @escaping (String, Bool, Bool?, Message?) -> Void, shareCurrentLocation: @escaping () -> Void, shareAccountContact: @escaping () -> Void, sendBotCommand: @escaping (MessageId?, String) -> Void, openInstantPage: @escaping (Message, ChatMessageItemAssociatedData?) -> Void, openWallpaper: @escaping (Message) -> Void, openTheme: @escaping (Message) -> Void, openHashtag: @escaping (String?, String) -> Void, updateInputState: @escaping ((ChatTextInputState) -> ChatTextInputState) -> Void, updateInputMode: @escaping ((ChatInputMode) -> ChatInputMode) -> Void, openMessageShareMenu: @escaping (MessageId) -> Void, presentController: @escaping (ViewController, Any?) -> Void, navigationController: @escaping () -> NavigationController?, chatControllerNode: @escaping () -> ASDisplayNode?, reactionContainerNode: @escaping () -> ReactionSelectionParentNode?, presentGlobalOverlayController: @escaping (ViewController, Any?) -> Void, callPeer: @escaping (PeerId, Bool) -> Void, longTap: @escaping (ChatControllerInteractionLongTapAction, Message?) -> Void, openCheckoutOrReceipt: @escaping (MessageId) -> Void, openSearch: @escaping () -> Void, setupReply: @escaping (MessageId) -> Void, canSetupReply: @escaping (Message) -> ChatControllerInteractionSwipeAction, navigateToFirstDateMessage: @escaping(Int32) ->Void, requestRedeliveryOfFailedMessages: @escaping (MessageId) -> Void, addContact: @escaping (String) -> Void, rateCall: @escaping (Message, CallId) -> Void, requestSelectMessagePollOptions: @escaping (MessageId, [Data]) -> Void, requestOpenMessagePollResults: @escaping (MessageId, MediaId) -> Void, openAppStorePage: @escaping () -> Void, displayMessageTooltip: @escaping (MessageId, String, ASDisplayNode?, CGRect?) -> Void, seekToTimecode: @escaping (Message, Double, Bool) -> Void, scheduleCurrentMessage: @escaping () -> Void, sendScheduledMessagesNow: @escaping ([MessageId]) -> Void, editScheduledMessagesTime: @escaping ([MessageId]) -> Void, performTextSelectionAction: @escaping (UInt32, NSAttributedString, TextSelectionAction) -> Void, updateMessageLike: @escaping (MessageId, Bool) -> Void, openMessageReactions: @escaping (MessageId) -> Void, displaySwipeToReplyHint: @escaping () -> Void, dismissReplyMarkupMessage: @escaping (Message) -> Void, openMessagePollResults: @escaping (MessageId, Data) -> Void, openPollCreation: @escaping (Bool?) -> Void, displayPollSolution: @escaping (TelegramMediaPollResults.Solution, ASDisplayNode) -> Void, displayPsa: @escaping (String, ASDisplayNode) -> Void, displayDiceTooltip: @escaping (TelegramMediaDice) -> Void, animateDiceSuccess: @escaping () -> Void, requestMessageUpdate: @escaping (MessageId) -> Void, cancelInteractiveKeyboardGestures: @escaping () -> Void, automaticMediaDownloadSettings: MediaAutoDownloadSettings, pollActionState: ChatInterfacePollActionState, stickerSettings: ChatInterfaceStickerSettings) {
self.openMessage = openMessage self.openMessage = openMessage
self.openPeer = openPeer self.openPeer = openPeer
self.openPeerMention = openPeerMention self.openPeerMention = openPeerMention
@ -218,7 +218,7 @@ public final class ChatControllerInteraction {
return nil return nil
}, reactionContainerNode: { }, reactionContainerNode: {
return nil return nil
}, presentGlobalOverlayController: { _, _ in }, callPeer: { _ in }, longTap: { _, _ in }, openCheckoutOrReceipt: { _ in }, openSearch: { }, setupReply: { _ in }, presentGlobalOverlayController: { _, _ in }, callPeer: { _, _ in }, longTap: { _, _ in }, openCheckoutOrReceipt: { _ in }, openSearch: { }, setupReply: { _ in
}, canSetupReply: { _ in }, canSetupReply: { _ in
return .none return .none
}, navigateToFirstDateMessage: { _ in }, navigateToFirstDateMessage: { _ in

View File

@ -223,7 +223,7 @@ final class ChatInfoTitlePanelNode: ChatTitleAccessoryPanelNode {
case .search: case .search:
self.interfaceInteraction?.beginMessageSearch(.everything, "") self.interfaceInteraction?.beginMessageSearch(.everything, "")
case .call: case .call:
self.interfaceInteraction?.beginCall() self.interfaceInteraction?.beginCall(false)
case .report: case .report:
self.interfaceInteraction?.reportPeer() self.interfaceInteraction?.reportPeer()
case .unarchive: case .unarchive:

View File

@ -406,7 +406,7 @@ func contextMenuForChatPresentationIntefaceState(chatPresentationInterfaceState:
if data.messageActions.options.contains(.rateCall) { if data.messageActions.options.contains(.rateCall) {
var callId: CallId? var callId: CallId?
for media in message.media { for media in message.media {
if let action = media as? TelegramMediaAction, case let .phoneCall(id, discardReason, _) = action.action { if let action = media as? TelegramMediaAction, case let .phoneCall(id, discardReason, _, _) = action.action {
if discardReason != .busy && discardReason != .missed { if discardReason != .busy && discardReason != .missed {
if let logName = callLogNameForId(id: id, account: context.account) { if let logName = callLogNameForId(id: id, account: context.account) {
let logsPath = callLogsPath(account: context.account) let logsPath = callLogsPath(account: context.account)

View File

@ -78,7 +78,7 @@ enum ChatMessageBubbleContentTapAction {
case instantPage case instantPage
case wallpaper case wallpaper
case theme case theme
case call(PeerId) case call(peerId: PeerId, isVideo: Bool)
case openMessage case openMessage
case timecode(Double, String) case timecode(Double, String)
case tooltip(String, ASDisplayNode?, CGRect?) case tooltip(String, ASDisplayNode?, CGRect?)

View File

@ -480,8 +480,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode
return false return false
} }
else if let media = media as? TelegramMediaAction { else if let media = media as? TelegramMediaAction {
if case .phoneCall(_, _, _) = media.action { if case .phoneCall(_, _, _, _) = media.action {
} else { } else {
return false return false
} }
@ -2547,9 +2546,9 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode
item.controllerInteraction.openTheme(item.message) item.controllerInteraction.openTheme(item.message)
}) })
} }
case let .call(peerId): case let .call(peerId, isVideo):
return .optionalAction({ return .optionalAction({
self.item?.controllerInteraction.callPeer(peerId) self.item?.controllerInteraction.callPeer(peerId, isVideo)
}) })
case .openMessage: case .openMessage:
if let item = self.item { if let item = self.item {

View File

@ -76,8 +76,10 @@ class ChatMessageCallBubbleContentNode: ChatMessageBubbleContentNode {
var titleString: String? var titleString: String?
var callDuration: Int32? var callDuration: Int32?
var callSuccessful = true var callSuccessful = true
var isVideo = false
for media in item.message.media { for media in item.message.media {
if let action = media as? TelegramMediaAction, case let .phoneCall(_, discardReason, duration) = action.action { if let action = media as? TelegramMediaAction, case let .phoneCall(_, discardReason, duration, isVideoValue) = action.action {
isVideo = isVideoValue
callDuration = duration callDuration = duration
if let discardReason = discardReason { if let discardReason = discardReason {
switch discardReason { switch discardReason {
@ -98,9 +100,17 @@ class ChatMessageCallBubbleContentNode: ChatMessageBubbleContentNode {
if titleString == nil { if titleString == nil {
let baseString: String let baseString: String
if message.flags.contains(.Incoming) { if message.flags.contains(.Incoming) {
baseString = item.presentationData.strings.Notification_CallIncoming if isVideo {
baseString = item.presentationData.strings.Notification_VideoCallIncoming
} else {
baseString = item.presentationData.strings.Notification_CallIncoming
}
} else { } else {
baseString = item.presentationData.strings.Notification_CallOutgoing if isVideo {
baseString = item.presentationData.strings.Notification_VideoCallOutgoing
} else {
baseString = item.presentationData.strings.Notification_CallOutgoing
}
} }
titleString = baseString titleString = baseString
@ -203,7 +213,13 @@ class ChatMessageCallBubbleContentNode: ChatMessageBubbleContentNode {
@objc func callButtonPressed() { @objc func callButtonPressed() {
if let item = self.item { if let item = self.item {
item.controllerInteraction.callPeer(item.message.id.peerId) var isVideo = false
for media in item.message.media {
if let action = media as? TelegramMediaAction, case let .phoneCall(_, _, _, isVideoValue) = action.action {
isVideo = isVideoValue
}
}
item.controllerInteraction.callPeer(item.message.id.peerId, isVideo)
} }
} }
@ -211,7 +227,13 @@ class ChatMessageCallBubbleContentNode: ChatMessageBubbleContentNode {
if self.buttonNode.frame.contains(point) { if self.buttonNode.frame.contains(point) {
return .ignore return .ignore
} else if self.bounds.contains(point), let item = self.item { } else if self.bounds.contains(point), let item = self.item {
return .call(item.message.id.peerId) var isVideo = false
for media in item.message.media {
if let action = media as? TelegramMediaAction, case let .phoneCall(_, _, _, isVideoValue) = action.action {
isVideo = isVideoValue
}
}
return .call(peerId: item.message.id.peerId, isVideo: isVideo)
} else { } else {
return .none return .none
} }

View File

@ -616,7 +616,7 @@ final class ChatMessageAccessibilityData {
canReply = false canReply = false
} }
else if let media = media as? TelegramMediaAction { else if let media = media as? TelegramMediaAction {
if case .phoneCall(_, _, _) = media.action { if case .phoneCall = media.action {
} else { } else {
canReply = false canReply = false
} }

View File

@ -100,7 +100,7 @@ final class ChatPanelInterfaceInteraction {
let presentPeerContact: () -> Void let presentPeerContact: () -> Void
let dismissReportPeer: () -> Void let dismissReportPeer: () -> Void
let deleteChat: () -> Void let deleteChat: () -> Void
let beginCall: () -> Void let beginCall: (Bool) -> Void
let toggleMessageStickerStarred: (MessageId) -> Void let toggleMessageStickerStarred: (MessageId) -> Void
let presentController: (ViewController, Any?) -> Void let presentController: (ViewController, Any?) -> Void
let getNavigationController: () -> NavigationController? let getNavigationController: () -> NavigationController?
@ -120,7 +120,7 @@ final class ChatPanelInterfaceInteraction {
let displaySearchResultsTooltip: (ASDisplayNode, CGRect) -> Void let displaySearchResultsTooltip: (ASDisplayNode, CGRect) -> Void
let statuses: ChatPanelInterfaceInteractionStatuses? let statuses: ChatPanelInterfaceInteractionStatuses?
init(setupReplyMessage: @escaping (MessageId, @escaping (ContainedViewLayoutTransition) -> Void) -> Void, setupEditMessage: @escaping (MessageId?, @escaping (ContainedViewLayoutTransition) -> Void) -> Void, beginMessageSelection: @escaping ([MessageId], @escaping (ContainedViewLayoutTransition) -> Void) -> Void, deleteSelectedMessages: @escaping () -> Void, reportSelectedMessages: @escaping () -> Void, reportMessages: @escaping ([Message], ContextController?) -> Void, deleteMessages: @escaping ([Message], ContextController?, @escaping (ContextMenuActionResult) -> Void) -> Void, forwardSelectedMessages: @escaping () -> Void, forwardCurrentForwardMessages: @escaping () -> Void, forwardMessages: @escaping ([Message]) -> Void, shareSelectedMessages: @escaping () -> Void, updateTextInputStateAndMode: @escaping ((ChatTextInputState, ChatInputMode) -> (ChatTextInputState, ChatInputMode)) -> Void, updateInputModeAndDismissedButtonKeyboardMessageId: @escaping ((ChatPresentationInterfaceState) -> (ChatInputMode, MessageId?)) -> Void, openStickers: @escaping () -> Void, editMessage: @escaping () -> Void, beginMessageSearch: @escaping (ChatSearchDomain, String) -> Void, dismissMessageSearch: @escaping () -> Void, updateMessageSearch: @escaping (String) -> Void, openSearchResults: @escaping () -> Void, navigateMessageSearch: @escaping (ChatPanelSearchNavigationAction) -> Void, openCalendarSearch: @escaping () -> Void, toggleMembersSearch: @escaping (Bool) -> Void, navigateToMessage: @escaping (MessageId) -> Void, navigateToChat: @escaping (PeerId) -> Void, navigateToProfile: @escaping (PeerId) -> Void, openPeerInfo: @escaping () -> Void, togglePeerNotifications: @escaping () -> Void, sendContextResult: @escaping (ChatContextResultCollection, ChatContextResult, ASDisplayNode, CGRect) -> Bool, sendBotCommand: @escaping (Peer, String) -> Void, sendBotStart: @escaping (String?) -> Void, botSwitchChatWithPayload: @escaping (PeerId, String) -> Void, beginMediaRecording: @escaping (Bool) -> Void, finishMediaRecording: @escaping (ChatFinishMediaRecordingAction) -> Void, stopMediaRecording: @escaping () -> Void, lockMediaRecording: @escaping () -> Void, deleteRecordedMedia: @escaping () -> Void, sendRecordedMedia: @escaping () -> Void, displayRestrictedInfo: @escaping (ChatPanelRestrictionInfoSubject, ChatPanelRestrictionInfoDisplayType) -> Void, displayVideoUnmuteTip: @escaping (CGPoint?) -> Void, switchMediaRecordingMode: @escaping () -> Void, setupMessageAutoremoveTimeout: @escaping () -> Void, sendSticker: @escaping (FileMediaReference, ASDisplayNode, CGRect) -> Bool, unblockPeer: @escaping () -> Void, pinMessage: @escaping (MessageId) -> Void, unpinMessage: @escaping () -> Void, shareAccountContact: @escaping () -> Void, reportPeer: @escaping () -> Void, presentPeerContact: @escaping () -> Void, dismissReportPeer: @escaping () -> Void, deleteChat: @escaping () -> Void, beginCall: @escaping () -> Void, toggleMessageStickerStarred: @escaping (MessageId) -> Void, presentController: @escaping (ViewController, Any?) -> Void, getNavigationController: @escaping () -> NavigationController?, presentGlobalOverlayController: @escaping (ViewController, Any?) -> Void, navigateFeed: @escaping () -> Void, openGrouping: @escaping () -> Void, toggleSilentPost: @escaping () -> Void, requestUnvoteInMessage: @escaping (MessageId) -> Void, requestStopPollInMessage: @escaping (MessageId) -> Void, updateInputLanguage: @escaping ((String?) -> String?) -> Void, unarchiveChat: @escaping () -> Void, openLinkEditing: @escaping () -> Void, reportPeerIrrelevantGeoLocation: @escaping () -> Void, displaySlowmodeTooltip: @escaping (ASDisplayNode, CGRect) -> Void, displaySendMessageOptions: @escaping (ASDisplayNode, ContextGesture) -> Void, openScheduledMessages: @escaping () -> Void, displaySearchResultsTooltip: @escaping (ASDisplayNode, CGRect) -> Void, statuses: ChatPanelInterfaceInteractionStatuses?) { init(setupReplyMessage: @escaping (MessageId, @escaping (ContainedViewLayoutTransition) -> Void) -> Void, setupEditMessage: @escaping (MessageId?, @escaping (ContainedViewLayoutTransition) -> Void) -> Void, beginMessageSelection: @escaping ([MessageId], @escaping (ContainedViewLayoutTransition) -> Void) -> Void, deleteSelectedMessages: @escaping () -> Void, reportSelectedMessages: @escaping () -> Void, reportMessages: @escaping ([Message], ContextController?) -> Void, deleteMessages: @escaping ([Message], ContextController?, @escaping (ContextMenuActionResult) -> Void) -> Void, forwardSelectedMessages: @escaping () -> Void, forwardCurrentForwardMessages: @escaping () -> Void, forwardMessages: @escaping ([Message]) -> Void, shareSelectedMessages: @escaping () -> Void, updateTextInputStateAndMode: @escaping ((ChatTextInputState, ChatInputMode) -> (ChatTextInputState, ChatInputMode)) -> Void, updateInputModeAndDismissedButtonKeyboardMessageId: @escaping ((ChatPresentationInterfaceState) -> (ChatInputMode, MessageId?)) -> Void, openStickers: @escaping () -> Void, editMessage: @escaping () -> Void, beginMessageSearch: @escaping (ChatSearchDomain, String) -> Void, dismissMessageSearch: @escaping () -> Void, updateMessageSearch: @escaping (String) -> Void, openSearchResults: @escaping () -> Void, navigateMessageSearch: @escaping (ChatPanelSearchNavigationAction) -> Void, openCalendarSearch: @escaping () -> Void, toggleMembersSearch: @escaping (Bool) -> Void, navigateToMessage: @escaping (MessageId) -> Void, navigateToChat: @escaping (PeerId) -> Void, navigateToProfile: @escaping (PeerId) -> Void, openPeerInfo: @escaping () -> Void, togglePeerNotifications: @escaping () -> Void, sendContextResult: @escaping (ChatContextResultCollection, ChatContextResult, ASDisplayNode, CGRect) -> Bool, sendBotCommand: @escaping (Peer, String) -> Void, sendBotStart: @escaping (String?) -> Void, botSwitchChatWithPayload: @escaping (PeerId, String) -> Void, beginMediaRecording: @escaping (Bool) -> Void, finishMediaRecording: @escaping (ChatFinishMediaRecordingAction) -> Void, stopMediaRecording: @escaping () -> Void, lockMediaRecording: @escaping () -> Void, deleteRecordedMedia: @escaping () -> Void, sendRecordedMedia: @escaping () -> Void, displayRestrictedInfo: @escaping (ChatPanelRestrictionInfoSubject, ChatPanelRestrictionInfoDisplayType) -> Void, displayVideoUnmuteTip: @escaping (CGPoint?) -> Void, switchMediaRecordingMode: @escaping () -> Void, setupMessageAutoremoveTimeout: @escaping () -> Void, sendSticker: @escaping (FileMediaReference, ASDisplayNode, CGRect) -> Bool, unblockPeer: @escaping () -> Void, pinMessage: @escaping (MessageId) -> Void, unpinMessage: @escaping () -> Void, shareAccountContact: @escaping () -> Void, reportPeer: @escaping () -> Void, presentPeerContact: @escaping () -> Void, dismissReportPeer: @escaping () -> Void, deleteChat: @escaping () -> Void, beginCall: @escaping (Bool) -> Void, toggleMessageStickerStarred: @escaping (MessageId) -> Void, presentController: @escaping (ViewController, Any?) -> Void, getNavigationController: @escaping () -> NavigationController?, presentGlobalOverlayController: @escaping (ViewController, Any?) -> Void, navigateFeed: @escaping () -> Void, openGrouping: @escaping () -> Void, toggleSilentPost: @escaping () -> Void, requestUnvoteInMessage: @escaping (MessageId) -> Void, requestStopPollInMessage: @escaping (MessageId) -> Void, updateInputLanguage: @escaping ((String?) -> String?) -> Void, unarchiveChat: @escaping () -> Void, openLinkEditing: @escaping () -> Void, reportPeerIrrelevantGeoLocation: @escaping () -> Void, displaySlowmodeTooltip: @escaping (ASDisplayNode, CGRect) -> Void, displaySendMessageOptions: @escaping (ASDisplayNode, ContextGesture) -> Void, openScheduledMessages: @escaping () -> Void, displaySearchResultsTooltip: @escaping (ASDisplayNode, CGRect) -> Void, statuses: ChatPanelInterfaceInteractionStatuses?) {
self.setupReplyMessage = setupReplyMessage self.setupReplyMessage = setupReplyMessage
self.setupEditMessage = setupEditMessage self.setupEditMessage = setupEditMessage
self.beginMessageSelection = beginMessageSelection self.beginMessageSelection = beginMessageSelection

View File

@ -103,7 +103,7 @@ final class ChatRecentActionsController: TelegramBaseController {
}, presentPeerContact: { }, presentPeerContact: {
}, dismissReportPeer: { }, dismissReportPeer: {
}, deleteChat: { }, deleteChat: {
}, beginCall: { }, beginCall: { _ in
}, toggleMessageStickerStarred: { _ in }, toggleMessageStickerStarred: { _ in
}, presentController: { _, _ in }, presentController: { _, _ in
}, getNavigationController: { }, getNavigationController: {

View File

@ -180,8 +180,8 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
self?.openUrl(url) self?.openUrl(url)
}, openPeer: { peer, navigation in }, openPeer: { peer, navigation in
self?.openPeer(peerId: peer.id, peer: peer) self?.openPeer(peerId: peer.id, peer: peer)
}, callPeer: { peerId in }, callPeer: { peerId, isVideo in
self?.controllerInteraction?.callPeer(peerId) self?.controllerInteraction?.callPeer(peerId, isVideo)
}, enqueueMessage: { _ in }, enqueueMessage: { _ in
}, sendSticker: nil, setupTemporaryHiddenMedia: { _, _, _ in }, chatAvatarHiddenMedia: { _, _ in })) }, sendSticker: nil, setupTemporaryHiddenMedia: { _, _, _ in }, chatAvatarHiddenMedia: { _, _ in }))
} }
@ -242,7 +242,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
return self return self
}, reactionContainerNode: { }, reactionContainerNode: {
return nil return nil
}, presentGlobalOverlayController: { _, _ in }, callPeer: { _ in }, longTap: { [weak self] action, message in }, presentGlobalOverlayController: { _, _ in }, callPeer: { _, _ in }, longTap: { [weak self] action, message in
if let strongSelf = self { if let strongSelf = self {
switch action { switch action {
case let .url(url): case let .url(url):

View File

@ -115,7 +115,7 @@ private final class DrawingStickersScreenNode: ViewControllerTracingNode {
return nil return nil
}, reactionContainerNode: { }, reactionContainerNode: {
return nil return nil
}, presentGlobalOverlayController: { _, _ in }, callPeer: { _ in }, longTap: { _, _ in }, openCheckoutOrReceipt: { _ in }, openSearch: { }, setupReply: { _ in }, presentGlobalOverlayController: { _, _ in }, callPeer: { _, _ in }, longTap: { _, _ in }, openCheckoutOrReceipt: { _ in }, openSearch: { }, setupReply: { _ in
}, canSetupReply: { _ in }, canSetupReply: { _ in
return .none return .none
}, navigateToFirstDateMessage: { _ in }, navigateToFirstDateMessage: { _ in

View File

@ -98,7 +98,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, UIGestu
}, reactionContainerNode: { }, reactionContainerNode: {
return nil return nil
}, presentGlobalOverlayController: { _, _ in }, presentGlobalOverlayController: { _, _ in
}, callPeer: { _ in }, callPeer: { _, _ in
}, longTap: { _, _ in }, longTap: { _, _ in
}, openCheckoutOrReceipt: { _ in }, openCheckoutOrReceipt: { _ in
}, openSearch: { }, openSearch: {
@ -234,7 +234,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, UIGestu
openMessageImpl = { [weak self] id in openMessageImpl = { [weak self] id in
if let strongSelf = self, strongSelf.isNodeLoaded, let message = strongSelf.historyNode.messageInCurrentHistoryView(id) { if let strongSelf = self, strongSelf.isNodeLoaded, let message = strongSelf.historyNode.messageInCurrentHistoryView(id) {
return strongSelf.context.sharedContext.openChatMessage(OpenChatMessageParams(context: strongSelf.context, message: message, standalone: false, reverseMessageGalleryOrder: false, navigationController: nil, dismissInput: { }, present: { _, _ in }, transitionNode: { _, _ in return nil }, addToTransitionSurface: { _ in }, openUrl: { _ in }, openPeer: { _, _ in }, callPeer: { _ in }, enqueueMessage: { _ in }, sendSticker: nil, setupTemporaryHiddenMedia: { _, _, _ in }, chatAvatarHiddenMedia: { _, _ in })) return strongSelf.context.sharedContext.openChatMessage(OpenChatMessageParams(context: strongSelf.context, message: message, standalone: false, reverseMessageGalleryOrder: false, navigationController: nil, dismissInput: { }, present: { _, _ in }, transitionNode: { _, _ in return nil }, addToTransitionSurface: { _ in }, openUrl: { _ in }, openPeer: { _, _ in }, callPeer: { _, _ in }, enqueueMessage: { _ in }, sendSticker: nil, setupTemporaryHiddenMedia: { _, _, _ in }, chatAvatarHiddenMedia: { _, _ in }))
} }
return false return false
} }

View File

@ -704,7 +704,7 @@ func availableActionsForMemberOfPeer(accountPeerId: PeerId, peer: Peer, member:
return result return result
} }
func peerInfoHeaderButtons(peer: Peer?, cachedData: CachedPeerData?, isOpenedFromChat: Bool) -> [PeerInfoHeaderButtonKey] { func peerInfoHeaderButtons(peer: Peer?, cachedData: CachedPeerData?, isOpenedFromChat: Bool, videoCallsEnabled: Bool) -> [PeerInfoHeaderButtonKey] {
var result: [PeerInfoHeaderButtonKey] = [] var result: [PeerInfoHeaderButtonKey] = []
if let user = peer as? TelegramUser { if let user = peer as? TelegramUser {
if !isOpenedFromChat { if !isOpenedFromChat {
@ -719,6 +719,9 @@ func peerInfoHeaderButtons(peer: Peer?, cachedData: CachedPeerData?, isOpenedFro
} }
if callsAvailable { if callsAvailable {
result.append(.call) result.append(.call)
if videoCallsEnabled {
result.append(.videoCall)
}
} }
result.append(.mute) result.append(.mute)
if isOpenedFromChat { if isOpenedFromChat {

View File

@ -21,6 +21,7 @@ enum PeerInfoHeaderButtonKey: Hashable {
case message case message
case discussion case discussion
case call case call
case videoCall
case mute case mute
case more case more
case addMember case addMember
@ -31,6 +32,7 @@ enum PeerInfoHeaderButtonKey: Hashable {
enum PeerInfoHeaderButtonIcon { enum PeerInfoHeaderButtonIcon {
case message case message
case call case call
case videoCall
case mute case mute
case unmute case unmute
case more case more
@ -103,6 +105,8 @@ final class PeerInfoHeaderButtonNode: HighlightableButtonNode {
imageName = "Peer Info/ButtonMessage" imageName = "Peer Info/ButtonMessage"
case .call: case .call:
imageName = "Peer Info/ButtonCall" imageName = "Peer Info/ButtonCall"
case .videoCall:
imageName = "Chat/Input/Text/IconVideo"
case .mute: case .mute:
imageName = "Peer Info/ButtonMute" imageName = "Peer Info/ButtonMute"
case .unmute: case .unmute:
@ -116,7 +120,7 @@ final class PeerInfoHeaderButtonNode: HighlightableButtonNode {
case .leave: case .leave:
imageName = "Peer Info/ButtonLeave" imageName = "Peer Info/ButtonLeave"
} }
if let image = UIImage(bundleImageName: imageName) { if let image = generateTintedImage(image: UIImage(bundleImageName: imageName), color: .white) {
let imageRect = CGRect(origin: CGPoint(x: floor((size.width - image.size.width) / 2.0), y: floor((size.height - image.size.height) / 2.0)), size: image.size) let imageRect = CGRect(origin: CGPoint(x: floor((size.width - image.size.width) / 2.0), y: floor((size.height - image.size.height) / 2.0)), size: image.size)
context.clip(to: imageRect, mask: image.cgImage!) context.clip(to: imageRect, mask: image.cgImage!)
context.fill(imageRect) context.fill(imageRect)
@ -1650,6 +1654,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
private var presentationData: PresentationData? private var presentationData: PresentationData?
private let isOpenedFromChat: Bool private let isOpenedFromChat: Bool
private let videoCallsEnabled: Bool
private(set) var isAvatarExpanded: Bool private(set) var isAvatarExpanded: Bool
@ -1682,6 +1687,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
self.context = context self.context = context
self.isAvatarExpanded = avatarInitiallyExpanded self.isAvatarExpanded = avatarInitiallyExpanded
self.isOpenedFromChat = isOpenedFromChat self.isOpenedFromChat = isOpenedFromChat
self.videoCallsEnabled = context.sharedContext.immediateExperimentalUISettings.videoCalls
self.avatarListNode = PeerInfoAvatarListNode(context: context, readyWhenGalleryLoads: avatarInitiallyExpanded) self.avatarListNode = PeerInfoAvatarListNode(context: context, readyWhenGalleryLoads: avatarInitiallyExpanded)
@ -1868,7 +1874,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
let expandedAvatarListHeight = min(width, containerHeight - expandedAvatarControlsHeight) let expandedAvatarListHeight = min(width, containerHeight - expandedAvatarControlsHeight)
let expandedAvatarListSize = CGSize(width: width, height: expandedAvatarListHeight) let expandedAvatarListSize = CGSize(width: width, height: expandedAvatarListHeight)
let buttonKeys: [PeerInfoHeaderButtonKey] = peerInfoHeaderButtons(peer: peer, cachedData: cachedData, isOpenedFromChat: self.isOpenedFromChat) let buttonKeys: [PeerInfoHeaderButtonKey] = peerInfoHeaderButtons(peer: peer, cachedData: cachedData, isOpenedFromChat: self.isOpenedFromChat, videoCallsEnabled: self.videoCallsEnabled)
var isVerified = false var isVerified = false
let titleString: NSAttributedString let titleString: NSAttributedString
@ -2239,6 +2245,9 @@ final class PeerInfoHeaderNode: ASDisplayNode {
case .call: case .call:
buttonText = presentationData.strings.PeerInfo_ButtonCall buttonText = presentationData.strings.PeerInfo_ButtonCall
buttonIcon = .call buttonIcon = .call
case .videoCall:
buttonText = presentationData.strings.PeerInfo_ButtonVideoCall
buttonIcon = .videoCall
case .mute: case .mute:
if let notificationSettings = notificationSettings, case .muted = notificationSettings.muteState { if let notificationSettings = notificationSettings, case .muted = notificationSettings.muteState {
buttonText = presentationData.strings.PeerInfo_ButtonUnmute buttonText = presentationData.strings.PeerInfo_ButtonUnmute

View File

@ -395,7 +395,7 @@ final class PeerInfoSelectionPanelNode: ASDisplayNode {
}, presentPeerContact: { }, presentPeerContact: {
}, dismissReportPeer: { }, dismissReportPeer: {
}, deleteChat: { }, deleteChat: {
}, beginCall: { }, beginCall: { _ in
}, toggleMessageStickerStarred: { _ in }, toggleMessageStickerStarred: { _ in
}, presentController: { _, _ in }, presentController: { _, _ in
}, getNavigationController: { }, getNavigationController: {
@ -1035,6 +1035,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
private let context: AccountContext private let context: AccountContext
private let peerId: PeerId private let peerId: PeerId
private let isOpenedFromChat: Bool private let isOpenedFromChat: Bool
private let videoCallsEnabled: Bool
private let callMessages: [Message] private let callMessages: [Message]
private let isMediaOnly: Bool private let isMediaOnly: Bool
@ -1096,6 +1097,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
self.context = context self.context = context
self.peerId = peerId self.peerId = peerId
self.isOpenedFromChat = isOpenedFromChat self.isOpenedFromChat = isOpenedFromChat
self.videoCallsEnabled = context.sharedContext.immediateExperimentalUISettings.videoCalls
self.presentationData = context.sharedContext.currentPresentationData.with { $0 } self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
self.nearbyPeerDistance = nearbyPeerDistance self.nearbyPeerDistance = nearbyPeerDistance
self.callMessages = callMessages self.callMessages = callMessages
@ -1529,7 +1531,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
return nil return nil
}, reactionContainerNode: { }, reactionContainerNode: {
return nil return nil
}, presentGlobalOverlayController: { _, _ in }, callPeer: { _ in }, presentGlobalOverlayController: { _, _ in }, callPeer: { _, _ in
}, longTap: { [weak self] content, _ in }, longTap: { [weak self] content, _ in
guard let strongSelf = self else { guard let strongSelf = self else {
return return
@ -2133,7 +2135,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
self?.openUrl(url: url, concealed: false, external: false) self?.openUrl(url: url, concealed: false, external: false)
}, openPeer: { [weak self] peer, navigation in }, openPeer: { [weak self] peer, navigation in
self?.openPeer(peerId: peer.id, navigation: navigation) self?.openPeer(peerId: peer.id, navigation: navigation)
}, callPeer: { peerId in }, callPeer: { peerId, isVideo in
//self?.controllerInteraction?.callPeer(peerId) //self?.controllerInteraction?.callPeer(peerId)
}, enqueueMessage: { _ in }, enqueueMessage: { _ in
}, sendSticker: nil, setupTemporaryHiddenMedia: { _, _, _ in }, chatAvatarHiddenMedia: { _, _ in })) }, sendSticker: nil, setupTemporaryHiddenMedia: { _, _, _ in }, chatAvatarHiddenMedia: { _, _ in }))
@ -2213,7 +2215,9 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
} }
} }
case .call: case .call:
self.requestCall() self.requestCall(isVideo: false)
case .videoCall:
self.requestCall(isVideo: true)
case .mute: case .mute:
if let notificationSettings = self.data?.notificationSettings, case .muted = notificationSettings.muteState { if let notificationSettings = self.data?.notificationSettings, case .muted = notificationSettings.muteState {
let _ = updatePeerMuteSetting(account: self.context.account, peerId: self.peerId, muteInterval: nil).start() let _ = updatePeerMuteSetting(account: self.context.account, peerId: self.peerId, muteInterval: nil).start()
@ -2259,7 +2263,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
actionSheet?.dismissAnimated() actionSheet?.dismissAnimated()
} }
var items: [ActionSheetItem] = [] var items: [ActionSheetItem] = []
if !peerInfoHeaderButtons(peer: peer, cachedData: data.cachedData, isOpenedFromChat: self.isOpenedFromChat).contains(.search) || self.headerNode.isAvatarExpanded { if !peerInfoHeaderButtons(peer: peer, cachedData: data.cachedData, isOpenedFromChat: self.isOpenedFromChat, videoCallsEnabled: self.videoCallsEnabled).contains(.search) || self.headerNode.isAvatarExpanded {
items.append(ActionSheetButtonItem(title: presentationData.strings.ChatSearch_SearchPlaceholder, color: .accent, action: { [weak self] in items.append(ActionSheetButtonItem(title: presentationData.strings.ChatSearch_SearchPlaceholder, color: .accent, action: { [weak self] in
dismissAction() dismissAction()
self?.openChatWithMessageSearch() self?.openChatWithMessageSearch()
@ -2361,7 +2365,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
self?.openDeletePeer() self?.openDeletePeer()
})) }))
} else { } else {
if !peerInfoHeaderButtons(peer: peer, cachedData: data.cachedData, isOpenedFromChat: self.isOpenedFromChat).contains(.leave) { if !peerInfoHeaderButtons(peer: peer, cachedData: data.cachedData, isOpenedFromChat: self.isOpenedFromChat, videoCallsEnabled: self.videoCallsEnabled).contains(.leave) {
if case .member = channel.participationStatus { if case .member = channel.participationStatus {
items.append(ActionSheetButtonItem(title: presentationData.strings.Channel_LeaveChannel, color: .destructive, action: { [weak self] in items.append(ActionSheetButtonItem(title: presentationData.strings.Channel_LeaveChannel, color: .destructive, action: { [weak self] in
dismissAction() dismissAction()
@ -2510,7 +2514,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
self.controller?.present(shareController, in: .window(.root)) self.controller?.present(shareController, in: .window(.root))
} }
private func requestCall() { private func requestCall(isVideo: Bool) {
guard let peer = self.data?.peer as? TelegramUser, let cachedUserData = self.data?.cachedData as? CachedUserData else { guard let peer = self.data?.peer as? TelegramUser, let cachedUserData = self.data?.cachedData as? CachedUserData else {
return return
} }
@ -2519,7 +2523,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
return return
} }
let callResult = self.context.sharedContext.callManager?.requestCall(account: self.context.account, peerId: peer.id, endCurrentIfAny: false) let callResult = self.context.sharedContext.callManager?.requestCall(account: self.context.account, peerId: peer.id, isVideo: isVideo, endCurrentIfAny: false)
if let callResult = callResult, case let .alreadyInProgress(currentPeerId) = callResult { if let callResult = callResult, case let .alreadyInProgress(currentPeerId) = callResult {
if currentPeerId == peer.id { if currentPeerId == peer.id {
self.context.sharedContext.navigateToCurrentCall() self.context.sharedContext.navigateToCurrentCall()
@ -2536,7 +2540,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
guard let strongSelf = self else { guard let strongSelf = self else {
return return
} }
let _ = strongSelf.context.sharedContext.callManager?.requestCall(account: strongSelf.context.account, peerId: peer.id, endCurrentIfAny: true) let _ = strongSelf.context.sharedContext.callManager?.requestCall(account: strongSelf.context.account, peerId: peer.id, isVideo: isVideo, endCurrentIfAny: true)
})]), in: .window(.root)) })]), in: .window(.root))
} }
}) })
@ -2559,7 +2563,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
ActionSheetItemGroup(items: [ ActionSheetItemGroup(items: [
ActionSheetButtonItem(title: strongSelf.presentationData.strings.UserInfo_TelegramCall, action: { ActionSheetButtonItem(title: strongSelf.presentationData.strings.UserInfo_TelegramCall, action: {
dismissAction() dismissAction()
self?.requestCall() self?.requestCall(isVideo: false)
}), }),
ActionSheetButtonItem(title: strongSelf.presentationData.strings.UserInfo_PhoneCall, action: { ActionSheetButtonItem(title: strongSelf.presentationData.strings.UserInfo_PhoneCall, action: {
dismissAction() dismissAction()

View File

@ -127,8 +127,8 @@ public class PeerMediaCollectionController: TelegramBaseController {
self?.openUrl(url) self?.openUrl(url)
}, openPeer: { peer, navigation in }, openPeer: { peer, navigation in
self?.controllerInteraction?.openPeer(peer.id, navigation, nil) self?.controllerInteraction?.openPeer(peer.id, navigation, nil)
}, callPeer: { peerId in }, callPeer: { peerId, isVideo in
self?.controllerInteraction?.callPeer(peerId) self?.controllerInteraction?.callPeer(peerId, isVideo)
}, enqueueMessage: { _ in }, enqueueMessage: { _ in
}, sendSticker: nil, setupTemporaryHiddenMedia: { _, _, _ in }, chatAvatarHiddenMedia: { _, _ in })) }, sendSticker: nil, setupTemporaryHiddenMedia: { _, _, _ in }, chatAvatarHiddenMedia: { _, _ in }))
} }
@ -357,7 +357,7 @@ public class PeerMediaCollectionController: TelegramBaseController {
return nil return nil
}, reactionContainerNode: { }, reactionContainerNode: {
return nil return nil
}, presentGlobalOverlayController: { _, _ in }, callPeer: { _ in }, presentGlobalOverlayController: { _, _ in }, callPeer: { _, _ in
}, longTap: { [weak self] content, _ in }, longTap: { [weak self] content, _ in
if let strongSelf = self { if let strongSelf = self {
strongSelf.view.endEditing(true) strongSelf.view.endEditing(true)
@ -530,7 +530,7 @@ public class PeerMediaCollectionController: TelegramBaseController {
}, presentPeerContact: { }, presentPeerContact: {
}, dismissReportPeer: { }, dismissReportPeer: {
}, deleteChat: { }, deleteChat: {
}, beginCall: { }, beginCall: { _ in
}, toggleMessageStickerStarred: { _ in }, toggleMessageStickerStarred: { _ in
}, presentController: { _, _ in }, presentController: { _, _ in
}, getNavigationController: { }, getNavigationController: {

View File

@ -537,7 +537,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
}) })
if let mainWindow = mainWindow, applicationBindings.isMainApp { if let mainWindow = mainWindow, applicationBindings.isMainApp {
let callManager = PresentationCallManagerImpl(accountManager: self.accountManager, getDeviceAccessData: { let callManager = PresentationCallManagerImpl(accountManager: self.accountManager, enableVideoCalls: self.immediateExperimentalUISettings.videoCalls, getDeviceAccessData: {
return (self.currentPresentationData.with { $0 }, { [weak self] c, a in return (self.currentPresentationData.with { $0 }, { [weak self] c, a in
self?.presentGlobalController(c, a) self?.presentGlobalController(c, a)
}, { }, {
@ -1120,7 +1120,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
return nil return nil
}, reactionContainerNode: { }, reactionContainerNode: {
return nil return nil
}, presentGlobalOverlayController: { _, _ in }, callPeer: { _ in }, longTap: { _, _ in }, openCheckoutOrReceipt: { _ in }, openSearch: { }, setupReply: { _ in }, presentGlobalOverlayController: { _, _ in }, callPeer: { _, _ in }, longTap: { _, _ in }, openCheckoutOrReceipt: { _ in }, openSearch: { }, setupReply: { _ in
}, canSetupReply: { _ in }, canSetupReply: { _ in
return .none return .none
}, navigateToFirstDateMessage: { _ in }, navigateToFirstDateMessage: { _ in

View File

@ -100,13 +100,22 @@ public struct OngoingCallContextState: Equatable {
case reconnecting case reconnecting
case failed case failed
} }
public enum VideoState: Equatable { public enum VideoState: Equatable {
case notAvailable case notAvailable
case available(Bool) case available(Bool)
case active case active
case activeOutgoing
} }
public enum RemoteVideoState: Equatable {
case inactive
case active
}
public let state: State public let state: State
public let videoState: VideoState public let videoState: VideoState
public let remoteVideoState: RemoteVideoState
} }
private final class OngoingCallThreadLocalContextQueueImpl: NSObject, OngoingCallThreadLocalContextQueue, OngoingCallThreadLocalContextQueueWebrtc /*, OngoingCallThreadLocalContextQueueWebrtcCustom*/ { private final class OngoingCallThreadLocalContextQueueImpl: NSObject, OngoingCallThreadLocalContextQueue, OngoingCallThreadLocalContextQueueWebrtc /*, OngoingCallThreadLocalContextQueueWebrtcCustom*/ {
@ -395,6 +404,27 @@ private extension OngoingCallContextState.State {
}*/ }*/
public final class OngoingCallContext { public final class OngoingCallContext {
public struct AuxiliaryServer {
public enum Connection {
case stun
case turn(username: String, password: String)
}
public let host: String
public let port: Int
public let connection: Connection
public init(
host: String,
port: Int,
connection: Connection
) {
self.host = host
self.port = port
self.connection = connection
}
}
public let internalId: CallSessionInternalId public let internalId: CallSessionInternalId
private let queue = Queue() private let queue = Queue()
@ -433,7 +463,7 @@ public final class OngoingCallContext {
return result return result
} }
public init(account: Account, callSessionManager: CallSessionManager, internalId: CallSessionInternalId, proxyServer: ProxyServerSettings?, initialNetworkType: NetworkType, updatedNetworkType: Signal<NetworkType, NoError>, serializedData: String?, dataSaving: VoiceCallDataSaving, derivedState: VoipDerivedState, key: Data, isOutgoing: Bool, connections: CallSessionConnectionSet, maxLayer: Int32, version: String, allowP2P: Bool, audioSessionActive: Signal<Bool, NoError>, logName: String) { public init(account: Account, callSessionManager: CallSessionManager, internalId: CallSessionInternalId, proxyServer: ProxyServerSettings?, auxiliaryServers: [AuxiliaryServer], initialNetworkType: NetworkType, updatedNetworkType: Signal<NetworkType, NoError>, serializedData: String?, dataSaving: VoiceCallDataSaving, derivedState: VoipDerivedState, key: Data, isOutgoing: Bool, isVideo: Bool, connections: CallSessionConnectionSet, maxLayer: Int32, version: String, allowP2P: Bool, audioSessionActive: Signal<Bool, NoError>, logName: String) {
let _ = setupLogs let _ = setupLogs
OngoingCallThreadLocalContext.applyServerConfig(serializedData) OngoingCallThreadLocalContext.applyServerConfig(serializedData)
//OngoingCallThreadLocalContextWebrtc.applyServerConfig(serializedData) //OngoingCallThreadLocalContextWebrtc.applyServerConfig(serializedData)
@ -491,12 +521,33 @@ public final class OngoingCallContext {
break break
} }
} }
let context = OngoingCallThreadLocalContextWebrtc(queue: OngoingCallThreadLocalContextQueueImpl(queue: queue), proxy: voipProxyServer, networkType: ongoingNetworkTypeForTypeWebrtc(initialNetworkType), dataSaving: ongoingDataSavingForTypeWebrtc(dataSaving), derivedState: derivedState.data, key: key, isOutgoing: isOutgoing, primaryConnection: callConnectionDescriptionWebrtc(connections.primary), alternativeConnections: connections.alternatives.map(callConnectionDescriptionWebrtc), maxLayer: maxLayer, allowP2P: allowP2P, logPath: logPath, sendSignalingData: { [weak callSessionManager] data in var rtcServers: [VoipRtcServerWebrtc] = []
for server in auxiliaryServers {
switch server.connection {
case .stun:
rtcServers.append(VoipRtcServerWebrtc(
host: server.host,
port: Int32(clamping: server.port),
username: "",
password: "",
isTurn: false
))
case let .turn(username, password):
rtcServers.append(VoipRtcServerWebrtc(
host: server.host,
port: Int32(clamping: server.port),
username: username,
password: password,
isTurn: false
))
}
}
let context = OngoingCallThreadLocalContextWebrtc(queue: OngoingCallThreadLocalContextQueueImpl(queue: queue), proxy: voipProxyServer, rtcServers: rtcServers, networkType: ongoingNetworkTypeForTypeWebrtc(initialNetworkType), dataSaving: ongoingDataSavingForTypeWebrtc(dataSaving), derivedState: derivedState.data, key: key, isOutgoing: isOutgoing, isVideo: isVideo, primaryConnection: callConnectionDescriptionWebrtc(connections.primary), alternativeConnections: connections.alternatives.map(callConnectionDescriptionWebrtc), maxLayer: maxLayer, allowP2P: allowP2P, logPath: logPath, sendSignalingData: { [weak callSessionManager] data in
callSessionManager?.sendSignalingData(internalId: internalId, data: data) callSessionManager?.sendSignalingData(internalId: internalId, data: data)
}) })
strongSelf.contextRef = Unmanaged.passRetained(OngoingCallThreadLocalContextHolder(context)) strongSelf.contextRef = Unmanaged.passRetained(OngoingCallThreadLocalContextHolder(context))
context.stateChanged = { state, videoState in context.stateChanged = { state, videoState, remoteVideoState in
queue.async { queue.async {
guard let strongSelf = self else { guard let strongSelf = self else {
return return
@ -508,12 +559,21 @@ public final class OngoingCallContext {
mappedVideoState = .available(true) mappedVideoState = .available(true)
case .active: case .active:
mappedVideoState = .active mappedVideoState = .active
case .invited, .requesting: case .activeOutgoing:
mappedVideoState = .available(false) mappedVideoState = .activeOutgoing
@unknown default: @unknown default:
mappedVideoState = .available(false) mappedVideoState = .available(false)
} }
strongSelf.contextState.set(.single(OngoingCallContextState(state: mappedState, videoState: mappedVideoState))) let mappedRemoteVideoState: OngoingCallContextState.RemoteVideoState
switch remoteVideoState {
case .inactive:
mappedRemoteVideoState = .inactive
case .active:
mappedRemoteVideoState = .active
@unknown default:
mappedRemoteVideoState = .inactive
}
strongSelf.contextState.set(.single(OngoingCallContextState(state: mappedState, videoState: mappedVideoState, remoteVideoState: mappedRemoteVideoState)))
} }
} }
context.signalBarsChanged = { signalBars in context.signalBarsChanged = { signalBars in
@ -540,7 +600,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))) self?.contextState.set(.single(OngoingCallContextState(state: OngoingCallContextState.State(state), videoState: .notAvailable, remoteVideoState: .inactive)))
} }
context.signalBarsChanged = { signalBars in context.signalBarsChanged = { signalBars in
self?.receptionPromise.set(.single(signalBars)) self?.receptionPromise.set(.single(signalBars))

View File

@ -20,7 +20,7 @@ std::unique_ptr<webrtc::VideoEncoderFactory> makeVideoEncoderFactory();
std::unique_ptr<webrtc::VideoDecoderFactory> makeVideoDecoderFactory(); std::unique_ptr<webrtc::VideoDecoderFactory> makeVideoDecoderFactory();
bool supportsH265Encoding(); bool supportsH265Encoding();
rtc::scoped_refptr<webrtc::VideoTrackSourceInterface> makeVideoSource(rtc::Thread *signalingThread, rtc::Thread *workerThread); rtc::scoped_refptr<webrtc::VideoTrackSourceInterface> makeVideoSource(rtc::Thread *signalingThread, rtc::Thread *workerThread);
std::unique_ptr<VideoCapturerInterface> makeVideoCapturer(rtc::scoped_refptr<webrtc::VideoTrackSourceInterface> source, bool useFrontCamera); std::unique_ptr<VideoCapturerInterface> makeVideoCapturer(rtc::scoped_refptr<webrtc::VideoTrackSourceInterface> source, bool useFrontCamera, std::function<void(bool)> isActiveUpdated);
#ifdef TGVOIP_NAMESPACE #ifdef TGVOIP_NAMESPACE
} }

View File

@ -40,12 +40,12 @@
@implementation VideoCapturerInterfaceImplReference @implementation VideoCapturerInterfaceImplReference
- (instancetype)initWithSource:(rtc::scoped_refptr<webrtc::VideoTrackSourceInterface>)source useFrontCamera:(bool)useFrontCamera { - (instancetype)initWithSource:(rtc::scoped_refptr<webrtc::VideoTrackSourceInterface>)source useFrontCamera:(bool)useFrontCamera isActiveUpdated:(void (^)(bool))isActiveUpdated {
self = [super init]; self = [super init];
if (self != nil) { if (self != nil) {
assert([NSThread isMainThread]); assert([NSThread isMainThread]);
_videoCapturer = [[VideoCameraCapturer alloc] initWithSource:source]; _videoCapturer = [[VideoCameraCapturer alloc] initWithSource:source isActiveUpdated:isActiveUpdated];
AVCaptureDevice *frontCamera = nil; AVCaptureDevice *frontCamera = nil;
AVCaptureDevice *backCamera = nil; AVCaptureDevice *backCamera = nil;
@ -130,12 +130,14 @@ namespace TGVOIP_NAMESPACE {
class VideoCapturerInterfaceImpl: public VideoCapturerInterface { class VideoCapturerInterfaceImpl: public VideoCapturerInterface {
public: public:
VideoCapturerInterfaceImpl(rtc::scoped_refptr<webrtc::VideoTrackSourceInterface> source, bool useFrontCamera) : VideoCapturerInterfaceImpl(rtc::scoped_refptr<webrtc::VideoTrackSourceInterface> source, bool useFrontCamera, std::function<void(bool)> isActiveUpdated) :
_source(source) { _source(source) {
_implReference = [[VideoCapturerInterfaceImplHolder alloc] init]; _implReference = [[VideoCapturerInterfaceImplHolder alloc] init];
VideoCapturerInterfaceImplHolder *implReference = _implReference; VideoCapturerInterfaceImplHolder *implReference = _implReference;
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
VideoCapturerInterfaceImplReference *value = [[VideoCapturerInterfaceImplReference alloc] initWithSource:source useFrontCamera:useFrontCamera]; VideoCapturerInterfaceImplReference *value = [[VideoCapturerInterfaceImplReference alloc] initWithSource:source useFrontCamera:useFrontCamera isActiveUpdated:^(bool isActive) {
isActiveUpdated(isActive);
}];
if (value != nil) { if (value != nil) {
implReference.reference = (void *)CFBridgingRetain(value); implReference.reference = (void *)CFBridgingRetain(value);
} }
@ -185,8 +187,8 @@ rtc::scoped_refptr<webrtc::VideoTrackSourceInterface> makeVideoSource(rtc::Threa
return webrtc::VideoTrackSourceProxy::Create(signalingThread, workerThread, objCVideoTrackSource); return webrtc::VideoTrackSourceProxy::Create(signalingThread, workerThread, objCVideoTrackSource);
} }
std::unique_ptr<VideoCapturerInterface> makeVideoCapturer(rtc::scoped_refptr<webrtc::VideoTrackSourceInterface> source, bool useFrontCamera) { std::unique_ptr<VideoCapturerInterface> makeVideoCapturer(rtc::scoped_refptr<webrtc::VideoTrackSourceInterface> source, bool useFrontCamera, std::function<void(bool)> isActiveUpdated) {
return std::make_unique<VideoCapturerInterfaceImpl>(source, useFrontCamera); return std::make_unique<VideoCapturerInterfaceImpl>(source, useFrontCamera, isActiveUpdated);
} }
#ifdef TGVOIP_NAMESPACE #ifdef TGVOIP_NAMESPACE

View File

@ -36,15 +36,21 @@ Manager::Manager(
rtc::Thread *thread, rtc::Thread *thread,
TgVoipEncryptionKey encryptionKey, TgVoipEncryptionKey encryptionKey,
bool enableP2P, bool enableP2P,
std::vector<TgVoipRtcServer> const &rtcServers,
bool isVideo,
std::function<void (const TgVoipState &)> stateUpdated, std::function<void (const TgVoipState &)> stateUpdated,
std::function<void (bool)> videoStateUpdated, std::function<void (bool)> videoStateUpdated,
std::function<void (bool)> remoteVideoIsActiveUpdated,
std::function<void (const std::vector<uint8_t> &)> signalingDataEmitted std::function<void (const std::vector<uint8_t> &)> signalingDataEmitted
) : ) :
_thread(thread), _thread(thread),
_encryptionKey(encryptionKey), _encryptionKey(encryptionKey),
_enableP2P(enableP2P), _enableP2P(enableP2P),
_rtcServers(rtcServers),
_startWithVideo(isVideo),
_stateUpdated(stateUpdated), _stateUpdated(stateUpdated),
_videoStateUpdated(videoStateUpdated), _videoStateUpdated(videoStateUpdated),
_remoteVideoIsActiveUpdated(remoteVideoIsActiveUpdated),
_signalingDataEmitted(signalingDataEmitted), _signalingDataEmitted(signalingDataEmitted),
_isVideoRequested(false) { _isVideoRequested(false) {
assert(_thread->IsCurrent()); assert(_thread->IsCurrent());
@ -56,11 +62,12 @@ Manager::~Manager() {
void Manager::start() { void Manager::start() {
auto weakThis = std::weak_ptr<Manager>(shared_from_this()); auto weakThis = std::weak_ptr<Manager>(shared_from_this());
_networkManager.reset(new ThreadLocalObject<NetworkManager>(getNetworkThread(), [encryptionKey = _encryptionKey, enableP2P = _enableP2P, thread = _thread, weakThis, signalingDataEmitted = _signalingDataEmitted]() { _networkManager.reset(new ThreadLocalObject<NetworkManager>(getNetworkThread(), [encryptionKey = _encryptionKey, enableP2P = _enableP2P, rtcServers = _rtcServers, thread = _thread, weakThis, signalingDataEmitted = _signalingDataEmitted]() {
return new NetworkManager( return new NetworkManager(
getNetworkThread(), getNetworkThread(),
encryptionKey, encryptionKey,
enableP2P, enableP2P,
rtcServers,
[thread, weakThis](const NetworkManager::State &state) { [thread, weakThis](const NetworkManager::State &state) {
thread->PostTask(RTC_FROM_HERE, [weakThis, state]() { thread->PostTask(RTC_FROM_HERE, [weakThis, state]() {
auto strongThis = weakThis.lock(); auto strongThis = weakThis.lock();
@ -104,10 +111,11 @@ void Manager::start() {
); );
})); }));
bool isOutgoing = _encryptionKey.isOutgoing; bool isOutgoing = _encryptionKey.isOutgoing;
_mediaManager.reset(new ThreadLocalObject<MediaManager>(getMediaThread(), [isOutgoing, thread = _thread, weakThis]() { _mediaManager.reset(new ThreadLocalObject<MediaManager>(getMediaThread(), [isOutgoing, thread = _thread, startWithVideo = _startWithVideo, weakThis]() {
return new MediaManager( return new MediaManager(
getMediaThread(), getMediaThread(),
isOutgoing, isOutgoing,
startWithVideo,
[thread, weakThis](const rtc::CopyOnWriteBuffer &packet) { [thread, weakThis](const rtc::CopyOnWriteBuffer &packet) {
thread->PostTask(RTC_FROM_HERE, [weakThis, packet]() { thread->PostTask(RTC_FROM_HERE, [weakThis, packet]() {
auto strongThis = weakThis.lock(); auto strongThis = weakThis.lock();
@ -118,6 +126,15 @@ void Manager::start() {
networkManager->sendPacket(packet); networkManager->sendPacket(packet);
}); });
}); });
},
[thread, weakThis](bool isActive) {
thread->PostTask(RTC_FROM_HERE, [weakThis, isActive]() {
auto strongThis = weakThis.lock();
if (strongThis == nullptr) {
return;
}
strongThis->notifyIsLocalVideoActive(isActive);
});
} }
); );
})); }));
@ -148,6 +165,11 @@ void Manager::receiveSignalingData(const std::vector<uint8_t> &data) {
_networkManager->perform([candidatesData](NetworkManager *networkManager) { _networkManager->perform([candidatesData](NetworkManager *networkManager) {
networkManager->receiveSignalingData(candidatesData); networkManager->receiveSignalingData(candidatesData);
}); });
} else if (mode == 4) {
uint8_t value = 0;
if (reader.ReadUInt8(&value)) {
_remoteVideoIsActiveUpdated(value != 0);
}
} }
} }
@ -159,6 +181,7 @@ void Manager::setSendVideo(bool sendVideo) {
rtc::CopyOnWriteBuffer buffer; rtc::CopyOnWriteBuffer buffer;
uint8_t mode = 1; uint8_t mode = 1;
buffer.AppendData(&mode, 1); buffer.AppendData(&mode, 1);
std::vector<uint8_t> data; std::vector<uint8_t> data;
data.resize(buffer.size()); data.resize(buffer.size());
memcpy(data.data(), buffer.data(), buffer.size()); memcpy(data.data(), buffer.data(), buffer.size());
@ -174,12 +197,31 @@ void Manager::setSendVideo(bool sendVideo) {
} }
} }
void Manager::setMuteOutgoingAudio(bool mute) {
_mediaManager->perform([mute](MediaManager *mediaManager) {
mediaManager->setMuteOutgoingAudio(mute);
});
}
void Manager::switchVideoCamera() { void Manager::switchVideoCamera() {
_mediaManager->perform([](MediaManager *mediaManager) { _mediaManager->perform([](MediaManager *mediaManager) {
mediaManager->switchVideoCamera(); mediaManager->switchVideoCamera();
}); });
} }
void Manager::notifyIsLocalVideoActive(bool isActive) {
rtc::CopyOnWriteBuffer buffer;
uint8_t mode = 4;
buffer.AppendData(&mode, 1);
uint8_t value = isActive ? 1 : 0;
buffer.AppendData(&value, 1);
std::vector<uint8_t> data;
data.resize(buffer.size());
memcpy(data.data(), buffer.data(), buffer.size());
_signalingDataEmitted(data);
}
void Manager::setIncomingVideoOutput(std::shared_ptr<rtc::VideoSinkInterface<webrtc::VideoFrame>> sink) { void Manager::setIncomingVideoOutput(std::shared_ptr<rtc::VideoSinkInterface<webrtc::VideoFrame>> sink) {
_mediaManager->perform([sink](MediaManager *mediaManager) { _mediaManager->perform([sink](MediaManager *mediaManager) {
mediaManager->setIncomingVideoOutput(sink); mediaManager->setIncomingVideoOutput(sink);

View File

@ -16,8 +16,11 @@ public:
rtc::Thread *thread, rtc::Thread *thread,
TgVoipEncryptionKey encryptionKey, TgVoipEncryptionKey encryptionKey,
bool enableP2P, bool enableP2P,
std::vector<TgVoipRtcServer> const &rtcServers,
bool isVideo,
std::function<void (const TgVoipState &)> stateUpdated, std::function<void (const TgVoipState &)> stateUpdated,
std::function<void (bool)> videoStateUpdated, std::function<void (bool)> videoStateUpdated,
std::function<void (bool)> remoteVideoIsActiveUpdated,
std::function<void (const std::vector<uint8_t> &)> signalingDataEmitted std::function<void (const std::vector<uint8_t> &)> signalingDataEmitted
); );
~Manager(); ~Manager();
@ -25,7 +28,9 @@ public:
void start(); void start();
void receiveSignalingData(const std::vector<uint8_t> &data); void receiveSignalingData(const std::vector<uint8_t> &data);
void setSendVideo(bool sendVideo); void setSendVideo(bool sendVideo);
void setMuteOutgoingAudio(bool mute);
void switchVideoCamera(); void switchVideoCamera();
void notifyIsLocalVideoActive(bool isActive);
void setIncomingVideoOutput(std::shared_ptr<rtc::VideoSinkInterface<webrtc::VideoFrame>> sink); void setIncomingVideoOutput(std::shared_ptr<rtc::VideoSinkInterface<webrtc::VideoFrame>> sink);
void setOutgoingVideoOutput(std::shared_ptr<rtc::VideoSinkInterface<webrtc::VideoFrame>> sink); void setOutgoingVideoOutput(std::shared_ptr<rtc::VideoSinkInterface<webrtc::VideoFrame>> sink);
@ -33,8 +38,11 @@ private:
rtc::Thread *_thread; rtc::Thread *_thread;
TgVoipEncryptionKey _encryptionKey; TgVoipEncryptionKey _encryptionKey;
bool _enableP2P; bool _enableP2P;
std::vector<TgVoipRtcServer> _rtcServers;
bool _startWithVideo;
std::function<void (const TgVoipState &)> _stateUpdated; std::function<void (const TgVoipState &)> _stateUpdated;
std::function<void (bool)> _videoStateUpdated; std::function<void (bool)> _videoStateUpdated;
std::function<void (bool)> _remoteVideoIsActiveUpdated;
std::function<void (const std::vector<uint8_t> &)> _signalingDataEmitted; std::function<void (const std::vector<uint8_t> &)> _signalingDataEmitted;
std::unique_ptr<ThreadLocalObject<NetworkManager>> _networkManager; std::unique_ptr<ThreadLocalObject<NetworkManager>> _networkManager;
std::unique_ptr<ThreadLocalObject<MediaManager>> _mediaManager; std::unique_ptr<ThreadLocalObject<MediaManager>> _mediaManager;

View File

@ -172,9 +172,12 @@ static rtc::Thread *getWorkerThread() {
MediaManager::MediaManager( MediaManager::MediaManager(
rtc::Thread *thread, rtc::Thread *thread,
bool isOutgoing, bool isOutgoing,
std::function<void (const rtc::CopyOnWriteBuffer &)> packetEmitted bool startWithVideo,
std::function<void (const rtc::CopyOnWriteBuffer &)> packetEmitted,
std::function<void (bool)> localVideoCaptureActiveUpdated
) : ) :
_packetEmitted(packetEmitted), _packetEmitted(packetEmitted),
_localVideoCaptureActiveUpdated(localVideoCaptureActiveUpdated),
_thread(thread), _thread(thread),
_eventLog(std::make_unique<webrtc::RtcEventLogNull>()), _eventLog(std::make_unique<webrtc::RtcEventLogNull>()),
_taskQueueFactory(webrtc::CreateDefaultTaskQueueFactory()) { _taskQueueFactory(webrtc::CreateDefaultTaskQueueFactory()) {
@ -190,6 +193,7 @@ _taskQueueFactory(webrtc::CreateDefaultTaskQueueFactory()) {
_enableFlexfec = true; _enableFlexfec = true;
_isConnected = false; _isConnected = false;
_muteOutgoingAudio = false;
auto videoEncoderFactory = makeVideoEncoderFactory(); auto videoEncoderFactory = makeVideoEncoderFactory();
_videoCodecs = AssignPayloadTypesAndDefaultCodecs(videoEncoderFactory->GetSupportedFormats()); _videoCodecs = AssignPayloadTypesAndDefaultCodecs(videoEncoderFactory->GetSupportedFormats());
@ -280,6 +284,10 @@ _taskQueueFactory(webrtc::CreateDefaultTaskQueueFactory()) {
_videoChannel->SetInterface(_videoNetworkInterface.get(), webrtc::MediaTransportConfig()); _videoChannel->SetInterface(_videoNetworkInterface.get(), webrtc::MediaTransportConfig());
_nativeVideoSource = makeVideoSource(_thread, getWorkerThread()); _nativeVideoSource = makeVideoSource(_thread, getWorkerThread());
if (startWithVideo) {
setSendVideo(true);
}
} }
MediaManager::~MediaManager() { MediaManager::~MediaManager() {
@ -318,7 +326,7 @@ void MediaManager::setIsConnected(bool isConnected) {
if (_audioChannel) { if (_audioChannel) {
_audioChannel->OnReadyToSend(_isConnected); _audioChannel->OnReadyToSend(_isConnected);
_audioChannel->SetSend(_isConnected); _audioChannel->SetSend(_isConnected);
_audioChannel->SetAudioSend(_ssrcAudio.outgoing, _isConnected, nullptr, &_audioSource); _audioChannel->SetAudioSend(_ssrcAudio.outgoing, _isConnected && !_muteOutgoingAudio, nullptr, &_audioSource);
} }
if (_isSendingVideo && _videoChannel) { if (_isSendingVideo && _videoChannel) {
_videoChannel->OnReadyToSend(_isConnected); _videoChannel->OnReadyToSend(_isConnected);
@ -364,7 +372,9 @@ void MediaManager::setSendVideo(bool sendVideo) {
codec.SetParam(cricket::kCodecParamStartBitrate, 512); codec.SetParam(cricket::kCodecParamStartBitrate, 512);
codec.SetParam(cricket::kCodecParamMaxBitrate, 2500); codec.SetParam(cricket::kCodecParamMaxBitrate, 2500);
_videoCapturer = makeVideoCapturer(_nativeVideoSource, _useFrontCamera); _videoCapturer = makeVideoCapturer(_nativeVideoSource, _useFrontCamera, [localVideoCaptureActiveUpdated = _localVideoCaptureActiveUpdated](bool isActive) {
localVideoCaptureActiveUpdated(isActive);
});
cricket::VideoSendParameters videoSendParameters; cricket::VideoSendParameters videoSendParameters;
videoSendParameters.codecs.push_back(codec); videoSendParameters.codecs.push_back(codec);
@ -450,10 +460,18 @@ void MediaManager::setSendVideo(bool sendVideo) {
} }
} }
void MediaManager::setMuteOutgoingAudio(bool mute) {
_muteOutgoingAudio = mute;
_audioChannel->SetAudioSend(_ssrcAudio.outgoing, _isConnected && !_muteOutgoingAudio, nullptr, &_audioSource);
}
void MediaManager::switchVideoCamera() { void MediaManager::switchVideoCamera() {
if (_isSendingVideo) { if (_isSendingVideo) {
_useFrontCamera = !_useFrontCamera; _useFrontCamera = !_useFrontCamera;
_videoCapturer = makeVideoCapturer(_nativeVideoSource, _useFrontCamera); _videoCapturer = makeVideoCapturer(_nativeVideoSource, _useFrontCamera, [localVideoCaptureActiveUpdated = _localVideoCaptureActiveUpdated](bool isActive) {
localVideoCaptureActiveUpdated(isActive);
});
} }
} }

View File

@ -57,7 +57,9 @@ public:
MediaManager( MediaManager(
rtc::Thread *thread, rtc::Thread *thread,
bool isOutgoing, bool isOutgoing,
std::function<void (const rtc::CopyOnWriteBuffer &)> packetEmitted bool startWithVideo,
std::function<void (const rtc::CopyOnWriteBuffer &)> packetEmitted,
std::function<void (bool)> localVideoCaptureActiveUpdated
); );
~MediaManager(); ~MediaManager();
@ -65,12 +67,14 @@ public:
void receivePacket(const rtc::CopyOnWriteBuffer &packet); void receivePacket(const rtc::CopyOnWriteBuffer &packet);
void notifyPacketSent(const rtc::SentPacket &sentPacket); void notifyPacketSent(const rtc::SentPacket &sentPacket);
void setSendVideo(bool sendVideo); void setSendVideo(bool sendVideo);
void setMuteOutgoingAudio(bool mute);
void switchVideoCamera(); void switchVideoCamera();
void setIncomingVideoOutput(std::shared_ptr<rtc::VideoSinkInterface<webrtc::VideoFrame>> sink); void setIncomingVideoOutput(std::shared_ptr<rtc::VideoSinkInterface<webrtc::VideoFrame>> sink);
void setOutgoingVideoOutput(std::shared_ptr<rtc::VideoSinkInterface<webrtc::VideoFrame>> sink); void setOutgoingVideoOutput(std::shared_ptr<rtc::VideoSinkInterface<webrtc::VideoFrame>> sink);
protected: protected:
std::function<void (const rtc::CopyOnWriteBuffer &)> _packetEmitted; std::function<void (const rtc::CopyOnWriteBuffer &)> _packetEmitted;
std::function<void (bool)> _localVideoCaptureActiveUpdated;
private: private:
rtc::Thread *_thread; rtc::Thread *_thread;
@ -82,6 +86,7 @@ private:
bool _enableFlexfec; bool _enableFlexfec;
bool _isConnected; bool _isConnected;
bool _muteOutgoingAudio;
std::vector<cricket::VideoCodec> _videoCodecs; std::vector<cricket::VideoCodec> _videoCodecs;
bool _isSendingVideo; bool _isSendingVideo;

View File

@ -154,6 +154,7 @@ NetworkManager::NetworkManager(
rtc::Thread *thread, rtc::Thread *thread,
TgVoipEncryptionKey encryptionKey, TgVoipEncryptionKey encryptionKey,
bool enableP2P, bool enableP2P,
std::vector<TgVoipRtcServer> const &rtcServers,
std::function<void (const NetworkManager::State &)> stateUpdated, std::function<void (const NetworkManager::State &)> stateUpdated,
std::function<void (const rtc::CopyOnWriteBuffer &)> packetReceived, std::function<void (const rtc::CopyOnWriteBuffer &)> packetReceived,
std::function<void (const std::vector<uint8_t> &)> signalingDataEmitted std::function<void (const std::vector<uint8_t> &)> signalingDataEmitted
@ -178,16 +179,35 @@ _signalingDataEmitted(signalingDataEmitted) {
_portAllocator->set_flags(_portAllocator->flags() | flags); _portAllocator->set_flags(_portAllocator->flags() | flags);
_portAllocator->Initialize(); _portAllocator->Initialize();
rtc::SocketAddress defaultStunAddress = rtc::SocketAddress("134.122.52.178", 3478);
cricket::ServerAddresses stunServers; cricket::ServerAddresses stunServers;
stunServers.insert(defaultStunAddress);
std::vector<cricket::RelayServerConfig> turnServers; std::vector<cricket::RelayServerConfig> turnServers;
turnServers.push_back(cricket::RelayServerConfig(
rtc::SocketAddress("134.122.52.178", 3478), if (rtcServers.size() == 0) {
"openrelay", rtc::SocketAddress defaultStunAddress = rtc::SocketAddress("134.122.52.178", 3478);
"openrelay", stunServers.insert(defaultStunAddress);
cricket::PROTO_UDP
)); turnServers.push_back(cricket::RelayServerConfig(
rtc::SocketAddress("134.122.52.178", 3478),
"openrelay",
"openrelay",
cricket::PROTO_UDP
));
} else {
for (auto &server : rtcServers) {
if (server.isTurn) {
turnServers.push_back(cricket::RelayServerConfig(
rtc::SocketAddress(server.host, server.port),
server.login,
server.password,
cricket::PROTO_UDP
));
} else {
rtc::SocketAddress stunAddress = rtc::SocketAddress(server.host, server.port);
stunServers.insert(stunAddress);
}
}
}
_portAllocator->SetConfiguration(stunServers, turnServers, 2, webrtc::NO_PRUNE); _portAllocator->SetConfiguration(stunServers, turnServers, 2, webrtc::NO_PRUNE);
_asyncResolverFactory = std::make_unique<webrtc::BasicAsyncResolverFactory>(); _asyncResolverFactory = std::make_unique<webrtc::BasicAsyncResolverFactory>();

View File

@ -41,6 +41,7 @@ public:
rtc::Thread *thread, rtc::Thread *thread,
TgVoipEncryptionKey encryptionKey, TgVoipEncryptionKey encryptionKey,
bool enableP2P, bool enableP2P,
std::vector<TgVoipRtcServer> const &rtcServers,
std::function<void (const NetworkManager::State &)> stateUpdated, std::function<void (const NetworkManager::State &)> stateUpdated,
std::function<void (const rtc::CopyOnWriteBuffer &)> packetReceived, std::function<void (const rtc::CopyOnWriteBuffer &)> packetReceived,
std::function<void (const std::vector<uint8_t> &)> signalingDataEmitted std::function<void (const std::vector<uint8_t> &)> signalingDataEmitted

View File

@ -26,6 +26,14 @@ struct TgVoipProxy {
std::string password; std::string password;
}; };
struct TgVoipRtcServer {
std::string host;
uint16_t port;
std::string login;
std::string password;
bool isTurn;
};
enum class TgVoipEndpointType { enum class TgVoipEndpointType {
Inet, Inet,
Lan, Lan,
@ -135,10 +143,13 @@ public:
TgVoipPersistentState const &persistentState, TgVoipPersistentState const &persistentState,
std::vector<TgVoipEndpoint> const &endpoints, std::vector<TgVoipEndpoint> const &endpoints,
std::unique_ptr<TgVoipProxy> const &proxy, std::unique_ptr<TgVoipProxy> const &proxy,
std::vector<TgVoipRtcServer> const &rtcServers,
TgVoipNetworkType initialNetworkType, TgVoipNetworkType initialNetworkType,
TgVoipEncryptionKey const &encryptionKey, TgVoipEncryptionKey const &encryptionKey,
bool isVideo,
std::function<void(TgVoipState)> stateUpdated, std::function<void(TgVoipState)> stateUpdated,
std::function<void(bool)> videoStateUpdated, std::function<void(bool)> videoStateUpdated,
std::function<void(bool)> remoteVideoIsActiveUpdated,
std::function<void(const std::vector<uint8_t> &)> signalingDataEmitted std::function<void(const std::vector<uint8_t> &)> signalingDataEmitted
); );

View File

@ -139,11 +139,14 @@ public:
std::vector<TgVoipEndpoint> const &endpoints, std::vector<TgVoipEndpoint> const &endpoints,
TgVoipPersistentState const &persistentState, TgVoipPersistentState const &persistentState,
std::unique_ptr<TgVoipProxy> const &proxy, std::unique_ptr<TgVoipProxy> const &proxy,
std::vector<TgVoipRtcServer> const &rtcServers,
TgVoipConfig const &config, TgVoipConfig const &config,
TgVoipEncryptionKey const &encryptionKey, TgVoipEncryptionKey const &encryptionKey,
bool isVideo,
TgVoipNetworkType initialNetworkType, TgVoipNetworkType initialNetworkType,
std::function<void(TgVoipState)> stateUpdated, std::function<void(TgVoipState)> stateUpdated,
std::function<void(bool)> videoStateUpdated, std::function<void(bool)> videoStateUpdated,
std::function<void(bool)> remoteVideoIsActiveUpdated,
std::function<void(const std::vector<uint8_t> &)> signalingDataEmitted std::function<void(const std::vector<uint8_t> &)> signalingDataEmitted
) : ) :
_stateUpdated(stateUpdated), _stateUpdated(stateUpdated),
@ -157,17 +160,22 @@ public:
bool enableP2P = config.enableP2P; bool enableP2P = config.enableP2P;
_manager.reset(new ThreadLocalObject<Manager>(getManagerThread(), [encryptionKey = encryptionKey, enableP2P = enableP2P, stateUpdated, videoStateUpdated, signalingDataEmitted](){ _manager.reset(new ThreadLocalObject<Manager>(getManagerThread(), [encryptionKey = encryptionKey, enableP2P = enableP2P, isVideo, stateUpdated, videoStateUpdated, remoteVideoIsActiveUpdated, signalingDataEmitted, rtcServers](){
return new Manager( return new Manager(
getManagerThread(), getManagerThread(),
encryptionKey, encryptionKey,
enableP2P, enableP2P,
rtcServers,
isVideo,
[stateUpdated](const TgVoipState &state) { [stateUpdated](const TgVoipState &state) {
stateUpdated(state); stateUpdated(state);
}, },
[videoStateUpdated](bool isActive) { [videoStateUpdated](bool isActive) {
videoStateUpdated(isActive); videoStateUpdated(isActive);
}, },
[remoteVideoIsActiveUpdated](bool isActive) {
remoteVideoIsActiveUpdated(isActive);
},
[signalingDataEmitted](const std::vector<uint8_t> &data) { [signalingDataEmitted](const std::vector<uint8_t> &data) {
signalingDataEmitted(data); signalingDataEmitted(data);
} }
@ -249,7 +257,9 @@ public:
} }
void setMuteMicrophone(bool muteMicrophone) override { void setMuteMicrophone(bool muteMicrophone) override {
//controller_->SetMute(muteMicrophone); _manager->perform([muteMicrophone](Manager *manager) {
manager->setMuteOutgoingAudio(muteMicrophone);
});
} }
void setIncomingVideoOutput(std::shared_ptr<rtc::VideoSinkInterface<webrtc::VideoFrame>> sink) override { void setIncomingVideoOutput(std::shared_ptr<rtc::VideoSinkInterface<webrtc::VideoFrame>> sink) override {
@ -374,21 +384,27 @@ TgVoip *TgVoip::makeInstance(
TgVoipPersistentState const &persistentState, TgVoipPersistentState const &persistentState,
std::vector<TgVoipEndpoint> const &endpoints, std::vector<TgVoipEndpoint> const &endpoints,
std::unique_ptr<TgVoipProxy> const &proxy, std::unique_ptr<TgVoipProxy> const &proxy,
std::vector<TgVoipRtcServer> const &rtcServers,
TgVoipNetworkType initialNetworkType, TgVoipNetworkType initialNetworkType,
TgVoipEncryptionKey const &encryptionKey, TgVoipEncryptionKey const &encryptionKey,
bool isVideo,
std::function<void(TgVoipState)> stateUpdated, std::function<void(TgVoipState)> stateUpdated,
std::function<void(bool)> videoStateUpdated, std::function<void(bool)> videoStateUpdated,
std::function<void(bool)> remoteVideoIsActiveUpdated,
std::function<void(const std::vector<uint8_t> &)> signalingDataEmitted std::function<void(const std::vector<uint8_t> &)> signalingDataEmitted
) { ) {
return new TgVoipImpl( return new TgVoipImpl(
endpoints, endpoints,
persistentState, persistentState,
proxy, proxy,
rtcServers,
config, config,
encryptionKey, encryptionKey,
isVideo,
initialNetworkType, initialNetworkType,
stateUpdated, stateUpdated,
videoStateUpdated, videoStateUpdated,
remoteVideoIsActiveUpdated,
signalingDataEmitted signalingDataEmitted
); );
} }

View File

@ -13,7 +13,7 @@
+ (NSArray<AVCaptureDevice *> *)captureDevices; + (NSArray<AVCaptureDevice *> *)captureDevices;
+ (NSArray<AVCaptureDeviceFormat *> *)supportedFormatsForDevice:(AVCaptureDevice *)device; + (NSArray<AVCaptureDeviceFormat *> *)supportedFormatsForDevice:(AVCaptureDevice *)device;
- (instancetype)initWithSource:(rtc::scoped_refptr<webrtc::VideoTrackSourceInterface>)source; - (instancetype)initWithSource:(rtc::scoped_refptr<webrtc::VideoTrackSourceInterface>)source isActiveUpdated:(void (^)(bool))isActiveUpdated;
- (void)startCaptureWithDevice:(AVCaptureDevice *)device format:(AVCaptureDeviceFormat *)format fps:(NSInteger)fps; - (void)startCaptureWithDevice:(AVCaptureDevice *)device format:(AVCaptureDeviceFormat *)format fps:(NSInteger)fps;
- (void)stopCapture; - (void)stopCapture;

View File

@ -37,16 +37,20 @@ static webrtc::ObjCVideoTrackSource *getObjCVideoSource(const rtc::scoped_refptr
FourCharCode _outputPixelFormat; FourCharCode _outputPixelFormat;
RTCVideoRotation _rotation; RTCVideoRotation _rotation;
UIDeviceOrientation _orientation; UIDeviceOrientation _orientation;
void (^_isActiveUpdated)(bool);
} }
@end @end
@implementation VideoCameraCapturer @implementation VideoCameraCapturer
- (instancetype)initWithSource:(rtc::scoped_refptr<webrtc::VideoTrackSourceInterface>)source { - (instancetype)initWithSource:(rtc::scoped_refptr<webrtc::VideoTrackSourceInterface>)source isActiveUpdated:(void (^)(bool))isActiveUpdated {
self = [super init]; self = [super init];
if (self != nil) { if (self != nil) {
_source = source; _source = source;
_isActiveUpdated = [isActiveUpdated copy];
if (![self setupCaptureSession:[[AVCaptureSession alloc] init]]) { if (![self setupCaptureSession:[[AVCaptureSession alloc] init]]) {
return nil; return nil;
} }
@ -310,10 +314,17 @@ static webrtc::ObjCVideoTrackSource *getObjCVideoSource(const rtc::scoped_refptr
// allow future retries on fatal errors. // allow future retries on fatal errors.
_hasRetriedOnFatalError = NO; _hasRetriedOnFatalError = NO;
}]; }];
if (_isActiveUpdated) {
_isActiveUpdated(true);
}
} }
- (void)handleCaptureSessionDidStopRunning:(NSNotification *)notification { - (void)handleCaptureSessionDidStopRunning:(NSNotification *)notification {
RTCLog(@"Capture session stopped."); RTCLog(@"Capture session stopped.");
if (_isActiveUpdated) {
_isActiveUpdated(false);
}
} }
- (void)handleFatalError { - (void)handleFatalError {

View File

@ -25,11 +25,15 @@ typedef NS_ENUM(int32_t, OngoingCallStateWebrtc) {
typedef NS_ENUM(int32_t, OngoingCallVideoStateWebrtc) { typedef NS_ENUM(int32_t, OngoingCallVideoStateWebrtc) {
OngoingCallVideoStateInactive, OngoingCallVideoStateInactive,
OngoingCallVideoStateRequesting, OngoingCallVideoStateActiveOutgoing,
OngoingCallVideoStateInvited,
OngoingCallVideoStateActive OngoingCallVideoStateActive
}; };
typedef NS_ENUM(int32_t, OngoingCallRemoteVideoStateWebrtc) {
OngoingCallRemoteVideoStateInactive,
OngoingCallRemoteVideoStateActive
};
typedef NS_ENUM(int32_t, OngoingCallNetworkTypeWebrtc) { typedef NS_ENUM(int32_t, OngoingCallNetworkTypeWebrtc) {
OngoingCallNetworkTypeWifi, OngoingCallNetworkTypeWifi,
OngoingCallNetworkTypeCellularGprs, OngoingCallNetworkTypeCellularGprs,
@ -62,6 +66,18 @@ typedef NS_ENUM(int32_t, OngoingCallDataSavingWebrtc) {
@end @end
@interface VoipRtcServerWebrtc : NSObject
@property (nonatomic, strong, readonly) NSString * _Nonnull host;
@property (nonatomic, readonly) int32_t port;
@property (nonatomic, strong, readonly) NSString * _Nullable username;
@property (nonatomic, strong, readonly) NSString * _Nullable password;
@property (nonatomic, readonly) bool isTurn;
- (instancetype _Nonnull)initWithHost:(NSString * _Nonnull)host port:(int32_t)port username:(NSString * _Nullable)username password:(NSString * _Nullable)password isTurn:(bool)isTurn;
@end
@interface OngoingCallThreadLocalContextWebrtc : NSObject @interface OngoingCallThreadLocalContextWebrtc : NSObject
+ (void)setupLoggingFunction:(void (* _Nullable)(NSString * _Nullable))loggingFunction; + (void)setupLoggingFunction:(void (* _Nullable)(NSString * _Nullable))loggingFunction;
@ -69,10 +85,10 @@ typedef NS_ENUM(int32_t, OngoingCallDataSavingWebrtc) {
+ (int32_t)maxLayer; + (int32_t)maxLayer;
+ (NSString * _Nonnull)version; + (NSString * _Nonnull)version;
@property (nonatomic, copy) void (^ _Nullable stateChanged)(OngoingCallStateWebrtc, OngoingCallVideoStateWebrtc); @property (nonatomic, copy) void (^ _Nullable stateChanged)(OngoingCallStateWebrtc, OngoingCallVideoStateWebrtc, OngoingCallRemoteVideoStateWebrtc);
@property (nonatomic, copy) void (^ _Nullable signalBarsChanged)(int32_t); @property (nonatomic, copy) void (^ _Nullable signalBarsChanged)(int32_t);
- (instancetype _Nonnull)initWithQueue:(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; - (instancetype _Nonnull)initWithQueue:(id<OngoingCallThreadLocalContextQueueWebrtc> _Nonnull)queue proxy:(VoipProxyServerWebrtc * _Nullable)proxy rtcServers:(NSArray<VoipRtcServerWebrtc *> * _Nonnull)rtcServers networkType:(OngoingCallNetworkTypeWebrtc)networkType dataSaving:(OngoingCallDataSavingWebrtc)dataSaving derivedState:(NSData * _Nonnull)derivedState key:(NSData * _Nonnull)key isOutgoing:(bool)isOutgoing isVideo:(bool)isVideo 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;
- (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;
- (bool)needRate; - (bool)needRate;

View File

@ -35,6 +35,7 @@ using namespace TGVOIP_NAMESPACE;
OngoingCallStateWebrtc _state; OngoingCallStateWebrtc _state;
OngoingCallVideoStateWebrtc _videoState; OngoingCallVideoStateWebrtc _videoState;
OngoingCallRemoteVideoStateWebrtc _remoteVideoState;
int32_t _signalBars; int32_t _signalBars;
NSData *_lastDerivedState; NSData *_lastDerivedState;
@ -62,6 +63,22 @@ using namespace TGVOIP_NAMESPACE;
@end @end
@implementation VoipRtcServerWebrtc
- (instancetype _Nonnull)initWithHost:(NSString * _Nonnull)host port:(int32_t)port username:(NSString * _Nullable)username password:(NSString * _Nullable)password isTurn:(bool)isTurn {
self = [super init];
if (self != nil) {
_host = host;
_port = port;
_username = username;
_password = password;
_isTurn = isTurn;
}
return self;
}
@end
static TgVoipNetworkType callControllerNetworkTypeForType(OngoingCallNetworkTypeWebrtc type) { static TgVoipNetworkType callControllerNetworkTypeForType(OngoingCallNetworkTypeWebrtc type) {
switch (type) { switch (type) {
case OngoingCallNetworkTypeWifi: case OngoingCallNetworkTypeWifi:
@ -117,7 +134,7 @@ static void (*InternalVoipLoggingFunction)(NSString *) = NULL;
return @"2.7.7"; return @"2.7.7";
} }
- (instancetype _Nonnull)initWithQueue:(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; { - (instancetype _Nonnull)initWithQueue:(id<OngoingCallThreadLocalContextQueueWebrtc> _Nonnull)queue proxy:(VoipProxyServerWebrtc * _Nullable)proxy rtcServers:(NSArray<VoipRtcServerWebrtc *> * _Nonnull)rtcServers networkType:(OngoingCallNetworkTypeWebrtc)networkType dataSaving:(OngoingCallDataSavingWebrtc)dataSaving derivedState:(NSData * _Nonnull)derivedState key:(NSData * _Nonnull)key isOutgoing:(bool)isOutgoing isVideo:(bool)isVideo 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; {
self = [super init]; self = [super init];
if (self != nil) { if (self != nil) {
_queue = queue; _queue = queue;
@ -129,7 +146,13 @@ static void (*InternalVoipLoggingFunction)(NSString *) = NULL;
_callPacketTimeout = 10.0; _callPacketTimeout = 10.0;
_networkType = networkType; _networkType = networkType;
_sendSignalingData = [sendSignalingData copy]; _sendSignalingData = [sendSignalingData copy];
_videoState = OngoingCallVideoStateInactive; if (isVideo) {
_videoState = OngoingCallVideoStateActiveOutgoing;
_remoteVideoState = OngoingCallRemoteVideoStateActive;
} else {
_videoState = OngoingCallVideoStateInactive;
_remoteVideoState = OngoingCallRemoteVideoStateInactive;
}
std::vector<uint8_t> derivedStateValue; std::vector<uint8_t> derivedStateValue;
derivedStateValue.resize(derivedState.length); derivedStateValue.resize(derivedState.length);
@ -145,6 +168,17 @@ static void (*InternalVoipLoggingFunction)(NSString *) = NULL;
proxyValue = std::unique_ptr<TgVoipProxy>(proxyObject); proxyValue = std::unique_ptr<TgVoipProxy>(proxyObject);
} }
std::vector<TgVoipRtcServer> parsedRtcServers;
for (VoipRtcServerWebrtc *server in rtcServers) {
parsedRtcServers.push_back((TgVoipRtcServer){
.host = server.host.UTF8String,
.port = (uint16_t)server.port,
.login = server.username.UTF8String,
.password = server.password.UTF8String,
.isTurn = server.isTurn
});
}
/*TgVoipCrypto crypto; /*TgVoipCrypto crypto;
crypto.sha1 = &TGCallSha1; crypto.sha1 = &TGCallSha1;
crypto.sha256 = &TGCallSha256; crypto.sha256 = &TGCallSha256;
@ -199,8 +233,10 @@ static void (*InternalVoipLoggingFunction)(NSString *) = NULL;
{ derivedStateValue }, { derivedStateValue },
endpoints, endpoints,
proxyValue, proxyValue,
parsedRtcServers,
callControllerNetworkTypeForType(networkType), callControllerNetworkTypeForType(networkType),
encryptionKey, encryptionKey,
isVideo,
[weakSelf, queue](TgVoipState state) { [weakSelf, queue](TgVoipState state) {
[queue dispatch:^{ [queue dispatch:^{
__strong OngoingCallThreadLocalContextWebrtc *strongSelf = weakSelf; __strong OngoingCallThreadLocalContextWebrtc *strongSelf = weakSelf;
@ -222,7 +258,26 @@ static void (*InternalVoipLoggingFunction)(NSString *) = NULL;
if (strongSelf->_videoState != videoState) { if (strongSelf->_videoState != videoState) {
strongSelf->_videoState = videoState; strongSelf->_videoState = videoState;
if (strongSelf->_stateChanged) { if (strongSelf->_stateChanged) {
strongSelf->_stateChanged(strongSelf->_state, strongSelf->_videoState); strongSelf->_stateChanged(strongSelf->_state, strongSelf->_videoState, strongSelf->_remoteVideoState);
}
}
}
}];
},
[weakSelf, queue](bool isActive) {
[queue dispatch:^{
__strong OngoingCallThreadLocalContextWebrtc *strongSelf = weakSelf;
if (strongSelf) {
OngoingCallRemoteVideoStateWebrtc remoteVideoState;
if (isActive) {
remoteVideoState = OngoingCallRemoteVideoStateActive;
} else {
remoteVideoState = OngoingCallRemoteVideoStateInactive;
}
if (strongSelf->_remoteVideoState != remoteVideoState) {
strongSelf->_remoteVideoState = remoteVideoState;
if (strongSelf->_stateChanged) {
strongSelf->_stateChanged(strongSelf->_state, strongSelf->_videoState, strongSelf->_remoteVideoState);
} }
} }
} }
@ -322,7 +377,12 @@ static void (*InternalVoipLoggingFunction)(NSString *) = NULL;
_state = callState; _state = callState;
if (_stateChanged) { if (_stateChanged) {
_stateChanged(_state, _videoState); if (_videoState == OngoingCallVideoStateActiveOutgoing) {
if (_state == OngoingCallStateConnected) {
_videoState = OngoingCallVideoStateActive;
}
}
_stateChanged(_state, _videoState, _remoteVideoState);
} }
} }
} }

View File

@ -211,7 +211,7 @@ func makeBridgeMedia(message: Message, strings: PresentationStrings, chatPeer: P
bridgeAction?.actionType = .channelCreated bridgeAction?.actionType = .channelCreated
} }
} }
case let .phoneCall(_, discardReason, _): case let .phoneCall(_, discardReason, _, _):
let bridgeAttachment = TGBridgeUnsupportedMediaAttachment() let bridgeAttachment = TGBridgeUnsupportedMediaAttachment()
let incoming = message.flags.contains(.Incoming) let incoming = message.flags.contains(.Incoming)
var compactTitle: String = "" var compactTitle: String = ""