/// <summary> /// Sets the nominated checklist entry. This action completes the checklist processing and /// indicates the connection checks were successful. /// </summary> /// <param name="entry">The checklist entry that was nominated.</param> private void SetNominatedEntry(ChecklistEntry entry) { entry.Nominated = true; _checklistState = ChecklistState.Completed; NominatedCandidate = entry.RemoteCandidate; ConnectionState = RTCIceConnectionState.connected; OnIceConnectionStateChange?.Invoke(RTCIceConnectionState.connected); }
/// <summary> /// We've been given the green light to start the ICE candidate gathering process. /// This could include contacting external STUN and TURN servers. Events will /// be fired as each ICE is identified and as the gathering state machine changes /// state. /// </summary> public void StartGathering() { GatheringState = RTCIceGatheringState.gathering; OnIceGatheringStateChange?.Invoke(RTCIceGatheringState.gathering); ConnectionState = RTCIceConnectionState.checking; OnIceConnectionStateChange?.Invoke(RTCIceConnectionState.checking); _stunChecksTimer = new Timer(SendStunConnectivityChecks, null, 0, Ta); }
public void SetRemoteCredentials(string username, string password) { RemoteIceUser = username; RemoteIcePassword = password; // Once the remote party's ICE credentials are known connection checking can // commence immediately as candidates trickle in. ConnectionState = RTCIceConnectionState.checking; OnIceConnectionStateChange?.Invoke(ConnectionState); }
private RTCPeerConnection(IJSRuntime jsRuntime, JsObjectRef jsObjectRef, RTCConfiguration rtcConfiguration) : base(jsRuntime, jsObjectRef) { AddNativeEventListener("connectionstatechange", (s, e) => OnConnectionStateChanged?.Invoke(s, e)); AddNativeEventListenerForObjectRef("datachannel", (s, e) => OnDataChannel?.Invoke(s, e), RTCDataChannelEvent.Create); AddNativeEventListenerForObjectRef("icecandidate", (s, e) => OnIceCandidate?.Invoke(s, e), RTCPeerConnectionIceEvent.Create); AddNativeEventListener("iceconnectionstatechange", (s, e) => OnIceConnectionStateChange?.Invoke(s, e)); AddNativeEventListener("icegatheringstatechange", (s, e) => OnIceGatheringStateChange?.Invoke(s, e)); AddNativeEventListener("negotiationneeded", (s, e) => OnNegotiationNeeded?.Invoke(s, e)); AddNativeEventListener("signallingstatechange", (s, e) => OnSignallingStateChange?.Invoke(s, e)); AddNativeEventListenerForObjectRef("track", (s, e) => OnTrack?.Invoke(s, e), RTCTrackEvent.Create); }
public void DidChangeIceConnectionState(Webrtc.RTCPeerConnection peerConnection, Webrtc.RTCIceConnectionState newState) { //System.Diagnostics.Debug.WriteLine($"OOOOOOOOOOOOOOOOOOOOOOO PeerConnection.IceConnectionState: {newState}"); OnIceConnectionStateChange?.Invoke(this, EventArgs.Empty); //// Make sure that state is connected with several attempts. //if (newState == Webrtc.RTCIceConnectionState.Connected) //{ // Timer timer = null; // int count = 5; // // Make sure that state is connected with several attempts. // timer = new Timer(new TimerCallback((state) => // { // if (((Webrtc.RTCPeerConnection)NativeObject).ConnectionState == // Webrtc.RTCPeerConnectionState.Connected || --count == 0) // { // timer.Dispose(); // System.Diagnostics.Debug.WriteLine($"OOOOOOOOOOOOOOOOOOOOOOO PeerConnection GENERATED CONNECTED {count}"); // OnConnectionStateChanged?.Invoke(this, EventArgs.Empty); // return; // } // }), null, 10, 50); // return; //} //else if (newState == Webrtc.RTCIceConnectionState.Disconnected) //{ // Timer timer = null; // int count = 5; // // Make sure that state is disconnected with several attempsts. // timer = new Timer(new TimerCallback((state) => // { // if (((Webrtc.RTCPeerConnection)NativeObject).ConnectionState == // Webrtc.RTCPeerConnectionState.Disconnected || --count == 0) // { // timer.Dispose(); // OnConnectionStateChanged?.Invoke(this, EventArgs.Empty); // return; // } // }), null, 10, 50); // return; //} }
public void OnIceConnectionChange(Webrtc.PeerConnection.IceConnectionState p0) { OnIceConnectionStateChange?.Invoke(this, EventArgs.Empty); // !!! I don't know why Android DOES NOT provide Connection State Change event??? // I drive this event from Ice Connection State Change event here for now. #if false if (p0 == Webrtc.PeerConnection.IceConnectionState.New) { } else if (p0 == Webrtc.PeerConnection.IceConnectionState.Checking) { } else if (p0 == Webrtc.PeerConnection.IceConnectionState.Connected) { Timer timer = null; int count = 5; // Make sure that state is connected with several attempts. timer = new Timer(new TimerCallback((state) => { if (((Webrtc.PeerConnection)NativeObject).ConnectionState() == Webrtc.PeerConnection.PeerConnectionState.Connected || --count == 0) { timer.Dispose(); System.Diagnostics.Debug.WriteLine($"OOOOOOOOOOOOOOOOOOOOOOO PeerConnection GENERATED CONNECTED {count}"); OnConnectionStateChanged?.Invoke(this, EventArgs.Empty); return; } }), null, 10, 50); return; } else if (p0 == Webrtc.PeerConnection.IceConnectionState.Completed) { } else if (p0 == Webrtc.PeerConnection.IceConnectionState.Failed) { } else if (p0 == Webrtc.PeerConnection.IceConnectionState.Disconnected) { Timer timer = null; int count = 5; // Make sure that state is disconnected with several attempsts. timer = new Timer(new TimerCallback((state) => { if (((Webrtc.PeerConnection)NativeObject).ConnectionState() == Webrtc.PeerConnection.PeerConnectionState.Disconnected || --count == 0) { timer.Dispose(); OnConnectionStateChanged?.Invoke(this, EventArgs.Empty); return; } }), null, 10, 50); return; } else if (p0 == Webrtc.PeerConnection.IceConnectionState.Closed) { } #endif if (p0 == PeerConnection.IceConnectionState.Connected || p0 == PeerConnection.IceConnectionState.Completed) { if (!_isConnected) { _isConnected = true; OnConnectionStateChanged?.Invoke(this, EventArgs.Empty); } } else if (_isConnected) { _isConnected = false; OnConnectionStateChanged?.Invoke(this, EventArgs.Empty); } }
/// <summary> /// Attempts to get a list of local ICE candidates. /// </summary> //private async Task GetIceCandidatesAsync() //{ // // The media is being multiplexed so the audio and video RTP channel is the same. // var rtpChannel = GetRtpChannel(SDPMediaTypesEnum.audio); // if (rtpChannel == null) // { // throw new ApplicationException("Cannot start gathering ICE candidates without an RTP channel."); // } // else // { // var localIPAddresses = _offerAddresses ?? NetServices.GetAllLocalIPAddresses(); // IceNegotiationStartedAt = DateTime.Now; // LocalIceCandidates = new List<IceCandidate>(); // foreach (var address in localIPAddresses.Where(x => x.AddressFamily == rtpChannel.RTPLocalEndPoint.AddressFamily)) // { // var iceCandidate = new IceCandidate(address, (ushort)rtpChannel.RTPPort); // if (_turnServerEndPoint != null) // { // iceCandidate.TurnServer = new TurnServer() { ServerEndPoint = _turnServerEndPoint }; // iceCandidate.InitialStunBindingCheck = SendTurnServerBindingRequest(iceCandidate); // } // LocalIceCandidates.Add(iceCandidate); // } // await Task.WhenAll(LocalIceCandidates.Where(x => x.InitialStunBindingCheck != null).Select(x => x.InitialStunBindingCheck)).ConfigureAwait(false); // } //} public void ProcessStunMessage(STUNv2Message stunMessage, IPEndPoint receivedOn) { IPEndPoint remoteEndPoint = (!receivedOn.Address.IsIPv4MappedToIPv6) ? receivedOn : new IPEndPoint(receivedOn.Address.MapToIPv4(), receivedOn.Port); //logger.LogDebug($"STUN message received from remote {remoteEndPoint} {stunMessage.Header.MessageType}."); if (stunMessage.Header.MessageType == STUNv2MessageTypesEnum.BindingRequest) { STUNv2Message stunResponse = new STUNv2Message(STUNv2MessageTypesEnum.BindingSuccessResponse); stunResponse.Header.TransactionId = stunMessage.Header.TransactionId; stunResponse.AddXORMappedAddressAttribute(remoteEndPoint.Address, remoteEndPoint.Port); // ToDo: Check authentication. string localIcePassword = LocalIcePassword; byte[] stunRespBytes = stunResponse.ToByteBufferStringKey(localIcePassword, true); //iceCandidate.LocalRtpSocket.SendTo(stunRespBytes, remoteEndPoint); _rtpChannel.SendAsync(RTPChannelSocketsEnum.RTP, remoteEndPoint, stunRespBytes); //iceCandidate.LastStunRequestReceivedAt = DateTime.Now; //iceCandidate.IsStunRemoteExchangeComplete = true; //if (remoteEndPoint == null) //{ //RemoteEndPoint = remoteEndPoint; //SetDestination(SDPMediaTypesEnum.audio, RemoteEndPoint, RemoteEndPoint); //OnIceConnected?.Invoke(iceCandidate, remoteEndPoint); //IceConnectionState = RTCIceConnectionState.connected; //} if (_remoteCandidates != null && !_remoteCandidates.Any(x => (x.address == remoteEndPoint.Address.ToString() || x.relatedAddress == remoteEndPoint.Address.ToString()) && (x.port == remoteEndPoint.Port || x.relatedPort == remoteEndPoint.Port))) { // This STUN request has come from a socket not in the remote ICE candidates list. Add it so we can send our STUN binding request to it. // RTCIceCandidate remoteIceCandidate = new IceCandidate("udp", remoteEndPoint.Address, (ushort)remoteEndPoint.Port, RTCIceCandidateType.host); RTCIceCandidate peerRflxCandidate = new RTCIceCandidate(new RTCIceCandidateInit()); peerRflxCandidate.SetAddressProperties(RTCIceProtocol.udp, remoteEndPoint.Address, (ushort)remoteEndPoint.Port, RTCIceCandidateType.prflx, null, 0); logger.LogDebug($"Adding peer reflex ICE candidate for {remoteEndPoint}."); _remoteCandidates.Add(peerRflxCandidate); // Some browsers require a STUN binding request from our end before the DTLS handshake will be initiated. // The STUN connectivity checks are already scheduled but we can speed things up by sending a binding // request immediately. SendStunConnectivityChecks(null); } } else if (stunMessage.Header.MessageType == STUNv2MessageTypesEnum.BindingSuccessResponse) { if (ConnectionState != RTCIceConnectionState.connected) { logger.LogDebug($"ICE session setting connected remote end point to {remoteEndPoint}."); _connectedRemoteEndPoint = remoteEndPoint; ConnectionState = RTCIceConnectionState.connected; OnIceConnectionStateChange?.Invoke(RTCIceConnectionState.connected); } // TODO: What needs to be done here? //if (_turnServerEndPoint != null && remoteEndPoint.ToString() == _turnServerEndPoint.ToString()) //{ // if (iceCandidate.IsGatheringComplete == false) // { // var reflexAddressAttribute = stunMessage.Attributes.FirstOrDefault(y => y.AttributeType == STUNv2AttributeTypesEnum.XORMappedAddress) as STUNv2XORAddressAttribute; // if (reflexAddressAttribute != null) // { // iceCandidate.StunRflxIPEndPoint = new IPEndPoint(reflexAddressAttribute.Address, reflexAddressAttribute.Port); // iceCandidate.IsGatheringComplete = true; // logger.LogDebug("ICE gathering complete for local socket " + iceCandidate.RtpChannel.RTPLocalEndPoint + ", rflx address " + iceCandidate.StunRflxIPEndPoint + "."); // } // else // { // iceCandidate.IsGatheringComplete = true; // logger.LogDebug("The STUN binding response received on " + iceCandidate.RtpChannel.RTPLocalEndPoint + " from " + remoteEndPoint + " did not have an XORMappedAddress attribute, rlfx address can not be determined."); // } // } //} //else //{ // iceCandidate.LastStunResponseReceivedAt = DateTime.Now; // if (iceCandidate.IsStunLocalExchangeComplete == false) // { // iceCandidate.IsStunLocalExchangeComplete = true; // logger.LogDebug("WebRTC client STUN exchange complete for call " + CallID + ", candidate local socket " + iceCandidate.RtpChannel.RTPLocalEndPoint + ", remote socket " + remoteEndPoint + "."); // SetIceConnectionState(IceConnectionStatesEnum.Connected); // } //} } else if (stunMessage.Header.MessageType == STUNv2MessageTypesEnum.BindingErrorResponse) { logger.LogWarning($"A STUN binding error response was received from {remoteEndPoint}."); } else { logger.LogWarning($"An unrecognised STUN request was received from {remoteEndPoint}."); } }
/// <summary> /// Processes the checklist and sends any required STUN requests to perform connectivity checks. /// </summary> /// <remarks> /// The scheduling mechanism for ICE is specified in https://tools.ietf.org/html/rfc8445#section-6.1.4. /// </remarks> private void ProcessChecklist(Object stateInfo) { try { if (ConnectionState == RTCIceConnectionState.checking && _checklist != null && _checklist.Count > 0) { if (RemoteIceUser == null || RemoteIcePassword == null) { logger.LogWarning("ICE session checklist processing cannot occur as either the remote ICE user or password are not set."); ConnectionState = RTCIceConnectionState.failed; } else { lock (_checklist) { // The checklist gets sorted into priority order whenever a remote candidate and its corresponding candidate pairs // are added. At this point it can be relied upon that the checklist is correctly sorted by candidate pair priority. // Do a check for any timed out entries. var failedEntries = _checklist.Where(x => x.State == ChecklistEntryState.InProgress && DateTime.Now.Subtract(x.LastCheckSentAt).TotalMilliseconds > RTO && x.ChecksSent >= N).ToList(); foreach (var failedEntry in failedEntries) { logger.LogDebug($"Checks for checklist entry have timed out, state being set to failed: {failedEntry.LocalCandidate} -> {failedEntry.RemoteCandidate}."); failedEntry.State = ChecklistEntryState.Failed; } // Move on to checking for checklist entries that need an initial check sent. var nextEntry = _checklist.Where(x => x.State == ChecklistEntryState.Waiting).FirstOrDefault(); if (nextEntry != null) { SendConnectivityCheck(nextEntry, false); return; } // No waiting entries so check for ones requiring a retransmit. var retransmitEntry = _checklist.Where(x => x.State == ChecklistEntryState.InProgress && DateTime.Now.Subtract(x.LastCheckSentAt).TotalMilliseconds > RTO).FirstOrDefault(); if (retransmitEntry != null) { SendConnectivityCheck(retransmitEntry, false); return; } // If this point is reached and all entries are in a failed state then the overall result // of the ICE check is a failure. if (_checklist.All(x => x.State == ChecklistEntryState.Failed)) { _stunChecksTimer.Dispose(); _checklistState = ChecklistState.Failed; ConnectionState = RTCIceConnectionState.failed; OnIceConnectionStateChange?.Invoke(ConnectionState); } } } } } catch (Exception excp) { logger.LogError("Exception ProcessChecklist. " + excp); } }