Skip to content

Commit 65c16b0

Browse files
authored
Adds a new WebRTC signalling peer web socket using asp.net's implementation (#1380)
* wip: add web socket peer using aspnet. * New ws working. * Fixed aspnet web socket. * FFmpeg get started demo working in k8s.
1 parent f17671d commit 65c16b0

File tree

10 files changed

+411
-100
lines changed

10 files changed

+411
-100
lines changed

examples/WebRTCExamples/WebRTCFFmpegGetStarted/Dockerfile

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,11 @@ FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
3838
ARG BUILD_CONFIGURATION=Release
3939
WORKDIR /src
4040

41+
# Add local NuGet source (replace with your actual package version)
42+
RUN mkdir -p /local-nuget
43+
COPY ./local-nuget/*.nupkg /local-nuget/
44+
RUN dotnet nuget add source /local-nuget --name local
45+
4146
COPY [".", "."]
4247

4348
# Publish the application

examples/WebRTCExamples/WebRTCFFmpegGetStarted/Program.cs

Lines changed: 76 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -16,82 +16,120 @@
1616
//-----------------------------------------------------------------------------
1717

1818
using System;
19-
using System.Diagnostics.Eventing.Reader;
19+
using System.Collections.Generic;
2020
using System.Linq;
21-
using System.Net;
2221
using System.Threading.Tasks;
2322
using Microsoft.AspNetCore.Builder;
24-
using Microsoft.AspNetCore.Hosting;
23+
using Microsoft.AspNetCore.Http;
2524
using Microsoft.Extensions.Logging;
26-
using Microsoft.Extensions.Logging.Abstractions;
2725
using Serilog;
26+
using Serilog.Events;
2827
using Serilog.Extensions.Logging;
2928
using SIPSorcery.Media;
3029
using SIPSorcery.Net;
3130
using SIPSorceryMedia.FFmpeg;
32-
using WebSocketSharp.Server;
3331

3432
namespace demo;
3533

3634
class Program
3735
{
38-
private const int ASPNET_PORT = 8080;
39-
private const int WEBSOCKET_PORT = 8081;
40-
//private const string STUN_URL = "stun:stun.cloudflare.com";
4136
private const string LINUX_FFMPEG_LIB_PATH = "/usr/local/lib/";
4237

43-
private static Microsoft.Extensions.Logging.ILogger logger = NullLogger.Instance;
38+
private static string _stunUrl = string.Empty;
39+
private static bool _waitForIceGatheringToSendOffer = false;
40+
private static int _webrtcBindPort = 0;
4441

45-
static void Main()
42+
static async Task Main()
4643
{
4744
Console.WriteLine("WebRTC FFmpeg Get Started");
4845

46+
_stunUrl = Environment.GetEnvironmentVariable("STUN_URL");
47+
bool.TryParse(Environment.GetEnvironmentVariable("WAIT_FOR_ICE_GATHERING_TO_SEND_OFFER"), out _waitForIceGatheringToSendOffer);
48+
int.TryParse(Environment.GetEnvironmentVariable("BIND_PORT"), out _webrtcBindPort);
49+
50+
Log.Logger = new LoggerConfiguration()
51+
.MinimumLevel.Debug()
52+
.MinimumLevel.Override("Microsoft", LogEventLevel.Information)
53+
.Enrich.FromLogContext()
54+
.WriteTo.Console()
55+
.CreateLogger();
56+
57+
var factory = new SerilogLoggerFactory(Log.Logger);
58+
SIPSorcery.LogFactory.Set(factory);
59+
var programLogger = factory.CreateLogger<Program>();
60+
4961
if (Environment.OSVersion.Platform == PlatformID.Unix)
5062
{
51-
SIPSorceryMedia.FFmpeg.FFmpegInit.Initialise(SIPSorceryMedia.FFmpeg.FfmpegLogLevelEnum.AV_LOG_VERBOSE, LINUX_FFMPEG_LIB_PATH, logger);
63+
FFmpegInit.Initialise(FfmpegLogLevelEnum.AV_LOG_VERBOSE, LINUX_FFMPEG_LIB_PATH, programLogger);
5264
}
5365
else
5466
{
55-
SIPSorceryMedia.FFmpeg.FFmpegInit.Initialise(SIPSorceryMedia.FFmpeg.FfmpegLogLevelEnum.AV_LOG_VERBOSE, null, logger);
67+
FFmpegInit.Initialise(FfmpegLogLevelEnum.AV_LOG_VERBOSE, null, programLogger);
5668
}
5769

58-
logger = AddConsoleLogger();
59-
60-
// Start web socket.
61-
Console.WriteLine("Starting web socket server...");
62-
var webSocketServer = new WebSocketServer(IPAddress.Any, WEBSOCKET_PORT);
63-
webSocketServer.AddWebSocketService<WebRTCWebSocketPeer>("/ws", (peer) => peer.CreatePeerConnection = CreatePeerConnection);
64-
webSocketServer.Start();
65-
66-
Console.WriteLine($"Waiting for web socket connections on {webSocketServer.Address}:{webSocketServer.Port}...");
67-
Console.WriteLine("Press ctrl-c to exit.");
70+
programLogger.LogDebug(_stunUrl != null ? $"STUN URL: {_stunUrl}" : "No STUN URL provided.");
71+
programLogger.LogDebug($"Wait for ICE gathering to send offer: {_waitForIceGatheringToSendOffer}");
6872

6973
var builder = WebApplication.CreateBuilder();
7074

71-
builder.WebHost.ConfigureKestrel(options =>
72-
{
73-
options.Listen(IPAddress.Any, ASPNET_PORT);
74-
});
75+
builder.Host.UseSerilog();
7576

7677
var app = builder.Build();
7778

78-
// Map the root URL (/) to return "Hello World"
79-
///app.MapGet("/", () => "Hello World");
80-
8179
app.UseDefaultFiles();
8280
app.UseStaticFiles();
81+
var webSocketOptions = new WebSocketOptions
82+
{
83+
KeepAliveInterval = TimeSpan.FromMinutes(2)
84+
};
85+
86+
app.UseWebSockets(webSocketOptions);
87+
88+
app.Map("/ws", async context =>
89+
{
90+
programLogger.LogDebug("Web socket client connection established.");
91+
92+
if (context.WebSockets.IsWebSocketRequest)
93+
{
94+
var webSocket = await context.WebSockets.AcceptWebSocketAsync();
95+
96+
var webSocketPeer = new WebRTCWebSocketPeerAspNet(webSocket,
97+
CreatePeerConnection,
98+
RTCSdpType.offer,
99+
programLogger);
100+
101+
webSocketPeer.OfferOptions = new RTCOfferOptions
102+
{
103+
X_WaitForIceGatheringToComplete = _waitForIceGatheringToSendOffer
104+
};
105+
106+
await webSocketPeer.Run();
83107

84-
app.Run();
108+
await webSocketPeer.Close();
109+
}
110+
else
111+
{
112+
// Not a WebSocket request
113+
context.Response.StatusCode = StatusCodes.Status400BadRequest;
114+
}
115+
});
116+
117+
await app.RunAsync();
85118
}
86119

87-
private static Task<RTCPeerConnection> CreatePeerConnection()
120+
private static Task<RTCPeerConnection> CreatePeerConnection(Microsoft.Extensions.Logging.ILogger logger)
88121
{
89122
RTCConfiguration config = new RTCConfiguration
90123
{
91-
//iceServers = new List<RTCIceServer> { new RTCIceServer { urls = STUN_URL } },
92-
X_BindAddress = IPAddress.Any // Docker images typically don't support IPv6 so force bind to IPv4.
124+
X_ICEIncludeAllInterfaceAddresses = true
93125
};
94-
var pc = new RTCPeerConnection(config);
126+
127+
if (!string.IsNullOrWhiteSpace(_stunUrl))
128+
{
129+
config.iceServers = new List<RTCIceServer> { new RTCIceServer { urls = _stunUrl } };
130+
}
131+
132+
var pc = new RTCPeerConnection(config, _webrtcBindPort);
95133

96134
var testPatternSource = new VideoTestPatternSource(new FFmpegVideoEncoder());
97135
var audioSource = new AudioExtrasSource(new AudioEncoder(), new AudioSourceOptions { AudioSource = AudioSourcesEnum.Music });
@@ -106,6 +144,9 @@ private static Task<RTCPeerConnection> CreatePeerConnection()
106144

107145
pc.OnVideoFormatsNegotiated += (formats) => testPatternSource.SetVideoSourceFormat(formats.First());
108146
pc.OnAudioFormatsNegotiated += (formats) => audioSource.SetAudioSourceFormat(formats.First());
147+
pc.onicegatheringstatechange += (state) => logger.LogDebug($"ICE gathering state changed to {state}."); ;
148+
pc.oniceconnectionstatechange += (state) => logger.LogDebug($"ICE connection state changed to {state}.");
149+
109150
pc.onsignalingstatechange += () =>
110151
{
111152
logger.LogDebug($"Signalling state change to {pc.signalingState}.");
@@ -119,7 +160,7 @@ private static Task<RTCPeerConnection> CreatePeerConnection()
119160
logger.LogDebug($"Remote SDP offer:\n{pc.remoteDescription.sdp}");
120161
}
121162
};
122-
163+
123164
pc.onconnectionstatechange += async (state) =>
124165
{
125166
logger.LogDebug($"Peer connection state change to {state}.");
@@ -146,33 +187,6 @@ private static Task<RTCPeerConnection> CreatePeerConnection()
146187
pc.GetRtpChannel().OnStunMessageReceived += (msg, ep, isRelay) => logger.LogDebug($"STUN {msg.Header.MessageType} received from {ep}.");
147188
pc.oniceconnectionstatechange += (state) => logger.LogDebug($"ICE connection state change to {state}.");
148189

149-
// To test closing.
150-
//_ = Task.Run(async () =>
151-
//{
152-
// await Task.Delay(5000);
153-
154-
// audioSource.OnAudioSourceEncodedSample -= pc.SendAudio;
155-
// videoEncoderEndPoint.OnVideoSourceEncodedSample -= pc.SendVideo;
156-
157-
// logger.LogDebug("Closing peer connection.");
158-
// pc.Close("normal");
159-
//});
160-
161190
return Task.FromResult(pc);
162191
}
163-
164-
/// <summary>
165-
/// Adds a console logger. Can be omitted if internal SIPSorcery debug and warning messages are not required.
166-
/// </summary>
167-
private static Microsoft.Extensions.Logging.ILogger AddConsoleLogger()
168-
{
169-
var seriLogger = new LoggerConfiguration()
170-
.Enrich.FromLogContext()
171-
.MinimumLevel.Is(Serilog.Events.LogEventLevel.Debug)
172-
.WriteTo.Console()
173-
.CreateLogger();
174-
var factory = new SerilogLoggerFactory(seriLogger);
175-
SIPSorcery.LogFactory.Set(factory);
176-
return factory.CreateLogger<Program>();
177-
}
178192
}

examples/WebRTCExamples/WebRTCFFmpegGetStarted/Properties/launchSettings.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
"environmentVariables": {
77
"ASPNETCORE_ENVIRONMENT": "Development"
88
},
9-
"applicationUrl": "https://localhost:52561;http://localhost:52562"
9+
"applicationUrl": "http://localhost:8080"
1010
}
1111
}
1212
}

examples/WebRTCExamples/WebRTCFFmpegGetStarted/README.md

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,32 @@ is a mismtach in the verion of the FFmpeg binaries supported by the [FFmpeg.Auto
1212

1313
As the time of writing the correct FFmpeg version was `n7.0` and a docker build image was created [here](https://github.com/sipsorcery-org/SIPSorceryMedia.FFmpeg/tree/master/ffmpeg-build).
1414

15-
# Docker Build & Run
15+
# Docker Build
1616

1717
`c:\dev\sipsorcery\examples\WebRTCExamples\WebRTCFFmpegGetStarted> docker build -t webrtcgetstarted --progress=plain -f Dockerfile .`
18-
`c:\dev\sipsorcery\examples\WebRTCExamples\WebRTCFFmpegGetStarted> docker run --rm -it -p 8080:8080 -p 8081:8081 webrtcgetstarted`
18+
19+
# Docker Run
20+
21+
Establishing a WebRTC connection to a docker container has not yet been successful. The normal docker address range of 172.x.x.x isn't directly accessible fro the host OS. Typically access to docker
22+
containers relies on port mapping but that doesn't work with the WebRTC ICE mechanism. Below are the options attempted. All of which were unsuccessful. The docker image will work without any special
23+
networking options if hosted externally, for example in a kubernetes cluster. The recommended alternative is to run the app directly using `dotnet run`.
24+
25+
`docker run --rm -it -p 8080:8080 -e ASPNETCORE_URLS="http://0.0.0.0:8080" webrtcgetstarted`
26+
or
27+
`docker run --rm -it --network=host -p 8080:8080 -e ASPNETCORE_URLS="http://0.0.0.0:8080" webrtcgetstarted`
28+
or
29+
`docker run --rm -it -p 8080:8080 -p 50042:50042/udp -e ASPNETCORE_URLS="http://0.0.0.0:8080" -e BIND_PORT="50042" webrtcgetstarted`
30+
or
31+
`docker run --rm -it -p 8080:8080 -p 50042:50042/udp --add-host=host.docker.internal:host-gateway -e ASPNETCORE_URLS="http://0.0.0.0:8080" -e BIND_PORT="50042" webrtcgetstarted`
32+
or
33+
`docker run --rm -it -p 8080:8080 -e ASPNETCORE_URLS="http://0.0.0.0:8080" -e WAIT_FOR_ICE_GATHERING_TO_SEND_OFFER="True" webrtcgetstarted`
34+
or
35+
`docker run --rm -it -p 8080:8080 -e ASPNETCORE_URLS="http://0.0.0.0:8080" -e WAIT_FOR_ICE_GATHERING_TO_SEND_OFFER="True" -e STUN_URL="stun:stun.cloudflare.com" webrtcgetstarted`
1936

2037
# From DockerHub
2138

22-
`docker run --rm -it -p 8080:8080 -p 8081:8081 sipsorcery/webrtcgetstarted`
39+
`docker run --rm -it -p 8080:8080 -e ASPNETCORE_URLS="http://0.0.0.0:8080" sipsorcery/webrtcgetstarted`
2340

2441
# Troubleshooting
2542

26-
`c:\dev\sipsorcery\examples\WebRTCExamples\WebRTCFFmpegGetStarted> docker run --rm -it -p 8080:8080 -p 8081:8081 --entrypoint "/bin/bash" webrtcgetstarted`
43+
`c:\dev\sipsorcery\examples\WebRTCExamples\WebRTCFFmpegGetStarted> docker run --rm -it -p 8080:8080 -e ASPNETCORE_URLS="http://0.0.0.0:8080" --entrypoint "/bin/bash" webrtcgetstarted`

examples/WebRTCExamples/WebRTCFFmpegGetStarted/WebRTCFFmpegGetStarted.csproj

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,8 @@
99
</PropertyGroup>
1010

1111
<ItemGroup>
12-
<PackageReference Include="Microsoft.Extensions.Logging" Version="8.0.0" />
13-
<PackageReference Include="Serilog.Extensions.Logging" Version="3.0.1" />
14-
<PackageReference Include="Serilog.Sinks.Console" Version="3.1.1" />
15-
<PackageReference Include="SIPSorcery" Version="8.0.11" />
16-
<PackageReference Include="SIPSorcery.WebSocketSharp" Version="0.0.1" />
12+
<PackageReference Include="Serilog.AspNetCore" Version="9.0.0" />
13+
<PackageReference Include="SIPSorcery" Version="8.0.12" />
1714
<PackageReference Include="SIPSorceryMedia.FFmpeg" Version="8.0.10" />
1815
</ItemGroup>
1916

examples/WebRTCExamples/WebRTCFFmpegGetStarted/wwwroot/index.html

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,21 @@
11
<!DOCTYPE html>
22
<head>
33
<script type="text/javascript">
4-
const WEBSOCKET_URL = "ws://127.0.0.1:8081/ws"
4+
const WEBSOCKET_URL = "ws://127.0.0.1:8080/ws"
55
const STUN_URL = "stun:stun.cloudflare.com";
66

77
var pc, ws;
88

9+
// Auto-detect WebSocket URL based on current page
10+
function getWebSocketUrl() {
11+
const location = window.location;
12+
const protocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
13+
const host = location.host;
14+
const path = '/ws'; // Change this to match your server's WebSocket endpoint
15+
16+
return `${protocol}//${host}${path}`;
17+
}
18+
919
async function start() {
1020
pc = new RTCPeerConnection({
1121
iceServers: [
@@ -35,7 +45,9 @@
3545
};
3646

3747
ws = new WebSocket(document.querySelector('#websockurl').value, []);
48+
3849
ws.onmessage = async function (evt) {
50+
console.log("WebSocket message received:", evt.data);
3951
var obj = JSON.parse(evt.data);
4052
if (obj?.candidate) {
4153
pc.addIceCandidate(obj);
@@ -47,15 +59,20 @@
4759
.then(() => ws.send(JSON.stringify(pc.localDescription)));
4860
}
4961
};
62+
63+
ws.onclose = function (evt) {
64+
console.log("WebSocket closed, code: " + evt.code + ", reason: " + evt.reason);
65+
};
66+
67+
ws.onerror = function (evt) {
68+
console.error("WebSocket error:", evt);
69+
};
5070
};
5171

5272
async function closePeer() {
5373
await pc?.close();
5474
await ws?.close();
5575
};
56-
57-
58-
5976
</script>
6077
</head>
6178
<body>
@@ -71,5 +88,5 @@
7188
</body>
7289

7390
<script>
74-
document.querySelector('#websockurl').value = WEBSOCKET_URL;
91+
document.querySelector('#websockurl').value = getWebSocketUrl();
7592
</script>

src/SIPSorcery.csproj

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,9 +62,9 @@
6262
-v8.0.1-pre: Performance improvements (thanks to @weltmeyer). Add ECDSA as default option for WebRTC DTLS.
6363
-v8.0.0: RTP header extension improvements (thanks to @ChristopheI). Major version to 8 to reflect highest .net runtime supported.</PackageReleaseNotes>
6464
<NeutralLanguage>en</NeutralLanguage>
65-
<Version>8.0.11</Version>
66-
<AssemblyVersion>8.0.11</AssemblyVersion>
67-
<FileVersion>8.0.11</FileVersion>
65+
<Version>8.0.12</Version>
66+
<AssemblyVersion>8.0.12</AssemblyVersion>
67+
<FileVersion>8.0.12</FileVersion>
6868
</PropertyGroup>
6969

7070
<PropertyGroup Label="SourceLink">

src/net/WebRTC/IRTCPeerConnection.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,12 @@ public class RTCOfferOptions
4444
/// and will be added to the offer SDP.
4545
/// </summary>
4646
public bool X_ExcludeIceCandidates;
47+
48+
/// <summary>
49+
/// If set to true it indicates the generation of the SDP offer should wait until the ICE gathering
50+
/// is compelte so the ICE cnadidates can be included in the SDP offer.
51+
/// </summary>
52+
public bool X_WaitForIceGatheringToComplete;
4753
}
4854

4955
/// <summary>

0 commit comments

Comments
 (0)