From 226d321368c37db344099ddd08111ff2e43a8be9 Mon Sep 17 00:00:00 2001 From: openhands Date: Fri, 31 Jan 2025 15:34:20 +0000 Subject: [PATCH] WEBRTC-2470: Improve iOS SDK documentation - Enhanced Call class documentation with better examples and explanations - Improved Peer class documentation with detailed audio session handling - Added comprehensive documentation for TxClientDelegate protocol - Enhanced TxClient audio session and route change documentation - Added detailed documentation for WebRTCStatsReporter --- TelnyxRTC/Telnyx/TxClient.swift | 69 +++++++- TelnyxRTC/Telnyx/TxClientDelegate.swift | 74 ++++++--- TelnyxRTC/Telnyx/WebRTC/Call.swift | 155 ++++++++++++------ TelnyxRTC/Telnyx/WebRTC/Peer.swift | 74 +++++++-- .../WebRTC/Stats/WebRTCStatsReporter.swift | 53 +++++- 5 files changed, 330 insertions(+), 95 deletions(-) diff --git a/TelnyxRTC/Telnyx/TxClient.swift b/TelnyxRTC/Telnyx/TxClient.swift index 6fa5fb0a..a3d9d61b 100644 --- a/TelnyxRTC/Telnyx/TxClient.swift +++ b/TelnyxRTC/Telnyx/TxClient.swift @@ -161,9 +161,25 @@ public class TxClient { } } - /// When implementing CallKit framework, audio has to be manually handled. - /// Set this property to TRUE when `provider(CXProvider, didActivate: AVAudioSession)` is called on your CallKit implementation - /// Set this property to FALSE when `provider(CXProvider, didDeactivate: AVAudioSession)` is called on your CallKit implementation + /// Controls the audio device state when using CallKit integration. + /// This property manages the WebRTC audio session activation and deactivation. + /// + /// When implementing CallKit, you must manually handle the audio session state: + /// - Set to `true` in `provider(_:didActivate:)` to enable audio + /// - Set to `false` in `provider(_:didDeactivate:)` to disable audio + /// + /// Example usage with CallKit: + /// ```swift + /// extension CallKitProvider: CXProviderDelegate { + /// func provider(_ provider: CXProvider, didActivate audioSession: AVAudioSession) { + /// telnyxClient.isAudioDeviceEnabled = true + /// } + /// + /// func provider(_ provider: CXProvider, didDeactivate audioSession: AVAudioSession) { + /// telnyxClient.isAudioDeviceEnabled = false + /// } + /// } + /// ``` public var isAudioDeviceEnabled : Bool { get { return RTCAudioSession.sharedInstance().isAudioEnabled @@ -178,17 +194,28 @@ public class TxClient { } } - - public func enableAudioSession(audioSession: AVAudioSession){ + /// Enables and configures the audio session for a call. + /// This method sets up the appropriate audio configuration and activates the session. + /// + /// - Parameter audioSession: The AVAudioSession instance to configure + /// - Important: This method should be called when starting a call or when audio is needed + public func enableAudioSession(audioSession: AVAudioSession) { setupCorrectAudioConfiguration() setAudioSessionActive(true) } - public func disableAudioSession(audioSession: AVAudioSession){ + /// Disables and resets the audio session. + /// This method cleans up the audio configuration and deactivates the session. + /// + /// - Parameter audioSession: The AVAudioSession instance to reset + /// - Important: This method should be called when ending a call or when audio is no longer needed + public func disableAudioSession(audioSession: AVAudioSession) { resetAudioConfiguration() setAudioSessionActive(false) } + /// The current audio route configuration. + /// This provides information about the active input and output ports. let currentRoute = AVAudioSession.sharedInstance().currentRoute /// Client must be registered in order to receive or place calls. @@ -208,6 +235,13 @@ public class TxClient { setupAudioRouteChangeMonitoring() } + /// Sets up monitoring for audio route changes (e.g., headphones connected/disconnected, + /// Bluetooth device connected/disconnected). + /// + /// This method registers for AVAudioSession route change notifications to: + /// - Track when audio devices are connected or disconnected + /// - Monitor changes in the active audio output + /// - Update the speaker state accordingly private func setupAudioRouteChangeMonitoring() { NotificationCenter.default.addObserver( self, @@ -216,6 +250,23 @@ public class TxClient { object: nil) } + /// Handles audio route change notifications from the system. + /// + /// This method processes audio route changes and: + /// - Updates the internal speaker state + /// - Notifies observers about audio route changes + /// - Manages audio routing between available outputs + /// + /// The method posts an "AudioRouteChanged" notification with: + /// - isSpeakerEnabled: Whether the built-in speaker is active + /// - outputPortType: The type of the current audio output port + /// + /// Common route change reasons handled: + /// - .categoryChange: Audio session category was changed + /// - .override: Route was overridden by the system or user + /// - .routeConfigurationChange: Available routes were changed + /// + /// @objc attribute is required for NotificationCenter selector @objc private func handleAudioRouteChange(notification: Notification) { guard let userInfo = notification.userInfo, let reasonValue = userInfo[AVAudioSessionRouteChangeReasonKey] as? UInt, @@ -226,7 +277,7 @@ public class TxClient { let session = AVAudioSession.sharedInstance() let currentRoute = session.currentRoute - // Check if we have any output ports + // Ensure we have at least one output port guard let output = currentRoute.outputs.first else { return } @@ -235,11 +286,11 @@ public class TxClient { switch reason { case .categoryChange, .override, .routeConfigurationChange: - // Update speaker state based on current output + // Update internal speaker state based on current output let isSpeaker = output.portType == .builtInSpeaker _isSpeakerEnabled = isSpeaker - // Notify UI of the change + // Notify observers about the route change NotificationCenter.default.post( name: NSNotification.Name("AudioRouteChanged"), object: nil, diff --git a/TelnyxRTC/Telnyx/TxClientDelegate.swift b/TelnyxRTC/Telnyx/TxClientDelegate.swift index aff88458..5bcbc6cc 100644 --- a/TelnyxRTC/Telnyx/TxClientDelegate.swift +++ b/TelnyxRTC/Telnyx/TxClientDelegate.swift @@ -8,47 +8,77 @@ import Foundation -/// Delegate protocol asociated with the TxClient -/// Methods for receiving TxClient events. +/// The TxClientDelegate protocol defines methods for receiving events and updates from a TxClient instance. +/// Implement this protocol to handle various states and events in your WebRTC-enabled application, +/// including connection status, call state changes, and push notifications. +/// +/// ## Usage Example: +/// ```swift +/// class CallHandler: TxClientDelegate { +/// func onSocketConnected() { +/// print("Connected to Telnyx backend") +/// } +/// +/// func onIncomingCall(call: Call) { +/// // Handle incoming call +/// call.answer() +/// } +/// +/// // Implement other required methods... +/// } +/// ``` public protocol TxClientDelegate: AnyObject { - /// Tells the delegate when the Telnyx Client has successfully connected to the Telnyx Backend + /// Called when the WebSocket connection to Telnyx's backend is established. + /// This indicates a successful network connection, but the client may not be fully ready yet. + /// Wait for `onClientReady` before initiating calls. func onSocketConnected() - /// Tells the delegate when the Telnyx Client has disconnected from the Telnyx Backend + /// Called when the WebSocket connection to Telnyx's backend is lost or closed. + /// The client will automatically attempt to reconnect unless explicitly disconnected. func onSocketDisconnected() - /// Tells the delegate when there's an error in the Telnyx Client - /// - Parameter error: error occurred inside the Telnyx Client + /// Called when an error occurs in the TxClient. + /// - Parameter error: The error that occurred. Check the error type and message for details. + /// Common errors include authentication failures and network connectivity issues. func onClientError(error: Error) - /// Tells the delegate that the The Telnyx Client is ready to be used. - /// Has successfully connected and logged in + /// Called when the client has successfully connected AND authenticated. + /// The client is now ready to make and receive calls. + /// This is the appropriate time to enable UI elements for calling functionality. func onClientReady() + /// Called when push notification status changes for the current user. + /// - Parameters: + /// - success: Whether the push notification operation succeeded + /// - message: Descriptive message about the operation result + func onPushDisabled(success: Bool, message: String) - /// Push notification is disabled for the current user - func onPushDisabled(success:Bool,message:String) - - /// Tells the delegate that the Telnyx Client session has been updated. - /// - Parameter sessionId: The new sessionId assigned to the client connection. + /// Called when the client's session is updated, typically after a reconnection. + /// - Parameter sessionId: The new session identifier for the connection. + /// Store this ID if you need to track or debug connection issues. func onSessionUpdated(sessionId: String) - /// Tells the delegate that a call has been updated. + /// Called whenever a call's state changes (e.g., ringing, answered, ended). /// - Parameters: - /// - callState: The new call state - /// - callId: The UUID of the affected call + /// - callState: The new state of the call (NEW, CONNECTING, RINGING, ACTIVE, HELD, DONE) + /// - callId: The unique identifier of the affected call + /// Use this to update your UI to reflect the current call state. func onCallStateUpdated(callState: CallState, callId: UUID) - /// Tells the delegate that someone is calling - /// - Parameter call: The call object of the incoming call. + /// Called when a new incoming call is received. + /// - Parameter call: The Call object representing the incoming call. + /// You can use this object to answer or reject the call. func onIncomingCall(call: Call) - /// Tells the delegate that a call has ended - /// - Parameter callId: the UUID of the call that has ended. + /// Called when a remote party ends the call. + /// - Parameter callId: The unique identifier of the ended call. + /// Use this to clean up any call-related UI elements or state. func onRemoteCallEnded(callId: UUID) - /// Tells the delegate that an INVITE has been received for the incoming push - /// - Parameter call: The call object of the incoming call. + /// Called when a push notification triggers an incoming call. + /// - Parameter call: The Call object created from the push notification data. + /// This is specifically for handling calls that arrive via push notifications + /// when the app is in the background. func onPushCall(call: Call) } diff --git a/TelnyxRTC/Telnyx/WebRTC/Call.swift b/TelnyxRTC/Telnyx/WebRTC/Call.swift index 5b1120f8..3922d49e 100644 --- a/TelnyxRTC/Telnyx/WebRTC/Call.swift +++ b/TelnyxRTC/Telnyx/WebRTC/Call.swift @@ -43,51 +43,65 @@ protocol CallProtocol: AnyObject { } -/// A Call is the representation of an audio or video call between two WebRTC Clients, SIP clients or phone numbers. -/// The call object is created whenever a new call is initiated, either by you or the remote caller. -/// You can access and act upon calls initiated by a remote caller by registering to TxClientDelegate of the TxClient +/// A Call represents an audio or video communication session between two endpoints: WebRTC Clients, SIP clients, or phone numbers. +/// The Call object manages the entire lifecycle of a call, from initiation to termination, handling both outbound and inbound calls. /// -/// ## Examples: -/// ### Create a call: +/// A Call object is created in two scenarios: +/// 1. When you initiate a new outbound call using TxClient's newCall method +/// 2. When you receive an inbound call through the TxClientDelegate's onIncomingCall callback /// -/// ``` -/// // Create a client instance -/// self.telnyxClient = TxClient() +/// ## Key Features +/// - Audio and video call support +/// - Call state management (NEW, CONNECTING, RINGING, ACTIVE, HELD, DONE) +/// - Mute/unmute functionality +/// - DTMF tone sending +/// - Custom headers support for both INVITE and ANSWER messages +/// - Call statistics reporting when debug mode is enabled /// -/// // Asign the delegate to get SDK events +/// ## Examples +/// ### Creating an Outbound Call: +/// ```swift +/// // Initialize the client +/// self.telnyxClient = TxClient() /// self.telnyxClient?.delegate = self /// -/// // Connect the client (Check TxClient class for more info) +/// // Connect the client (see TxClient documentation for connection options) /// self.telnyxClient?.connect(....) /// -/// // Create the call and start calling -/// self.currentCall = try self.telnyxClient?.newCall(callerName: "Caller name", -/// callerNumber: "155531234567", -/// // Destination is required and can be a phone number or SIP URI -/// destinationNumber: "18004377950", -/// callId: UUID.init()) +/// // Create and initiate a call +/// self.currentCall = try self.telnyxClient?.newCall( +/// callerName: "John Doe", // The name to display for the caller +/// callerNumber: "155531234567", // The caller's phone number +/// destinationNumber: "18004377950", // The target phone number or SIP URI +/// callId: UUID.init(), // Unique identifier for the call +/// clientState: nil, // Optional client state information +/// customHeaders: [:] // Optional custom SIP headers +/// ) /// ``` /// -/// ### Answer an incoming call: -/// ``` -/// //Init your client -/// func initTelnyxClient() { -/// // -/// self.telnyxClient = TxClient() -/// -/// // Asign the delegate to get SDK events -/// self.telnyxClient?.delegate = self +/// ### Handling an Incoming Call: +/// ```swift +/// class CallHandler: TxClientDelegate { +/// var activeCall: Call? /// -/// // Connect the client (Check TxClient class for more info) -/// self.telnyxClient?.connect(....) -/// } +/// func initTelnyxClient() { +/// let client = TxClient() +/// client.delegate = self +/// client.connect(....) +/// } /// -/// extension ViewController: TxClientDelegate { -/// //.... /// func onIncomingCall(call: Call) { -/// //We are automatically answering any incoming call as an example, but -/// //maybe you want to store a reference of the call, and answer the call after a button press. -/// self.myCall = call.answer() +/// // Store the call reference +/// self.activeCall = call +/// +/// // Option 1: Auto-answer the call +/// call.answer() +/// +/// // Option 2: Answer with custom headers +/// call.answer(customHeaders: ["X-Custom-Header": "Value"]) +/// +/// // Option 3: Reject the call +/// // call.hangup() /// } /// } /// ``` @@ -104,29 +118,58 @@ public class Call { var statsReporter: WebRTCStatsReporter? - /// Custum headers pased /from webrtc telnyx_rtc.INVITE Messages + /// Custom headers received from the WebRTC INVITE message. + /// These headers are passed during call initiation and can contain application-specific information. + /// Format should be ["X-Header-Name": "Value"] where header names must start with "X-". public internal(set) var inviteCustomHeaders: [String:String]? - /// Custum headers pased tfrom telnyx_rtc.ANSWER webrtcMessages + /// Custom headers received from the WebRTC ANSWER message. + /// These headers are passed during call acceptance and can contain application-specific information. + /// Format should be ["X-Header-Name": "Value"] where header names must start with "X-". public internal(set) var answerCustomHeaders: [String:String]? - /// The Session ID of the current connection + /// The unique session identifier for the current WebRTC connection. + /// This ID is established during client connection and remains constant for the session duration. public internal(set) var sessionId: String? - /// Telnyx call session ID. + + /// The unique Telnyx session identifier for this call. + /// This ID can be used to track the call in Telnyx's systems and logs. public internal(set) var telnyxSessionId: UUID? - /// Telnyx call leg ID + + /// The unique Telnyx leg identifier for this call. + /// A call can have multiple legs (e.g., in call transfers). This ID identifies this specific leg. public internal(set) var telnyxLegId: UUID? - /// To enable call stats + + /// Enables WebRTC statistics reporting for debugging purposes. + /// When true, the SDK will collect and send WebRTC statistics to Telnyx servers. + /// This is useful for troubleshooting call quality issues. public internal(set) var debug: Bool = false // MARK: - Properties - /// `TxCallInfo` Contains the required information of the current Call. + /// Contains essential information about the current call including: + /// - callId: Unique identifier for this call + /// - callerName: Display name of the caller + /// - callerNumber: Phone number or SIP URI of the caller + /// See `TxCallInfo` for complete details. public var callInfo: TxCallInfo? - /// `CallState` The actual state of the Call. + + /// The current state of the call. Possible values: + /// - NEW: Call object created but not yet initiated + /// - CONNECTING: Outbound call is being established + /// - RINGING: Incoming call waiting to be answered + /// - ACTIVE: Call is connected and media is flowing + /// - HELD: Call is temporarily suspended + /// - DONE: Call has ended + /// + /// The state changes are notified through the `CallProtocol` delegate. public var callState: CallState = .NEW - /// `isMuted` Indicates if the call audio is muted based on the peer's audio track state. + /// Indicates whether the local audio is currently muted. + /// - Returns: `true` if the call is muted (audio track disabled) + /// - Returns: `false` if the call is not muted (audio track enabled) + /// + /// Use `muteAudio()` and `unmuteAudio()` to change the mute state. public var isMuted: Bool { return !(peer?.isAudioTrackEnabled ?? false) } @@ -465,17 +508,29 @@ extension Call { // MARK: - DTMF extension Call { - /// Sends dual-tone multi-frequency (DTMF) signal - /// - Parameter dtmf: Single DTMF key + /// Sends a DTMF (Dual-Tone Multi-Frequency) signal during an active call. + /// DTMF signals are used to send digits and symbols over a phone line, typically + /// for interacting with automated systems, voicemail, or IVR menus. + /// + /// - Parameter dtmf: A string containing a single DTMF character. Valid characters are: + /// - Digits: 0-9 + /// - Special characters: * (asterisk), # (pound) + /// - Letters: A-D (less commonly used) + /// /// ## Examples: - /// ### Send DTMF signals: + /// ```swift + /// // Navigate an IVR menu + /// currentCall?.dtmf("1") // Select option 1 + /// currentCall?.dtmf("0") // Select option 0 /// + /// // Special characters + /// currentCall?.dtmf("*") // Send asterisk + /// currentCall?.dtmf("#") // Send pound/hash /// ``` - /// currentCall?.dtmf("0") - /// currentCall?.dtmf("1") - /// currentCall?.dtmf("*") - /// currentCall?.dtmf("#") - /// ``` + /// + /// Note: The call must be in ACTIVE state for DTMF signals to be sent successfully. + /// Each DTMF tone should be sent individually with appropriate timing between tones + /// when sending multiple digits. public func dtmf(dtmf: String) { Logger.log.i(message: "Call:: dtmf() \(dtmf)") guard let sessionId = self.sessionId, diff --git a/TelnyxRTC/Telnyx/WebRTC/Peer.swift b/TelnyxRTC/Telnyx/WebRTC/Peer.swift index 0037ad01..08928f3c 100644 --- a/TelnyxRTC/Telnyx/WebRTC/Peer.swift +++ b/TelnyxRTC/Telnyx/WebRTC/Peer.swift @@ -13,15 +13,34 @@ protocol PeerDelegate: AnyObject { func onNegotiationEnded(sdp: RTCSessionDescription?) } +/// The Peer class manages WebRTC peer connections, handling audio/video streams and ICE negotiation. +/// It provides functionality for: +/// - Setting up and managing WebRTC peer connections +/// - Handling audio and video tracks +/// - Managing ICE candidates and negotiation +/// - Controlling media state (mute/unmute) +/// - Monitoring connection state changes class Peer : NSObject, WebRTCEventHandler { + /// Queue for handling audio operations to ensure thread safety private let audioQueue = DispatchQueue(label: "audio") - private let NEGOTIATION_TIMOUT = 0.3 //time in milliseconds + + /// Timeout duration for ICE negotiation in milliseconds + private let NEGOTIATION_TIMOUT = 0.3 + + /// Identifier for the audio track in WebRTC connection private let AUDIO_TRACK_ID = "audio0" + + /// Identifier for the video track in WebRTC connection private let VIDEO_TRACK_ID = "video0" - //TODO: REMOVE THIS FOR V1 + + /// Local video file used for testing in simulator environment private let VIDEO_DEMO_LOCAL_VIDEO = "local_video_streaming.mp4" + + /// Collection of gathered ICE candidates during connection setup private var gatheredICECandidates: [String] = [] + + /// Socket connection for signaling with the WebRTC server var socket: Socket? @@ -123,9 +142,22 @@ class Peer : NSObject, WebRTCEventHandler { self.muteUnmuteAudio(mute: false) } - /** - iOS specific: we need to configure the device AudioSession. - */ + /// Configures the iOS device's audio session for optimal WebRTC call handling. + /// This setup is crucial for proper audio routing and behavior during calls. + /// + /// The configuration includes: + /// - Setting up manual audio control for precise handling + /// - Configuring the audio session for VoIP calls + /// - Enabling Bluetooth device support + /// - Setting up audio mixing behavior with other apps + /// + /// Audio session options: + /// - `.allowBluetoothA2DP`: Enables high-quality Bluetooth audio + /// - `.duckOthers`: Reduces other apps' audio volume during calls + /// - `.allowBluetooth`: Enables classic Bluetooth headset support + /// - `.mixWithOthers`: Allows mixing with audio from other apps + /// + /// This method runs asynchronously on a dedicated audio queue to prevent blocking. internal func configureAudioSession() { self.audioQueue.async { [weak self] in guard let self = self else { @@ -139,13 +171,13 @@ class Peer : NSObject, WebRTCEventHandler { try rtcAudioSession.setCategory(AVAudioSession.Category.playAndRecord, mode: AVAudioSession.Mode.voiceChat, options: [ - .allowBluetoothA2DP, - .duckOthers, - .allowBluetooth, - .mixWithOthers + .allowBluetoothA2DP, // Enable high-quality Bluetooth audio + .duckOthers, // Reduce other apps' volume + .allowBluetooth, // Enable Bluetooth headsets + .mixWithOthers // Allow mixing with other audio ]) - Logger.log.i(message: "Peer:: Configuring AVAudioSession configured") + Logger.log.i(message: "Peer:: AVAudioSession configured successfully") } catch let error { Logger.log.e(message: "Peer:: Error changing AVAudioSession category: \(error.localizedDescription)") } @@ -319,14 +351,30 @@ extension Peer { // MARK: - Audio handling extension Peer { + /// Controls the mute state of the local audio track in the WebRTC connection. + /// + /// This method handles audio track state changes for both legacy (Plan B) and modern (Unified Plan) + /// WebRTC implementations. It properly manages the audio track state based on the connection's + /// SDP semantics. + /// + /// - Parameter mute: Boolean flag to control audio state + /// - `true`: Mutes the local audio (disables the audio track) + /// - `false`: Unmutes the local audio (enables the audio track) + /// + /// Implementation details: + /// - For Plan B semantics: Uses the connection's senders to find and modify the audio track + /// - For Unified Plan: Uses transceivers to manage the audio track state + /// + /// Note: This method is used internally by the Call class through its public `muteAudio()` + /// and `unmuteAudio()` methods. func muteUnmuteAudio(mute: Bool) { - //GetTransceivers is only supported with Unified Plan SdpSemantics. - //PlanB doesn't have support to access transeivers, so we need to use the storedAudio track + // GetTransceivers is only supported with Unified Plan SdpSemantics. + // PlanB doesn't have support to access transeivers, so we need to use the stored audio track if self.connection?.configuration.sdpSemantics == .planB { self.connection?.senders .compactMap { return $0.track as? RTCAudioTrack } // Search for Audio track .forEach { - $0.isEnabled = !mute // disable RTCAudioTrack + $0.isEnabled = !mute // disable/enable RTCAudioTrack } } else { self.setTrackEnabled(RTCAudioTrack.self, isEnabled: !mute) diff --git a/TelnyxRTC/Telnyx/WebRTC/Stats/WebRTCStatsReporter.swift b/TelnyxRTC/Telnyx/WebRTC/Stats/WebRTCStatsReporter.swift index f599f571..97d1b6bf 100644 --- a/TelnyxRTC/Telnyx/WebRTC/Stats/WebRTCStatsReporter.swift +++ b/TelnyxRTC/Telnyx/WebRTC/Stats/WebRTCStatsReporter.swift @@ -1,13 +1,48 @@ import WebRTC import Foundation +/// The WebRTCStatsReporter class collects and reports WebRTC statistics and events +/// to Telnyx's servers for debugging and quality monitoring purposes. +/// +/// This reporter tracks various aspects of the WebRTC connection including: +/// - Audio statistics (inbound and outbound) +/// - ICE candidate information +/// - Connection state changes +/// - Media track events +/// - Network statistics +/// +/// The reporter is enabled when the `debug` flag is set to true in the Call configuration. +/// Statistics are collected every 2 seconds and sent to Telnyx's servers for analysis. +/// +/// ## Usage +/// ```swift +/// // Create a reporter instance +/// let reporter = WebRTCStatsReporter(socket: socket) +/// +/// // Start reporting for a specific peer +/// reporter.startDebugReport(peerId: callId, peer: peerConnection) +/// +/// // Stop reporting when done +/// reporter.dispose() +/// ``` class WebRTCStatsReporter { // MARK: - Properties + /// Timer for periodic stats collection private var timer: DispatchSourceTimer? + + /// Unique identifier for the peer connection being monitored private var peerId: UUID? + + /// Unique identifier for this reporting session private var reportId: UUID = UUID.init() + + /// Reference to the peer connection being monitored private weak var peer: Peer? + + /// Socket connection for sending stats to Telnyx servers weak var socket: Socket? + + /// Queue for handling message sending to avoid blocking the main thread private let messageQueue = DispatchQueue(label: "WebRTCStatsReporter.MessageQueue") // MARK: - Initializer @@ -236,8 +271,19 @@ extension WebRTCStatsReporter { // MARK: - Peer Event Handling extension WebRTCStatsReporter { - + /// Sets up handlers for various WebRTC events to collect debugging information. + /// This method configures callbacks for: + /// - Media stream and track events + /// - ICE candidate gathering and selection + /// - Signaling state changes + /// - Connection state changes + /// - ICE gathering state changes + /// - Negotiation events + /// + /// Each event handler collects relevant data and sends it to Telnyx's servers + /// for analysis and debugging purposes. public func setupEventHandler() { + // Handle new media streams being added to the connection self.peer?.onAddStream = { [weak self] stream in guard let self = self else { return } var debugData = [String: Any]() @@ -249,6 +295,7 @@ extension WebRTCStatsReporter { self.sendWebRTCStatsEvent(event: .onTrack, tag: .track, data: debugData) } + // Handle new ICE candidates being discovered self.peer?.onIceCandidate = { [weak self] candidate in guard let self = self else { return } var debugCandidate = [String: Any]() @@ -259,6 +306,7 @@ extension WebRTCStatsReporter { self.sendWebRTCStatsEvent(event: .onIceCandidate, tag: .connection, data: debugCandidate) } + // Handle changes in the WebRTC signaling state self.peer?.onSignalingStateChange = { [weak self] state, connection in guard let self = self else { return } var debugData = [String: Any]() @@ -268,16 +316,19 @@ extension WebRTCStatsReporter { self.sendWebRTCStatsEvent(event: .onSignalingStateChange, tag: .connection, data: debugData) } + // Handle changes in the ICE connection state 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()]) } + // Handle changes in the ICE gathering state 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()]) } + // Handle WebRTC negotiation needed events self.peer?.onNegotiationNeeded = { [weak self] in guard let self = self else { return } self.sendWebRTCStatsEvent(event: .onNegotiationNeeded, tag: .connection, data: [:])