mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-10-09 03:20:48 +00:00
WIP
This commit is contained in:
parent
a93646ea84
commit
a79856582e
@ -14,6 +14,7 @@ public final class GroupCallController: ViewController {
|
|||||||
private let context: AccountContext
|
private let context: AccountContext
|
||||||
private let presentationData: PresentationData
|
private let presentationData: PresentationData
|
||||||
|
|
||||||
|
private var videoCapturer: OngoingCallVideoCapturer?
|
||||||
private var callContext: GroupCallContext?
|
private var callContext: GroupCallContext?
|
||||||
private var callDisposable: Disposable?
|
private var callDisposable: Disposable?
|
||||||
private var memberCountDisposable: Disposable?
|
private var memberCountDisposable: Disposable?
|
||||||
@ -59,7 +60,10 @@ public final class GroupCallController: ViewController {
|
|||||||
}, availableOutputsChanged: { _, _ in
|
}, 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.callContext = callContext
|
||||||
|
|
||||||
self.memberCountDisposable = (callContext.memberCount
|
self.memberCountDisposable = (callContext.memberCount
|
||||||
|
@ -167,6 +167,11 @@ private struct ScrolledToMessageId: Equatable {
|
|||||||
var allowedReplacementDirection: AllowedReplacementDirections
|
var allowedReplacementDirection: AllowedReplacementDirections
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum ChatLoadingMessageSubject {
|
||||||
|
case generic
|
||||||
|
case pinnedMessage
|
||||||
|
}
|
||||||
|
|
||||||
public final class ChatControllerImpl: TelegramBaseController, ChatController, GalleryHiddenMediaTarget, UIDropInteractionDelegate {
|
public final class ChatControllerImpl: TelegramBaseController, ChatController, GalleryHiddenMediaTarget, UIDropInteractionDelegate {
|
||||||
private var validLayout: ContainerViewLayout?
|
private var validLayout: ContainerViewLayout?
|
||||||
|
|
||||||
@ -230,7 +235,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
private let unblockingPeer = ValuePromise<Bool>(false, ignoreRepeated: true)
|
private let unblockingPeer = ValuePromise<Bool>(false, ignoreRepeated: true)
|
||||||
private let searching = ValuePromise<Bool>(false, ignoreRepeated: true)
|
private let searching = ValuePromise<Bool>(false, ignoreRepeated: true)
|
||||||
private let searchResult = Promise<(SearchMessagesResult, SearchMessagesState, SearchMessagesLocation)?>()
|
private let searchResult = Promise<(SearchMessagesResult, SearchMessagesState, SearchMessagesLocation)?>()
|
||||||
private let loadingMessage = ValuePromise<Bool>(false, ignoreRepeated: true)
|
private let loadingMessage = ValuePromise<ChatLoadingMessageSubject?>(nil, ignoreRepeated: true)
|
||||||
private let performingInlineSearch = ValuePromise<Bool>(false, ignoreRepeated: true)
|
private let performingInlineSearch = ValuePromise<Bool>(false, ignoreRepeated: true)
|
||||||
|
|
||||||
private var preloadHistoryPeerId: PeerId?
|
private var preloadHistoryPeerId: PeerId?
|
||||||
@ -4740,7 +4745,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
strongSelf.loadingMessage.set(true)
|
strongSelf.loadingMessage.set(.generic)
|
||||||
|
|
||||||
let peerId: PeerId
|
let peerId: PeerId
|
||||||
let threadId: Int64?
|
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
|
strongSelf.messageIndexDisposable.set((searchMessageIdByTimestamp(account: strongSelf.context.account, peerId: peerId, threadId: threadId, timestamp: timestamp) |> deliverOnMainQueue).start(next: { messageId in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
strongSelf.loadingMessage.set(false)
|
strongSelf.loadingMessage.set(nil)
|
||||||
if let messageId = messageId {
|
if let messageId = messageId {
|
||||||
strongSelf.navigateToMessage(from: nil, to: .id(messageId), forceInCurrentChat: true)
|
strongSelf.navigateToMessage(from: nil, to: .id(messageId), forceInCurrentChat: true)
|
||||||
}
|
}
|
||||||
@ -4784,8 +4789,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
})
|
})
|
||||||
strongSelf.updateItemNodesSearchTextHighlightStates()
|
strongSelf.updateItemNodesSearchTextHighlightStates()
|
||||||
}
|
}
|
||||||
}, navigateToMessage: { [weak self] messageId, dropStack, forceInCurrentChat in
|
}, navigateToMessage: { [weak self] messageId, dropStack, forceInCurrentChat, statusSubject in
|
||||||
self?.navigateToMessage(from: nil, to: .id(messageId), forceInCurrentChat: forceInCurrentChat, dropStack: dropStack)
|
self?.navigateToMessage(from: nil, to: .id(messageId), forceInCurrentChat: forceInCurrentChat, dropStack: dropStack, statusSubject: statusSubject)
|
||||||
}, navigateToChat: { [weak self] peerId in
|
}, navigateToChat: { [weak self] peerId in
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
@ -9112,13 +9117,13 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
}
|
}
|
||||||
}, completed: { [weak self] in
|
}, completed: { [weak self] in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
strongSelf.loadingMessage.set(false)
|
strongSelf.loadingMessage.set(nil)
|
||||||
strongSelf.chatDisplayNode.historyNode.scrollToEndOfHistory()
|
strongSelf.chatDisplayNode.historyNode.scrollToEndOfHistory()
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
cancelImpl = { [weak self] in
|
cancelImpl = { [weak self] in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
strongSelf.loadingMessage.set(false)
|
strongSelf.loadingMessage.set(nil)
|
||||||
strongSelf.messageIndexDisposable.set(nil)
|
strongSelf.messageIndexDisposable.set(nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -9176,13 +9181,13 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
}
|
}
|
||||||
}, completed: { [weak self] in
|
}, completed: { [weak self] in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
strongSelf.loadingMessage.set(false)
|
strongSelf.loadingMessage.set(nil)
|
||||||
strongSelf.chatDisplayNode.historyNode.scrollToStartOfHistory()
|
strongSelf.chatDisplayNode.historyNode.scrollToStartOfHistory()
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
cancelImpl = { [weak self] in
|
cancelImpl = { [weak self] in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
strongSelf.loadingMessage.set(false)
|
strongSelf.loadingMessage.set(nil)
|
||||||
strongSelf.messageIndexDisposable.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)
|
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 {
|
if self.isNodeLoaded {
|
||||||
var fromIndex: MessageIndex?
|
var fromIndex: MessageIndex?
|
||||||
|
|
||||||
@ -9360,14 +9365,14 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
|
|
||||||
if let scrollFromIndex = scrollFromIndex {
|
if let scrollFromIndex = scrollFromIndex {
|
||||||
if let messageId = messageLocation.messageId, let message = self.chatDisplayNode.historyNode.messageInCurrentHistoryView(messageId) {
|
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.messageIndexDisposable.set(nil)
|
||||||
self.chatDisplayNode.historyNode.scrollToMessage(from: scrollFromIndex, to: message.index, animated: animated, scrollPosition: scrollPosition)
|
self.chatDisplayNode.historyNode.scrollToMessage(from: scrollFromIndex, to: message.index, animated: animated, scrollPosition: scrollPosition)
|
||||||
completion?()
|
completion?()
|
||||||
} else if case let .index(index) = messageLocation, index.id.id == 0, index.timestamp > 0, case .scheduledMessages = self.presentationInterfaceState.subject {
|
} 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)
|
self.chatDisplayNode.historyNode.scrollToMessage(from: scrollFromIndex, to: index, animated: animated, scrollPosition: scrollPosition)
|
||||||
} else {
|
} else {
|
||||||
self.loadingMessage.set(true)
|
self.loadingMessage.set(statusSubject)
|
||||||
let searchLocation: ChatHistoryInitialSearchLocation
|
let searchLocation: ChatHistoryInitialSearchLocation
|
||||||
switch messageLocation {
|
switch messageLocation {
|
||||||
case let .id(id):
|
case let .id(id):
|
||||||
@ -9377,7 +9382,11 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
case .upperBound:
|
case .upperBound:
|
||||||
searchLocation = .index(MessageIndex.upperBound(peerId: self.chatLocation.peerId))
|
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
|
let signal = historyView
|
||||||
|> mapToSignal { historyView -> Signal<(MessageIndex?, Bool), NoError> in
|
|> mapToSignal { historyView -> Signal<(MessageIndex?, Bool), NoError> in
|
||||||
switch historyView {
|
switch historyView {
|
||||||
@ -9441,12 +9450,12 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
}
|
}
|
||||||
}, completed: { [weak self] in
|
}, completed: { [weak self] in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
strongSelf.loadingMessage.set(false)
|
strongSelf.loadingMessage.set(nil)
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
cancelImpl = { [weak self] in
|
cancelImpl = { [weak self] in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
strongSelf.loadingMessage.set(false)
|
strongSelf.loadingMessage.set(nil)
|
||||||
strongSelf.messageIndexDisposable.set(nil)
|
strongSelf.messageIndexDisposable.set(nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -9468,7 +9477,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
if let _ = fromId, rememberInStack {
|
if let _ = fromId, rememberInStack {
|
||||||
self.historyNavigationStack.add(fromIndex)
|
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 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
|
let signal = historyView
|
||||||
|> mapToSignal { historyView -> Signal<MessageIndex?, NoError> in
|
|> mapToSignal { historyView -> Signal<MessageIndex?, NoError> in
|
||||||
@ -9499,7 +9508,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
}
|
}
|
||||||
}, completed: { [weak self] in
|
}, completed: { [weak self] in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
strongSelf.loadingMessage.set(false)
|
strongSelf.loadingMessage.set(nil)
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
} else {
|
} else {
|
||||||
|
@ -20,10 +20,10 @@ final class ChatPanelInterfaceInteractionStatuses {
|
|||||||
let startingBot: Signal<Bool, NoError>
|
let startingBot: Signal<Bool, NoError>
|
||||||
let unblockingPeer: Signal<Bool, NoError>
|
let unblockingPeer: Signal<Bool, NoError>
|
||||||
let searching: Signal<Bool, NoError>
|
let searching: Signal<Bool, NoError>
|
||||||
let loadingMessage: Signal<Bool, NoError>
|
let loadingMessage: Signal<ChatLoadingMessageSubject?, NoError>
|
||||||
let inlineSearch: Signal<Bool, NoError>
|
let inlineSearch: Signal<Bool, NoError>
|
||||||
|
|
||||||
init(editingMessage: Signal<Float?, NoError>, startingBot: Signal<Bool, NoError>, unblockingPeer: Signal<Bool, NoError>, searching: Signal<Bool, NoError>, loadingMessage: Signal<Bool, NoError>, inlineSearch: Signal<Bool, NoError>) {
|
init(editingMessage: Signal<Float?, NoError>, startingBot: Signal<Bool, NoError>, unblockingPeer: Signal<Bool, NoError>, searching: Signal<Bool, NoError>, loadingMessage: Signal<ChatLoadingMessageSubject?, NoError>, inlineSearch: Signal<Bool, NoError>) {
|
||||||
self.editingMessage = editingMessage
|
self.editingMessage = editingMessage
|
||||||
self.startingBot = startingBot
|
self.startingBot = startingBot
|
||||||
self.unblockingPeer = unblockingPeer
|
self.unblockingPeer = unblockingPeer
|
||||||
@ -73,7 +73,7 @@ final class ChatPanelInterfaceInteraction {
|
|||||||
let openSearchResults: () -> Void
|
let openSearchResults: () -> Void
|
||||||
let openCalendarSearch: () -> Void
|
let openCalendarSearch: () -> Void
|
||||||
let toggleMembersSearch: (Bool) -> Void
|
let toggleMembersSearch: (Bool) -> Void
|
||||||
let navigateToMessage: (MessageId, Bool, Bool) -> Void
|
let navigateToMessage: (MessageId, Bool, Bool, ChatLoadingMessageSubject) -> Void
|
||||||
let navigateToChat: (PeerId) -> Void
|
let navigateToChat: (PeerId) -> Void
|
||||||
let navigateToProfile: (PeerId) -> Void
|
let navigateToProfile: (PeerId) -> Void
|
||||||
let openPeerInfo: () -> Void
|
let openPeerInfo: () -> Void
|
||||||
@ -152,7 +152,7 @@ final class ChatPanelInterfaceInteraction {
|
|||||||
navigateMessageSearch: @escaping (ChatPanelSearchNavigationAction) -> Void,
|
navigateMessageSearch: @escaping (ChatPanelSearchNavigationAction) -> Void,
|
||||||
openCalendarSearch: @escaping () -> Void,
|
openCalendarSearch: @escaping () -> Void,
|
||||||
toggleMembersSearch: @escaping (Bool) -> Void,
|
toggleMembersSearch: @escaping (Bool) -> Void,
|
||||||
navigateToMessage: @escaping (MessageId, Bool, Bool) -> Void,
|
navigateToMessage: @escaping (MessageId, Bool, Bool, ChatLoadingMessageSubject) -> Void,
|
||||||
navigateToChat: @escaping (PeerId) -> Void,
|
navigateToChat: @escaping (PeerId) -> Void,
|
||||||
navigateToProfile: @escaping (PeerId) -> Void,
|
navigateToProfile: @escaping (PeerId) -> Void,
|
||||||
openPeerInfo: @escaping () -> Void,
|
openPeerInfo: @escaping () -> Void,
|
||||||
|
@ -15,17 +15,33 @@ import TelegramStringFormatting
|
|||||||
import AnimatedCountLabelNode
|
import AnimatedCountLabelNode
|
||||||
import AnimatedNavigationStripeNode
|
import AnimatedNavigationStripeNode
|
||||||
import ContextUI
|
import ContextUI
|
||||||
|
import RadialStatusNode
|
||||||
|
|
||||||
private enum PinnedMessageAnimation {
|
private enum PinnedMessageAnimation {
|
||||||
case slideToTop
|
case slideToTop
|
||||||
case slideToBottom
|
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 {
|
final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode {
|
||||||
private let context: AccountContext
|
private let context: AccountContext
|
||||||
private let tapButton: HighlightTrackingButtonNode
|
private let tapButton: HighlightTrackingButtonNode
|
||||||
|
private let buttonsContainer: ButtonsContainerNode
|
||||||
private let closeButton: HighlightableButtonNode
|
private let closeButton: HighlightableButtonNode
|
||||||
private let listButton: HighlightableButtonNode
|
private let listButton: HighlightableButtonNode
|
||||||
|
private let activityIndicator: RadialStatusNode
|
||||||
|
|
||||||
private let contextContainer: ContextControllerSourceNode
|
private let contextContainer: ContextControllerSourceNode
|
||||||
private let clippingContainer: ASDisplayNode
|
private let clippingContainer: ASDisplayNode
|
||||||
@ -47,6 +63,8 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode {
|
|||||||
private var isReplyThread: Bool = false
|
private var isReplyThread: Bool = false
|
||||||
|
|
||||||
private let fetchDisposable = MetaDisposable()
|
private let fetchDisposable = MetaDisposable()
|
||||||
|
|
||||||
|
private var statusDisposable: Disposable?
|
||||||
|
|
||||||
private let queue = Queue()
|
private let queue = Queue()
|
||||||
|
|
||||||
@ -55,6 +73,8 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode {
|
|||||||
|
|
||||||
self.tapButton = HighlightTrackingButtonNode()
|
self.tapButton = HighlightTrackingButtonNode()
|
||||||
|
|
||||||
|
self.buttonsContainer = ButtonsContainerNode()
|
||||||
|
|
||||||
self.closeButton = HighlightableButtonNode()
|
self.closeButton = HighlightableButtonNode()
|
||||||
self.closeButton.hitTestSlop = UIEdgeInsets(top: -8.0, left: -8.0, bottom: -8.0, right: -8.0)
|
self.closeButton.hitTestSlop = UIEdgeInsets(top: -8.0, left: -8.0, bottom: -8.0, right: -8.0)
|
||||||
self.closeButton.displaysAsynchronously = false
|
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.hitTestSlop = UIEdgeInsets(top: -8.0, left: -8.0, bottom: -8.0, right: -8.0)
|
||||||
self.listButton.displaysAsynchronously = false
|
self.listButton.displaysAsynchronously = false
|
||||||
|
|
||||||
|
self.activityIndicator = RadialStatusNode(backgroundNodeColor: .clear)
|
||||||
|
self.activityIndicator.isUserInteractionEnabled = false
|
||||||
|
|
||||||
self.separatorNode = ASDisplayNode()
|
self.separatorNode = ASDisplayNode()
|
||||||
self.separatorNode.isLayerBacked = true
|
self.separatorNode.isLayerBacked = true
|
||||||
|
|
||||||
@ -126,8 +149,9 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode {
|
|||||||
self.imageNodeContainer.addSubnode(self.imageNode)
|
self.imageNodeContainer.addSubnode(self.imageNode)
|
||||||
self.contentContainer.addSubnode(self.imageNodeContainer)
|
self.contentContainer.addSubnode(self.imageNodeContainer)
|
||||||
|
|
||||||
self.contextContainer.addSubnode(self.closeButton)
|
self.buttonsContainer.addSubnode(self.closeButton)
|
||||||
self.contextContainer.addSubnode(self.listButton)
|
self.buttonsContainer.addSubnode(self.listButton)
|
||||||
|
self.contextContainer.addSubnode(self.buttonsContainer)
|
||||||
|
|
||||||
self.tapButton.addTarget(self, action: #selector(self.tapped), forControlEvents: [.touchUpInside])
|
self.tapButton.addTarget(self, action: #selector(self.tapped), forControlEvents: [.touchUpInside])
|
||||||
self.contextContainer.addSubnode(self.tapButton)
|
self.contextContainer.addSubnode(self.tapButton)
|
||||||
@ -146,6 +170,7 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode {
|
|||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
self.fetchDisposable.dispose()
|
self.fetchDisposable.dispose()
|
||||||
|
self.statusDisposable?.dispose()
|
||||||
}
|
}
|
||||||
|
|
||||||
private var theme: PresentationTheme?
|
private var theme: PresentationTheme?
|
||||||
@ -165,6 +190,35 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode {
|
|||||||
self.separatorNode.backgroundColor = interfaceState.theme.chat.historyNavigation.strokeColor
|
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
|
let isReplyThread: Bool
|
||||||
if case .replyThread = interfaceState.chatLocation {
|
if case .replyThread = interfaceState.chatLocation {
|
||||||
isReplyThread = true
|
isReplyThread = true
|
||||||
@ -219,12 +273,17 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode {
|
|||||||
|
|
||||||
let rightInset: CGFloat = 18.0 + rightInset
|
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))
|
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))
|
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))
|
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))
|
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)))
|
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))
|
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 {
|
if self.isReplyThread {
|
||||||
interfaceInteraction.scrollToTop()
|
interfaceInteraction.scrollToTop()
|
||||||
} else {
|
} else {
|
||||||
interfaceInteraction.navigateToMessage(message.message.id, false, true)
|
interfaceInteraction.navigateToMessage(message.message.id, false, true, .pinnedMessage)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -76,7 +76,7 @@ final class ChatRecentActionsController: TelegramBaseController {
|
|||||||
}, navigateMessageSearch: { _ in
|
}, navigateMessageSearch: { _ in
|
||||||
}, openCalendarSearch: {
|
}, openCalendarSearch: {
|
||||||
}, toggleMembersSearch: { _ in
|
}, toggleMembersSearch: { _ in
|
||||||
}, navigateToMessage: { _, _, _ in
|
}, navigateToMessage: { _, _, _, _ in
|
||||||
}, navigateToChat: { _ in
|
}, navigateToChat: { _ in
|
||||||
}, navigateToProfile: { _ in
|
}, navigateToProfile: { _ in
|
||||||
}, openPeerInfo: {
|
}, openPeerInfo: {
|
||||||
|
@ -34,7 +34,7 @@ final class ChatSearchInputPanelNode: ChatInputPanelNode {
|
|||||||
didSet {
|
didSet {
|
||||||
if let statuses = self.interfaceInteraction?.statuses {
|
if let statuses = self.interfaceInteraction?.statuses {
|
||||||
self.activityDisposable.set((combineLatest((statuses.searching |> deliverOnMainQueue), (statuses.loadingMessage |> deliverOnMainQueue))).start(next: { [weak self] searching, loadingMessage in
|
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 {
|
if let strongSelf = self, strongSelf.displayActivity != value {
|
||||||
strongSelf.displayActivity = value
|
strongSelf.displayActivity = value
|
||||||
strongSelf.activityIndicator.isHidden = !value
|
strongSelf.activityIndicator.isHidden = !value
|
||||||
|
@ -347,7 +347,7 @@ final class EditAccessoryPanelNode: AccessoryPanelNode {
|
|||||||
|
|
||||||
@objc func contentTap(_ recognizer: UITapGestureRecognizer) {
|
@objc func contentTap(_ recognizer: UITapGestureRecognizer) {
|
||||||
if case .ended = recognizer.state, let message = self.currentMessage {
|
if case .ended = recognizer.state, let message = self.currentMessage {
|
||||||
self.interfaceInteraction?.navigateToMessage(message.id, false, true)
|
self.interfaceInteraction?.navigateToMessage(message.id, false, true, .generic)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -382,7 +382,7 @@ final class PeerInfoSelectionPanelNode: ASDisplayNode {
|
|||||||
}, navigateMessageSearch: { _ in
|
}, navigateMessageSearch: { _ in
|
||||||
}, openCalendarSearch: {
|
}, openCalendarSearch: {
|
||||||
}, toggleMembersSearch: { _ in
|
}, toggleMembersSearch: { _ in
|
||||||
}, navigateToMessage: { _, _, _ in
|
}, navigateToMessage: { _, _, _, _ in
|
||||||
}, navigateToChat: { _ in
|
}, navigateToChat: { _ in
|
||||||
}, navigateToProfile: { _ in
|
}, navigateToProfile: { _ in
|
||||||
}, openPeerInfo: {
|
}, openPeerInfo: {
|
||||||
|
@ -256,7 +256,7 @@ final class ReplyAccessoryPanelNode: AccessoryPanelNode {
|
|||||||
|
|
||||||
@objc func tapGesture(_ recognizer: UITapGestureRecognizer) {
|
@objc func tapGesture(_ recognizer: UITapGestureRecognizer) {
|
||||||
if case .ended = recognizer.state {
|
if case .ended = recognizer.state {
|
||||||
self.interfaceInteraction?.navigateToMessage(self.messageId, false, true)
|
self.interfaceInteraction?.navigateToMessage(self.messageId, false, true, .generic)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -497,9 +497,14 @@ private extension ConferenceDescription.Content.Channel.PayloadType {
|
|||||||
result["name"] = self.name
|
result["name"] = self.name
|
||||||
result["channels"] = self.channels
|
result["channels"] = self.channels
|
||||||
result["clockrate"] = self.clockrate
|
result["clockrate"] = self.clockrate
|
||||||
result["rtcp-fbs"] = [[
|
result["rtcp-fbs"] = [
|
||||||
"type": "transport-cc"
|
[
|
||||||
] as [String: Any]] as [Any]
|
"type": "transport-cc"
|
||||||
|
] as [String: Any],
|
||||||
|
/*[
|
||||||
|
"type": "nack"
|
||||||
|
] as [String: Any]*/
|
||||||
|
] as [Any]
|
||||||
if let parameters = self.parameters {
|
if let parameters = self.parameters {
|
||||||
result["parameters"] = parameters
|
result["parameters"] = parameters
|
||||||
}
|
}
|
||||||
@ -642,7 +647,8 @@ private extension ConferenceDescription.ChannelBundle {
|
|||||||
private struct RemoteOffer {
|
private struct RemoteOffer {
|
||||||
struct State: Equatable {
|
struct State: Equatable {
|
||||||
struct Item: Equatable {
|
struct Item: Equatable {
|
||||||
var ssrc: Int
|
var audioSsrc: Int
|
||||||
|
var videoSsrc: Int
|
||||||
var isRemoved: Bool
|
var isRemoved: Bool
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -650,7 +656,6 @@ private struct RemoteOffer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var sdpList: [String]
|
var sdpList: [String]
|
||||||
var isPartial: Bool
|
|
||||||
var state: State
|
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? {
|
func offerSdp(sessionId: UInt32, bundleId: String, bridgeHost: String, transport: ConferenceDescription.Transport, currentState: RemoteOffer.State?) -> RemoteOffer? {
|
||||||
struct Ssrc {
|
struct StreamSpec {
|
||||||
var isMain: Bool
|
var isMain: Bool
|
||||||
var value: Int
|
var audioSsrc: Int
|
||||||
var streamId: String
|
var videoSsrc: Int
|
||||||
var isRemoved: Bool
|
var isRemoved: Bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func createSdp(sessionId: UInt32, bundleSsrcs: [Ssrc], isPartial: Bool) -> String {
|
func createSdp(sessionId: UInt32, bundleStreams: [StreamSpec]) -> String {
|
||||||
var sdp = ""
|
var sdp = ""
|
||||||
func appendSdp(_ string: String) {
|
func appendSdp(_ string: String) {
|
||||||
if !sdp.isEmpty {
|
if !sdp.isEmpty {
|
||||||
@ -687,98 +692,131 @@ private extension ConferenceDescription {
|
|||||||
appendSdp("s=-")
|
appendSdp("s=-")
|
||||||
appendSdp("t=0 0")
|
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")
|
appendSdp("a=ice-lite")
|
||||||
|
|
||||||
for ssrc in bundleSsrcs {
|
for stream in bundleStreams {
|
||||||
appendSdp("m=audio \(ssrc.isMain ? "1" : "0") RTP/SAVPF 111 126")
|
appendSdp("m=audio \(stream.isMain ? "1" : "0") RTP/SAVPF 111 126")
|
||||||
if ssrc.isMain {
|
if stream.isMain {
|
||||||
appendSdp("c=IN IP4 0.0.0.0")
|
appendSdp("c=IN IP4 0.0.0.0")
|
||||||
}
|
}
|
||||||
appendSdp("a=mid:audio\(ssrc.value)")
|
appendSdp("a=mid:audio\(stream.audioSsrc)")
|
||||||
if ssrc.isRemoved {
|
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")
|
appendSdp("a=inactive")
|
||||||
continue
|
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 {
|
} 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=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("")
|
appendSdp("")
|
||||||
@ -786,82 +824,84 @@ private extension ConferenceDescription {
|
|||||||
return sdp
|
return sdp
|
||||||
}
|
}
|
||||||
|
|
||||||
var ssrcList: [Ssrc] = []
|
var streams: [StreamSpec] = []
|
||||||
var maybeMainSsrcId: Int?
|
var maybeMainStreamAudioSsrc: Int?
|
||||||
for content in self.contents {
|
|
||||||
for channel in content.channels {
|
for audioContent in self.contents {
|
||||||
if channel.endpoint == bundleId {
|
if audioContent.name != "audio" {
|
||||||
precondition(channel.sources.count == 1)
|
continue
|
||||||
ssrcList.append(contentsOf: channel.sources.map { ssrc in
|
}
|
||||||
return Ssrc(
|
for audioChannel in audioContent.channels {
|
||||||
isMain: true,
|
for videoContent in self.contents {
|
||||||
value: ssrc,
|
if videoContent.name != "video" {
|
||||||
streamId: "stream0",
|
continue
|
||||||
isRemoved: false
|
}
|
||||||
)
|
for videoChannel in videoContent.channels {
|
||||||
})
|
if videoChannel.channelBundleId == audioChannel.channelBundleId {
|
||||||
maybeMainSsrcId = channel.sources[0]
|
if audioChannel.channelBundleId == bundleId {
|
||||||
} else {
|
precondition(audioChannel.sources.count == 1)
|
||||||
precondition(channel.ssrcs.count <= 1)
|
precondition(videoChannel.sources.count == 1)
|
||||||
ssrcList.append(contentsOf: channel.ssrcs.map { ssrc in
|
streams.append(StreamSpec(
|
||||||
return Ssrc(
|
isMain: true,
|
||||||
isMain: false,
|
audioSsrc: audioChannel.sources[0],
|
||||||
value: ssrc,
|
videoSsrc: videoChannel.sources[0],
|
||||||
streamId: "stream\(ssrc)",
|
isRemoved: false
|
||||||
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()
|
preconditionFailure()
|
||||||
}
|
}
|
||||||
|
|
||||||
var bundleSsrcs: [Ssrc] = []
|
var bundleStreams: [StreamSpec] = []
|
||||||
if let currentState = currentState {
|
if let currentState = currentState {
|
||||||
for item in currentState.items {
|
for item in currentState.items {
|
||||||
let isRemoved = !ssrcList.contains(where: { $0.value == item.ssrc })
|
let isRemoved = !streams.contains(where: { $0.audioSsrc == item.audioSsrc })
|
||||||
bundleSsrcs.append(Ssrc(
|
bundleStreams.append(StreamSpec(
|
||||||
isMain: item.ssrc == mainSsrcId,
|
isMain: item.audioSsrc == mainStreamAudioSsrc,
|
||||||
value: item.ssrc,
|
audioSsrc: item.audioSsrc,
|
||||||
streamId: item.ssrc == mainSsrcId ? "audio0" : "stream\(item.ssrc)",
|
videoSsrc: item.videoSsrc,
|
||||||
isRemoved: isRemoved
|
isRemoved: isRemoved
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for ssrc in ssrcList {
|
for stream in streams {
|
||||||
if bundleSsrcs.contains(where: { $0.value == ssrc.value }) {
|
if bundleStreams.contains(where: { $0.audioSsrc == stream.audioSsrc }) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
bundleSsrcs.append(ssrc)
|
bundleStreams.append(stream)
|
||||||
}
|
}
|
||||||
|
|
||||||
var sdpList: [String] = []
|
var sdpList: [String] = []
|
||||||
|
|
||||||
sdpList.append(createSdp(sessionId: sessionId, bundleSsrcs: bundleSsrcs, isPartial: false))
|
sdpList.append(createSdp(sessionId: sessionId, bundleStreams: bundleStreams))
|
||||||
|
|
||||||
/*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))
|
|
||||||
}
|
|
||||||
}*/
|
|
||||||
|
|
||||||
return RemoteOffer(
|
return RemoteOffer(
|
||||||
sdpList: sdpList,
|
sdpList: sdpList,
|
||||||
isPartial: false,
|
|
||||||
state: RemoteOffer.State(
|
state: RemoteOffer.State(
|
||||||
items: bundleSsrcs.map { ssrc in
|
items: bundleStreams.map { stream in
|
||||||
RemoteOffer.State.Item(
|
RemoteOffer.State.Item(
|
||||||
ssrc: ssrc.value,
|
audioSsrc: stream.audioSsrc,
|
||||||
isRemoved: ssrc.isRemoved
|
videoSsrc: stream.videoSsrc,
|
||||||
|
isRemoved: stream.isRemoved
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ -870,10 +910,15 @@ private extension ConferenceDescription {
|
|||||||
|
|
||||||
mutating func updateLocalChannelFromSdpAnswer(bundleId: String, sdpAnswer: String) {
|
mutating func updateLocalChannelFromSdpAnswer(bundleId: String, sdpAnswer: String) {
|
||||||
var maybeAudioChannel: ConferenceDescription.Content.Channel?
|
var maybeAudioChannel: ConferenceDescription.Content.Channel?
|
||||||
|
var maybeVideoChannel: ConferenceDescription.Content.Channel?
|
||||||
for content in self.contents {
|
for content in self.contents {
|
||||||
for channel in content.channels {
|
for channel in content.channels {
|
||||||
if channel.endpoint == bundleId {
|
if channel.endpoint == bundleId {
|
||||||
maybeAudioChannel = channel
|
if content.name == "audio" {
|
||||||
|
maybeAudioChannel = channel
|
||||||
|
} else if content.name == "video" {
|
||||||
|
maybeVideoChannel = channel
|
||||||
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -883,8 +928,33 @@ private extension ConferenceDescription {
|
|||||||
assert(false)
|
assert(false)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
guard var videoChannel = maybeVideoChannel else {
|
||||||
|
assert(false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
let lines = sdpAnswer.components(separatedBy: "\n")
|
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] {
|
func getLines(prefix: String) -> [String] {
|
||||||
var result: [String] = []
|
var result: [String] = []
|
||||||
for line in lines {
|
for line in lines {
|
||||||
@ -899,8 +969,23 @@ private extension ConferenceDescription {
|
|||||||
return result
|
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] = []
|
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)
|
let scanner = Scanner(string: line)
|
||||||
if #available(iOS 13.0, *) {
|
if #available(iOS 13.0, *) {
|
||||||
if let ssrc = scanner.scanInt() {
|
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.sources = audioSources
|
||||||
/*audioChannel.ssrcGroups = [ConferenceDescription.Content.Channel.SsrcGroup(
|
/*audioChannel.ssrcGroups = [ConferenceDescription.Content.Channel.SsrcGroup(
|
||||||
@ -927,23 +1022,16 @@ private extension ConferenceDescription {
|
|||||||
"fmtp": [
|
"fmtp": [
|
||||||
"minptime=10;useinbandfec=1"
|
"minptime=10;useinbandfec=1"
|
||||||
] as [Any],
|
] as [Any],
|
||||||
"rtcp-fbs": [[
|
"rtcp-fbs": [
|
||||||
"type": "transport-cc"
|
[
|
||||||
] as [String: Any]] as [Any]
|
"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(
|
ConferenceDescription.Content.Channel.PayloadType(
|
||||||
id: 126,
|
id: 126,
|
||||||
name: "telephone-event",
|
name: "telephone-event",
|
||||||
@ -963,7 +1051,46 @@ private extension ConferenceDescription {
|
|||||||
),
|
),
|
||||||
ConferenceDescription.Content.Channel.RtpHdrExt(
|
ConferenceDescription.Content.Channel.RtpHdrExt(
|
||||||
id: 5,
|
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 {
|
for j in 0 ..< self.contents[i].channels.count {
|
||||||
if self.contents[i].channels[j].endpoint == bundleId {
|
if self.contents[i].channels[j].endpoint == bundleId {
|
||||||
self.contents[i].channels[j] = audioChannel
|
if self.contents[i].name == "audio" {
|
||||||
break outerContents
|
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
|
private var isMutedValue: Bool = false
|
||||||
let isMuted = ValuePromise<Bool>(false, ignoreRepeated: true)
|
let isMuted = ValuePromise<Bool>(false, ignoreRepeated: true)
|
||||||
|
|
||||||
init(queue: Queue, audioSessionActive: Signal<Bool, NoError>) {
|
init(queue: Queue, audioSessionActive: Signal<Bool, NoError>, video: OngoingCallVideoCapturer?) {
|
||||||
self.queue = queue
|
self.queue = queue
|
||||||
|
|
||||||
self.sessionId = UInt32.random(in: 0 ..< UInt32(Int32.max))
|
self.sessionId = UInt32.random(in: 0 ..< UInt32(Int32.max))
|
||||||
self.colibriHost = "51.11.141.27"
|
self.colibriHost = "192.168.8.118"
|
||||||
//self.colibriHost = "192.168.93.24"
|
|
||||||
//self.colibriHost = "51.104.206.109"
|
|
||||||
|
|
||||||
var relaySdpAnswerImpl: ((String) -> Void)?
|
var relaySdpAnswerImpl: ((String) -> Void)?
|
||||||
|
|
||||||
@ -1151,7 +1279,7 @@ public final class GroupCallContext {
|
|||||||
queue.async {
|
queue.async {
|
||||||
relaySdpAnswerImpl?(sdpAnswer)
|
relaySdpAnswerImpl?(sdpAnswer)
|
||||||
}
|
}
|
||||||
})
|
}, videoCapturer: video?.impl)
|
||||||
|
|
||||||
relaySdpAnswerImpl = { [weak self] sdpAnswer in
|
relaySdpAnswerImpl = { [weak self] sdpAnswer in
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
@ -1231,8 +1359,23 @@ public final class GroupCallContext {
|
|||||||
payloadTypes: [],
|
payloadTypes: [],
|
||||||
rtpHdrExts: []
|
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 {
|
for i in 0 ..< conference.contents.count {
|
||||||
if conference.contents[i].name == "audio" {
|
if conference.contents[i].name == "audio" {
|
||||||
for j in 0 ..< conference.contents[i].channels.count {
|
for j in 0 ..< conference.contents[i].channels.count {
|
||||||
@ -1253,16 +1396,43 @@ public final class GroupCallContext {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
conference.contents[i].channels.append(audioChannel)
|
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
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !foundContent {
|
if !foundAudioContent {
|
||||||
conference.contents.append(ConferenceDescription.Content(
|
conference.contents.append(ConferenceDescription.Content(
|
||||||
name: "audio",
|
name: "audio",
|
||||||
channels: [audioChannel]
|
channels: [audioChannel]
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
if !foundVideoContent {
|
||||||
|
conference.contents.append(ConferenceDescription.Content(
|
||||||
|
name: "video",
|
||||||
|
channels: [videoChannel]
|
||||||
|
))
|
||||||
|
}
|
||||||
conference.channelBundles.append(ConferenceDescription.ChannelBundle(
|
conference.channelBundles.append(ConferenceDescription.ChannelBundle(
|
||||||
id: bundleId,
|
id: bundleId,
|
||||||
transport: ConferenceDescription.Transport(
|
transport: ConferenceDescription.Transport(
|
||||||
@ -1329,7 +1499,7 @@ public final class GroupCallContext {
|
|||||||
strongSelf.memberCount.set(offer.state.items.filter({ !$0.isRemoved }).count)
|
strongSelf.memberCount.set(offer.state.items.filter({ !$0.isRemoved }).count)
|
||||||
|
|
||||||
for sdp in offer.sdpList {
|
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)
|
strongSelf.memberCount.set(offer.state.items.filter({ !$0.isRemoved }).count)
|
||||||
|
|
||||||
for sdp in offer.sdpList {
|
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 queue = Queue()
|
||||||
private let impl: QueueLocalObject<Impl>
|
private let impl: QueueLocalObject<Impl>
|
||||||
|
|
||||||
public init(audioSessionActive: Signal<Bool, NoError>) {
|
public init(audioSessionActive: Signal<Bool, NoError>, video: OngoingCallVideoCapturer?) {
|
||||||
let queue = self.queue
|
let queue = self.queue
|
||||||
self.impl = QueueLocalObject(queue: queue, generate: {
|
self.impl = QueueLocalObject(queue: queue, generate: {
|
||||||
return Impl(queue: queue, audioSessionActive: audioSessionActive)
|
return Impl(queue: queue, audioSessionActive: audioSessionActive, video: video)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -333,7 +333,7 @@ extension OngoingCallThreadLocalContext: OngoingCallThreadLocalContextProtocol {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public final class OngoingCallVideoCapturer {
|
public final class OngoingCallVideoCapturer {
|
||||||
fileprivate let impl: OngoingCallThreadLocalContextVideoCapturer
|
internal let impl: OngoingCallThreadLocalContextVideoCapturer
|
||||||
|
|
||||||
public init() {
|
public init() {
|
||||||
self.impl = OngoingCallThreadLocalContextVideoCapturer()
|
self.impl = OngoingCallThreadLocalContextVideoCapturer()
|
||||||
|
@ -1,18 +0,0 @@
|
|||||||
#ifndef GroupCallThreadLocalContext_h
|
|
||||||
#define GroupCallThreadLocalContext_h
|
|
||||||
|
|
||||||
#import <Foundation/Foundation.h>
|
|
||||||
|
|
||||||
#import <TgVoipWebrtc/OngoingCallThreadLocalContext.h>
|
|
||||||
|
|
||||||
@interface GroupCallThreadLocalContext : NSObject
|
|
||||||
|
|
||||||
- (instancetype _Nonnull)initWithQueue:(id<OngoingCallThreadLocalContextQueueWebrtc> _Nonnull)queue relaySdpAnswer:(void (^ _Nonnull)(NSString * _Nonnull))relaySdpAnswer;
|
|
||||||
|
|
||||||
- (void)emitOffer;
|
|
||||||
- (void)setOfferSdp:(NSString * _Nonnull)offerSdp isPartial:(bool)isPartial;
|
|
||||||
- (void)setIsMuted:(bool)isMuted;
|
|
||||||
|
|
||||||
@end
|
|
||||||
|
|
||||||
#endif
|
|
@ -150,4 +150,15 @@ typedef NS_ENUM(int32_t, OngoingCallDataSavingWebrtc) {
|
|||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
|
|
||||||
|
@interface GroupCallThreadLocalContext : NSObject
|
||||||
|
|
||||||
|
- (instancetype _Nonnull)initWithQueue:(id<OngoingCallThreadLocalContextQueueWebrtc> _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
|
#endif
|
||||||
|
@ -1,58 +0,0 @@
|
|||||||
|
|
||||||
#import <TgVoipWebrtc/GroupCallThreadLocalContext.h>
|
|
||||||
|
|
||||||
#import "group/GroupInstanceImpl.h"
|
|
||||||
|
|
||||||
@interface GroupCallThreadLocalContext () {
|
|
||||||
id<OngoingCallThreadLocalContextQueueWebrtc> _queue;
|
|
||||||
|
|
||||||
std::unique_ptr<tgcalls::GroupInstanceImpl> _instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
@end
|
|
||||||
|
|
||||||
@implementation GroupCallThreadLocalContext
|
|
||||||
|
|
||||||
- (instancetype _Nonnull)initWithQueue:(id<OngoingCallThreadLocalContextQueueWebrtc> _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
|
|
||||||
|
|
@ -4,7 +4,6 @@
|
|||||||
#import <TgVoipWebrtc/OngoingCallThreadLocalContext.h>
|
#import <TgVoipWebrtc/OngoingCallThreadLocalContext.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
#import "Instance.h"
|
#import "Instance.h"
|
||||||
#import "InstanceImpl.h"
|
#import "InstanceImpl.h"
|
||||||
#import "reference/InstanceImplReference.h"
|
#import "reference/InstanceImplReference.h"
|
||||||
@ -22,6 +21,8 @@
|
|||||||
#import "platform/darwin/GLVideoView.h"
|
#import "platform/darwin/GLVideoView.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#import "group/GroupInstanceImpl.h"
|
||||||
|
|
||||||
@implementation OngoingCallConnectionDescriptionWebrtc
|
@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 {
|
- (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
|
@end
|
||||||
|
|
||||||
|
|
||||||
|
@interface GroupCallThreadLocalContext () {
|
||||||
|
id<OngoingCallThreadLocalContextQueueWebrtc> _queue;
|
||||||
|
|
||||||
|
std::unique_ptr<tgcalls::GroupInstanceImpl> _instance;
|
||||||
|
OngoingCallThreadLocalContextVideoCapturer *_videoCapturer;
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
@implementation GroupCallThreadLocalContext
|
||||||
|
|
||||||
|
- (instancetype _Nonnull)initWithQueue:(id<OngoingCallThreadLocalContextQueueWebrtc> _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
|
||||||
|
|
||||||
|
2
third-party/webrtc/BUILD
vendored
2
third-party/webrtc/BUILD
vendored
@ -1,4 +1,4 @@
|
|||||||
use_gn_build = True
|
use_gn_build = False
|
||||||
|
|
||||||
webrtc_libs = [
|
webrtc_libs = [
|
||||||
"libwebrtc.a",
|
"libwebrtc.a",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user