|
@@ -21,6 +21,7 @@ namespace DotnetRtmpServer.Net
|
|
|
private readonly ConcurrentDictionary<string, IPublisher> _publishers = new();
|
|
|
private readonly ConcurrentDictionary<string, List<IPlayer>> _players = new();
|
|
|
private readonly ConcurrentDictionary<ushort, RtmpConnection> _connections = new();
|
|
|
+ private readonly ConcurrentDictionary<string, RtmpProxy> _proxies = new();
|
|
|
|
|
|
|
|
|
private readonly Random _random = new();
|
|
@@ -50,7 +51,10 @@ namespace DotnetRtmpServer.Net
|
|
|
}
|
|
|
}
|
|
|
_listener = new TcpListener(IPAddress.Any, Config.RtmpPort);
|
|
|
- RegisterApp(Config.DefaultApp);
|
|
|
+ if (!Config.ProxyMode)
|
|
|
+ {
|
|
|
+ RegisterApp(Config.DefaultApp);
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
public void Start()
|
|
@@ -110,49 +114,57 @@ namespace DotnetRtmpServer.Net
|
|
|
|
|
|
public async void HandshakeAsync(Socket socket)
|
|
|
{
|
|
|
- var stream = new NetworkStream(socket);
|
|
|
- var randomBytes = new byte[1528];
|
|
|
- _random.NextBytes(randomBytes);
|
|
|
- var s01 = new RtmpHandshake()
|
|
|
+ try
|
|
|
{
|
|
|
- Version = 3,
|
|
|
- Time = (uint)Environment.TickCount,
|
|
|
- Time2 = 0,
|
|
|
- Random = randomBytes
|
|
|
- };
|
|
|
-
|
|
|
- var c01 = await RtmpHandshake.ReadAsync(stream, true).ConfigureAwait(false);
|
|
|
- //S0 + S1
|
|
|
- await RtmpHandshake.WriteAsync(stream, s01, true).ConfigureAwait(false);
|
|
|
- //S0 + S1 + S2
|
|
|
- //S2 = S1
|
|
|
- var s2 = c01.Clone();
|
|
|
- await RtmpHandshake.WriteAsync(stream, s2, false);
|
|
|
- //Read C2
|
|
|
- var c2 = await RtmpHandshake.ReadAsync(stream, false);
|
|
|
-
|
|
|
- //handshake check
|
|
|
- if (!c2.Random.SequenceEqual(s01.Random))
|
|
|
- {
|
|
|
- throw new ProtocolViolationException();
|
|
|
- }
|
|
|
+ var stream = new NetworkStream(socket);
|
|
|
+ var randomBytes = new byte[1528];
|
|
|
+ _random.NextBytes(randomBytes);
|
|
|
+ var s1 = new RtmpHandshake()
|
|
|
+ {
|
|
|
+ Version = 3,
|
|
|
+ Time = (uint) Environment.TickCount,
|
|
|
+ Time2 = 0,
|
|
|
+ Random = randomBytes
|
|
|
+ };
|
|
|
+
|
|
|
+ var c1 = await RtmpHandshake.ReadAsync(stream, true).ConfigureAwait(false);
|
|
|
+ //S0 + S1
|
|
|
+ await RtmpHandshake.WriteAsync(stream, s1, true).ConfigureAwait(false);
|
|
|
+ //S0 + S1 + S2
|
|
|
+ //S2 = S1
|
|
|
+ var s2 = c1.Clone();
|
|
|
+ await RtmpHandshake.WriteAsync(stream, s2, false);
|
|
|
+ //Read C2
|
|
|
+ var c2 = await RtmpHandshake.ReadAsync(stream, false).ConfigureAwait(false);
|
|
|
+
|
|
|
+ //handshake check
|
|
|
+ if (!c2.Random.SequenceEqual(s1.Random))
|
|
|
+ {
|
|
|
+ throw new ProtocolViolationException();
|
|
|
+ }
|
|
|
|
|
|
- var connection = new RtmpConnection(socket);
|
|
|
- connection.RequestConnect += OnConnectionRequestConnectAsync;
|
|
|
- connection.RequestPublish += OnConnectionRequestPublishAsync;
|
|
|
- connection.Published += OnConnectionPublishedAsync;
|
|
|
- connection.RequestPlay += OnConnectionRequestPlayAsync;
|
|
|
- connection.Played += OnConnectionPlayedAsync;
|
|
|
- connection.Closed += OnConnectionClosedAsync;
|
|
|
- _connections.AddOrUpdate(connection.Id, connection, (_, exists) =>
|
|
|
- {
|
|
|
- exists.DisposeAsync().AsTask().Wait();
|
|
|
- return connection;
|
|
|
- });
|
|
|
- if (!Config.EnableLowPowerMode)
|
|
|
+ var connection = new RtmpConnection(socket);
|
|
|
+ connection.RequestConnect += OnConnectionRequestConnectAsync;
|
|
|
+ connection.RequestPublish += OnConnectionRequestPublishAsync;
|
|
|
+ connection.Published += OnConnectionPublishedAsync;
|
|
|
+ connection.RequestPlay += OnConnectionRequestPlayAsync;
|
|
|
+ connection.Played += OnConnectionPlayedAsync;
|
|
|
+ connection.Closed += OnConnectionClosedAsync;
|
|
|
+ _connections.AddOrUpdate(connection.Id, connection, (_, exists) =>
|
|
|
+ {
|
|
|
+ exists.DisposeAsync().AsTask().Wait();
|
|
|
+ return connection;
|
|
|
+ });
|
|
|
+ if (!Config.EnableLowPowerMode)
|
|
|
+ {
|
|
|
+ connection.StartIoProcess();
|
|
|
+ Logger.WriteLineInfo($"IO processor for connection {connection.Id} started.");
|
|
|
+ }
|
|
|
+ }
|
|
|
+ catch (Exception)
|
|
|
{
|
|
|
- connection.StartIoProcess();
|
|
|
- Logger.WriteLineInfo($"IO processor for connection {connection.Id} started.");
|
|
|
+ Logger.WriteLineInfo($"Handshake error for socket {socket.RemoteEndPoint}.");
|
|
|
+ socket.Close();
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -182,14 +194,23 @@ namespace DotnetRtmpServer.Net
|
|
|
#region Connection
|
|
|
private async Task OnConnectionRequestConnectAsync(object sender, ConnectEventArgs e)
|
|
|
{
|
|
|
- lock (_registeredApps)
|
|
|
+ if (!Config.ProxyMode)
|
|
|
{
|
|
|
- e.AuthSuccess = _registeredApps.IndexOf(e.App) != -1;
|
|
|
- if (!e.AuthSuccess)
|
|
|
+ lock (_registeredApps)
|
|
|
{
|
|
|
- Logger.WriteLineError($"Auth connect failed, connect app is {e.App}, exist apps are [{string.Join(",", _registeredApps)}]");
|
|
|
+ e.AuthSuccess = _registeredApps.IndexOf(e.App) != -1;
|
|
|
+ if (!e.AuthSuccess)
|
|
|
+ {
|
|
|
+ Logger.WriteLineError(
|
|
|
+ $"Auth connect failed, connect app is {e.App}, exist apps are [{string.Join(",", _registeredApps)}]");
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
+ else
|
|
|
+ {
|
|
|
+ e.AuthSuccess = true;
|
|
|
+ }
|
|
|
+
|
|
|
await Task.CompletedTask.ConfigureAwait(false);
|
|
|
}
|
|
|
|
|
@@ -211,16 +232,25 @@ namespace DotnetRtmpServer.Net
|
|
|
|
|
|
private async Task OnConnectionRequestPublishAsync(object sender, PublishEventArgs e)
|
|
|
{
|
|
|
- var channelName = e.App + "/" + e.Path;
|
|
|
- if (_publishers.ContainsKey(channelName))
|
|
|
+ if (Config.ProxyMode && !e.App.Contains(RtmpProxy.ProxyId))
|
|
|
{
|
|
|
- Logger.WriteLineWarn($"Publisher for channel:{channelName} already exists.");
|
|
|
+ Logger.WriteLineWarn("Can not publish stream under proxy mode.");
|
|
|
e.CanPublish = false;
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
- e.CanPublish = true;
|
|
|
+ var channelName = e.App + "/" + e.Path;
|
|
|
+ if (_publishers.ContainsKey(channelName))
|
|
|
+ {
|
|
|
+ Logger.WriteLineWarn($"Publisher for channel:{channelName} already exists.");
|
|
|
+ e.CanPublish = false;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ e.CanPublish = true;
|
|
|
+ }
|
|
|
}
|
|
|
+
|
|
|
await Task.CompletedTask.ConfigureAwait(false);
|
|
|
}
|
|
|
|
|
@@ -321,24 +351,46 @@ namespace DotnetRtmpServer.Net
|
|
|
#region player
|
|
|
private async Task OnConnectionRequestPlayAsync(object sender, PlayEventArgs e)
|
|
|
{
|
|
|
- if (!Config.AllowPlayWithoutPublish)
|
|
|
+ if (Config.ProxyMode)
|
|
|
{
|
|
|
- var channelName = e.App + "/" + e.Path;
|
|
|
- if (_publishers.ContainsKey(channelName))
|
|
|
+ var channelName = $"{e.App}_{RtmpProxy.ProxyId}/{e.Path}";
|
|
|
+ _proxies.AddOrUpdate(channelName, _ =>
|
|
|
{
|
|
|
- e.CanPlay = true;
|
|
|
+ var proxy = new RtmpProxy(Config.ProxySourceHost, e.App, e.Path);
|
|
|
+ proxy.Closed += OnProxyClosedAsync;
|
|
|
+ proxy.Start();
|
|
|
+ return proxy;
|
|
|
+ }, (_, exist) => exist);
|
|
|
+ e.CanPlay = true;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ if (!Config.AllowPlayWithoutPublish)
|
|
|
+ {
|
|
|
+ var channelName = e.App + "/" + e.Path;
|
|
|
+ if (_publishers.ContainsKey(channelName))
|
|
|
+ {
|
|
|
+ e.CanPlay = true;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ Logger.WriteLineWarn($"No publisher for channel:{channelName}.");
|
|
|
+ e.CanPlay = false;
|
|
|
+ }
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
- Logger.WriteLineWarn($"No publisher for channel:{channelName}.");
|
|
|
- e.CanPlay = false;
|
|
|
+ e.CanPlay = true;
|
|
|
}
|
|
|
}
|
|
|
- else
|
|
|
- {
|
|
|
- e.CanPlay = true;
|
|
|
- }
|
|
|
+ await Task.CompletedTask.ConfigureAwait(false);
|
|
|
+ }
|
|
|
|
|
|
+ private async Task OnProxyClosedAsync(object sender, EventArgs args)
|
|
|
+ {
|
|
|
+ var proxy = (RtmpProxy) sender;
|
|
|
+ proxy.Closed -= OnProxyClosedAsync;
|
|
|
+ _proxies.TryRemove(proxy.ChannelName, out _);
|
|
|
await Task.CompletedTask.ConfigureAwait(false);
|
|
|
}
|
|
|
|
|
@@ -346,7 +398,7 @@ namespace DotnetRtmpServer.Net
|
|
|
private async Task OnConnectionPlayedAsync(object sender, PlayEventArgs e)
|
|
|
{
|
|
|
var connection = (RtmpConnection)sender;
|
|
|
- var channelName = e.App + "/" + e.Path;
|
|
|
+ var channelName = (Config.ProxyMode? (e.App + "_" + RtmpProxy.ProxyId) : e.App) + "/" + e.Path;
|
|
|
var player = new RtmpPlayer(channelName, connection);
|
|
|
player.Closed += OnPlayerClosedAsync;
|
|
|
|
|
@@ -403,6 +455,13 @@ namespace DotnetRtmpServer.Net
|
|
|
if (players.Count == 0)
|
|
|
{
|
|
|
_players.TryRemove(player.ChannelName, out _);
|
|
|
+ if (Config.ProxyMode)
|
|
|
+ {
|
|
|
+ if (_proxies.TryGetValue(player.ChannelName, out var proxy))
|
|
|
+ {
|
|
|
+ proxy.Dispose();
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
else
|
|
|
{
|
|
@@ -418,6 +477,10 @@ namespace DotnetRtmpServer.Net
|
|
|
|
|
|
public void RegisterApp(string appName)
|
|
|
{
|
|
|
+ if (Config.ProxyMode)
|
|
|
+ {
|
|
|
+ throw new InvalidOperationException("Can not register app under proxy mode.");
|
|
|
+ }
|
|
|
lock (_registeredApps)
|
|
|
{
|
|
|
if (_registeredApps.IndexOf(appName) != -1)
|