mirror of
https://github.com/oxen-io/session-ios.git
synced 2023-12-13 21:30:14 +01:00
1378 lines
55 KiB
Swift
1378 lines
55 KiB
Swift
////
|
|
//// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
|
|
////
|
|
//
|
|
//import Foundation
|
|
//import PromiseKit
|
|
//import WebRTC
|
|
//import SignalUtilitiesKit
|
|
//import SignalUtilitiesKit
|
|
//
|
|
//// HACK - Seeing crazy SEGFAULTs on iOS9 when accessing these objc externs.
|
|
//// iOS10 seems unaffected. Reproducible for ~1 in 3 calls.
|
|
//// Binding them to a file constant seems to work around the problem.
|
|
//let kAudioTrackType = kRTCMediaStreamTrackKindAudio
|
|
//let kVideoTrackType = kRTCMediaStreamTrackKindVideo
|
|
//let kMediaConstraintsMinWidth = kRTCMediaConstraintsMinWidth
|
|
//let kMediaConstraintsMaxWidth = kRTCMediaConstraintsMaxWidth
|
|
//let kMediaConstraintsMinHeight = kRTCMediaConstraintsMinHeight
|
|
//let kMediaConstraintsMaxHeight = kRTCMediaConstraintsMaxHeight
|
|
//
|
|
///**
|
|
// * The PeerConnectionClient notifies it's delegate (the CallService) of key events in the call signaling life cycle
|
|
// *
|
|
// * The delegate's methods will always be called on the main thread.
|
|
// */
|
|
//protocol PeerConnectionClientDelegate: class {
|
|
//
|
|
// /**
|
|
// * The connection has been established. The clients can now communicate.
|
|
// * This can be called multiple times throughout the call in the event of temporary network disconnects.
|
|
// */
|
|
// func peerConnectionClientIceConnected(_ peerconnectionClient: PeerConnectionClient)
|
|
//
|
|
// /**
|
|
// * The connection failed to establish. The clients will not be able to communicate.
|
|
// */
|
|
// func peerConnectionClientIceFailed(_ peerconnectionClient: PeerConnectionClient)
|
|
//
|
|
// /**
|
|
// * After initially connecting, the connection disconnected.
|
|
// * It maybe be temporary, in which case `peerConnectionClientIceConnected` will be called again once we're reconnected.
|
|
// * Otherwise, `peerConnectionClientIceFailed` will eventually called.
|
|
// */
|
|
// func peerConnectionClientIceDisconnected(_ peerconnectionClient: PeerConnectionClient)
|
|
//
|
|
// /**
|
|
// * During the Signaling process each client generates IceCandidates locally, which contain information about how to
|
|
// * reach the local client via the internet. The delegate must shuttle these IceCandates to the other (remote) client
|
|
// * out of band, as part of establishing a connection over WebRTC.
|
|
// */
|
|
// func peerConnectionClient(_ peerconnectionClient: PeerConnectionClient, addedLocalIceCandidate iceCandidate: RTCIceCandidate)
|
|
//
|
|
// /**
|
|
// * Once the peerconnection is established, we can receive messages via the data channel, and notify the delegate.
|
|
// */
|
|
// func peerConnectionClient(_ peerconnectionClient: PeerConnectionClient, received dataChannelMessage: WebRTCProtoData)
|
|
//
|
|
// /**
|
|
// * Fired whenever the local video track become active or inactive.
|
|
// */
|
|
// func peerConnectionClient(_ peerconnectionClient: PeerConnectionClient, didUpdateLocalVideoCaptureSession captureSession: AVCaptureSession?)
|
|
//
|
|
// /**
|
|
// * Fired whenever the remote video track become active or inactive.
|
|
// */
|
|
// func peerConnectionClient(_ peerconnectionClient: PeerConnectionClient, didUpdateRemoteVideoTrack videoTrack: RTCVideoTrack?)
|
|
//}
|
|
//
|
|
//// In Swift (at least in Swift v3.3), weak variables aren't thread safe. It
|
|
//// isn't safe to resolve/acquire/lock a weak reference into a strong reference
|
|
//// while the instance might be being deallocated on another thread.
|
|
////
|
|
//// PeerConnectionProxy provides thread-safe access to a strong reference.
|
|
//// PeerConnectionClient has an PeerConnectionProxy to itself that its many async blocks
|
|
//// (which run on more than one thread) can use to safely try to acquire a strong
|
|
//// reference to the PeerConnectionClient. In ARC we'd normally, we'd avoid
|
|
//// having an instance retain a strong reference to itself to avoid retain
|
|
//// cycles, but it's safe in this case: PeerConnectionClient is owned (and only
|
|
//// used by) a single entity CallService and CallService always calls
|
|
//// [PeerConnectionClient terminate] when it is done with a PeerConnectionClient
|
|
//// instance, so terminate is a reliable place where we can break the retain cycle.
|
|
////
|
|
//// Note that we use the proxy in two ways:
|
|
////
|
|
//// * As a delegate for the peer connection and the data channel,
|
|
//// safely forwarding delegate method invocations to the PCC.
|
|
//// * To safely obtain references to the PCC within the PCC's
|
|
//// async blocks.
|
|
////
|
|
//// This should be fixed in Swift 4, but it isn't.
|
|
////
|
|
//// To test using the following scenarios:
|
|
////
|
|
//// * Alice and Bob place simultaneous calls to each other. Both should get busy.
|
|
//// Repeat 10-20x. Then verify that they can connect a call by having just one
|
|
//// call the other.
|
|
//// * Alice or Bob (randomly alternating) calls the other. Recipient (randomly)
|
|
//// accepts call or hangs up. If accepted, Alice or Bob (randomly) hangs up.
|
|
//// Repeat immediately, as fast as you can, 10-20x.
|
|
//class PeerConnectionProxy: NSObject, RTCPeerConnectionDelegate, RTCDataChannelDelegate {
|
|
//
|
|
// private var value: PeerConnectionClient?
|
|
//
|
|
// deinit {
|
|
// Logger.info("[PeerConnectionProxy] deinit")
|
|
// }
|
|
//
|
|
// func set(value: PeerConnectionClient) {
|
|
// objc_sync_enter(self)
|
|
// self.value = value
|
|
// objc_sync_exit(self)
|
|
// }
|
|
//
|
|
// func get() -> PeerConnectionClient? {
|
|
// objc_sync_enter(self)
|
|
// let result = value
|
|
// objc_sync_exit(self)
|
|
//
|
|
// if result == nil {
|
|
// // Every time this method returns nil is a
|
|
// // possible crash avoided.
|
|
// Logger.verbose("cleared get.")
|
|
// }
|
|
//
|
|
// return result
|
|
// }
|
|
//
|
|
// func clear() {
|
|
// Logger.info("")
|
|
//
|
|
// objc_sync_enter(self)
|
|
// value = nil
|
|
// objc_sync_exit(self)
|
|
// }
|
|
//
|
|
// // MARK: - RTCPeerConnectionDelegate
|
|
//
|
|
// public func peerConnection(_ peerConnection: RTCPeerConnection, didChange stateChanged: RTCSignalingState) {
|
|
// self.get()?.peerConnection(peerConnection, didChange: stateChanged)
|
|
// }
|
|
//
|
|
// public func peerConnection(_ peerConnection: RTCPeerConnection, didAdd stream: RTCMediaStream) {
|
|
// self.get()?.peerConnection(peerConnection, didAdd: stream)
|
|
// }
|
|
//
|
|
// public func peerConnection(_ peerConnection: RTCPeerConnection, didRemove stream: RTCMediaStream) {
|
|
// self.get()?.peerConnection(peerConnection, didRemove: stream)
|
|
// }
|
|
//
|
|
// public func peerConnectionShouldNegotiate(_ peerConnection: RTCPeerConnection) {
|
|
// self.get()?.peerConnectionShouldNegotiate(peerConnection)
|
|
// }
|
|
//
|
|
// public func peerConnection(_ peerConnection: RTCPeerConnection, didChange newState: RTCIceConnectionState) {
|
|
// self.get()?.peerConnection(peerConnection, didChange: newState)
|
|
// }
|
|
//
|
|
// public func peerConnection(_ peerConnection: RTCPeerConnection, didChange newState: RTCIceGatheringState) {
|
|
// self.get()?.peerConnection(peerConnection, didChange: newState)
|
|
// }
|
|
//
|
|
// public func peerConnection(_ peerConnection: RTCPeerConnection, didGenerate candidate: RTCIceCandidate) {
|
|
// self.get()?.peerConnection(peerConnection, didGenerate: candidate)
|
|
// }
|
|
//
|
|
// public func peerConnection(_ peerConnection: RTCPeerConnection, didRemove candidates: [RTCIceCandidate]) {
|
|
// self.get()?.peerConnection(peerConnection, didRemove: candidates)
|
|
// }
|
|
//
|
|
// public func peerConnection(_ peerConnection: RTCPeerConnection, didOpen dataChannel: RTCDataChannel) {
|
|
// self.get()?.peerConnection(peerConnection, didOpen: dataChannel)
|
|
// }
|
|
//
|
|
// public func peerConnection(_ peerConnection: RTCPeerConnection, didChange connectionState: RTCPeerConnectionState) {
|
|
// self.get()?.peerConnection(peerConnection, didChange: connectionState)
|
|
// }
|
|
//
|
|
// // MARK: - RTCDataChannelDelegate
|
|
//
|
|
// public func dataChannelDidChangeState(_ dataChannel: RTCDataChannel) {
|
|
// self.get()?.dataChannelDidChangeState(dataChannel)
|
|
// }
|
|
//
|
|
// public func dataChannel(_ dataChannel: RTCDataChannel, didReceiveMessageWith buffer: RTCDataBuffer) {
|
|
// self.get()?.dataChannel(dataChannel, didReceiveMessageWith: buffer)
|
|
// }
|
|
//
|
|
// public func dataChannel(_ dataChannel: RTCDataChannel, didChangeBufferedAmount amount: UInt64) {
|
|
// self.get()?.dataChannel(dataChannel, didChangeBufferedAmount: amount)
|
|
// }
|
|
//}
|
|
//
|
|
///**
|
|
// * `PeerConnectionClient` is our interface to WebRTC.
|
|
// *
|
|
// * It is primarily a wrapper around `RTCPeerConnection`, which is responsible for sending and receiving our call data
|
|
// * including audio, video, and some post-connected signaling (hangup, add video)
|
|
// */
|
|
//class PeerConnectionClient: NSObject, RTCPeerConnectionDelegate, RTCDataChannelDelegate, VideoCaptureSettingsDelegate {
|
|
//
|
|
// enum Identifiers: String {
|
|
// case mediaStream = "ARDAMS",
|
|
// videoTrack = "ARDAMSv0",
|
|
// audioTrack = "ARDAMSa0",
|
|
// dataChannelSignaling = "signaling"
|
|
// }
|
|
//
|
|
// // A state in this class should only be accessed on this queue in order to
|
|
// // serialize access.
|
|
// //
|
|
// // This queue is also used to perform expensive calls to the WebRTC API.
|
|
// private static let signalingQueue = DispatchQueue(label: "CallServiceSignalingQueue")
|
|
//
|
|
// // Delegate is notified of key events in the call lifecycle.
|
|
// //
|
|
// // This property should only be accessed on the main thread.
|
|
// private weak var delegate: PeerConnectionClientDelegate?
|
|
//
|
|
// // Connection
|
|
//
|
|
// private var peerConnection: RTCPeerConnection?
|
|
// private let iceServers: [RTCIceServer]
|
|
// private let connectionConstraints: RTCMediaConstraints
|
|
// private let configuration: RTCConfiguration
|
|
// private let factory: RTCPeerConnectionFactory
|
|
//
|
|
// // DataChannel
|
|
//
|
|
// private var dataChannel: RTCDataChannel?
|
|
//
|
|
// // Audio
|
|
//
|
|
// private var audioSender: RTCRtpSender?
|
|
// private var audioTrack: RTCAudioTrack?
|
|
// private var audioConstraints: RTCMediaConstraints
|
|
//
|
|
// // Video
|
|
//
|
|
// private var videoCaptureController: VideoCaptureController?
|
|
// private var videoSender: RTCRtpSender?
|
|
//
|
|
// // RTCVideoTrack is fragile and prone to throwing exceptions and/or
|
|
// // causing deadlock in its destructor. Therefore we take great care
|
|
// // with this property.
|
|
// private var localVideoTrack: RTCVideoTrack?
|
|
// private var remoteVideoTrack: RTCVideoTrack?
|
|
// private var cameraConstraints: RTCMediaConstraints
|
|
//
|
|
// private let proxy = PeerConnectionProxy()
|
|
// // Note that we're deliberately leaking proxy instances using this
|
|
// // collection to avoid EXC_BAD_ACCESS. Calls are rare and the proxy
|
|
// // is tiny (a single property), so it's better to leak and be safe.
|
|
// private static var expiredProxies = [PeerConnectionProxy]()
|
|
//
|
|
// init(iceServers: [RTCIceServer], delegate: PeerConnectionClientDelegate, callDirection: CallDirection, useTurnOnly: Bool) {
|
|
// AssertIsOnMainThread()
|
|
//
|
|
// self.iceServers = iceServers
|
|
// self.delegate = delegate
|
|
//
|
|
// // Ensure we enable SW decoders to enable VP8 support
|
|
// let decoderFactory = RTCDefaultVideoDecoderFactory()
|
|
// let encoderFactory = RTCDefaultVideoEncoderFactory()
|
|
// let factory = RTCPeerConnectionFactory(encoderFactory: encoderFactory, decoderFactory: decoderFactory)
|
|
//
|
|
// self.factory = factory
|
|
// configuration = RTCConfiguration()
|
|
// configuration.iceServers = iceServers
|
|
// configuration.bundlePolicy = .maxBundle
|
|
// configuration.rtcpMuxPolicy = .require
|
|
// if useTurnOnly {
|
|
// Logger.debug("using iceTransportPolicy: relay")
|
|
// configuration.iceTransportPolicy = .relay
|
|
// } else {
|
|
// Logger.debug("using iceTransportPolicy: default")
|
|
// }
|
|
//
|
|
// let connectionConstraintsDict = ["DtlsSrtpKeyAgreement": "true"]
|
|
// connectionConstraints = RTCMediaConstraints(mandatoryConstraints: nil, optionalConstraints: connectionConstraintsDict)
|
|
//
|
|
// audioConstraints = RTCMediaConstraints(mandatoryConstraints: nil, optionalConstraints: nil)
|
|
// cameraConstraints = RTCMediaConstraints(mandatoryConstraints: nil, optionalConstraints: nil)
|
|
//
|
|
// super.init()
|
|
//
|
|
// proxy.set(value: self)
|
|
//
|
|
// peerConnection = factory.peerConnection(with: configuration,
|
|
// constraints: connectionConstraints,
|
|
// delegate: proxy)
|
|
// createAudioSender()
|
|
// createVideoSender()
|
|
//
|
|
// if callDirection == .outgoing {
|
|
// // When placing an outgoing call, it's our responsibility to create the DataChannel.
|
|
// // Recipient will not have to do this explicitly.
|
|
// createSignalingDataChannel()
|
|
// }
|
|
// }
|
|
//
|
|
// deinit {
|
|
// // TODO: We can demote this log level to debug once we're confident that
|
|
// // this class is always deallocated.
|
|
// Logger.info("[PeerConnectionClient] deinit")
|
|
// }
|
|
//
|
|
// // MARK: - Media Streams
|
|
//
|
|
// private func createSignalingDataChannel() {
|
|
// AssertIsOnMainThread()
|
|
// guard let peerConnection = peerConnection else {
|
|
// Logger.debug("Ignoring obsolete event in terminated client")
|
|
// return
|
|
// }
|
|
//
|
|
// let configuration = RTCDataChannelConfiguration()
|
|
// // Insist upon an "ordered" TCP data channel for delivery reliability.
|
|
// configuration.isOrdered = true
|
|
//
|
|
// guard let dataChannel = peerConnection.dataChannel(forLabel: Identifiers.dataChannelSignaling.rawValue,
|
|
// configuration: configuration) else {
|
|
//
|
|
// // TODO fail outgoing call?
|
|
// owsFailDebug("dataChannel was unexpectedly nil")
|
|
// return
|
|
// }
|
|
// dataChannel.delegate = proxy
|
|
//
|
|
// assert(self.dataChannel == nil)
|
|
// self.dataChannel = dataChannel
|
|
// }
|
|
//
|
|
// // MARK: - Video
|
|
//
|
|
// fileprivate func createVideoSender() {
|
|
// AssertIsOnMainThread()
|
|
// Logger.debug("")
|
|
// assert(self.videoSender == nil, "\(#function) should only be called once.")
|
|
//
|
|
// guard !Platform.isSimulator else {
|
|
// Logger.warn("Refusing to create local video track on simulator which has no capture device.")
|
|
// return
|
|
// }
|
|
// guard let peerConnection = peerConnection else {
|
|
// Logger.debug("Ignoring obsolete event in terminated client")
|
|
// return
|
|
// }
|
|
//
|
|
// let videoSource = factory.videoSource()
|
|
//
|
|
// let localVideoTrack = factory.videoTrack(with: videoSource, trackId: Identifiers.videoTrack.rawValue)
|
|
// self.localVideoTrack = localVideoTrack
|
|
// // Disable by default until call is connected.
|
|
// // FIXME - do we require mic permissions at this point?
|
|
// // if so maybe it would be better to not even add the track until the call is connected
|
|
// // instead of creating it and disabling it.
|
|
// localVideoTrack.isEnabled = false
|
|
//
|
|
// let capturer = RTCCameraVideoCapturer(delegate: videoSource)
|
|
// self.videoCaptureController = VideoCaptureController(capturer: capturer, settingsDelegate: self)
|
|
//
|
|
// let videoSender = peerConnection.sender(withKind: kVideoTrackType, streamId: Identifiers.mediaStream.rawValue)
|
|
// videoSender.track = localVideoTrack
|
|
// self.videoSender = videoSender
|
|
// }
|
|
//
|
|
// public func setCameraSource(isUsingFrontCamera: Bool) {
|
|
// AssertIsOnMainThread()
|
|
//
|
|
// let proxyCopy = self.proxy
|
|
// PeerConnectionClient.signalingQueue.async {
|
|
// guard let strongSelf = proxyCopy.get() else { return }
|
|
//
|
|
// guard let captureController = strongSelf.videoCaptureController else {
|
|
// owsFailDebug("captureController was unexpectedly nil")
|
|
// return
|
|
// }
|
|
//
|
|
// captureController.switchCamera(isUsingFrontCamera: isUsingFrontCamera)
|
|
// }
|
|
// }
|
|
//
|
|
// public func setLocalVideoEnabled(enabled: Bool) {
|
|
// AssertIsOnMainThread()
|
|
// let proxyCopy = self.proxy
|
|
// let completion = {
|
|
// guard let strongSelf = proxyCopy.get() else { return }
|
|
// guard let strongDelegate = strongSelf.delegate else { return }
|
|
//
|
|
// let captureSession: AVCaptureSession? = {
|
|
// guard enabled else {
|
|
// return nil
|
|
// }
|
|
//
|
|
// guard let captureController = strongSelf.videoCaptureController else {
|
|
// owsFailDebug("videoCaptureController was unexpectedly nil")
|
|
// return nil
|
|
// }
|
|
//
|
|
// return captureController.captureSession
|
|
// }()
|
|
//
|
|
// strongDelegate.peerConnectionClient(strongSelf, didUpdateLocalVideoCaptureSession: captureSession)
|
|
// }
|
|
//
|
|
// PeerConnectionClient.signalingQueue.async {
|
|
// guard let strongSelf = proxyCopy.get() else { return }
|
|
// guard strongSelf.peerConnection != nil else {
|
|
// Logger.debug("Ignoring obsolete event in terminated client")
|
|
// return
|
|
// }
|
|
//
|
|
// guard let videoCaptureController = strongSelf.videoCaptureController else {
|
|
// Logger.debug("Ignoring obsolete event in terminated client")
|
|
// return
|
|
// }
|
|
//
|
|
// guard let localVideoTrack = strongSelf.localVideoTrack else {
|
|
// Logger.debug("Ignoring obsolete event in terminated client")
|
|
// return
|
|
// }
|
|
// localVideoTrack.isEnabled = enabled
|
|
//
|
|
// if enabled {
|
|
// Logger.debug("starting video capture")
|
|
// videoCaptureController.startCapture()
|
|
// } else {
|
|
// Logger.debug("stopping video capture")
|
|
// videoCaptureController.stopCapture()
|
|
// }
|
|
//
|
|
// DispatchQueue.main.async(execute: completion)
|
|
// }
|
|
// }
|
|
//
|
|
// // MARK: VideoCaptureSettingsDelegate
|
|
//
|
|
// var videoWidth: Int32 {
|
|
// return 400
|
|
// }
|
|
//
|
|
// var videoHeight: Int32 {
|
|
// return 400
|
|
// }
|
|
//
|
|
// // MARK: - Audio
|
|
//
|
|
// fileprivate func createAudioSender() {
|
|
// AssertIsOnMainThread()
|
|
// Logger.debug("")
|
|
// assert(self.audioSender == nil, "\(#function) should only be called once.")
|
|
//
|
|
// guard let peerConnection = peerConnection else {
|
|
// Logger.debug("Ignoring obsolete event in terminated client")
|
|
// return
|
|
// }
|
|
//
|
|
// let audioSource = factory.audioSource(with: self.audioConstraints)
|
|
//
|
|
// let audioTrack = factory.audioTrack(with: audioSource, trackId: Identifiers.audioTrack.rawValue)
|
|
// self.audioTrack = audioTrack
|
|
//
|
|
// // Disable by default until call is connected.
|
|
// // FIXME - do we require mic permissions at this point?
|
|
// // if so maybe it would be better to not even add the track until the call is connected
|
|
// // instead of creating it and disabling it.
|
|
// audioTrack.isEnabled = false
|
|
//
|
|
// let audioSender = peerConnection.sender(withKind: kAudioTrackType, streamId: Identifiers.mediaStream.rawValue)
|
|
// audioSender.track = audioTrack
|
|
// self.audioSender = audioSender
|
|
// }
|
|
//
|
|
// public func setAudioEnabled(enabled: Bool) {
|
|
// AssertIsOnMainThread()
|
|
// let proxyCopy = self.proxy
|
|
// PeerConnectionClient.signalingQueue.async {
|
|
// guard let strongSelf = proxyCopy.get() else { return }
|
|
// guard strongSelf.peerConnection != nil else {
|
|
// Logger.debug("Ignoring obsolete event in terminated client")
|
|
// return
|
|
// }
|
|
// guard let audioTrack = strongSelf.audioTrack else {
|
|
// Logger.debug("Ignoring obsolete event in terminated client")
|
|
// return
|
|
// }
|
|
//
|
|
// audioTrack.isEnabled = enabled
|
|
// }
|
|
// }
|
|
//
|
|
// // MARK: - Session negotiation
|
|
//
|
|
// private var defaultOfferConstraints: RTCMediaConstraints {
|
|
// let mandatoryConstraints = [
|
|
// "OfferToReceiveAudio": "true",
|
|
// "OfferToReceiveVideo": "true"
|
|
// ]
|
|
// return RTCMediaConstraints(mandatoryConstraints: mandatoryConstraints, optionalConstraints: nil)
|
|
// }
|
|
//
|
|
// public func createOffer() -> Promise<HardenedRTCSessionDescription> {
|
|
// AssertIsOnMainThread()
|
|
// let proxyCopy = self.proxy
|
|
// let (promise, resolver) = Promise<HardenedRTCSessionDescription>.pending()
|
|
// let completion: ((RTCSessionDescription?, Error?) -> Void) = { (sdp, error) in
|
|
// guard let strongSelf = proxyCopy.get() else {
|
|
// resolver.reject(NSError(domain: "Obsolete client", code: 0, userInfo: nil))
|
|
// return
|
|
// }
|
|
// strongSelf.assertOnSignalingQueue()
|
|
// guard strongSelf.peerConnection != nil else {
|
|
// Logger.debug("Ignoring obsolete event in terminated client")
|
|
// resolver.reject(NSError(domain: "Obsolete client", code: 0, userInfo: nil))
|
|
// return
|
|
// }
|
|
// if let error = error {
|
|
// resolver.reject(error)
|
|
// return
|
|
// }
|
|
//
|
|
// guard let sessionDescription = sdp else {
|
|
// Logger.error("No session description was obtained, even though there was no error reported.")
|
|
// let error = OWSErrorMakeUnableToProcessServerResponseError()
|
|
// resolver.reject(error)
|
|
// return
|
|
// }
|
|
//
|
|
// resolver.fulfill(HardenedRTCSessionDescription(rtcSessionDescription: sessionDescription))
|
|
// }
|
|
//
|
|
// PeerConnectionClient.signalingQueue.async {
|
|
// guard let strongSelf = proxyCopy.get() else {
|
|
// resolver.reject(NSError(domain: "Obsolete client", code: 0, userInfo: nil))
|
|
// return
|
|
// }
|
|
// strongSelf.assertOnSignalingQueue()
|
|
// guard let peerConnection = strongSelf.peerConnection else {
|
|
// Logger.debug("Ignoring obsolete event in terminated client")
|
|
// resolver.reject(NSError(domain: "Obsolete client", code: 0, userInfo: nil))
|
|
// return
|
|
// }
|
|
//
|
|
// peerConnection.offer(for: strongSelf.defaultOfferConstraints, completionHandler: { (sdp: RTCSessionDescription?, error: Error?) in
|
|
// PeerConnectionClient.signalingQueue.async {
|
|
// completion(sdp, error)
|
|
// }
|
|
// })
|
|
// }
|
|
//
|
|
// return promise
|
|
// }
|
|
//
|
|
// public func setLocalSessionDescriptionInternal(_ sessionDescription: HardenedRTCSessionDescription) -> Promise<Void> {
|
|
// let proxyCopy = self.proxy
|
|
// let (promise, resolver) = Promise<Void>.pending()
|
|
// PeerConnectionClient.signalingQueue.async {
|
|
// guard let strongSelf = proxyCopy.get() else {
|
|
// resolver.reject(NSError(domain: "Obsolete client", code: 0, userInfo: nil))
|
|
// return
|
|
// }
|
|
// strongSelf.assertOnSignalingQueue()
|
|
//
|
|
// guard let peerConnection = strongSelf.peerConnection else {
|
|
// Logger.debug("Ignoring obsolete event in terminated client")
|
|
// resolver.reject(NSError(domain: "Obsolete client", code: 0, userInfo: nil))
|
|
// return
|
|
// }
|
|
//
|
|
// Logger.verbose("setting local session description: \(sessionDescription)")
|
|
// peerConnection.setLocalDescription(sessionDescription.rtcSessionDescription, completionHandler: { (error) in
|
|
// if let error = error {
|
|
// resolver.reject(error)
|
|
// } else {
|
|
// resolver.fulfill(())
|
|
// }
|
|
// })
|
|
// }
|
|
// return promise
|
|
// }
|
|
//
|
|
// public func setLocalSessionDescription(_ sessionDescription: HardenedRTCSessionDescription) -> Promise<Void> {
|
|
// AssertIsOnMainThread()
|
|
// let proxyCopy = self.proxy
|
|
// let (promise, resolver) = Promise<Void>.pending()
|
|
// PeerConnectionClient.signalingQueue.async {
|
|
// guard let strongSelf = proxyCopy.get() else {
|
|
// resolver.reject(NSError(domain: "Obsolete client", code: 0, userInfo: nil))
|
|
// return
|
|
// }
|
|
// strongSelf.assertOnSignalingQueue()
|
|
// guard let peerConnection = strongSelf.peerConnection else {
|
|
// Logger.debug("Ignoring obsolete event in terminated client")
|
|
// resolver.reject(NSError(domain: "Obsolete client", code: 0, userInfo: nil))
|
|
// return
|
|
// }
|
|
//
|
|
// Logger.verbose("setting local session description: \(sessionDescription)")
|
|
// peerConnection.setLocalDescription(sessionDescription.rtcSessionDescription,
|
|
// completionHandler: { error in
|
|
// if let error = error {
|
|
// resolver.reject(error)
|
|
// return
|
|
// }
|
|
// resolver.fulfill(())
|
|
// })
|
|
// }
|
|
//
|
|
// return promise
|
|
// }
|
|
//
|
|
// public func negotiateSessionDescription(remoteDescription: RTCSessionDescription, constraints: RTCMediaConstraints) -> Promise<HardenedRTCSessionDescription> {
|
|
// AssertIsOnMainThread()
|
|
// let proxyCopy = self.proxy
|
|
// return setRemoteSessionDescription(remoteDescription)
|
|
// .then(on: PeerConnectionClient.signalingQueue) { _ -> Promise<HardenedRTCSessionDescription> in
|
|
// guard let strongSelf = proxyCopy.get() else {
|
|
// return Promise(error: NSError(domain: "Obsolete client", code: 0, userInfo: nil))
|
|
// }
|
|
// return strongSelf.negotiateAnswerSessionDescription(constraints: constraints)
|
|
// }
|
|
// }
|
|
//
|
|
// public func setRemoteSessionDescription(_ sessionDescription: RTCSessionDescription) -> Promise<Void> {
|
|
// AssertIsOnMainThread()
|
|
// let proxyCopy = self.proxy
|
|
// let (promise, resolver) = Promise<Void>.pending()
|
|
// PeerConnectionClient.signalingQueue.async {
|
|
// guard let strongSelf = proxyCopy.get() else {
|
|
// resolver.reject(NSError(domain: "Obsolete client", code: 0, userInfo: nil))
|
|
// return
|
|
// }
|
|
// strongSelf.assertOnSignalingQueue()
|
|
// guard let peerConnection = strongSelf.peerConnection else {
|
|
// Logger.debug("Ignoring obsolete event in terminated client")
|
|
// resolver.reject(NSError(domain: "Obsolete client", code: 0, userInfo: nil))
|
|
// return
|
|
// }
|
|
// Logger.verbose("setting remote description: \(sessionDescription)")
|
|
// peerConnection.setRemoteDescription(sessionDescription,
|
|
// completionHandler: { error in
|
|
// if let error = error {
|
|
// resolver.reject(error)
|
|
// return
|
|
// }
|
|
// resolver.fulfill(())
|
|
// })
|
|
// }
|
|
// return promise
|
|
// }
|
|
//
|
|
// private func negotiateAnswerSessionDescription(constraints: RTCMediaConstraints) -> Promise<HardenedRTCSessionDescription> {
|
|
// assertOnSignalingQueue()
|
|
// let proxyCopy = self.proxy
|
|
// let (promise, resolver) = Promise<HardenedRTCSessionDescription>.pending()
|
|
// let completion: ((RTCSessionDescription?, Error?) -> Void) = { (sdp, error) in
|
|
// guard let strongSelf = proxyCopy.get() else {
|
|
// resolver.reject(NSError(domain: "Obsolete client", code: 0, userInfo: nil))
|
|
// return
|
|
// }
|
|
// strongSelf.assertOnSignalingQueue()
|
|
// guard strongSelf.peerConnection != nil else {
|
|
// Logger.debug("Ignoring obsolete event in terminated client")
|
|
// resolver.reject(NSError(domain: "Obsolete client", code: 0, userInfo: nil))
|
|
// return
|
|
// }
|
|
// if let error = error {
|
|
// resolver.reject(error)
|
|
// return
|
|
// }
|
|
//
|
|
// guard let sessionDescription = sdp else {
|
|
// Logger.error("unexpected empty session description, even though no error was reported.")
|
|
// let error = OWSErrorMakeUnableToProcessServerResponseError()
|
|
// resolver.reject(error)
|
|
// return
|
|
// }
|
|
//
|
|
// let hardenedSessionDescription = HardenedRTCSessionDescription(rtcSessionDescription: sessionDescription)
|
|
//
|
|
// firstly {
|
|
// strongSelf.setLocalSessionDescriptionInternal(hardenedSessionDescription)
|
|
// }.done(on: PeerConnectionClient.signalingQueue) {
|
|
// resolver.fulfill(hardenedSessionDescription)
|
|
// }.catch { error in
|
|
// resolver.reject(error)
|
|
// }.retainUntilComplete()
|
|
// }
|
|
//
|
|
// PeerConnectionClient.signalingQueue.async {
|
|
// guard let strongSelf = proxyCopy.get() else {
|
|
// resolver.reject(NSError(domain: "Obsolete client", code: 0, userInfo: nil))
|
|
// return
|
|
// }
|
|
// strongSelf.assertOnSignalingQueue()
|
|
//
|
|
// guard let peerConnection = strongSelf.peerConnection else {
|
|
// Logger.debug("Ignoring obsolete event in terminated client")
|
|
// resolver.reject(NSError(domain: "Obsolete client", code: 0, userInfo: nil))
|
|
// return
|
|
// }
|
|
//
|
|
// Logger.debug("negotiating answer session.")
|
|
//
|
|
// peerConnection.answer(for: constraints, completionHandler: { (sdp: RTCSessionDescription?, error: Error?) in
|
|
// PeerConnectionClient.signalingQueue.async {
|
|
// completion(sdp, error)
|
|
// }
|
|
// })
|
|
// }
|
|
// return promise
|
|
// }
|
|
//
|
|
// public func addRemoteIceCandidate(_ candidate: RTCIceCandidate) {
|
|
// let proxyCopy = self.proxy
|
|
// PeerConnectionClient.signalingQueue.async {
|
|
// guard let strongSelf = proxyCopy.get() else { return }
|
|
// guard let peerConnection = strongSelf.peerConnection else {
|
|
// Logger.debug("Ignoring obsolete event in terminated client")
|
|
// return
|
|
// }
|
|
// Logger.info("adding remote ICE candidate: \(candidate.sdp)")
|
|
// peerConnection.add(candidate)
|
|
// }
|
|
// }
|
|
//
|
|
// public func terminate() {
|
|
// AssertIsOnMainThread()
|
|
// Logger.debug("")
|
|
//
|
|
// // Clear the delegate immediately so that we can guarantee that
|
|
// // no delegate methods are called after terminate() returns.
|
|
// delegate = nil
|
|
//
|
|
// // Clear the proxy immediately so that enqueued work is aborted
|
|
// // going forward.
|
|
// PeerConnectionClient.expiredProxies.append(proxy)
|
|
// proxy.clear()
|
|
//
|
|
// // Don't use [weak self]; we always want to perform terminateInternal().
|
|
// PeerConnectionClient.signalingQueue.async {
|
|
// self.terminateInternal()
|
|
// }
|
|
// }
|
|
//
|
|
// private func terminateInternal() {
|
|
// assertOnSignalingQueue()
|
|
// Logger.debug("")
|
|
//
|
|
// // Some notes on preventing crashes while disposing of peerConnection for video calls
|
|
// // from: https://groups.google.com/forum/#!searchin/discuss-webrtc/objc$20crash$20dealloc%7Csort:relevance/discuss-webrtc/7D-vk5yLjn8/rBW2D6EW4GYJ
|
|
// // The sequence to make it work appears to be
|
|
// //
|
|
// // [capturer stop]; // I had to add this as a method to RTCVideoCapturer
|
|
// // [localRenderer stop];
|
|
// // [remoteRenderer stop];
|
|
// // [peerConnection close];
|
|
//
|
|
// // audioTrack is a strong property because we need access to it to mute/unmute, but I was seeing it
|
|
// // become nil when it was only a weak property. So we retain it and manually nil the reference here, because
|
|
// // we are likely to crash if we retain any peer connection properties when the peerconnection is released
|
|
//
|
|
// localVideoTrack?.isEnabled = false
|
|
// remoteVideoTrack?.isEnabled = false
|
|
//
|
|
// if let dataChannel = self.dataChannel {
|
|
// dataChannel.delegate = nil
|
|
// }
|
|
//
|
|
// dataChannel = nil
|
|
// audioSender = nil
|
|
// audioTrack = nil
|
|
// videoSender = nil
|
|
// localVideoTrack = nil
|
|
// remoteVideoTrack = nil
|
|
// videoCaptureController = nil
|
|
//
|
|
// if let peerConnection = peerConnection {
|
|
// peerConnection.delegate = nil
|
|
// peerConnection.close()
|
|
// }
|
|
// peerConnection = nil
|
|
// }
|
|
//
|
|
// // MARK: - Data Channel
|
|
//
|
|
// // should only be accessed on PeerConnectionClient.signalingQueue
|
|
// var pendingDataChannelMessages: [PendingDataChannelMessage] = []
|
|
// struct PendingDataChannelMessage {
|
|
// let data: Data
|
|
// let description: String
|
|
// let isCritical: Bool
|
|
// }
|
|
//
|
|
// public func sendDataChannelMessage(data: Data, description: String, isCritical: Bool) {
|
|
// AssertIsOnMainThread()
|
|
// let proxyCopy = self.proxy
|
|
// PeerConnectionClient.signalingQueue.async {
|
|
// guard let strongSelf = proxyCopy.get() else { return }
|
|
//
|
|
// guard strongSelf.peerConnection != nil else {
|
|
// Logger.debug("Ignoring obsolete event in terminated client: \(description)")
|
|
// return
|
|
// }
|
|
//
|
|
// guard let dataChannel = strongSelf.dataChannel else {
|
|
// if isCritical {
|
|
// Logger.info("enqueuing critical data channel message for after we have a dataChannel: \(description)")
|
|
// strongSelf.pendingDataChannelMessages.append(PendingDataChannelMessage(data: data, description: description, isCritical: isCritical))
|
|
// } else {
|
|
// Logger.error("ignoring sending \(data) for nil dataChannel: \(description)")
|
|
// }
|
|
// return
|
|
// }
|
|
//
|
|
// Logger.debug("sendDataChannelMessage trying: \(description)")
|
|
//
|
|
// let buffer = RTCDataBuffer(data: data, isBinary: false)
|
|
// let result = dataChannel.sendData(buffer)
|
|
//
|
|
// if result {
|
|
// Logger.debug("sendDataChannelMessage succeeded: \(description)")
|
|
// } else {
|
|
// Logger.warn("sendDataChannelMessage failed: \(description)")
|
|
// if isCritical {
|
|
// OWSProdError(OWSAnalyticsEvents.peerConnectionClientErrorSendDataChannelMessageFailed(), file: #file, function: #function, line: #line)
|
|
// }
|
|
// }
|
|
// }
|
|
// }
|
|
//
|
|
// // MARK: RTCDataChannelDelegate
|
|
//
|
|
// /** The data channel state changed. */
|
|
// internal func dataChannelDidChangeState(_ dataChannel: RTCDataChannel) {
|
|
// Logger.debug("dataChannelDidChangeState: \(dataChannel)")
|
|
// }
|
|
//
|
|
// /** The data channel successfully received a data buffer. */
|
|
// internal func dataChannel(_ dataChannel: RTCDataChannel, didReceiveMessageWith buffer: RTCDataBuffer) {
|
|
// let proxyCopy = self.proxy
|
|
// let completion: (WebRTCProtoData) -> Void = { (dataChannelMessage) in
|
|
// AssertIsOnMainThread()
|
|
// guard let strongSelf = proxyCopy.get() else { return }
|
|
// guard let strongDelegate = strongSelf.delegate else { return }
|
|
// strongDelegate.peerConnectionClient(strongSelf, received: dataChannelMessage)
|
|
// }
|
|
//
|
|
// PeerConnectionClient.signalingQueue.async {
|
|
// guard let strongSelf = proxyCopy.get() else { return }
|
|
// guard strongSelf.peerConnection != nil else {
|
|
// Logger.debug("Ignoring obsolete event in terminated client")
|
|
// return
|
|
// }
|
|
// Logger.debug("dataChannel didReceiveMessageWith buffer:\(buffer)")
|
|
//
|
|
// var dataChannelMessage: WebRTCProtoData
|
|
// do {
|
|
// dataChannelMessage = try WebRTCProtoData.parseData(buffer.data)
|
|
// } catch {
|
|
// Logger.error("failed to parse dataProto")
|
|
// return
|
|
// }
|
|
//
|
|
// DispatchQueue.main.async {
|
|
// completion(dataChannelMessage)
|
|
// }
|
|
// }
|
|
// }
|
|
//
|
|
// /** The data channel's |bufferedAmount| changed. */
|
|
// internal func dataChannel(_ dataChannel: RTCDataChannel, didChangeBufferedAmount amount: UInt64) {
|
|
// Logger.debug("didChangeBufferedAmount: \(amount)")
|
|
// }
|
|
//
|
|
// // MARK: - RTCPeerConnectionDelegate
|
|
//
|
|
// /** Called when the SignalingState changed. */
|
|
// internal func peerConnection(_ peerConnectionParam: RTCPeerConnection, didChange stateChanged: RTCSignalingState) {
|
|
// Logger.debug("didChange signalingState:\(stateChanged.debugDescription)")
|
|
// }
|
|
//
|
|
// /** Called when media is received on a new stream from remote peer. */
|
|
// internal func peerConnection(_ peerConnectionParam: RTCPeerConnection, didAdd stream: RTCMediaStream) {
|
|
// let proxyCopy = self.proxy
|
|
// let completion: (RTCVideoTrack) -> Void = { (remoteVideoTrack) in
|
|
// AssertIsOnMainThread()
|
|
// guard let strongSelf = proxyCopy.get() else { return }
|
|
// guard let strongDelegate = strongSelf.delegate else { return }
|
|
//
|
|
// // TODO: Consider checking for termination here.
|
|
//
|
|
// strongDelegate.peerConnectionClient(strongSelf, didUpdateRemoteVideoTrack: remoteVideoTrack)
|
|
// }
|
|
//
|
|
// PeerConnectionClient.signalingQueue.async {
|
|
// guard let strongSelf = proxyCopy.get() else { return }
|
|
// guard let peerConnection = strongSelf.peerConnection else {
|
|
// Logger.debug("Ignoring obsolete event in terminated client")
|
|
// return
|
|
// }
|
|
// guard peerConnection == peerConnectionParam else {
|
|
// owsFailDebug("mismatched peerConnection callback.")
|
|
// return
|
|
// }
|
|
// guard stream.videoTracks.count > 0 else {
|
|
// owsFailDebug("didAdd stream missing stream.")
|
|
// return
|
|
// }
|
|
// let remoteVideoTrack = stream.videoTracks[0]
|
|
// Logger.debug("didAdd stream:\(stream) video tracks: \(stream.videoTracks.count) audio tracks: \(stream.audioTracks.count)")
|
|
//
|
|
// strongSelf.remoteVideoTrack = remoteVideoTrack
|
|
//
|
|
// DispatchQueue.main.async {
|
|
// completion(remoteVideoTrack)
|
|
// }
|
|
// }
|
|
// }
|
|
//
|
|
// /** Called when a remote peer closes a stream. */
|
|
// internal func peerConnection(_ peerConnectionParam: RTCPeerConnection, didRemove stream: RTCMediaStream) {
|
|
// Logger.debug("didRemove Stream:\(stream)")
|
|
// }
|
|
//
|
|
// /** Called when negotiation is needed, for example ICE has restarted. */
|
|
// internal func peerConnectionShouldNegotiate(_ peerConnectionParam: RTCPeerConnection) {
|
|
// Logger.debug("shouldNegotiate")
|
|
// }
|
|
//
|
|
// /** Called any time the IceConnectionState changes. */
|
|
// internal func peerConnection(_ peerConnectionParam: RTCPeerConnection, didChange newState: RTCIceConnectionState) {
|
|
// let proxyCopy = self.proxy
|
|
// let connectedCompletion : () -> Void = {
|
|
// AssertIsOnMainThread()
|
|
// guard let strongSelf = proxyCopy.get() else { return }
|
|
// guard let strongDelegate = strongSelf.delegate else { return }
|
|
// strongDelegate.peerConnectionClientIceConnected(strongSelf)
|
|
// }
|
|
// let failedCompletion : () -> Void = {
|
|
// AssertIsOnMainThread()
|
|
// guard let strongSelf = proxyCopy.get() else { return }
|
|
// guard let strongDelegate = strongSelf.delegate else { return }
|
|
// strongDelegate.peerConnectionClientIceFailed(strongSelf)
|
|
// }
|
|
// let disconnectedCompletion : () -> Void = {
|
|
// AssertIsOnMainThread()
|
|
// guard let strongSelf = proxyCopy.get() else { return }
|
|
// guard let strongDelegate = strongSelf.delegate else { return }
|
|
// strongDelegate.peerConnectionClientIceDisconnected(strongSelf)
|
|
// }
|
|
//
|
|
// PeerConnectionClient.signalingQueue.async {
|
|
// guard let strongSelf = proxyCopy.get() else { return }
|
|
// guard let peerConnection = strongSelf.peerConnection else {
|
|
// Logger.debug("Ignoring obsolete event in terminated client")
|
|
// return
|
|
// }
|
|
// guard peerConnection == peerConnectionParam else {
|
|
// owsFailDebug("mismatched peerConnection callback.")
|
|
// return
|
|
// }
|
|
//
|
|
// Logger.info("didChange IceConnectionState:\(newState.debugDescription)")
|
|
// switch newState {
|
|
// case .connected, .completed:
|
|
// DispatchQueue.main.async(execute: connectedCompletion)
|
|
// case .failed:
|
|
// Logger.warn("RTCIceConnection failed.")
|
|
// DispatchQueue.main.async(execute: failedCompletion)
|
|
// case .disconnected:
|
|
// Logger.warn("RTCIceConnection disconnected.")
|
|
// DispatchQueue.main.async(execute: disconnectedCompletion)
|
|
// default:
|
|
// Logger.debug("ignoring change IceConnectionState:\(newState.debugDescription)")
|
|
// }
|
|
// }
|
|
// }
|
|
//
|
|
// /** Called any time the IceGatheringState changes. */
|
|
// internal func peerConnection(_ peerConnectionParam: RTCPeerConnection, didChange newState: RTCIceGatheringState) {
|
|
// Logger.info("didChange IceGatheringState:\(newState.debugDescription)")
|
|
// }
|
|
//
|
|
// /** New ice candidate has been found. */
|
|
// internal func peerConnection(_ peerConnectionParam: RTCPeerConnection, didGenerate candidate: RTCIceCandidate) {
|
|
// let proxyCopy = self.proxy
|
|
// let completion: (RTCIceCandidate) -> Void = { (candidate) in
|
|
// AssertIsOnMainThread()
|
|
// guard let strongSelf = proxyCopy.get() else { return }
|
|
// guard let strongDelegate = strongSelf.delegate else { return }
|
|
// strongDelegate.peerConnectionClient(strongSelf, addedLocalIceCandidate: candidate)
|
|
// }
|
|
//
|
|
// PeerConnectionClient.signalingQueue.async {
|
|
// guard let strongSelf = proxyCopy.get() else { return }
|
|
// guard let peerConnection = strongSelf.peerConnection else {
|
|
// Logger.debug("Ignoring obsolete event in terminated client")
|
|
// return
|
|
// }
|
|
// guard peerConnection == peerConnectionParam else {
|
|
// owsFailDebug("mismatched peerConnection callback.")
|
|
// return
|
|
// }
|
|
// Logger.info("adding local ICE candidate:\(candidate.sdp)")
|
|
// DispatchQueue.main.async {
|
|
// completion(candidate)
|
|
// }
|
|
// }
|
|
// }
|
|
//
|
|
// /** Called when a group of local Ice candidates have been removed. */
|
|
// internal func peerConnection(_ peerConnectionParam: RTCPeerConnection, didRemove candidates: [RTCIceCandidate]) {
|
|
// Logger.debug("didRemove IceCandidates:\(candidates)")
|
|
// }
|
|
//
|
|
// /** New data channel has been opened. */
|
|
// internal func peerConnection(_ peerConnectionParam: RTCPeerConnection, didOpen dataChannel: RTCDataChannel) {
|
|
// let proxyCopy = self.proxy
|
|
// let completion: ([PendingDataChannelMessage]) -> Void = { (pendingMessages) in
|
|
// AssertIsOnMainThread()
|
|
// guard let strongSelf = proxyCopy.get() else { return }
|
|
// pendingMessages.forEach { message in
|
|
// strongSelf.sendDataChannelMessage(data: message.data, description: message.description, isCritical: message.isCritical)
|
|
// }
|
|
// }
|
|
//
|
|
// PeerConnectionClient.signalingQueue.async {
|
|
// guard let strongSelf = proxyCopy.get() else { return }
|
|
// guard let peerConnection = strongSelf.peerConnection else {
|
|
// Logger.debug("Ignoring obsolete event in terminated client")
|
|
// return
|
|
// }
|
|
// guard peerConnection == peerConnectionParam else {
|
|
// owsFailDebug("mismatched peerConnection callback.")
|
|
// return
|
|
// }
|
|
// Logger.info("didOpen dataChannel:\(dataChannel)")
|
|
// if strongSelf.dataChannel != nil {
|
|
// owsFailDebug("dataChannel unexpectedly set twice.")
|
|
// }
|
|
// strongSelf.dataChannel = dataChannel
|
|
// dataChannel.delegate = strongSelf.proxy
|
|
//
|
|
// let pendingMessages = strongSelf.pendingDataChannelMessages
|
|
// strongSelf.pendingDataChannelMessages = []
|
|
// DispatchQueue.main.async {
|
|
// completion(pendingMessages)
|
|
// }
|
|
// }
|
|
// }
|
|
//
|
|
// internal func peerConnection(_ peerConnectionParam: RTCPeerConnection, didChange connectionState: RTCPeerConnectionState) {
|
|
// Logger.info("didChange PeerConnectionState:\(connectionState.debugDescription)")
|
|
// }
|
|
//
|
|
// // MARK: Helpers
|
|
//
|
|
// /**
|
|
// * We synchronize access to state in this class using this queue.
|
|
// */
|
|
// private func assertOnSignalingQueue() {
|
|
// assertOnQueue(type(of: self).signalingQueue)
|
|
// }
|
|
//
|
|
// // MARK: Test-only accessors
|
|
//
|
|
// internal func peerConnectionForTests() -> RTCPeerConnection {
|
|
// AssertIsOnMainThread()
|
|
//
|
|
// var result: RTCPeerConnection?
|
|
// PeerConnectionClient.signalingQueue.sync {
|
|
// result = peerConnection
|
|
// Logger.info("")
|
|
// }
|
|
// return result!
|
|
// }
|
|
//
|
|
// internal func dataChannelForTests() -> RTCDataChannel {
|
|
// AssertIsOnMainThread()
|
|
//
|
|
// var result: RTCDataChannel?
|
|
// PeerConnectionClient.signalingQueue.sync {
|
|
// result = dataChannel
|
|
// Logger.info("")
|
|
// }
|
|
// return result!
|
|
// }
|
|
//
|
|
// internal func flushSignalingQueueForTests() {
|
|
// AssertIsOnMainThread()
|
|
//
|
|
// PeerConnectionClient.signalingQueue.sync {
|
|
// // Noop.
|
|
// }
|
|
// }
|
|
//}
|
|
//
|
|
///**
|
|
// * Restrict an RTCSessionDescription to more secure parameters
|
|
// */
|
|
//class HardenedRTCSessionDescription {
|
|
// let rtcSessionDescription: RTCSessionDescription
|
|
// var sdp: String { return rtcSessionDescription.sdp }
|
|
//
|
|
// init(rtcSessionDescription: RTCSessionDescription) {
|
|
// self.rtcSessionDescription = HardenedRTCSessionDescription.harden(rtcSessionDescription: rtcSessionDescription)
|
|
// }
|
|
//
|
|
// /**
|
|
// * Set some more secure parameters for the session description
|
|
// */
|
|
// class func harden(rtcSessionDescription: RTCSessionDescription) -> RTCSessionDescription {
|
|
// var description = rtcSessionDescription.sdp
|
|
//
|
|
// // Enforce Constant bit rate.
|
|
// let cbrRegex = try! NSRegularExpression(pattern: "(a=fmtp:111 ((?!cbr=).)*)\r?\n", options: .caseInsensitive)
|
|
// description = cbrRegex.stringByReplacingMatches(in: description, options: [], range: NSRange(location: 0, length: description.utf16.count), withTemplate: "$1;cbr=1\r\n")
|
|
//
|
|
// // Strip plaintext audio-level details
|
|
// // https://tools.ietf.org/html/rfc6464
|
|
// let audioLevelRegex = try! NSRegularExpression(pattern: ".+urn:ietf:params:rtp-hdrext:ssrc-audio-level.*\r?\n", options: .caseInsensitive)
|
|
// description = audioLevelRegex.stringByReplacingMatches(in: description, options: [], range: NSRange(location: 0, length: description.utf16.count), withTemplate: "")
|
|
//
|
|
// return RTCSessionDescription.init(type: rtcSessionDescription.type, sdp: description)
|
|
// }
|
|
//
|
|
// var logSafeDescription: String {
|
|
// #if DEBUG
|
|
// return sdp
|
|
// #else
|
|
// return redactIPV6(sdp: redactIcePwd(sdp: sdp))
|
|
// #endif
|
|
// }
|
|
//
|
|
// private func redactIcePwd(sdp: String) -> String {
|
|
// #if DEBUG
|
|
// return sdp
|
|
// #else
|
|
// var text = sdp
|
|
// text = text.replacingOccurrences(of: "\r", with: "\n")
|
|
// text = text.replacingOccurrences(of: "\n\n", with: "\n")
|
|
// let lines = text.components(separatedBy: "\n")
|
|
// let filteredLines: [String] = lines.map { line in
|
|
// guard !line.contains("ice-pwd") else {
|
|
// return "[ REDACTED ice-pwd ]"
|
|
// }
|
|
// return line
|
|
// }
|
|
// let filteredText = filteredLines.joined(separator: "\n")
|
|
// return filteredText
|
|
// #endif
|
|
// }
|
|
//
|
|
// private func redactIPV6(sdp: String) -> String {
|
|
// #if DEBUG
|
|
// return sdp
|
|
// #else
|
|
//
|
|
// // Example values to match:
|
|
// //
|
|
// // * 2001:0db8:85a3:0000:0000:8a2e:0370:7334
|
|
// // * 2001:db8:85a3::8a2e:370:7334
|
|
// // * ::1
|
|
// // * ::
|
|
// // * ::ffff:192.0.2.128
|
|
// //
|
|
// // See: https://en.wikipedia.org/wiki/IPv6_addresshttps://en.wikipedia.org/wiki/IPv6_address
|
|
// do {
|
|
// let regex = try NSRegularExpression(pattern: "[\\da-f]*:[\\da-f]*:[\\da-f:\\.]*",
|
|
// options: .caseInsensitive)
|
|
// return regex.stringByReplacingMatches(in: sdp, options: [], range: NSRange(location: 0, length: sdp.utf16.count), withTemplate: "[ REDACTED_IPV6_ADDRESS ]")
|
|
// } catch {
|
|
// owsFailDebug("Could not redact IPv6 addresses.")
|
|
// return "[Could not redact IPv6 addresses.]"
|
|
// }
|
|
// #endif
|
|
// }
|
|
//}
|
|
//
|
|
//protocol VideoCaptureSettingsDelegate: class {
|
|
// var videoWidth: Int32 { get }
|
|
// var videoHeight: Int32 { get }
|
|
//}
|
|
//
|
|
//class VideoCaptureController {
|
|
//
|
|
// private let capturer: RTCCameraVideoCapturer
|
|
// private weak var settingsDelegate: VideoCaptureSettingsDelegate?
|
|
// private let serialQueue = DispatchQueue(label: "org.signal.videoCaptureController")
|
|
// private var isUsingFrontCamera: Bool = true
|
|
//
|
|
// public var captureSession: AVCaptureSession {
|
|
// return capturer.captureSession
|
|
// }
|
|
//
|
|
// public init(capturer: RTCCameraVideoCapturer, settingsDelegate: VideoCaptureSettingsDelegate) {
|
|
// self.capturer = capturer
|
|
// self.settingsDelegate = settingsDelegate
|
|
// }
|
|
//
|
|
// public func startCapture() {
|
|
// serialQueue.sync { [weak self] in
|
|
// guard let strongSelf = self else {
|
|
// return
|
|
// }
|
|
//
|
|
// strongSelf.startCaptureSync()
|
|
// }
|
|
// }
|
|
//
|
|
// public func stopCapture() {
|
|
// serialQueue.sync { [weak self] in
|
|
// guard let strongSelf = self else {
|
|
// return
|
|
// }
|
|
//
|
|
// strongSelf.capturer.stopCapture()
|
|
// }
|
|
// }
|
|
//
|
|
// public func switchCamera(isUsingFrontCamera: Bool) {
|
|
// serialQueue.sync { [weak self] in
|
|
// guard let strongSelf = self else {
|
|
// return
|
|
// }
|
|
//
|
|
// strongSelf.isUsingFrontCamera = isUsingFrontCamera
|
|
// strongSelf.startCaptureSync()
|
|
// }
|
|
// }
|
|
//
|
|
// private func assertIsOnSerialQueue() {
|
|
// if _isDebugAssertConfiguration(), #available(iOS 10.0, *) {
|
|
// assertOnQueue(serialQueue)
|
|
// }
|
|
// }
|
|
//
|
|
// private func startCaptureSync() {
|
|
// assertIsOnSerialQueue()
|
|
//
|
|
// let position: AVCaptureDevice.Position = isUsingFrontCamera ? .front : .back
|
|
// guard let device: AVCaptureDevice = self.device(position: position) else {
|
|
// owsFailDebug("unable to find captureDevice")
|
|
// return
|
|
// }
|
|
//
|
|
// guard let format: AVCaptureDevice.Format = self.format(device: device) else {
|
|
// owsFailDebug("unable to find captureDevice")
|
|
// return
|
|
// }
|
|
//
|
|
// let fps = self.framesPerSecond(format: format)
|
|
// capturer.startCapture(with: device, format: format, fps: fps)
|
|
// }
|
|
//
|
|
// private func device(position: AVCaptureDevice.Position) -> AVCaptureDevice? {
|
|
// let captureDevices = RTCCameraVideoCapturer.captureDevices()
|
|
// guard let device = (captureDevices.first { $0.position == position }) else {
|
|
// Logger.debug("unable to find desired position: \(position)")
|
|
// return captureDevices.first
|
|
// }
|
|
//
|
|
// return device
|
|
// }
|
|
//
|
|
// private func format(device: AVCaptureDevice) -> AVCaptureDevice.Format? {
|
|
// let formats = RTCCameraVideoCapturer.supportedFormats(for: device)
|
|
// let targetWidth = settingsDelegate?.videoWidth ?? 0
|
|
// let targetHeight = settingsDelegate?.videoHeight ?? 0
|
|
//
|
|
// var selectedFormat: AVCaptureDevice.Format?
|
|
// var currentDiff: Int32 = Int32.max
|
|
//
|
|
// for format in formats {
|
|
// let dimension = CMVideoFormatDescriptionGetDimensions(format.formatDescription)
|
|
// let diff = abs(targetWidth - dimension.width) + abs(targetHeight - dimension.height)
|
|
// if diff < currentDiff {
|
|
// selectedFormat = format
|
|
// currentDiff = diff
|
|
// }
|
|
// }
|
|
//
|
|
// if _isDebugAssertConfiguration(), let selectedFormat = selectedFormat {
|
|
// let dimension = CMVideoFormatDescriptionGetDimensions(selectedFormat.formatDescription)
|
|
// Logger.debug("selected format width: \(dimension.width) height: \(dimension.height)")
|
|
// }
|
|
//
|
|
// assert(selectedFormat != nil)
|
|
//
|
|
// return selectedFormat
|
|
// }
|
|
//
|
|
// private func framesPerSecond(format: AVCaptureDevice.Format) -> Int {
|
|
// var maxFrameRate: Float64 = 0
|
|
// for range in format.videoSupportedFrameRateRanges {
|
|
// maxFrameRate = max(maxFrameRate, range.maxFrameRate)
|
|
// }
|
|
//
|
|
// return Int(maxFrameRate)
|
|
// }
|
|
//}
|
|
//
|
|
//// MARK: Pretty Print Objc enums.
|
|
//
|
|
//fileprivate extension RTCSignalingState {
|
|
// var debugDescription: String {
|
|
// switch self {
|
|
// case .stable:
|
|
// return "stable"
|
|
// case .haveLocalOffer:
|
|
// return "haveLocalOffer"
|
|
// case .haveLocalPrAnswer:
|
|
// return "haveLocalPrAnswer"
|
|
// case .haveRemoteOffer:
|
|
// return "haveRemoteOffer"
|
|
// case .haveRemotePrAnswer:
|
|
// return "haveRemotePrAnswer"
|
|
// case .closed:
|
|
// return "closed"
|
|
// }
|
|
// }
|
|
//}
|
|
//
|
|
//fileprivate extension RTCIceGatheringState {
|
|
// var debugDescription: String {
|
|
// switch self {
|
|
// case .new:
|
|
// return "new"
|
|
// case .gathering:
|
|
// return "gathering"
|
|
// case .complete:
|
|
// return "complete"
|
|
// }
|
|
// }
|
|
//}
|
|
//
|
|
//fileprivate extension RTCIceConnectionState {
|
|
// var debugDescription: String {
|
|
// switch self {
|
|
// case .new:
|
|
// return "new"
|
|
// case .checking:
|
|
// return "checking"
|
|
// case .connected:
|
|
// return "connected"
|
|
// case .completed:
|
|
// return "completed"
|
|
// case .failed:
|
|
// return "failed"
|
|
// case .disconnected:
|
|
// return "disconnected"
|
|
// case .closed:
|
|
// return "closed"
|
|
// case .count:
|
|
// return "count"
|
|
// }
|
|
// }
|
|
//}
|
|
//
|
|
//fileprivate extension RTCPeerConnectionState {
|
|
// var debugDescription: String {
|
|
// switch self {
|
|
// case .new:
|
|
// return "new"
|
|
// case .connecting:
|
|
// return "connecting"
|
|
// case .connected:
|
|
// return "connected"
|
|
// case .disconnected:
|
|
// return "disconnected"
|
|
// case .failed:
|
|
// return "failed"
|
|
// case .closed:
|
|
// return "closed"
|
|
// }
|
|
// }
|
|
//}
|