diff --git a/RelayClient/MainPage.xaml b/RelayClient/MainPage.xaml
index ee2be68..86a54b9 100644
--- a/RelayClient/MainPage.xaml
+++ b/RelayClient/MainPage.xaml
@@ -53,6 +53,14 @@
Spacing="8" />
+
+
+
+
+
+
\ No newline at end of file
diff --git a/RelayClient/MainPage.xaml.cs b/RelayClient/MainPage.xaml.cs
index 6cda033..f1bd0ff 100644
--- a/RelayClient/MainPage.xaml.cs
+++ b/RelayClient/MainPage.xaml.cs
@@ -2,13 +2,10 @@
using RelayClient.Models;
using WebSocketSharp;
using System.Text.Json;
-using System.Threading.Tasks;
-using Microsoft.MixedReality.WebRTC;
-using System.IO;
-using System.Collections.Generic;
namespace RelayClient;
+
public partial class MainPage : ContentPage
{
private readonly string _username;
@@ -19,14 +16,14 @@ public partial class MainPage : ContentPage
private readonly Dictionary> _messagesByChannel = new();
private readonly List _channels = new();
-
+
public MainPage(string username)
{
InitializeComponent();
_username = username;
UserLabel.Text = $"Logged in as: {_username}";
-
+
if (!KeyStorage.HasKeys(_username))
{
var keys = E2EeHelper.GenerateRsaKeyPair();
@@ -38,13 +35,12 @@ public partial class MainPage : ContentPage
_wsc.OnMessage += WscOnMessage;
_wsc.Connect();
-
+
var publicKey = KeyStorage.LoadPublicKey(_username);
_wsc.Send($"REGISTER_KEY|{_username}|{publicKey}");
_wsc.Send("GET_SERVER_KEY");
_wsc.Send("GET_CHANNELS");
- _ = webRTCTest();
}
private void SendButton_OnClicked(object? sender, EventArgs e)
@@ -217,7 +213,7 @@ public partial class MainPage : ContentPage
_wsc.Close();
base.OnDisappearing();
}
-
+
private void RenderChannelList()
{
SidebarList.Children.Clear();
@@ -246,7 +242,7 @@ public partial class MainPage : ContentPage
SidebarList.Children.Add(button);
}
}
-
+
private void RenderCurrentChannelMessages()
{
MessagesLayout.Children.Clear();
@@ -262,7 +258,7 @@ public partial class MainPage : ContentPage
RenderSingleMessage(message);
}
}
-
+
private async void RenderSingleMessage(ChatMessage message)
{
bool isOwnMessage = message.SenderUsername == _username;
@@ -293,146 +289,20 @@ public partial class MainPage : ContentPage
await MessagesScrollView.ScrollToAsync(MessagesLayout, ScrollToPosition.End, true);
}
- private async Task webRTCTest()
+ private void SwapView_OnClicked(object? sender, EventArgs e)
{
- var path = Path.GetFullPath(
- Path.Combine(AppContext.BaseDirectory, @"..\..\..\debug.txt")
- );
-
- void Log(string msg)
+ if (RtcView.IsVisible)
{
- File.AppendAllText(path, msg + Environment.NewLine);
- Console.WriteLine(msg);
+ MessagesScrollView.IsVisible = true;
+ RtcView.IsVisible = false;
+ ViewSwapped.Text = "Swap to Message View";
+
}
-
- AudioTrackSource? microphoneSource = null;
- DeviceVideoTrackSource? webcamSource = null;
- Transceiver? audioTransceiver = null;
- Transceiver? videoTransceiver = null;
- LocalAudioTrack? localAudioTrack = null;
- LocalVideoTrack? localVideoTrack = null;
-
- Log("=== WebRTC TEST START ===");
-
- IReadOnlyList? deviceList = null;
-
- try
+ else
{
- Log("Getting video devices...");
-
- deviceList = await DeviceVideoTrackSource.GetCaptureDevicesAsync();
-
- foreach (var device in deviceList)
- {
- Log($"Found device: {device.name} (id: {device.id})");
- }
-
- Log("Device enumeration complete.");
- }
- catch (Exception ex)
- {
- Log("Device enumeration FAILED:");
- Log(ex.ToString());
- return;
- }
-
- try
- {
- Log("Creating PeerConnection...");
-
- using var pc = new PeerConnection();
-
- Log("PeerConnection created.");
-
- var config = new PeerConnectionConfiguration
- {
- IceServers =
- {
- new IceServer { Urls = { "stun:stun.l.google.com:19302" } }
- }
- };
-
- Log("Config created.");
- Log("Calling InitializeAsync...");
-
- await pc.InitializeAsync(config);
-
- Log("InitializeAsync SUCCESS.");
-
- if (deviceList.Count > 0)
- {
- try
- {
- Log($"Getting formats for first device: {deviceList[0].name}");
-
- var formats = await DeviceVideoTrackSource.GetCaptureFormatsAsync(deviceList[0].id);
-
- foreach (var f in formats)
- {
- Log($"Format: {f.width}x{f.height} @ {f.framerate}");
- }
- }
- catch (Exception ex)
- {
- Log("Format enumeration FAILED:");
- Log(ex.ToString());
- }
- }
-
- Log("Creating webcam source (default device)...");
- webcamSource = await DeviceVideoTrackSource.CreateAsync();
- Log("Webcam source created.");
-
- Log("Creating local video track...");
- var videoTrackConfig = new LocalVideoTrackInitConfig
- {
- trackName = "webcam_track"
- };
- localVideoTrack = LocalVideoTrack.CreateFromSource(webcamSource, videoTrackConfig);
- Log("Local video track created.");
-
- Log("Creating microphone source...");
- microphoneSource = await DeviceAudioTrackSource.CreateAsync();
- Log("Microphone source created.");
-
- Log("Creating local audio track...");
- var audioTrackConfig = new LocalAudioTrackInitConfig
- {
- trackName = "microphone_track"
- };
- localAudioTrack = LocalAudioTrack.CreateFromSource(microphoneSource, audioTrackConfig);
- Log("Local audio track created.");
-
- Log("Adding video transceiver...");
- videoTransceiver = pc.AddTransceiver(MediaKind.Video);
- videoTransceiver.LocalVideoTrack = localVideoTrack;
- videoTransceiver.DesiredDirection = Transceiver.Direction.SendReceive;
- Log("Video transceiver configured.");
-
- Log("Adding audio transceiver...");
- audioTransceiver = pc.AddTransceiver(MediaKind.Audio);
- audioTransceiver.LocalAudioTrack = localAudioTrack;
- audioTransceiver.DesiredDirection = Transceiver.Direction.SendReceive;
- Log("Audio transceiver configured.");
-
- Log("WebRTC setup complete.");
- }
- catch (Exception ex)
- {
- Log("WebRTC setup FAILED:");
- Log(ex.ToString());
- }
- finally
- {
- Log("Disposing WebRTC resources...");
-
- localAudioTrack?.Dispose();
- localVideoTrack?.Dispose();
- microphoneSource?.Dispose();
- webcamSource?.Dispose();
-
- Log("Resource disposal complete.");
- Log("=== WebRTC TEST END ===");
+ MessagesScrollView.IsVisible = false;
+ RtcView.IsVisible = true;
+ ViewSwapped.Text = "Swap to Web View";
}
}
}
\ No newline at end of file
diff --git a/RelayClient/RelayClient.csproj b/RelayClient/RelayClient.csproj
index 3fb3ed5..9fbdf2b 100644
--- a/RelayClient/RelayClient.csproj
+++ b/RelayClient/RelayClient.csproj
@@ -36,14 +36,24 @@
+
+
+ PreserveNewest
+
+
-
+
+
+ PreserveNewest
+
+
+
diff --git a/RelayClient/Resources/Raw/test.css b/RelayClient/Resources/Raw/test.css
new file mode 100644
index 0000000..b62e775
--- /dev/null
+++ b/RelayClient/Resources/Raw/test.css
@@ -0,0 +1,3 @@
+#header{
+ border: black solid 2px;
+}
\ No newline at end of file
diff --git a/RelayClient/Resources/Raw/test.html b/RelayClient/Resources/Raw/test.html
new file mode 100644
index 0000000..661c7af
--- /dev/null
+++ b/RelayClient/Resources/Raw/test.html
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/RelayClient/Resources/Raw/test.js b/RelayClient/Resources/Raw/test.js
new file mode 100644
index 0000000..9d0408f
--- /dev/null
+++ b/RelayClient/Resources/Raw/test.js
@@ -0,0 +1,10 @@
+var toggle = true;
+
+function onClicked()
+{
+ if (toggle)
+ document.getElementById("header").style.color = "green";
+ else
+ document.getElementById("header").style.color = "red";
+ toggle = !toggle;
+}
\ No newline at end of file
diff --git a/RelayClient/WebRTC.cs b/RelayClient/WebRTC.cs
new file mode 100644
index 0000000..20b2ab5
--- /dev/null
+++ b/RelayClient/WebRTC.cs
@@ -0,0 +1,170 @@
+using System;
+using System.Runtime.InteropServices;
+using System.Threading.Tasks;
+using Microsoft.Maui.Dispatching;
+using Microsoft.AspNetCore.SignalR.Client;
+
+namespace RelayClient;
+
+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);
+ }
+ }
+}
\ No newline at end of file
diff --git a/RelayServer/Program.cs b/RelayServer/Program.cs
index cbdf928..008e16e 100644
--- a/RelayServer/Program.cs
+++ b/RelayServer/Program.cs
@@ -1,6 +1,8 @@
using System.Text.Json;
using RelayServer.Services;
using WebSocketSharp.Server;
+using Microsoft.AspNetCore.SignalR;
+using Microsoft.AspNetCore.Builder;
using RelayServer.Models;
var surrealService = new SurrealService();
@@ -12,6 +14,14 @@ await using var db = await surrealService.ConnectAsync();
ChatTest.ClientKeyService = new ClientKeyService(db);
ChatTest.Db = db;
+var builder = WebApplication.CreateBuilder(args);
+builder.Services.AddSignalR();
+
+var app = builder.Build();
+app.MapGet("/", () => "Server Running!");
+app.MapHub("/webrtc");
+app.Run();
+
var wssv = new WebSocketServer("ws://localhost:1337");
wssv.AddWebSocketService("/");
wssv.Start();
@@ -146,4 +156,25 @@ static string GetRecordId(object? id)
var table = root.GetProperty("Table").GetString() ?? string.Empty;
return $"{table}:{recordId}";
+}
+
+public class WebRtcHub : Hub
+{
+ public async Task SendOffer(string targetConnectionId, string sdp)
+ {
+ await Clients.Client(targetConnectionId)
+ .SendAsync("ReceiveOffer", Context.ConnectionId, sdp);
+ }
+
+ public async Task SendAnswer(string targetConnectionId, string sdp)
+ {
+ await Clients.Client(targetConnectionId)
+ .SendAsync("ReceiveAnswer", Context.ConnectionId, sdp);
+ }
+
+ public async Task SendIceCandidate(string targetConnectionId, string candidate)
+ {
+ await Clients.Client(targetConnectionId)
+ .SendAsync("ReceiveIceCandidate", Context.ConnectionId, candidate);
+ }
}
\ No newline at end of file
diff --git a/RelayServer/RelayServer.csproj b/RelayServer/RelayServer.csproj
index 78b8450..1a2c504 100644
--- a/RelayServer/RelayServer.csproj
+++ b/RelayServer/RelayServer.csproj
@@ -1,4 +1,4 @@
-
+
Exe
@@ -8,6 +8,7 @@
+