Compare commits
3 Commits
a56e246095
...
b70189c619
| Author | SHA1 | Date | |
|---|---|---|---|
| b70189c619 | |||
| 88c5d597d3 | |||
| 4a8170c448 |
@@ -62,8 +62,6 @@
|
|||||||
<!-- <WebView Source="test.html"/> -->
|
<!-- <WebView Source="test.html"/> -->
|
||||||
<Grid RowDefinitions="Auto,*"
|
<Grid RowDefinitions="Auto,*"
|
||||||
ColumnDefinitions="*">
|
ColumnDefinitions="*">
|
||||||
<Button Text="Send message to JavaScript"
|
|
||||||
Clicked="OnSendMessageButtonClicked" />
|
|
||||||
<HybridWebView x:Name="hybridWebView"
|
<HybridWebView x:Name="hybridWebView"
|
||||||
RawMessageReceived="OnHybridWebViewRawMessageReceived"
|
RawMessageReceived="OnHybridWebViewRawMessageReceived"
|
||||||
Grid.Row="1" />
|
Grid.Row="1" />
|
||||||
|
|||||||
@@ -198,13 +198,13 @@ public partial class MainPage : ContentPage
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (type == SignalType.OfferUpdated || type == SignalType.AnswerUpdated || type == SignalType.CandidateAdded || type == SignalType.CallLeft)
|
if (type is SignalType.OfferUpdated or SignalType.AnswerUpdated or SignalType.CandidateAdded or SignalType.CallLeft)
|
||||||
{
|
{
|
||||||
var rtcNotification = JsonSerializer.Deserialize<RtcNotificationMessage>(e.Data);
|
var rtcNotification = JsonSerializer.Deserialize<RtcNotificationMessage>(e.Data);
|
||||||
if (rtcNotification is null)
|
if (rtcNotification is null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var notificationType = rtcNotification.Type ?? null;
|
var notificationType = rtcNotification.Type;
|
||||||
var notificationChannelId = rtcNotification.ChannelId ?? string.Empty;
|
var notificationChannelId = rtcNotification.ChannelId ?? string.Empty;
|
||||||
|
|
||||||
if (notificationChannelId != _currentChannelId)
|
if (notificationChannelId != _currentChannelId)
|
||||||
@@ -216,8 +216,11 @@ public partial class MainPage : ContentPage
|
|||||||
{
|
{
|
||||||
switch (notificationType)
|
switch (notificationType)
|
||||||
{
|
{
|
||||||
case SignalType.OfferUpdated :
|
case SignalType.OfferUpdated:
|
||||||
{
|
{
|
||||||
|
if (rtcNotification.Username == _username)
|
||||||
|
break;
|
||||||
|
|
||||||
var offer = await GetRtcOffer();
|
var offer = await GetRtcOffer();
|
||||||
await SendRtcSignalToJsAsync(offer);
|
await SendRtcSignalToJsAsync(offer);
|
||||||
break;
|
break;
|
||||||
@@ -233,9 +236,16 @@ public partial class MainPage : ContentPage
|
|||||||
}
|
}
|
||||||
case SignalType.CandidateAdded:
|
case SignalType.CandidateAdded:
|
||||||
{
|
{
|
||||||
|
if (rtcNotification.Username == _username)
|
||||||
|
break;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
IceCandidate? iceCandidate = JsonSerializer.Deserialize<IceCandidate>(rtcNotification.Direction);
|
IceCandidate? iceCandidate = JsonSerializer.Deserialize<IceCandidate>(rtcNotification.Direction);
|
||||||
|
|
||||||
|
if (iceCandidate is null)
|
||||||
|
break;
|
||||||
|
|
||||||
IceCandidateCallback(iceCandidate);
|
IceCandidateCallback(iceCandidate);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -600,6 +610,11 @@ public partial class MainPage : ContentPage
|
|||||||
Console.WriteLine($"[{_username}] sent RTC signal: {rtcSignal.Type} -> {rtcSignal.ChannelId}");
|
Console.WriteLine($"[{_username}] sent RTC signal: {rtcSignal.Type} -> {rtcSignal.ChannelId}");
|
||||||
} //Remove?
|
} //Remove?
|
||||||
|
|
||||||
|
//public async Task<string> GetRtcParticipants() // TODO: UNCOMMENT AND ADD
|
||||||
|
//{
|
||||||
|
// var participants = await ServerAPI.GetRtcParticipantsAsync(_currentChannelId);
|
||||||
|
// return JsonSerializer.Serialize(participants);
|
||||||
|
//}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
private void OnSendMessageButtonClicked(object sender, EventArgs e)
|
private void OnSendMessageButtonClicked(object sender, EventArgs e)
|
||||||
@@ -615,7 +630,7 @@ public partial class MainPage : ContentPage
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await DisplayAlertAsync("Raw Message Received", e.Message, "OK");
|
SafeSendRawToWebView($"JS RAW -> C#: {e.Message}");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SafeSendRawToWebView(string message)
|
private void SafeSendRawToWebView(string message)
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
let peerConnection = null;
|
let peerConnection = null;
|
||||||
|
let peerConnections = {};
|
||||||
|
let remoteStreams = {};
|
||||||
let localStream = null;
|
let localStream = null;
|
||||||
let currentUsername = null;
|
let currentUsername = null;
|
||||||
let currentChannelId = null;
|
let currentChannelId = null;
|
||||||
@@ -241,7 +243,7 @@ async function joinChannelCall() {
|
|||||||
// }
|
// }
|
||||||
} //Combine with channelCallJoin
|
} //Combine with channelCallJoin
|
||||||
|
|
||||||
async function ensurePeerConnection2()
|
async function ensurePeerConnectionForUser(username)
|
||||||
{
|
{
|
||||||
if (peerConnection) return;
|
if (peerConnection) return;
|
||||||
peerConnection = new RTCPeerConnection(configuration);
|
peerConnection = new RTCPeerConnection(configuration);
|
||||||
@@ -263,7 +265,7 @@ async function ensurePeerConnection2()
|
|||||||
console.log(`Ice Candidate: ${JSON.stringify(event.candidate)}`);
|
console.log(`Ice Candidate: ${JSON.stringify(event.candidate)}`);
|
||||||
// LogMessage(`Ice Candidate: ${JSON.stringify(event.candidate)}`);
|
// LogMessage(`Ice Candidate: ${JSON.stringify(event.candidate)}`);
|
||||||
await window.HybridWebView.InvokeDotNet("WriteIceCandidate", [JSON.stringify(event.candidate)]);
|
await window.HybridWebView.InvokeDotNet("WriteIceCandidate", [JSON.stringify(event.candidate)]);
|
||||||
await IceCandidateAdded(event.candidate);
|
//await IceCandidateAdded(event.candidate);
|
||||||
};
|
};
|
||||||
|
|
||||||
peerConnection.ontrack = (event) => {
|
peerConnection.ontrack = (event) => {
|
||||||
@@ -297,7 +299,7 @@ async function ensurePeerConnection2()
|
|||||||
async function channelCallJoin(activeCall)
|
async function channelCallJoin(activeCall)
|
||||||
{
|
{
|
||||||
// LogMessage("Active call: " + activeCall);
|
// LogMessage("Active call: " + activeCall);
|
||||||
await ensurePeerConnection2();
|
await ensurePeerConnectionForUser();
|
||||||
await ensureLocalMedia();
|
await ensureLocalMedia();
|
||||||
await applyLocalStreamToPeerConnection();
|
await applyLocalStreamToPeerConnection();
|
||||||
|
|
||||||
@@ -529,7 +531,7 @@ async function waitForIceGatheringComplete(pc) {
|
|||||||
});
|
});
|
||||||
} //Remove?
|
} //Remove?
|
||||||
|
|
||||||
// window.handleRtcSignal = handleRtcSignal;
|
window.handleRtcSignal = handleRtcSignal;
|
||||||
|
|
||||||
window.addEventListener("HybridWebViewMessageReceived", function (e) {
|
window.addEventListener("HybridWebViewMessageReceived", function (e) {
|
||||||
LogMessage("Raw message: " + e.detail.message);
|
LogMessage("Raw message: " + e.detail.message);
|
||||||
|
|||||||
@@ -1,170 +0,0 @@
|
|||||||
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<RTCSessionDescription> onOfferCreated)
|
|
||||||
{
|
|
||||||
var sdp = NativeWebRtc.CreateOffer(_nativeHandle);
|
|
||||||
onOfferCreated?.Invoke(new RTCSessionDescription { Type = RTCSdpType.Offer, Sdp = sdp });
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task CreateAnswer(Action<RTCSessionDescription> 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<string, string>("ReceiveOffer", (fromId, sdp) =>
|
|
||||||
{
|
|
||||||
MainThread.BeginInvokeOnMainThread(async () =>
|
|
||||||
{
|
|
||||||
await HandleOffer(fromId, sdp);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
_signal.On<string, string>("ReceiveAnswer", (fromId, sdp) =>
|
|
||||||
{
|
|
||||||
MainThread.BeginInvokeOnMainThread(async () =>
|
|
||||||
{
|
|
||||||
await HandleAnswer(sdp);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
_signal.On<string, string>("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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -70,6 +70,12 @@ public static class RtcEndpoints
|
|||||||
{
|
{
|
||||||
return Results.Ok(await rtcCallService.GetAnswersAsync(channelId));
|
return Results.Ok(await rtcCallService.GetAnswersAsync(channelId));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
//app.MapGet("/api/rtc/participants/{channelId}", (string channelId) => // TODO: UNCOMMENT AND ADD
|
||||||
|
//{
|
||||||
|
// var participants = RtcChannelPresenceService.GetUsernamesInChannel(channelId);
|
||||||
|
// return Results.Ok(participants);
|
||||||
|
//});
|
||||||
|
|
||||||
// Return the latest answer stored for the specified channel.
|
// Return the latest answer stored for the specified channel.
|
||||||
app.MapGet("/api/rtc/answer/{channelId}", async (string channelId, RtcCallService rtcCallService) =>
|
app.MapGet("/api/rtc/answer/{channelId}", async (string channelId, RtcCallService rtcCallService) =>
|
||||||
|
|||||||
Reference in New Issue
Block a user