Messaging works, let's start there.
This commit is contained in:
@@ -115,11 +115,9 @@ public partial class MainPage : ContentPage
|
||||
using var doc = JsonDocument.Parse(e.Data);
|
||||
var root = doc.RootElement;
|
||||
|
||||
if (!root.TryGetProperty("Type", out var typeElement))
|
||||
if (!TryReadSignalType(root, out var type))
|
||||
return;
|
||||
|
||||
var type = (SignalType) typeElement.GetInt32();
|
||||
|
||||
if (type == SignalType.ChannelList)
|
||||
{
|
||||
var channelList = JsonSerializer.Deserialize<SocketChannelList>(e.Data);
|
||||
@@ -218,25 +216,46 @@ public partial class MainPage : ContentPage
|
||||
{
|
||||
case SignalType.OfferUpdated :
|
||||
{
|
||||
var offer = await GetRtcOffer();
|
||||
await SendRtcSignalToJsAsync(offer);
|
||||
if (!string.Equals(rtcNotification.TargetUsername, _username, StringComparison.OrdinalIgnoreCase))
|
||||
break;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(_currentChannelId) || string.IsNullOrWhiteSpace(rtcNotification.Username))
|
||||
break;
|
||||
|
||||
var offer = await ServerAPI.GetOfferForChannelAsync(_currentChannelId, rtcNotification.Username, _username);
|
||||
if (offer is not null)
|
||||
{
|
||||
await SendRtcOfferToJsAsync(rtcNotification.Username, offer);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SignalType.AnswerUpdated:
|
||||
{
|
||||
var answer = await ServerAPI.GetAnswerForChannelAsync(_currentChannelId);
|
||||
if (!string.Equals(rtcNotification.TargetUsername, _username, StringComparison.OrdinalIgnoreCase))
|
||||
break;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(_currentChannelId) || string.IsNullOrWhiteSpace(rtcNotification.Username))
|
||||
break;
|
||||
|
||||
var answer = await ServerAPI.GetAnswerForChannelAsync(_currentChannelId, rtcNotification.Username, _username);
|
||||
if (answer is not null)
|
||||
{
|
||||
await AnswerCallback(answer);
|
||||
await SendRtcAnswerToJsAsync(rtcNotification.Username, answer);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SignalType.CandidateAdded:
|
||||
{
|
||||
if (!string.Equals(rtcNotification.TargetUsername, _username, StringComparison.OrdinalIgnoreCase))
|
||||
break;
|
||||
|
||||
try
|
||||
{
|
||||
IceCandidate? iceCandidate = JsonSerializer.Deserialize<IceCandidate>(rtcNotification.Direction);
|
||||
IceCandidateCallback(iceCandidate);
|
||||
if (iceCandidate is not null && !string.IsNullOrWhiteSpace(rtcNotification.Username))
|
||||
{
|
||||
await SendRtcCandidateToJsAsync(rtcNotification.Username, iceCandidate);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -248,7 +267,10 @@ public partial class MainPage : ContentPage
|
||||
case SignalType.CallLeft:
|
||||
{
|
||||
SafeSendRawToWebView("RTC call left notification received.");
|
||||
RtcLeaveCallback();
|
||||
if (!string.IsNullOrWhiteSpace(rtcNotification.Username))
|
||||
{
|
||||
RtcLeaveCallback(rtcNotification.Username);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -264,7 +286,7 @@ public partial class MainPage : ContentPage
|
||||
if (pyload is null)
|
||||
return;
|
||||
|
||||
if (pyload.RecipientUsername == _username)
|
||||
if (!string.Equals(pyload.RecipientUsername, _username, StringComparison.OrdinalIgnoreCase))
|
||||
return;
|
||||
|
||||
Console.WriteLine($"[{_username}] received encrypted payload for {pyload.RecipientUsername}");
|
||||
@@ -417,20 +439,24 @@ public partial class MainPage : ContentPage
|
||||
}
|
||||
|
||||
#region RTC Functions
|
||||
public async Task<bool> JoinRtcChannel()
|
||||
public async Task<string> JoinRtcChannel()
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(_currentChannelId))
|
||||
return false;
|
||||
return "[]";
|
||||
|
||||
_wsc.Send($"RTC_JOIN_CHANNEL|{_username}|{_currentChannelId}");
|
||||
|
||||
SafeSendRawToWebView($"Attempting to join RTC Channel {_currentChannelName} | {_currentChannelId} ");
|
||||
|
||||
bool active = await ServerAPI.GetIsChannelActiveAsync(_currentChannelId);
|
||||
var participants = await ServerAPI.GetParticipantsForChannelAsync(_currentChannelId);
|
||||
var otherParticipants = participants
|
||||
.Where(x => !string.Equals(x, _username, StringComparison.OrdinalIgnoreCase))
|
||||
.Distinct(StringComparer.OrdinalIgnoreCase)
|
||||
.ToList();
|
||||
|
||||
SafeSendRawToWebView($"Rtc Channel {_currentChannelName} | {_currentChannelId} is active: {active}");
|
||||
SafeSendRawToWebView($"RTC participants in {_currentChannelName}: {string.Join(", ", otherParticipants)}");
|
||||
|
||||
return active;
|
||||
return JsonSerializer.Serialize(otherParticipants);
|
||||
}
|
||||
|
||||
public void LeaveRtcChannel()
|
||||
@@ -441,17 +467,14 @@ public partial class MainPage : ContentPage
|
||||
_wsc.Send($"RTC_LEAVE_CHANNEL|{_username}|{_currentChannelId}");
|
||||
}
|
||||
|
||||
public async void WriteRtcOffer(string json)
|
||||
public async Task WriteRtcOffer(string json)
|
||||
{
|
||||
try
|
||||
{
|
||||
RtcDescription? description = JsonSerializer.Deserialize<RtcDescription>(json);
|
||||
DBOffer offer = new DBOffer
|
||||
{
|
||||
ChannelId = _currentChannelId,
|
||||
Username = _username,
|
||||
SessionDescription = description
|
||||
};
|
||||
RtcOffer? offer = JsonSerializer.Deserialize<RtcOffer>(json);
|
||||
if (offer is null)
|
||||
return;
|
||||
|
||||
await ServerAPI.PostOfferAsync(offer);
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -460,25 +483,15 @@ public partial class MainPage : ContentPage
|
||||
}
|
||||
|
||||
}
|
||||
public async Task<string> GetRtcOffer()
|
||||
{
|
||||
RtcDescription? offer = await ServerAPI.GetOffersForChannelAsync(_currentChannelId);
|
||||
return JsonSerializer.Serialize(offer);
|
||||
}
|
||||
|
||||
public async void WriteRtcAnswer(string json)
|
||||
public async Task WriteRtcAnswer(string json)
|
||||
{
|
||||
// SafeSendRawToWebView("WriteRtcAnswer entered with: " + json);
|
||||
|
||||
try
|
||||
{
|
||||
RtcDescription? description = JsonSerializer.Deserialize<RtcDescription>(json);
|
||||
DBOffer answer = new DBOffer
|
||||
{
|
||||
ChannelId = _currentChannelId,
|
||||
Username = _username,
|
||||
SessionDescription = description
|
||||
};
|
||||
RtcAnswer? answer = JsonSerializer.Deserialize<RtcAnswer>(json);
|
||||
if (answer is null)
|
||||
return;
|
||||
|
||||
await ServerAPI.PostAnswerAsync(answer);
|
||||
SafeSendRawToWebView("WriteRtcAnswer posted successfully");
|
||||
}
|
||||
@@ -488,19 +501,15 @@ public partial class MainPage : ContentPage
|
||||
}
|
||||
}
|
||||
|
||||
public async void WriteIceCandidate(string json)
|
||||
public async Task WriteIceCandidate(string json)
|
||||
{
|
||||
try
|
||||
{
|
||||
IceCandidate? candidate = JsonSerializer.Deserialize<IceCandidate>(json);
|
||||
DBIceCandidate DBCandidate = new DBIceCandidate
|
||||
{
|
||||
ChannelId = _currentChannelId,
|
||||
Username = _username,
|
||||
Candidate = candidate
|
||||
};
|
||||
if (candidate == null) return;
|
||||
await ServerAPI.PostIceCandidateAsync(DBCandidate);
|
||||
DBIceCandidate? dbCandidate = JsonSerializer.Deserialize<DBIceCandidate>(json);
|
||||
if (dbCandidate is null)
|
||||
return;
|
||||
|
||||
await ServerAPI.PostIceCandidateAsync(dbCandidate);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -508,47 +517,45 @@ public partial class MainPage : ContentPage
|
||||
}
|
||||
}
|
||||
|
||||
public async void IceCandidateCallback(IceCandidate candidate)
|
||||
private async Task SendRtcOfferToJsAsync(string remoteUsername, RtcSessionDescription offer)
|
||||
{
|
||||
try
|
||||
{
|
||||
await hybridWebView.InvokeJavaScriptAsync("IceCandidateAdded", [candidate], [HybridJSType.Default.IceCandidate]);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
SafeSendRawToWebView("WriteIceCandidate failed: " + ex.Message);
|
||||
}
|
||||
}
|
||||
public async Task AnswerCallback(RtcDescription answer)
|
||||
{
|
||||
answer.sdp = answer.sdp.Replace("\r\n", "(rn)");
|
||||
try
|
||||
{
|
||||
await hybridWebView.InvokeJavaScriptAsync("AnswerCallbackJS", [answer], [HybridJSType.Default.RtcDescription]);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
SafeSendRawToWebView("AnswerCallback failed: " + ex.Message);
|
||||
}
|
||||
var remoteUsernameJson = JsonSerializer.Serialize(remoteUsername);
|
||||
var offerJson = JsonSerializer.Serialize(offer);
|
||||
await hybridWebView.EvaluateJavaScriptAsync($"window.handleRtcOffer({remoteUsernameJson}, {offerJson})");
|
||||
}
|
||||
|
||||
public async void RtcLeaveCallback()
|
||||
private async Task SendRtcAnswerToJsAsync(string remoteUsername, RtcSessionDescription answer)
|
||||
{
|
||||
var remoteUsernameJson = JsonSerializer.Serialize(remoteUsername);
|
||||
var answerJson = JsonSerializer.Serialize(answer);
|
||||
await hybridWebView.EvaluateJavaScriptAsync($"window.handleRtcAnswer({remoteUsernameJson}, {answerJson})");
|
||||
}
|
||||
|
||||
private async Task SendRtcCandidateToJsAsync(string remoteUsername, IceCandidate candidate)
|
||||
{
|
||||
var remoteUsernameJson = JsonSerializer.Serialize(remoteUsername);
|
||||
var candidateJson = JsonSerializer.Serialize(candidate);
|
||||
await hybridWebView.EvaluateJavaScriptAsync($"window.handleRtcCandidate({remoteUsernameJson}, {candidateJson})");
|
||||
}
|
||||
|
||||
public async void RtcLeaveCallback(string username)
|
||||
{
|
||||
try
|
||||
{
|
||||
await hybridWebView.InvokeJavaScriptAsync("RtcLeaveCall", [], []);
|
||||
var usernameJson = JsonSerializer.Serialize(username);
|
||||
await hybridWebView.EvaluateJavaScriptAsync($"window.handleRtcParticipantLeft({usernameJson})");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
SafeSendRawToWebView("RtcLeaveCallback failed: " + ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private async Task SendRtcSignalToJsAsync(string rawJson)
|
||||
{
|
||||
var jsArg = JsonSerializer.Serialize(rawJson);
|
||||
await hybridWebView.EvaluateJavaScriptAsync($"window.handleRtcSignal({jsArg})");
|
||||
} //Remove?
|
||||
await hybridWebView.EvaluateJavaScriptAsync($"window.handleRtcSignal?.({jsArg})");
|
||||
}
|
||||
|
||||
private async Task PushRtcContextToJsAsync()
|
||||
{
|
||||
@@ -634,15 +641,50 @@ public partial class MainPage : ContentPage
|
||||
}
|
||||
|
||||
[JsonSourceGenerationOptions(WriteIndented = false)]
|
||||
[JsonSerializable(typeof(RtcDescription))]
|
||||
[JsonSerializable(typeof(List<RtcSignalMessage>))]
|
||||
[JsonSerializable(typeof(RtcSessionDescription))]
|
||||
[JsonSerializable(typeof(IceCandidate))]
|
||||
[JsonSerializable(typeof(List<IceCandidate>))]
|
||||
[JsonSerializable(typeof(string))]
|
||||
internal partial class HybridJSType : JsonSerializerContext
|
||||
{
|
||||
// This type's attributes specify JSON serialization info to preserve type structure
|
||||
// for trimmed builds.
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static bool TryReadSignalType(JsonElement root, out SignalType type)
|
||||
{
|
||||
if (TryGetProperty(root, "type", out var typeElement))
|
||||
{
|
||||
if (typeElement.ValueKind == JsonValueKind.String &&
|
||||
Enum.TryParse(typeElement.GetString(), true, out SignalType parsedType))
|
||||
{
|
||||
type = parsedType;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (typeElement.ValueKind == JsonValueKind.Number &&
|
||||
typeElement.TryGetInt32(out var rawValue))
|
||||
{
|
||||
type = (SignalType)rawValue;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
type = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool TryGetProperty(JsonElement root, string propertyName, out JsonElement value)
|
||||
{
|
||||
foreach (var property in root.EnumerateObject())
|
||||
{
|
||||
if (string.Equals(property.Name, propertyName, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
value = property.Value;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
value = default;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user