swapping to webview webrtc setup as temp solution
will build a custom C# webrtc implementation later
This commit is contained in:
@@ -53,6 +53,14 @@
|
|||||||
Spacing="8" />
|
Spacing="8" />
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
</Border>
|
</Border>
|
||||||
|
<Border x:Name="RtcView"
|
||||||
|
Grid.Row="1"
|
||||||
|
Grid.Column="1"
|
||||||
|
StrokeThickness="1"
|
||||||
|
Padding="10"
|
||||||
|
IsVisible="False">
|
||||||
|
<WebView Source="test.html"/>
|
||||||
|
</Border>
|
||||||
|
|
||||||
<!-- Input -->
|
<!-- Input -->
|
||||||
<Grid Grid.Row="2"
|
<Grid Grid.Row="2"
|
||||||
@@ -70,5 +78,10 @@
|
|||||||
Clicked="SendButton_OnClicked" />
|
Clicked="SendButton_OnClicked" />
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
|
<!-- Swap View -->
|
||||||
|
<Button x:Name="ViewSwapped" Grid.Row="2" Grid.Column="0"
|
||||||
|
Text="Swap to WebView"
|
||||||
|
Clicked="SwapView_OnClicked" />
|
||||||
|
|
||||||
</Grid>
|
</Grid>
|
||||||
</ContentPage>
|
</ContentPage>
|
||||||
@@ -2,13 +2,10 @@
|
|||||||
using RelayClient.Models;
|
using RelayClient.Models;
|
||||||
using WebSocketSharp;
|
using WebSocketSharp;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Microsoft.MixedReality.WebRTC;
|
|
||||||
using System.IO;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace RelayClient;
|
namespace RelayClient;
|
||||||
|
|
||||||
|
|
||||||
public partial class MainPage : ContentPage
|
public partial class MainPage : ContentPage
|
||||||
{
|
{
|
||||||
private readonly string _username;
|
private readonly string _username;
|
||||||
@@ -44,7 +41,6 @@ public partial class MainPage : ContentPage
|
|||||||
_wsc.Send("GET_SERVER_KEY");
|
_wsc.Send("GET_SERVER_KEY");
|
||||||
_wsc.Send("GET_CHANNELS");
|
_wsc.Send("GET_CHANNELS");
|
||||||
|
|
||||||
_ = webRTCTest();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SendButton_OnClicked(object? sender, EventArgs e)
|
private void SendButton_OnClicked(object? sender, EventArgs e)
|
||||||
@@ -293,146 +289,20 @@ public partial class MainPage : ContentPage
|
|||||||
await MessagesScrollView.ScrollToAsync(MessagesLayout, ScrollToPosition.End, true);
|
await MessagesScrollView.ScrollToAsync(MessagesLayout, ScrollToPosition.End, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task webRTCTest()
|
private void SwapView_OnClicked(object? sender, EventArgs e)
|
||||||
{
|
{
|
||||||
var path = Path.GetFullPath(
|
if (RtcView.IsVisible)
|
||||||
Path.Combine(AppContext.BaseDirectory, @"..\..\..\debug.txt")
|
|
||||||
);
|
|
||||||
|
|
||||||
void Log(string msg)
|
|
||||||
{
|
{
|
||||||
File.AppendAllText(path, msg + Environment.NewLine);
|
MessagesScrollView.IsVisible = true;
|
||||||
Console.WriteLine(msg);
|
RtcView.IsVisible = false;
|
||||||
|
ViewSwapped.Text = "Swap to Message View";
|
||||||
|
|
||||||
}
|
}
|
||||||
|
else
|
||||||
AudioTrackSource? microphoneSource = null;
|
|
||||||
DeviceVideoTrackSource? webcamSource = null;
|
|
||||||
Transceiver? audioTransceiver = null;
|
|
||||||
Transceiver? videoTransceiver = null;
|
|
||||||
LocalAudioTrack? localAudioTrack = null;
|
|
||||||
LocalVideoTrack? localVideoTrack = null;
|
|
||||||
|
|
||||||
Log("=== WebRTC TEST START ===");
|
|
||||||
|
|
||||||
IReadOnlyList<VideoCaptureDevice>? deviceList = null;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
Log("Getting video devices...");
|
MessagesScrollView.IsVisible = false;
|
||||||
|
RtcView.IsVisible = true;
|
||||||
deviceList = await DeviceVideoTrackSource.GetCaptureDevicesAsync();
|
ViewSwapped.Text = "Swap to Web View";
|
||||||
|
|
||||||
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 ===");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -36,14 +36,24 @@
|
|||||||
<MauiImage Update="Resources\Images\dotnet_bot.png" Resize="True" BaseSize="300,185" />
|
<MauiImage Update="Resources\Images\dotnet_bot.png" Resize="True" BaseSize="300,185" />
|
||||||
<MauiFont Include="Resources\Fonts\*" />
|
<MauiFont Include="Resources\Fonts\*" />
|
||||||
<MauiAsset Include="Resources\Raw\**" LogicalName="%(RecursiveDir)%(Filename)%(Extension)" />
|
<MauiAsset Include="Resources\Raw\**" LogicalName="%(RecursiveDir)%(Filename)%(Extension)" />
|
||||||
|
<None Remove="Resources\Raw\test.html" />
|
||||||
|
<MauiAsset Include="Resources\Raw\test.html">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</MauiAsset>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="11.0.0-preview.2.26159.112" />
|
||||||
<PackageReference Include="Microsoft.Maui.Controls" Version="$(MauiVersion)" />
|
<PackageReference Include="Microsoft.Maui.Controls" Version="$(MauiVersion)" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="10.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="10.0.0" />
|
||||||
<PackageReference Include="Microsoft.MixedReality.WebRTC" Version="2.0.2" />
|
|
||||||
<PackageReference Include="SurrealDb.Net" Version="0.9.0" />
|
<PackageReference Include="SurrealDb.Net" Version="0.9.0" />
|
||||||
<PackageReference Include="WebSocketSharp" Version="1.0.3-rc11" />
|
<PackageReference Include="WebSocketSharp" Version="1.0.3-rc11" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<MauiCss Update="Resources\Raw\test.css">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</MauiCss>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
3
RelayClient/Resources/Raw/test.css
Normal file
3
RelayClient/Resources/Raw/test.css
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
#header{
|
||||||
|
border: black solid 2px;
|
||||||
|
}
|
||||||
9
RelayClient/Resources/Raw/test.html
Normal file
9
RelayClient/Resources/Raw/test.html
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<html lang="enus">
|
||||||
|
<head>
|
||||||
|
<link rel="stylesheet" href="test.css">
|
||||||
|
<script src="test.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<Button id="header" onclick="onClicked()">Hello World!</Button>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
10
RelayClient/Resources/Raw/test.js
Normal file
10
RelayClient/Resources/Raw/test.js
Normal file
@@ -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;
|
||||||
|
}
|
||||||
170
RelayClient/WebRTC.cs
Normal file
170
RelayClient/WebRTC.cs
Normal file
@@ -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<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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using RelayServer.Services;
|
using RelayServer.Services;
|
||||||
using WebSocketSharp.Server;
|
using WebSocketSharp.Server;
|
||||||
|
using Microsoft.AspNetCore.SignalR;
|
||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
using RelayServer.Models;
|
using RelayServer.Models;
|
||||||
|
|
||||||
var surrealService = new SurrealService();
|
var surrealService = new SurrealService();
|
||||||
@@ -12,6 +14,14 @@ await using var db = await surrealService.ConnectAsync();
|
|||||||
ChatTest.ClientKeyService = new ClientKeyService(db);
|
ChatTest.ClientKeyService = new ClientKeyService(db);
|
||||||
ChatTest.Db = db;
|
ChatTest.Db = db;
|
||||||
|
|
||||||
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
builder.Services.AddSignalR();
|
||||||
|
|
||||||
|
var app = builder.Build();
|
||||||
|
app.MapGet("/", () => "Server Running!");
|
||||||
|
app.MapHub<WebRtcHub>("/webrtc");
|
||||||
|
app.Run();
|
||||||
|
|
||||||
var wssv = new WebSocketServer("ws://localhost:1337");
|
var wssv = new WebSocketServer("ws://localhost:1337");
|
||||||
wssv.AddWebSocketService<ChatTest>("/");
|
wssv.AddWebSocketService<ChatTest>("/");
|
||||||
wssv.Start();
|
wssv.Start();
|
||||||
@@ -147,3 +157,24 @@ static string GetRecordId(object? id)
|
|||||||
|
|
||||||
return $"{table}:{recordId}";
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
@@ -8,6 +8,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.SignalR" Version="1.2.9" />
|
||||||
<PackageReference Include="SurrealDb.Net" Version="0.9.0" />
|
<PackageReference Include="SurrealDb.Net" Version="0.9.0" />
|
||||||
<PackageReference Include="WebSocketSharp" Version="1.0.3-rc11" />
|
<PackageReference Include="WebSocketSharp" Version="1.0.3-rc11" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|||||||
Reference in New Issue
Block a user