using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; using Tango.Core; using Tango.Core.Threading; using Tango.Logging; using Tango.Transport; using Tango.Transport.Adapters; using Tango.Transport.Compression; using Tango.WebRTC.Network; namespace Tango.WebRTC { public class WebRtcTransportAdapter : TransportAdapterBase { private WebRtcClient _client; private bool _answerReceived; private List _queuedIceCandidates; public event EventHandler Ready; public ITransporter SignalingTransporter { get; set; } public WebRtcTransportAdapterMode Mode { get; set; } public String DataChannelName { get; set; } public WebRtcTransportAdapter(ITransporter signalingTransporter, WebRtcTransportAdapterMode mode) : this(signalingTransporter, mode, null) { } public WebRtcTransportAdapter(ITransporter signalingTransporter, WebRtcTransportAdapterMode mode, String dataChannelName) { SignalingTransporter = signalingTransporter; Mode = mode; DataChannelName = dataChannelName; Address = dataChannelName; ComponentName = $"WebRTC Adapter {_component_counter++}"; SignalingTransporter.RegisterRequestHandler(OnIceCandidateRequestReceived); SignalingTransporter.RegisterRequestHandler(OnOfferRequestReceived); } public override void Write(byte[] data, bool immidiate = false) { ThrowIfDisposed(); try { if (EnableCompression) { data = GZipHelper.Compress(data); } _client.SendBinary(data); } catch (Exception ex) { OnFailed(ex); } } public override Task Connect() { ThrowIfDisposed(); TaskCompletionSource completionSource = new TaskCompletionSource(); bool completed = false; _queuedIceCandidates = new List(); _answerReceived = false; ThreadFactory.StartNew(async () => { if (State != TransportComponentState.Connected) { try { _client = new WebRtcClient(); if (DataChannelName != null) { _client.DataChannelName = DataChannelName; } Address = _client.DataChannelName; _client.NewIceCandidate += WebRtc_NewIceCandidate; _client.Disconnected += WebRtc_Disconnected; _client.BinaryMessageReceived += WebRtc_BinaryMessageReceived; _client.Ready += (x, e) => { if (!completed) { LogManager.Log($"{ComponentName}: WebRTC Active Transport Adapter is ready."); completed = true; State = TransportComponentState.Connected; completionSource.SetResult(true); Ready?.Invoke(this, new EventArgs()); } if (Mode == WebRtcTransportAdapterMode.Passive) { LogManager.Log($"{ComponentName}: WebRTC Passive Transport Adapter is ready."); Ready?.Invoke(this, new EventArgs()); } }; LogManager.Log($"{ComponentName}: Initializing WebRTC client..."); await _client.Init(); if (Mode == WebRtcTransportAdapterMode.Active) { LogManager.Log($"{ComponentName}: Creating WebRTC offer...", LogCategory.Debug); var offer = await _client.CreateOffer(); LogManager.Log($"{ComponentName}: Sending WebRTC offer via signaling transporter...", LogCategory.Debug); var response = await SignalingTransporter.SendGenericRequest(new OfferRequest() { Offer = offer }, new TransportRequestConfig() { Timeout = TimeSpan.FromSeconds(30), }); LogManager.Log($"{ComponentName}: WebRTC offer sent and responded with an answer. Setting WebRTC answer...", LogCategory.Debug); _client.SetAnswer(response.Answer); _answerReceived = true; foreach (var ice in _queuedIceCandidates.ToList()) { LogManager.Log($"{ComponentName}: Sending existing ice candidate '{ice.Sdp}'...", LogCategory.Debug); try { await SignalingTransporter.SendGenericRequest(new IceCandidateRequest() { IceCandidate = ice }, new TransportRequestConfig() { Timeout = TimeSpan.FromSeconds(30), }); } catch (Exception ex) { LogManager.Log(ex, $"{ComponentName}: Error sending ice candidate."); } } } else { LogManager.Log($"{ComponentName}: Waiting for offer...", LogCategory.Debug); State = TransportComponentState.Connected; if (!completed) { completed = true; completionSource.SetResult(true); } } } catch (Exception ex) { if (!completed) { completed = true; completionSource.SetException(ex); } } } else { if (!completed) { completed = true; completionSource.SetResult(true); } } }); if (Mode == WebRtcTransportAdapterMode.Active) { TimeoutTask.StartNew(() => { if (!completed) { completed = true; completionSource.SetException(new TimeoutException("Could not reach the remote peer using the WebRTC adapter.")); } }, TimeSpan.FromSeconds(30)); } return completionSource.Task; } private void WebRtc_BinaryMessageReceived(object sender, DataMessageReceivedEventArgs e) { if (EnableCompression) { try { var decompressed = GZipHelper.Decompress(e.Data); OnDataAvailable(decompressed); } catch (Exception ex) { if (ex.Message.Contains("GZip")) { //Temporarily ignore, probably switching protocol definitions... } else { //Do nothing... ? } } } else { OnDataAvailable(e.Data); } } private async void OnOfferRequestReceived(ITransporter transporter, OfferRequest request, string token) { if (Mode == WebRtcTransportAdapterMode.Passive) { try { var answer = await _client.CreateAnswer(request.Offer); await SignalingTransporter.SendGenericResponse(new OfferResponse() { Answer = answer }, token); _answerReceived = true; foreach (var ice in _queuedIceCandidates.ToList()) { LogManager.Log($"{ComponentName}: Sending existing ice candidate '{ice.Sdp}'...", LogCategory.Debug); try { await SignalingTransporter.SendGenericRequest(new IceCandidateRequest() { IceCandidate = ice }, new TransportRequestConfig() { Timeout = TimeSpan.FromSeconds(30), }); } catch (Exception ex) { LogManager.Log(ex, $"{ComponentName}: Error sending ice candidate to remote peer."); } } } catch (Exception ex) { LogManager.Log(ex, $"{ComponentName}: Error occurred while trying to return WebRTC answer."); } } } private async void WebRtc_NewIceCandidate(object sender, NewIceCandidateEventArgs e) { try { if (_answerReceived) { LogManager.Log($"{ComponentName}: New WebRTC candidate available. Sending ice to remote peer...", LogCategory.Debug); await SignalingTransporter.SendGenericRequest(new IceCandidateRequest() { IceCandidate = e.IceCandidate }, new TransportRequestConfig() { Timeout = TimeSpan.FromSeconds(30), }); } else { if (Mode == WebRtcTransportAdapterMode.Active) { LogManager.Log($"{ComponentName}: New WebRTC candidate available. Will be sent after an answer is received...", LogCategory.Debug); } else { LogManager.Log($"{ComponentName}: New WebRTC candidate available. Will be sent after an offer is received...", LogCategory.Debug); } _queuedIceCandidates.Add(e.IceCandidate); } } catch (Exception ex) { LogManager.Log(ex, $"{ComponentName}: Error sending ice candidate to remote peer."); } } private async void OnIceCandidateRequestReceived(ITransporter transporter, IceCandidateRequest request, string token) { try { LogManager.Log($"{ComponentName}: Ice candidate request received from the remote peer.", LogCategory.Debug); await SignalingTransporter.SendGenericResponse(new IceCandidateResponse() { }, token); LogManager.Log($"{ComponentName}: Adding ice candidate '{request.IceCandidate.Sdp}'...", LogCategory.Debug); _client.AddIceCandidate(request.IceCandidate); LogManager.Log($"{ComponentName}: Ice candidate added.", LogCategory.Debug); } catch (Exception ex) { LogManager.Log(ex, $"{ComponentName}: Error occurred on ice candidate received handling."); } } private void WebRtc_Disconnected(object sender, EventArgs e) { OnFailed(new WebRtcTransportAdapterDisconnectedException("WebRtc Transport Adapter RTC client has disconnected.")); } public override Task Disconnect() { TaskCompletionSource completionSource = new TaskCompletionSource(); ThreadFactory.StartNew(() => { if (State != TransportComponentState.Disconnected) { if (_client != null) { LogManager.Log($"{ComponentName}: Disposing WebRTC client..."); _client.NewIceCandidate -= WebRtc_NewIceCandidate; _client.Disconnected -= WebRtc_Disconnected; _client.BinaryMessageReceived -= WebRtc_BinaryMessageReceived; try { _client.Dispose(); _client = null; SignalingTransporter.UnregisterRequestHandler(OnIceCandidateRequestReceived); SignalingTransporter.UnregisterRequestHandler(OnOfferRequestReceived); LogManager.Log($"{ComponentName}: WebRTC client disposed."); } catch (Exception ex) { LogManager.Log(ex, $"{ComponentName}: Error disposing WebRTC client."); } } State = TransportComponentState.Disconnected; completionSource.SetResult(true); } else { completionSource.SetResult(true); } }); return completionSource.Task; } } }