This commit is contained in:
Ali 2020-10-16 15:10:19 +04:00
parent 303fa91caa
commit 799bae0cc2
6 changed files with 379 additions and 305 deletions

View File

@ -13,6 +13,7 @@ import ItemListUI
import PresentationDataUtils import PresentationDataUtils
import OverlayStatusController import OverlayStatusController
import AccountContext import AccountContext
import TelegramCallsUI
@objc private final class DebugControllerMailComposeDelegate: NSObject, MFMailComposeViewControllerDelegate { @objc private final class DebugControllerMailComposeDelegate: NSObject, MFMailComposeViewControllerDelegate {
public func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) { public func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) {
@ -74,6 +75,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
case alternativeFolderTabs(Bool) case alternativeFolderTabs(Bool)
case playerEmbedding(Bool) case playerEmbedding(Bool)
case playlistPlayback(Bool) case playlistPlayback(Bool)
case voiceConference
case preferredVideoCodec(Int, String, String?, Bool) case preferredVideoCodec(Int, String, String?, Bool)
case disableVideoAspectScaling(Bool) case disableVideoAspectScaling(Bool)
case enableVoipTcp(Bool) case enableVoipTcp(Bool)
@ -90,7 +92,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
return DebugControllerSection.logging.rawValue return DebugControllerSection.logging.rawValue
case .enableRaiseToSpeak, .keepChatNavigationStack, .skipReadHistory, .crashOnSlowQueries: case .enableRaiseToSpeak, .keepChatNavigationStack, .skipReadHistory, .crashOnSlowQueries:
return DebugControllerSection.experiments.rawValue return DebugControllerSection.experiments.rawValue
case .clearTips, .reimport, .resetData, .resetDatabase, .resetDatabaseAndCache, .resetHoles, .reindexUnread, .resetBiometricsData, .optimizeDatabase, .photoPreview, .knockoutWallpaper, .alternativeFolderTabs, .playerEmbedding, .playlistPlayback: case .clearTips, .reimport, .resetData, .resetDatabase, .resetDatabaseAndCache, .resetHoles, .reindexUnread, .resetBiometricsData, .optimizeDatabase, .photoPreview, .knockoutWallpaper, .alternativeFolderTabs, .playerEmbedding, .playlistPlayback, .voiceConference:
return DebugControllerSection.experiments.rawValue return DebugControllerSection.experiments.rawValue
case .preferredVideoCodec: case .preferredVideoCodec:
return DebugControllerSection.videoExperiments.rawValue return DebugControllerSection.videoExperiments.rawValue
@ -155,8 +157,10 @@ private enum DebugControllerEntry: ItemListNodeEntry {
return 24 return 24
case .playlistPlayback: case .playlistPlayback:
return 25 return 25
case .voiceConference:
return 26
case let .preferredVideoCodec(index, _, _, _): case let .preferredVideoCodec(index, _, _, _):
return 26 + index return 27 + index
case .disableVideoAspectScaling: case .disableVideoAspectScaling:
return 100 return 100
case .enableVoipTcp: case .enableVoipTcp:
@ -648,6 +652,15 @@ private enum DebugControllerEntry: ItemListNodeEntry {
}) })
}).start() }).start()
}) })
case .voiceConference:
return ItemListDisclosureItem(presentationData: presentationData, title: "Voice Conference (Test)", label: "", sectionId: self.section, style: .blocks, action: {
guard let context = arguments.context else {
return
}
let controller = GroupCallController(context: context)
controller.navigationPresentation = .modal
arguments.pushController(controller)
})
case let .preferredVideoCodec(_, title, value, isSelected): case let .preferredVideoCodec(_, title, value, isSelected):
return ItemListCheckboxItem(presentationData: presentationData, title: title, style: .right, checked: isSelected, zeroSeparatorInsets: false, sectionId: self.section, action: { return ItemListCheckboxItem(presentationData: presentationData, title: title, style: .right, checked: isSelected, zeroSeparatorInsets: false, sectionId: self.section, action: {
let _ = arguments.sharedContext.accountManager.transaction ({ transaction in let _ = arguments.sharedContext.accountManager.transaction ({ transaction in
@ -725,6 +738,8 @@ private func debugControllerEntries(presentationData: PresentationData, loggingS
entries.append(.playerEmbedding(experimentalSettings.playerEmbedding)) entries.append(.playerEmbedding(experimentalSettings.playerEmbedding))
entries.append(.playlistPlayback(experimentalSettings.playlistPlayback)) entries.append(.playlistPlayback(experimentalSettings.playlistPlayback))
entries.append(.voiceConference)
let codecs: [(String, String?)] = [ let codecs: [(String, String?)] = [
("No Preference", nil), ("No Preference", nil),
("H265", "H265"), ("H265", "H265"),

View File

@ -0,0 +1,109 @@
import Foundation
import UIKit
import Display
import AsyncDisplayKit
import SwiftSignalKit
import TelegramPresentationData
import TelegramUIPreferences
import TelegramVoip
import TelegramAudio
import AccountContext
public final class GroupCallController: ViewController {
private final class Node: ViewControllerTracingNode {
private let context: AccountContext
private let presentationData: PresentationData
private var callContext: GroupCallContext?
private var callDisposable: Disposable?
private var memberCountDisposable: Disposable?
private let audioSessionActive = Promise<Bool>(false)
private var memberCount: Int = 0
private let memberCountNode: ImmediateTextNode
private var validLayout: ContainerViewLayout?
init(context: AccountContext) {
self.context = context
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
self.memberCountNode = ImmediateTextNode()
super.init()
self.backgroundColor = self.presentationData.theme.list.plainBackgroundColor
self.addSubnode(self.memberCountNode)
let audioSessionActive = self.audioSessionActive
self.callDisposable = self.context.sharedContext.mediaManager.audioSession.push(audioSessionType: .voiceCall, manualActivate: { audioSessionControl in
audioSessionControl.activate({ _ in })
audioSessionActive.set(.single(true))
}, deactivate: {
return Signal { subscriber in
subscriber.putCompletion()
return EmptyDisposable
}
}, availableOutputsChanged: { _, _ in
})
let callContext = GroupCallContext(audioSessionActive: self.audioSessionActive.get())
self.callContext = callContext
memberCountDisposable = (callContext.memberCount
|> deliverOnMainQueue).start(next: { [weak self] value in
guard let strongSelf = self else {
return
}
strongSelf.memberCount = value
if let layout = strongSelf.validLayout {
strongSelf.containerLayoutUpdated(layout, transition: .immediate)
}
})
}
deinit {
self.callDisposable?.dispose()
self.memberCountDisposable?.dispose()
}
func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
self.validLayout = layout
self.memberCountNode.attributedText = NSAttributedString(string: "Members: \(self.memberCount)", font: Font.regular(17.0), textColor: self.presentationData.theme.list.itemPrimaryTextColor)
let textSize = self.memberCountNode.updateLayout(CGSize(width: layout.size.width - 16.0 * 2.0, height: 100.0))
transition.updateFrameAdditiveToCenter(node: self.memberCountNode, frame: CGRect(origin: CGPoint(x: floor((layout.size.width - textSize.width) / 2.0), y: floor((layout.size.width - textSize.width) / 2.0)), size: textSize))
}
}
private let context: AccountContext
private let presentationData: PresentationData
private var controllerNode: Node {
return self.displayNode as! Node
}
public init(context: AccountContext) {
self.context = context
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData))
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override public func loadDisplayNode() {
self.displayNode = Node(context: self.context)
self.displayNodeDidLoad()
}
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
super.containerLayoutUpdated(layout, transition: transition)
self.controllerNode.containerLayoutUpdated(layout, transition: transition)
}
}

View File

@ -497,6 +497,9 @@ private extension ConferenceDescription.Content.Channel.PayloadType {
result["name"] = self.name result["name"] = self.name
result["channels"] = self.channels result["channels"] = self.channels
result["clockrate"] = self.clockrate result["clockrate"] = self.clockrate
result["rtcp-fbs"] = [[
"type": "transport-cc"
] as [String: Any]] as [Any]
if let parameters = self.parameters { if let parameters = self.parameters {
result["parameters"] = parameters result["parameters"] = parameters
} }
@ -544,6 +547,7 @@ private extension ConferenceDescription.Content.Channel {
if !self.rtpHdrExts.isEmpty { if !self.rtpHdrExts.isEmpty {
result["rtp-hdrexts"] = self.rtpHdrExts.map { $0.outgoingColibriDescription() } result["rtp-hdrexts"] = self.rtpHdrExts.map { $0.outgoingColibriDescription() }
} }
result["rtcp-mux"] = true
return result return result
} }
@ -635,6 +639,21 @@ private extension ConferenceDescription.ChannelBundle {
} }
} }
private struct RemoteOffer {
struct State: Equatable {
struct Item: Equatable {
var ssrc: Int
var isRemoved: Bool
}
var items: [Item]
}
var sdpList: [String]
var isPartial: Bool
var state: State
}
private extension ConferenceDescription { private extension ConferenceDescription {
func outgoingColibriDescription() -> [String: Any] { func outgoingColibriDescription() -> [String: Any] {
var result: [String: Any] = [:] var result: [String: Any] = [:]
@ -646,35 +665,15 @@ private extension ConferenceDescription {
return result return result
} }
func offerSdp(sessionId: UInt32, bundleId: String, bridgeHost: String, transport: ConferenceDescription.Transport, currentSsrcOrder: [Int]) -> (String, [Int])? { func offerSdp(sessionId: UInt32, bundleId: String, bridgeHost: String, transport: ConferenceDescription.Transport, currentState: RemoteOffer.State?) -> RemoteOffer? {
var otherSsrc: [(Bool, Int, String)] = [] struct Ssrc {
for content in self.contents { var isMain: Bool
for channel in content.channels { var value: Int
if channel.endpoint == bundleId { var streamId: String
otherSsrc.append(contentsOf: channel.sources.map { ssrc in var isRemoved: Bool
return (true, ssrc, "stream0")
})
} else {
otherSsrc.append(contentsOf: channel.ssrcs.map { ssrc in
return (false, ssrc, channel.channelBundleId)
})
} }
}
}
otherSsrc.sort(by: { lhs, rhs in
/*if let previousLhsIndex = currentSsrcOrder.firstIndex(of: lhs.1), let previousRhsIndex = currentSsrcOrder.firstIndex(of: rhs.1) {
return previousLhsIndex < previousRhsIndex
}
if currentSsrcOrder.contains(lhs.1) != currentSsrcOrder.contains(rhs.1) {
return currentSsrcOrder.contains(lhs.1)
}*/
if lhs.0 != rhs.0 {
return lhs.0
} else {
return lhs.1 < rhs.1
}
})
func createSdp(sessionId: UInt32, bundleSsrcs: [Ssrc], isPartial: Bool) -> String {
var sdp = "" var sdp = ""
func appendSdp(_ string: String) { func appendSdp(_ string: String) {
if !sdp.isEmpty { if !sdp.isEmpty {
@ -688,95 +687,24 @@ private extension ConferenceDescription {
appendSdp("s=-") appendSdp("s=-")
appendSdp("t=0 0") appendSdp("t=0 0")
/*appendSdp("a=group:BUNDLE audio0") appendSdp("a=group:BUNDLE \(bundleSsrcs.map({ "audio\($0.value)" }).joined(separator: " "))")
do {
appendSdp("m=audio 1 RTP/SAVPF 111 103 104 126")
appendSdp("c=IN IP4 0.0.0.0")
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")
appendSdp("a=rtcp:1 IN IP4 0.0.0.0")
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=mid:audio0")
appendSdp("a=sendrecv")
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
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)
}
for ssrc in bridgeSources {
appendSdp("a=ssrc:\(ssrc) cname:cname\(ssrc)")
appendSdp("a=ssrc:\(ssrc) msid:stream0 audio0")
appendSdp("a=ssrc:\(ssrc) mslabel:stream0")
appendSdp("a=ssrc:\(ssrc) label:audio0")
}
/*for (ssrc, streamId) in otherSsrc {
appendSdp("a=ssrc:\(ssrc) cname:cname\(ssrc)")
appendSdp("a=ssrc:\(ssrc) msid:\(streamId) audio0")
//appendSdp("a=ssrc:\(ssrc) mslabel:\(streamId)")
//appendSdp("a=ssrc:\(ssrc) label:\(streamId)")
}*/
appendSdp("a=rtcp-mux")
}*/
appendSdp("a=group:BUNDLE audio")
appendSdp("a=ice-lite") appendSdp("a=ice-lite")
appendSdp("a=msid-semantic:WMS *") for ssrc in bundleSsrcs {
appendSdp("m=audio \(ssrc.isMain ? "1" : "0") RTP/SAVPF 111 103 104 126")
appendSdp("m=audio 1 RTP/SAVPF 111 103 104 126") if ssrc.isMain {
appendSdp("c=IN IP4 0.0.0.0") appendSdp("c=IN IP4 0.0.0.0")
}
appendSdp("a=mid:audio\(ssrc.value)")
if ssrc.isRemoved {
appendSdp("a=inactive")
continue
}
if ssrc.isMain {
appendSdp("a=ice-ufrag:\(transport.ufrag)") appendSdp("a=ice-ufrag:\(transport.ufrag)")
appendSdp("a=ice-pwd:\(transport.pwd)") appendSdp("a=ice-pwd:\(transport.pwd)")
for fingerprint in transport.fingerprints { for fingerprint in transport.fingerprints {
appendSdp("a=fingerprint:\(fingerprint.hashType) \(fingerprint.fingerprint)") appendSdp("a=fingerprint:\(fingerprint.hashType) \(fingerprint.fingerprint)")
appendSdp("a=setup:\(fingerprint.setup)") appendSdp("a=setup:\(fingerprint.setup)")
@ -821,6 +749,7 @@ private extension ConferenceDescription {
appendSdp(candidateString) appendSdp(candidateString)
} }
}
appendSdp("a=rtpmap:111 opus/48000/2") appendSdp("a=rtpmap:111 opus/48000/2")
appendSdp("a=rtpmap:103 ISAC/16000") appendSdp("a=rtpmap:103 ISAC/16000")
@ -831,98 +760,110 @@ private extension ConferenceDescription {
appendSdp("a=rtcp-mux") appendSdp("a=rtcp-mux")
appendSdp("a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level") 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: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")
appendSdp("a=mid:audio") if ssrc.isMain {
appendSdp("a=sendrecv")
for (_, ssrc, streamId) in otherSsrc {
appendSdp("a=ssrc-group:FID \(ssrc)")
appendSdp("a=ssrc:\(ssrc) cname:stream\(streamId)")
appendSdp("a=ssrc:\(ssrc) msid:stream\(streamId) audio\(streamId)")
//appendSdp("a=ssrc:\(ssrc) mslabel:stream\(streamId)")
//appendSdp("a=ssrc:\(ssrc) label:audio\(streamId)")
}
/*for (isBridge, ssrc, streamId) in otherSsrc {
let mPort: Int
if isBridge {
mPort = 1
} else {
mPort = 0
}
appendSdp("m=audio \(mPort) RTP/SAVPF 111 103 104 126")
appendSdp("c=IN IP4 0.0.0.0")
if isBridge {
appendSdp("a=sendrecv") appendSdp("a=sendrecv")
} else { } else {
appendSdp("a=bundle-only")
appendSdp("a=sendonly") appendSdp("a=sendonly")
appendSdp("a=bundle-only")
} }
appendSdp("a=rtpmap:111 opus/48000/2") appendSdp("a=ssrc-group:FID \(ssrc.value)")
appendSdp("a=rtpmap:103 ISAC/16000") appendSdp("a=ssrc:\(ssrc.value) cname:stream\(ssrc.value)")
appendSdp("a=rtpmap:104 ISAC/32000") appendSdp("a=ssrc:\(ssrc.value) msid:stream\(ssrc.value) audio\(ssrc.value)")
appendSdp("a=rtpmap:126 telephone-event/8000") appendSdp("a=ssrc:\(ssrc.value) mslabel:audio\(ssrc.value)")
appendSdp("a=fmtp:111 minptime=10; useinbandfec=1") appendSdp("a=ssrc:\(ssrc.value) label:audio\(ssrc.value)")
appendSdp("a=rtcp:1 IN IP4 0.0.0.0")
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=mid:audio\(ssrc)")
if isBridge {
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
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=ssrc:\(ssrc) cname:stream\(streamId)")
appendSdp("a=ssrc:\(ssrc) msid:stream\(streamId) audio\(streamId)")
//appendSdp("a=ssrc:\(ssrc) mslabel:stream\(streamId)")
//appendSdp("a=ssrc:\(ssrc) label:audio\(streamId)")
appendSdp("a=rtcp-mux")
}*/
appendSdp("") appendSdp("")
return (sdp, otherSsrc.map(\.1)) 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
)
})
}
}
}
guard let mainSsrcId = maybeMainSsrcId else {
preconditionFailure()
}
var bundleSsrcs: [Ssrc] = []
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)",
isRemoved: isRemoved
))
}
}
for ssrc in ssrcList {
if bundleSsrcs.contains(where: { $0.value == ssrc.value }) {
continue
}
bundleSsrcs.append(ssrc)
}
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))
}
}*/
return RemoteOffer(
sdpList: sdpList,
isPartial: false,
state: RemoteOffer.State(
items: bundleSsrcs.map { ssrc in
RemoteOffer.State.Item(
ssrc: ssrc.value,
isRemoved: ssrc.isRemoved
)
}
)
)
} }
mutating func updateLocalChannelFromSdpAnswer(bundleId: String, sdpAnswer: String) { mutating func updateLocalChannelFromSdpAnswer(bundleId: String, sdpAnswer: String) {
@ -956,36 +897,6 @@ private extension ConferenceDescription {
return result return result
} }
/*
v=0
o=- 3432551037272164134 2 IN IP4 127.0.0.1
s=-
t=0 0
a=group:BUNDLE audio
a=msid-semantic: WMS stream0
m=audio 9 RTP/SAVPF 103 104 126
c=IN IP4 0.0.0.0
a=rtcp:9 IN IP4 0.0.0.0
a=ice-ufrag:XTZl
a=ice-pwd:GS+K9fcajkZ96gy5hCIyx1BV
a=ice-options:trickle
a=fingerprint:sha-256 88:A3:3E:2C:E3:3C:DF:E8:31:1B:59:AA:73:60:D8:EF:E7:FE:0D:F5:B8:F1:79:26:58:A3:D2:93:D9:8C:49:29
a=setup:active
a=mid:audio
a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level
a=extmap:3 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time
a=sendrecv
a=msid:stream0 audio0
a=rtcp-mux
a=rtpmap:103 ISAC/16000
a=rtpmap:104 ISAC/32000
a=rtpmap:126 telephone-event/8000
a=ssrc:666769703 cname:g5ORSLYV5oOfoEBX
a=ssrc:666769703 msid:stream0 audio0
a=ssrc:666769703 mslabel:stream0
a=ssrc:666769703 label:audio0
*/
var audioSources: [Int] = [] var audioSources: [Int] = []
for line in getLines(prefix: "a=ssrc:") { for line in getLines(prefix: "a=ssrc:") {
let scanner = Scanner(string: line) let scanner = Scanner(string: line)
@ -1013,7 +924,10 @@ private extension ConferenceDescription {
parameters: [ parameters: [
"fmtp": [ "fmtp": [
"minptime=10;useinbandfec=1" "minptime=10;useinbandfec=1"
] as [Any] ] as [Any],
"rtcp-fbs": [[
"type": "transport-cc"
] as [String: Any]] as [Any]
] ]
), ),
ConferenceDescription.Content.Channel.PayloadType( ConferenceDescription.Content.Channel.PayloadType(
@ -1045,6 +959,10 @@ private extension ConferenceDescription {
id: 3, id: 3,
uri: "http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time" uri: "http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time"
), ),
ConferenceDescription.Content.Channel.RtpHdrExt(
id: 5,
uri: "http://www.webrtc.org/experiments/rtp-hdrext/transport-wide-cc-02"
),
] ]
guard let ufrag = getLines(prefix: "a=ice-ufrag:").first else { guard let ufrag = getLines(prefix: "a=ice-ufrag:").first else {
@ -1190,12 +1108,14 @@ public final class GroupCallContext {
private var localBundleId: String? private var localBundleId: String?
private var localTransport: ConferenceDescription.Transport? private var localTransport: ConferenceDescription.Transport?
let memberCount = ValuePromise<Int>(0, ignoreRepeated: true)
init(queue: Queue, audioSessionActive: Signal<Bool, NoError>) { init(queue: Queue, audioSessionActive: Signal<Bool, NoError>) {
self.queue = queue self.queue = queue
self.sessionId = UInt32.random(in: 0 ..< UInt32(Int32.max)) self.sessionId = UInt32.random(in: 0 ..< UInt32(Int32.max))
//self.colibriHost = "192.168.8.118" self.colibriHost = "192.168.8.118"
self.colibriHost = "192.168.93.24" //self.colibriHost = "192.168.93.24"
//self.colibriHost = "51.104.206.109" //self.colibriHost = "51.104.206.109"
var relaySdpAnswerImpl: ((String) -> Void)? var relaySdpAnswerImpl: ((String) -> Void)?
@ -1264,7 +1184,7 @@ public final class GroupCallContext {
})) }))
} }
private var currentSsrcOrder: [Int] = [] private var currentOfferState: RemoteOffer.State?
func allocateChannels(conference: ConferenceDescription) { func allocateChannels(conference: ConferenceDescription) {
let bundleId = UUID().uuidString let bundleId = UUID().uuidString
@ -1368,16 +1288,22 @@ public final class GroupCallContext {
return return
} }
guard let (offerSdp, updatedOrder) = conference.offerSdp(sessionId: strongSelf.sessionId, bundleId: bundleId, bridgeHost: strongSelf.colibriHost, transport: transport, currentSsrcOrder: strongSelf.currentSsrcOrder) else {
return
}
strongSelf.currentSsrcOrder = updatedOrder
strongSelf.conferenceId = conference.id strongSelf.conferenceId = conference.id
strongSelf.localBundleId = bundleId strongSelf.localBundleId = bundleId
strongSelf.localTransport = transport strongSelf.localTransport = transport
strongSelf.context.setOfferSdp(offerSdp) //strongSelf.context.emitOffer()
guard let offer = conference.offerSdp(sessionId: strongSelf.sessionId, bundleId: bundleId, bridgeHost: strongSelf.colibriHost, transport: transport, currentState: strongSelf.currentOfferState) else {
return
}
strongSelf.currentOfferState = offer.state
strongSelf.memberCount.set(offer.state.items.filter({ !$0.isRemoved }).count)
for sdp in offer.sdpList {
strongSelf.context.setOfferSdp(sdp, isPartial: offer.isPartial)
}
})) }))
} }
@ -1453,9 +1379,14 @@ public final class GroupCallContext {
return return
} }
if let (offerSdp, updatedOrder) = conference.offerSdp(sessionId: strongSelf.sessionId, bundleId: localBundleId, bridgeHost: strongSelf.colibriHost, transport: localTransport, currentSsrcOrder: strongSelf.currentSsrcOrder) { if let offer = conference.offerSdp(sessionId: strongSelf.sessionId, bundleId: localBundleId, bridgeHost: strongSelf.colibriHost, transport: localTransport, currentState: strongSelf.currentOfferState) {
strongSelf.currentSsrcOrder = updatedOrder strongSelf.currentOfferState = offer.state
strongSelf.context.setOfferSdp(offerSdp)
strongSelf.memberCount.set(offer.state.items.filter({ !$0.isRemoved }).count)
for sdp in offer.sdpList {
strongSelf.context.setOfferSdp(sdp, isPartial: offer.isPartial)
}
} }
strongSelf.pollOnceDelayed() strongSelf.pollOnceDelayed()
@ -1472,4 +1403,16 @@ public final class GroupCallContext {
return Impl(queue: queue, audioSessionActive: audioSessionActive) return Impl(queue: queue, audioSessionActive: audioSessionActive)
}) })
} }
public var memberCount: Signal<Int, NoError> {
return Signal { subscriber in
let disposable = MetaDisposable()
self.impl.with { impl in
disposable.set(impl.memberCount.get().start(next: { value in
subscriber.putNext(value)
}))
}
return disposable
}
}
} }

