Skip to content

Commit

Permalink
feat: add RtmpUrlParser
Browse files Browse the repository at this point in the history
  • Loading branch information
josephnhtam committed Oct 1, 2024
1 parent 03d275e commit 6d1190b
Show file tree
Hide file tree
Showing 2 changed files with 110 additions and 4 deletions.
14 changes: 10 additions & 4 deletions samples/LiveStreamingServerNet.RtmpClientPlayDemo/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,25 @@ public class Program
/// </summary>
public static async Task Main()
{
var rtmpUrl = "rtmp://127.0.0.1/live/demo";
var parsedRtmpUrl = await RtmpUrlParser.ParseAsync(rtmpUrl);

var serverEndPoint = parsedRtmpUrl.ServerEndPoint;
var information = new Dictionary<string, object> { ["tcUrl"] = parsedRtmpUrl.TcUrl };
var appName = parsedRtmpUrl.AppName;
var streamName = parsedRtmpUrl.StreamName;

var rtmpClient = RtmpClientBuilder.Create()
.ConfigureLogging(options => options.AddConsole().SetMinimumLevel(LogLevel.Trace))
.Build();

var logger = rtmpClient.Services.GetRequiredService<ILogger<Program>>();

var serverEndPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 1935);
var information = new Dictionary<string, object> { ["tcUrl"] = "rtmp://127.0.0.1/live" };
await rtmpClient.ConnectAsync(serverEndPoint, "live", information);
await rtmpClient.ConnectAsync(serverEndPoint, appName, information);

var rtmpStream = await rtmpClient.CreateStreamAsync();

await RecordStreamAsFlvAsync("demo", rtmpClient, rtmpStream, logger);
await RecordStreamAsFlvAsync(streamName, rtmpClient, rtmpStream, logger);
}

private static async Task RecordStreamAsFlvAsync(string streamName, IRtmpClient rtmpClient, IRtmpStream rtmpStream, ILogger<Program> logger)
Expand Down
100 changes: 100 additions & 0 deletions src/LiveStreamingServerNet.Rtmp.Client/RtmpUrlParser.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
using System.Net.Sockets;
using System.Net;
using System.Text.RegularExpressions;
using LiveStreamingServerNet.Networking;

namespace LiveStreamingServerNet.Rtmp.Client
{
public static class RtmpUrlParser
{
private static Regex _regex = new Regex(
@"^(?<scheme>rtmp[s]?)://(?<hostname>\[[^\]]+\]|[^:/]+)(?::(?<port>\d+))?/(?<appName>[^/]+)/(?<streamName>.+)$",
RegexOptions.IgnoreCase | RegexOptions.Compiled
);

public static async ValueTask<ParsedRtmpUrl> ParseAsync(string rtmpUrl)
{
if (string.IsNullOrWhiteSpace(rtmpUrl))
throw new ArgumentException("RTMP URL cannot be null or empty.");

rtmpUrl = rtmpUrl.Trim();

var match = _regex.Match(rtmpUrl);

if (!match.Success)
throw new ArgumentException("Invalid RTMP URL format.");

var schemeRaw = match.Groups["scheme"].Value.ToLower();
var hostnameRaw = match.Groups["hostname"].Value;
var portStr = match.Groups["port"].Value;
var appName = match.Groups["appName"].Value;
var streamName = match.Groups["streamName"].Value;

if (!Enum.TryParse<RtmpScheme>(schemeRaw, true, out var scheme))
throw new ArgumentException("Invalid RTMP scheme in RTMP URL.");

var hostname = NormalizeHostname(hostnameRaw);

var port = portStr switch
{
var str when (string.IsNullOrEmpty(str)) => scheme == RtmpScheme.RTMPS ? 443 : 1935,
_ => int.TryParse(portStr, out var p) ? p : throw new ArgumentException("Invalid port number in RTMP URL.")
};

var ipAddress = await GetIpAddressAsync(hostname);
var serverEndPoint = new ServerEndPoint(new IPEndPoint(ipAddress, port), scheme == RtmpScheme.RTMPS);
var tcUrl = $"{schemeRaw}://{hostname}:{port}/{appName}";
return new ParsedRtmpUrl(serverEndPoint, appName, streamName, tcUrl);
}

private static string NormalizeHostname(string hostnameRaw)
{
if (hostnameRaw.StartsWith("[") && hostnameRaw.EndsWith("]"))
return hostnameRaw.Substring(1, hostnameRaw.Length - 2);

return hostnameRaw;
}

private static async ValueTask<IPAddress> GetIpAddressAsync(string hostname)
{
if (IPAddress.TryParse(hostname, out var ipAddress) && ipAddress != null)
return ipAddress;

return await ResolveHostnameToIpAddressAsync(hostname);
}

private static async Task<IPAddress> ResolveHostnameToIpAddressAsync(string hostname)
{
try
{
var addresses = await Dns.GetHostAddressesAsync(hostname);

foreach (var address in addresses)
{
if (address.AddressFamily == AddressFamily.InterNetwork)
return address;
}

foreach (var address in addresses)
{
if (address.AddressFamily == AddressFamily.InterNetworkV6)
return address;
}

throw new SocketException((int)SocketError.HostNotFound);
}
catch (SocketException ex)
{
throw new SocketException((int)ex.SocketErrorCode);
}
}

private enum RtmpScheme
{
RTMP,
RTMPS
}
}

public record ParsedRtmpUrl(ServerEndPoint ServerEndPoint, string AppName, string StreamName, string TcUrl);
}

0 comments on commit 6d1190b

Please sign in to comment.