From 9e587ad7b56a508c75e7a46a03cc15e5188a0547 Mon Sep 17 00:00:00 2001 From: Cody Larkin Date: Wed, 8 Apr 2026 18:56:30 -0400 Subject: [PATCH 1/4] fixed missing audio/video devices --- RelayClient/Resources/Raw/wwwroot/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RelayClient/Resources/Raw/wwwroot/index.js b/RelayClient/Resources/Raw/wwwroot/index.js index df3cb5c..81f13d9 100644 --- a/RelayClient/Resources/Raw/wwwroot/index.js +++ b/RelayClient/Resources/Raw/wwwroot/index.js @@ -24,7 +24,7 @@ window.setChannelId = function(channelId) { currentChannelId = channelId; LogMessage("Channel set to: " + currentChannelId); }; -let userMedia = getUserMedia() +// let userMedia = getUserMedia() function LogMessage(msg) { const messageLog = document.getElementById("messageLog"); messageLog.value += '\r\n' + msg; From dad5de3d7f8907e3a63ee9d2c27cf98afdd13d33 Mon Sep 17 00:00:00 2001 From: Cody Larkin Date: Wed, 8 Apr 2026 22:29:29 -0400 Subject: [PATCH 2/4] CALL WORKS, NEEDS TO HAVE LEAVE CALL SETUP AND HOTSWAP FIXED --- RelayClient/MainPage.xaml.cs | 19 ++++++++++++++++--- RelayClient/Models/RtcNotificationMessage.cs | 2 +- RelayClient/Resources/Raw/wwwroot/index.js | 15 ++++++++++++++- RelayServer/Endpoints/RtcEndpoints.cs | 6 +++--- .../Models/Rtc/RtcNotificationMessage.cs | 8 ++++++++ .../Services/Rtc/RtcNotificationService.cs | 18 ++++++++++++++++++ 6 files changed, 60 insertions(+), 8 deletions(-) diff --git a/RelayClient/MainPage.xaml.cs b/RelayClient/MainPage.xaml.cs index 546f9e6..9d66f62 100644 --- a/RelayClient/MainPage.xaml.cs +++ b/RelayClient/MainPage.xaml.cs @@ -234,6 +234,16 @@ public partial class MainPage : ContentPage } case "rtc_candidate_added": { + try + { + IceCandidate? iceCandidate = JsonSerializer.Deserialize(rtcNotification.Direction); + IceCandidateCallback(iceCandidate); + } + catch (Exception ex) + { + SafeSendRawToWebView($"Candidate rejected: {ex.Message}"); + } + break; } case "rtc_call_left": @@ -254,7 +264,7 @@ public partial class MainPage : ContentPage if (pyload is null) return; - if (pyload.RecipientUsername != _username) + if (pyload.RecipientUsername == _username) return; Console.WriteLine($"[{_username}] received encrypted payload for {pyload.RecipientUsername}"); @@ -488,6 +498,7 @@ public partial class MainPage : ContentPage Username = _username, Candidate = candidate }; + if (candidate == null) return; await ServerAPI.PostIceCandidateAsync(DBCandidate); } catch (Exception ex) @@ -496,11 +507,11 @@ public partial class MainPage : ContentPage } } - public async void IceCandidateCallback(string json) + public async void IceCandidateCallback(IceCandidate candidate) { try { - await hybridWebView.InvokeJavaScriptAsync("IceCandidateAdded"); + await hybridWebView.InvokeJavaScriptAsync("IceCandidateAdded", [candidate], [HybridJSType.Default.IceCandidate]); } catch (Exception ex) { @@ -610,6 +621,8 @@ public partial class MainPage : ContentPage [JsonSourceGenerationOptions(WriteIndented = false)] [JsonSerializable(typeof(RtcDescription))] [JsonSerializable(typeof(List))] + [JsonSerializable(typeof(IceCandidate))] + [JsonSerializable(typeof(List))] [JsonSerializable(typeof(string))] internal partial class HybridJSType : JsonSerializerContext { diff --git a/RelayClient/Models/RtcNotificationMessage.cs b/RelayClient/Models/RtcNotificationMessage.cs index 519ee34..4113254 100644 --- a/RelayClient/Models/RtcNotificationMessage.cs +++ b/RelayClient/Models/RtcNotificationMessage.cs @@ -6,4 +6,4 @@ public sealed class RtcNotificationMessage public string? ChannelId { get; set; } public string? Username { get; set; } public string? Direction { get; set; } -} \ No newline at end of file +} diff --git a/RelayClient/Resources/Raw/wwwroot/index.js b/RelayClient/Resources/Raw/wwwroot/index.js index ee060e7..da5125d 100644 --- a/RelayClient/Resources/Raw/wwwroot/index.js +++ b/RelayClient/Resources/Raw/wwwroot/index.js @@ -4,6 +4,7 @@ let currentUsername = null; let currentChannelId = null; let availableCameras = []; let availableMics = []; +let candidateQueue = []; const configuration = { iceServers:[ { @@ -262,6 +263,7 @@ async function ensurePeerConnection2() console.log(`Ice Candidate: ${JSON.stringify(event.candidate)}`); LogMessage(`Ice Candidate: ${JSON.stringify(event.candidate)}`); await window.HybridWebView.InvokeDotNet("WriteIceCandidate", [JSON.stringify(event.candidate)]); + await IceCandidateAdded(event.candidate); }; peerConnection.ontrack = (event) => { @@ -334,11 +336,22 @@ async function AnswerCallbackJS(answer) LogMessage("Current answer: " + JSON.stringify(answer)); const desc = new RTCSessionDescription(answer); await peerConnection.setRemoteDescription(desc); + for (const candidate of candidateQueue) { + await peerConnection.addIceCandidate(candidate); + } } } async function IceCandidateAdded(candidate) { - await peerConnection.addIceCandidate(candidate); + if (peerConnection.currentRemoteDescription) { + await peerConnection.addIceCandidate(candidate); + LogMessage("ICE CANDIDATE ADDED: " + JSON.stringify(candidate)); + } + else { + LogMessage("RemoteDescription Missing") + candidateQueue.push(candidate); + + } } async function handleRtcSignal(rawJson) { try { diff --git a/RelayServer/Endpoints/RtcEndpoints.cs b/RelayServer/Endpoints/RtcEndpoints.cs index 8eef93d..e44f176 100644 --- a/RelayServer/Endpoints/RtcEndpoints.cs +++ b/RelayServer/Endpoints/RtcEndpoints.cs @@ -1,4 +1,5 @@ -using RelayServer.Models.Rtc; +using System.Text.Json; +using RelayServer.Models.Rtc; using RelayServer.Services.Rtc; namespace RelayServer.Endpoints; @@ -86,7 +87,6 @@ public static class RtcEndpoints request.Candidate.candidate, request.Candidate.sdpMid, request.Candidate.sdpMLineIndex - // request.Candidate.direction ); RtcNotificationService.BroadcastToChannel(new RtcNotificationMessage @@ -94,7 +94,7 @@ public static class RtcEndpoints Type = "rtc_candidate_added", ChannelId = request.ChannelId, Username = request.Username, - /*Direction = request.Direction*/ + Direction = JsonSerializer.Serialize(request.Candidate) }); return Results.Ok(); diff --git a/RelayServer/Models/Rtc/RtcNotificationMessage.cs b/RelayServer/Models/Rtc/RtcNotificationMessage.cs index 89ab5e9..dc22983 100644 --- a/RelayServer/Models/Rtc/RtcNotificationMessage.cs +++ b/RelayServer/Models/Rtc/RtcNotificationMessage.cs @@ -6,4 +6,12 @@ public sealed class RtcNotificationMessage public required string ChannelId { get; set; } public string? Username { get; set; } public string? Direction { get; set; } +} + +public sealed class RtcIceNotificationMessage +{ + public required string Type { get; set; } + public required string ChannelId { get; set; } + public string? Username { get; set; } + public required IceCandidate Candidate { get; set; } } \ No newline at end of file diff --git a/RelayServer/Services/Rtc/RtcNotificationService.cs b/RelayServer/Services/Rtc/RtcNotificationService.cs index 9968d74..1a6652b 100644 --- a/RelayServer/Services/Rtc/RtcNotificationService.cs +++ b/RelayServer/Services/Rtc/RtcNotificationService.cs @@ -25,4 +25,22 @@ public static class RtcNotificationService host.Sessions.SendTo(json, sessionId); } } + + public static void BroadcastToChannel(RtcIceNotificationMessage message) + { + if (Server is null) + return; + + var host = Server.WebSocketServices["/"]; + if (host is null) + return; + + var json = JsonSerializer.Serialize(message); + var sessionIds = RtcChannelPresenceService.GetSessionsInChannel(message.ChannelId); + + foreach (var sessionId in sessionIds) + { + host.Sessions.SendTo(json, sessionId); + } + } } \ No newline at end of file From dc37933fb8c0aca73951a6260df9f3202e6e28a1 Mon Sep 17 00:00:00 2001 From: Cody Larkin Date: Thu, 9 Apr 2026 16:53:29 -0400 Subject: [PATCH 3/4] cleanup prep and leave call prep --- RelayClient/MainPage.xaml.cs | 62 ++++++++++++++-------- RelayClient/Resources/Raw/wwwroot/index.js | 12 +++-- 2 files changed, 46 insertions(+), 28 deletions(-) diff --git a/RelayClient/MainPage.xaml.cs b/RelayClient/MainPage.xaml.cs index 9d66f62..7fccd16 100644 --- a/RelayClient/MainPage.xaml.cs +++ b/RelayClient/MainPage.xaml.cs @@ -249,6 +249,7 @@ public partial class MainPage : ContentPage case "rtc_call_left": { SafeSendRawToWebView("RTC call left notification received."); + RtcLeaveCallback(); break; } } @@ -416,6 +417,7 @@ public partial class MainPage : ContentPage } } + #region RTC Functions public async Task JoinRtcChannel() { if (string.IsNullOrWhiteSpace(_currentChannelId)) @@ -530,23 +532,36 @@ public partial class MainPage : ContentPage SafeSendRawToWebView("AnswerCallback failed: " + ex.Message); } } - - private void OnSendMessageButtonClicked(object sender, EventArgs e) - { - SafeSendRawToWebView($"Hello from C#!"); - } - private async void OnHybridWebViewRawMessageReceived(object sender, HybridWebViewRawMessageReceivedEventArgs e) + public async void RtcLeaveCallback() { - if (e.Message == "rtc_page_ready") + try { - await PushRtcContextToJsAsync(); - return; + await hybridWebView.InvokeJavaScriptAsync("RtcLeaveCall", [], []); + } + catch (Exception ex) + { + SafeSendRawToWebView("RtcLeaveCallback failed: " + ex.Message); } - - await DisplayAlertAsync("Raw Message Received", e.Message, "OK"); } + private async Task SendRtcSignalToJsAsync(string rawJson) + { + var jsArg = JsonSerializer.Serialize(rawJson); + await hybridWebView.EvaluateJavaScriptAsync($"window.handleRtcSignal({jsArg})"); + } //Remove? + + private async Task PushRtcContextToJsAsync() + { + var usernameJson = JsonSerializer.Serialize(_username); + var channelIdJson = JsonSerializer.Serialize(_currentChannelId); + + await hybridWebView.EvaluateJavaScriptAsync($"window.setUsername({usernameJson})"); + await hybridWebView.EvaluateJavaScriptAsync($"window.setChannelId({channelIdJson})"); + + Console.WriteLine($"[{_username}] pushed RTC context into HybridWebView."); + } //Remove? + public void SendRtcSignal(string json) { if (string.IsNullOrWhiteSpace(_serverPublicKey)) @@ -584,23 +599,24 @@ public partial class MainPage : ContentPage _wsc.Send(JsonSerializer.Serialize(payload)); Console.WriteLine($"[{_username}] sent RTC signal: {rtcSignal.Type} -> {rtcSignal.ChannelId}"); - } + } //Remove? - private async Task SendRtcSignalToJsAsync(string rawJson) - { - var jsArg = JsonSerializer.Serialize(rawJson); - await hybridWebView.EvaluateJavaScriptAsync($"window.handleRtcSignal({jsArg})"); - } - private async Task PushRtcContextToJsAsync() + #endregion + private void OnSendMessageButtonClicked(object sender, EventArgs e) { - var usernameJson = JsonSerializer.Serialize(_username); - var channelIdJson = JsonSerializer.Serialize(_currentChannelId); + SafeSendRawToWebView($"Hello from C#!"); + } - await hybridWebView.EvaluateJavaScriptAsync($"window.setUsername({usernameJson})"); - await hybridWebView.EvaluateJavaScriptAsync($"window.setChannelId({channelIdJson})"); + private async void OnHybridWebViewRawMessageReceived(object sender, HybridWebViewRawMessageReceivedEventArgs e) + { + if (e.Message == "rtc_page_ready") + { + await PushRtcContextToJsAsync(); + return; + } - Console.WriteLine($"[{_username}] pushed RTC context into HybridWebView."); + await DisplayAlertAsync("Raw Message Received", e.Message, "OK"); } private void SafeSendRawToWebView(string message) diff --git a/RelayClient/Resources/Raw/wwwroot/index.js b/RelayClient/Resources/Raw/wwwroot/index.js index da5125d..09db517 100644 --- a/RelayClient/Resources/Raw/wwwroot/index.js +++ b/RelayClient/Resources/Raw/wwwroot/index.js @@ -25,7 +25,6 @@ window.setChannelId = function(channelId) { currentChannelId = channelId; LogMessage("Channel set to: " + currentChannelId); }; -// let userMedia = getUserMedia() function LogMessage(msg) { const messageLog = document.getElementById("messageLog"); messageLog.value += '\r\n' + msg; @@ -96,7 +95,7 @@ async function ensurePeerConnection() { peerConnection.onicegatheringstatechange = () => { LogMessage("ICE gathering state: " + peerConnection.iceGatheringState); }; -} +} //Remove? async function ensureLocalMedia(forceReload = false) { const localMediaStatus = document.getElementById("localMediaStatus"); const localVideoStatus = document.getElementById("localVideoStatus"); @@ -239,7 +238,7 @@ async function joinChannelCall() { // } catch (err) { // LogMessage("joinChannelCall failed: " + err); // } -} +} //Combine with channelCallJoin async function ensurePeerConnection2() { @@ -353,6 +352,9 @@ async function IceCandidateAdded(candidate) } } + +async function RtcLeaveCall() +{} async function handleRtcSignal(rawJson) { try { const msg = typeof rawJson === "string" ? JSON.parse(rawJson) : rawJson; @@ -442,7 +444,7 @@ async function handleRtcSignal(rawJson) { } catch (err) { LogMessage("handleRtcSignal failed: " + err); } -} +} //Remove? async function loadDevices() { try { @@ -526,7 +528,7 @@ async function waitForIceGatheringComplete(pc) { pc.addEventListener("icegatheringstatechange", checkState); }); -} +} //Remove? window.handleRtcSignal = handleRtcSignal; From 63a12b8d178e2600b3c80f3726f137e498d35466 Mon Sep 17 00:00:00 2001 From: Cody Larkin Date: Fri, 10 Apr 2026 00:55:15 -0400 Subject: [PATCH 4/4] review for cleanup --- RelayClient/Resources/Raw/wwwroot/index.js | 6 +++--- start-all.ps1 | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/RelayClient/Resources/Raw/wwwroot/index.js b/RelayClient/Resources/Raw/wwwroot/index.js index 09db517..9af4de3 100644 --- a/RelayClient/Resources/Raw/wwwroot/index.js +++ b/RelayClient/Resources/Raw/wwwroot/index.js @@ -369,7 +369,7 @@ async function handleRtcSignal(rawJson) { const offer = await peerConnection.createOffer(); await peerConnection.setLocalDescription(offer); - await waitForIceGatheringComplete(peerConnection); + // await waitForIceGatheringComplete(peerConnection); const payload = { type: "rtc_offer", @@ -401,7 +401,7 @@ async function handleRtcSignal(rawJson) { const answer = await peerConnection.createAnswer(); await peerConnection.setLocalDescription(answer); - await waitForIceGatheringComplete(peerConnection); + // await waitForIceGatheringComplete(peerConnection); const payload = { type: "rtc_answer", @@ -530,7 +530,7 @@ async function waitForIceGatheringComplete(pc) { }); } //Remove? -window.handleRtcSignal = handleRtcSignal; +// window.handleRtcSignal = handleRtcSignal; window.addEventListener("HybridWebViewMessageReceived", function (e) { LogMessage("Raw message: " + e.detail.message); diff --git a/start-all.ps1 b/start-all.ps1 index a258d13..2b864a0 100644 --- a/start-all.ps1 +++ b/start-all.ps1 @@ -64,19 +64,19 @@ Start-Sleep -Seconds 5 & '$clientExe' --user Ru_Kira "@ -#$testScript = New-TabScript -Name "Test" -Content @" -#Set-Location '$root' -#Start-Sleep -Seconds 25 -#& '$clientExe' --user Test -#"@ +# $testScript = New-TabScript -Name "Test" -Content @" +# Set-Location '$root' +# Start-Sleep -Seconds 25 +# & '$clientExe' --user Test +# "@ $wtArgs = @( "new-tab --title `"SurrealDB`" `"$ps`" -NoExit -ExecutionPolicy Bypass -File `"$dockerScript`"", "new-tab --title `"RelayCore`" `"$ps`" -NoExit -ExecutionPolicy Bypass -File `"$coreScript`"", "new-tab --title `"RelayServer`" `"$ps`" -NoExit -ExecutionPolicy Bypass -File `"$serverScript`"", "new-tab --title `"Keeper317`" `"$ps`" -NoExit -ExecutionPolicy Bypass -File `"$keeperScript`"", +# "new-tab --title `"Test`" `"$ps`" -NoExit -ExecutionPolicy Bypass -File `"$testScript`"", "new-tab --title `"Ru_Kira`" `"$ps`" -NoExit -ExecutionPolicy Bypass -File `"$kiraScript`"" - #"new-tab --title `"Test`" `"$ps`" -NoExit -ExecutionPolicy Bypass -File `"$testScript`"" ) -join " ; " Write-Host ""