View File

@ -9,7 +9,8 @@
- (instancetype _Nonnull)initWithQueue:(id<OngoingCallThreadLocalContextQueueWebrtc> _Nonnull)queue relaySdpAnswer:(void (^ _Nonnull)(NSString * _Nonnull))relaySdpAnswer; - (instancetype _Nonnull)initWithQueue:(id<OngoingCallThreadLocalContextQueueWebrtc> _Nonnull)queue relaySdpAnswer:(void (^ _Nonnull)(NSString * _Nonnull))relaySdpAnswer;
- (void)setOfferSdp:(NSString * _Nonnull)offerSdp; - (void)emitOffer;
- (void)setOfferSdp:(NSString * _Nonnull)offerSdp isPartial:(bool)isPartial;
@end @end

View File

@ -36,9 +36,15 @@
return self; return self;
} }
- (void)setOfferSdp:(NSString * _Nonnull)offerSdp { - (void)emitOffer {
if (_instance) { if (_instance) {
_instance->setOfferSdp([offerSdp UTF8String]); _instance->emitOffer();
}
}
- (void)setOfferSdp:(NSString * _Nonnull)offerSdp isPartial:(bool)isPartial {
if (_instance) {
_instance->setOfferSdp([offerSdp UTF8String], isPartial);
} }
} }

View File

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