Host on edges
This commit is contained in:
@ -40,6 +40,10 @@ public class Config
|
|||||||
public S3BlobConfig S3Store { get; init; } = null!;
|
public S3BlobConfig S3Store { get; init; } = null!;
|
||||||
|
|
||||||
public DateTime TosDate { get; init; }
|
public DateTime TosDate { get; init; }
|
||||||
|
|
||||||
|
public string GeoIpDatabase { get; init; } = null!;
|
||||||
|
|
||||||
|
public List<EdgeLocation> Edges { get; init; } = new();
|
||||||
}
|
}
|
||||||
|
|
||||||
public class LndConfig
|
public class LndConfig
|
||||||
@ -62,3 +66,11 @@ public sealed class S3BlobConfig
|
|||||||
public bool DisablePayloadSigning { get; init; }
|
public bool DisablePayloadSigning { get; init; }
|
||||||
public Uri PublicHost { get; init; } = null!;
|
public Uri PublicHost { get; init; } = null!;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public sealed class EdgeLocation
|
||||||
|
{
|
||||||
|
public string Name { get; init; } = null!;
|
||||||
|
public Uri Url { get; init; } = null!;
|
||||||
|
public double Latitude { get; init; }
|
||||||
|
public double Longitude { get; init; }
|
||||||
|
}
|
@ -16,9 +16,10 @@ public class PlaylistController : Controller
|
|||||||
private readonly SrsApi _srsApi;
|
private readonly SrsApi _srsApi;
|
||||||
private readonly ViewCounter _viewCounter;
|
private readonly ViewCounter _viewCounter;
|
||||||
private readonly StreamManagerFactory _streamManagerFactory;
|
private readonly StreamManagerFactory _streamManagerFactory;
|
||||||
|
private readonly EdgeSteering _edgeSteering;
|
||||||
|
|
||||||
public PlaylistController(Config config, ILogger<PlaylistController> logger,
|
public PlaylistController(Config config, ILogger<PlaylistController> logger,
|
||||||
HttpClient client, SrsApi srsApi, ViewCounter viewCounter, StreamManagerFactory streamManagerFactory)
|
HttpClient client, SrsApi srsApi, ViewCounter viewCounter, StreamManagerFactory streamManagerFactory, EdgeSteering edgeSteering)
|
||||||
{
|
{
|
||||||
_config = config;
|
_config = config;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
@ -26,6 +27,7 @@ public class PlaylistController : Controller
|
|||||||
_srsApi = srsApi;
|
_srsApi = srsApi;
|
||||||
_viewCounter = viewCounter;
|
_viewCounter = viewCounter;
|
||||||
_streamManagerFactory = streamManagerFactory;
|
_streamManagerFactory = streamManagerFactory;
|
||||||
|
_edgeSteering = edgeSteering;
|
||||||
}
|
}
|
||||||
|
|
||||||
[ResponseCache(Duration = 1, Location = ResponseCacheLocation.Any)]
|
[ResponseCache(Duration = 1, Location = ResponseCacheLocation.Any)]
|
||||||
@ -106,6 +108,7 @@ public class PlaylistController : Controller
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
var edge = _edgeSteering.GetEdge(HttpContext);
|
||||||
var streamManager = await _streamManagerFactory.ForStream(id);
|
var streamManager = await _streamManagerFactory.ForStream(id);
|
||||||
var userStream = streamManager.GetStream();
|
var userStream = streamManager.GetStream();
|
||||||
|
|
||||||
@ -138,8 +141,17 @@ public class PlaylistController : Controller
|
|||||||
await sw.WriteLineAsync(
|
await sw.WriteLineAsync(
|
||||||
$"#EXT-X-STREAM-INF:{string.Join(",", allArgs)}");
|
$"#EXT-X-STREAM-INF:{string.Join(",", allArgs)}");
|
||||||
|
|
||||||
var u = $"../{variant.SourceName}/{userStream.Id}.m3u8{(!string.IsNullOrEmpty(hlsCtx) ? $"?hls_ctx={hlsCtx}" : "")}";
|
var path = $"{variant.SourceName}/{userStream.Id}.m3u8{(!string.IsNullOrEmpty(hlsCtx) ? $"?hls_ctx={hlsCtx}" : "")}";
|
||||||
await sw.WriteLineAsync(u);
|
if (edge != default)
|
||||||
|
{
|
||||||
|
var u = new Uri(edge.Url, path);
|
||||||
|
await sw.WriteLineAsync(u.ToString());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var u = $"../{path}";
|
||||||
|
await sw.WriteLineAsync(u);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
using Amazon;
|
using Amazon;
|
||||||
using Amazon.Runtime;
|
using Amazon.Runtime;
|
||||||
using Amazon.S3;
|
using Amazon.S3;
|
||||||
|
using MaxMind.GeoIP2;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Nostr.Client.Json;
|
using Nostr.Client.Json;
|
||||||
using Nostr.Client.Keys;
|
using Nostr.Client.Keys;
|
||||||
@ -50,6 +51,46 @@ public static class Extensions
|
|||||||
return !string.IsNullOrEmpty(user.Tags) ?
|
return !string.IsNullOrEmpty(user.Tags) ?
|
||||||
user.Tags.Split(",", StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries) : Array.Empty<string>();
|
user.Tags.Split(",", StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries) : Array.Empty<string>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static (double lat, double lon)? GetLocation(this HttpContext ctx, IGeoIP2DatabaseReader db)
|
||||||
|
{
|
||||||
|
var ip = ctx.GetRealIp();
|
||||||
|
var loc = db.TryCity(ip, out var city) ? city?.Location : default;
|
||||||
|
if ((loc?.Latitude.HasValue ?? false) && loc.Longitude.HasValue)
|
||||||
|
{
|
||||||
|
return (loc.Latitude.Value, loc.Longitude.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return default;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string GetRealIp(this HttpContext ctx)
|
||||||
|
{
|
||||||
|
var cci = ctx.Request.Headers.TryGetValue("CF-Connecting-IP", out var xx) ? xx.ToString() : null;
|
||||||
|
if (!string.IsNullOrEmpty(cci))
|
||||||
|
{
|
||||||
|
return cci;
|
||||||
|
}
|
||||||
|
|
||||||
|
var xff = ctx.Request.Headers.TryGetValue("X-Forwarded-For", out var x) ? x.ToString() : null;
|
||||||
|
if (!string.IsNullOrEmpty(xff))
|
||||||
|
{
|
||||||
|
return xff.Split(",", StringSplitOptions.RemoveEmptyEntries).First();
|
||||||
|
}
|
||||||
|
|
||||||
|
return ctx.Connection.RemoteIpAddress!.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static double GetDistance(double longitude, double latitude, double otherLongitude, double otherLatitude)
|
||||||
|
{
|
||||||
|
var d1 = latitude * (Math.PI / 180.0);
|
||||||
|
var num1 = longitude * (Math.PI / 180.0);
|
||||||
|
var d2 = otherLatitude * (Math.PI / 180.0);
|
||||||
|
var num2 = otherLongitude * (Math.PI / 180.0) - num1;
|
||||||
|
var d3 = Math.Pow(Math.Sin((d2 - d1) / 2.0), 2.0) + Math.Cos(d1) * Math.Cos(d2) * Math.Pow(Math.Sin(num2 / 2.0), 2.0);
|
||||||
|
|
||||||
|
return 6376500.0 * (2.0 * Math.Atan2(Math.Sqrt(d3), Math.Sqrt(1.0 - d3)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class Variant
|
public class Variant
|
||||||
|
@ -38,6 +38,7 @@
|
|||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="LNURL" Version="0.0.30" />
|
<PackageReference Include="LNURL" Version="0.0.30" />
|
||||||
|
<PackageReference Include="MaxMind.GeoIP2" Version="5.1.0" />
|
||||||
<PackageReference Include="MediaFormatLibrary.Lib" Version="1.0.0" />
|
<PackageReference Include="MediaFormatLibrary.Lib" Version="1.0.0" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="6.0.19" />
|
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="6.0.19" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="7.0.8" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="7.0.8" />
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using System.Security.Claims;
|
using System.Security.Claims;
|
||||||
|
using MaxMind.GeoIP2;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Authorization.Infrastructure;
|
using Microsoft.AspNetCore.Authorization.Infrastructure;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
@ -29,6 +30,10 @@ internal static class Program
|
|||||||
services.AddControllers().AddNewtonsoftJson();
|
services.AddControllers().AddNewtonsoftJson();
|
||||||
services.AddSingleton(config);
|
services.AddSingleton(config);
|
||||||
|
|
||||||
|
// GeoIP
|
||||||
|
services.AddSingleton<IGeoIP2DatabaseReader>(_ => new DatabaseReader(config.GeoIpDatabase));
|
||||||
|
services.AddTransient<EdgeSteering>();
|
||||||
|
|
||||||
// nostr auth
|
// nostr auth
|
||||||
services.AddTransient<NostrAuthHandler>();
|
services.AddTransient<NostrAuthHandler>();
|
||||||
services.AddAuthentication(o =>
|
services.AddAuthentication(o =>
|
||||||
|
35
NostrStreamer/Services/EdgeSteering.cs
Normal file
35
NostrStreamer/Services/EdgeSteering.cs
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
using System.Diagnostics;
|
||||||
|
using MaxMind.GeoIP2;
|
||||||
|
|
||||||
|
namespace NostrStreamer.Services;
|
||||||
|
|
||||||
|
public class EdgeSteering
|
||||||
|
{
|
||||||
|
private readonly Config _config;
|
||||||
|
private readonly IGeoIP2DatabaseReader _db;
|
||||||
|
private readonly ILogger<EdgeSteering> _logger;
|
||||||
|
|
||||||
|
public EdgeSteering(Config config, IGeoIP2DatabaseReader db, ILogger<EdgeSteering> logger)
|
||||||
|
{
|
||||||
|
_config = config;
|
||||||
|
_db = db;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public EdgeLocation? GetEdge(HttpContext ctx)
|
||||||
|
{
|
||||||
|
var sw = Stopwatch.StartNew();
|
||||||
|
var loc = ctx.GetLocation(_db);
|
||||||
|
if (loc != default)
|
||||||
|
{
|
||||||
|
var ret = _config.Edges.MinBy(a => Extensions.GetDistance(a.Longitude, a.Latitude, loc.Value.lon, loc.Value.lat));
|
||||||
|
sw.Stop();
|
||||||
|
_logger.LogTrace("Found edge in {n:#,##0.#}ms", sw.Elapsed.TotalMilliseconds);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
sw.Stop();
|
||||||
|
_logger.LogTrace("Found no edge in {n:#,##0.#}ms", sw.Elapsed.TotalMilliseconds);
|
||||||
|
return default;
|
||||||
|
}
|
||||||
|
}
|
@ -32,6 +32,21 @@
|
|||||||
"AccessKey": "TQcxug1ZAXfnZ5bvc9n5",
|
"AccessKey": "TQcxug1ZAXfnZ5bvc9n5",
|
||||||
"SecretKey": "p7EK4qew6DBkBPqrpRPuJgTOc6ChUlfIcEdAwE7K",
|
"SecretKey": "p7EK4qew6DBkBPqrpRPuJgTOc6ChUlfIcEdAwE7K",
|
||||||
"PublicHost": "http://localhost:9010"
|
"PublicHost": "http://localhost:9010"
|
||||||
}
|
},
|
||||||
|
"GeoIpDatabase": "/Users/kieran/Downloads/GeoLite2-City_20230801/GeoLite2-City.mmdb",
|
||||||
|
"Edges": [
|
||||||
|
{
|
||||||
|
"Name": "US0",
|
||||||
|
"Url": "https://us0.edge.zap.stream/",
|
||||||
|
"Longitude": -73.8024,
|
||||||
|
"Latitude": 45.4616
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "Origin",
|
||||||
|
"Url": "https://data.zap.stream/",
|
||||||
|
"Longitude": 2.12664,
|
||||||
|
"Latitude": 50.98515
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user