setup client to make calls to servers api for webrtc data management
This commit is contained in:
@@ -42,6 +42,7 @@ public partial class MainPage : ContentPage
|
|||||||
_wsc.Send("GET_CHANNELS");
|
_wsc.Send("GET_CHANNELS");
|
||||||
|
|
||||||
hybridWebView.SetInvokeJavaScriptTarget(this);
|
hybridWebView.SetInvokeJavaScriptTarget(this);
|
||||||
|
ServerAPI.setupClient();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -353,21 +354,58 @@ public partial class MainPage : ContentPage
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void JoinRtcChannel()
|
public async Task<bool> JoinRtcChannel()
|
||||||
{
|
{
|
||||||
//TODO: get bool value for if channel ID has an active call
|
//TODO: get bool value for if channel ID has an active call
|
||||||
//TODO: Join RTC using current channel ID
|
//TODO: Join RTC using current channel ID
|
||||||
|
hybridWebView.SendRawMessage($"Attempting to join RTC Channel {_currentChannelName}");
|
||||||
|
bool active = await ServerAPI.GetIsChannelActiveAsync(_currentChannelId);
|
||||||
|
hybridWebView.SendRawMessage($"Rtc Channel {_currentChannelName} is active: {active}");
|
||||||
|
return active;
|
||||||
|
//await hybridWebView.EvaluateJavaScriptAsync($"window.channelCallJoin({active})");
|
||||||
|
|
||||||
|
}
|
||||||
|
public async void WriteRtcOffer(string json)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
RtcDescription? description = JsonSerializer.Deserialize<RtcDescription>(json);
|
||||||
|
DBOffer offer = new DBOffer
|
||||||
|
{
|
||||||
|
ChannelId = _currentChannelId,
|
||||||
|
Username = _username,
|
||||||
|
SessionDescription = description
|
||||||
|
};
|
||||||
|
await ServerAPI.PostOfferAsync(offer);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
hybridWebView.SendRawMessage(ex.Message);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void WriteRtcOffer(string json)
|
|
||||||
{
|
|
||||||
RTCOffer? offer = JsonSerializer.Deserialize<RTCOffer>(json);
|
|
||||||
}
|
}
|
||||||
private class RTCOffer
|
public async Task<string> GetRtcOffer()
|
||||||
{
|
{
|
||||||
private string type { get; set; }
|
RtcDescription? offer = await ServerAPI.GetOffersForChannelAsync(_currentChannelId);
|
||||||
private string sdp { get; set; }
|
return JsonSerializer.Serialize(offer);
|
||||||
}
|
}
|
||||||
|
public async void WriteRtcAnswer(string json)
|
||||||
|
{
|
||||||
|
RtcDescription? description = JsonSerializer.Deserialize<RtcDescription>(json);
|
||||||
|
DBOffer answer = new DBOffer
|
||||||
|
{
|
||||||
|
ChannelId = _currentChannelId,
|
||||||
|
Username = _username,
|
||||||
|
SessionDescription = description
|
||||||
|
};
|
||||||
|
await ServerAPI.PostAnswerAsync(answer);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async void AnswerCallback(RtcDescription answer)
|
||||||
|
{
|
||||||
|
await hybridWebView.EvaluateJavaScriptAsync($"window.AnswerCallback({answer})");
|
||||||
|
}
|
||||||
|
|
||||||
private void OnSendMessageButtonClicked(object sender, EventArgs e)
|
private void OnSendMessageButtonClicked(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
hybridWebView.SendRawMessage($"Hello from C#!");
|
hybridWebView.SendRawMessage($"Hello from C#!");
|
||||||
|
|||||||
@@ -166,33 +166,35 @@ async function ensureLocalMedia() {
|
|||||||
async function joinChannelCall() {
|
async function joinChannelCall() {
|
||||||
LogMessage("Current username: " + currentUsername);
|
LogMessage("Current username: " + currentUsername);
|
||||||
LogMessage("Current channel: " + currentChannelId);
|
LogMessage("Current channel: " + currentChannelId);
|
||||||
//TODO: Update Server DB to hold bool if channel has an active call
|
LogMessage("Joining RTCChannel");
|
||||||
//TODO: First check if channel already has an active offer, if it does join with an answer, otherwise make a new offer
|
let active = await window.HybridWebView.InvokeDotNet("JoinRtcChannel");
|
||||||
try {
|
await channelCallJoin(active);
|
||||||
if (!currentChannelId) {
|
LogMessage("Joined RTCChannel");
|
||||||
LogMessage("No current channel set.");
|
// return;
|
||||||
return;
|
// try {
|
||||||
}
|
// if (!currentChannelId) {
|
||||||
|
// LogMessage("No current channel set.");
|
||||||
await ensurePeerConnection();
|
// return;
|
||||||
await ensureLocalMedia();
|
// }
|
||||||
|
//
|
||||||
LogMessage(`Joining call with media: audio=${hasAudioTrack()} video=${hasVideoTrack()}`);
|
// await ensurePeerConnection();
|
||||||
|
// await ensureLocalMedia();
|
||||||
const payload = {
|
//
|
||||||
type: "rtc_join",
|
// LogMessage(`Joining call with media: audio=${hasAudioTrack()} video=${hasVideoTrack()}`);
|
||||||
from: currentUsername,
|
//
|
||||||
channelId: currentChannelId
|
// const payload = {
|
||||||
};
|
// type: "rtc_join",
|
||||||
|
// from: currentUsername,
|
||||||
LogMessage("Requesting join for channel " + currentChannelId);
|
// channelId: currentChannelId
|
||||||
await window.HybridWebView.InvokeDotNet("SendRtcSignal", [JSON.stringify(payload)]);
|
// };
|
||||||
} catch (err) {
|
//
|
||||||
LogMessage("joinChannelCall failed: " + err);
|
// LogMessage("Requesting join for channel " + currentChannelId);
|
||||||
}
|
// await window.HybridWebView.InvokeDotNet("SendRtcSignal", [JSON.stringify(payload)]);
|
||||||
|
// } catch (err) {
|
||||||
|
// LogMessage("joinChannelCall failed: " + err);
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async function ensurePeerConnection2()
|
async function ensurePeerConnection2()
|
||||||
{
|
{
|
||||||
if (peerConnection) return;
|
if (peerConnection) return;
|
||||||
@@ -215,60 +217,36 @@ async function ensurePeerConnection2()
|
|||||||
console.log(
|
console.log(
|
||||||
`ICE connection state change: ${peerConnection.iceConnectionState}`);
|
`ICE connection state change: ${peerConnection.iceConnectionState}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
async function channelCallJoin(activeCall)
|
async function channelCallJoin(activeCall)
|
||||||
{
|
{
|
||||||
|
LogMessage("Active call: " + activeCall);
|
||||||
await ensurePeerConnection2();
|
await ensurePeerConnection2();
|
||||||
|
|
||||||
if (activeCall)
|
if (activeCall)
|
||||||
{
|
{
|
||||||
const offer = roomSnapshot.data().offer; //TODO: Replace with active call offer from DB using the active ID for current channel
|
const rawJson = await window.HybridWebView.InvokeDotNet("GetRtcOffer");
|
||||||
|
const offer = typeof rawJson === "string" ? JSON.parse(rawJson) : rawJson;
|
||||||
await peerConnection.setRemoteDescription(offer);
|
await peerConnection.setRemoteDescription(offer);
|
||||||
const answer = await peerConnection.createAnswer();
|
const answer = await peerConnection.createAnswer();
|
||||||
await peerConnection.setLocalDescription(answer);
|
await peerConnection.setLocalDescription(answer);
|
||||||
|
|
||||||
const roomAnswer = {
|
LogMessage(`Joining call with media answer: ${JSON.stringify(answer)}`);
|
||||||
answer: {
|
await window.HybridWebView.InvokeDotNet("WriteRtcAnswer", [JSON.stringify(roomAnswer)]);
|
||||||
type: answer.type,
|
//TODO: Update offer in SurrealDB to include answer
|
||||||
sdp: answer.sdp
|
|
||||||
}
|
|
||||||
}
|
|
||||||
await roomRef.update(roomAnswer); //TODO: Update offer in SurrealDB to include answer
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
const offer = await peerConnection.createOffer();
|
const offer = await peerConnection.createOffer();
|
||||||
await peerConnection.setLocalDescription(offer);
|
await peerConnection.setLocalDescription(offer);
|
||||||
|
|
||||||
const roomOffer = {
|
|
||||||
offer: {
|
|
||||||
type: offer.type,
|
|
||||||
sdp: offer.sdp
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await window.HybridWebView.InvokeDotNet("WriteRtcOffer", [JSON.stringify(offer)]);
|
await window.HybridWebView.InvokeDotNet("WriteRtcOffer", [JSON.stringify(offer)]);
|
||||||
|
LogMessage(`Joining call with media offer: ${JSON.stringify(offer)}`);
|
||||||
//TODO: Write roomId to surreal DB with channel id as active call
|
|
||||||
|
|
||||||
//TODO: Add callback function for when call is answered to replace following code block
|
|
||||||
roomRef.onSnapshot(async snapshot => {
|
|
||||||
console.log('Got updated room:', snapshot.data());
|
|
||||||
const data = snapshot.data();
|
|
||||||
if (!peerConnection.currentRemoteDescription && data.answer) {
|
|
||||||
console.log('Set remote description: ', data.answer);
|
|
||||||
const answer = new RTCSessionDescription(data.answer)
|
|
||||||
await peerConnection.setRemoteDescription(answer);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
localStream.getTracks().forEach(track => {
|
localStream.getTracks().forEach(track => {
|
||||||
peerConnection.addTrack(track, localStream);
|
peerConnection.addTrack(track, localStream);
|
||||||
});
|
});
|
||||||
|
|
||||||
//TODO: collect ICE candidates
|
|
||||||
|
|
||||||
peerConnection.addEventListener('track', event => {
|
peerConnection.addEventListener('track', event => {
|
||||||
LogMessage("Received track: " + event.streams[0]);
|
LogMessage("Received track: " + event.streams[0]);
|
||||||
event.streams[0].getTracks().forEach(track => {
|
event.streams[0].getTracks().forEach(track => {
|
||||||
@@ -277,9 +255,23 @@ async function channelCallJoin(activeCall)
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
async function AnswerCallback(answer)
|
||||||
|
{
|
||||||
|
LogMessage("Answer: " + JSON.stringify(answer));
|
||||||
|
|
||||||
|
if (!peerConnection.currentRemoteDescription && answer.answer)
|
||||||
|
{
|
||||||
|
LogMessage("Current answer: " + answer);
|
||||||
|
const desc = new RTCSessionDescription(answer);
|
||||||
|
await peerConnection.setRemoteDescription(desc);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function CollectIceCandidates()
|
||||||
|
{
|
||||||
|
//TODO: collect ICE candidates
|
||||||
|
}
|
||||||
async function handleRtcSignal(rawJson) {
|
async function handleRtcSignal(rawJson) {
|
||||||
try {
|
try {
|
||||||
const msg = typeof rawJson === "string" ? JSON.parse(rawJson) : rawJson;
|
const msg = typeof rawJson === "string" ? JSON.parse(rawJson) : rawJson;
|
||||||
|
|||||||
121
RelayClient/ServerAPI.cs
Normal file
121
RelayClient/ServerAPI.cs
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
using System.Net.Http.Headers;
|
||||||
|
using System.Net.Http.Json;
|
||||||
|
using System.Text.Json;
|
||||||
|
|
||||||
|
namespace RelayClient;
|
||||||
|
|
||||||
|
public class ServerAPI
|
||||||
|
{
|
||||||
|
static HttpClient client = new HttpClient { BaseAddress = new Uri("http://localhost:5000/") };
|
||||||
|
|
||||||
|
public static void setupClient()
|
||||||
|
{
|
||||||
|
client.DefaultRequestHeaders.Accept.Clear();
|
||||||
|
client.DefaultRequestHeaders.Accept.Add(
|
||||||
|
new MediaTypeWithQualityHeaderValue("application/json"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task<Uri> PostOfferAsync(DBOffer offer)
|
||||||
|
{
|
||||||
|
HttpResponseMessage response = await client.PostAsJsonAsync(
|
||||||
|
"api/rtc/offer", offer);
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
return response.Headers.Location;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task<Uri> GetAllOffersAsync()
|
||||||
|
{
|
||||||
|
HttpResponseMessage response = await client.GetAsync("api/rtc/offers");
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
return response.Headers.Location;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task<bool> GetIsChannelActiveAsync(string channelId)
|
||||||
|
{
|
||||||
|
HttpResponseMessage response = await client.GetAsync($"api/rtc/active/{channelId}");
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
return bool.Parse(response.Content.ReadAsStringAsync().Result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task<RtcDescription> GetOffersForChannelAsync(string channelId)
|
||||||
|
{
|
||||||
|
HttpResponseMessage response = await client.GetAsync($"api/rtc/offers/{channelId}");
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
RtcDescription? offer = JsonSerializer.Deserialize<RtcDescription>(await response.Content.ReadAsStringAsync());
|
||||||
|
return offer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task<Uri> PostAnswerAsync(DBOffer answer)
|
||||||
|
{
|
||||||
|
HttpResponseMessage response = await client.PostAsJsonAsync("api/rtc/answer", answer);
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
return response.Headers.Location;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task<Uri> GetAnswersForChannelAsync(string channelId)
|
||||||
|
{
|
||||||
|
HttpResponseMessage response = await client.GetAsync($"api/rtc/answers/{channelId}");
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
return response.Headers.Location;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task<Uri> GetLatestAnswerForChannelAsync(string channelId)
|
||||||
|
{
|
||||||
|
HttpResponseMessage response = await client.GetAsync($"api/rtc/latest/{channelId}");
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
return response.Headers.Location;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task<Uri> PostIceCandidateAsync(IceCandidate candidate)
|
||||||
|
{
|
||||||
|
HttpResponseMessage response = await client.PostAsJsonAsync("api/rtc/candidate", candidate);
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
return response.Headers.Location;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task<Uri> GetIceCandidatesForChannelAsync(string channelId)
|
||||||
|
{
|
||||||
|
HttpResponseMessage response = await client.GetAsync($"api/rtc/candidates/{channelId}");
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
return response.Headers.Location;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task<Uri> GetIceCandidatesForChannelByUserAsync(string channelId, string userId, string directions)
|
||||||
|
{
|
||||||
|
HttpResponseMessage response = await client.GetAsync($"api/rtc/candidates/{channelId}/{userId}/{directions}");
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
return response.Headers.Location;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task<Uri> PostLeave(RtcLeave leave)
|
||||||
|
{
|
||||||
|
HttpResponseMessage response = await client.PostAsJsonAsync("api/rtc/leave", leave);
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
return response.Headers.Location;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public class RtcDescription
|
||||||
|
{
|
||||||
|
public string type { get; set; }
|
||||||
|
public string sdp { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class DBOffer
|
||||||
|
{
|
||||||
|
public required string ChannelId { get; set; }
|
||||||
|
public required string Username { get; set; }
|
||||||
|
public required RtcDescription SessionDescription { get; set; }
|
||||||
|
}
|
||||||
|
public class IceCandidate
|
||||||
|
{
|
||||||
|
public string type { get; set; }
|
||||||
|
public string sdp { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class RtcLeave
|
||||||
|
{
|
||||||
|
public string ChannelId { get; set; }
|
||||||
|
public string Username { get; set; }
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user