This commit is contained in:
Ali 2020-11-03 21:16:20 +04:00
parent a93646ea84
commit a79856582e
16 changed files with 521 additions and 285 deletions

View File

@ -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

View File

@ -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 {

View File

@ -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,

View File

@ -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)
}
}
}

View File

@ -76,7 +76,7 @@ final class ChatRecentActionsController: TelegramBaseController {
}, navigateMessageSearch: { _ in
}, openCalendarSearch: {
}, toggleMembersSearch: { _ in
}, navigateToMessage: { _, _, _ in
}, navigateToMessage: { _, _, _, _ in
}, navigateToChat: { _ in
}, navigateToProfile: { _ in
}, openPeerInfo: {

View File

@ -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

View File

@ -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)
}
}
}

View File

@ -382,7 +382,7 @@ final class PeerInfoSelectionPanelNode: ASDisplayNode {
}, navigateMessageSearch: { _ in
}, openCalendarSearch: {
}, toggleMembersSearch: { _ in
}, navigateToMessage: { _, _, _ in
}, navigateToMessage: { _, _, _, _ in
}, navigateToChat: { _ in
}, navigateToProfile: { _ in
}, openPeerInfo: {

View File

@ -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)
}
}
}

View File

@ -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)
})
}

View File

@ -333,7 +333,7 @@ extension OngoingCallThreadLocalContext: OngoingCallThreadLocalContextProtocol {
}
public final class OngoingCallVideoCapturer {
fileprivate let impl: OngoingCallThreadLocalContextVideoCapturer
internal let impl: OngoingCallThreadLocalContextVideoCapturer
public init() {
self.impl = OngoingCallThreadLocalContextVideoCapturer()

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -1,4 +1,4 @@
use_gn_build = True
use_gn_build = False
webrtc_libs = [
"libwebrtc.a",