mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-10-08 19:10:53 +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 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
|
||||
|
@ -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<Bool>(false, ignoreRepeated: true)
|
||||
private let searching = ValuePromise<Bool>(false, ignoreRepeated: true)
|
||||
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 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<MessageIndex?, NoError> 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 {
|
||||
|
@ -20,10 +20,10 @@ final class ChatPanelInterfaceInteractionStatuses {
|
||||
let startingBot: Signal<Bool, NoError>
|
||||
let unblockingPeer: Signal<Bool, NoError>
|
||||
let searching: Signal<Bool, NoError>
|
||||
let loadingMessage: Signal<Bool, NoError>
|
||||
let loadingMessage: Signal<ChatLoadingMessageSubject?, 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.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,
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -76,7 +76,7 @@ final class ChatRecentActionsController: TelegramBaseController {
|
||||
}, navigateMessageSearch: { _ in
|
||||
}, openCalendarSearch: {
|
||||
}, toggleMembersSearch: { _ in
|
||||
}, navigateToMessage: { _, _, _ in
|
||||
}, navigateToMessage: { _, _, _, _ in
|
||||
}, navigateToChat: { _ in
|
||||
}, navigateToProfile: { _ in
|
||||
}, openPeerInfo: {
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -382,7 +382,7 @@ final class PeerInfoSelectionPanelNode: ASDisplayNode {
|
||||
}, navigateMessageSearch: { _ in
|
||||
}, openCalendarSearch: {
|
||||
}, toggleMembersSearch: { _ in
|
||||
}, navigateToMessage: { _, _, _ in
|
||||
}, navigateToMessage: { _, _, _, _ in
|
||||
}, navigateToChat: { _ in
|
||||
}, navigateToProfile: { _ in
|
||||
}, openPeerInfo: {
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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<Bool>(false, ignoreRepeated: true)
|
||||
|
||||
init(queue: Queue, audioSessionActive: Signal<Bool, NoError>) {
|
||||
init(queue: Queue, audioSessionActive: Signal<Bool, NoError>, 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<Impl>
|
||||
|
||||
public init(audioSessionActive: Signal<Bool, NoError>) {
|
||||
public init(audioSessionActive: Signal<Bool, NoError>, 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)
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -333,7 +333,7 @@ extension OngoingCallThreadLocalContext: OngoingCallThreadLocalContextProtocol {
|
||||
}
|
||||
|
||||
public final class OngoingCallVideoCapturer {
|
||||
fileprivate let impl: OngoingCallThreadLocalContextVideoCapturer
|
||||
internal let impl: OngoingCallThreadLocalContextVideoCapturer
|
||||
|
||||
public init() {
|
||||
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
|
||||
|
||||
|
||||
@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
|
||||
|
@ -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>
|
||||
#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<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 = [
|
||||
"libwebrtc.a",
|
||||
|
Loading…
x
Reference in New Issue
Block a user