|
|
|
@ -192,7 +192,7 @@ class PeerConnectionProxy: NSObject, RTCPeerConnectionDelegate, RTCDataChannelDe
|
|
|
|
|
* 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 {
|
|
|
|
|
class PeerConnectionClient: NSObject, RTCPeerConnectionDelegate, RTCDataChannelDelegate, VideoCaptureSettingsDelegate {
|
|
|
|
|
|
|
|
|
|
enum Identifiers: String {
|
|
|
|
|
case mediaStream = "ARDAMS",
|
|
|
|
@ -232,6 +232,7 @@ class PeerConnectionClient: NSObject, RTCPeerConnectionDelegate, RTCDataChannelD
|
|
|
|
|
|
|
|
|
|
// Video
|
|
|
|
|
|
|
|
|
|
private var videoCaptureController: VideoCaptureController?
|
|
|
|
|
private var videoCaptureSession: AVCaptureSession?
|
|
|
|
|
private var videoSender: RTCRtpSender?
|
|
|
|
|
private var localVideoTrack: RTCVideoTrack?
|
|
|
|
@ -307,15 +308,21 @@ class PeerConnectionClient: NSObject, RTCPeerConnectionDelegate, RTCDataChannelD
|
|
|
|
|
let configuration = RTCDataChannelConfiguration()
|
|
|
|
|
// Insist upon an "ordered" TCP data channel for delivery reliability.
|
|
|
|
|
configuration.isOrdered = true
|
|
|
|
|
let dataChannel = peerConnection.dataChannel(forLabel: Identifiers.dataChannelSignaling.rawValue,
|
|
|
|
|
configuration: configuration)
|
|
|
|
|
|
|
|
|
|
guard let dataChannel = peerConnection.dataChannel(forLabel: Identifiers.dataChannelSignaling.rawValue,
|
|
|
|
|
configuration: configuration) else {
|
|
|
|
|
|
|
|
|
|
// TODO fail outgoing call?
|
|
|
|
|
owsFail("dataChannel was unexpectedly nil")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
dataChannel.delegate = proxy
|
|
|
|
|
|
|
|
|
|
assert(self.dataChannel == nil)
|
|
|
|
|
self.dataChannel = dataChannel
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// MARK: Video
|
|
|
|
|
// MARK: - Video
|
|
|
|
|
|
|
|
|
|
fileprivate func createVideoSender() {
|
|
|
|
|
SwiftAssertIsOnMainThread(#function)
|
|
|
|
@ -331,20 +338,18 @@ class PeerConnectionClient: NSObject, RTCPeerConnectionDelegate, RTCDataChannelD
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TODO: We could cap the maximum video size.
|
|
|
|
|
let cameraConstraints = RTCMediaConstraints(mandatoryConstraints: nil,
|
|
|
|
|
optionalConstraints: nil)
|
|
|
|
|
let videoSource = factory.videoSource()
|
|
|
|
|
|
|
|
|
|
// TODO: Revisit the cameraConstraints.
|
|
|
|
|
let videoSource = factory.avFoundationVideoSource(with: cameraConstraints)
|
|
|
|
|
// TODO - MJK I don't think anyone cares about videoSource, just the capturer. Remove it?
|
|
|
|
|
self.localVideoSource = videoSource
|
|
|
|
|
|
|
|
|
|
self.videoCaptureSession = videoSource.captureSession
|
|
|
|
|
videoSource.useBackCamera = false
|
|
|
|
|
let capturer = RTCCameraVideoCapturer(delegate: videoSource)
|
|
|
|
|
self.videoCaptureSession = capturer.captureSession
|
|
|
|
|
|
|
|
|
|
let localVideoTrack = factory.videoTrack(with: videoSource, trackId: Identifiers.videoTrack.rawValue)
|
|
|
|
|
self.localVideoTrack = localVideoTrack
|
|
|
|
|
|
|
|
|
|
self.videoCaptureController = VideoCaptureController(capturer: capturer, settingsDelegate: self)
|
|
|
|
|
|
|
|
|
|
// 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
|
|
|
|
@ -356,24 +361,20 @@ class PeerConnectionClient: NSObject, RTCPeerConnectionDelegate, RTCDataChannelD
|
|
|
|
|
self.videoSender = videoSender
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public func setCameraSource(useBackCamera: Bool) {
|
|
|
|
|
public func setCameraSource(isUsingFrontCamera: Bool) {
|
|
|
|
|
SwiftAssertIsOnMainThread(#function)
|
|
|
|
|
|
|
|
|
|
let proxyCopy = self.proxy
|
|
|
|
|
PeerConnectionClient.signalingQueue.async {
|
|
|
|
|
guard let strongSelf = proxyCopy.get() else { return }
|
|
|
|
|
guard let localVideoSource = strongSelf.localVideoSource else {
|
|
|
|
|
Logger.debug("\(strongSelf.logTag) \(#function) Ignoring obsolete event in terminated client")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// certain devices, e.g. 16GB iPod touch don't have a back camera
|
|
|
|
|
guard localVideoSource.canUseBackCamera else {
|
|
|
|
|
owsFail("\(strongSelf.logTag) in \(#function) canUseBackCamera was unexpectedly false")
|
|
|
|
|
guard let captureController = strongSelf.videoCaptureController else {
|
|
|
|
|
owsFail("\(self.logTag) in \(#function) captureController was unexpectedly nil")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
localVideoSource.useBackCamera = useBackCamera
|
|
|
|
|
captureController.switchCamera(isUsingFrontCamera: isUsingFrontCamera)
|
|
|
|
|
captureController.startCapture()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -416,7 +417,18 @@ class PeerConnectionClient: NSObject, RTCPeerConnectionDelegate, RTCDataChannelD
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// MARK: Audio
|
|
|
|
|
// MARK: VideoCaptureSettingsDelegate
|
|
|
|
|
|
|
|
|
|
// MJK: fixme
|
|
|
|
|
var videoWidth: Int32 {
|
|
|
|
|
return 400
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var videoHeight: Int32 {
|
|
|
|
|
return 400
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// MARK: - Audio
|
|
|
|
|
|
|
|
|
|
fileprivate func createAudioSender() {
|
|
|
|
|
SwiftAssertIsOnMainThread(#function)
|
|
|
|
@ -1092,6 +1104,90 @@ class HardenedRTCSessionDescription {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protocol VideoCaptureSettingsDelegate: class {
|
|
|
|
|
var videoWidth: Int32 { get }
|
|
|
|
|
var videoHeight: Int32 { get }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class VideoCaptureController {
|
|
|
|
|
|
|
|
|
|
let capturer: RTCCameraVideoCapturer
|
|
|
|
|
weak var settingsDelegate: VideoCaptureSettingsDelegate?
|
|
|
|
|
var isUsingFrontCamera: Bool = true
|
|
|
|
|
|
|
|
|
|
public init(capturer: RTCCameraVideoCapturer, settingsDelegate: VideoCaptureSettingsDelegate) {
|
|
|
|
|
self.capturer = capturer
|
|
|
|
|
self.settingsDelegate = settingsDelegate
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public func startCapture() {
|
|
|
|
|
let position: AVCaptureDevice.Position = isUsingFrontCamera ? .front : .back
|
|
|
|
|
guard let device: AVCaptureDevice = self.device(position: position) else {
|
|
|
|
|
owsFail("unable to find captureDevice")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
guard let format: AVCaptureDevice.Format = self.format(device: device) else {
|
|
|
|
|
owsFail("unable to find captureDevice")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let fps = self.framesPerSecond(format: format)
|
|
|
|
|
|
|
|
|
|
capturer.startCapture(with: device, format: format, fps: fps)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public func stopCapture() {
|
|
|
|
|
self.capturer.stopCapture()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public func switchCamera(isUsingFrontCamera: Bool) {
|
|
|
|
|
self.isUsingFrontCamera = isUsingFrontCamera
|
|
|
|
|
self.startCapture()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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 {
|
|
|
|
|