diff --git a/TelnyxRTC.xcodeproj/project.pbxproj b/TelnyxRTC.xcodeproj/project.pbxproj index ab332ba4..11784757 100644 --- a/TelnyxRTC.xcodeproj/project.pbxproj +++ b/TelnyxRTC.xcodeproj/project.pbxproj @@ -8,6 +8,7 @@ /* Begin PBXBuildFile section */ 1B0B82F8E58A096FBA0FB9CC /* (null) in Frameworks */ = {isa = PBXBuildFile; }; + 1CC3E964C59C4D2044ADA7E6 /* Pods_TelnyxRTC.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1C63EBFB1E9D4F3E073EA2C0 /* Pods_TelnyxRTC.framework */; }; 3B0F56CB2C7F15830011A48A /* StatsMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B0F56CA2C7F15830011A48A /* StatsMessage.swift */; }; 3B1BE6F72AA9A467000B7962 /* TxPushIPConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B1BE6F62AA9A467000B7962 /* TxPushIPConfig.swift */; }; 3B1F43EF2AE0B01E00A610BA /* Params.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B1F43EE2AE0B01E00A610BA /* Params.swift */; }; @@ -19,14 +20,37 @@ 3BC03B4E2CC25F5F00FD2B29 /* Reachability.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B0F56CC2C7F1CCA0011A48A /* Reachability.framework */; }; 3BC03B572CC261F500FD2B29 /* Bugsnag.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3BC03B562CC261F500FD2B29 /* Bugsnag.framework */; }; 3BC03B592CC2653700FD2B29 /* TelnyxRTC.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B368BEC025EDDB610032AE52 /* TelnyxRTC.framework */; }; - 3BC03B5E2CC2659500FD2B29 /* Pods_TelnyxRTC.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8A11DAA0171B02D67683620E /* Pods_TelnyxRTC.framework */; }; 3BF1D5842BEB7B8F0097453F /* TelnyxRTC.podspec in Resources */ = {isa = PBXBuildFile; fileRef = 3BF1D5832BEB7B8F0097453F /* TelnyxRTC.podspec */; }; + 4693A831F1CCCA0D5D5B8CA6 /* Pods_TelnyxWebRTCDemo.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8AB796C314FCBADB322CF012 /* Pods_TelnyxWebRTCDemo.framework */; }; + 5AE35712706D8C76E4140B0F /* Pods_TelnyxRTC_TelnyxRTCTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 24EBBCA1A06AC881C03BACF9 /* Pods_TelnyxRTC_TelnyxRTCTests.framework */; }; + 9911247E2CF50092000C23BA /* Dictionary+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9911247D2CF50088000C23BA /* Dictionary+Extensions.swift */; }; 995BF7132CE7E8F100454076 /* WebRTCEnvironmentExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 995BF7122CE7E8EB00454076 /* WebRTCEnvironmentExtension.swift */; }; 995BF7162CE7EB2600454076 /* SipUserCredential.swift in Sources */ = {isa = PBXBuildFile; fileRef = 995BF7152CE7EB2200454076 /* SipUserCredential.swift */; }; 995BF71C2CE7EC0600454076 /* SipCredentialsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 995BF71A2CE7EBF800454076 /* SipCredentialsManager.swift */; }; 995BF7202CE8175B00454076 /* SipCredentialsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 995BF71F2CE8175200454076 /* SipCredentialsViewController.swift */; }; 995BF7222CE818A400454076 /* SipCredentialsViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 995BF7212CE818A400454076 /* SipCredentialsViewController.xib */; }; 995BF7242CE8474A00454076 /* UISipCredentialHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 995BF7232CE8471B00454076 /* UISipCredentialHeaderView.swift */; }; + 996C67BC2CFEA4880056E508 /* RTCIceServer+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 996C67BB2CFEA4790056E508 /* RTCIceServer+Extension.swift */; }; + 996C67BE2CFEA4D70056E508 /* RTClsCertPolicy+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 996C67BD2CFEA4C80056E508 /* RTClsCertPolicy+Extension.swift */; }; + 996C67C02CFEA5600056E508 /* RTCBundlePolicy+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 996C67BF2CFEA55B0056E508 /* RTCBundlePolicy+Extension.swift */; }; + 996C67C22CFEA5870056E508 /* RTCSdpSemantics+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 996C67C12CFEA5830056E508 /* RTCSdpSemantics+Extension.swift */; }; + 996C67C42CFEA5AC0056E508 /* RTCContinualGatheringPolicy+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 996C67C32CFEA5A70056E508 /* RTCContinualGatheringPolicy+Extension.swift */; }; + 996C67C82CFEA6530056E508 /* RTCRtcpMuxPolicy+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 996C67C72CFEA6520056E508 /* RTCRtcpMuxPolicy+Extension.swift */; }; + 996C67CA2CFEA6740056E508 /* RTCIceTransportPolicy+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 996C67C92CFEA66C0056E508 /* RTCIceTransportPolicy+Extension.swift */; }; + 996C67CC2CFEA6910056E508 /* RTCSignalingState+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 996C67CB2CFEA68E0056E508 /* RTCSignalingState+Extension.swift */; }; + 996C67CE2CFEA6AF0056E508 /* RTCIceConnectionState+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 996C67CD2CFEA6A70056E508 /* RTCIceConnectionState+Extension.swift */; }; + 996C67D02CFEA6F30056E508 /* RTCIceGatheringState+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 996C67CF2CFEA6EF0056E508 /* RTCIceGatheringState+Extension.swift */; }; + 996C67D22CFEA70F0056E508 /* RTCMediaStream+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 996C67D12CFEA70A0056E508 /* RTCMediaStream+Extension.swift */; }; + 996C67D42CFEA72C0056E508 /* RTCMediaStreamTrack+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 996C67D32CFEA7270056E508 /* RTCMediaStreamTrack+Extension.swift */; }; + 996C67D72CFEA96B0056E508 /* RTCIceCandidate+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 996C67D62CFEA9670056E508 /* RTCIceCandidate+Extension.swift */; }; + 996C67D92CFEB7E10056E508 /* RTCConfiguration+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 996C67D82CFEB7D30056E508 /* RTCConfiguration+Extension.swift */; }; + 99B6DFF82CF522140010CA96 /* WebRTCStatsEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99B6DFF72CF522070010CA96 /* WebRTCStatsEvent.swift */; }; + 99B6DFFA2CF5226F0010CA96 /* WebRTCStatsTag.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99B6DFF92CF522610010CA96 /* WebRTCStatsTag.swift */; }; + 99B6DFFD2CF546310010CA96 /* DebugReportStartMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99B6DFFC2CF546220010CA96 /* DebugReportStartMessage.swift */; }; + 99B6DFFF2CF547470010CA96 /* DebugReportStopMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99B6DFFE2CF547470010CA96 /* DebugReportStopMessage.swift */; }; + 99B6E0012CF547680010CA96 /* DebugReportDataMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99B6E0002CF547680010CA96 /* DebugReportDataMessage.swift */; }; + 99CC5BAC2CF804A500EF43DC /* WebRTCStatsReporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99CC5BAB2CF804A200EF43DC /* WebRTCStatsReporter.swift */; }; + 99CC5BAE2CF80D3A00EF43DC /* WebRTCEventHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99CC5BAD2CF80D3400EF43DC /* WebRTCEventHandler.swift */; }; B309D13D25EF119E00A2AADF /* Starscream.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B309D11125EF107F00A2AADF /* Starscream.framework */; }; B309D1D625F020B300A2AADF /* Message.swift in Sources */ = {isa = PBXBuildFile; fileRef = B309D1D525F020B300A2AADF /* Message.swift */; }; B309D1DB25F020D400A2AADF /* Method.swift in Sources */ = {isa = PBXBuildFile; fileRef = B309D1DA25F020D400A2AADF /* Method.swift */; }; @@ -84,7 +108,6 @@ B3E1029A25F2C16500227DCE /* ModifyMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = B3E1029925F2C16500227DCE /* ModifyMessage.swift */; }; B3E1033225F7F94900227DCE /* incoming_call.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = B3E1033025F7F94900227DCE /* incoming_call.mp3 */; }; B3E1033325F7F94900227DCE /* ringback_tone.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = B3E1033125F7F94900227DCE /* ringback_tone.mp3 */; }; - F1213C92A3E9EC2593B0F1F2 /* Pods_TelnyxRTC_TelnyxRTCTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 65B0B19B5206EECCAE2E29CA /* Pods_TelnyxRTC_TelnyxRTCTests.framework */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -119,34 +142,54 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ - 00B066EA7AECA1E61F1CC13D /* Pods-TelnyxRTC-TelnyxRTCTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-TelnyxRTC-TelnyxRTCTests.debug.xcconfig"; path = "Target Support Files/Pods-TelnyxRTC-TelnyxRTCTests/Pods-TelnyxRTC-TelnyxRTCTests.debug.xcconfig"; sourceTree = ""; }; - 064620D5372C98975322D84C /* Pods-TelnyxRTC.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-TelnyxRTC.release.xcconfig"; path = "Target Support Files/Pods-TelnyxRTC/Pods-TelnyxRTC.release.xcconfig"; sourceTree = ""; }; - 0BC0EF15A80C9BA767873075 /* Pods-TelnyxWebRTCDemo.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-TelnyxWebRTCDemo.debug.xcconfig"; path = "Target Support Files/Pods-TelnyxWebRTCDemo/Pods-TelnyxWebRTCDemo.debug.xcconfig"; sourceTree = ""; }; + 0EA7D806EFC85429822C55AB /* Pods-TelnyxWebRTCDemo.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-TelnyxWebRTCDemo.debug.xcconfig"; path = "Target Support Files/Pods-TelnyxWebRTCDemo/Pods-TelnyxWebRTCDemo.debug.xcconfig"; sourceTree = ""; }; + 11DB698F63D169FDB0B67522 /* Pods-TelnyxRTC-TelnyxRTCTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-TelnyxRTC-TelnyxRTCTests.release.xcconfig"; path = "Target Support Files/Pods-TelnyxRTC-TelnyxRTCTests/Pods-TelnyxRTC-TelnyxRTCTests.release.xcconfig"; sourceTree = ""; }; + 166A37B0BC920E949D232AF6 /* Pods-TelnyxRTC.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-TelnyxRTC.debug.xcconfig"; path = "Target Support Files/Pods-TelnyxRTC/Pods-TelnyxRTC.debug.xcconfig"; sourceTree = ""; }; + 1C63EBFB1E9D4F3E073EA2C0 /* Pods_TelnyxRTC.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_TelnyxRTC.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 24EBBCA1A06AC881C03BACF9 /* Pods_TelnyxRTC_TelnyxRTCTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_TelnyxRTC_TelnyxRTCTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 2C3CA87490B0D027798FEF54 /* Pods-TelnyxRTC.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-TelnyxRTC.release.xcconfig"; path = "Target Support Files/Pods-TelnyxRTC/Pods-TelnyxRTC.release.xcconfig"; sourceTree = ""; }; 3B0F56CA2C7F15830011A48A /* StatsMessage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatsMessage.swift; sourceTree = ""; }; 3B0F56CC2C7F1CCA0011A48A /* Reachability.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Reachability.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 3B1BE6F62AA9A467000B7962 /* TxPushIPConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TxPushIPConfig.swift; sourceTree = ""; }; 3B1F43EE2AE0B01E00A610BA /* Params.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Params.swift; sourceTree = ""; }; - 3B304DB22CAD823600459A97 /* Pods_TelnyxRTC.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Pods_TelnyxRTC.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 3B49B7142AA9B0A20026D36D /* AttachCallMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttachCallMessage.swift; sourceTree = ""; }; 3B72695C2A9396BF00D2A602 /* DisablePushMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DisablePushMessage.swift; sourceTree = ""; }; 3B758F192BF44D7800D50069 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = ""; }; 3B758F1D2BF97E8C00D50069 /* FileLoger.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileLoger.swift; sourceTree = ""; }; 3B91C0F42BE3A44600A03067 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = ""; }; - 3BC03B462CC2544800FD2B29 /* Pods_TelnyxRTC.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Pods_TelnyxRTC.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 3BC03B562CC261F500FD2B29 /* Bugsnag.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Bugsnag.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 3BF1D5832BEB7B8F0097453F /* TelnyxRTC.podspec */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = TelnyxRTC.podspec; sourceTree = ""; }; - 3F6CE2F8F9D0DBE4DA219605 /* Pods-TelnyxRTC-TelnyxRTCTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-TelnyxRTC-TelnyxRTCTests.release.xcconfig"; path = "Target Support Files/Pods-TelnyxRTC-TelnyxRTCTests/Pods-TelnyxRTC-TelnyxRTCTests.release.xcconfig"; sourceTree = ""; }; - 4DE56344987C90A16F102652 /* Pods-TelnyxWebRTCDemo.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-TelnyxWebRTCDemo.release.xcconfig"; path = "Target Support Files/Pods-TelnyxWebRTCDemo/Pods-TelnyxWebRTCDemo.release.xcconfig"; sourceTree = ""; }; - 65B0B19B5206EECCAE2E29CA /* Pods_TelnyxRTC_TelnyxRTCTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_TelnyxRTC_TelnyxRTCTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 711958501DD45FDFBD112FC6 /* Pods-TelnyxRTC.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-TelnyxRTC.debug.xcconfig"; path = "Target Support Files/Pods-TelnyxRTC/Pods-TelnyxRTC.debug.xcconfig"; sourceTree = ""; }; - 8A11DAA0171B02D67683620E /* Pods_TelnyxRTC.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_TelnyxRTC.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 4F3296FA2F329EA893F0F24F /* Pods-TelnyxWebRTCDemo.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-TelnyxWebRTCDemo.release.xcconfig"; path = "Target Support Files/Pods-TelnyxWebRTCDemo/Pods-TelnyxWebRTCDemo.release.xcconfig"; sourceTree = ""; }; + 8AB796C314FCBADB322CF012 /* Pods_TelnyxWebRTCDemo.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_TelnyxWebRTCDemo.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 8E263BC959E36B52361D0CB4 /* Pods-TelnyxRTC-TelnyxRTCTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-TelnyxRTC-TelnyxRTCTests.debug.xcconfig"; path = "Target Support Files/Pods-TelnyxRTC-TelnyxRTCTests/Pods-TelnyxRTC-TelnyxRTCTests.debug.xcconfig"; sourceTree = ""; }; + 9911247D2CF50088000C23BA /* Dictionary+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Dictionary+Extensions.swift"; sourceTree = ""; }; 995BF7122CE7E8EB00454076 /* WebRTCEnvironmentExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebRTCEnvironmentExtension.swift; sourceTree = ""; }; 995BF7152CE7EB2200454076 /* SipUserCredential.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SipUserCredential.swift; sourceTree = ""; }; 995BF71A2CE7EBF800454076 /* SipCredentialsManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SipCredentialsManager.swift; sourceTree = ""; }; 995BF71F2CE8175200454076 /* SipCredentialsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SipCredentialsViewController.swift; sourceTree = ""; }; 995BF7212CE818A400454076 /* SipCredentialsViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = SipCredentialsViewController.xib; sourceTree = ""; }; 995BF7232CE8471B00454076 /* UISipCredentialHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UISipCredentialHeaderView.swift; sourceTree = ""; }; - A7CE9BEFEDE503E7BC7BE3C8 /* Pods_TelnyxWebRTCDemo.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_TelnyxWebRTCDemo.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 996C67BB2CFEA4790056E508 /* RTCIceServer+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RTCIceServer+Extension.swift"; sourceTree = ""; }; + 996C67BD2CFEA4C80056E508 /* RTClsCertPolicy+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RTClsCertPolicy+Extension.swift"; sourceTree = ""; }; + 996C67BF2CFEA55B0056E508 /* RTCBundlePolicy+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RTCBundlePolicy+Extension.swift"; sourceTree = ""; }; + 996C67C12CFEA5830056E508 /* RTCSdpSemantics+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RTCSdpSemantics+Extension.swift"; sourceTree = ""; }; + 996C67C32CFEA5A70056E508 /* RTCContinualGatheringPolicy+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RTCContinualGatheringPolicy+Extension.swift"; sourceTree = ""; }; + 996C67C72CFEA6520056E508 /* RTCRtcpMuxPolicy+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RTCRtcpMuxPolicy+Extension.swift"; sourceTree = ""; }; + 996C67C92CFEA66C0056E508 /* RTCIceTransportPolicy+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RTCIceTransportPolicy+Extension.swift"; sourceTree = ""; }; + 996C67CB2CFEA68E0056E508 /* RTCSignalingState+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RTCSignalingState+Extension.swift"; sourceTree = ""; }; + 996C67CD2CFEA6A70056E508 /* RTCIceConnectionState+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RTCIceConnectionState+Extension.swift"; sourceTree = ""; }; + 996C67CF2CFEA6EF0056E508 /* RTCIceGatheringState+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RTCIceGatheringState+Extension.swift"; sourceTree = ""; }; + 996C67D12CFEA70A0056E508 /* RTCMediaStream+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RTCMediaStream+Extension.swift"; sourceTree = ""; }; + 996C67D32CFEA7270056E508 /* RTCMediaStreamTrack+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RTCMediaStreamTrack+Extension.swift"; sourceTree = ""; }; + 996C67D62CFEA9670056E508 /* RTCIceCandidate+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RTCIceCandidate+Extension.swift"; sourceTree = ""; }; + 996C67D82CFEB7D30056E508 /* RTCConfiguration+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RTCConfiguration+Extension.swift"; sourceTree = ""; }; + 99B6DFF72CF522070010CA96 /* WebRTCStatsEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebRTCStatsEvent.swift; sourceTree = ""; }; + 99B6DFF92CF522610010CA96 /* WebRTCStatsTag.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebRTCStatsTag.swift; sourceTree = ""; }; + 99B6DFFC2CF546220010CA96 /* DebugReportStartMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DebugReportStartMessage.swift; sourceTree = ""; }; + 99B6DFFE2CF547470010CA96 /* DebugReportStopMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DebugReportStopMessage.swift; sourceTree = ""; }; + 99B6E0002CF547680010CA96 /* DebugReportDataMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DebugReportDataMessage.swift; sourceTree = ""; }; + 99CC5BAB2CF804A200EF43DC /* WebRTCStatsReporter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebRTCStatsReporter.swift; sourceTree = ""; }; + 99CC5BAD2CF80D3400EF43DC /* WebRTCEventHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebRTCEventHandler.swift; sourceTree = ""; }; B309D11125EF107F00A2AADF /* Starscream.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Starscream.framework; sourceTree = BUILT_PRODUCTS_DIR; }; B309D1D525F020B300A2AADF /* Message.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Message.swift; sourceTree = ""; }; B309D1DA25F020D400A2AADF /* Method.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Method.swift; sourceTree = ""; }; @@ -217,8 +260,8 @@ files = ( 3BC03B572CC261F500FD2B29 /* Bugsnag.framework in Frameworks */, B309D13D25EF119E00A2AADF /* Starscream.framework in Frameworks */, - 3BC03B5E2CC2659500FD2B29 /* Pods_TelnyxRTC.framework in Frameworks */, B309D26225F1574C00A2AADF /* (null) in Frameworks */, + 1CC3E964C59C4D2044ADA7E6 /* Pods_TelnyxRTC.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -227,7 +270,7 @@ buildActionMask = 2147483647; files = ( B368BED625EDDBC90032AE52 /* TelnyxRTC.framework in Frameworks */, - F1213C92A3E9EC2593B0F1F2 /* Pods_TelnyxRTC_TelnyxRTCTests.framework in Frameworks */, + 5AE35712706D8C76E4140B0F /* Pods_TelnyxRTC_TelnyxRTCTests.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -238,6 +281,7 @@ 3BC03B592CC2653700FD2B29 /* TelnyxRTC.framework in Frameworks */, 1B0B82F8E58A096FBA0FB9CC /* (null) in Frameworks */, 3BC03B4E2CC25F5F00FD2B29 /* Reachability.framework in Frameworks */, + 4693A831F1CCCA0D5D5B8CA6 /* Pods_TelnyxWebRTCDemo.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -247,16 +291,35 @@ 8AB3A6A2ADA6A9FAC6C77491 /* Pods */ = { isa = PBXGroup; children = ( - 711958501DD45FDFBD112FC6 /* Pods-TelnyxRTC.debug.xcconfig */, - 064620D5372C98975322D84C /* Pods-TelnyxRTC.release.xcconfig */, - 00B066EA7AECA1E61F1CC13D /* Pods-TelnyxRTC-TelnyxRTCTests.debug.xcconfig */, - 3F6CE2F8F9D0DBE4DA219605 /* Pods-TelnyxRTC-TelnyxRTCTests.release.xcconfig */, - 0BC0EF15A80C9BA767873075 /* Pods-TelnyxWebRTCDemo.debug.xcconfig */, - 4DE56344987C90A16F102652 /* Pods-TelnyxWebRTCDemo.release.xcconfig */, + 166A37B0BC920E949D232AF6 /* Pods-TelnyxRTC.debug.xcconfig */, + 2C3CA87490B0D027798FEF54 /* Pods-TelnyxRTC.release.xcconfig */, + 8E263BC959E36B52361D0CB4 /* Pods-TelnyxRTC-TelnyxRTCTests.debug.xcconfig */, + 11DB698F63D169FDB0B67522 /* Pods-TelnyxRTC-TelnyxRTCTests.release.xcconfig */, + 0EA7D806EFC85429822C55AB /* Pods-TelnyxWebRTCDemo.debug.xcconfig */, + 4F3296FA2F329EA893F0F24F /* Pods-TelnyxWebRTCDemo.release.xcconfig */, ); path = Pods; sourceTree = ""; }; + 991124752CF4FE95000C23BA /* Stats */ = { + isa = PBXGroup; + children = ( + 99CC5BAB2CF804A200EF43DC /* WebRTCStatsReporter.swift */, + 99B6DFF92CF522610010CA96 /* WebRTCStatsTag.swift */, + 99B6DFF72CF522070010CA96 /* WebRTCStatsEvent.swift */, + 99CC5BAD2CF80D3400EF43DC /* WebRTCEventHandler.swift */, + ); + path = Stats; + sourceTree = ""; + }; + 9911247C2CF5007F000C23BA /* Extensions */ = { + isa = PBXGroup; + children = ( + 9911247D2CF50088000C23BA /* Dictionary+Extensions.swift */, + ); + path = Extensions; + sourceTree = ""; + }; 995BF7142CE7EB0B00454076 /* Models */ = { isa = PBXGroup; children = ( @@ -282,10 +345,42 @@ path = ViewControllers; sourceTree = ""; }; - B309D1D425F020A300A2AADF /* Verto */ = { + 996C67D52CFEA7430056E508 /* Extensions */ = { + isa = PBXGroup; + children = ( + 996C67D82CFEB7D30056E508 /* RTCConfiguration+Extension.swift */, + 996C67D62CFEA9670056E508 /* RTCIceCandidate+Extension.swift */, + 996C67D32CFEA7270056E508 /* RTCMediaStreamTrack+Extension.swift */, + 996C67D12CFEA70A0056E508 /* RTCMediaStream+Extension.swift */, + 996C67CF2CFEA6EF0056E508 /* RTCIceGatheringState+Extension.swift */, + 996C67CD2CFEA6A70056E508 /* RTCIceConnectionState+Extension.swift */, + 996C67CB2CFEA68E0056E508 /* RTCSignalingState+Extension.swift */, + 996C67C92CFEA66C0056E508 /* RTCIceTransportPolicy+Extension.swift */, + 996C67C72CFEA6520056E508 /* RTCRtcpMuxPolicy+Extension.swift */, + 996C67C32CFEA5A70056E508 /* RTCContinualGatheringPolicy+Extension.swift */, + 996C67C12CFEA5830056E508 /* RTCSdpSemantics+Extension.swift */, + 996C67BF2CFEA55B0056E508 /* RTCBundlePolicy+Extension.swift */, + 996C67BD2CFEA4C80056E508 /* RTClsCertPolicy+Extension.swift */, + 996C67BB2CFEA4790056E508 /* RTCIceServer+Extension.swift */, + ); + path = Extensions; + sourceTree = ""; + }; + 99B6DFFB2CF542E20010CA96 /* Stats */ = { isa = PBXGroup; children = ( 3B0F56CA2C7F15830011A48A /* StatsMessage.swift */, + 99B6DFFC2CF546220010CA96 /* DebugReportStartMessage.swift */, + 99B6DFFE2CF547470010CA96 /* DebugReportStopMessage.swift */, + 99B6E0002CF547680010CA96 /* DebugReportDataMessage.swift */, + ); + path = Stats; + sourceTree = ""; + }; + B309D1D425F020A300A2AADF /* Verto */ = { + isa = PBXGroup; + children = ( + 99B6DFFB2CF542E20010CA96 /* Stats */, B309D1DA25F020D400A2AADF /* Method.swift */, B309D1D525F020B300A2AADF /* Message.swift */, B309D1EB25F024B900A2AADF /* LoginMessage.swift */, @@ -334,6 +429,8 @@ B309D22725F06C5F00A2AADF /* WebRTC */ = { isa = PBXGroup; children = ( + 996C67D52CFEA7430056E508 /* Extensions */, + 991124752CF4FE95000C23BA /* Stats */, B309D22825F06C6900A2AADF /* Call.swift */, B309D23025F06CA800A2AADF /* Peer.swift */, ); @@ -423,6 +520,7 @@ B368BF5225EE71A20032AE52 /* Telnyx */ = { isa = PBXGroup; children = ( + 9911247C2CF5007F000C23BA /* Extensions */, B36FC8BE2612791E00A30BC4 /* Utils */, B309D22725F06C5F00A2AADF /* WebRTC */, B309D1E225F023F400A2AADF /* Models */, @@ -504,13 +602,11 @@ isa = PBXGroup; children = ( 3BC03B562CC261F500FD2B29 /* Bugsnag.framework */, - 3BC03B462CC2544800FD2B29 /* Pods_TelnyxRTC.framework */, - 3B304DB22CAD823600459A97 /* Pods_TelnyxRTC.framework */, 3B0F56CC2C7F1CCA0011A48A /* Reachability.framework */, B309D11125EF107F00A2AADF /* Starscream.framework */, - 8A11DAA0171B02D67683620E /* Pods_TelnyxRTC.framework */, - 65B0B19B5206EECCAE2E29CA /* Pods_TelnyxRTC_TelnyxRTCTests.framework */, - A7CE9BEFEDE503E7BC7BE3C8 /* Pods_TelnyxWebRTCDemo.framework */, + 1C63EBFB1E9D4F3E073EA2C0 /* Pods_TelnyxRTC.framework */, + 24EBBCA1A06AC881C03BACF9 /* Pods_TelnyxRTC_TelnyxRTCTests.framework */, + 8AB796C314FCBADB322CF012 /* Pods_TelnyxWebRTCDemo.framework */, ); name = Frameworks; sourceTree = ""; @@ -533,7 +629,7 @@ isa = PBXNativeTarget; buildConfigurationList = B368BEC825EDDB610032AE52 /* Build configuration list for PBXNativeTarget "TelnyxRTC" */; buildPhases = ( - C8DCE6A4E57624543606AF26 /* [CP] Check Pods Manifest.lock */, + 3C8140559002FC9F46F2C055 /* [CP] Check Pods Manifest.lock */, B368BEBB25EDDB610032AE52 /* Headers */, B368BEBC25EDDB610032AE52 /* Sources */, B368BEBD25EDDB610032AE52 /* Frameworks */, @@ -553,11 +649,11 @@ isa = PBXNativeTarget; buildConfigurationList = B368BEDB25EDDBC90032AE52 /* Build configuration list for PBXNativeTarget "TelnyxRTCTests" */; buildPhases = ( - 372CB862CEB12F0BAD6BC8EF /* [CP] Check Pods Manifest.lock */, + E4F6C0092347FDDCCC4BFD51 /* [CP] Check Pods Manifest.lock */, B368BECD25EDDBC90032AE52 /* Sources */, B368BECE25EDDBC90032AE52 /* Frameworks */, B368BECF25EDDBC90032AE52 /* Resources */, - AE5BBE6944FFEBD4BF981BE3 /* [CP] Embed Pods Frameworks */, + 7F0F3016996D93EC8C3E754E /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -573,11 +669,11 @@ isa = PBXNativeTarget; buildConfigurationList = B368BEF925EDDD070032AE52 /* Build configuration list for PBXNativeTarget "TelnyxWebRTCDemo" */; buildPhases = ( - 531941316CE2E1E6229D4E33 /* [CP] Check Pods Manifest.lock */, + 088F360799E6499CFED6C38C /* [CP] Check Pods Manifest.lock */, B368BEE425EDDD060032AE52 /* Sources */, B368BEE525EDDD060032AE52 /* Frameworks */, B368BEE625EDDD060032AE52 /* Resources */, - 706A487C202DF580DCB3143E /* [CP] Embed Pods Frameworks */, + 1BD93DE6E3E37362FAD266A7 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -668,7 +764,7 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ - 372CB862CEB12F0BAD6BC8EF /* [CP] Check Pods Manifest.lock */ = { + 088F360799E6499CFED6C38C /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -683,53 +779,53 @@ outputFileListPaths = ( ); outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-TelnyxRTC-TelnyxRTCTests-checkManifestLockResult.txt", + "$(DERIVED_FILE_DIR)/Pods-TelnyxWebRTCDemo-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - 531941316CE2E1E6229D4E33 /* [CP] Check Pods Manifest.lock */ = { + 1BD93DE6E3E37362FAD266A7 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-TelnyxWebRTCDemo/Pods-TelnyxWebRTCDemo-frameworks-${CONFIGURATION}-input-files.xcfilelist", ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; + name = "[CP] Embed Pods Frameworks"; outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-TelnyxWebRTCDemo-checkManifestLockResult.txt", + "${PODS_ROOT}/Target Support Files/Pods-TelnyxWebRTCDemo/Pods-TelnyxWebRTCDemo-frameworks-${CONFIGURATION}-output-files.xcfilelist", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-TelnyxWebRTCDemo/Pods-TelnyxWebRTCDemo-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; - 706A487C202DF580DCB3143E /* [CP] Embed Pods Frameworks */ = { + 3C8140559002FC9F46F2C055 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-TelnyxWebRTCDemo/Pods-TelnyxWebRTCDemo-frameworks-${CONFIGURATION}-input-files.xcfilelist", ); - name = "[CP] Embed Pods Frameworks"; + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-TelnyxWebRTCDemo/Pods-TelnyxWebRTCDemo-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-TelnyxRTC-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-TelnyxWebRTCDemo/Pods-TelnyxWebRTCDemo-frameworks.sh\"\n"; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - AE5BBE6944FFEBD4BF981BE3 /* [CP] Embed Pods Frameworks */ = { + 7F0F3016996D93EC8C3E754E /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -746,7 +842,7 @@ shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-TelnyxRTC-TelnyxRTCTests/Pods-TelnyxRTC-TelnyxRTCTests-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; - C8DCE6A4E57624543606AF26 /* [CP] Check Pods Manifest.lock */ = { + E4F6C0092347FDDCCC4BFD51 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -761,7 +857,7 @@ outputFileListPaths = ( ); outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-TelnyxRTC-checkManifestLockResult.txt", + "$(DERIVED_FILE_DIR)/Pods-TelnyxRTC-TelnyxRTCTests-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; @@ -776,34 +872,56 @@ buildActionMask = 2147483647; files = ( 3B0F56CB2C7F15830011A48A /* StatsMessage.swift in Sources */, + 99B6DFFF2CF547470010CA96 /* DebugReportStopMessage.swift in Sources */, 3B49B7152AA9B0A20026D36D /* AttachCallMessage.swift in Sources */, B36FC8C02612794A00A30BC4 /* Logger.swift in Sources */, B3AF248B25EE7C350062EDA9 /* Socket.swift in Sources */, 3B758F1E2BF97E8C00D50069 /* FileLoger.swift in Sources */, + 996C67D42CFEA72C0056E508 /* RTCMediaStreamTrack+Extension.swift in Sources */, + 996C67CE2CFEA6AF0056E508 /* RTCIceConnectionState+Extension.swift in Sources */, B3BB8FA026BC076000DDCB84 /* GatewayMessage.swift in Sources */, B309D1DB25F020D400A2AADF /* Method.swift in Sources */, B39121AF25FFCE680051E076 /* TxError.swift in Sources */, + 99B6E0012CF547680010CA96 /* DebugReportDataMessage.swift in Sources */, + 99B6DFF82CF522140010CA96 /* WebRTCStatsEvent.swift in Sources */, B32AE8B326CD4F9200C7C6F4 /* TxServerConfiguration.swift in Sources */, B368BEE125EDDC7D0032AE52 /* TxClient.swift in Sources */, B309D28625F184A100A2AADF /* AnswerMessage.swift in Sources */, 3B1BE6F72AA9A467000B7962 /* TxPushIPConfig.swift in Sources */, + 996C67C22CFEA5870056E508 /* RTCSdpSemantics+Extension.swift in Sources */, B309D23125F06CA800A2AADF /* Peer.swift in Sources */, + 996C67CA2CFEA6740056E508 /* RTCIceTransportPolicy+Extension.swift in Sources */, + 996C67D22CFEA70F0056E508 /* RTCMediaStream+Extension.swift in Sources */, + 996C67D72CFEA96B0056E508 /* RTCIceCandidate+Extension.swift in Sources */, + 996C67C02CFEA5600056E508 /* RTCBundlePolicy+Extension.swift in Sources */, + 996C67BE2CFEA4D70056E508 /* RTClsCertPolicy+Extension.swift in Sources */, B309D1E425F0240200A2AADF /* TxConfig.swift in Sources */, B3AF248025EE7B1F0062EDA9 /* TxClientDelegate.swift in Sources */, + 996C67CC2CFEA6910056E508 /* RTCSignalingState+Extension.swift in Sources */, B309D28225F1838700A2AADF /* ByeMessage.swift in Sources */, B309D1D625F020B300A2AADF /* Message.swift in Sources */, B3B1D9A126542860008D28C9 /* TxPushConfig.swift in Sources */, + 996C67C82CFEA6530056E508 /* RTCRtcpMuxPolicy+Extension.swift in Sources */, B309D23625F06D2100A2AADF /* TxCallInfo.swift in Sources */, B309D23B25F06DC000A2AADF /* TxCallOptions.swift in Sources */, 3B1F43EF2AE0B01E00A610BA /* Params.swift in Sources */, + 99B6DFFD2CF546310010CA96 /* DebugReportStartMessage.swift in Sources */, B3AF249825EE7DC70062EDA9 /* SocketDelegate.swift in Sources */, B3E0B0662656ED73005E7431 /* InfoMessage.swift in Sources */, B3E1029A25F2C16500227DCE /* ModifyMessage.swift in Sources */, B309D24025F06EA600A2AADF /* InviteMessage.swift in Sources */, 3B72695D2A9396BF00D2A602 /* DisablePushMessage.swift in Sources */, B309D22925F06C6900A2AADF /* Call.swift in Sources */, + 996C67C42CFEA5AC0056E508 /* RTCContinualGatheringPolicy+Extension.swift in Sources */, B309D1EC25F024B900A2AADF /* LoginMessage.swift in Sources */, + 99B6DFFA2CF5226F0010CA96 /* WebRTCStatsTag.swift in Sources */, + 99CC5BAE2CF80D3A00EF43DC /* WebRTCEventHandler.swift in Sources */, + 996C67D02CFEA6F30056E508 /* RTCIceGatheringState+Extension.swift in Sources */, + 996C67D92CFEB7E10056E508 /* RTCConfiguration+Extension.swift in Sources */, B3AF248425EE7B6A0062EDA9 /* InternalConfig.swift in Sources */, + 99CC5BAC2CF804A500EF43DC /* WebRTCStatsReporter.swift in Sources */, + 9911247E2CF50092000C23BA /* Dictionary+Extensions.swift in Sources */, + 996C67BC2CFEA4880056E508 /* RTCIceServer+Extension.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1005,7 +1123,7 @@ }; B368BEC925EDDB610032AE52 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 711958501DD45FDFBD112FC6 /* Pods-TelnyxRTC.debug.xcconfig */; + baseConfigurationReference = 166A37B0BC920E949D232AF6 /* Pods-TelnyxRTC.debug.xcconfig */; buildSettings = { CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = "Apple Development"; @@ -1044,7 +1162,7 @@ }; B368BECA25EDDB610032AE52 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 064620D5372C98975322D84C /* Pods-TelnyxRTC.release.xcconfig */; + baseConfigurationReference = 2C3CA87490B0D027798FEF54 /* Pods-TelnyxRTC.release.xcconfig */; buildSettings = { CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = "Apple Development"; @@ -1077,7 +1195,7 @@ }; B368BED925EDDBC90032AE52 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 00B066EA7AECA1E61F1CC13D /* Pods-TelnyxRTC-TelnyxRTCTests.debug.xcconfig */; + baseConfigurationReference = 8E263BC959E36B52361D0CB4 /* Pods-TelnyxRTC-TelnyxRTCTests.debug.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = "$(inherited)"; CODE_SIGN_STYLE = Automatic; @@ -1096,7 +1214,7 @@ }; B368BEDA25EDDBC90032AE52 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 3F6CE2F8F9D0DBE4DA219605 /* Pods-TelnyxRTC-TelnyxRTCTests.release.xcconfig */; + baseConfigurationReference = 11DB698F63D169FDB0B67522 /* Pods-TelnyxRTC-TelnyxRTCTests.release.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = "$(inherited)"; CODE_SIGN_STYLE = Automatic; @@ -1115,7 +1233,7 @@ }; B368BEFA25EDDD070032AE52 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 0BC0EF15A80C9BA767873075 /* Pods-TelnyxWebRTCDemo.debug.xcconfig */; + baseConfigurationReference = 0EA7D806EFC85429822C55AB /* Pods-TelnyxWebRTCDemo.debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; @@ -1140,7 +1258,7 @@ }; B368BEFB25EDDD070032AE52 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 4DE56344987C90A16F102652 /* Pods-TelnyxWebRTCDemo.release.xcconfig */; + baseConfigurationReference = 4F3296FA2F329EA893F0F24F /* Pods-TelnyxWebRTCDemo.release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; diff --git a/TelnyxRTC/Telnyx/Extensions/Dictionary+Extensions.swift b/TelnyxRTC/Telnyx/Extensions/Dictionary+Extensions.swift new file mode 100644 index 00000000..8318f9f3 --- /dev/null +++ b/TelnyxRTC/Telnyx/Extensions/Dictionary+Extensions.swift @@ -0,0 +1,17 @@ +// MARK: - Dictionary +extension Dictionary { + + var telnyx_webrtc_json: String { + let invalidJson = "Not a valid JSON" + do { + let jsonData = try JSONSerialization.data(withJSONObject: self, options: .prettyPrinted) + return String(bytes: jsonData, encoding: String.Encoding.utf8) ?? invalidJson + } catch { + return invalidJson + } + } + + func printJson() { + Logger.log.i(message: "\(telnyx_webrtc_json)") + } +} diff --git a/TelnyxRTC/Telnyx/Models/TxConfig.swift b/TelnyxRTC/Telnyx/Models/TxConfig.swift index d183718d..108bf196 100644 --- a/TelnyxRTC/Telnyx/Models/TxConfig.swift +++ b/TelnyxRTC/Telnyx/Models/TxConfig.swift @@ -27,6 +27,9 @@ public struct TxConfig { public internal(set) var ringtone: String? public internal(set) var reconnectClient: Bool = true public internal(set) var pushEnvironment: PushEnvironment? + + // To enable stats debuggin + public internal(set) var debug: Bool = false // MARK: - Initializers @@ -44,7 +47,8 @@ public struct TxConfig { ringBackTone: String? = nil, pushEnvironment: PushEnvironment? = nil, logLevel: LogLevel = .none, - reconnectClient:Bool = true + reconnectClient: Bool = true, + debug: Bool = false ) { self.sipUser = sipUser self.password = password @@ -56,6 +60,7 @@ public struct TxConfig { self.ringtone = ringtone self.reconnectClient = reconnectClient self.pushEnvironment = pushEnvironment + self.debug = debug Logger.log.verboseLevel = logLevel } @@ -72,7 +77,8 @@ public struct TxConfig { ringtone: String? = nil, ringBackTone: String? = nil, pushEnvironment: PushEnvironment? = nil, - logLevel: LogLevel = .none) { + logLevel: LogLevel = .none, + debug: Bool = false) { self.token = token if let pushToken = pushDeviceToken { //Create a notification configuration if there's an available a device push notification token @@ -81,6 +87,7 @@ public struct TxConfig { self.ringBackTone = ringBackTone self.ringtone = ringtone self.pushEnvironment = pushEnvironment + self.debug = debug Logger.log.verboseLevel = logLevel } diff --git a/TelnyxRTC/Telnyx/TxClient.swift b/TelnyxRTC/Telnyx/TxClient.swift index fb32fde9..1f2639ec 100644 --- a/TelnyxRTC/Telnyx/TxClient.swift +++ b/TelnyxRTC/Telnyx/TxClient.swift @@ -468,8 +468,9 @@ extension TxClient { delegate: self, ringtone: self.txConfig?.ringtone, ringbackTone: self.txConfig?.ringBackTone, - iceServers: self.serverConfiguration.webRTCIceServers) - call.newCall(callerName: callerName, callerNumber: callerNumber, destinationNumber: destinationNumber, clientState: clientState,customHeaders: customHeaders) + iceServers: self.serverConfiguration.webRTCIceServers, + debug: self.txConfig?.debug ?? false) + call.newCall(callerName: callerName, callerNumber: callerNumber, destinationNumber: destinationNumber, clientState: clientState, customHeaders: customHeaders) currentCallId = callId self.calls[callId] = call @@ -509,8 +510,8 @@ extension TxClient { ringtone: self.txConfig?.ringtone, ringbackTone: self.txConfig?.ringBackTone, iceServers: self.serverConfiguration.webRTCIceServers, - isAttach: isAttach - ) + isAttach: isAttach, + debug: self.txConfig?.debug ?? false) call.callInfo?.callerName = callerName call.callInfo?.callerNumber = callerNumber call.callOptions = TxCallOptions(audio: true) @@ -593,11 +594,11 @@ extension TxClient { if(noActiveCalls){ do { Logger.log.i(message: "TxClient:: No Active Calls Connecting Again") - try self.connectFromPush(txConfig: txConfig, serverConfiguration: pnServerConfig) + try self.connectFromPush(txConfig: txConfig, serverConfiguration: pnServerConfig) // Create an initial call_object to handle early bye message if let newCallId = (pushMetaData["call_id"] as? String) { - self.calls[UUID(uuidString: newCallId)!] = Call(callId: UUID(uuidString: newCallId)! , sessionId: newCallId, socket: self.socket!, delegate: self, iceServers: self.serverConfiguration.webRTCIceServers) + self.calls[UUID(uuidString: newCallId)!] = Call(callId: UUID(uuidString: newCallId)! , sessionId: newCallId, socket: self.socket!, delegate: self, iceServers: self.serverConfiguration.webRTCIceServers, debug: self.txConfig?.debug ?? false) } } catch let error { Logger.log.e(message: "TxClient:: push flow connect error \(error.localizedDescription)") diff --git a/TelnyxRTC/Telnyx/Utils/Logger.swift b/TelnyxRTC/Telnyx/Utils/Logger.swift index 9dd50eac..c7ee5202 100644 --- a/TelnyxRTC/Telnyx/Utils/Logger.swift +++ b/TelnyxRTC/Telnyx/Utils/Logger.swift @@ -29,6 +29,8 @@ public enum LogLevel: Int { case info /// Print `verto` messages. Incoming and outgoing verto messages are printed. case verto + /// Print `Debug Report` messages. Statistics of the RTCP connection + case stats /// All the SDK logs are printed. case all } @@ -62,6 +64,8 @@ class Logger { /// represents the current log level: `all` is set as default internal var verboseLevel: LogLevel = .all + private var statsGlyph: String = "\u{1F4CA}" // Glyph for messages of level .Stats + private var rightArrowGlyph: String = "\u{25B6}" private var leftArrowGlyph: String = "\u{25C0}" @@ -115,6 +119,12 @@ class Logger { print("TxClient : \(timeStamp.printTimestamp())" + buildMessage(level: .verto, message: message, direction: direction)) } } + + public func stats(message: String) { + if verboseLevel == .all || verboseLevel == .stats { + print("TxClient : \(timeStamp.printTimestamp())" + buildMessage(level: .stats, message: message)) + } + } private func getLogGlyph(level: LogLevel, direction: VertoDirection = .none) -> String { switch(level) { @@ -125,6 +135,7 @@ class Logger { case .info: return infoGlyph case .success: return successGlyph case .warning: return warningGlyph + case .stats: return statsGlyph } } diff --git a/TelnyxRTC/Telnyx/Verto/Message.swift b/TelnyxRTC/Telnyx/Verto/Message.swift index 285b3492..444517d3 100644 --- a/TelnyxRTC/Telnyx/Verto/Message.swift +++ b/TelnyxRTC/Telnyx/Verto/Message.swift @@ -21,7 +21,7 @@ class Message { } } - private var jsonMessage: [String: Any] = [String: Any]() + internal var jsonMessage: [String: Any] = [String: Any]() let jsonrpc = PROTOCOL_VERSION var id: String = UUID.init().uuidString.lowercased() @@ -31,12 +31,13 @@ class Message { var result: [String: Any]? var serverError: [String: Any]? - init() {} + init() { + self.jsonMessage["jsonrpc"] = self.jsonrpc + self.jsonMessage["id"] = self.id + } init(_ params: [String: Any], method: Method) { self.method = method - - self.jsonMessage = [String: Any]() self.jsonMessage["jsonrpc"] = self.jsonrpc self.jsonMessage["id"] = self.id self.jsonMessage["method"] = self.method?.rawValue diff --git a/TelnyxRTC/Telnyx/Verto/Stats/DebugReportDataMessage.swift b/TelnyxRTC/Telnyx/Verto/Stats/DebugReportDataMessage.swift new file mode 100644 index 00000000..15ce3599 --- /dev/null +++ b/TelnyxRTC/Telnyx/Verto/Stats/DebugReportDataMessage.swift @@ -0,0 +1,15 @@ + +class DebugReportDataMessage: StatsMessage { + init(reportID: String, reportData: [String: Any]) { + + var data = reportData + data["timeTaken"] = 1 + let dateFormatter = ISO8601DateFormatter() + dateFormatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds] + let timestamp = dateFormatter.string(from: Date()) + data["timestamp"] = timestamp + super.init(type: .DEBUG_REPORT_DATA, + reportID: reportID, + reportData: data) + } +} diff --git a/TelnyxRTC/Telnyx/Verto/Stats/DebugReportStartMessage.swift b/TelnyxRTC/Telnyx/Verto/Stats/DebugReportStartMessage.swift new file mode 100644 index 00000000..d400e958 --- /dev/null +++ b/TelnyxRTC/Telnyx/Verto/Stats/DebugReportStartMessage.swift @@ -0,0 +1,8 @@ + +class DebugReportStartMessage: StatsMessage { + init(reportID: String) { + super.init(type: .DEBUG_REPORT_START, + reportID: reportID, + reportData: nil) + } +} diff --git a/TelnyxRTC/Telnyx/Verto/Stats/DebugReportStopMessage.swift b/TelnyxRTC/Telnyx/Verto/Stats/DebugReportStopMessage.swift new file mode 100644 index 00000000..72a9bdcd --- /dev/null +++ b/TelnyxRTC/Telnyx/Verto/Stats/DebugReportStopMessage.swift @@ -0,0 +1,8 @@ + +class DebugReportStopMessage: StatsMessage { + init(reportID: String) { + super.init(type: .DEBUG_REPORT_STOP, + reportID: reportID, + reportData: nil) + } +} diff --git a/TelnyxRTC/Telnyx/Verto/Stats/StatsMessage.swift b/TelnyxRTC/Telnyx/Verto/Stats/StatsMessage.swift new file mode 100644 index 00000000..c7b53a9e --- /dev/null +++ b/TelnyxRTC/Telnyx/Verto/Stats/StatsMessage.swift @@ -0,0 +1,28 @@ +// +// StatsMessage.swift +// TelnyxRTC +// +// Created by Isaac Akakpo on 8/16/24. +// + +import Foundation + +private let DEBUG_REPORT_VERSION: Int = 1 + +enum StatsMessageType : String { + case DEBUG_REPORT_STOP = "debug_report_stop" + case DEBUG_REPORT_START = "debug_report_start" + case DEBUG_REPORT_DATA = "debug_report_data" +} + +class StatsMessage: Message { + init(type: StatsMessageType, + reportID: String, + reportData: [String:Any]?) { + super.init() + self.jsonMessage["debug_report_version"] = DEBUG_REPORT_VERSION + self.jsonMessage["debug_report_data"] = reportData + self.jsonMessage["type"] = type.rawValue + self.jsonMessage["debug_report_id"] = reportID + } +} diff --git a/TelnyxRTC/Telnyx/Verto/StatsMessage.swift b/TelnyxRTC/Telnyx/Verto/StatsMessage.swift deleted file mode 100644 index 4c4bacc5..00000000 --- a/TelnyxRTC/Telnyx/Verto/StatsMessage.swift +++ /dev/null @@ -1,40 +0,0 @@ -// -// StatsMessage.swift -// TelnyxRTC -// -// Created by Isaac Akakpo on 8/16/24. -// - -import Foundation - -private let PROTOCOL_VERSION: String = "2.0" - -public class StatsMessage { - - private var jsonMessage: [String: Any] = [String: Any]() - let jsonrpc = PROTOCOL_VERSION - var id: String = UUID.init().uuidString.lowercased() - - - init(reportID:String,reportData:[String:Any]) { - self.jsonMessage = [String: Any]() - self.jsonMessage["jsonrpc"] = self.jsonrpc - self.jsonMessage["id"] = self.id - self.jsonMessage["debug_report_version"] = 1 - self.jsonMessage["debug_report_data"] = reportData - self.jsonMessage["type"] = "debug_report_data" - self.jsonMessage["debug_report_id"] = reportID - } - - func encode() -> String? { - guard let jsonData = try? JSONSerialization.data(withJSONObject: jsonMessage, options: []), - let jsonString = String(data: jsonData, encoding: .utf8) else { - Logger.log.e(message: "Message:: encode() error") - return nil - } - Logger.log.i(message: "Message:: encode() " + jsonString) - return jsonString - } - - -} diff --git a/TelnyxRTC/Telnyx/WebRTC/Call.swift b/TelnyxRTC/Telnyx/WebRTC/Call.swift index 9e0d7b78..455b26a4 100644 --- a/TelnyxRTC/Telnyx/WebRTC/Call.swift +++ b/TelnyxRTC/Telnyx/WebRTC/Call.swift @@ -101,6 +101,8 @@ public class Call { var remoteSdp: String? var callOptions: TxCallOptions? + + var statsReporter: WebRTCStatsReporter? /// Custum headers pased /from webrtc telnyx_rtc.INVITE Messages public internal(set) var inviteCustomHeaders: [String:String]? @@ -114,6 +116,9 @@ public class Call { public internal(set) var telnyxSessionId: UUID? /// Telnyx call leg ID public internal(set) var telnyxLegId: UUID? + /// To enable call stats + public internal(set) var debug: Bool = false + // MARK: - Properties /// `TxCallInfo` Contains the required information of the current Call. @@ -143,9 +148,10 @@ public class Call { ringtone: String? = nil, ringbackTone: String? = nil, iceServers: [RTCIceServer], - isAttach:Bool = false + isAttach: Bool = false, + debug: Bool = false ) { - if(isAttach){ + if isAttach { self.direction = CallDirection.ATTACH } else { self.direction = CallDirection.INBOUND @@ -165,7 +171,7 @@ public class Call { // Configure iceServers self.iceServers = iceServers - if(!isAttach){ + if !isAttach { //Ringtone and ringbacktone self.ringTonePlayer = self.buildAudioPlayer(fileName: ringtone,fileType: .RINGTONE) self.ringbackPlayer = self.buildAudioPlayer(fileName: ringbackTone,fileType: .RINGBACK) @@ -173,9 +179,11 @@ public class Call { self.playRingtone() } - if(!isAttach){ + if !isAttach { updateCallState(callState: .NEW) } + + self.debug = debug } //Contructor for attachCalls @@ -186,7 +194,8 @@ public class Call { delegate: CallProtocol, telnyxSessionId: UUID? = nil, telnyxLegId: UUID? = nil, - iceServers: [RTCIceServer]) { + iceServers: [RTCIceServer], + debug: Bool = false) { self.direction = CallDirection.ATTACH //Session obtained after login with the signaling socket self.sessionId = sessionId @@ -202,6 +211,8 @@ public class Call { // Configure iceServers self.iceServers = iceServers + + self.debug = debug } /// Constructor for outgoing calls @@ -211,7 +222,8 @@ public class Call { delegate: CallProtocol, ringtone: String? = nil, ringbackTone: String? = nil, - iceServers: [RTCIceServer]) { + iceServers: [RTCIceServer], + debug: Bool = false) { //Session obtained after login with the signaling socket self.sessionId = sessionId //this is the signaling server socket @@ -227,14 +239,7 @@ public class Call { self.ringbackPlayer = self.buildAudioPlayer(fileName: ringbackTone,fileType: .RINGBACK) self.updateCallState(callState: .RINGING) - } - - public func startDebugStats() { - self.peer?.startTimer() - } - - private func stopDebugStats() { - self.peer?.stopTimer() + self.debug = debug } // MARK: - Private functions @@ -250,7 +255,12 @@ public class Call { self.callOptions = TxCallOptions(destinationNumber: destinationNumber, clientState: clientState) + // We need to: + // - Create the reporter to send the startReporting message before creating the peer connection + // - Start the reporter once the peer connection is created + self.configureStatsReporter() self.peer = Peer(iceServers: self.iceServers) + self.startStatsReporter() self.peer?.delegate = self self.peer?.socket = self.socket self.peer?.offer(completion: { (sdp, error) in @@ -317,11 +327,13 @@ public class Call { private func endCall() { self.stopRingtone() self.stopRingbackTone() + self.statsReporter?.dispose() self.peer?.dispose() self.updateCallState(callState: .DONE) } internal func endForAttachCall() { + self.statsReporter?.dispose() self.peer?.dispose() // self.updateCallState(callState: .DONE) } @@ -338,8 +350,8 @@ extension Call { /// Creates a new oubound call internal func newCall(callerName: String, - callerNumber: String, - destinationNumber: String, + callerNumber: String, + destinationNumber: String, clientState: String? = nil, customHeaders:[String:String] = [:]) { if (destinationNumber.isEmpty) { @@ -375,7 +387,9 @@ extension Call { return } self.answerCustomHeaders = customHeaders + self.configureStatsReporter() self.peer = Peer(iceServers: self.iceServers) + self.startStatsReporter() self.peer?.delegate = self self.peer?.socket = self.socket self.incomingOffer(sdp: remoteSdp) @@ -401,14 +415,17 @@ extension Call { /// - Parameters: /// - customHeaders: (optional) Custom Headers to be passed over webRTC Messages, should be in the /// format `X-key:Value` `X` is required for headers to be passed. - internal func acceptReAttach(peer:Peer?,customHeaders:[String:String] = [:]) { + internal func acceptReAttach(peer: Peer?, customHeaders:[String:String] = [:]) { //TODO: Create an error if there's no remote SDP guard let remoteSdp = self.remoteSdp else { return } peer?.dispose() + self.statsReporter?.dispose() self.answerCustomHeaders = customHeaders - self.peer = Peer(iceServers: self.iceServers,isAttach: true) + self.configureStatsReporter() + self.peer = Peer(iceServers: self.iceServers, isAttach: true) + self.startStatsReporter() self.peer?.delegate = self self.peer?.socket = self.socket self.incomingOffer(sdp: remoteSdp) @@ -427,6 +444,22 @@ extension Call { //self.updateCallState(callState: .ACTIVE) }) } + + private func configureStatsReporter() { + if debug, + let socket = self.socket { + self.statsReporter?.dispose() + self.statsReporter = WebRTCStatsReporter(socket: socket) + } + } + + private func startStatsReporter() { + if debug, + let callId = self.callInfo?.callId, + let peer = self.peer { + self.statsReporter?.startDebugReport(peerId: callId, peer: peer) + } + } } // MARK: - DTMF @@ -534,14 +567,14 @@ extension Call { extension Call : PeerDelegate { //If we received at least one ICE Candidate, then we can send the telnyx_rtc.invite message to start a call - func onICECandidate(sdp: RTCSessionDescription?, iceCandidate: RTCIceCandidate) { + func onNegotiationEnded(sdp: RTCSessionDescription?) { guard let sdp = sdp, let sessionId = self.sessionId, let callInfo = self.callInfo, let callOptions = self.callOptions, let _ = self.callInfo?.callId else { - Logger.log.e(message: "Call:: onICECandidate missing arguments") + Logger.log.e(message: "Call:: onNegotiationEnded missing arguments") return } @@ -561,10 +594,9 @@ extension Call : PeerDelegate { let message = inviteMessage.encode() ?? "" self.socket?.sendMessage(message: message) self.updateCallState(callState: .CONNECTING) - Logger.log.s(message: "Call:: Send invite >> \(message)") + Logger.log.s(message: "Send invite >> \(message)") } - else if (self.direction == .ATTACH){ - + else if (self.direction == .ATTACH) { let attachCallOption = TxCallOptions(destinationNumber: callOptions.destinationNumber,attach: true,userVariables: callOptions.userVariables) @@ -702,7 +734,7 @@ extension Call { self.ringTonePlayer?.stop() } -private func playRingbackTone() { + private func playRingbackTone() { Logger.log.i(message: "Call:: playRingbackTone()") guard let ringbackPlayer = self.ringbackPlayer else { return } @@ -734,5 +766,3 @@ private func playRingbackTone() { return nil } } - - diff --git a/TelnyxRTC/Telnyx/WebRTC/Extensions/RTCBundlePolicy+Extension.swift b/TelnyxRTC/Telnyx/WebRTC/Extensions/RTCBundlePolicy+Extension.swift new file mode 100644 index 00000000..2443d2f8 --- /dev/null +++ b/TelnyxRTC/Telnyx/WebRTC/Extensions/RTCBundlePolicy+Extension.swift @@ -0,0 +1,12 @@ +import WebRTC + +extension RTCBundlePolicy { + func telnyx_to_string() -> String { + switch self { + case .balanced: return "balanced" + case .maxBundle: return "maxBundle" + case .maxCompat: return "maxCompat" + @unknown default: return "unknown" + } + } +} diff --git a/TelnyxRTC/Telnyx/WebRTC/Extensions/RTCConfiguration+Extension.swift b/TelnyxRTC/Telnyx/WebRTC/Extensions/RTCConfiguration+Extension.swift new file mode 100644 index 00000000..bad01cb6 --- /dev/null +++ b/TelnyxRTC/Telnyx/WebRTC/Extensions/RTCConfiguration+Extension.swift @@ -0,0 +1,18 @@ +import WebRTC + +extension RTCConfiguration { + func telnyx_to_stats_dictionary() -> [String: Any] { + var configurationData = [String: Any]() + configurationData["bundlePolicy"] = self.bundlePolicy.telnyx_to_string() + configurationData["iceTransportPolicy"] = self.iceTransportPolicy.telnyx_to_string() + configurationData["rtcpMuxPolicy"] = self.rtcpMuxPolicy.telnyx_to_string() + configurationData["continualGatheringPolicy"] = self.continualGatheringPolicy.telnyx_to_string() + configurationData["sdpSemantics"] = self.sdpSemantics.telnyx_to_string() + configurationData["iceCandidatePoolSize"] = self.iceCandidatePoolSize + configurationData["iceServers"] = self.iceServers.map { $0.telnyx_to_stats_dictionary() } + configurationData["rtcpAudioReportIntervalMs"] = self.rtcpAudioReportIntervalMs + configurationData["rtcpVideoReportIntervalMs"] = self.rtcpVideoReportIntervalMs + + return configurationData + } +} diff --git a/TelnyxRTC/Telnyx/WebRTC/Extensions/RTCContinualGatheringPolicy+Extension.swift b/TelnyxRTC/Telnyx/WebRTC/Extensions/RTCContinualGatheringPolicy+Extension.swift new file mode 100644 index 00000000..3ef287c4 --- /dev/null +++ b/TelnyxRTC/Telnyx/WebRTC/Extensions/RTCContinualGatheringPolicy+Extension.swift @@ -0,0 +1,11 @@ +import WebRTC + +extension RTCContinualGatheringPolicy { + func telnyx_to_string() -> String { + switch self { + case .gatherContinually: return "gatherContinually" + case .gatherOnce: return "gatherOnce" + @unknown default: return "unknown" + } + } +} diff --git a/TelnyxRTC/Telnyx/WebRTC/Extensions/RTCIceCandidate+Extension.swift b/TelnyxRTC/Telnyx/WebRTC/Extensions/RTCIceCandidate+Extension.swift new file mode 100644 index 00000000..6468d0a2 --- /dev/null +++ b/TelnyxRTC/Telnyx/WebRTC/Extensions/RTCIceCandidate+Extension.swift @@ -0,0 +1,14 @@ +import WebRTC + +extension RTCIceCandidate { + + func telnyx_stats_extractUfrag() -> String? { + let pattern = "ufrag\\s+(\\w+)" + guard let regex = try? NSRegularExpression(pattern: pattern), + let match = regex.firstMatch(in: sdp, range: NSRange(sdp.startIndex..., in: sdp)), + let range = Range(match.range(at: 1), in: sdp) else { + return nil + } + return String(sdp[range]) + } +} diff --git a/TelnyxRTC/Telnyx/WebRTC/Extensions/RTCIceConnectionState+Extension.swift b/TelnyxRTC/Telnyx/WebRTC/Extensions/RTCIceConnectionState+Extension.swift new file mode 100644 index 00000000..8ce21bd0 --- /dev/null +++ b/TelnyxRTC/Telnyx/WebRTC/Extensions/RTCIceConnectionState+Extension.swift @@ -0,0 +1,17 @@ +import WebRTC + +extension RTCIceConnectionState { + func telnyx_to_string() -> 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" + @unknown default: return "unknown" + } + } +} diff --git a/TelnyxRTC/Telnyx/WebRTC/Extensions/RTCIceGatheringState+Extension.swift b/TelnyxRTC/Telnyx/WebRTC/Extensions/RTCIceGatheringState+Extension.swift new file mode 100644 index 00000000..697c6c62 --- /dev/null +++ b/TelnyxRTC/Telnyx/WebRTC/Extensions/RTCIceGatheringState+Extension.swift @@ -0,0 +1,12 @@ +import WebRTC + +extension RTCIceGatheringState { + func telnyx_to_string() -> String { + switch self { + case .new: return "new" + case .gathering: return "gathering" + case .complete: return "complete" + @unknown default: return "unknown" + } + } +} diff --git a/TelnyxRTC/Telnyx/WebRTC/Extensions/RTCIceServer+Extension.swift b/TelnyxRTC/Telnyx/WebRTC/Extensions/RTCIceServer+Extension.swift new file mode 100644 index 00000000..139ee964 --- /dev/null +++ b/TelnyxRTC/Telnyx/WebRTC/Extensions/RTCIceServer+Extension.swift @@ -0,0 +1,16 @@ +import WebRTC + +// MARK: - RTCIceServer Extension +extension RTCIceServer { + func telnyx_to_stats_dictionary() -> [String: Any] { + return [ + "urlStrings": self.urlStrings, + "username": self.username ?? NSNull(), + "credential": self.credential ?? NSNull(), + "tlsCertPolicy": self.tlsCertPolicy.telnyx_to_string(), + "hostname": self.hostname ?? NSNull(), + "tlsAlpnProtocols": self.tlsAlpnProtocols.isEmpty ? NSNull() : self.tlsAlpnProtocols, + "tlsEllipticCurves": self.tlsEllipticCurves.isEmpty ? NSNull() : self.tlsEllipticCurves + ] + } +} diff --git a/TelnyxRTC/Telnyx/WebRTC/Extensions/RTCIceTransportPolicy+Extension.swift b/TelnyxRTC/Telnyx/WebRTC/Extensions/RTCIceTransportPolicy+Extension.swift new file mode 100644 index 00000000..89c8d94e --- /dev/null +++ b/TelnyxRTC/Telnyx/WebRTC/Extensions/RTCIceTransportPolicy+Extension.swift @@ -0,0 +1,13 @@ +import WebRTC + +extension RTCIceTransportPolicy { + func telnyx_to_string() -> String { + switch self { + case .all: return "all" + case .noHost: return "noHost" + case .none: return "none" + case .relay: return "relay" + @unknown default: return "unknown" + } + } +} diff --git a/TelnyxRTC/Telnyx/WebRTC/Extensions/RTCMediaStream+Extension.swift b/TelnyxRTC/Telnyx/WebRTC/Extensions/RTCMediaStream+Extension.swift new file mode 100644 index 00000000..1f00b0b6 --- /dev/null +++ b/TelnyxRTC/Telnyx/WebRTC/Extensions/RTCMediaStream+Extension.swift @@ -0,0 +1,7 @@ +import WebRTC + +extension RTCMediaStream { + func telnyx_to_stats_dictionary() -> [String: Any] { + return ["id": self.streamId] + } +} diff --git a/TelnyxRTC/Telnyx/WebRTC/Extensions/RTCMediaStreamTrack+Extension.swift b/TelnyxRTC/Telnyx/WebRTC/Extensions/RTCMediaStreamTrack+Extension.swift new file mode 100644 index 00000000..f780e818 --- /dev/null +++ b/TelnyxRTC/Telnyx/WebRTC/Extensions/RTCMediaStreamTrack+Extension.swift @@ -0,0 +1,13 @@ +import WebRTC + +extension RTCMediaStreamTrack { + func telnyx_to_stats_dictionary() -> [String: Any] { + return [ + "enabled": self.isEnabled, + "id": self.trackId, + "kind": self.kind, + "readyState": self.readyState.rawValue + ] + } +} + diff --git a/TelnyxRTC/Telnyx/WebRTC/Extensions/RTCRtcpMuxPolicy+Extension.swift b/TelnyxRTC/Telnyx/WebRTC/Extensions/RTCRtcpMuxPolicy+Extension.swift new file mode 100644 index 00000000..f45bfb7d --- /dev/null +++ b/TelnyxRTC/Telnyx/WebRTC/Extensions/RTCRtcpMuxPolicy+Extension.swift @@ -0,0 +1,11 @@ +import WebRTC + +extension RTCRtcpMuxPolicy { + func telnyx_to_string() -> String { + switch self { + case .negotiate: return "negotiate" + case .require: return "require" + @unknown default: return "unknown" + } + } +} diff --git a/TelnyxRTC/Telnyx/WebRTC/Extensions/RTCSdpSemantics+Extension.swift b/TelnyxRTC/Telnyx/WebRTC/Extensions/RTCSdpSemantics+Extension.swift new file mode 100644 index 00000000..477ce22e --- /dev/null +++ b/TelnyxRTC/Telnyx/WebRTC/Extensions/RTCSdpSemantics+Extension.swift @@ -0,0 +1,12 @@ +import WebRTC + +extension RTCSdpSemantics { + func telnyx_to_string() -> String { + switch self { + case .planB: return "planB" + case .unifiedPlan: return "unifiedPlan" + @unknown default: return "unknown" + } + } +} + diff --git a/TelnyxRTC/Telnyx/WebRTC/Extensions/RTCSignalingState+Extension.swift b/TelnyxRTC/Telnyx/WebRTC/Extensions/RTCSignalingState+Extension.swift new file mode 100644 index 00000000..38761fe1 --- /dev/null +++ b/TelnyxRTC/Telnyx/WebRTC/Extensions/RTCSignalingState+Extension.swift @@ -0,0 +1,15 @@ +import WebRTC + +extension RTCSignalingState { + func telnyx_to_string() -> String { + switch self { + case .stable: return "stable" + case .haveLocalOffer: return "have-local-offer" + case .haveLocalPrAnswer: return "have-local-pr-answer" + case .haveRemoteOffer: return "have-remote-offer" + case .haveRemotePrAnswer: return "have-remote-pr-answer" + case .closed: return "closed" + @unknown default: return "unknown" + } + } +} diff --git a/TelnyxRTC/Telnyx/WebRTC/Extensions/RTClsCertPolicy+Extension.swift b/TelnyxRTC/Telnyx/WebRTC/Extensions/RTClsCertPolicy+Extension.swift new file mode 100644 index 00000000..367bced2 --- /dev/null +++ b/TelnyxRTC/Telnyx/WebRTC/Extensions/RTClsCertPolicy+Extension.swift @@ -0,0 +1,11 @@ +import WebRTC + +extension RTCTlsCertPolicy { + func telnyx_to_string() -> String { + switch self { + case .secure: return "secure" + case .insecureNoCheck: return "insecureNoCheck" + @unknown default: return "unknown" + } + } +} diff --git a/TelnyxRTC/Telnyx/WebRTC/Peer.swift b/TelnyxRTC/Telnyx/WebRTC/Peer.swift index ff36e4d1..1bd2b9f2 100644 --- a/TelnyxRTC/Telnyx/WebRTC/Peer.swift +++ b/TelnyxRTC/Telnyx/WebRTC/Peer.swift @@ -10,11 +10,10 @@ import Foundation import WebRTC protocol PeerDelegate: AnyObject { - //TODO: Rename this to "negotiationDidEnded" - func onICECandidate(sdp: RTCSessionDescription?, iceCandidate: RTCIceCandidate) + func onNegotiationEnded(sdp: RTCSessionDescription?) } -class Peer : NSObject { +class Peer : NSObject, WebRTCEventHandler { private let audioQueue = DispatchQueue(label: "audio") private let NEGOTIATION_TIMOUT = 0.3 //time in milliseconds @@ -22,11 +21,12 @@ class Peer : NSObject { private let VIDEO_TRACK_ID = "video0" //TODO: REMOVE THIS FOR V1 private let VIDEO_DEMO_LOCAL_VIDEO = "local_video_streaming.mp4" - private var gatheredICECandidates:[String] = [] - var socket:Socket? - private let timeStamp = Timestamp() + private var gatheredICECandidates: [String] = [] + var socket: Socket? - private let mediaConstrains = [kRTCMediaConstraintsOfferToReceiveAudio: kRTCMediaConstraintsValueTrue, kRTCMediaConstraintsOfferToReceiveVideo: kRTCMediaConstraintsValueFalse] + + private let mediaConstrains = [kRTCMediaConstraintsOfferToReceiveAudio: kRTCMediaConstraintsValueTrue, + kRTCMediaConstraintsOfferToReceiveVideo: kRTCMediaConstraintsValueFalse] weak var delegate: PeerDelegate? var connection : RTCPeerConnection? @@ -39,7 +39,7 @@ class Peer : NSObject { private var videoCapturer: RTCVideoCapturer? private var localVideoTrack: RTCVideoTrack? private var remoteVideoTrack: RTCVideoTrack? - private var callLegID: String? + internal var callLegID: String? //Data channel private var localDataChannel: RTCDataChannel? @@ -49,6 +49,17 @@ class Peer : NSObject { private var negotiationTimer: Timer? private var negotiationEnded: Bool = false + // WEBRTC STATS + var onSignalingStateChange: ((RTCSignalingState, RTCPeerConnection) -> Void)? + var onAddStream: ((RTCMediaStream) -> Void)? + var onRemoveStream: ((RTCMediaStream) -> Void)? + var onNegotiationNeeded: (() -> Void)? + var onIceConnectionChange: ((RTCIceConnectionState) -> Void)? + var onIceGatheringChange: ((RTCIceGatheringState) -> Void)? + var onIceCandidate: ((RTCIceCandidate) -> Void)? + var onRemoveIceCandidates: (([RTCIceCandidate]) -> Void)? + var onDataChannel: ((RTCDataChannel) -> Void)? + public var isAudioTrackEnabled: Bool { if self.connection?.configuration.sdpSemantics == .planB { return self.connection?.senders @@ -76,7 +87,8 @@ class Peer : NSObject { fatalError("Peer:init is unavailable") } - required init(iceServers: [RTCIceServer],isAttach:Bool = false) { + required init(iceServers: [RTCIceServer], + isAttach: Bool = false) { let config = RTCConfiguration() config.iceServers = iceServers @@ -94,7 +106,7 @@ class Peer : NSObject { super.init() self.createMediaSenders() - if(!isAttach){ + if (!isAttach) { self.configureAudioSession() } //listen RTCPeer connection events @@ -187,7 +199,7 @@ class Peer : NSObject { // MARK: Signaling ANSWER - func answer(callLegId:String,completion: @escaping (_ sdp: RTCSessionDescription?, _ error: Error?) -> Void) { + func answer(callLegId: String, completion: @escaping (_ sdp: RTCSessionDescription?, _ error: Error?) -> Void) { self.negotiationEnded = false let constrains = RTCMediaConstraints(mandatoryConstraints: self.mediaConstrains, optionalConstraints: nil) @@ -213,108 +225,6 @@ class Peer : NSObject { }) } } - - private var timer: DispatchSourceTimer? - - private var statsEvent = [String: Any]() - private var inboundStats = [Any]() - private var outBoundStats = [Any]() - private var statsData = [String: Any]() - private var audio = [String: [Any]]() - private var candidatePairs = [Any]() - private let CANDIDATE_PAIR_LIMIT = 5 - private var debugStatsId = UUID.init() - private var debugReportStarted = false - private var isDebugStats = false - - func startTimer() { - isDebugStats = true - let queue = DispatchQueue.main - timer = DispatchSource.makeTimerSource(queue: queue) - timer?.schedule(deadline: .now(), repeating: 2.0) - timer?.setEventHandler { [weak self] in - self?.executeTask() - } - timer?.resume() - } - - func stopTimer() { - statsData["audio"] = audio - statsEvent["data"] = statsData - statsEvent.printJson() - timer?.cancel() - timer = nil - sendStatsType(id: debugStatsId, type: StatsType.STOP_STARTS.rawValue) - debugReportStarted = false - isDebugStats = false - } - - /// To receive INVITE message after Push Noficiation is Received. Send attachCall Command - fileprivate func sendStats(id:UUID,data:[String:Any]) { - Logger.log.e(message: "TxClient:: Sending Stats") - let statsMessage = StatsMessage(reportID: id.uuidString.lowercased(), reportData: data) - self.socket?.sendMessage(message: statsMessage.encode()) - } - - fileprivate func sendStatsType(id:UUID,type:String) { - Logger.log.e(message: "TxClient:: Sending Stats \(type)") - let statsMessage = InitiateOrStopStats(type: type, reportID: id.uuidString.lowercased()) - self.socket?.sendMessage(message: statsMessage.encode()) - } - - - - private func executeTask() { - print("Task executed at \(Date())") - - - if(!debugReportStarted){ - debugStatsId = UUID.init() - sendStatsType(id: debugStatsId, type: StatsType.START_STARTS.rawValue) - debugReportStarted = true - } - - statsEvent["event"] = "stats" - statsEvent["tag"] = "stats" - statsEvent["peerId"] = "stats" - statsEvent["connectionId"] = self.callLegID ?? "" - statsEvent["timeTaken"] = 1 - - - - self.connection?.statistics(completionHandler: { reports in - reports.statistics.forEach { report in - if(report.value.type == "inbound-rtp") { - //Logger.log.i(message: "Peer:: ICE negotiation updated. Report New: \(report.values)") - self.inboundStats.append(report.value.values) - } - if(report.value.type == "outbound-rtp") { - //Logger.log.i(message: "Peer:: ICE negotiation updated. Report New: \(report.values)") - self.outBoundStats.append(report.value.values) - } - if(report.value.type == "candidate-pair" && self.candidatePairs.count < self.CANDIDATE_PAIR_LIMIT) { - //Logger.log.i(message: "Peer:: ICE negotiation updated. Report New: \(report.values)") - self.candidatePairs.append(report.value.values) - } - } - }) - audio["outbound"] = outBoundStats - audio["inbound"] = inboundStats - statsData["audio"] = audio - statsEvent["data"] = statsData - statsEvent["timestamp"] = timeStamp.getTimestamp() - - if(inboundStats.count > 0 && outBoundStats.count > 0 && candidatePairs.count > 0){ - inboundStats.removeAll() - outBoundStats.removeAll() - candidatePairs.removeAll() - statsData.removeAll() - audio.removeAll() - self.sendStats(id: debugStatsId, data: statsEvent) - } - - } - /** This code should be started when the first ICE candidate is created. @@ -347,7 +257,7 @@ class Peer : NSObject { self.negotiationEnded = true // At this moment we should have at least one ICE candidate. // Lets stop the ICE negotiation process and call the apropiate delegate - self.delegate?.onICECandidate(sdp: peerConnection.localDescription, iceCandidate: candidate) + self.delegate?.onNegotiationEnded(sdp: peerConnection.localDescription) Logger.log.i(message: "Peer:: ICE negotiation ended.") } } @@ -356,23 +266,29 @@ class Peer : NSObject { /// Close connection and release resources func dispose() { Logger.log.i(message: "Peer:: dispose()") - if(isDebugStats){ - stopTimer() - } - //This should release all the connection resources - //including audio / video streams self.connection?.close() self.delegate = nil - + self.localAudioTrack = nil self.localVideoTrack = nil self.localDataChannel = nil - + self.remoteVideoTrack = nil self.remoteDataChannel = nil + + self.onSignalingStateChange = nil + self.onAddStream = nil + self.onRemoveStream = nil + self.onNegotiationNeeded = nil + self.onIceConnectionChange = nil + self.onIceGatheringChange = nil + self.onIceCandidate = nil + self.onRemoveIceCandidates = nil + self.onDataChannel = nil } + } // MARK: - Tracks handling extension Peer { @@ -417,27 +333,13 @@ extension Peer { */ extension Peer : RTCPeerConnectionDelegate { func peerConnection(_ peerConnection: RTCPeerConnection, didChange stateChanged: RTCSignalingState) { - var state = "" - switch(stateChanged) { - case .stable: - state = "stable" - case .haveLocalOffer: - state = "haveLocalOffer" - case .haveLocalPrAnswer: - state = "haveLocalPrAnswer" - case .haveRemoteOffer: - state = "haveRemoteOffer" - case .haveRemotePrAnswer: - state = "haveRemotePrAnswer" - case .closed: - state = "closed" - @unknown default: - state = "unknown" - } - Logger.log.i(message: "Peer:: connection didChange state: [\(state.uppercased())]") + let state = stateChanged.telnyx_to_string() + onSignalingStateChange?(stateChanged, peerConnection) + Logger.log.i(message: "Peer:: connection didChange state: [\(state)]") } func peerConnection(_ peerConnection: RTCPeerConnection, didAdd stream: RTCMediaStream) { + onAddStream?(stream) Logger.log.i(message: "Peer:: connection didAdd: \(stream)") if stream.videoTracks.count > 0 { self.remoteVideoTrack = stream.videoTracks[0] @@ -445,51 +347,23 @@ extension Peer : RTCPeerConnectionDelegate { } func peerConnection(_ peerConnection: RTCPeerConnection, didRemove stream: RTCMediaStream) { - // Logger.log.i(message: "Peer:: connection didRemove \(stream)") + onRemoveStream?(stream) + Logger.log.i(message: "Peer:: connection didRemove \(stream)") } func peerConnectionShouldNegotiate(_ peerConnection: RTCPeerConnection) { + onNegotiationNeeded?() Logger.log.i(message: "Peer:: connection should negotiate") } func peerConnection(_ peerConnection: RTCPeerConnection, didChange newState: RTCIceConnectionState) { - var state = "" - switch(newState) { - case .checking: - state = "checking" - case .new: - state = "new" - case .connected: - state = "connected" - case .completed: - state = "completed" - case .failed: - state = "failed" - case .disconnected: - state = "disconnected" - case .closed: - state = "closed" - case .count: - state = "count" - @unknown default: - state = "unknown" - } - Logger.log.i(message: "Peer:: connection didChange RTCIceConnectionState [\(state.uppercased())]") + onIceConnectionChange?(newState) + Logger.log.i(message: "Peer:: connection didChange ICE connection state: [\(newState.telnyx_to_string().uppercased())]") } func peerConnection(_ peerConnection: RTCPeerConnection, didChange newState: RTCIceGatheringState) { - var state = "" - switch(newState) { - case .new: - state = "new" - case .gathering: - state = "gathering" - case .complete: - state = "complete" - @unknown default: - state = "unknown" - } - Logger.log.s(message: "Peer:: connection didChange RTCIceGatheringState [\(state.uppercased())]") + onIceGatheringChange?(newState) + Logger.log.s(message: "Peer:: connection didChange ICE gathering state: [\(newState.telnyx_to_string().uppercased())]") } func peerConnection(_ peerConnection: RTCPeerConnection, didGenerate candidate: RTCIceCandidate) { @@ -509,6 +383,8 @@ extension Peer : RTCPeerConnectionDelegate { return } + // We call the callback when the iceCandidate is added + onIceCandidate?(candidate) // Add the generated ICE candidate to the peer connection. // This helps populate the local SDP with the ICE candidate information. connection?.add(candidate, completionHandler: { error in @@ -531,71 +407,13 @@ extension Peer : RTCPeerConnectionDelegate { } } - func peerConnection(_ peerConnection: RTCPeerConnection, didRemove candidates: [RTCIceCandidate]) { - // Logger.log.i(message: "Peer:: connection didRemove [RTCIceCandidate]: \(candidates)") + onRemoveIceCandidates?(candidates) + Logger.log.i(message: "Peer:: connection didRemove [RTCIceCandidate]: \(candidates)") } - - func peerConnection(_ peerConnection: RTCPeerConnection, didOpen dataChannel: RTCDataChannel) { + onDataChannel?(dataChannel) Logger.log.i(message: "Peer:: connection didOpen RTCDataChannel: \(dataChannel)") } } - -// MARK: - Dictionary -extension Dictionary { - - var json: String { - let invalidJson = "Not a valid JSON" - do { - let jsonData = try JSONSerialization.data(withJSONObject: self, options: .prettyPrinted) - return String(bytes: jsonData, encoding: String.Encoding.utf8) ?? invalidJson - } catch { - return invalidJson - } - } - - func printJson() { - print(json) - } - -} - -// MARK: - Stats - -private let PROTOCOL_VERSION: String = "2.0" - - - -enum StatsType : String { - case STOP_STARTS = "debug_report_stop" - case START_STARTS = "debug_report_start" -} - -class InitiateOrStopStats { - - private var jsonMessage: [String: Any] = [String: Any]() - let jsonrpc = PROTOCOL_VERSION - var id: String = UUID.init().uuidString.lowercased() - - init(type:String,reportID:String){ - self.jsonMessage = [String: Any]() - self.jsonMessage["jsonrpc"] = self.jsonrpc - self.jsonMessage["id"] = self.id - self.jsonMessage["debug_report_version"] = 1 - self.jsonMessage["type"] = type - self.jsonMessage["debug_report_id"] = reportID - } - - func encode() -> String? { - guard let jsonData = try? JSONSerialization.data(withJSONObject: jsonMessage, options: []), - let jsonString = String(data: jsonData, encoding: .utf8) else { - Logger.log.e(message: "Message:: encode() error") - return nil - } - return jsonString - } -} - - diff --git a/TelnyxRTC/Telnyx/WebRTC/Stats/WebRTCEventHandler.swift b/TelnyxRTC/Telnyx/WebRTC/Stats/WebRTCEventHandler.swift new file mode 100644 index 00000000..1420443e --- /dev/null +++ b/TelnyxRTC/Telnyx/WebRTC/Stats/WebRTCEventHandler.swift @@ -0,0 +1,14 @@ +import WebRTC + +protocol WebRTCEventHandler { + var onSignalingStateChange: ((RTCSignalingState, RTCPeerConnection) -> Void)? { get set } + var onAddStream: ((RTCMediaStream) -> Void)? { get set } + var onRemoveStream: ((RTCMediaStream) -> Void)? { get set } + var onNegotiationNeeded: (() -> Void)? { get set } + var onIceConnectionChange: ((RTCIceConnectionState) -> Void)? { get set } + var onIceGatheringChange: ((RTCIceGatheringState) -> Void)? { get set } + var onIceCandidate: ((RTCIceCandidate) -> Void)? { get set } + var onRemoveIceCandidates: (([RTCIceCandidate]) -> Void)? { get set } + var onDataChannel: ((RTCDataChannel) -> Void)? { get set } +} + diff --git a/TelnyxRTC/Telnyx/WebRTC/Stats/WebRTCStatsEvent.swift b/TelnyxRTC/Telnyx/WebRTC/Stats/WebRTCStatsEvent.swift new file mode 100644 index 00000000..9a2635d4 --- /dev/null +++ b/TelnyxRTC/Telnyx/WebRTC/Stats/WebRTCStatsEvent.swift @@ -0,0 +1,18 @@ +enum WebRTCStatsEvent: String { + case addConnection = "addConnection" + case stats = "stats" + case onIceCandidate = "onicecandidate" + case onTrack = "ontrack" + case onSignalingStateChange = "onsignalingstatechange" + case onIceConnectionStateChange = "oniceconnectionstatechange" + case onIceGatheringStateChange = "onicegatheringstatechange" + case onIceCandidateError = "onicecandidateerror" + case onConnectionStateChange = "onconnectionstatechange" + case onNegotiationNeeded = "onnegotiationneeded" + case onDataChannel = "ondatachannel" + case getUserMedia = "getUserMedia" + case mute = "mute" + case unmute = "unmute" + case overconstrained = "overconstrained" + case ended = "ended" +} diff --git a/TelnyxRTC/Telnyx/WebRTC/Stats/WebRTCStatsReporter.swift b/TelnyxRTC/Telnyx/WebRTC/Stats/WebRTCStatsReporter.swift new file mode 100644 index 00000000..8fa10778 --- /dev/null +++ b/TelnyxRTC/Telnyx/WebRTC/Stats/WebRTCStatsReporter.swift @@ -0,0 +1,285 @@ +import WebRTC + +class WebRTCStatsReporter { + // MARK: - Properties + private var timer: DispatchSourceTimer? + private var peerId: UUID? + private var reportId: UUID = UUID.init() + private weak var peer: Peer? + weak var socket: Socket? + private let messageQueue = DispatchQueue(label: "WebRTCStatsReporter.MessageQueue") + + // MARK: - Initializer + init(socket: Socket) { + self.socket = socket + } + + public func startDebugReport(peerId: UUID, + peer: Peer) { + + self.peerId = peerId + self.peer = peer + self.sendDebugReportStartMessage(id: self.reportId) + + let delay = DispatchTime.now() + 0.2 + DispatchQueue.main.asyncAfter(deadline: delay) { [weak self] in + self?.sendAddConnectionMessage() + } + self.setupEventHandler() + let queue = DispatchQueue.main + timer = DispatchSource.makeTimerSource(queue: queue) + timer?.schedule(deadline: .now(), repeating: 2.0) + timer?.setEventHandler { [weak self] in + self?.executeTask() + } + timer?.resume() + } + + private func stopDebugReport() { + timer?.cancel() + timer = nil + sendDebugReportStopMessage(id: reportId) + } + + // MARK: - Private Helper Methods + private func sendDebugReportStartMessage(id: UUID) { + let statsMessage = DebugReportStartMessage(reportID: id.uuidString.lowercased()) + if let message = statsMessage.encode() { + enqueueMessage(message) + Logger.log.stats(message: "WebRTCStatsReporter:: sendDebugReportStartMessage [\(id.uuidString.lowercased())] message [\(String(describing: message))]") + } else { + Logger.log.e(message: "WebRTCStatsReporter:: sendDebugReportStartMessage error") + } + } + + private func sendDebugReportStopMessage(id: UUID) { + let statsMessage = DebugReportStopMessage(reportID: id.uuidString.lowercased()) + if let message = statsMessage.encode() { + enqueueMessage(message) + Logger.log.stats(message: "WebRTCStatsReporter:: sendDebugReportStopMessage [\(id.uuidString.lowercased())] message [\(message)]") + } else { + Logger.log.e(message: "WebRTCStatsReporter:: sendDebugReportStopMessage error") + } + } + + private func sendDebugReportDataMessage(id: UUID, data: [String: Any]) { + let statsMessage = DebugReportDataMessage(reportID: id.uuidString.lowercased(), reportData: data) + if let message = statsMessage.encode() { + enqueueMessage(message) + Logger.log.stats(message: "WebRTCStatsReporter:: sendDebugReportDataMessage [\(id.uuidString.lowercased())] message [\(String(describing: message))]") + } else { + Logger.log.e(message: "WebRTCStatsReporter:: sendDebugReportDataMessage error") + } + } + + private func sendWebRTCStatsEvent(event: WebRTCStatsEvent, tag: WebRTCStatsTag, data: [String: Any]) { + var reportData = [String: Any]() + reportData["event"] = event.rawValue + reportData["tag"] = tag.rawValue + reportData["connectionId"] = self.peer?.callLegID ?? UUID.init().uuidString.lowercased() + reportData["peerId"] = peerId?.uuidString.lowercased() ?? UUID.init().uuidString.lowercased() + reportData["data"] = data + self.sendDebugReportDataMessage(id: reportId, data: reportData) + } + + private func sendAddConnectionMessage() { + var data = [String : Any]() + data["event"] = WebRTCStatsEvent.addConnection.rawValue + data["tag"] = WebRTCStatsTag.peer.rawValue + + data["connectionId"] = self.peer?.callLegID ?? UUID.init().uuidString.lowercased() + data["peerId"] = peerId?.uuidString.lowercased() ?? UUID.init().uuidString.lowercased() + + var debugData = [String: Any]() + var options = [String: Any]() + options["peerId"] = peerId?.uuidString.lowercased() ?? UUID.init().uuidString.lowercased() + + if let connection = peer?.connection { + debugData["peerConfiguration"] = connection.configuration.telnyx_to_stats_dictionary() + } + + debugData["options"] = options + data["data"] = debugData + self.sendDebugReportDataMessage(id: reportId, data: data) + } + + + // MARK: - Task Execution + private func executeTask() { + guard let peer = peer else { return } + Logger.log.i(message: "WebRTCStatsReporter:: Task executed at \(Date())") + peer.connection?.statistics(completionHandler: { [weak self] reports in + guard let self = self else { return } + var statsEvent = [String: Any]() + var audioInboundStats = [Any]() + var audioOutboundStats = [Any]() + var connectionCandidates = [Any]() + var statsData = [String: Any]() + var statsObject = [String: Any]() + Logger.log.i(message: "WebRTCStatsReporter:: Task executed at \(reports.statistics)") + + reports.statistics.forEach { report in + var values = report.value.values + values["type"] = report.value.type as NSObject + values["id"] = report.value.id as NSObject + values["timestamp"] = (report.value.timestamp_us / 1000.0) as NSObject + + switch report.value.type { + case "inbound-rtp": + if let kind = values["kind"] as? String, kind == "audio" { + audioInboundStats.append(values) + statsObject[report.key] = values + } + + case "outbound-rtp": + if let kind = values["kind"] as? String, kind == "audio" { + audioOutboundStats.append(values) + statsObject[report.key] = values + } + + case "candidate-pair": + connectionCandidates.append(values) + statsObject[report.key] = values + + default: + statsObject[report.key] = values + } + } + + // Otbound Stats + audioOutboundStats.enumerated().forEach { (index, outboundStat) in + if let outboundDict = outboundStat as? [String: NSObject], + let mediaSourceId = outboundDict["mediaSourceId"] as? String, + let mediaSource = statsObject[mediaSourceId] as? [String: NSObject] { + var updatedStat = outboundDict + var updatedMediaSource = mediaSource + updatedMediaSource["id"] = mediaSourceId as NSObject + updatedStat["track"] = updatedMediaSource as NSObject + audioOutboundStats[index] = updatedStat as NSDictionary + } + } + + // Retrieve the T01 stats and selectedCandidatePairId from the statsObject + if let t01Stats = statsObject["T01"] as? [String: NSObject], + let selectedCandidatePairId = t01Stats["selectedCandidatePairId"] as? String { + // Find the corresponding candidate pair based on the selectedCandidatePairId + if let connectionCandidateMap = connectionCandidates.first(where: { candidate in + if let candidateDict = candidate as? [String: NSObject], + let id = candidateDict["id"] as? String { + // Match the candidate id with the selectedCandidatePairId + return id == selectedCandidatePairId + } + return false + }) as? [String: NSObject] { + var updatedConnection = connectionCandidateMap + + // Search for local and remote candidates using their respective ids + if let localId = connectionCandidateMap["localCandidateId"] as? String, + let local = statsObject[localId] as? [String: NSObject] { + // Update the local candidate data + var updatedLocal = local + updatedLocal["id"] = localId as NSObject + updatedConnection["local"] = updatedLocal as NSObject + } + + if let remoteId = connectionCandidateMap["remoteCandidateId"] as? String, + let remote = statsObject[remoteId] as? [String: NSObject] { + // Update the remote candidate data + var updatedRemote = remote + updatedRemote["id"] = remoteId as NSObject + updatedConnection["remote"] = updatedRemote as NSObject + } + // Add the updated connection candidate to the stats data + statsData["connection"] = updatedConnection + } + } + + // Event Object + statsEvent["event"] = WebRTCStatsEvent.stats.rawValue as NSObject + statsEvent["tag"] = WebRTCStatsTag.stats.rawValue as NSObject + statsEvent["peerId"] = self.peerId?.uuidString as NSObject? ?? NSNull() + statsEvent["connectionId"] = peer.callLegID as NSObject? ?? NSNull() + + statsData["audio"] = [ + "inbound": audioInboundStats, + "outbound": audioOutboundStats + ] as NSObject + statsEvent["data"] = statsData as NSObject + statsEvent["statsObject"] = statsObject as NSObject + + self.sendDebugReportDataMessage(id: self.reportId, data: statsEvent) + }) + } + + // MARK: - Message Queue + private func enqueueMessage(_ message: String) { + messageQueue.async { [weak self] in + self?.socket?.sendMessage(message: message) + } + } +} + +// MARK: - Dispose +extension WebRTCStatsReporter { + public func dispose() { + self.stopDebugReport() + timer?.cancel() + timer = nil + + peerId = nil + peer = nil + socket = nil + Logger.log.i(message: "WebRTCStatsReporter:: Disposed and resources cleared") + } +} + +// MARK: - Peer Event Handling +extension WebRTCStatsReporter { + + public func setupEventHandler() { + self.peer?.onAddStream = { [weak self] stream in + guard let self = self else { return } + var debugData = [String: Any]() + debugData["stream"] = stream.telnyx_to_stats_dictionary() + if let track = stream.audioTracks.first { + debugData["track"] = track.telnyx_to_stats_dictionary() + debugData["title"] = track.kind + ":" + track.trackId + " stream:" + stream.streamId + } + self.sendWebRTCStatsEvent(event: .onTrack, tag: .track, data: debugData) + } + + self.peer?.onIceCandidate = { [weak self] candidate in + guard let self = self else { return } + var debugCandidate = [String: Any]() + debugCandidate["candidate"] = candidate.sdp + debugCandidate["sdpMLineIndex"] = candidate.sdpMLineIndex + debugCandidate["sdpMid"] = candidate.sdpMid + debugCandidate["usernameFragment"] = candidate.telnyx_stats_extractUfrag() + self.sendWebRTCStatsEvent(event: .onIceCandidate, tag: .connection, data: debugCandidate) + } + + self.peer?.onSignalingStateChange = { [weak self] state, connection in + guard let self = self else { return } + var debugData = [String: Any]() + debugData["signalingState"] = state.telnyx_to_string() + debugData["localDescription"] = connection.localDescription?.sdp ?? "" + debugData["remoteDescription"] = connection.remoteDescription?.sdp ?? "" + self.sendWebRTCStatsEvent(event: .onSignalingStateChange, tag: .connection, data: debugData) + } + + self.peer?.onIceConnectionChange = { [weak self] state in + guard let self = self else { return } + self.sendWebRTCStatsEvent(event: .onIceConnectionStateChange, tag: .connection, data: ["data": state.telnyx_to_string()]) + } + + self.peer?.onIceGatheringChange = { [weak self] state in + guard let self = self else { return } + self.sendWebRTCStatsEvent(event: .onIceGatheringStateChange, tag: .connection, data: ["data": state.telnyx_to_string()]) + } + + self.peer?.onNegotiationNeeded = { [weak self] in + guard let self = self else { return } + self.sendWebRTCStatsEvent(event: .onNegotiationNeeded, tag: .connection, data: [:]) + } + } +} diff --git a/TelnyxRTC/Telnyx/WebRTC/Stats/WebRTCStatsTag.swift b/TelnyxRTC/Telnyx/WebRTC/Stats/WebRTCStatsTag.swift new file mode 100644 index 00000000..3fea3092 --- /dev/null +++ b/TelnyxRTC/Telnyx/WebRTC/Stats/WebRTCStatsTag.swift @@ -0,0 +1,8 @@ +enum WebRTCStatsTag: String { + case peer = "peer" + case stats = "stats" + case connection = "connection" + case track = "track" + case dataChannel = "datachannel" + case getUserMedia = "getUserMedia" +} diff --git a/TelnyxWebRTCDemo/Extensions/AppDelegateCallKitExtension.swift b/TelnyxWebRTCDemo/Extensions/AppDelegateCallKitExtension.swift index f6f0965c..85a63f71 100644 --- a/TelnyxWebRTCDemo/Extensions/AppDelegateCallKitExtension.swift +++ b/TelnyxWebRTCDemo/Extensions/AppDelegateCallKitExtension.swift @@ -167,13 +167,11 @@ extension AppDelegate : CXProviderDelegate { } } action.fulfill() - self.currentCall?.startDebugStats() } func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) { print("AppDelegate:: ANSWER call action: callKitUUID [\(String(describing: self.callKitUUID))] action [\(action.callUUID)]") self.telnyxClient?.answerFromCallkit(answerAction: action, customHeaders: ["X-test-answer":"ios-test"]) - self.currentCall?.startDebugStats() } func provider(_ provider: CXProvider, perform action: CXEndCallAction) { @@ -266,7 +264,9 @@ extension AppDelegate : CXProviderDelegate { ringBackTone: "ringback_tone.mp3", //You can choose the appropriate verbosity level of the SDK. logLevel: .all, - reconnectClient: true) + reconnectClient: true, + // Enable WebRTC stats debug + debug: true) do { try telnyxClient?.processVoIPNotification(txConfig: txConfig, serverConfiguration: serverConfig,pushMetaData: pushMetaData) diff --git a/TelnyxWebRTCDemo/ViewController.swift b/TelnyxWebRTCDemo/ViewController.swift index b7f764cb..dbe8f935 100644 --- a/TelnyxWebRTCDemo/ViewController.swift +++ b/TelnyxWebRTCDemo/ViewController.swift @@ -221,7 +221,9 @@ class ViewController: UIViewController { ringBackTone: "ringback_tone.mp3", pushEnvironment: .production, //You can choose the appropriate verbosity level of the SDK. - logLevel: .all) + logLevel: .all, + // Enable webrtc stats debug + debug: true) } else { // To obtain SIP credentials, please go to https://portal.telnyx.com guard let sipUser = self.settingsView.sipUsernameLabel.text, !sipUser.isEmpty, @@ -231,14 +233,15 @@ class ViewController: UIViewController { } txConfig = TxConfig(sipUser: sipUser, - password: password, - pushDeviceToken: deviceToken, - ringtone: "incoming_call.mp3", - ringBackTone: "ringback_tone.mp3", - //You can choose the appropriate verbosity level of the SDK. - logLevel: .all, - reconnectClient: true - ) + password: password, + pushDeviceToken: deviceToken, + ringtone: "incoming_call.mp3", + ringBackTone: "ringback_tone.mp3", + //You can choose the appropriate verbosity level of the SDK. + logLevel: .all, + reconnectClient: true, + // Enable webrtc stats debug + debug: true) //store user / password in user defaults let selectedCredential = SipCredential(username: sipUser, password: password)