mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Video improvements
This commit is contained in:
parent
74e9681c02
commit
c0aa075f2e
@ -2289,7 +2289,9 @@ Unused sets are archived when you add more.";
|
||||
|
||||
"Notification.CallTimeFormat" = "%1$@ (%2$@)"; // 1 - type, 2 - duration
|
||||
"Notification.CallOutgoing" = "Outgoing Call";
|
||||
"Notification.VideoCallOutgoing" = "Outgoing Video Call";
|
||||
"Notification.CallIncoming" = "Incoming Call";
|
||||
"Notification.VideoCallIncoming" = "Incoming Video Call";
|
||||
"Notification.CallMissed" = "Missed Call";
|
||||
"Notification.CallCanceled" = "Cancelled Call";
|
||||
"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.ButtonDiscuss" = "Discuss";
|
||||
"PeerInfo.ButtonCall" = "Call";
|
||||
"PeerInfo.ButtonVideoCall" = "Video Call";
|
||||
"PeerInfo.ButtonMute" = "Mute";
|
||||
"PeerInfo.ButtonUnmute" = "Unmute";
|
||||
"PeerInfo.ButtonMore" = "More";
|
||||
|
@ -30,7 +30,7 @@ public final class OpenChatMessageParams {
|
||||
public let addToTransitionSurface: (UIView) -> Void
|
||||
public let openUrl: (String) -> 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 sendSticker: ((FileMediaReference, ASDisplayNode, CGRect) -> Bool)?
|
||||
public let setupTemporaryHiddenMedia: (Signal<Any?, NoError>, Int, Media) -> Void
|
||||
@ -51,7 +51,7 @@ public final class OpenChatMessageParams {
|
||||
addToTransitionSurface: @escaping (UIView) -> Void,
|
||||
openUrl: @escaping (String) -> Void,
|
||||
openPeer: @escaping (Peer, ChatControllerInteractionNavigateToPeer) -> Void,
|
||||
callPeer: @escaping (PeerId) -> Void,
|
||||
callPeer: @escaping (PeerId, Bool) -> Void,
|
||||
enqueueMessage: @escaping (EnqueueMessage) -> Void,
|
||||
sendSticker: ((FileMediaReference, ASDisplayNode, CGRect) -> Bool)?,
|
||||
setupTemporaryHiddenMedia: @escaping (Signal<Any?, NoError>, Int, Media) -> Void,
|
||||
|
@ -11,6 +11,27 @@ public enum RequestCallResult {
|
||||
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 enum State: Equatable {
|
||||
case waiting
|
||||
@ -27,14 +48,22 @@ public struct PresentationCallState: Equatable {
|
||||
case notAvailable
|
||||
case available(Bool)
|
||||
case active
|
||||
case activeOutgoing
|
||||
}
|
||||
|
||||
public enum RemoteVideoState: Equatable {
|
||||
case inactive
|
||||
case active
|
||||
}
|
||||
|
||||
public var state: State
|
||||
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.videoState = videoState
|
||||
self.remoteVideoState = remoteVideoState
|
||||
}
|
||||
}
|
||||
|
||||
@ -72,5 +101,5 @@ public protocol PresentationCall: class {
|
||||
public protocol PresentationCallManager: class {
|
||||
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
|
||||
}
|
||||
|
@ -135,7 +135,16 @@ class CallListCallItem: ListViewItem {
|
||||
|
||||
func selected(listView: ListView) {
|
||||
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) {
|
||||
@ -237,7 +246,16 @@ class CallListCallItemNode: ItemListRevealOptionsItemNode {
|
||||
guard let item = self?.layoutParams?.0 else {
|
||||
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
|
||||
}
|
||||
}
|
||||
@ -357,7 +375,7 @@ class CallListCallItemNode: ItemListRevealOptionsItemNode {
|
||||
for message in item.messages {
|
||||
inner: for media in message.media {
|
||||
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) {
|
||||
hasIncoming = true
|
||||
|
||||
|
@ -145,9 +145,9 @@ public final class CallListController: ViewController {
|
||||
}
|
||||
|
||||
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 {
|
||||
strongSelf.call(peerId)
|
||||
strongSelf.call(peerId, isVideo: isVideo)
|
||||
}
|
||||
}, openInfo: { [weak self] peerId, messages in
|
||||
if let strongSelf = self {
|
||||
@ -201,6 +201,10 @@ public final class CallListController: ViewController {
|
||||
}
|
||||
|
||||
@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 }))
|
||||
controller.navigationPresentation = .modal
|
||||
self.createActionDisposable.set((controller.result
|
||||
@ -208,7 +212,7 @@ public final class CallListController: ViewController {
|
||||
|> deliverOnMainQueue).start(next: { [weak controller, weak self] peer in
|
||||
controller?.dismissSearch()
|
||||
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 {
|
||||
let _ = (strongSelf.context.sharedContext.hasOngoingCall.get()
|
||||
|> 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)
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] view in
|
||||
@ -273,7 +277,7 @@ public final class CallListController: ViewController {
|
||||
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 case let .alreadyInProgress(currentPeerId) = callResult {
|
||||
if currentPeerId == peerId {
|
||||
@ -287,7 +291,7 @@ public final class CallListController: ViewController {
|
||||
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: {
|
||||
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?()
|
||||
}
|
||||
})]), in: .window(.root))
|
||||
|
@ -59,12 +59,12 @@ private extension CallListViewEntry {
|
||||
|
||||
final class CallListNodeInteraction {
|
||||
let setMessageIdWithRevealedOptions: (MessageId?, MessageId?) -> Void
|
||||
let call: (PeerId) -> Void
|
||||
let call: (PeerId, Bool) -> Void
|
||||
let openInfo: (PeerId, [Message]) -> Void
|
||||
let delete: ([MessageId]) -> 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.call = call
|
||||
self.openInfo = openInfo
|
||||
@ -190,14 +190,14 @@ final class CallListControllerNode: ASDisplayNode {
|
||||
private let rightOverlayNode: ASDisplayNode
|
||||
private let emptyTextNode: ASTextNode
|
||||
|
||||
private let call: (PeerId) -> Void
|
||||
private let call: (PeerId, Bool) -> Void
|
||||
private let openInfo: (PeerId, [Message]) -> Void
|
||||
private let emptyStateUpdated: (Bool) -> Void
|
||||
|
||||
private let emptyStatePromise = Promise<Bool>()
|
||||
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.mode = mode
|
||||
self.presentationData = presentationData
|
||||
@ -248,8 +248,8 @@ final class CallListControllerNode: ASDisplayNode {
|
||||
}
|
||||
}
|
||||
}
|
||||
}, call: { [weak self] peerId in
|
||||
self?.call(peerId)
|
||||
}, call: { [weak self] peerId, isVideo in
|
||||
self?.call(peerId, isVideo)
|
||||
}, openInfo: { [weak self] peerId, messages in
|
||||
self?.openInfo(peerId, messages)
|
||||
}, delete: { [weak self] messageIds in
|
||||
|
@ -189,7 +189,7 @@ public func chatListItemStrings(strings: PresentationStrings, nameDisplayOrder:
|
||||
messageText = invoice.title
|
||||
case let action as TelegramMediaAction:
|
||||
switch action.action {
|
||||
case let .phoneCall(_, discardReason, _):
|
||||
case let .phoneCall(_, discardReason, _, _):
|
||||
hideAuthor = !isPeerGroup
|
||||
let incoming = message.flags.contains(.Incoming)
|
||||
if let discardReason = discardReason {
|
||||
|
@ -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 {
|
||||
canCall = false
|
||||
}
|
||||
var canVideoCall = false
|
||||
if canCall {
|
||||
if context.sharedContext.immediateExperimentalUISettings.videoCalls {
|
||||
canVideoCall = true
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
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 currentPeerId == peerId {
|
||||
context.sharedContext.navigateToCurrentCall()
|
||||
@ -127,7 +133,33 @@ func contactContextMenuItems(context: AccountContext, peerId: PeerId, contactsCo
|
||||
|> 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, 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))
|
||||
}
|
||||
})
|
||||
|
@ -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) {
|
||||
let t = node.layer.transform
|
||||
let currentScale = sqrt((t.m11 * t.m11) + (t.m12 * t.m12) + (t.m13 * t.m13))
|
||||
|
@ -877,7 +877,7 @@ public func deviceContactInfoController(context: AccountContext, subject: Device
|
||||
ActionSheetItemGroup(items: [
|
||||
ActionSheetButtonItem(title: presentationData.strings.UserInfo_TelegramCall, action: {
|
||||
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 currentPeerId == user.id {
|
||||
context.sharedContext.navigateToCurrentCall()
|
||||
@ -888,7 +888,7 @@ public func deviceContactInfoController(context: AccountContext, subject: Device
|
||||
} |> deliverOnMainQueue).start(next: { peer, current in
|
||||
if let peer = peer, let current = current {
|
||||
presentControllerImpl?(textAlertController(context: context, title: presentationData.strings.Call_CallInProgressTitle, text: presentationData.strings.Call_CallInProgressMessage(current.compactDisplayTitle, peer.compactDisplayTitle).0, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .genericAction, title: presentationData.strings.Common_OK, action: {
|
||||
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)
|
||||
}
|
||||
})
|
||||
|
@ -68,7 +68,7 @@ private func stringForCallType(message: Message, strings: PresentationStrings) -
|
||||
switch media {
|
||||
case let action as TelegramMediaAction:
|
||||
switch action.action {
|
||||
case let .phoneCall(_, discardReason, _):
|
||||
case let .phoneCall(_, discardReason, _, _):
|
||||
let incoming = message.flags.contains(.Incoming)
|
||||
if let discardReason = discardReason {
|
||||
switch discardReason {
|
||||
|
@ -859,7 +859,7 @@ public func userInfoController(context: AccountContext, peerId: PeerId, mode: Pe
|
||||
return .single((view, view.cachedData))
|
||||
}))
|
||||
|
||||
let requestCallImpl: () -> Void = {
|
||||
let requestCallImpl: (Bool) -> Void = { isVideo in
|
||||
let _ = (peerView.get()
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).start(next: { view in
|
||||
@ -873,7 +873,7 @@ public func userInfoController(context: AccountContext, peerId: PeerId, mode: Pe
|
||||
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 currentPeerId == peer.id {
|
||||
context.sharedContext.navigateToCurrentCall()
|
||||
@ -884,7 +884,7 @@ public func userInfoController(context: AccountContext, peerId: PeerId, mode: Pe
|
||||
} |> deliverOnMainQueue).start(next: { peer, current in
|
||||
if let peer = peer, let current = current {
|
||||
presentControllerImpl?(textAlertController(context: context, title: presentationData.strings.Call_CallInProgressTitle, text: presentationData.strings.Call_CallInProgressMessage(current.compactDisplayTitle, peer.compactDisplayTitle).0, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .genericAction, title: presentationData.strings.Common_OK, action: {
|
||||
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)
|
||||
}
|
||||
})
|
||||
@ -1111,7 +1111,7 @@ public func userInfoController(context: AccountContext, peerId: PeerId, mode: Pe
|
||||
}, displayCopyContextMenu: { tag, phone in
|
||||
displayCopyContextMenuImpl?(tag, phone)
|
||||
}, call: {
|
||||
requestCallImpl()
|
||||
requestCallImpl(false)
|
||||
}, openCallMenu: { number in
|
||||
let _ = (getUserPeer(postbox: context.account.postbox, peerId: peerId)
|
||||
|> deliverOnMainQueue).start(next: { peer, _ in
|
||||
@ -1125,7 +1125,7 @@ public func userInfoController(context: AccountContext, peerId: PeerId, mode: Pe
|
||||
ActionSheetItemGroup(items: [
|
||||
ActionSheetButtonItem(title: presentationData.strings.UserInfo_TelegramCall, action: {
|
||||
dismissAction()
|
||||
requestCallImpl()
|
||||
requestCallImpl(false)
|
||||
}),
|
||||
ActionSheetButtonItem(title: presentationData.strings.UserInfo_PhoneCall, action: {
|
||||
dismissAction()
|
||||
|
@ -38,7 +38,7 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
|
||||
case historyScreenshot
|
||||
case messageAutoremoveTimeoutUpdated(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 customText(text: String, entities: [MessageTextEntity])
|
||||
case botDomainAccessGranted(domain: String)
|
||||
@ -80,7 +80,7 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
|
||||
if let value = decoder.decodeOptionalInt32ForKey("dr") {
|
||||
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:
|
||||
self = .paymentSent(currency: decoder.decodeStringForKey("currency", orElse: ""), totalAmount: decoder.decodeInt64ForKey("ta", orElse: 0))
|
||||
case 16:
|
||||
@ -152,7 +152,7 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
|
||||
encoder.encodeInt32(15, forKey: "_rawValue")
|
||||
encoder.encodeString(currency, forKey: "currency")
|
||||
encoder.encodeInt64(totalAmount, forKey: "ta")
|
||||
case let .phoneCall(callId, discardReason, duration):
|
||||
case let .phoneCall(callId, discardReason, duration, isVideo):
|
||||
encoder.encodeInt32(14, forKey: "_rawValue")
|
||||
encoder.encodeInt64(callId, forKey: "i")
|
||||
if let discardReason = discardReason {
|
||||
@ -165,6 +165,7 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
|
||||
} else {
|
||||
encoder.encodeNil(forKey: "d")
|
||||
}
|
||||
encoder.encodeInt32(isVideo ? 1 : 0, forKey: "vc")
|
||||
case let .customText(text, entities):
|
||||
encoder.encodeInt32(16, forKey: "_rawValue")
|
||||
encoder.encodeString(text, forKey: "text")
|
||||
|
@ -37,7 +37,7 @@ private func presentLiveLocationController(context: AccountContext, peerId: Peer
|
||||
}, addToTransitionSurface: { _ in
|
||||
}, openUrl: { _ in
|
||||
}, openPeer: { peer, navigation in
|
||||
}, callPeer: { _ in
|
||||
}, callPeer: { _, _ in
|
||||
}, enqueueMessage: { _ in
|
||||
}, sendSticker: nil,
|
||||
setupTemporaryHiddenMedia: { _, _, _ in
|
||||
|
@ -33,8 +33,6 @@ final class CallControllerButtonsNode: ASDisplayNode {
|
||||
private let endButton: CallControllerButtonNode
|
||||
private let speakerButton: CallControllerButtonNode
|
||||
|
||||
private let videoButton: CallControllerButtonNode
|
||||
|
||||
private var mode: CallControllerButtonsMode?
|
||||
|
||||
private var validLayout: CGFloat?
|
||||
@ -64,9 +62,6 @@ final class CallControllerButtonsNode: ASDisplayNode {
|
||||
self.speakerButton = CallControllerButtonNode(type: .speaker, label: nil)
|
||||
self.speakerButton.alpha = 0.0
|
||||
|
||||
self.videoButton = CallControllerButtonNode(type: .video, label: nil)
|
||||
self.videoButton.alpha = 0.0
|
||||
|
||||
super.init()
|
||||
|
||||
self.addSubnode(self.acceptButton)
|
||||
@ -74,14 +69,12 @@ final class CallControllerButtonsNode: ASDisplayNode {
|
||||
self.addSubnode(self.muteButton)
|
||||
self.addSubnode(self.endButton)
|
||||
self.addSubnode(self.speakerButton)
|
||||
self.addSubnode(self.videoButton)
|
||||
|
||||
self.acceptButton.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.endButton.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) {
|
||||
@ -122,10 +115,6 @@ final class CallControllerButtonsNode: ASDisplayNode {
|
||||
for button in [self.muteButton, self.endButton, self.speakerButton] {
|
||||
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
|
||||
}
|
||||
|
||||
@ -140,7 +129,7 @@ final class CallControllerButtonsNode: ASDisplayNode {
|
||||
for button in [self.declineButton, self.acceptButton] {
|
||||
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
|
||||
}
|
||||
case let .active(speakerMode, videoState):
|
||||
@ -171,7 +160,7 @@ final class CallControllerButtonsNode: ASDisplayNode {
|
||||
self.endButton.alpha = 1.0
|
||||
}
|
||||
|
||||
switch videoState {
|
||||
/*switch videoState {
|
||||
case .notAvailable:
|
||||
self.videoButton.alpha = 0.0
|
||||
case let .available(isEnabled):
|
||||
@ -185,8 +174,7 @@ final class CallControllerButtonsNode: ASDisplayNode {
|
||||
case .active:
|
||||
self.videoButton.isUserInteractionEnabled = true
|
||||
self.videoButton.alpha = 0.0
|
||||
}
|
||||
|
||||
}*/
|
||||
|
||||
if !self.declineButton.alpha.isZero {
|
||||
if animated {
|
||||
@ -223,9 +211,9 @@ final class CallControllerButtonsNode: ASDisplayNode {
|
||||
self.speaker?()
|
||||
} else if button === self.acceptButton {
|
||||
self.accept?()
|
||||
} else if button === self.videoButton {
|
||||
}/* else if button === self.videoButton {
|
||||
self.toggleVideo?()
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
||||
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||
@ -235,7 +223,7 @@ final class CallControllerButtonsNode: ASDisplayNode {
|
||||
self.muteButton,
|
||||
self.endButton,
|
||||
self.speakerButton,
|
||||
self.videoButton
|
||||
//self.videoButton
|
||||
]
|
||||
for button in buttons {
|
||||
if let result = button.view.hitTest(self.view.convert(point, to: button.view), with: event) {
|
||||
|
@ -16,6 +16,8 @@ import CallsEmoji
|
||||
|
||||
private final class IncomingVideoNode: ASDisplayNode {
|
||||
private let videoView: UIView
|
||||
private var effectView: UIVisualEffectView?
|
||||
private var isBlurred: Bool = false
|
||||
|
||||
init(videoView: UIView) {
|
||||
self.videoView = videoView
|
||||
@ -28,6 +30,29 @@ private final class IncomingVideoNode: ASDisplayNode {
|
||||
func updateLayout(size: CGSize) {
|
||||
self.videoView.frame = CGRect(origin: CGPoint(), size: size)
|
||||
}
|
||||
|
||||
func updateIsBlurred(isBlurred: Bool) {
|
||||
if self.isBlurred == isBlurred {
|
||||
return
|
||||
}
|
||||
self.isBlurred = isBlurred
|
||||
|
||||
if isBlurred {
|
||||
if self.effectView == nil {
|
||||
let effectView = UIVisualEffectView()
|
||||
self.effectView = effectView
|
||||
effectView.frame = self.videoView.frame
|
||||
self.view.addSubview(effectView)
|
||||
}
|
||||
UIView.animate(withDuration: 0.3, animations: {
|
||||
self.effectView?.effect = UIBlurEffect(style: .dark)
|
||||
})
|
||||
} else if let effectView = self.effectView {
|
||||
UIView.animate(withDuration: 0.3, animations: {
|
||||
effectView.effect = nil
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final class OutgoingVideoNode: ASDisplayNode {
|
||||
@ -51,8 +76,9 @@ private final class OutgoingVideoNode: ASDisplayNode {
|
||||
self.switchCamera()
|
||||
}
|
||||
|
||||
func updateLayout(size: CGSize) {
|
||||
self.videoView.frame = CGRect(origin: CGPoint(), size: size)
|
||||
func updateLayout(size: CGSize, isExpanded: Bool, transition: ContainedViewLayoutTransition) {
|
||||
transition.updateFrame(view: self.videoView, frame: CGRect(origin: CGPoint(), size: size))
|
||||
transition.updateCornerRadius(layer: self.videoView.layer, cornerRadius: isExpanded ? 0.0 : 16.0)
|
||||
self.switchCameraButton.frame = CGRect(origin: CGPoint(), size: size)
|
||||
}
|
||||
}
|
||||
@ -75,8 +101,9 @@ final class CallControllerNode: ASDisplayNode {
|
||||
private let imageNode: TransformImageNode
|
||||
private let dimNode: ASDisplayNode
|
||||
private var incomingVideoNode: IncomingVideoNode?
|
||||
private var incomingVideoViewRequested: Bool = false
|
||||
private var outgoingVideoNode: OutgoingVideoNode?
|
||||
private var videoViewsRequested: Bool = false
|
||||
private var outgoingVideoViewRequested: Bool = false
|
||||
private let backButtonArrowNode: ASImageNode
|
||||
private let backButtonNode: HighlightableButtonNode
|
||||
private let statusNode: CallControllerStatusNode
|
||||
@ -256,8 +283,8 @@ final class CallControllerNode: ASDisplayNode {
|
||||
|
||||
switch callState.videoState {
|
||||
case .active:
|
||||
if !self.videoViewsRequested {
|
||||
self.videoViewsRequested = true
|
||||
if !self.incomingVideoViewRequested {
|
||||
self.incomingVideoViewRequested = true
|
||||
self.call.makeIncomingVideoView(completion: { [weak self] incomingVideoView in
|
||||
guard let strongSelf = self else {
|
||||
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
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
@ -305,6 +363,17 @@ final class CallControllerNode: ASDisplayNode {
|
||||
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 {
|
||||
case .waiting, .connecting:
|
||||
statusValue = .text(self.presentationData.strings.Call_StatusConnecting)
|
||||
@ -444,6 +513,8 @@ final class CallControllerNode: ASDisplayNode {
|
||||
mappedVideoState = .available(true)
|
||||
case .active:
|
||||
mappedVideoState = .active
|
||||
case .activeOutgoing:
|
||||
mappedVideoState = .active
|
||||
}
|
||||
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
|
||||
transition.updateFrame(node: self.buttonsNode, frame: CGRect(origin: CGPoint(x: 0.0, y: buttonsOriginY), size: CGSize(width: layout.size.width, height: buttonsHeight)))
|
||||
|
||||
var outgoingVideoTransition = transition
|
||||
if let incomingVideoNode = self.incomingVideoNode {
|
||||
if incomingVideoNode.frame.width.isZero, let outgoingVideoNode = self.outgoingVideoNode, !outgoingVideoNode.frame.width.isZero, !transition.isAnimated {
|
||||
outgoingVideoTransition = .animated(duration: 0.3, curve: .easeInOut)
|
||||
}
|
||||
incomingVideoNode.frame = CGRect(origin: CGPoint(), size: layout.size)
|
||||
incomingVideoNode.updateLayout(size: layout.size)
|
||||
}
|
||||
if let outgoingVideoNode = self.outgoingVideoNode {
|
||||
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)
|
||||
outgoingVideoNode.frame = outgoingFrame
|
||||
outgoingVideoNode.updateLayout(size: outgoingFrame.size)
|
||||
if self.incomingVideoNode == nil {
|
||||
outgoingVideoNode.frame = CGRect(origin: CGPoint(), size: layout.size)
|
||||
outgoingVideoNode.updateLayout(size: layout.size, isExpanded: true, transition: transition)
|
||||
} else {
|
||||
let outgoingSize = layout.size.aspectFitted(CGSize(width: 200.0, height: 200.0))
|
||||
let outgoingFrame = CGRect(origin: CGPoint(x: layout.size.width - 16.0 - outgoingSize.width, y: buttonsOriginY - 32.0 - outgoingSize.height), size: outgoingSize)
|
||||
outgoingVideoTransition.updateFrame(node: outgoingVideoNode, frame: outgoingFrame)
|
||||
outgoingVideoNode.updateLayout(size: outgoingFrame.size, isExpanded: false, transition: outgoingVideoTransition)
|
||||
}
|
||||
}
|
||||
|
||||
let keyTextSize = self.keyButtonNode.frame.size
|
||||
|
@ -15,13 +15,13 @@ public final class CallKitIntegration {
|
||||
public static var isAvailable: Bool {
|
||||
#if targetEnvironment(simulator)
|
||||
return false
|
||||
#endif
|
||||
|
||||
#else
|
||||
if #available(iOSApplicationExtension 10.0, iOS 10.0, *) {
|
||||
return Locale.current.regionCode?.lowercased() != "cn"
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
private let audioSessionActivePromise = ValuePromise<Bool>(false, ignoreRepeated: true)
|
||||
@ -29,7 +29,7 @@ public final class CallKitIntegration {
|
||||
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 {
|
||||
return nil
|
||||
}
|
||||
@ -40,7 +40,7 @@ public final class CallKitIntegration {
|
||||
|
||||
if #available(iOSApplicationExtension 10.0, iOS 10.0, *) {
|
||||
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)
|
||||
} else {
|
||||
@ -49,9 +49,9 @@ public final class CallKitIntegration {
|
||||
#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, *) {
|
||||
(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)
|
||||
}
|
||||
}
|
||||
@ -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, *) {
|
||||
(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 startCall: ((Account, UUID, String) -> Signal<Bool, NoError>)?
|
||||
private var startCall: ((Account, UUID, String, Bool) -> Signal<Bool, NoError>)?
|
||||
private var answerCall: ((UUID) -> Void)?
|
||||
private var endCall: ((UUID) -> Signal<Bool, NoError>)?
|
||||
private var setCallMuted: ((UUID, Bool) -> Void)?
|
||||
@ -112,15 +112,15 @@ class CallKitProviderDelegate: NSObject, CXProviderDelegate {
|
||||
|
||||
fileprivate var audioSessionActivePromise: ValuePromise<Bool>?
|
||||
|
||||
override init() {
|
||||
self.provider = CXProvider(configuration: CallKitProviderDelegate.providerConfiguration)
|
||||
init(enableVideoCalls: Bool) {
|
||||
self.provider = CXProvider(configuration: CallKitProviderDelegate.providerConfiguration(enableVideoCalls: enableVideoCalls))
|
||||
|
||||
super.init()
|
||||
|
||||
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.startCall = startCall
|
||||
self.answerCall = answerCall
|
||||
@ -129,7 +129,7 @@ class CallKitProviderDelegate: NSObject, CXProviderDelegate {
|
||||
self.audioSessionActivationChanged = audioSessionActivationChanged
|
||||
}
|
||||
|
||||
static var providerConfiguration: CXProviderConfiguration {
|
||||
private static func providerConfiguration(enableVideoCalls: Bool) -> CXProviderConfiguration {
|
||||
let providerConfiguration = CXProviderConfiguration(localizedName: "Telegram")
|
||||
|
||||
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()
|
||||
self.currentStartCallAccount = (uuid, account)
|
||||
let handle = CXHandle(type: .generic, value: "\(peerId.id)")
|
||||
let startCallAction = CXStartCallAction(call: uuid, handle: handle)
|
||||
startCallAction.contactIdentifier = displayTitle
|
||||
|
||||
startCallAction.isVideo = false
|
||||
startCallAction.isVideo = isVideo
|
||||
let transaction = CXTransaction(action: startCallAction)
|
||||
|
||||
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()
|
||||
update.remoteHandle = CXHandle(type: .generic, value: handle)
|
||||
update.localizedCallerName = displayTitle
|
||||
@ -197,6 +197,7 @@ class CallKitProviderDelegate: NSObject, CXProviderDelegate {
|
||||
update.supportsGrouping = false
|
||||
update.supportsUngrouping = false
|
||||
update.supportsDTMF = false
|
||||
update.hasVideo = isVideo
|
||||
|
||||
self.provider.reportNewIncomingCall(with: uuid, update: update, completion: { error in
|
||||
completion?(error as NSError?)
|
||||
@ -222,7 +223,7 @@ class CallKitProviderDelegate: NSObject, CXProviderDelegate {
|
||||
self.currentStartCallAccount = nil
|
||||
let disposable = MetaDisposable()
|
||||
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
|
||||
|> afterDisposed { [weak self, weak disposable] in
|
||||
if let strongSelf = self, let disposable = disposable {
|
||||
|
@ -166,12 +166,14 @@ public final class PresentationCallImpl: PresentationCall {
|
||||
public let internalId: CallSessionInternalId
|
||||
public let peerId: PeerId
|
||||
public let isOutgoing: Bool
|
||||
private var isVideo: Bool
|
||||
public let peer: Peer?
|
||||
|
||||
private let serializedData: String?
|
||||
private let dataSaving: VoiceCallDataSaving
|
||||
private let derivedState: VoipDerivedState
|
||||
private let proxyServer: ProxyServerSettings?
|
||||
private let auxiliaryServers: [OngoingCallContext.AuxiliaryServer]
|
||||
private let currentNetworkType: NetworkType
|
||||
private let updatedNetworkType: Signal<NetworkType, NoError>
|
||||
|
||||
@ -188,7 +190,7 @@ public final class PresentationCallImpl: PresentationCall {
|
||||
|
||||
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> {
|
||||
return self.statePromise.get()
|
||||
}
|
||||
@ -231,16 +233,31 @@ public final class PresentationCallImpl: PresentationCall {
|
||||
private var droppedCall = false
|
||||
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.audioSession = audioSession
|
||||
self.callSessionManager = callSessionManager
|
||||
self.callKitIntegration = callKitIntegration
|
||||
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.peerId = peerId
|
||||
self.isOutgoing = isOutgoing
|
||||
self.isVideo = initialState?.type == .video
|
||||
self.peer = peer
|
||||
|
||||
self.serializedData = serializedData
|
||||
@ -369,6 +386,9 @@ public final class PresentationCallImpl: PresentationCall {
|
||||
}
|
||||
|
||||
private func updateSessionState(sessionState: CallSession, callContextState: OngoingCallContextState?, reception: Int32?, audioSessionControl: ManagedAudioSessionControl?) {
|
||||
if case .video = sessionState.type {
|
||||
self.isVideo = true
|
||||
}
|
||||
let previous = self.sessionState
|
||||
let previousControl = self.audioSessionControl
|
||||
self.sessionState = sessionState
|
||||
@ -400,13 +420,37 @@ public final class PresentationCallImpl: PresentationCall {
|
||||
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 {
|
||||
case .ringing:
|
||||
presentationState = PresentationCallState(state: .ringing, videoState: .notAvailable)
|
||||
presentationState = PresentationCallState(state: .ringing, videoState: .notAvailable, remoteVideoState: .inactive)
|
||||
if previous == nil || previousControl == nil {
|
||||
if !self.reportedIncomingCall {
|
||||
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 error.domain == "com.apple.CallKit.error.incomingcall" && (error.code == -3 || error.code == 3) {
|
||||
Logger.shared.log("PresentationCall", "reportIncomingCall device in DND mode")
|
||||
@ -429,28 +473,19 @@ public final class PresentationCallImpl: PresentationCall {
|
||||
}
|
||||
case .accepting:
|
||||
self.callWasActive = true
|
||||
presentationState = PresentationCallState(state: .connecting(nil), videoState: .notAvailable)
|
||||
presentationState = PresentationCallState(state: .connecting(nil), videoState: .notAvailable, remoteVideoState: mappedRemoteVideoState)
|
||||
case .dropping:
|
||||
presentationState = PresentationCallState(state: .terminating, videoState: .notAvailable)
|
||||
presentationState = PresentationCallState(state: .terminating, videoState: .notAvailable, remoteVideoState: mappedRemoteVideoState)
|
||||
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):
|
||||
presentationState = PresentationCallState(state: .requesting(ringing), videoState: .notAvailable)
|
||||
presentationState = PresentationCallState(state: .requesting(ringing), videoState: .notAvailable, remoteVideoState: mappedRemoteVideoState)
|
||||
case let .active(_, _, keyVisualHash, _, _, _, _):
|
||||
self.callWasActive = true
|
||||
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 {
|
||||
case .initializing:
|
||||
presentationState = PresentationCallState(state: .connecting(keyVisualHash), videoState: mappedVideoState)
|
||||
presentationState = PresentationCallState(state: .connecting(keyVisualHash), videoState: mappedVideoState, remoteVideoState: mappedRemoteVideoState)
|
||||
case .failed:
|
||||
presentationState = nil
|
||||
self.callSessionManager.drop(internalId: self.internalId, reason: .disconnect, debugLog: .single(nil))
|
||||
@ -462,7 +497,7 @@ public final class PresentationCallImpl: PresentationCall {
|
||||
timestamp = CFAbsoluteTimeGetCurrent()
|
||||
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:
|
||||
let timestamp: Double
|
||||
if let activeTimestamp = self.activeTimestamp {
|
||||
@ -471,10 +506,10 @@ public final class PresentationCallImpl: PresentationCall {
|
||||
timestamp = CFAbsoluteTimeGetCurrent()
|
||||
self.activeTimestamp = timestamp
|
||||
}
|
||||
presentationState = PresentationCallState(state: .reconnecting(timestamp, reception, keyVisualHash), videoState: mappedVideoState)
|
||||
presentationState = PresentationCallState(state: .reconnecting(timestamp, reception, keyVisualHash), videoState: mappedVideoState, remoteVideoState: mappedRemoteVideoState)
|
||||
}
|
||||
} 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 {
|
||||
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.debugInfoValue.set(ongoingContext.debugInfo())
|
||||
@ -629,8 +664,26 @@ public final class PresentationCallImpl: PresentationCall {
|
||||
return
|
||||
}
|
||||
if value {
|
||||
strongSelf.callSessionManager.accept(internalId: strongSelf.internalId)
|
||||
strongSelf.callKitIntegration?.answerCall(uuid: strongSelf.internalId)
|
||||
if strongSelf.isVideo {
|
||||
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 {
|
||||
let _ = strongSelf.hangUp().start()
|
||||
}
|
||||
|
@ -16,6 +16,47 @@ private func callKitIntegrationIfEnabled(_ integration: CallKitIntegration?, set
|
||||
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 {
|
||||
case none
|
||||
case incomingRinging(CallSessionRingingState)
|
||||
@ -79,7 +120,7 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
|
||||
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.accountManager = accountManager
|
||||
self.audioSession = audioSession
|
||||
@ -87,15 +128,15 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
|
||||
self.isMediaPlaying = isMediaPlaying
|
||||
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 endCallImpl: ((UUID) -> Signal<Bool, NoError>)?
|
||||
var setCallMutedImpl: ((UUID, 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 {
|
||||
return startCallImpl(account, uuid, handle)
|
||||
return startCallImpl(account, uuid, handle, isVideo)
|
||||
} else {
|
||||
return .single(false)
|
||||
}
|
||||
@ -169,9 +210,9 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
|
||||
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) {
|
||||
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)
|
||||
|> map { result -> Bool in
|
||||
return result
|
||||
@ -245,7 +286,7 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
|
||||
let semaphore = DispatchSemaphore(value: 0)
|
||||
var data: (PreferencesView, AccountSharedDataView, Peer?)?
|
||||
let _ = combineLatest(
|
||||
account.postbox.preferencesView(keys: [PreferencesKeys.voipConfiguration, ApplicationSpecificPreferencesKeys.voipDerivedState])
|
||||
account.postbox.preferencesView(keys: [PreferencesKeys.voipConfiguration, ApplicationSpecificPreferencesKeys.voipDerivedState, PreferencesKeys.appConfiguration])
|
||||
|> take(1),
|
||||
accountManager.sharedData(keys: [SharedDataKeys.autodownloadSettings])
|
||||
|> take(1),
|
||||
@ -260,12 +301,13 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
|
||||
|
||||
if let (preferences, sharedData, maybePeer) = data, let peer = maybePeer {
|
||||
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 autodownloadSettings = sharedData.entries[SharedDataKeys.autodownloadSettings] as? AutodownloadSettings ?? .defaultSettings
|
||||
|
||||
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.currentCallPromise.set(.single(call))
|
||||
self.hasActiveCallsPromise.set(true)
|
||||
@ -285,7 +327,7 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
|
||||
private func ringingStatesUpdated(_ ringingStates: [(Account, Peer, CallSessionRingingState, Bool, NetworkType)], enableCallKit: Bool) {
|
||||
if let firstState = ringingStates.first {
|
||||
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
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
@ -294,8 +336,9 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
|
||||
let configuration = preferences.values[PreferencesKeys.voipConfiguration] as? VoipConfiguration ?? .defaultValue
|
||||
let derivedState = preferences.values[ApplicationSpecificPreferencesKeys.voipDerivedState] as? VoipDerivedState ?? .default
|
||||
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.currentCallPromise.set(.single(call))
|
||||
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 {
|
||||
return .alreadyInProgress(call.peerId)
|
||||
}
|
||||
@ -337,8 +380,19 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
|
||||
}, openSettings: {
|
||||
openSettings()
|
||||
}, { value in
|
||||
subscriber.putNext(value)
|
||||
subscriber.putCompletion()
|
||||
if isVideo {
|
||||
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
|
||||
}
|
||||
@ -357,7 +411,7 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
|
||||
guard let strongSelf = self, let peer = peer else {
|
||||
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 {
|
||||
@ -374,7 +428,7 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
let _ = strongSelf.startCall(account: account, peerId: peerId).start()
|
||||
let _ = strongSelf.startCall(account: account, peerId: peerId, isVideo: isVideo).start()
|
||||
}
|
||||
if let currentCall = self.currentCall {
|
||||
self.startCallDisposable.set((currentCall.hangUp()
|
||||
@ -388,7 +442,7 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
|
||||
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 accessEnabledSignal: Signal<Bool, NoError> = Signal { subscriber in
|
||||
@ -397,8 +451,19 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
|
||||
}, openSettings: {
|
||||
openSettings()
|
||||
}, { value in
|
||||
subscriber.putNext(value)
|
||||
subscriber.putCompletion()
|
||||
if isVideo {
|
||||
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
|
||||
}
|
||||
@ -411,9 +476,9 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
|
||||
if !accessEnabled {
|
||||
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
|
||||
} |> 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
|
||||
|> beforeNext { internalId, currentNetworkType, isContact, preferences, sharedData in
|
||||
if let strongSelf = self, accessEnabled {
|
||||
@ -424,8 +489,9 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
|
||||
let configuration = preferences.values[PreferencesKeys.voipConfiguration] as? VoipConfiguration ?? .defaultValue
|
||||
let derivedState = preferences.values[ApplicationSpecificPreferencesKeys.voipDerivedState] as? VoipDerivedState ?? .default
|
||||
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.currentCallPromise.set(.single(call))
|
||||
strongSelf.hasActiveCallsPromise.set(true)
|
||||
|
@ -1322,7 +1322,7 @@ public final class AccountViewTracker {
|
||||
var lhsOther = false
|
||||
inner: for media in lhs.media {
|
||||
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 {
|
||||
lhsMissed = true
|
||||
} else {
|
||||
@ -1336,7 +1336,7 @@ public final class AccountViewTracker {
|
||||
var rhsOther = false
|
||||
inner: for media in rhs.media {
|
||||
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 {
|
||||
rhsMissed = true
|
||||
} else {
|
||||
|
@ -179,8 +179,14 @@ public enum CallSessionState {
|
||||
}
|
||||
|
||||
public struct CallSession {
|
||||
public enum CallType {
|
||||
case audio
|
||||
case video
|
||||
}
|
||||
|
||||
public let id: CallSessionInternalId
|
||||
public let isOutgoing: Bool
|
||||
public let type: CallType
|
||||
public let state: CallSessionState
|
||||
}
|
||||
|
||||
@ -211,6 +217,7 @@ private func parseConnectionSet(primary: Api.PhoneConnection, alternative: [Api.
|
||||
private final class CallSessionContext {
|
||||
let peerId: PeerId
|
||||
let isOutgoing: Bool
|
||||
let type: CallSession.CallType
|
||||
var state: CallSessionInternalState
|
||||
let subscribers = Bag<(CallSession) -> 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.isOutgoing = isOutgoing
|
||||
self.type = type
|
||||
self.state = state
|
||||
}
|
||||
|
||||
@ -311,7 +319,7 @@ private final class CallSessionManagerContext {
|
||||
let index = context.subscribers.add { next in
|
||||
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 {
|
||||
queue.async {
|
||||
if let strongSelf = self, let context = strongSelf.contexts[internalId] {
|
||||
@ -372,14 +380,14 @@ private final class CallSessionManagerContext {
|
||||
|
||||
private func contextUpdated(internalId: CallSessionInternalId) {
|
||||
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() {
|
||||
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 {
|
||||
return nil
|
||||
}
|
||||
@ -390,7 +398,7 @@ private final class CallSessionManagerContext {
|
||||
|
||||
if randomStatus == 0 {
|
||||
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
|
||||
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
|
||||
@ -414,6 +422,7 @@ private final class CallSessionManagerContext {
|
||||
if let context = self.contexts[internalId] {
|
||||
var dropData: (CallSessionStableId, Int64, DropCallSessionReason)?
|
||||
var wasRinging = false
|
||||
let isVideo = context.type == .video
|
||||
switch context.state {
|
||||
case let .ringing(id, accessHash, _, _, _):
|
||||
wasRinging = true
|
||||
@ -471,7 +480,7 @@ private final class CallSessionManagerContext {
|
||||
|
||||
if let (id, accessHash, reason) = dropData {
|
||||
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
|
||||
if let strongSelf = self {
|
||||
if let context = strongSelf.contexts[internalId] {
|
||||
@ -722,13 +731,14 @@ private final class CallSessionManagerContext {
|
||||
}
|
||||
}
|
||||
case let .phoneCallRequested(flags, id, accessHash, date, adminId, _, gAHash, requestedProtocol):
|
||||
let isVideo = (flags & (1 << 5)) != 0
|
||||
let versions: [String]
|
||||
switch requestedProtocol {
|
||||
case let .phoneCallProtocol(_, _, _, libraryVersions):
|
||||
versions = libraryVersions
|
||||
}
|
||||
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 {
|
||||
var resultRingingStateValue: CallSessionRingingState?
|
||||
for ringingState in self.ringingStatesValue() {
|
||||
@ -738,7 +748,7 @@ private final class CallSessionManagerContext {
|
||||
}
|
||||
}
|
||||
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 {
|
||||
resultRingingState = (resultRingingStateValue, callSession)
|
||||
}
|
||||
@ -802,12 +812,12 @@ private final class CallSessionManagerContext {
|
||||
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 randomStatus = SecRandomCopyBytes(nil, 256, aBytes.assumingMemoryBound(to: UInt8.self))
|
||||
let a = Data(bytesNoCopy: aBytes, count: 256, deallocator: .free)
|
||||
if randomStatus == 0 {
|
||||
self.contexts[internalId] = CallSessionContext(peerId: peerId, isOutgoing: true, 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 case .requesting = context.state {
|
||||
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
|
||||
let disposable = MetaDisposable()
|
||||
|
||||
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.putCompletion()
|
||||
}
|
||||
@ -1040,7 +1050,7 @@ private enum RequestCallSessionResult {
|
||||
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)
|
||||
|> mapToSignal { config -> 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)!
|
||||
|
||||
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
|
||||
switch result {
|
||||
case let .phoneCall(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)
|
||||
case let .phoneCallWaiting(_, id, accessHash, _, _, _, _, receiveDate):
|
||||
return .success(id: id, accessHash: accessHash, config: config, gA: ga, remoteConfirmationTimestamp: receiveDate)
|
||||
@ -1118,7 +1133,7 @@ private enum DropCallSessionReason {
|
||||
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 duration: Int32 = 0
|
||||
switch reason {
|
||||
@ -1134,7 +1149,13 @@ private func dropCallSession(network: Network, addUpdates: @escaping (Api.Update
|
||||
case .missed:
|
||||
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)
|
||||
|> `catch` { _ -> Signal<Api.Updates?, NoError> in
|
||||
return .single(nil)
|
||||
|
@ -75,7 +75,7 @@ public func tagsForStoreMessage(incoming: Bool, attributes: [MessageAttribute],
|
||||
tags.insert(.webPage)
|
||||
} else if let action = attachment as? TelegramMediaAction {
|
||||
switch action.action {
|
||||
case let .phoneCall(_, discardReason, _):
|
||||
case let .phoneCall(_, discardReason, _, _):
|
||||
globalTags.insert(.Calls)
|
||||
if incoming, let discardReason = discardReason, case .missed = discardReason {
|
||||
globalTags.insert(.MissedCalls)
|
||||
|
@ -32,12 +32,13 @@ func telegramMediaActionFromApiAction(_ action: Api.MessageAction) -> TelegramMe
|
||||
return TelegramMediaAction(action: .pinnedMessageUpdated)
|
||||
case let .messageActionGameScore(gameId, 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?
|
||||
if let reason = 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:
|
||||
return nil
|
||||
case let .messageActionPaymentSent(currency, totalAmount):
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -371,7 +371,7 @@ public func universalServiceMessageString(presentationData: (PresentationTheme,
|
||||
} else {
|
||||
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
|
||||
let incoming: Bool
|
||||
if message.flags.contains(.Incoming) {
|
||||
|
Binary file not shown.
@ -141,6 +141,14 @@ protocol SupportedStartCallIntent {
|
||||
@available(iOS 10.0, *)
|
||||
extension INStartAudioCallIntent: SupportedStartCallIntent {}
|
||||
|
||||
protocol SupportedStartVideoCallIntent {
|
||||
@available(iOS 10.0, *)
|
||||
var contacts: [INPerson]? { get }
|
||||
}
|
||||
|
||||
@available(iOS 10.0, *)
|
||||
extension INStartVideoCallIntent: SupportedStartVideoCallIntent {}
|
||||
|
||||
private enum QueuedWakeup: Int32 {
|
||||
case call
|
||||
case backgroundLocation
|
||||
@ -1725,9 +1733,19 @@ final class SharedApplicationContext {
|
||||
|
||||
func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
|
||||
if #available(iOS 10.0, *) {
|
||||
var startCallContacts: [INPerson]?
|
||||
var startCallIsVideo = false
|
||||
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
|
||||
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 {
|
||||
@ -1754,7 +1772,7 @@ final class SharedApplicationContext {
|
||||
}
|
||||
}
|
||||
|
||||
if let contact = startCallIntent.contacts?.first {
|
||||
if let contact = startCallContacts.first {
|
||||
var processed = false
|
||||
if let handle = contact.customIdentifier, handle.hasPrefix("tg") {
|
||||
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()
|
||||
|> take(1)
|
||||
|> mapToSignal { sharedApplicationContext -> Signal<AuthorizedApplicationContext, NoError> in
|
||||
@ -1932,7 +1950,7 @@ final class SharedApplicationContext {
|
||||
}
|
||||
self.openChatWhenReadyDisposable.set((signal
|
||||
|> deliverOnMainQueue).start(next: { context in
|
||||
context.startCall(peerId: peerId)
|
||||
context.startCall(peerId: peerId, isVideo: isVideo)
|
||||
}))
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
return
|
||||
}
|
||||
@ -763,7 +763,7 @@ final class AuthorizedApplicationContext {
|
||||
guard let strongSelf = self else {
|
||||
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)
|
||||
}))
|
||||
}
|
||||
|
||||
|
@ -469,8 +469,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
self?.openUrl(url, concealed: false, message: nil)
|
||||
}, openPeer: { peer, navigation in
|
||||
self?.openPeer(peerId: peer.id, navigation: navigation, fromMessage: nil)
|
||||
}, callPeer: { peerId in
|
||||
self?.controllerInteraction?.callPeer(peerId)
|
||||
}, callPeer: { peerId, isVideo in
|
||||
self?.controllerInteraction?.callPeer(peerId, isVideo)
|
||||
}, enqueueMessage: { message in
|
||||
self?.sendMessages([message])
|
||||
}, sendSticker: canSendMessagesToChat(strongSelf.presentationInterfaceState) ? { fileReference, sourceNode, sourceRect in
|
||||
@ -1178,7 +1178,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
return self?.chatDisplayNode.reactionContainerNode
|
||||
}, presentGlobalOverlayController: { [weak self] controller, arguments in
|
||||
self?.presentInGlobalOverlay(controller, with: arguments)
|
||||
}, callPeer: { [weak self] peerId in
|
||||
}, callPeer: { [weak self] peerId, isVideo in
|
||||
if let strongSelf = self {
|
||||
strongSelf.commitPurposefulAction()
|
||||
|
||||
@ -1199,7 +1199,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
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 currentPeerId == peer.id {
|
||||
context.sharedContext.navigateToCurrentCall()
|
||||
@ -1211,7 +1211,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
|> deliverOnMainQueue).start(next: { peer, current in
|
||||
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: {
|
||||
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))
|
||||
}
|
||||
})
|
||||
@ -4254,9 +4254,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
self?.dismissPeerContactOptions()
|
||||
}, deleteChat: { [weak self] in
|
||||
self?.deleteChat(reportChatSpam: false)
|
||||
}, beginCall: { [weak self] in
|
||||
}, beginCall: { [weak self] isVideo in
|
||||
if let strongSelf = self, case let .peer(peerId) = strongSelf.chatLocation {
|
||||
strongSelf.controllerInteraction?.callPeer(peerId)
|
||||
strongSelf.controllerInteraction?.callPeer(peerId, isVideo)
|
||||
}
|
||||
}, toggleMessageStickerStarred: { [weak self] messageId in
|
||||
if let strongSelf = self, let message = strongSelf.chatDisplayNode.historyNode.messageInCurrentHistoryView(messageId) {
|
||||
|
@ -90,7 +90,7 @@ public final class ChatControllerInteraction {
|
||||
let chatControllerNode: () -> ASDisplayNode?
|
||||
let reactionContainerNode: () -> ReactionSelectionParentNode?
|
||||
let presentGlobalOverlayController: (ViewController, Any?) -> Void
|
||||
let callPeer: (PeerId) -> Void
|
||||
let callPeer: (PeerId, Bool) -> Void
|
||||
let longTap: (ChatControllerInteractionLongTapAction, Message?) -> Void
|
||||
let openCheckoutOrReceipt: (MessageId) -> Void
|
||||
let openSearch: () -> Void
|
||||
@ -136,7 +136,7 @@ public final class ChatControllerInteraction {
|
||||
var searchTextHighightState: (String, [MessageIndex])?
|
||||
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.openPeer = openPeer
|
||||
self.openPeerMention = openPeerMention
|
||||
@ -218,7 +218,7 @@ public final class ChatControllerInteraction {
|
||||
return nil
|
||||
}, reactionContainerNode: {
|
||||
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
|
||||
return .none
|
||||
}, navigateToFirstDateMessage: { _ in
|
||||
|
@ -223,7 +223,7 @@ final class ChatInfoTitlePanelNode: ChatTitleAccessoryPanelNode {
|
||||
case .search:
|
||||
self.interfaceInteraction?.beginMessageSearch(.everything, "")
|
||||
case .call:
|
||||
self.interfaceInteraction?.beginCall()
|
||||
self.interfaceInteraction?.beginCall(false)
|
||||
case .report:
|
||||
self.interfaceInteraction?.reportPeer()
|
||||
case .unarchive:
|
||||
|
@ -406,7 +406,7 @@ func contextMenuForChatPresentationIntefaceState(chatPresentationInterfaceState:
|
||||
if data.messageActions.options.contains(.rateCall) {
|
||||
var callId: CallId?
|
||||
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 let logName = callLogNameForId(id: id, account: context.account) {
|
||||
let logsPath = callLogsPath(account: context.account)
|
||||
|
@ -78,7 +78,7 @@ enum ChatMessageBubbleContentTapAction {
|
||||
case instantPage
|
||||
case wallpaper
|
||||
case theme
|
||||
case call(PeerId)
|
||||
case call(peerId: PeerId, isVideo: Bool)
|
||||
case openMessage
|
||||
case timecode(Double, String)
|
||||
case tooltip(String, ASDisplayNode?, CGRect?)
|
||||
|
@ -480,8 +480,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode
|
||||
return false
|
||||
}
|
||||
else if let media = media as? TelegramMediaAction {
|
||||
if case .phoneCall(_, _, _) = media.action {
|
||||
|
||||
if case .phoneCall(_, _, _, _) = media.action {
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
@ -2547,9 +2546,9 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode
|
||||
item.controllerInteraction.openTheme(item.message)
|
||||
})
|
||||
}
|
||||
case let .call(peerId):
|
||||
case let .call(peerId, isVideo):
|
||||
return .optionalAction({
|
||||
self.item?.controllerInteraction.callPeer(peerId)
|
||||
self.item?.controllerInteraction.callPeer(peerId, isVideo)
|
||||
})
|
||||
case .openMessage:
|
||||
if let item = self.item {
|
||||
|
@ -76,8 +76,10 @@ class ChatMessageCallBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
var titleString: String?
|
||||
var callDuration: Int32?
|
||||
var callSuccessful = true
|
||||
var isVideo = false
|
||||
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
|
||||
if let discardReason = discardReason {
|
||||
switch discardReason {
|
||||
@ -98,9 +100,17 @@ class ChatMessageCallBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
if titleString == nil {
|
||||
let baseString: String
|
||||
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 {
|
||||
baseString = item.presentationData.strings.Notification_CallOutgoing
|
||||
if isVideo {
|
||||
baseString = item.presentationData.strings.Notification_VideoCallOutgoing
|
||||
} else {
|
||||
baseString = item.presentationData.strings.Notification_CallOutgoing
|
||||
}
|
||||
}
|
||||
|
||||
titleString = baseString
|
||||
@ -203,7 +213,13 @@ class ChatMessageCallBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
|
||||
@objc func callButtonPressed() {
|
||||
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) {
|
||||
return .ignore
|
||||
} 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 {
|
||||
return .none
|
||||
}
|
||||
|
@ -616,7 +616,7 @@ final class ChatMessageAccessibilityData {
|
||||
canReply = false
|
||||
}
|
||||
else if let media = media as? TelegramMediaAction {
|
||||
if case .phoneCall(_, _, _) = media.action {
|
||||
if case .phoneCall = media.action {
|
||||
} else {
|
||||
canReply = false
|
||||
}
|
||||
|
@ -100,7 +100,7 @@ final class ChatPanelInterfaceInteraction {
|
||||
let presentPeerContact: () -> Void
|
||||
let dismissReportPeer: () -> Void
|
||||
let deleteChat: () -> Void
|
||||
let beginCall: () -> Void
|
||||
let beginCall: (Bool) -> Void
|
||||
let toggleMessageStickerStarred: (MessageId) -> Void
|
||||
let presentController: (ViewController, Any?) -> Void
|
||||
let getNavigationController: () -> NavigationController?
|
||||
@ -120,7 +120,7 @@ final class ChatPanelInterfaceInteraction {
|
||||
let displaySearchResultsTooltip: (ASDisplayNode, CGRect) -> Void
|
||||
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.setupEditMessage = setupEditMessage
|
||||
self.beginMessageSelection = beginMessageSelection
|
||||
|
@ -103,7 +103,7 @@ final class ChatRecentActionsController: TelegramBaseController {
|
||||
}, presentPeerContact: {
|
||||
}, dismissReportPeer: {
|
||||
}, deleteChat: {
|
||||
}, beginCall: {
|
||||
}, beginCall: { _ in
|
||||
}, toggleMessageStickerStarred: { _ in
|
||||
}, presentController: { _, _ in
|
||||
}, getNavigationController: {
|
||||
|
@ -180,8 +180,8 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
|
||||
self?.openUrl(url)
|
||||
}, openPeer: { peer, navigation in
|
||||
self?.openPeer(peerId: peer.id, peer: peer)
|
||||
}, callPeer: { peerId in
|
||||
self?.controllerInteraction?.callPeer(peerId)
|
||||
}, callPeer: { peerId, isVideo in
|
||||
self?.controllerInteraction?.callPeer(peerId, isVideo)
|
||||
}, enqueueMessage: { _ in
|
||||
}, sendSticker: nil, setupTemporaryHiddenMedia: { _, _, _ in }, chatAvatarHiddenMedia: { _, _ in }))
|
||||
}
|
||||
@ -242,7 +242,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
|
||||
return self
|
||||
}, reactionContainerNode: {
|
||||
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 {
|
||||
switch action {
|
||||
case let .url(url):
|
||||
|
@ -115,7 +115,7 @@ private final class DrawingStickersScreenNode: ViewControllerTracingNode {
|
||||
return nil
|
||||
}, reactionContainerNode: {
|
||||
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
|
||||
return .none
|
||||
}, navigateToFirstDateMessage: { _ in
|
||||
|
@ -98,7 +98,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, UIGestu
|
||||
}, reactionContainerNode: {
|
||||
return nil
|
||||
}, presentGlobalOverlayController: { _, _ in
|
||||
}, callPeer: { _ in
|
||||
}, callPeer: { _, _ in
|
||||
}, longTap: { _, _ in
|
||||
}, openCheckoutOrReceipt: { _ in
|
||||
}, openSearch: {
|
||||
@ -234,7 +234,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, UIGestu
|
||||
|
||||
openMessageImpl = { [weak self] id in
|
||||
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
|
||||
}
|
||||
|
@ -704,7 +704,7 @@ func availableActionsForMemberOfPeer(accountPeerId: PeerId, peer: Peer, member:
|
||||
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] = []
|
||||
if let user = peer as? TelegramUser {
|
||||
if !isOpenedFromChat {
|
||||
@ -719,6 +719,9 @@ func peerInfoHeaderButtons(peer: Peer?, cachedData: CachedPeerData?, isOpenedFro
|
||||
}
|
||||
if callsAvailable {
|
||||
result.append(.call)
|
||||
if videoCallsEnabled {
|
||||
result.append(.videoCall)
|
||||
}
|
||||
}
|
||||
result.append(.mute)
|
||||
if isOpenedFromChat {
|
||||
|
@ -21,6 +21,7 @@ enum PeerInfoHeaderButtonKey: Hashable {
|
||||
case message
|
||||
case discussion
|
||||
case call
|
||||
case videoCall
|
||||
case mute
|
||||
case more
|
||||
case addMember
|
||||
@ -31,6 +32,7 @@ enum PeerInfoHeaderButtonKey: Hashable {
|
||||
enum PeerInfoHeaderButtonIcon {
|
||||
case message
|
||||
case call
|
||||
case videoCall
|
||||
case mute
|
||||
case unmute
|
||||
case more
|
||||
@ -103,6 +105,8 @@ final class PeerInfoHeaderButtonNode: HighlightableButtonNode {
|
||||
imageName = "Peer Info/ButtonMessage"
|
||||
case .call:
|
||||
imageName = "Peer Info/ButtonCall"
|
||||
case .videoCall:
|
||||
imageName = "Chat/Input/Text/IconVideo"
|
||||
case .mute:
|
||||
imageName = "Peer Info/ButtonMute"
|
||||
case .unmute:
|
||||
@ -116,7 +120,7 @@ final class PeerInfoHeaderButtonNode: HighlightableButtonNode {
|
||||
case .leave:
|
||||
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)
|
||||
context.clip(to: imageRect, mask: image.cgImage!)
|
||||
context.fill(imageRect)
|
||||
@ -1650,6 +1654,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
||||
private var presentationData: PresentationData?
|
||||
|
||||
private let isOpenedFromChat: Bool
|
||||
private let videoCallsEnabled: Bool
|
||||
|
||||
private(set) var isAvatarExpanded: Bool
|
||||
|
||||
@ -1682,6 +1687,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
||||
self.context = context
|
||||
self.isAvatarExpanded = avatarInitiallyExpanded
|
||||
self.isOpenedFromChat = isOpenedFromChat
|
||||
self.videoCallsEnabled = context.sharedContext.immediateExperimentalUISettings.videoCalls
|
||||
|
||||
self.avatarListNode = PeerInfoAvatarListNode(context: context, readyWhenGalleryLoads: avatarInitiallyExpanded)
|
||||
|
||||
@ -1868,7 +1874,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
||||
let expandedAvatarListHeight = min(width, containerHeight - expandedAvatarControlsHeight)
|
||||
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
|
||||
let titleString: NSAttributedString
|
||||
@ -2239,6 +2245,9 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
||||
case .call:
|
||||
buttonText = presentationData.strings.PeerInfo_ButtonCall
|
||||
buttonIcon = .call
|
||||
case .videoCall:
|
||||
buttonText = presentationData.strings.PeerInfo_ButtonVideoCall
|
||||
buttonIcon = .videoCall
|
||||
case .mute:
|
||||
if let notificationSettings = notificationSettings, case .muted = notificationSettings.muteState {
|
||||
buttonText = presentationData.strings.PeerInfo_ButtonUnmute
|
||||
|
@ -395,7 +395,7 @@ final class PeerInfoSelectionPanelNode: ASDisplayNode {
|
||||
}, presentPeerContact: {
|
||||
}, dismissReportPeer: {
|
||||
}, deleteChat: {
|
||||
}, beginCall: {
|
||||
}, beginCall: { _ in
|
||||
}, toggleMessageStickerStarred: { _ in
|
||||
}, presentController: { _, _ in
|
||||
}, getNavigationController: {
|
||||
@ -1035,6 +1035,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
|
||||
private let context: AccountContext
|
||||
private let peerId: PeerId
|
||||
private let isOpenedFromChat: Bool
|
||||
private let videoCallsEnabled: Bool
|
||||
private let callMessages: [Message]
|
||||
|
||||
private let isMediaOnly: Bool
|
||||
@ -1096,6 +1097,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
|
||||
self.context = context
|
||||
self.peerId = peerId
|
||||
self.isOpenedFromChat = isOpenedFromChat
|
||||
self.videoCallsEnabled = context.sharedContext.immediateExperimentalUISettings.videoCalls
|
||||
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
self.nearbyPeerDistance = nearbyPeerDistance
|
||||
self.callMessages = callMessages
|
||||
@ -1529,7 +1531,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
|
||||
return nil
|
||||
}, reactionContainerNode: {
|
||||
return nil
|
||||
}, presentGlobalOverlayController: { _, _ in }, callPeer: { _ in
|
||||
}, presentGlobalOverlayController: { _, _ in }, callPeer: { _, _ in
|
||||
}, longTap: { [weak self] content, _ in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
@ -2133,7 +2135,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
|
||||
self?.openUrl(url: url, concealed: false, external: false)
|
||||
}, openPeer: { [weak self] peer, navigation in
|
||||
self?.openPeer(peerId: peer.id, navigation: navigation)
|
||||
}, callPeer: { peerId in
|
||||
}, callPeer: { peerId, isVideo in
|
||||
//self?.controllerInteraction?.callPeer(peerId)
|
||||
}, enqueueMessage: { _ in
|
||||
}, sendSticker: nil, setupTemporaryHiddenMedia: { _, _, _ in }, chatAvatarHiddenMedia: { _, _ in }))
|
||||
@ -2213,7 +2215,9 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
|
||||
}
|
||||
}
|
||||
case .call:
|
||||
self.requestCall()
|
||||
self.requestCall(isVideo: false)
|
||||
case .videoCall:
|
||||
self.requestCall(isVideo: true)
|
||||
case .mute:
|
||||
if let notificationSettings = self.data?.notificationSettings, case .muted = notificationSettings.muteState {
|
||||
let _ = updatePeerMuteSetting(account: self.context.account, peerId: self.peerId, muteInterval: nil).start()
|
||||
@ -2259,7 +2263,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
|
||||
actionSheet?.dismissAnimated()
|
||||
}
|
||||
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
|
||||
dismissAction()
|
||||
self?.openChatWithMessageSearch()
|
||||
@ -2361,7 +2365,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
|
||||
self?.openDeletePeer()
|
||||
}))
|
||||
} 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 {
|
||||
items.append(ActionSheetButtonItem(title: presentationData.strings.Channel_LeaveChannel, color: .destructive, action: { [weak self] in
|
||||
dismissAction()
|
||||
@ -2510,7 +2514,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
|
||||
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 {
|
||||
return
|
||||
}
|
||||
@ -2519,7 +2523,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
|
||||
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 currentPeerId == peer.id {
|
||||
self.context.sharedContext.navigateToCurrentCall()
|
||||
@ -2536,7 +2540,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
|
||||
guard let strongSelf = self else {
|
||||
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))
|
||||
}
|
||||
})
|
||||
@ -2559,7 +2563,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
|
||||
ActionSheetItemGroup(items: [
|
||||
ActionSheetButtonItem(title: strongSelf.presentationData.strings.UserInfo_TelegramCall, action: {
|
||||
dismissAction()
|
||||
self?.requestCall()
|
||||
self?.requestCall(isVideo: false)
|
||||
}),
|
||||
ActionSheetButtonItem(title: strongSelf.presentationData.strings.UserInfo_PhoneCall, action: {
|
||||
dismissAction()
|
||||
|
@ -127,8 +127,8 @@ public class PeerMediaCollectionController: TelegramBaseController {
|
||||
self?.openUrl(url)
|
||||
}, openPeer: { peer, navigation in
|
||||
self?.controllerInteraction?.openPeer(peer.id, navigation, nil)
|
||||
}, callPeer: { peerId in
|
||||
self?.controllerInteraction?.callPeer(peerId)
|
||||
}, callPeer: { peerId, isVideo in
|
||||
self?.controllerInteraction?.callPeer(peerId, isVideo)
|
||||
}, enqueueMessage: { _ in
|
||||
}, sendSticker: nil, setupTemporaryHiddenMedia: { _, _, _ in }, chatAvatarHiddenMedia: { _, _ in }))
|
||||
}
|
||||
@ -357,7 +357,7 @@ public class PeerMediaCollectionController: TelegramBaseController {
|
||||
return nil
|
||||
}, reactionContainerNode: {
|
||||
return nil
|
||||
}, presentGlobalOverlayController: { _, _ in }, callPeer: { _ in
|
||||
}, presentGlobalOverlayController: { _, _ in }, callPeer: { _, _ in
|
||||
}, longTap: { [weak self] content, _ in
|
||||
if let strongSelf = self {
|
||||
strongSelf.view.endEditing(true)
|
||||
@ -530,7 +530,7 @@ public class PeerMediaCollectionController: TelegramBaseController {
|
||||
}, presentPeerContact: {
|
||||
}, dismissReportPeer: {
|
||||
}, deleteChat: {
|
||||
}, beginCall: {
|
||||
}, beginCall: { _ in
|
||||
}, toggleMessageStickerStarred: { _ in
|
||||
}, presentController: { _, _ in
|
||||
}, getNavigationController: {
|
||||
|
@ -537,7 +537,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
||||
})
|
||||
|
||||
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
|
||||
self?.presentGlobalController(c, a)
|
||||
}, {
|
||||
@ -1120,7 +1120,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
||||
return nil
|
||||
}, reactionContainerNode: {
|
||||
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
|
||||
return .none
|
||||
}, navigateToFirstDateMessage: { _ in
|
||||
|
@ -100,13 +100,22 @@ public struct OngoingCallContextState: Equatable {
|
||||
case reconnecting
|
||||
case failed
|
||||
}
|
||||
|
||||
public enum VideoState: Equatable {
|
||||
case notAvailable
|
||||
case available(Bool)
|
||||
case active
|
||||
case activeOutgoing
|
||||
}
|
||||
|
||||
public enum RemoteVideoState: Equatable {
|
||||
case inactive
|
||||
case active
|
||||
}
|
||||
|
||||
public let state: State
|
||||
public let videoState: VideoState
|
||||
public let remoteVideoState: RemoteVideoState
|
||||
}
|
||||
|
||||
private final class OngoingCallThreadLocalContextQueueImpl: NSObject, OngoingCallThreadLocalContextQueue, OngoingCallThreadLocalContextQueueWebrtc /*, OngoingCallThreadLocalContextQueueWebrtcCustom*/ {
|
||||
@ -395,6 +404,27 @@ private extension OngoingCallContextState.State {
|
||||
}*/
|
||||
|
||||
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
|
||||
|
||||
private let queue = Queue()
|
||||
@ -433,7 +463,7 @@ public final class OngoingCallContext {
|
||||
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
|
||||
OngoingCallThreadLocalContext.applyServerConfig(serializedData)
|
||||
//OngoingCallThreadLocalContextWebrtc.applyServerConfig(serializedData)
|
||||
@ -491,12 +521,33 @@ public final class OngoingCallContext {
|
||||
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)
|
||||
})
|
||||
|
||||
strongSelf.contextRef = Unmanaged.passRetained(OngoingCallThreadLocalContextHolder(context))
|
||||
context.stateChanged = { state, videoState in
|
||||
context.stateChanged = { state, videoState, remoteVideoState in
|
||||
queue.async {
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
@ -508,12 +559,21 @@ public final class OngoingCallContext {
|
||||
mappedVideoState = .available(true)
|
||||
case .active:
|
||||
mappedVideoState = .active
|
||||
case .invited, .requesting:
|
||||
mappedVideoState = .available(false)
|
||||
case .activeOutgoing:
|
||||
mappedVideoState = .activeOutgoing
|
||||
@unknown default:
|
||||
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
|
||||
@ -540,7 +600,7 @@ public final class OngoingCallContext {
|
||||
|
||||
strongSelf.contextRef = Unmanaged.passRetained(OngoingCallThreadLocalContextHolder(context))
|
||||
context.stateChanged = { state in
|
||||
self?.contextState.set(.single(OngoingCallContextState(state: OngoingCallContextState.State(state), videoState: .notAvailable)))
|
||||
self?.contextState.set(.single(OngoingCallContextState(state: OngoingCallContextState.State(state), videoState: .notAvailable, remoteVideoState: .inactive)))
|
||||
}
|
||||
context.signalBarsChanged = { signalBars in
|
||||
self?.receptionPromise.set(.single(signalBars))
|
||||
|
@ -20,7 +20,7 @@ std::unique_ptr<webrtc::VideoEncoderFactory> makeVideoEncoderFactory();
|
||||
std::unique_ptr<webrtc::VideoDecoderFactory> makeVideoDecoderFactory();
|
||||
bool supportsH265Encoding();
|
||||
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
|
||||
}
|
||||
|
@ -40,12 +40,12 @@
|
||||
|
||||
@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];
|
||||
if (self != nil) {
|
||||
assert([NSThread isMainThread]);
|
||||
|
||||
_videoCapturer = [[VideoCameraCapturer alloc] initWithSource:source];
|
||||
_videoCapturer = [[VideoCameraCapturer alloc] initWithSource:source isActiveUpdated:isActiveUpdated];
|
||||
|
||||
AVCaptureDevice *frontCamera = nil;
|
||||
AVCaptureDevice *backCamera = nil;
|
||||
@ -130,12 +130,14 @@ namespace TGVOIP_NAMESPACE {
|
||||
|
||||
class VideoCapturerInterfaceImpl: public VideoCapturerInterface {
|
||||
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) {
|
||||
_implReference = [[VideoCapturerInterfaceImplHolder alloc] init];
|
||||
VideoCapturerInterfaceImplHolder *implReference = _implReference;
|
||||
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) {
|
||||
implReference.reference = (void *)CFBridgingRetain(value);
|
||||
}
|
||||
@ -185,8 +187,8 @@ rtc::scoped_refptr<webrtc::VideoTrackSourceInterface> makeVideoSource(rtc::Threa
|
||||
return webrtc::VideoTrackSourceProxy::Create(signalingThread, workerThread, objCVideoTrackSource);
|
||||
}
|
||||
|
||||
std::unique_ptr<VideoCapturerInterface> makeVideoCapturer(rtc::scoped_refptr<webrtc::VideoTrackSourceInterface> source, bool useFrontCamera) {
|
||||
return std::make_unique<VideoCapturerInterfaceImpl>(source, 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, isActiveUpdated);
|
||||
}
|
||||
|
||||
#ifdef TGVOIP_NAMESPACE
|
||||
|
@ -36,15 +36,21 @@ Manager::Manager(
|
||||
rtc::Thread *thread,
|
||||
TgVoipEncryptionKey encryptionKey,
|
||||
bool enableP2P,
|
||||
std::vector<TgVoipRtcServer> const &rtcServers,
|
||||
bool isVideo,
|
||||
std::function<void (const TgVoipState &)> stateUpdated,
|
||||
std::function<void (bool)> videoStateUpdated,
|
||||
std::function<void (bool)> remoteVideoIsActiveUpdated,
|
||||
std::function<void (const std::vector<uint8_t> &)> signalingDataEmitted
|
||||
) :
|
||||
_thread(thread),
|
||||
_encryptionKey(encryptionKey),
|
||||
_enableP2P(enableP2P),
|
||||
_rtcServers(rtcServers),
|
||||
_startWithVideo(isVideo),
|
||||
_stateUpdated(stateUpdated),
|
||||
_videoStateUpdated(videoStateUpdated),
|
||||
_remoteVideoIsActiveUpdated(remoteVideoIsActiveUpdated),
|
||||
_signalingDataEmitted(signalingDataEmitted),
|
||||
_isVideoRequested(false) {
|
||||
assert(_thread->IsCurrent());
|
||||
@ -56,11 +62,12 @@ Manager::~Manager() {
|
||||
|
||||
void Manager::start() {
|
||||
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(
|
||||
getNetworkThread(),
|
||||
encryptionKey,
|
||||
enableP2P,
|
||||
rtcServers,
|
||||
[thread, weakThis](const NetworkManager::State &state) {
|
||||
thread->PostTask(RTC_FROM_HERE, [weakThis, state]() {
|
||||
auto strongThis = weakThis.lock();
|
||||
@ -104,10 +111,11 @@ void Manager::start() {
|
||||
);
|
||||
}));
|
||||
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(
|
||||
getMediaThread(),
|
||||
isOutgoing,
|
||||
startWithVideo,
|
||||
[thread, weakThis](const rtc::CopyOnWriteBuffer &packet) {
|
||||
thread->PostTask(RTC_FROM_HERE, [weakThis, packet]() {
|
||||
auto strongThis = weakThis.lock();
|
||||
@ -118,6 +126,15 @@ void Manager::start() {
|
||||
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->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;
|
||||
uint8_t mode = 1;
|
||||
buffer.AppendData(&mode, 1);
|
||||
|
||||
std::vector<uint8_t> data;
|
||||
data.resize(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() {
|
||||
_mediaManager->perform([](MediaManager *mediaManager) {
|
||||
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) {
|
||||
_mediaManager->perform([sink](MediaManager *mediaManager) {
|
||||
mediaManager->setIncomingVideoOutput(sink);
|
||||
|
@ -16,8 +16,11 @@ public:
|
||||
rtc::Thread *thread,
|
||||
TgVoipEncryptionKey encryptionKey,
|
||||
bool enableP2P,
|
||||
std::vector<TgVoipRtcServer> const &rtcServers,
|
||||
bool isVideo,
|
||||
std::function<void (const TgVoipState &)> stateUpdated,
|
||||
std::function<void (bool)> videoStateUpdated,
|
||||
std::function<void (bool)> remoteVideoIsActiveUpdated,
|
||||
std::function<void (const std::vector<uint8_t> &)> signalingDataEmitted
|
||||
);
|
||||
~Manager();
|
||||
@ -25,7 +28,9 @@ public:
|
||||
void start();
|
||||
void receiveSignalingData(const std::vector<uint8_t> &data);
|
||||
void setSendVideo(bool sendVideo);
|
||||
void setMuteOutgoingAudio(bool mute);
|
||||
void switchVideoCamera();
|
||||
void notifyIsLocalVideoActive(bool isActive);
|
||||
void setIncomingVideoOutput(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;
|
||||
TgVoipEncryptionKey _encryptionKey;
|
||||
bool _enableP2P;
|
||||
std::vector<TgVoipRtcServer> _rtcServers;
|
||||
bool _startWithVideo;
|
||||
std::function<void (const TgVoipState &)> _stateUpdated;
|
||||
std::function<void (bool)> _videoStateUpdated;
|
||||
std::function<void (bool)> _remoteVideoIsActiveUpdated;
|
||||
std::function<void (const std::vector<uint8_t> &)> _signalingDataEmitted;
|
||||
std::unique_ptr<ThreadLocalObject<NetworkManager>> _networkManager;
|
||||
std::unique_ptr<ThreadLocalObject<MediaManager>> _mediaManager;
|
||||
|
@ -172,9 +172,12 @@ static rtc::Thread *getWorkerThread() {
|
||||
MediaManager::MediaManager(
|
||||
rtc::Thread *thread,
|
||||
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),
|
||||
_localVideoCaptureActiveUpdated(localVideoCaptureActiveUpdated),
|
||||
_thread(thread),
|
||||
_eventLog(std::make_unique<webrtc::RtcEventLogNull>()),
|
||||
_taskQueueFactory(webrtc::CreateDefaultTaskQueueFactory()) {
|
||||
@ -190,6 +193,7 @@ _taskQueueFactory(webrtc::CreateDefaultTaskQueueFactory()) {
|
||||
_enableFlexfec = true;
|
||||
|
||||
_isConnected = false;
|
||||
_muteOutgoingAudio = false;
|
||||
|
||||
auto videoEncoderFactory = makeVideoEncoderFactory();
|
||||
_videoCodecs = AssignPayloadTypesAndDefaultCodecs(videoEncoderFactory->GetSupportedFormats());
|
||||
@ -280,6 +284,10 @@ _taskQueueFactory(webrtc::CreateDefaultTaskQueueFactory()) {
|
||||
_videoChannel->SetInterface(_videoNetworkInterface.get(), webrtc::MediaTransportConfig());
|
||||
|
||||
_nativeVideoSource = makeVideoSource(_thread, getWorkerThread());
|
||||
|
||||
if (startWithVideo) {
|
||||
setSendVideo(true);
|
||||
}
|
||||
}
|
||||
|
||||
MediaManager::~MediaManager() {
|
||||
@ -318,7 +326,7 @@ void MediaManager::setIsConnected(bool isConnected) {
|
||||
if (_audioChannel) {
|
||||
_audioChannel->OnReadyToSend(_isConnected);
|
||||
_audioChannel->SetSend(_isConnected);
|
||||
_audioChannel->SetAudioSend(_ssrcAudio.outgoing, _isConnected, nullptr, &_audioSource);
|
||||
_audioChannel->SetAudioSend(_ssrcAudio.outgoing, _isConnected && !_muteOutgoingAudio, nullptr, &_audioSource);
|
||||
}
|
||||
if (_isSendingVideo && _videoChannel) {
|
||||
_videoChannel->OnReadyToSend(_isConnected);
|
||||
@ -364,7 +372,9 @@ void MediaManager::setSendVideo(bool sendVideo) {
|
||||
codec.SetParam(cricket::kCodecParamStartBitrate, 512);
|
||||
codec.SetParam(cricket::kCodecParamMaxBitrate, 2500);
|
||||
|
||||
_videoCapturer = makeVideoCapturer(_nativeVideoSource, _useFrontCamera);
|
||||
_videoCapturer = makeVideoCapturer(_nativeVideoSource, _useFrontCamera, [localVideoCaptureActiveUpdated = _localVideoCaptureActiveUpdated](bool isActive) {
|
||||
localVideoCaptureActiveUpdated(isActive);
|
||||
});
|
||||
|
||||
cricket::VideoSendParameters videoSendParameters;
|
||||
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() {
|
||||
if (_isSendingVideo) {
|
||||
_useFrontCamera = !_useFrontCamera;
|
||||
_videoCapturer = makeVideoCapturer(_nativeVideoSource, _useFrontCamera);
|
||||
_videoCapturer = makeVideoCapturer(_nativeVideoSource, _useFrontCamera, [localVideoCaptureActiveUpdated = _localVideoCaptureActiveUpdated](bool isActive) {
|
||||
localVideoCaptureActiveUpdated(isActive);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -57,7 +57,9 @@ public:
|
||||
MediaManager(
|
||||
rtc::Thread *thread,
|
||||
bool isOutgoing,
|
||||
std::function<void (const rtc::CopyOnWriteBuffer &)> packetEmitted
|
||||
bool startWithVideo,
|
||||
std::function<void (const rtc::CopyOnWriteBuffer &)> packetEmitted,
|
||||
std::function<void (bool)> localVideoCaptureActiveUpdated
|
||||
);
|
||||
~MediaManager();
|
||||
|
||||
@ -65,12 +67,14 @@ public:
|
||||
void receivePacket(const rtc::CopyOnWriteBuffer &packet);
|
||||
void notifyPacketSent(const rtc::SentPacket &sentPacket);
|
||||
void setSendVideo(bool sendVideo);
|
||||
void setMuteOutgoingAudio(bool mute);
|
||||
void switchVideoCamera();
|
||||
void setIncomingVideoOutput(std::shared_ptr<rtc::VideoSinkInterface<webrtc::VideoFrame>> sink);
|
||||
void setOutgoingVideoOutput(std::shared_ptr<rtc::VideoSinkInterface<webrtc::VideoFrame>> sink);
|
||||
|
||||
protected:
|
||||
std::function<void (const rtc::CopyOnWriteBuffer &)> _packetEmitted;
|
||||
std::function<void (bool)> _localVideoCaptureActiveUpdated;
|
||||
|
||||
private:
|
||||
rtc::Thread *_thread;
|
||||
@ -82,6 +86,7 @@ private:
|
||||
bool _enableFlexfec;
|
||||
|
||||
bool _isConnected;
|
||||
bool _muteOutgoingAudio;
|
||||
|
||||
std::vector<cricket::VideoCodec> _videoCodecs;
|
||||
bool _isSendingVideo;
|
||||
|
@ -154,6 +154,7 @@ NetworkManager::NetworkManager(
|
||||
rtc::Thread *thread,
|
||||
TgVoipEncryptionKey encryptionKey,
|
||||
bool enableP2P,
|
||||
std::vector<TgVoipRtcServer> const &rtcServers,
|
||||
std::function<void (const NetworkManager::State &)> stateUpdated,
|
||||
std::function<void (const rtc::CopyOnWriteBuffer &)> packetReceived,
|
||||
std::function<void (const std::vector<uint8_t> &)> signalingDataEmitted
|
||||
@ -178,16 +179,35 @@ _signalingDataEmitted(signalingDataEmitted) {
|
||||
_portAllocator->set_flags(_portAllocator->flags() | flags);
|
||||
_portAllocator->Initialize();
|
||||
|
||||
rtc::SocketAddress defaultStunAddress = rtc::SocketAddress("134.122.52.178", 3478);
|
||||
cricket::ServerAddresses stunServers;
|
||||
stunServers.insert(defaultStunAddress);
|
||||
std::vector<cricket::RelayServerConfig> turnServers;
|
||||
turnServers.push_back(cricket::RelayServerConfig(
|
||||
rtc::SocketAddress("134.122.52.178", 3478),
|
||||
"openrelay",
|
||||
"openrelay",
|
||||
cricket::PROTO_UDP
|
||||
));
|
||||
|
||||
if (rtcServers.size() == 0) {
|
||||
rtc::SocketAddress defaultStunAddress = rtc::SocketAddress("134.122.52.178", 3478);
|
||||
stunServers.insert(defaultStunAddress);
|
||||
|
||||
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);
|
||||
|
||||
_asyncResolverFactory = std::make_unique<webrtc::BasicAsyncResolverFactory>();
|
||||
|
@ -41,6 +41,7 @@ public:
|
||||
rtc::Thread *thread,
|
||||
TgVoipEncryptionKey encryptionKey,
|
||||
bool enableP2P,
|
||||
std::vector<TgVoipRtcServer> const &rtcServers,
|
||||
std::function<void (const NetworkManager::State &)> stateUpdated,
|
||||
std::function<void (const rtc::CopyOnWriteBuffer &)> packetReceived,
|
||||
std::function<void (const std::vector<uint8_t> &)> signalingDataEmitted
|
||||
|
@ -26,6 +26,14 @@ struct TgVoipProxy {
|
||||
std::string password;
|
||||
};
|
||||
|
||||
struct TgVoipRtcServer {
|
||||
std::string host;
|
||||
uint16_t port;
|
||||
std::string login;
|
||||
std::string password;
|
||||
bool isTurn;
|
||||
};
|
||||
|
||||
enum class TgVoipEndpointType {
|
||||
Inet,
|
||||
Lan,
|
||||
@ -135,10 +143,13 @@ public:
|
||||
TgVoipPersistentState const &persistentState,
|
||||
std::vector<TgVoipEndpoint> const &endpoints,
|
||||
std::unique_ptr<TgVoipProxy> const &proxy,
|
||||
std::vector<TgVoipRtcServer> const &rtcServers,
|
||||
TgVoipNetworkType initialNetworkType,
|
||||
TgVoipEncryptionKey const &encryptionKey,
|
||||
bool isVideo,
|
||||
std::function<void(TgVoipState)> stateUpdated,
|
||||
std::function<void(bool)> videoStateUpdated,
|
||||
std::function<void(bool)> remoteVideoIsActiveUpdated,
|
||||
std::function<void(const std::vector<uint8_t> &)> signalingDataEmitted
|
||||
);
|
||||
|
||||
|
@ -139,11 +139,14 @@ public:
|
||||
std::vector<TgVoipEndpoint> const &endpoints,
|
||||
TgVoipPersistentState const &persistentState,
|
||||
std::unique_ptr<TgVoipProxy> const &proxy,
|
||||
std::vector<TgVoipRtcServer> const &rtcServers,
|
||||
TgVoipConfig const &config,
|
||||
TgVoipEncryptionKey const &encryptionKey,
|
||||
bool isVideo,
|
||||
TgVoipNetworkType initialNetworkType,
|
||||
std::function<void(TgVoipState)> stateUpdated,
|
||||
std::function<void(bool)> videoStateUpdated,
|
||||
std::function<void(bool)> remoteVideoIsActiveUpdated,
|
||||
std::function<void(const std::vector<uint8_t> &)> signalingDataEmitted
|
||||
) :
|
||||
_stateUpdated(stateUpdated),
|
||||
@ -157,17 +160,22 @@ public:
|
||||
|
||||
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(
|
||||
getManagerThread(),
|
||||
encryptionKey,
|
||||
enableP2P,
|
||||
rtcServers,
|
||||
isVideo,
|
||||
[stateUpdated](const TgVoipState &state) {
|
||||
stateUpdated(state);
|
||||
},
|
||||
[videoStateUpdated](bool isActive) {
|
||||
videoStateUpdated(isActive);
|
||||
},
|
||||
[remoteVideoIsActiveUpdated](bool isActive) {
|
||||
remoteVideoIsActiveUpdated(isActive);
|
||||
},
|
||||
[signalingDataEmitted](const std::vector<uint8_t> &data) {
|
||||
signalingDataEmitted(data);
|
||||
}
|
||||
@ -249,7 +257,9 @@ public:
|
||||
}
|
||||
|
||||
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 {
|
||||
@ -374,21 +384,27 @@ TgVoip *TgVoip::makeInstance(
|
||||
TgVoipPersistentState const &persistentState,
|
||||
std::vector<TgVoipEndpoint> const &endpoints,
|
||||
std::unique_ptr<TgVoipProxy> const &proxy,
|
||||
std::vector<TgVoipRtcServer> const &rtcServers,
|
||||
TgVoipNetworkType initialNetworkType,
|
||||
TgVoipEncryptionKey const &encryptionKey,
|
||||
bool isVideo,
|
||||
std::function<void(TgVoipState)> stateUpdated,
|
||||
std::function<void(bool)> videoStateUpdated,
|
||||
std::function<void(bool)> remoteVideoIsActiveUpdated,
|
||||
std::function<void(const std::vector<uint8_t> &)> signalingDataEmitted
|
||||
) {
|
||||
return new TgVoipImpl(
|
||||
endpoints,
|
||||
persistentState,
|
||||
proxy,
|
||||
rtcServers,
|
||||
config,
|
||||
encryptionKey,
|
||||
isVideo,
|
||||
initialNetworkType,
|
||||
stateUpdated,
|
||||
videoStateUpdated,
|
||||
remoteVideoIsActiveUpdated,
|
||||
signalingDataEmitted
|
||||
);
|
||||
}
|
||||
|
@ -13,7 +13,7 @@
|
||||
+ (NSArray<AVCaptureDevice *> *)captureDevices;
|
||||
+ (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)stopCapture;
|
||||
|
@ -37,16 +37,20 @@ static webrtc::ObjCVideoTrackSource *getObjCVideoSource(const rtc::scoped_refptr
|
||||
FourCharCode _outputPixelFormat;
|
||||
RTCVideoRotation _rotation;
|
||||
UIDeviceOrientation _orientation;
|
||||
|
||||
void (^_isActiveUpdated)(bool);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@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];
|
||||
if (self != nil) {
|
||||
_source = source;
|
||||
_isActiveUpdated = [isActiveUpdated copy];
|
||||
|
||||
if (![self setupCaptureSession:[[AVCaptureSession alloc] init]]) {
|
||||
return nil;
|
||||
}
|
||||
@ -310,10 +314,17 @@ static webrtc::ObjCVideoTrackSource *getObjCVideoSource(const rtc::scoped_refptr
|
||||
// allow future retries on fatal errors.
|
||||
_hasRetriedOnFatalError = NO;
|
||||
}];
|
||||
|
||||
if (_isActiveUpdated) {
|
||||
_isActiveUpdated(true);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)handleCaptureSessionDidStopRunning:(NSNotification *)notification {
|
||||
RTCLog(@"Capture session stopped.");
|
||||
if (_isActiveUpdated) {
|
||||
_isActiveUpdated(false);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)handleFatalError {
|
||||
|
@ -25,11 +25,15 @@ typedef NS_ENUM(int32_t, OngoingCallStateWebrtc) {
|
||||
|
||||
typedef NS_ENUM(int32_t, OngoingCallVideoStateWebrtc) {
|
||||
OngoingCallVideoStateInactive,
|
||||
OngoingCallVideoStateRequesting,
|
||||
OngoingCallVideoStateInvited,
|
||||
OngoingCallVideoStateActiveOutgoing,
|
||||
OngoingCallVideoStateActive
|
||||
};
|
||||
|
||||
typedef NS_ENUM(int32_t, OngoingCallRemoteVideoStateWebrtc) {
|
||||
OngoingCallRemoteVideoStateInactive,
|
||||
OngoingCallRemoteVideoStateActive
|
||||
};
|
||||
|
||||
typedef NS_ENUM(int32_t, OngoingCallNetworkTypeWebrtc) {
|
||||
OngoingCallNetworkTypeWifi,
|
||||
OngoingCallNetworkTypeCellularGprs,
|
||||
@ -62,6 +66,18 @@ typedef NS_ENUM(int32_t, OngoingCallDataSavingWebrtc) {
|
||||
|
||||
@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
|
||||
|
||||
+ (void)setupLoggingFunction:(void (* _Nullable)(NSString * _Nullable))loggingFunction;
|
||||
@ -69,10 +85,10 @@ typedef NS_ENUM(int32_t, OngoingCallDataSavingWebrtc) {
|
||||
+ (int32_t)maxLayer;
|
||||
+ (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);
|
||||
|
||||
- (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;
|
||||
|
||||
- (bool)needRate;
|
||||
|
@ -35,6 +35,7 @@ using namespace TGVOIP_NAMESPACE;
|
||||
|
||||
OngoingCallStateWebrtc _state;
|
||||
OngoingCallVideoStateWebrtc _videoState;
|
||||
OngoingCallRemoteVideoStateWebrtc _remoteVideoState;
|
||||
|
||||
int32_t _signalBars;
|
||||
NSData *_lastDerivedState;
|
||||
@ -62,6 +63,22 @@ using namespace TGVOIP_NAMESPACE;
|
||||
|
||||
@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) {
|
||||
switch (type) {
|
||||
case OngoingCallNetworkTypeWifi:
|
||||
@ -117,7 +134,7 @@ static void (*InternalVoipLoggingFunction)(NSString *) = NULL;
|
||||
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];
|
||||
if (self != nil) {
|
||||
_queue = queue;
|
||||
@ -129,7 +146,13 @@ static void (*InternalVoipLoggingFunction)(NSString *) = NULL;
|
||||
_callPacketTimeout = 10.0;
|
||||
_networkType = networkType;
|
||||
_sendSignalingData = [sendSignalingData copy];
|
||||
_videoState = OngoingCallVideoStateInactive;
|
||||
if (isVideo) {
|
||||
_videoState = OngoingCallVideoStateActiveOutgoing;
|
||||
_remoteVideoState = OngoingCallRemoteVideoStateActive;
|
||||
} else {
|
||||
_videoState = OngoingCallVideoStateInactive;
|
||||
_remoteVideoState = OngoingCallRemoteVideoStateInactive;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> derivedStateValue;
|
||||
derivedStateValue.resize(derivedState.length);
|
||||
@ -145,6 +168,17 @@ static void (*InternalVoipLoggingFunction)(NSString *) = NULL;
|
||||
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;
|
||||
crypto.sha1 = &TGCallSha1;
|
||||
crypto.sha256 = &TGCallSha256;
|
||||
@ -199,8 +233,10 @@ static void (*InternalVoipLoggingFunction)(NSString *) = NULL;
|
||||
{ derivedStateValue },
|
||||
endpoints,
|
||||
proxyValue,
|
||||
parsedRtcServers,
|
||||
callControllerNetworkTypeForType(networkType),
|
||||
encryptionKey,
|
||||
isVideo,
|
||||
[weakSelf, queue](TgVoipState state) {
|
||||
[queue dispatch:^{
|
||||
__strong OngoingCallThreadLocalContextWebrtc *strongSelf = weakSelf;
|
||||
@ -222,7 +258,26 @@ static void (*InternalVoipLoggingFunction)(NSString *) = NULL;
|
||||
if (strongSelf->_videoState != videoState) {
|
||||
strongSelf->_videoState = videoState;
|
||||
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;
|
||||
|
||||
if (_stateChanged) {
|
||||
_stateChanged(_state, _videoState);
|
||||
if (_videoState == OngoingCallVideoStateActiveOutgoing) {
|
||||
if (_state == OngoingCallStateConnected) {
|
||||
_videoState = OngoingCallVideoStateActive;
|
||||
}
|
||||
}
|
||||
_stateChanged(_state, _videoState, _remoteVideoState);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -211,7 +211,7 @@ func makeBridgeMedia(message: Message, strings: PresentationStrings, chatPeer: P
|
||||
bridgeAction?.actionType = .channelCreated
|
||||
}
|
||||
}
|
||||
case let .phoneCall(_, discardReason, _):
|
||||
case let .phoneCall(_, discardReason, _, _):
|
||||
let bridgeAttachment = TGBridgeUnsupportedMediaAttachment()
|
||||
let incoming = message.flags.contains(.Incoming)
|
||||
var compactTitle: String = ""
|
||||
|
Loading…
x
Reference in New Issue
Block a user