using System; using System.Runtime.InteropServices; using System.Threading.Tasks; using Microsoft.Maui.Dispatching; using Microsoft.AspNetCore.SignalR.Client; namespace RelayClient; //TODO: Remove this file public static class NativeWebRtc { [DllImport("webrtc_wrapper.dll")] public static extern IntPtr CreatePeerConnection(); [DllImport("webrtc_wrapper.dll")] public static extern string CreateOffer(IntPtr pc); [DllImport("webrtc_wrapper.dll")] public static extern string CreateAnswer(IntPtr pc); [DllImport("webrtc_wrapper.dll")] public static extern void SetLocalDescription(IntPtr pc, string type, string sdp); [DllImport("webrtc_wrapper.dll")] public static extern void SetRemoteDescription(IntPtr pc, string type, string sdp); [DllImport("webrtc_wrapper.dll")] public static extern void AddIceCandidate(IntPtr pc, string candidate); } public enum RTCSdpType { Offer, Answer } public class RTCSessionDescription { public RTCSdpType Type { get; set; } public string Sdp { get; set; } } public class RTCIceCandidate { public string Candidate { get; set; } } public class PeerConnection { private readonly IntPtr _nativeHandle; public string RemoteId { get; set; } public PeerConnection() { _nativeHandle = NativeWebRtc.CreatePeerConnection(); } public Task CreateOffer(Action onOfferCreated) { var sdp = NativeWebRtc.CreateOffer(_nativeHandle); onOfferCreated?.Invoke(new RTCSessionDescription { Type = RTCSdpType.Offer, Sdp = sdp }); return Task.CompletedTask; } public Task CreateAnswer(Action onAnswerCreated) { var sdp = NativeWebRtc.CreateAnswer(_nativeHandle); onAnswerCreated?.Invoke(new RTCSessionDescription { Type = RTCSdpType.Answer, Sdp = sdp }); return Task.CompletedTask; } public Task SetLocalDescription(RTCSessionDescription desc) { NativeWebRtc.SetLocalDescription(_nativeHandle, desc.Type.ToString(), desc.Sdp); return Task.CompletedTask; } public Task SetRemoteDescription(RTCSessionDescription desc) { NativeWebRtc.SetRemoteDescription(_nativeHandle, desc.Type.ToString(), desc.Sdp); return Task.CompletedTask; } public Task AddIceCandidate(RTCIceCandidate candidate) { NativeWebRtc.AddIceCandidate(_nativeHandle, candidate.Candidate); return Task.CompletedTask; } } public class WebRtcClient { private readonly PeerConnection _peerConnection = new(); private readonly HubConnection _signal; private string _myId; public WebRtcClient(string serverUrl) { _signal = new HubConnectionBuilder() .WithUrl($"{serverUrl}/webrtc") .WithAutomaticReconnect() .Build(); _signal.On("ReceiveOffer", (fromId, sdp) => { MainThread.BeginInvokeOnMainThread(async () => { await HandleOffer(fromId, sdp); }); }); _signal.On("ReceiveAnswer", (fromId, sdp) => { MainThread.BeginInvokeOnMainThread(async () => { await HandleAnswer(sdp); }); }); _signal.On("ReceiveIceCandidate", (fromId, candidate) => { MainThread.BeginInvokeOnMainThread(async () => { await HandleIceCandidate(candidate); }); }); } public async Task ConnectAsync() { await _signal.StartAsync(); _myId = _signal.ConnectionId; } public async Task CallAsync(string targetId) { _peerConnection.RemoteId = targetId; await _peerConnection.CreateOffer(async offer => { await _peerConnection.SetLocalDescription(offer); await _signal.InvokeAsync("SendOffer", targetId, offer.Sdp); }); } public async Task HandleOffer(string fromId, string sdp) { _peerConnection.RemoteId = fromId; var remoteDesc = new RTCSessionDescription { Type = RTCSdpType.Offer, Sdp = sdp }; await _peerConnection.SetRemoteDescription(remoteDesc); await _peerConnection.CreateAnswer(async answer => { await _peerConnection.SetLocalDescription(answer); await _signal.InvokeAsync("SendAnswer", fromId, answer.Sdp); }); } public async Task HandleAnswer(string sdp) { var remoteDesc = new RTCSessionDescription { Type = RTCSdpType.Answer, Sdp = sdp }; await _peerConnection.SetRemoteDescription(remoteDesc); } public async Task HandleIceCandidate(string candidate) { await _peerConnection.AddIceCandidate(new RTCIceCandidate { Candidate = candidate }); } public async Task SendIceCandidate(string candidate) { if (!string.IsNullOrEmpty(_peerConnection.RemoteId)) { await _signal.InvokeAsync("SendIceCandidate", _peerConnection.RemoteId, candidate); } } }