From a79856582e013db5cc8b9633bde7ab8f22162eb9 Mon Sep 17 00:00:00 2001 From: Ali <> Date: Tue, 3 Nov 2020 21:16:20 +0400 Subject: [PATCH] WIP --- .../Sources/GroupCallController.swift | 6 +- .../TelegramUI/Sources/ChatController.swift | 43 +- .../ChatPanelInterfaceInteraction.swift | 8 +- .../ChatPinnedMessageTitlePanelNode.swift | 65 ++- .../Sources/ChatRecentActionsController.swift | 2 +- .../Sources/ChatSearchInputPanelNode.swift | 2 +- .../Sources/EditAccessoryPanelNode.swift | 2 +- .../Sources/PeerInfo/PeerInfoScreen.swift | 2 +- .../Sources/ReplyAccessoryPanelNode.swift | 2 +- .../Sources/GroupCallContext.swift | 522 ++++++++++++------ .../Sources/OngoingCallContext.swift | 2 +- .../GroupCallThreadLocalContext.h | 18 - .../OngoingCallThreadLocalContext.h | 11 + .../Sources/GroupCallThreadLocalContext.mm | 58 -- .../Sources/OngoingCallThreadLocalContext.mm | 61 +- third-party/webrtc/BUILD | 2 +- 16 files changed, 521 insertions(+), 285 deletions(-) delete mode 100644 submodules/TgVoipWebrtc/PublicHeaders/TgVoipWebrtc/GroupCallThreadLocalContext.h delete mode 100644 submodules/TgVoipWebrtc/Sources/GroupCallThreadLocalContext.mm diff --git a/submodules/TelegramCallsUI/Sources/GroupCallController.swift b/submodules/TelegramCallsUI/Sources/GroupCallController.swift index e1455159a5..59d1d7399d 100644 --- a/submodules/TelegramCallsUI/Sources/GroupCallController.swift +++ b/submodules/TelegramCallsUI/Sources/GroupCallController.swift @@ -14,6 +14,7 @@ public final class GroupCallController: ViewController { private let context: AccountContext private let presentationData: PresentationData + private var videoCapturer: OngoingCallVideoCapturer? private var callContext: GroupCallContext? private var callDisposable: Disposable? private var memberCountDisposable: Disposable? @@ -59,7 +60,10 @@ public final class GroupCallController: ViewController { }, availableOutputsChanged: { _, _ in }) - let callContext = GroupCallContext(audioSessionActive: self.audioSessionActive.get()) + let videoCapturer = OngoingCallVideoCapturer() + self.videoCapturer = videoCapturer + + let callContext = GroupCallContext(audioSessionActive: self.audioSessionActive.get(), video: videoCapturer) self.callContext = callContext self.memberCountDisposable = (callContext.memberCount diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 2e8a2bf145..ac3d5f3ef5 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -167,6 +167,11 @@ private struct ScrolledToMessageId: Equatable { var allowedReplacementDirection: AllowedReplacementDirections } +enum ChatLoadingMessageSubject { + case generic + case pinnedMessage +} + public final class ChatControllerImpl: TelegramBaseController, ChatController, GalleryHiddenMediaTarget, UIDropInteractionDelegate { private var validLayout: ContainerViewLayout? @@ -230,7 +235,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G private let unblockingPeer = ValuePromise(false, ignoreRepeated: true) private let searching = ValuePromise(false, ignoreRepeated: true) private let searchResult = Promise<(SearchMessagesResult, SearchMessagesState, SearchMessagesLocation)?>() - private let loadingMessage = ValuePromise(false, ignoreRepeated: true) + private let loadingMessage = ValuePromise(nil, ignoreRepeated: true) private let performingInlineSearch = ValuePromise(false, ignoreRepeated: true) private var preloadHistoryPeerId: PeerId? @@ -4740,7 +4745,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G guard let strongSelf = self else { return } - strongSelf.loadingMessage.set(true) + strongSelf.loadingMessage.set(.generic) let peerId: PeerId let threadId: Int64? @@ -4755,7 +4760,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G strongSelf.messageIndexDisposable.set((searchMessageIdByTimestamp(account: strongSelf.context.account, peerId: peerId, threadId: threadId, timestamp: timestamp) |> deliverOnMainQueue).start(next: { messageId in if let strongSelf = self { - strongSelf.loadingMessage.set(false) + strongSelf.loadingMessage.set(nil) if let messageId = messageId { strongSelf.navigateToMessage(from: nil, to: .id(messageId), forceInCurrentChat: true) } @@ -4784,8 +4789,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G }) strongSelf.updateItemNodesSearchTextHighlightStates() } - }, navigateToMessage: { [weak self] messageId, dropStack, forceInCurrentChat in - self?.navigateToMessage(from: nil, to: .id(messageId), forceInCurrentChat: forceInCurrentChat, dropStack: dropStack) + }, navigateToMessage: { [weak self] messageId, dropStack, forceInCurrentChat, statusSubject in + self?.navigateToMessage(from: nil, to: .id(messageId), forceInCurrentChat: forceInCurrentChat, dropStack: dropStack, statusSubject: statusSubject) }, navigateToChat: { [weak self] peerId in guard let strongSelf = self else { return @@ -9112,13 +9117,13 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } }, completed: { [weak self] in if let strongSelf = self { - strongSelf.loadingMessage.set(false) + strongSelf.loadingMessage.set(nil) strongSelf.chatDisplayNode.historyNode.scrollToEndOfHistory() } })) cancelImpl = { [weak self] in if let strongSelf = self { - strongSelf.loadingMessage.set(false) + strongSelf.loadingMessage.set(nil) strongSelf.messageIndexDisposable.set(nil) } } @@ -9176,13 +9181,13 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } }, completed: { [weak self] in if let strongSelf = self { - strongSelf.loadingMessage.set(false) + strongSelf.loadingMessage.set(nil) strongSelf.chatDisplayNode.historyNode.scrollToStartOfHistory() } })) cancelImpl = { [weak self] in if let strongSelf = self { - strongSelf.loadingMessage.set(false) + strongSelf.loadingMessage.set(nil) strongSelf.messageIndexDisposable.set(nil) } } @@ -9310,7 +9315,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G self.navigateToMessage(from: nil, to: messageLocation, scrollPosition: scrollPosition, rememberInStack: false, forceInCurrentChat: forceInCurrentChat, dropStack: dropStack, animated: animated, completion: completion, customPresentProgress: customPresentProgress) } - private func navigateToMessage(from fromId: MessageId?, to messageLocation: NavigateToMessageLocation, scrollPosition: ListViewScrollPosition = .center(.bottom), rememberInStack: Bool = true, forceInCurrentChat: Bool = false, dropStack: Bool = false, animated: Bool = true, completion: (() -> Void)? = nil, customPresentProgress: ((ViewController, Any?) -> Void)? = nil) { + private func navigateToMessage(from fromId: MessageId?, to messageLocation: NavigateToMessageLocation, scrollPosition: ListViewScrollPosition = .center(.bottom), rememberInStack: Bool = true, forceInCurrentChat: Bool = false, dropStack: Bool = false, animated: Bool = true, completion: (() -> Void)? = nil, customPresentProgress: ((ViewController, Any?) -> Void)? = nil, statusSubject: ChatLoadingMessageSubject = .generic) { if self.isNodeLoaded { var fromIndex: MessageIndex? @@ -9360,14 +9365,14 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G if let scrollFromIndex = scrollFromIndex { if let messageId = messageLocation.messageId, let message = self.chatDisplayNode.historyNode.messageInCurrentHistoryView(messageId) { - self.loadingMessage.set(false) + self.loadingMessage.set(nil) self.messageIndexDisposable.set(nil) self.chatDisplayNode.historyNode.scrollToMessage(from: scrollFromIndex, to: message.index, animated: animated, scrollPosition: scrollPosition) completion?() } else if case let .index(index) = messageLocation, index.id.id == 0, index.timestamp > 0, case .scheduledMessages = self.presentationInterfaceState.subject { self.chatDisplayNode.historyNode.scrollToMessage(from: scrollFromIndex, to: index, animated: animated, scrollPosition: scrollPosition) } else { - self.loadingMessage.set(true) + self.loadingMessage.set(statusSubject) let searchLocation: ChatHistoryInitialSearchLocation switch messageLocation { case let .id(id): @@ -9377,7 +9382,11 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G case .upperBound: searchLocation = .index(MessageIndex.upperBound(peerId: self.chatLocation.peerId)) } - let historyView = preloadedChatHistoryViewForLocation(ChatHistoryLocationInput(content: .InitialSearch(location: searchLocation, count: 50, highlight: true), id: 0), context: self.context, chatLocation: self.chatLocation, subject: self.subject, chatLocationContextHolder: self.chatLocationContextHolder, fixedCombinedReadStates: nil, tagMask: nil, additionalData: []) + var historyView = preloadedChatHistoryViewForLocation(ChatHistoryLocationInput(content: .InitialSearch(location: searchLocation, count: 50, highlight: true), id: 0), context: self.context, chatLocation: self.chatLocation, subject: self.subject, chatLocationContextHolder: self.chatLocationContextHolder, fixedCombinedReadStates: nil, tagMask: nil, additionalData: []) + #if DEBUG + historyView = historyView |> delay(1.0, queue: .mainQueue()) + #endif + let signal = historyView |> mapToSignal { historyView -> Signal<(MessageIndex?, Bool), NoError> in switch historyView { @@ -9441,12 +9450,12 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } }, completed: { [weak self] in if let strongSelf = self { - strongSelf.loadingMessage.set(false) + strongSelf.loadingMessage.set(nil) } })) cancelImpl = { [weak self] in if let strongSelf = self { - strongSelf.loadingMessage.set(false) + strongSelf.loadingMessage.set(nil) strongSelf.messageIndexDisposable.set(nil) } } @@ -9468,7 +9477,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G if let _ = fromId, rememberInStack { self.historyNavigationStack.add(fromIndex) } - self.loadingMessage.set(true) + self.loadingMessage.set(statusSubject) let historyView = preloadedChatHistoryViewForLocation(ChatHistoryLocationInput(content: .InitialSearch(location: searchLocation, count: 50, highlight: true), id: 0), context: self.context, chatLocation: self.chatLocation, subject: self.subject, chatLocationContextHolder: self.chatLocationContextHolder, fixedCombinedReadStates: nil, tagMask: nil, additionalData: []) let signal = historyView |> mapToSignal { historyView -> Signal in @@ -9499,7 +9508,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } }, completed: { [weak self] in if let strongSelf = self { - strongSelf.loadingMessage.set(false) + strongSelf.loadingMessage.set(nil) } })) } else { diff --git a/submodules/TelegramUI/Sources/ChatPanelInterfaceInteraction.swift b/submodules/TelegramUI/Sources/ChatPanelInterfaceInteraction.swift index 5b7b604d99..d1689a584d 100644 --- a/submodules/TelegramUI/Sources/ChatPanelInterfaceInteraction.swift +++ b/submodules/TelegramUI/Sources/ChatPanelInterfaceInteraction.swift @@ -20,10 +20,10 @@ final class ChatPanelInterfaceInteractionStatuses { let startingBot: Signal let unblockingPeer: Signal let searching: Signal - let loadingMessage: Signal + let loadingMessage: Signal let inlineSearch: Signal - init(editingMessage: Signal, startingBot: Signal, unblockingPeer: Signal, searching: Signal, loadingMessage: Signal, inlineSearch: Signal) { + init(editingMessage: Signal, startingBot: Signal, unblockingPeer: Signal, searching: Signal, loadingMessage: Signal, inlineSearch: Signal) { self.editingMessage = editingMessage self.startingBot = startingBot self.unblockingPeer = unblockingPeer @@ -73,7 +73,7 @@ final class ChatPanelInterfaceInteraction { let openSearchResults: () -> Void let openCalendarSearch: () -> Void let toggleMembersSearch: (Bool) -> Void - let navigateToMessage: (MessageId, Bool, Bool) -> Void + let navigateToMessage: (MessageId, Bool, Bool, ChatLoadingMessageSubject) -> Void let navigateToChat: (PeerId) -> Void let navigateToProfile: (PeerId) -> Void let openPeerInfo: () -> Void @@ -152,7 +152,7 @@ final class ChatPanelInterfaceInteraction { navigateMessageSearch: @escaping (ChatPanelSearchNavigationAction) -> Void, openCalendarSearch: @escaping () -> Void, toggleMembersSearch: @escaping (Bool) -> Void, - navigateToMessage: @escaping (MessageId, Bool, Bool) -> Void, + navigateToMessage: @escaping (MessageId, Bool, Bool, ChatLoadingMessageSubject) -> Void, navigateToChat: @escaping (PeerId) -> Void, navigateToProfile: @escaping (PeerId) -> Void, openPeerInfo: @escaping () -> Void, diff --git a/submodules/TelegramUI/Sources/ChatPinnedMessageTitlePanelNode.swift b/submodules/TelegramUI/Sources/ChatPinnedMessageTitlePanelNode.swift index 829513eae6..1ea97f2f00 100644 --- a/submodules/TelegramUI/Sources/ChatPinnedMessageTitlePanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatPinnedMessageTitlePanelNode.swift @@ -15,17 +15,33 @@ import TelegramStringFormatting import AnimatedCountLabelNode import AnimatedNavigationStripeNode import ContextUI +import RadialStatusNode private enum PinnedMessageAnimation { case slideToTop case slideToBottom } +private final class ButtonsContainerNode: ASDisplayNode { + override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + if let subnodes = self.subnodes { + for subnode in subnodes { + if let result = subnode.view.hitTest(self.view.convert(point, to: subnode.view), with: event) { + return result + } + } + } + return nil + } +} + final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode { private let context: AccountContext private let tapButton: HighlightTrackingButtonNode + private let buttonsContainer: ButtonsContainerNode private let closeButton: HighlightableButtonNode private let listButton: HighlightableButtonNode + private let activityIndicator: RadialStatusNode private let contextContainer: ContextControllerSourceNode private let clippingContainer: ASDisplayNode @@ -47,6 +63,8 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode { private var isReplyThread: Bool = false private let fetchDisposable = MetaDisposable() + + private var statusDisposable: Disposable? private let queue = Queue() @@ -55,6 +73,8 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode { self.tapButton = HighlightTrackingButtonNode() + self.buttonsContainer = ButtonsContainerNode() + self.closeButton = HighlightableButtonNode() self.closeButton.hitTestSlop = UIEdgeInsets(top: -8.0, left: -8.0, bottom: -8.0, right: -8.0) self.closeButton.displaysAsynchronously = false @@ -63,6 +83,9 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode { self.listButton.hitTestSlop = UIEdgeInsets(top: -8.0, left: -8.0, bottom: -8.0, right: -8.0) self.listButton.displaysAsynchronously = false + self.activityIndicator = RadialStatusNode(backgroundNodeColor: .clear) + self.activityIndicator.isUserInteractionEnabled = false + self.separatorNode = ASDisplayNode() self.separatorNode.isLayerBacked = true @@ -126,8 +149,9 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode { self.imageNodeContainer.addSubnode(self.imageNode) self.contentContainer.addSubnode(self.imageNodeContainer) - self.contextContainer.addSubnode(self.closeButton) - self.contextContainer.addSubnode(self.listButton) + self.buttonsContainer.addSubnode(self.closeButton) + self.buttonsContainer.addSubnode(self.listButton) + self.contextContainer.addSubnode(self.buttonsContainer) self.tapButton.addTarget(self, action: #selector(self.tapped), forControlEvents: [.touchUpInside]) self.contextContainer.addSubnode(self.tapButton) @@ -146,6 +170,7 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode { deinit { self.fetchDisposable.dispose() + self.statusDisposable?.dispose() } private var theme: PresentationTheme? @@ -165,6 +190,35 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode { self.separatorNode.backgroundColor = interfaceState.theme.chat.historyNavigation.strokeColor } + if self.statusDisposable == nil, let interfaceInteraction = self.interfaceInteraction, let statuses = interfaceInteraction.statuses { + self.statusDisposable = (statuses.loadingMessage + |> map { status -> Bool in + return status == .pinnedMessage + } + |> deliverOnMainQueue).start(next: { [weak self] isLoading in + guard let strongSelf = self else { + return + } + if isLoading { + if strongSelf.activityIndicator.supernode == nil { + strongSelf.buttonsContainer.supernode?.insertSubnode(strongSelf.activityIndicator, aboveSubnode: strongSelf.buttonsContainer) + if let theme = strongSelf.theme { + strongSelf.activityIndicator.transitionToState(.progress(color: theme.chat.inputPanel.panelControlAccentColor, lineWidth: nil, value: nil, cancelEnabled: false), animated: false, completion: { + }) + } + } + strongSelf.buttonsContainer.isHidden = true + } else { + if strongSelf.activityIndicator.supernode != nil { + strongSelf.activityIndicator.removeFromSupernode() + strongSelf.activityIndicator.transitionToState(.none, animated: false, completion: { + }) + } + strongSelf.buttonsContainer.isHidden = false + } + }) + } + let isReplyThread: Bool if case .replyThread = interfaceState.chatLocation { isReplyThread = true @@ -219,12 +273,17 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode { let rightInset: CGFloat = 18.0 + rightInset + self.buttonsContainer.frame = CGRect(origin: CGPoint(), size: CGSize(width: width, height: panelHeight)) + let closeButtonSize = self.closeButton.measure(CGSize(width: 100.0, height: 100.0)) transition.updateFrame(node: self.closeButton, frame: CGRect(origin: CGPoint(x: width - rightInset - closeButtonSize.width, y: 19.0), size: closeButtonSize)) let listButtonSize = self.listButton.measure(CGSize(width: 100.0, height: 100.0)) transition.updateFrame(node: self.listButton, frame: CGRect(origin: CGPoint(x: width - rightInset - listButtonSize.width + 4.0, y: 13.0), size: listButtonSize)) + let indicatorSize = CGSize(width: 22.0, height: 22.0) + transition.updateFrame(node: self.activityIndicator, frame: CGRect(origin: CGPoint(x: width - rightInset - indicatorSize.width + 2.0, y: 15.0), size: indicatorSize)) + transition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: panelHeight - UIScreenPixel), size: CGSize(width: width, height: UIScreenPixel))) self.tapButton.frame = CGRect(origin: CGPoint(), size: CGSize(width: width - rightInset - closeButtonSize.width - 4.0, height: panelHeight)) @@ -450,7 +509,7 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode { if self.isReplyThread { interfaceInteraction.scrollToTop() } else { - interfaceInteraction.navigateToMessage(message.message.id, false, true) + interfaceInteraction.navigateToMessage(message.message.id, false, true, .pinnedMessage) } } } diff --git a/submodules/TelegramUI/Sources/ChatRecentActionsController.swift b/submodules/TelegramUI/Sources/ChatRecentActionsController.swift index b350a848f9..fc671058c0 100644 --- a/submodules/TelegramUI/Sources/ChatRecentActionsController.swift +++ b/submodules/TelegramUI/Sources/ChatRecentActionsController.swift @@ -76,7 +76,7 @@ final class ChatRecentActionsController: TelegramBaseController { }, navigateMessageSearch: { _ in }, openCalendarSearch: { }, toggleMembersSearch: { _ in - }, navigateToMessage: { _, _, _ in + }, navigateToMessage: { _, _, _, _ in }, navigateToChat: { _ in }, navigateToProfile: { _ in }, openPeerInfo: { diff --git a/submodules/TelegramUI/Sources/ChatSearchInputPanelNode.swift b/submodules/TelegramUI/Sources/ChatSearchInputPanelNode.swift index 8cbe14652a..abd35fa05a 100644 --- a/submodules/TelegramUI/Sources/ChatSearchInputPanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatSearchInputPanelNode.swift @@ -34,7 +34,7 @@ final class ChatSearchInputPanelNode: ChatInputPanelNode { didSet { if let statuses = self.interfaceInteraction?.statuses { self.activityDisposable.set((combineLatest((statuses.searching |> deliverOnMainQueue), (statuses.loadingMessage |> deliverOnMainQueue))).start(next: { [weak self] searching, loadingMessage in - let value = searching || loadingMessage + let value = searching || loadingMessage == .generic if let strongSelf = self, strongSelf.displayActivity != value { strongSelf.displayActivity = value strongSelf.activityIndicator.isHidden = !value diff --git a/submodules/TelegramUI/Sources/EditAccessoryPanelNode.swift b/submodules/TelegramUI/Sources/EditAccessoryPanelNode.swift index 197c2d71c9..de32baeb6d 100644 --- a/submodules/TelegramUI/Sources/EditAccessoryPanelNode.swift +++ b/submodules/TelegramUI/Sources/EditAccessoryPanelNode.swift @@ -347,7 +347,7 @@ final class EditAccessoryPanelNode: AccessoryPanelNode { @objc func contentTap(_ recognizer: UITapGestureRecognizer) { if case .ended = recognizer.state, let message = self.currentMessage { - self.interfaceInteraction?.navigateToMessage(message.id, false, true) + self.interfaceInteraction?.navigateToMessage(message.id, false, true, .generic) } } } diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift index a536be47e3..72db58f31e 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift @@ -382,7 +382,7 @@ final class PeerInfoSelectionPanelNode: ASDisplayNode { }, navigateMessageSearch: { _ in }, openCalendarSearch: { }, toggleMembersSearch: { _ in - }, navigateToMessage: { _, _, _ in + }, navigateToMessage: { _, _, _, _ in }, navigateToChat: { _ in }, navigateToProfile: { _ in }, openPeerInfo: { diff --git a/submodules/TelegramUI/Sources/ReplyAccessoryPanelNode.swift b/submodules/TelegramUI/Sources/ReplyAccessoryPanelNode.swift index 499f13a547..15d595332d 100644 --- a/submodules/TelegramUI/Sources/ReplyAccessoryPanelNode.swift +++ b/submodules/TelegramUI/Sources/ReplyAccessoryPanelNode.swift @@ -256,7 +256,7 @@ final class ReplyAccessoryPanelNode: AccessoryPanelNode { @objc func tapGesture(_ recognizer: UITapGestureRecognizer) { if case .ended = recognizer.state { - self.interfaceInteraction?.navigateToMessage(self.messageId, false, true) + self.interfaceInteraction?.navigateToMessage(self.messageId, false, true, .generic) } } } diff --git a/submodules/TelegramVoip/Sources/GroupCallContext.swift b/submodules/TelegramVoip/Sources/GroupCallContext.swift index d61fd85c82..ed915a8cc6 100644 --- a/submodules/TelegramVoip/Sources/GroupCallContext.swift +++ b/submodules/TelegramVoip/Sources/GroupCallContext.swift @@ -497,9 +497,14 @@ private extension ConferenceDescription.Content.Channel.PayloadType { result["name"] = self.name result["channels"] = self.channels result["clockrate"] = self.clockrate - result["rtcp-fbs"] = [[ - "type": "transport-cc" - ] as [String: Any]] as [Any] + result["rtcp-fbs"] = [ + [ + "type": "transport-cc" + ] as [String: Any], + /*[ + "type": "nack" + ] as [String: Any]*/ + ] as [Any] if let parameters = self.parameters { result["parameters"] = parameters } @@ -642,7 +647,8 @@ private extension ConferenceDescription.ChannelBundle { private struct RemoteOffer { struct State: Equatable { struct Item: Equatable { - var ssrc: Int + var audioSsrc: Int + var videoSsrc: Int var isRemoved: Bool } @@ -650,7 +656,6 @@ private struct RemoteOffer { } var sdpList: [String] - var isPartial: Bool var state: State } @@ -666,14 +671,14 @@ private extension ConferenceDescription { } func offerSdp(sessionId: UInt32, bundleId: String, bridgeHost: String, transport: ConferenceDescription.Transport, currentState: RemoteOffer.State?) -> RemoteOffer? { - struct Ssrc { + struct StreamSpec { var isMain: Bool - var value: Int - var streamId: String + var audioSsrc: Int + var videoSsrc: Int var isRemoved: Bool } - func createSdp(sessionId: UInt32, bundleSsrcs: [Ssrc], isPartial: Bool) -> String { + func createSdp(sessionId: UInt32, bundleStreams: [StreamSpec]) -> String { var sdp = "" func appendSdp(_ string: String) { if !sdp.isEmpty { @@ -687,98 +692,131 @@ private extension ConferenceDescription { appendSdp("s=-") appendSdp("t=0 0") - appendSdp("a=group:BUNDLE \(bundleSsrcs.map({ "audio\($0.value)" }).joined(separator: " "))") + appendSdp("a=group:BUNDLE \(bundleStreams.map({ "audio\($0.audioSsrc) video\($0.videoSsrc)" }).joined(separator: " "))") appendSdp("a=ice-lite") - for ssrc in bundleSsrcs { - appendSdp("m=audio \(ssrc.isMain ? "1" : "0") RTP/SAVPF 111 126") - if ssrc.isMain { + for stream in bundleStreams { + appendSdp("m=audio \(stream.isMain ? "1" : "0") RTP/SAVPF 111 126") + if stream.isMain { appendSdp("c=IN IP4 0.0.0.0") } - appendSdp("a=mid:audio\(ssrc.value)") - if ssrc.isRemoved { + appendSdp("a=mid:audio\(stream.audioSsrc)") + if stream.isRemoved { + appendSdp("a=inactive") + } else { + if stream.isMain { + appendSdp("a=ice-ufrag:\(transport.ufrag)") + appendSdp("a=ice-pwd:\(transport.pwd)") + + for fingerprint in transport.fingerprints { + appendSdp("a=fingerprint:\(fingerprint.hashType) \(fingerprint.fingerprint)") + appendSdp("a=setup:\(fingerprint.setup)") + } + + for candidate in transport.candidates { + var candidateString = "a=candidate:" + candidateString.append("\(candidate.foundation) ") + candidateString.append("\(candidate.component) ") + var protocolValue = candidate.protocol + if protocolValue == "ssltcp" { + protocolValue = "tcp" + } + candidateString.append("\(protocolValue) ") + candidateString.append("\(candidate.priority) ") + + var ip = candidate.ip + if ip.hasPrefix("192.") { + ip = bridgeHost + } + candidateString.append("\(ip) ") + candidateString.append("\(candidate.port) ") + + candidateString.append("typ \(candidate.type) ") + + switch candidate.type { + case "srflx", "prflx", "relay": + if let relAddr = candidate.relAddr, let relPort = candidate.relPort { + candidateString.append("raddr \(relAddr) rport \(relPort) ") + } + break + default: + break + } + + if protocolValue == "tcp" { + guard let tcpType = candidate.tcpType else { + continue + } + candidateString.append("tcptype \(tcpType) ") + } + + candidateString.append("generation \(candidate.generation)") + + appendSdp(candidateString) + } + } + + appendSdp("a=rtpmap:111 opus/48000/2") + appendSdp("a=rtpmap:126 telephone-event/8000") + appendSdp("a=fmtp:111 minptime=10; useinbandfec=1; usedtx=1") + appendSdp("a=rtcp:1 IN IP4 0.0.0.0") + appendSdp("a=rtcp-mux") + appendSdp("a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level") + appendSdp("a=extmap:3 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time") + appendSdp("a=extmap:5 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01") + appendSdp("a=rtcp-fb:111 transport-cc") + //appendSdp("a=rtcp-fb:111 ccm fir") + //appendSdp("a=rtcp-fb:111 nack") + + if stream.isMain { + appendSdp("a=sendrecv") + } else { + appendSdp("a=sendonly") + appendSdp("a=bundle-only") + } + + appendSdp("a=ssrc-group:FID \(stream.audioSsrc)") + appendSdp("a=ssrc:\(stream.audioSsrc) cname:stream\(stream.audioSsrc)") + appendSdp("a=ssrc:\(stream.audioSsrc) msid:stream\(stream.audioSsrc) audio\(stream.audioSsrc)") + appendSdp("a=ssrc:\(stream.audioSsrc) mslabel:audio\(stream.audioSsrc)") + appendSdp("a=ssrc:\(stream.audioSsrc) label:audio\(stream.audioSsrc)") + } + + appendSdp("m=video \(stream.isMain ? "1" : "0") RTP/SAVPF 100") + appendSdp("a=mid:video\(stream.videoSsrc)") + if stream.isRemoved { appendSdp("a=inactive") continue - } - - if ssrc.isMain { - appendSdp("a=ice-ufrag:\(transport.ufrag)") - appendSdp("a=ice-pwd:\(transport.pwd)") - - for fingerprint in transport.fingerprints { - appendSdp("a=fingerprint:\(fingerprint.hashType) \(fingerprint.fingerprint)") - appendSdp("a=setup:\(fingerprint.setup)") - } - - for candidate in transport.candidates { - var candidateString = "a=candidate:" - candidateString.append("\(candidate.foundation) ") - candidateString.append("\(candidate.component) ") - var protocolValue = candidate.protocol - if protocolValue == "ssltcp" { - protocolValue = "tcp" - } - candidateString.append("\(protocolValue) ") - candidateString.append("\(candidate.priority) ") - - var ip = candidate.ip - if ip.hasPrefix("192.") { - ip = bridgeHost - } - candidateString.append("\(ip) ") - candidateString.append("\(candidate.port) ") - - candidateString.append("typ \(candidate.type) ") - - switch candidate.type { - case "srflx", "prflx", "relay": - if let relAddr = candidate.relAddr, let relPort = candidate.relPort { - candidateString.append("raddr \(relAddr) rport \(relPort) ") - } - break - default: - break - } - - if protocolValue == "tcp" { - guard let tcpType = candidate.tcpType else { - continue - } - candidateString.append("tcptype \(tcpType) ") - } - - candidateString.append("generation \(candidate.generation)") - - appendSdp(candidateString) - } - } - - appendSdp("a=rtpmap:111 opus/48000/2") - //appendSdp("a=rtpmap:103 ISAC/16000") - //appendSdp("a=rtpmap:104 ISAC/32000") - appendSdp("a=rtpmap:126 telephone-event/8000") - appendSdp("a=fmtp:111 minptime=10; useinbandfec=1; usedtx=1") - appendSdp("a=rtcp:1 IN IP4 0.0.0.0") - appendSdp("a=rtcp-mux") - appendSdp("a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level") - appendSdp("a=extmap:3 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time") - appendSdp("a=extmap:5 http://www.webrtc.org/experiments/rtp-hdrext/transport-wide-cc-02") - appendSdp("a=rtcp-fb:111 transport-cc") - //appendSdp("a=rtcp-fb:111 ccm fir") - //appendSdp("a=rtcp-fb:111 nack") - - if ssrc.isMain { - appendSdp("a=sendrecv") } else { - appendSdp("a=sendonly") + /*a=rtpmap:100 VP8/90000 + a=fmtp:100 x-google-start-bitrate=800 + a=rtcp:1 IN IP4 0.0.0.0 + a=rtcp-fb:100 ccm fir + a=rtcp-fb:100 nack + a=rtcp-fb:100 nack pli + a=rtcp-fb:100 goog-remb*/ + + appendSdp("a=rtpmap:100 VP8/90000") + appendSdp("a=fmtp:100 x-google-start-bitrate=800") + appendSdp("a=rtcp:1 IN IP4 0.0.0.0") + appendSdp("a=rtcp-mux") + + appendSdp("a=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time") + appendSdp("a=extmap:4 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01") + + appendSdp("a=rtcp-fb:100 transport-cc") + appendSdp("a=rtcp-fb:100 ccm fir") + appendSdp("a=rtcp-fb:100 nack") + appendSdp("a=rtcp-fb:100 nack pli") + appendSdp("a=bundle-only") + + appendSdp("a=ssrc-group:FID \(stream.videoSsrc)") + appendSdp("a=ssrc:\(stream.videoSsrc) cname:stream\(stream.audioSsrc)") + appendSdp("a=ssrc:\(stream.videoSsrc) msid:stream\(stream.audioSsrc) video\(stream.videoSsrc)") + appendSdp("a=ssrc:\(stream.videoSsrc) mslabel:video\(stream.videoSsrc)") + appendSdp("a=ssrc:\(stream.videoSsrc) label:video\(stream.videoSsrc)") } - - appendSdp("a=ssrc-group:FID \(ssrc.value)") - appendSdp("a=ssrc:\(ssrc.value) cname:stream\(ssrc.value)") - appendSdp("a=ssrc:\(ssrc.value) msid:stream\(ssrc.value) audio\(ssrc.value)") - appendSdp("a=ssrc:\(ssrc.value) mslabel:audio\(ssrc.value)") - appendSdp("a=ssrc:\(ssrc.value) label:audio\(ssrc.value)") } appendSdp("") @@ -786,82 +824,84 @@ private extension ConferenceDescription { return sdp } - var ssrcList: [Ssrc] = [] - var maybeMainSsrcId: Int? - for content in self.contents { - for channel in content.channels { - if channel.endpoint == bundleId { - precondition(channel.sources.count == 1) - ssrcList.append(contentsOf: channel.sources.map { ssrc in - return Ssrc( - isMain: true, - value: ssrc, - streamId: "stream0", - isRemoved: false - ) - }) - maybeMainSsrcId = channel.sources[0] - } else { - precondition(channel.ssrcs.count <= 1) - ssrcList.append(contentsOf: channel.ssrcs.map { ssrc in - return Ssrc( - isMain: false, - value: ssrc, - streamId: "stream\(ssrc)", - isRemoved: false - ) - }) + var streams: [StreamSpec] = [] + var maybeMainStreamAudioSsrc: Int? + + for audioContent in self.contents { + if audioContent.name != "audio" { + continue + } + for audioChannel in audioContent.channels { + for videoContent in self.contents { + if videoContent.name != "video" { + continue + } + for videoChannel in videoContent.channels { + if videoChannel.channelBundleId == audioChannel.channelBundleId { + if audioChannel.channelBundleId == bundleId { + precondition(audioChannel.sources.count == 1) + precondition(videoChannel.sources.count == 1) + streams.append(StreamSpec( + isMain: true, + audioSsrc: audioChannel.sources[0], + videoSsrc: videoChannel.sources[0], + isRemoved: false + )) + maybeMainStreamAudioSsrc = audioChannel.sources[0] + } else { + precondition(audioChannel.ssrcs.count <= 1) + precondition(videoChannel.ssrcs.count <= 1) + if audioChannel.ssrcs.count == 1 && videoChannel.ssrcs.count == 1 { + streams.append(StreamSpec( + isMain: false, + audioSsrc: audioChannel.ssrcs[0], + videoSsrc: videoChannel.ssrcs[0], + isRemoved: false + )) + } + } + } + } } } } - guard let mainSsrcId = maybeMainSsrcId else { + guard let mainStreamAudioSsrc = maybeMainStreamAudioSsrc else { preconditionFailure() } - var bundleSsrcs: [Ssrc] = [] + var bundleStreams: [StreamSpec] = [] if let currentState = currentState { for item in currentState.items { - let isRemoved = !ssrcList.contains(where: { $0.value == item.ssrc }) - bundleSsrcs.append(Ssrc( - isMain: item.ssrc == mainSsrcId, - value: item.ssrc, - streamId: item.ssrc == mainSsrcId ? "audio0" : "stream\(item.ssrc)", + let isRemoved = !streams.contains(where: { $0.audioSsrc == item.audioSsrc }) + bundleStreams.append(StreamSpec( + isMain: item.audioSsrc == mainStreamAudioSsrc, + audioSsrc: item.audioSsrc, + videoSsrc: item.videoSsrc, isRemoved: isRemoved )) } } - for ssrc in ssrcList { - if bundleSsrcs.contains(where: { $0.value == ssrc.value }) { + for stream in streams { + if bundleStreams.contains(where: { $0.audioSsrc == stream.audioSsrc }) { continue } - bundleSsrcs.append(ssrc) + bundleStreams.append(stream) } var sdpList: [String] = [] - sdpList.append(createSdp(sessionId: sessionId, bundleSsrcs: bundleSsrcs, isPartial: false)) - - /*if currentState == nil { - sdpList.append(createSdp(sessionId: sessionId, bundleSsrcs: bundleSsrcs, isPartial: false)) - } else { - for ssrc in bundleSsrcs { - if ssrc.isMain { - continue - } - sdpList.append(createSdp(sessionId: sessionId, bundleSsrcs: [ssrc], isPartial: true)) - } - }*/ + sdpList.append(createSdp(sessionId: sessionId, bundleStreams: bundleStreams)) return RemoteOffer( sdpList: sdpList, - isPartial: false, state: RemoteOffer.State( - items: bundleSsrcs.map { ssrc in + items: bundleStreams.map { stream in RemoteOffer.State.Item( - ssrc: ssrc.value, - isRemoved: ssrc.isRemoved + audioSsrc: stream.audioSsrc, + videoSsrc: stream.videoSsrc, + isRemoved: stream.isRemoved ) } ) @@ -870,10 +910,15 @@ private extension ConferenceDescription { mutating func updateLocalChannelFromSdpAnswer(bundleId: String, sdpAnswer: String) { var maybeAudioChannel: ConferenceDescription.Content.Channel? + var maybeVideoChannel: ConferenceDescription.Content.Channel? for content in self.contents { for channel in content.channels { if channel.endpoint == bundleId { - maybeAudioChannel = channel + if content.name == "audio" { + maybeAudioChannel = channel + } else if content.name == "video" { + maybeVideoChannel = channel + } break } } @@ -883,8 +928,33 @@ private extension ConferenceDescription { assert(false) return } + guard var videoChannel = maybeVideoChannel else { + assert(false) + return + } let lines = sdpAnswer.components(separatedBy: "\n") + + var videoLines: [String] = [] + var audioLines: [String] = [] + var isAudioLine = false + var isVideoLine = false + for line in lines { + if line.hasPrefix("m=audio") { + isAudioLine = true + isVideoLine = false + } else if line.hasPrefix("m=video") { + isVideoLine = true + isAudioLine = false + } + + if isAudioLine { + audioLines.append(line) + } else if isVideoLine { + videoLines.append(line) + } + } + func getLines(prefix: String) -> [String] { var result: [String] = [] for line in lines { @@ -899,8 +969,23 @@ private extension ConferenceDescription { return result } + func getLines(prefix: String, isAudio: Bool) -> [String] { + var result: [String] = [] + for line in (isAudio ? audioLines : videoLines) { + if line.hasPrefix(prefix) { + var cleanLine = String(line[line.index(line.startIndex, offsetBy: prefix.count)...]) + if cleanLine.hasSuffix("\r") { + cleanLine.removeLast() + } + result.append(cleanLine) + } + } + return result + } + var audioSources: [Int] = [] - for line in getLines(prefix: "a=ssrc:") { + var videoSources: [Int] = [] + for line in getLines(prefix: "a=ssrc:", isAudio: true) { let scanner = Scanner(string: line) if #available(iOS 13.0, *) { if let ssrc = scanner.scanInt() { @@ -910,6 +995,16 @@ private extension ConferenceDescription { } } } + for line in getLines(prefix: "a=ssrc:", isAudio: false) { + let scanner = Scanner(string: line) + if #available(iOS 13.0, *) { + if let ssrc = scanner.scanInt() { + if !videoSources.contains(ssrc) { + videoSources.append(ssrc) + } + } + } + } audioChannel.sources = audioSources /*audioChannel.ssrcGroups = [ConferenceDescription.Content.Channel.SsrcGroup( @@ -927,23 +1022,16 @@ private extension ConferenceDescription { "fmtp": [ "minptime=10;useinbandfec=1" ] as [Any], - "rtcp-fbs": [[ - "type": "transport-cc" - ] as [String: Any]] as [Any] + "rtcp-fbs": [ + [ + "type": "transport-cc" + ] as [String: Any], + /*[ + "type": "nack" + ] as [String: Any]*/ + ] as [Any] ] ), - /*ConferenceDescription.Content.Channel.PayloadType( - id: 103, - name: "ISAC", - clockrate: 16000, - channels: 1 - ), - ConferenceDescription.Content.Channel.PayloadType( - id: 104, - name: "ISAC", - clockrate: 32000, - channels: 1 - ),*/ ConferenceDescription.Content.Channel.PayloadType( id: 126, name: "telephone-event", @@ -963,7 +1051,46 @@ private extension ConferenceDescription { ), ConferenceDescription.Content.Channel.RtpHdrExt( id: 5, - uri: "http://www.webrtc.org/experiments/rtp-hdrext/transport-wide-cc-02" + uri: "http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01" + ), + ] + + videoChannel.sources = videoSources + /*audioChannel.ssrcGroups = [ConferenceDescription.Content.Channel.SsrcGroup( + sources: audioSources, + semantics: "SIM" + )]*/ + + videoChannel.payloadTypes = [ + ConferenceDescription.Content.Channel.PayloadType( + id: 100, + name: "VP8", + clockrate: 9000, + channels: 1, + parameters: [ + "fmtp": [ + "x-google-start-bitrate=800" + ] as [Any], + "rtcp-fbs": [ + [ + "type": "transport-cc" + ] as [String: Any], + [ + "type": "nack" + ] as [String: Any] + ] as [Any] + ] + ) + ] + + audioChannel.rtpHdrExts = [ + ConferenceDescription.Content.Channel.RtpHdrExt( + id: 2, + uri: "http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time" + ), + ConferenceDescription.Content.Channel.RtpHdrExt( + id: 4, + uri: "http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01" ), ] @@ -989,11 +1116,14 @@ private extension ConferenceDescription { )) } - outerContents: for i in 0 ..< self.contents.count { + for i in 0 ..< self.contents.count { for j in 0 ..< self.contents[i].channels.count { if self.contents[i].channels[j].endpoint == bundleId { - self.contents[i].channels[j] = audioChannel - break outerContents + if self.contents[i].name == "audio" { + self.contents[i].channels[j] = audioChannel + } else if self.contents[i].name == "video" { + self.contents[i].channels[j] = videoChannel + } } } } @@ -1137,13 +1267,11 @@ public final class GroupCallContext { private var isMutedValue: Bool = false let isMuted = ValuePromise(false, ignoreRepeated: true) - init(queue: Queue, audioSessionActive: Signal) { + init(queue: Queue, audioSessionActive: Signal, video: OngoingCallVideoCapturer?) { self.queue = queue self.sessionId = UInt32.random(in: 0 ..< UInt32(Int32.max)) - self.colibriHost = "51.11.141.27" - //self.colibriHost = "192.168.93.24" - //self.colibriHost = "51.104.206.109" + self.colibriHost = "192.168.8.118" var relaySdpAnswerImpl: ((String) -> Void)? @@ -1151,7 +1279,7 @@ public final class GroupCallContext { queue.async { relaySdpAnswerImpl?(sdpAnswer) } - }) + }, videoCapturer: video?.impl) relaySdpAnswerImpl = { [weak self] sdpAnswer in guard let strongSelf = self else { @@ -1231,8 +1359,23 @@ public final class GroupCallContext { payloadTypes: [], rtpHdrExts: [] ) + let videoChannel = ConferenceDescription.Content.Channel( + id: nil, + endpoint: bundleId, + channelBundleId: bundleId, + sources: [], + ssrcs: [], + rtpLevelRelayType: "translator", + expire: 10, + initiator: true, + direction: "sendrecv", + ssrcGroups: [], + payloadTypes: [], + rtpHdrExts: [] + ) - var foundContent = false + var foundAudioContent = false + var foundVideoContent = false for i in 0 ..< conference.contents.count { if conference.contents[i].name == "audio" { for j in 0 ..< conference.contents[i].channels.count { @@ -1253,16 +1396,43 @@ public final class GroupCallContext { ) } conference.contents[i].channels.append(audioChannel) - foundContent = true + foundAudioContent = true + break + } else if conference.contents[i].name == "video" { + for j in 0 ..< conference.contents[i].channels.count { + let channel = conference.contents[i].channels[j] + conference.contents[i].channels[j] = ConferenceDescription.Content.Channel( + id: channel.id, + endpoint: channel.endpoint, + channelBundleId: channel.channelBundleId, + sources: channel.sources, + ssrcs: channel.ssrcs, + rtpLevelRelayType: channel.rtpLevelRelayType, + expire: channel.expire, + initiator: channel.initiator, + direction: channel.direction, + ssrcGroups: [], + payloadTypes: [], + rtpHdrExts: [] + ) + } + conference.contents[i].channels.append(videoChannel) + foundVideoContent = true break } } - if !foundContent { + if !foundAudioContent { conference.contents.append(ConferenceDescription.Content( name: "audio", channels: [audioChannel] )) } + if !foundVideoContent { + conference.contents.append(ConferenceDescription.Content( + name: "video", + channels: [videoChannel] + )) + } conference.channelBundles.append(ConferenceDescription.ChannelBundle( id: bundleId, transport: ConferenceDescription.Transport( @@ -1329,7 +1499,7 @@ public final class GroupCallContext { strongSelf.memberCount.set(offer.state.items.filter({ !$0.isRemoved }).count) for sdp in offer.sdpList { - strongSelf.context.setOfferSdp(sdp, isPartial: offer.isPartial) + strongSelf.context.setOfferSdp(sdp, isPartial: false) } })) } @@ -1412,7 +1582,7 @@ public final class GroupCallContext { strongSelf.memberCount.set(offer.state.items.filter({ !$0.isRemoved }).count) for sdp in offer.sdpList { - strongSelf.context.setOfferSdp(sdp, isPartial: offer.isPartial) + strongSelf.context.setOfferSdp(sdp, isPartial: false) } } @@ -1430,10 +1600,10 @@ public final class GroupCallContext { private let queue = Queue() private let impl: QueueLocalObject - public init(audioSessionActive: Signal) { + public init(audioSessionActive: Signal, video: OngoingCallVideoCapturer?) { let queue = self.queue self.impl = QueueLocalObject(queue: queue, generate: { - return Impl(queue: queue, audioSessionActive: audioSessionActive) + return Impl(queue: queue, audioSessionActive: audioSessionActive, video: video) }) } diff --git a/submodules/TelegramVoip/Sources/OngoingCallContext.swift b/submodules/TelegramVoip/Sources/OngoingCallContext.swift index 4966897420..564317977c 100644 --- a/submodules/TelegramVoip/Sources/OngoingCallContext.swift +++ b/submodules/TelegramVoip/Sources/OngoingCallContext.swift @@ -333,7 +333,7 @@ extension OngoingCallThreadLocalContext: OngoingCallThreadLocalContextProtocol { } public final class OngoingCallVideoCapturer { - fileprivate let impl: OngoingCallThreadLocalContextVideoCapturer + internal let impl: OngoingCallThreadLocalContextVideoCapturer public init() { self.impl = OngoingCallThreadLocalContextVideoCapturer() diff --git a/submodules/TgVoipWebrtc/PublicHeaders/TgVoipWebrtc/GroupCallThreadLocalContext.h b/submodules/TgVoipWebrtc/PublicHeaders/TgVoipWebrtc/GroupCallThreadLocalContext.h deleted file mode 100644 index ba75ba664a..0000000000 --- a/submodules/TgVoipWebrtc/PublicHeaders/TgVoipWebrtc/GroupCallThreadLocalContext.h +++ /dev/null @@ -1,18 +0,0 @@ -#ifndef GroupCallThreadLocalContext_h -#define GroupCallThreadLocalContext_h - -#import - -#import - -@interface GroupCallThreadLocalContext : NSObject - -- (instancetype _Nonnull)initWithQueue:(id _Nonnull)queue relaySdpAnswer:(void (^ _Nonnull)(NSString * _Nonnull))relaySdpAnswer; - -- (void)emitOffer; -- (void)setOfferSdp:(NSString * _Nonnull)offerSdp isPartial:(bool)isPartial; -- (void)setIsMuted:(bool)isMuted; - -@end - -#endif diff --git a/submodules/TgVoipWebrtc/PublicHeaders/TgVoipWebrtc/OngoingCallThreadLocalContext.h b/submodules/TgVoipWebrtc/PublicHeaders/TgVoipWebrtc/OngoingCallThreadLocalContext.h index 2e919600d9..0f348e830a 100644 --- a/submodules/TgVoipWebrtc/PublicHeaders/TgVoipWebrtc/OngoingCallThreadLocalContext.h +++ b/submodules/TgVoipWebrtc/PublicHeaders/TgVoipWebrtc/OngoingCallThreadLocalContext.h @@ -150,4 +150,15 @@ typedef NS_ENUM(int32_t, OngoingCallDataSavingWebrtc) { @end + +@interface GroupCallThreadLocalContext : NSObject + +- (instancetype _Nonnull)initWithQueue:(id _Nonnull)queue relaySdpAnswer:(void (^ _Nonnull)(NSString * _Nonnull))relaySdpAnswer videoCapturer:(OngoingCallThreadLocalContextVideoCapturer * _Nullable)videoCapturer; + +- (void)emitOffer; +- (void)setOfferSdp:(NSString * _Nonnull)offerSdp isPartial:(bool)isPartial; +- (void)setIsMuted:(bool)isMuted; + +@end + #endif diff --git a/submodules/TgVoipWebrtc/Sources/GroupCallThreadLocalContext.mm b/submodules/TgVoipWebrtc/Sources/GroupCallThreadLocalContext.mm deleted file mode 100644 index 0d035d8f3b..0000000000 --- a/submodules/TgVoipWebrtc/Sources/GroupCallThreadLocalContext.mm +++ /dev/null @@ -1,58 +0,0 @@ - -#import - -#import "group/GroupInstanceImpl.h" - -@interface GroupCallThreadLocalContext () { - id _queue; - - std::unique_ptr _instance; -} - -@end - -@implementation GroupCallThreadLocalContext - -- (instancetype _Nonnull)initWithQueue:(id _Nonnull)queue relaySdpAnswer:(void (^ _Nonnull)(NSString * _Nonnull))relaySdpAnswer { - self = [super init]; - if (self != nil) { - _queue = queue; - - tgcalls::GroupInstanceDescriptor descriptor; - __weak GroupCallThreadLocalContext *weakSelf = self; - descriptor.sdpAnswerEmitted = [weakSelf, queue, relaySdpAnswer](std::string const &sdpAnswer) { - NSString *string = [NSString stringWithUTF8String:sdpAnswer.c_str()]; - [queue dispatch:^{ - __strong GroupCallThreadLocalContext *strongSelf = weakSelf; - if (strongSelf == nil) { - return; - } - relaySdpAnswer(string); - }]; - }; - - _instance.reset(new tgcalls::GroupInstanceImpl(std::move(descriptor))); - } - return self; -} - -- (void)emitOffer { - if (_instance) { - _instance->emitOffer(); - } -} - -- (void)setOfferSdp:(NSString * _Nonnull)offerSdp isPartial:(bool)isPartial { - if (_instance) { - _instance->setOfferSdp([offerSdp UTF8String], isPartial); - } -} - -- (void)setIsMuted:(bool)isMuted { - if (_instance) { - _instance->setIsMuted(isMuted); - } -} - -@end - diff --git a/submodules/TgVoipWebrtc/Sources/OngoingCallThreadLocalContext.mm b/submodules/TgVoipWebrtc/Sources/OngoingCallThreadLocalContext.mm index 9a27674497..e2098faedf 100644 --- a/submodules/TgVoipWebrtc/Sources/OngoingCallThreadLocalContext.mm +++ b/submodules/TgVoipWebrtc/Sources/OngoingCallThreadLocalContext.mm @@ -4,7 +4,6 @@ #import #endif - #import "Instance.h" #import "InstanceImpl.h" #import "reference/InstanceImplReference.h" @@ -22,6 +21,8 @@ #import "platform/darwin/GLVideoView.h" #endif +#import "group/GroupInstanceImpl.h" + @implementation OngoingCallConnectionDescriptionWebrtc - (instancetype _Nonnull)initWithConnectionId:(int64_t)connectionId hasStun:(bool)hasStun hasTurn:(bool)hasTurn ip:(NSString * _Nonnull)ip port:(int32_t)port username:(NSString * _Nonnull)username password:(NSString * _Nonnull)password { @@ -785,3 +786,61 @@ static void (*InternalVoipLoggingFunction)(NSString *) = NULL; } @end + + +@interface GroupCallThreadLocalContext () { + id _queue; + + std::unique_ptr _instance; + OngoingCallThreadLocalContextVideoCapturer *_videoCapturer; +} + +@end + +@implementation GroupCallThreadLocalContext + +- (instancetype _Nonnull)initWithQueue:(id _Nonnull)queue relaySdpAnswer:(void (^ _Nonnull)(NSString * _Nonnull))relaySdpAnswer videoCapturer:(OngoingCallThreadLocalContextVideoCapturer * _Nullable)videoCapturer { + self = [super init]; + if (self != nil) { + _queue = queue; + + _videoCapturer = videoCapturer; + + __weak GroupCallThreadLocalContext *weakSelf = self; + _instance.reset(new tgcalls::GroupInstanceImpl((tgcalls::GroupInstanceDescriptor){ + .sdpAnswerEmitted = [weakSelf, queue, relaySdpAnswer](std::string const &sdpAnswer) { + NSString *string = [NSString stringWithUTF8String:sdpAnswer.c_str()]; + [queue dispatch:^{ + __strong GroupCallThreadLocalContext *strongSelf = weakSelf; + if (strongSelf == nil) { + return; + } + relaySdpAnswer(string); + }]; + }, + .videoCapture = [_videoCapturer getInterface] + })); + } + return self; +} + +- (void)emitOffer { + if (_instance) { + _instance->emitOffer(); + } +} + +- (void)setOfferSdp:(NSString * _Nonnull)offerSdp isPartial:(bool)isPartial { + if (_instance) { + _instance->setOfferSdp([offerSdp UTF8String], isPartial); + } +} + +- (void)setIsMuted:(bool)isMuted { + if (_instance) { + _instance->setIsMuted(isMuted); + } +} + +@end + diff --git a/third-party/webrtc/BUILD b/third-party/webrtc/BUILD index d7f382d8da..cf8dc4e622 100644 --- a/third-party/webrtc/BUILD +++ b/third-party/webrtc/BUILD @@ -1,4 +1,4 @@ -use_gn_build = True +use_gn_build = False webrtc_libs = [ "libwebrtc.a",