2023-03-02 16:17:01 +03:00
//
// C r e a t e d b y A v e n t l y o n 0 9 . 0 2 . 2 0 2 3 .
// C o p y r i g h t ( c ) 2 0 2 3 S i m p l e X C h a t . A l l r i g h t s r e s e r v e d .
//
import WebRTC
import LZString
import SwiftUI
import SimpleXChat
final class WebRTCClient : NSObject , RTCVideoViewDelegate , RTCFrameEncryptorDelegate , RTCFrameDecryptorDelegate {
private static let factory : RTCPeerConnectionFactory = {
RTCInitializeSSL ( )
let videoEncoderFactory = RTCDefaultVideoEncoderFactory ( )
let videoDecoderFactory = RTCDefaultVideoDecoderFactory ( )
2023-06-08 17:08:35 +03:00
videoEncoderFactory . preferredCodec = RTCVideoCodecInfo ( name : kRTCVp8CodecName )
2023-03-02 16:17:01 +03:00
return RTCPeerConnectionFactory ( encoderFactory : videoEncoderFactory , decoderFactory : videoDecoderFactory )
} ( )
private static let ivTagBytes : Int = 28
private static let enableEncryption : Bool = true
2023-12-21 00:42:40 +00:00
private var chat_ctrl = getChatCtrl ( )
2023-03-02 16:17:01 +03:00
struct Call {
var connection : RTCPeerConnection
2023-11-28 06:20:51 +08:00
var iceCandidates : IceCandidates
2023-03-02 16:17:01 +03:00
var localMedia : CallMediaType
var localCamera : RTCVideoCapturer ?
var localVideoSource : RTCVideoSource ?
var localStream : RTCVideoTrack ?
var remoteStream : RTCVideoTrack ?
var device : AVCaptureDevice . Position = . front
var aesKey : String ?
var frameEncryptor : RTCFrameEncryptor ?
var frameDecryptor : RTCFrameDecryptor ?
}
2023-11-28 06:20:51 +08:00
actor IceCandidates {
private var candidates : [ RTCIceCandidate ] = [ ]
func getAndClear ( ) async -> [ RTCIceCandidate ] {
let cs = candidates
candidates = [ ]
return cs
}
func append ( _ c : RTCIceCandidate ) async {
candidates . append ( c )
}
}
2023-03-02 16:17:01 +03:00
private let rtcAudioSession = RTCAudioSession . sharedInstance ( )
2024-04-11 17:31:30 +07:00
private let audioQueue = DispatchQueue ( label : " chat.simplex.app.audio " )
2023-03-02 16:17:01 +03:00
private var sendCallResponse : ( WVAPIMessage ) async -> Void
2023-11-29 01:36:05 +08:00
var activeCall : Binding < Call ? >
2023-03-02 16:17:01 +03:00
private var localRendererAspectRatio : Binding < CGFloat ? >
@ available ( * , unavailable )
override init ( ) {
fatalError ( " Unimplemented " )
}
required init ( _ activeCall : Binding < Call ? > , _ sendCallResponse : @ escaping ( WVAPIMessage ) async -> Void , _ localRendererAspectRatio : Binding < CGFloat ? > ) {
self . sendCallResponse = sendCallResponse
self . activeCall = activeCall
self . localRendererAspectRatio = localRendererAspectRatio
2023-03-14 11:12:40 +03:00
rtcAudioSession . useManualAudio = CallController . useCallKit ( )
rtcAudioSession . isAudioEnabled = ! CallController . useCallKit ( )
2024-04-15 01:18:12 +07:00
logger . debug ( " WebRTCClient: rtcAudioSession has manual audio \( self . rtcAudioSession . useManualAudio ) and audio enabled \( self . rtcAudioSession . isAudioEnabled ) " )
2023-03-02 16:17:01 +03:00
super . init ( )
}
let defaultIceServers : [ WebRTC . RTCIceServer ] = [
2024-04-15 01:18:12 +07:00
WebRTC . RTCIceServer ( urlStrings : [ " stuns:stun.simplex.im:443 " ] ) ,
// W e b R T C . R T C I c e S e r v e r ( u r l S t r i n g s : [ " t u r n s : t u r n . s i m p l e x . i m : 4 4 3 ? t r a n s p o r t = u d p " ] , u s e r n a m e : " p r i v a t e 2 " , c r e d e n t i a l : " H x u q 2 Q x U j n h j 9 6 Z q 2 r 4 H j q H R j " ) ,
WebRTC . RTCIceServer ( urlStrings : [ " turns:turn.simplex.im:443?transport=tcp " ] , username : " private2 " , credential : " Hxuq2QxUjnhj96Zq2r4HjqHRj " ) ,
2023-03-02 16:17:01 +03:00
]
2023-11-28 06:20:51 +08:00
func initializeCall ( _ iceServers : [ WebRTC . RTCIceServer ] ? , _ mediaType : CallMediaType , _ aesKey : String ? , _ relay : Bool ? ) -> Call {
2023-03-02 16:17:01 +03:00
let connection = createPeerConnection ( iceServers ? ? getWebRTCIceServers ( ) ? ? defaultIceServers , relay )
connection . delegate = self
createAudioSender ( connection )
var localStream : RTCVideoTrack ? = nil
var remoteStream : RTCVideoTrack ? = nil
var localCamera : RTCVideoCapturer ? = nil
var localVideoSource : RTCVideoSource ? = nil
if mediaType = = . video {
( localStream , remoteStream , localCamera , localVideoSource ) = createVideoSender ( connection )
}
var frameEncryptor : RTCFrameEncryptor ? = nil
var frameDecryptor : RTCFrameDecryptor ? = nil
if aesKey != nil {
let encryptor = RTCFrameEncryptor . init ( sizeChange : Int32 ( WebRTCClient . ivTagBytes ) )
encryptor . delegate = self
frameEncryptor = encryptor
connection . senders . forEach { $0 . setRtcFrameEncryptor ( encryptor ) }
let decryptor = RTCFrameDecryptor . init ( sizeChange : - Int32 ( WebRTCClient . ivTagBytes ) )
decryptor . delegate = self
frameDecryptor = decryptor
// H a s n o v i d e o r e c e i v e r i n o u t g o i n g c a l l i f a p p l i e d h e r e , s e e [ p e e r C o n n e c t i o n ( _ c o n n e c t i o n : R T C P e e r C o n n e c t i o n , d i d C h a n g e n e w S t a t e ]
// c o n n e c t i o n . r e c e i v e r s . f o r E a c h { $ 0 . s e t R t c F r a m e D e c r y p t o r ( d e c r y p t o r ) }
}
return Call (
connection : connection ,
2023-11-28 06:20:51 +08:00
iceCandidates : IceCandidates ( ) ,
2023-03-02 16:17:01 +03:00
localMedia : mediaType ,
localCamera : localCamera ,
localVideoSource : localVideoSource ,
localStream : localStream ,
remoteStream : remoteStream ,
aesKey : aesKey ,
frameEncryptor : frameEncryptor ,
frameDecryptor : frameDecryptor
)
}
func createPeerConnection ( _ iceServers : [ WebRTC . RTCIceServer ] , _ relay : Bool ? ) -> RTCPeerConnection {
let constraints = RTCMediaConstraints ( mandatoryConstraints : nil ,
optionalConstraints : [ " DtlsSrtpKeyAgreement " : kRTCMediaConstraintsValueTrue ] )
guard let connection = WebRTCClient . factory . peerConnection (
with : getCallConfig ( iceServers , relay ) ,
constraints : constraints , delegate : nil
)
else {
fatalError ( " Unable to create RTCPeerConnection " )
}
return connection
}
func getCallConfig ( _ iceServers : [ WebRTC . RTCIceServer ] , _ relay : Bool ? ) -> RTCConfiguration {
let config = RTCConfiguration ( )
config . iceServers = iceServers
config . sdpSemantics = . unifiedPlan
config . continualGatheringPolicy = . gatherContinually
config . iceTransportPolicy = relay = = true ? . relay : . all
// A l l o w s t o w a i t 3 0 s e c b e f o r e ` f a i l i n g ` c o n n e c t i o n i f t h e a n s w e r f r o m r e m o t e s i d e i s n o t r e c e i v e d i n t i m e
config . iceInactiveTimeout = 30_000
return config
}
func addIceCandidates ( _ connection : RTCPeerConnection , _ remoteIceCandidates : [ RTCIceCandidate ] ) {
remoteIceCandidates . forEach { candidate in
connection . add ( candidate . toWebRTCCandidate ( ) ) { error in
if let error = error {
logger . error ( " Adding candidate error \( error ) " )
}
}
}
}
func sendCallCommand ( command : WCallCommand ) async {
var resp : WCallResponse ? = nil
let pc = activeCall . wrappedValue ? . connection
switch command {
case . capabilities :
resp = . capabilities ( capabilities : CallCapabilities ( encryption : WebRTCClient . enableEncryption ) )
case let . start ( media : media , aesKey , iceServers , relay ) :
logger . debug ( " starting incoming call - create webrtc session " )
if activeCall . wrappedValue != nil { endCall ( ) }
let encryption = WebRTCClient . enableEncryption
2023-11-28 06:20:51 +08:00
let call = initializeCall ( iceServers ? . toWebRTCIceServers ( ) , media , encryption ? aesKey : nil , relay )
2023-03-02 16:17:01 +03:00
activeCall . wrappedValue = call
2023-11-29 01:36:05 +08:00
let ( offer , error ) = await call . connection . offer ( )
if let offer = offer {
resp = . offer (
offer : compressToBase64 ( input : encodeJSON ( CustomRTCSessionDescription ( type : offer . type . toSdpType ( ) , sdp : offer . sdp ) ) ) ,
iceCandidates : compressToBase64 ( input : encodeJSON ( await self . getInitialIceCandidates ( ) ) ) ,
capabilities : CallCapabilities ( encryption : encryption )
)
self . waitForMoreIceCandidates ( )
} else {
resp = . error ( message : " offer error: \( error ? . localizedDescription ? ? " unknown error " ) " )
2023-03-02 16:17:01 +03:00
}
case let . offer ( offer , iceCandidates , media , aesKey , iceServers , relay ) :
if activeCall . wrappedValue != nil {
resp = . error ( message : " accept: call already started " )
} else if ! WebRTCClient . enableEncryption && aesKey != nil {
resp = . error ( message : " accept: encryption is not supported " )
} else if let offer : CustomRTCSessionDescription = decodeJSON ( decompressFromBase64 ( input : offer ) ) ,
let remoteIceCandidates : [ RTCIceCandidate ] = decodeJSON ( decompressFromBase64 ( input : iceCandidates ) ) {
2023-11-28 06:20:51 +08:00
let call = initializeCall ( iceServers ? . toWebRTCIceServers ( ) , media , WebRTCClient . enableEncryption ? aesKey : nil , relay )
2023-03-02 16:17:01 +03:00
activeCall . wrappedValue = call
let pc = call . connection
if let type = offer . type , let sdp = offer . sdp {
if ( try ? await pc . setRemoteDescription ( RTCSessionDescription ( type : type . toWebRTCSdpType ( ) , sdp : sdp ) ) ) != nil {
2023-11-29 01:36:05 +08:00
let ( answer , error ) = await pc . answer ( )
if let answer = answer {
2023-03-02 16:17:01 +03:00
self . addIceCandidates ( pc , remoteIceCandidates )
2023-11-29 01:36:05 +08:00
resp = . answer (
answer : compressToBase64 ( input : encodeJSON ( CustomRTCSessionDescription ( type : answer . type . toSdpType ( ) , sdp : answer . sdp ) ) ) ,
iceCandidates : compressToBase64 ( input : encodeJSON ( await self . getInitialIceCandidates ( ) ) )
)
self . waitForMoreIceCandidates ( )
} else {
resp = . error ( message : " answer error: \( error ? . localizedDescription ? ? " unknown error " ) " )
2023-03-02 16:17:01 +03:00
}
} else {
resp = . error ( message : " accept: remote description is not set " )
}
}
}
case let . answer ( answer , iceCandidates ) :
if pc = = nil {
resp = . error ( message : " answer: call not started " )
} else if pc ? . localDescription = = nil {
resp = . error ( message : " answer: local description is not set " )
} else if pc ? . remoteDescription != nil {
resp = . error ( message : " answer: remote description already set " )
} else if let answer : CustomRTCSessionDescription = decodeJSON ( decompressFromBase64 ( input : answer ) ) ,
let remoteIceCandidates : [ RTCIceCandidate ] = decodeJSON ( decompressFromBase64 ( input : iceCandidates ) ) ,
let type = answer . type , let sdp = answer . sdp ,
let pc = pc {
if ( try ? await pc . setRemoteDescription ( RTCSessionDescription ( type : type . toWebRTCSdpType ( ) , sdp : sdp ) ) ) != nil {
addIceCandidates ( pc , remoteIceCandidates )
resp = . ok
} else {
resp = . error ( message : " answer: remote description is not set " )
}
}
case let . ice ( iceCandidates ) :
if let pc = pc ,
let remoteIceCandidates : [ RTCIceCandidate ] = decodeJSON ( decompressFromBase64 ( input : iceCandidates ) ) {
addIceCandidates ( pc , remoteIceCandidates )
resp = . ok
} else {
resp = . error ( message : " ice: call not started " )
}
case let . media ( media , enable ) :
if activeCall . wrappedValue = = nil {
resp = . error ( message : " media: call not started " )
} else if activeCall . wrappedValue ? . localMedia = = . audio && media = = . video {
resp = . error ( message : " media: no video " )
} else {
enableMedia ( media , enable )
resp = . ok
}
case . end :
2023-11-28 06:20:51 +08:00
// T O D O p o s s i b l y , e n d C a l l s h o u l d b e c a l l e d b e f o r e r e t u r n i n g . o k
2023-03-02 16:17:01 +03:00
await sendCallResponse ( . init ( corrId : nil , resp : . ok , command : command ) )
endCall ( )
}
if let resp = resp {
await sendCallResponse ( . init ( corrId : nil , resp : resp , command : command ) )
}
}
2023-11-28 06:20:51 +08:00
func getInitialIceCandidates ( ) async -> [ RTCIceCandidate ] {
await untilIceComplete ( timeoutMs : 750 , stepMs : 150 ) { }
let candidates = await activeCall . wrappedValue ? . iceCandidates . getAndClear ( ) ? ? [ ]
logger . debug ( " WebRTCClient: sending initial ice candidates: \( candidates . count ) " )
return candidates
}
2023-11-29 01:36:05 +08:00
func waitForMoreIceCandidates ( ) {
Task {
await untilIceComplete ( timeoutMs : 12000 , stepMs : 1500 ) {
let candidates = await self . activeCall . wrappedValue ? . iceCandidates . getAndClear ( ) ? ? [ ]
if candidates . count > 0 {
logger . debug ( " WebRTCClient: sending more ice candidates: \( candidates . count ) " )
await self . sendIceCandidates ( candidates )
}
2023-11-28 06:20:51 +08:00
}
}
}
func sendIceCandidates ( _ candidates : [ RTCIceCandidate ] ) async {
await self . sendCallResponse ( . init (
corrId : nil ,
resp : . ice ( iceCandidates : compressToBase64 ( input : encodeJSON ( candidates ) ) ) ,
command : nil )
)
}
2023-03-02 16:17:01 +03:00
func enableMedia ( _ media : CallMediaType , _ enable : Bool ) {
2023-03-15 13:21:21 +03:00
logger . debug ( " WebRTCClient: enabling media \( media . rawValue ) \( enable ) " )
2023-03-02 16:17:01 +03:00
media = = . video ? setVideoEnabled ( enable ) : setAudioEnabled ( enable )
}
func addLocalRenderer ( _ activeCall : Call , _ renderer : RTCEAGLVideoView ) {
activeCall . localStream ? . add ( renderer )
// T o g e t w i d t h a n d h e i g h t o f a f r a m e , s e e v i d e o V i e w ( v i d e o V i e w : , d i d C h a n g e V i d e o S i z e )
renderer . delegate = self
}
func videoView ( _ videoView : RTCVideoRenderer , didChangeVideoSize size : CGSize ) {
guard size . height > 0 else { return }
localRendererAspectRatio . wrappedValue = size . width / size . height
}
func frameDecryptor ( _ decryptor : RTCFrameDecryptor , mediaType : RTCRtpMediaType , withFrame encrypted : Data ) -> Data ? {
guard encrypted . count > 0 else { return nil }
if var key : [ CChar ] = activeCall . wrappedValue ? . aesKey ? . cString ( using : . utf8 ) ,
let pointer : UnsafeMutableRawPointer = malloc ( encrypted . count ) {
memcpy ( pointer , ( encrypted as NSData ) . bytes , encrypted . count )
let isKeyFrame = encrypted [ 0 ] & 1 = = 0
let clearTextBytesSize = mediaType . rawValue = = 0 ? 1 : isKeyFrame ? 10 : 3
logCrypto ( " decrypt " , chat_decrypt_media ( & key , pointer . advanced ( by : clearTextBytesSize ) , Int32 ( encrypted . count - clearTextBytesSize ) ) )
return Data ( bytes : pointer , count : encrypted . count - WebRTCClient . ivTagBytes )
} else {
return nil
}
}
func frameEncryptor ( _ encryptor : RTCFrameEncryptor , mediaType : RTCRtpMediaType , withFrame unencrypted : Data ) -> Data ? {
guard unencrypted . count > 0 else { return nil }
if var key : [ CChar ] = activeCall . wrappedValue ? . aesKey ? . cString ( using : . utf8 ) ,
let pointer : UnsafeMutableRawPointer = malloc ( unencrypted . count + WebRTCClient . ivTagBytes ) {
memcpy ( pointer , ( unencrypted as NSData ) . bytes , unencrypted . count )
let isKeyFrame = unencrypted [ 0 ] & 1 = = 0
let clearTextBytesSize = mediaType . rawValue = = 0 ? 1 : isKeyFrame ? 10 : 3
2023-12-21 00:42:40 +00:00
logCrypto ( " encrypt " , chat_encrypt_media ( chat_ctrl , & key , pointer . advanced ( by : clearTextBytesSize ) , Int32 ( unencrypted . count + WebRTCClient . ivTagBytes - clearTextBytesSize ) ) )
2023-03-02 16:17:01 +03:00
return Data ( bytes : pointer , count : unencrypted . count + WebRTCClient . ivTagBytes )
} else {
return nil
}
}
private func logCrypto ( _ op : String , _ r : UnsafeMutablePointer < CChar > ? ) {
if let r = r {
let err = fromCString ( r )
if err != " " {
logger . error ( " \( op ) error: \( err ) " )
// } e l s e {
// l o g g e r . d e b u g ( " \ ( o p ) o k " )
}
}
}
func addRemoteRenderer ( _ activeCall : Call , _ renderer : RTCVideoRenderer ) {
activeCall . remoteStream ? . add ( renderer )
}
2024-02-13 22:04:42 +07:00
func removeRemoteRenderer ( _ activeCall : Call , _ renderer : RTCVideoRenderer ) {
activeCall . remoteStream ? . remove ( renderer )
}
2023-03-02 16:17:01 +03:00
func startCaptureLocalVideo ( _ activeCall : Call ) {
2023-06-08 18:09:14 +03:00
#if targetEnvironment ( simulator )
guard
let capturer = activeCall . localCamera as ? RTCFileVideoCapturer
else {
logger . error ( " Unable to work with a file capturer " )
return
}
capturer . stopCapture ( )
// D r a g v i d e o f i l e n a m e d ` v i d e o . m p 4 ` t o ` s o u n d s ` d i r e c t o r y i n t h e p r o j e c t f r o m a n y o t h e r p a t h i n f i l e s y s t e m
capturer . startCapturing ( fromFileNamed : " sounds/video.mp4 " )
#else
2023-03-02 16:17:01 +03:00
guard
let capturer = activeCall . localCamera as ? RTCCameraVideoCapturer ,
let camera = ( RTCCameraVideoCapturer . captureDevices ( ) . first { $0 . position = = activeCall . device } )
else {
logger . error ( " Unable to find a camera " )
return
}
let supported = RTCCameraVideoCapturer . supportedFormats ( for : camera )
let height : ( AVCaptureDevice . Format ) -> Int32 = { ( format : AVCaptureDevice . Format ) in CMVideoFormatDescriptionGetDimensions ( format . formatDescription ) . height }
let format = supported . first ( where : { height ( $0 ) = = 1280 } )
? ? supported . first ( where : { height ( $0 ) >= 480 && height ( $0 ) < 1280 } )
? ? supported . first ( where : { height ( $0 ) > 1280 } )
guard
let format = format ,
let fps = format . videoSupportedFrameRateRanges . max ( by : { $0 . maxFrameRate < $1 . maxFrameRate } )
else {
logger . error ( " Unable to find any format for camera or to choose FPS " )
return
}
logger . debug ( " Format for camera is \( format . description ) " )
capturer . stopCapture ( )
capturer . startCapture ( with : camera ,
format : format ,
fps : Int ( min ( 24 , fps . maxFrameRate ) ) )
2023-06-08 18:09:14 +03:00
#endif
2023-03-02 16:17:01 +03:00
}
private func createAudioSender ( _ connection : RTCPeerConnection ) {
let streamId = " stream "
let audioTrack = createAudioTrack ( )
connection . add ( audioTrack , streamIds : [ streamId ] )
}
private func createVideoSender ( _ connection : RTCPeerConnection ) -> ( RTCVideoTrack ? , RTCVideoTrack ? , RTCVideoCapturer ? , RTCVideoSource ? ) {
let streamId = " stream "
let ( localVideoTrack , localCamera , localVideoSource ) = createVideoTrack ( )
connection . add ( localVideoTrack , streamIds : [ streamId ] )
return ( localVideoTrack , connection . transceivers . first { $0 . mediaType = = . video } ? . receiver . track as ? RTCVideoTrack , localCamera , localVideoSource )
}
private func createAudioTrack ( ) -> RTCAudioTrack {
let audioConstrains = RTCMediaConstraints ( mandatoryConstraints : nil , optionalConstraints : nil )
let audioSource = WebRTCClient . factory . audioSource ( with : audioConstrains )
let audioTrack = WebRTCClient . factory . audioTrack ( with : audioSource , trackId : " audio0 " )
return audioTrack
}
private func createVideoTrack ( ) -> ( RTCVideoTrack , RTCVideoCapturer , RTCVideoSource ) {
let localVideoSource = WebRTCClient . factory . videoSource ( )
#if targetEnvironment ( simulator )
let localCamera = RTCFileVideoCapturer ( delegate : localVideoSource )
#else
let localCamera = RTCCameraVideoCapturer ( delegate : localVideoSource )
#endif
let localVideoTrack = WebRTCClient . factory . videoTrack ( with : localVideoSource , trackId : " video0 " )
return ( localVideoTrack , localCamera , localVideoSource )
}
func endCall ( ) {
guard let call = activeCall . wrappedValue else { return }
2023-03-15 13:21:21 +03:00
logger . debug ( " WebRTCClient: ending the call " )
2023-03-02 16:17:01 +03:00
activeCall . wrappedValue = nil
2024-02-13 22:04:42 +07:00
( call . localCamera as ? RTCCameraVideoCapturer ) ? . stopCapture ( )
2023-03-02 16:17:01 +03:00
call . connection . close ( )
call . connection . delegate = nil
call . frameEncryptor ? . delegate = nil
call . frameDecryptor ? . delegate = nil
audioSessionToDefaults ( )
}
2023-11-28 06:20:51 +08:00
func untilIceComplete ( timeoutMs : UInt64 , stepMs : UInt64 , action : @ escaping ( ) async -> Void ) async {
var t : UInt64 = 0
repeat {
_ = try ? await Task . sleep ( nanoseconds : stepMs * 1000000 )
t += stepMs
await action ( )
} while t < timeoutMs && activeCall . wrappedValue ? . connection . iceGatheringState != . complete
2023-03-02 16:17:01 +03:00
}
}
extension WebRTC . RTCPeerConnection {
func mediaConstraints ( ) -> RTCMediaConstraints {
RTCMediaConstraints (
mandatoryConstraints : [ kRTCMediaConstraintsOfferToReceiveAudio : kRTCMediaConstraintsValueTrue ,
kRTCMediaConstraintsOfferToReceiveVideo : kRTCMediaConstraintsValueTrue ] ,
optionalConstraints : nil )
}
2023-11-29 01:36:05 +08:00
func offer ( ) async -> ( RTCSessionDescription ? , Error ? ) {
await withCheckedContinuation { cont in
offer ( for : mediaConstraints ( ) ) { ( sdp , error ) in
self . processSDP ( cont , sdp , error )
2023-03-02 16:17:01 +03:00
}
}
}
2023-11-29 01:36:05 +08:00
func answer ( ) async -> ( RTCSessionDescription ? , Error ? ) {
await withCheckedContinuation { cont in
answer ( for : mediaConstraints ( ) ) { ( sdp , error ) in
self . processSDP ( cont , sdp , error )
2023-03-02 16:17:01 +03:00
}
2023-11-29 01:36:05 +08:00
}
}
private func processSDP ( _ cont : CheckedContinuation < ( RTCSessionDescription ? , Error ? ) , Never > , _ sdp : RTCSessionDescription ? , _ error : Error ? ) {
if let sdp = sdp {
2023-03-02 16:17:01 +03:00
self . setLocalDescription ( sdp , completionHandler : { ( error ) in
2023-11-29 01:36:05 +08:00
if let error = error {
cont . resume ( returning : ( nil , error ) )
} else {
cont . resume ( returning : ( sdp , nil ) )
}
2023-03-02 16:17:01 +03:00
} )
2023-11-29 01:36:05 +08:00
} else {
cont . resume ( returning : ( nil , error ) )
2023-03-02 16:17:01 +03:00
}
}
}
extension WebRTCClient : RTCPeerConnectionDelegate {
func peerConnection ( _ connection : RTCPeerConnection , didChange stateChanged : RTCSignalingState ) {
logger . debug ( " Connection new signaling state: \( stateChanged . rawValue ) " )
}
func peerConnection ( _ connection : RTCPeerConnection , didAdd stream : RTCMediaStream ) {
logger . debug ( " Connection did add stream " )
}
func peerConnection ( _ connection : RTCPeerConnection , didRemove stream : RTCMediaStream ) {
logger . debug ( " Connection did remove stream " )
}
func peerConnectionShouldNegotiate ( _ connection : RTCPeerConnection ) {
logger . debug ( " Connection should negotiate " )
}
func peerConnection ( _ connection : RTCPeerConnection , didChange newState : RTCIceConnectionState ) {
debugPrint ( " Connection new connection state: \( newState . toString ( ) ? ? " " + newState . rawValue . description ) \( connection . receivers ) " )
guard let call = activeCall . wrappedValue ,
let connectionStateString = newState . toString ( ) ,
let iceConnectionStateString = connection . iceConnectionState . toString ( ) ,
let iceGatheringStateString = connection . iceGatheringState . toString ( ) ,
let signalingStateString = connection . signalingState . toString ( )
else {
return
}
Task {
await sendCallResponse ( . init (
corrId : nil ,
resp : . connection ( state : ConnectionState (
connectionState : connectionStateString ,
iceConnectionState : iceConnectionStateString ,
iceGatheringState : iceGatheringStateString ,
signalingState : signalingStateString )
) ,
command : nil )
)
switch newState {
case . checking :
if let frameDecryptor = activeCall . wrappedValue ? . frameDecryptor {
connection . receivers . forEach { $0 . setRtcFrameDecryptor ( frameDecryptor ) }
}
let enableSpeaker : Bool
switch call . localMedia {
case . video : enableSpeaker = true
default : enableSpeaker = false
}
setSpeakerEnabledAndConfigureSession ( enableSpeaker )
2023-11-28 06:20:51 +08:00
case . connected : sendConnectedEvent ( connection )
2023-03-02 16:17:01 +03:00
case . disconnected , . failed : endCall ( )
default : do { }
}
}
}
func peerConnection ( _ connection : RTCPeerConnection , didChange newState : RTCIceGatheringState ) {
logger . debug ( " connection new gathering state: \( newState . toString ( ) ? ? " " + newState . rawValue . description ) " )
}
func peerConnection ( _ connection : RTCPeerConnection , didGenerate candidate : WebRTC . RTCIceCandidate ) {
// l o g g e r . d e b u g ( " C o n n e c t i o n g e n e r a t e d c a n d i d a t e \ ( c a n d i d a t e . d e b u g D e s c r i p t i o n ) " )
2023-11-28 06:20:51 +08:00
Task {
await self . activeCall . wrappedValue ? . iceCandidates . append ( candidate . toCandidate ( nil , nil ) )
}
2023-03-02 16:17:01 +03:00
}
func peerConnection ( _ connection : RTCPeerConnection , didRemove candidates : [ WebRTC . RTCIceCandidate ] ) {
logger . debug ( " Connection did remove candidates " )
}
func peerConnection ( _ connection : RTCPeerConnection , didOpen dataChannel : RTCDataChannel ) { }
func peerConnection ( _ connection : RTCPeerConnection ,
didChangeLocalCandidate local : WebRTC . RTCIceCandidate ,
remoteCandidate remote : WebRTC . RTCIceCandidate ,
lastReceivedMs lastDataReceivedMs : Int32 ,
changeReason reason : String ) {
// l o g g e r . d e b u g ( " C o n n e c t i o n c h a n g e d c a n d i d a t e \ ( r e a s o n ) \ ( r e m o t e . d e b u g D e s c r i p t i o n ) \ ( r e m o t e . d e s c r i p t i o n ) " )
}
2023-11-28 06:20:51 +08:00
func sendConnectedEvent ( _ connection : WebRTC . RTCPeerConnection ) {
2023-03-02 16:17:01 +03:00
connection . statistics { ( stats : RTCStatisticsReport ) in
stats . statistics . values . forEach { stat in
// l o g g e r . d e b u g ( " S t a t \ ( s t a t . d e b u g D e s c r i p t i o n ) " )
if stat . type = = " candidate-pair " , stat . values [ " state " ] as ? String = = " succeeded " ,
let localId = stat . values [ " localCandidateId " ] as ? String ,
let remoteId = stat . values [ " remoteCandidateId " ] as ? String ,
let localStats = stats . statistics [ localId ] ,
2023-11-28 06:20:51 +08:00
let remoteStats = stats . statistics [ remoteId ]
2023-03-02 16:17:01 +03:00
{
Task {
await self . sendCallResponse ( . init (
corrId : nil ,
resp : . connected ( connectionInfo : ConnectionInfo (
2023-11-28 06:20:51 +08:00
localCandidate : RTCIceCandidate (
candidateType : RTCIceCandidateType . init ( rawValue : localStats . values [ " candidateType " ] as ! String ) ,
protocol : localStats . values [ " protocol " ] as ? String ,
sdpMid : nil ,
sdpMLineIndex : nil ,
candidate : " "
2023-03-02 16:17:01 +03:00
) ,
2023-11-28 06:20:51 +08:00
remoteCandidate : RTCIceCandidate (
candidateType : RTCIceCandidateType . init ( rawValue : remoteStats . values [ " candidateType " ] as ! String ) ,
protocol : remoteStats . values [ " protocol " ] as ? String ,
sdpMid : nil ,
sdpMLineIndex : nil ,
candidate : " " ) ) ) ,
2023-03-02 16:17:01 +03:00
command : nil )
)
}
}
}
}
}
}
extension WebRTCClient {
func setAudioEnabled ( _ enabled : Bool ) {
setTrackEnabled ( RTCAudioTrack . self , enabled )
}
func setSpeakerEnabledAndConfigureSession ( _ enabled : Bool ) {
2023-03-15 13:21:21 +03:00
logger . debug ( " WebRTCClient: configuring session with speaker enabled \( enabled ) " )
2023-03-02 16:17:01 +03:00
audioQueue . async { [ weak self ] in
guard let self = self else { return }
self . rtcAudioSession . lockForConfiguration ( )
defer {
self . rtcAudioSession . unlockForConfiguration ( )
}
do {
try self . rtcAudioSession . setCategory ( AVAudioSession . Category . playAndRecord . rawValue )
try self . rtcAudioSession . setMode ( AVAudioSession . Mode . voiceChat . rawValue )
try self . rtcAudioSession . overrideOutputAudioPort ( enabled ? . speaker : . none )
try self . rtcAudioSession . setActive ( true )
2023-03-15 13:21:21 +03:00
logger . debug ( " WebRTCClient: configuring session with speaker enabled \( enabled ) success " )
2023-03-02 16:17:01 +03:00
} catch let error {
logger . debug ( " Error configuring AVAudioSession: \( error ) " )
}
}
}
func audioSessionToDefaults ( ) {
2023-03-15 13:21:21 +03:00
logger . debug ( " WebRTCClient: audioSession to defaults " )
2023-03-02 16:17:01 +03:00
audioQueue . async { [ weak self ] in
guard let self = self else { return }
self . rtcAudioSession . lockForConfiguration ( )
defer {
self . rtcAudioSession . unlockForConfiguration ( )
}
do {
try self . rtcAudioSession . setCategory ( AVAudioSession . Category . ambient . rawValue )
try self . rtcAudioSession . setMode ( AVAudioSession . Mode . default . rawValue )
try self . rtcAudioSession . overrideOutputAudioPort ( . none )
try self . rtcAudioSession . setActive ( false )
2023-03-15 13:21:21 +03:00
logger . debug ( " WebRTCClient: audioSession to defaults success " )
2023-03-02 16:17:01 +03:00
} catch let error {
2023-03-14 11:12:40 +03:00
logger . debug ( " Error configuring AVAudioSession with defaults: \( error ) " )
2023-03-02 16:17:01 +03:00
}
}
}
func setVideoEnabled ( _ enabled : Bool ) {
setTrackEnabled ( RTCVideoTrack . self , enabled )
}
func flipCamera ( ) {
switch activeCall . wrappedValue ? . device {
case . front : activeCall . wrappedValue ? . device = . back
case . back : activeCall . wrappedValue ? . device = . front
default : ( )
}
if let call = activeCall . wrappedValue {
startCaptureLocalVideo ( call )
}
}
private func setTrackEnabled < T : RTCMediaStreamTrack > ( _ type : T . Type , _ enabled : Bool ) {
activeCall . wrappedValue ? . connection . transceivers
. compactMap { $0 . sender . track as ? T }
. forEach { $0 . isEnabled = enabled }
}
}
struct CustomRTCSessionDescription : Codable {
public var type : RTCSdpType ?
public var sdp : String ?
}
enum RTCSdpType : String , Codable {
case answer
case offer
case pranswer
case rollback
}
extension RTCIceCandidate {
func toWebRTCCandidate ( ) -> WebRTC . RTCIceCandidate {
WebRTC . RTCIceCandidate (
sdp : candidate ,
sdpMLineIndex : Int32 ( sdpMLineIndex ? ? 0 ) ,
sdpMid : sdpMid
)
}
}
extension WebRTC . RTCIceCandidate {
2023-11-28 06:20:51 +08:00
func toCandidate ( _ candidateType : RTCIceCandidateType ? , _ protocol : String ? ) -> RTCIceCandidate {
2023-03-02 16:17:01 +03:00
RTCIceCandidate (
candidateType : candidateType ,
protocol : ` protocol ` ,
sdpMid : sdpMid ,
sdpMLineIndex : Int ( sdpMLineIndex ) ,
candidate : sdp
)
}
}
extension [ RTCIceServer ] {
func toWebRTCIceServers ( ) -> [ WebRTC . RTCIceServer ] {
self . map {
WebRTC . RTCIceServer (
urlStrings : $0 . urls ,
username : $0 . username ,
credential : $0 . credential
) }
}
}
extension RTCSdpType {
func toWebRTCSdpType ( ) -> WebRTC . RTCSdpType {
switch self {
case . answer : return WebRTC . RTCSdpType . answer
case . offer : return WebRTC . RTCSdpType . offer
case . pranswer : return WebRTC . RTCSdpType . prAnswer
case . rollback : return WebRTC . RTCSdpType . rollback
}
}
}
extension WebRTC . RTCSdpType {
func toSdpType ( ) -> RTCSdpType {
switch self {
case . answer : return RTCSdpType . answer
case . offer : return RTCSdpType . offer
case . prAnswer : return RTCSdpType . pranswer
case . rollback : return RTCSdpType . rollback
default : return RTCSdpType . answer // s h o u l d n e v e r b e h e r e
}
}
}
extension RTCPeerConnectionState {
func toString ( ) -> String ? {
switch self {
case . new : return " new "
case . connecting : return " connecting "
case . connected : return " connected "
case . failed : return " failed "
case . disconnected : return " disconnected "
case . closed : return " closed "
default : return nil // u n k n o w n
}
}
}
extension RTCIceConnectionState {
func toString ( ) -> 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 "
default : return nil // u n k n o w n o r u n u s e d o n t h e o t h e r s i d e
}
}
}
extension RTCIceGatheringState {
func toString ( ) -> String ? {
switch self {
case . new : return " new "
case . gathering : return " gathering "
case . complete : return " complete "
default : return nil // u n k n o w n
}
}
}
extension RTCSignalingState {
func toString ( ) -> String ? {
switch self {
case . stable : return " stable "
case . haveLocalOffer : return " have-local-offer "
case . haveLocalPrAnswer : return " have-local-pranswer "
case . haveRemoteOffer : return " have-remote-offer "
case . haveRemotePrAnswer : return " have-remote-pranswer "
case . closed : return " closed "
default : return nil // u n k n o w n
}
}
}