diff --git a/NostrStreamer/Controllers/PlaylistController.cs b/NostrStreamer/Controllers/PlaylistController.cs index 4ca229a..ac8e8b5 100644 --- a/NostrStreamer/Controllers/PlaylistController.cs +++ b/NostrStreamer/Controllers/PlaylistController.cs @@ -16,9 +16,10 @@ public class PlaylistController : Controller private readonly IServiceScopeFactory _scopeFactory; private readonly HttpClient _client; private readonly SrsApi _srsApi; + private readonly ViewCounter _viewCounter; public PlaylistController(Config config, IMemoryCache cache, ILogger logger, IServiceScopeFactory scopeFactory, - HttpClient client, SrsApi srsApi) + HttpClient client, SrsApi srsApi, ViewCounter viewCounter) { _config = config; _cache = cache; @@ -26,10 +27,11 @@ public class PlaylistController : Controller _scopeFactory = scopeFactory; _client = client; _srsApi = srsApi; + _viewCounter = viewCounter; } [HttpGet("{variant}/{pubkey}.m3u8")] - public async Task RewritePlaylist([FromRoute] string pubkey, [FromRoute] string variant) + public async Task RewritePlaylist([FromRoute] string pubkey, [FromRoute] string variant, [FromQuery(Name = "hls_ctx")] string hlsCtx) { var key = await GetStreamKey(pubkey); if (string.IsNullOrEmpty(key)) @@ -74,6 +76,7 @@ public class PlaylistController : Controller } Response.Body.Close(); + _viewCounter.Activity(key, hlsCtx); } [HttpGet("{pubkey}.m3u8")] @@ -92,6 +95,7 @@ public class PlaylistController : Controller Response.StatusCode = 404; return; } + Response.ContentType = "application/x-mpegurl"; await using var sw = new StreamWriter(Response.Body); @@ -113,7 +117,9 @@ public class PlaylistController : Controller await sw.WriteLineAsync( $"#EXT-X-STREAM-INF:{string.Join(",", allArgs)},CODECS=\"avc1.640028,mp4a.40.2\""); - var u = new Uri(_config.DataHost, $"{variant.Name}/{pubkey}.m3u8{(!string.IsNullOrEmpty(hlsCtx) ? $"?hls_ctx={hlsCtx}" : "")}"); + var u = new Uri(_config.DataHost, + $"{variant.Name}/{pubkey}.m3u8{(!string.IsNullOrEmpty(hlsCtx) ? $"?hls_ctx={hlsCtx}" : "")}"); + await sw.WriteLineAsync(u.ToString()); } } diff --git a/NostrStreamer/Program.cs b/NostrStreamer/Program.cs index 782a50a..fd1b46c 100644 --- a/NostrStreamer/Program.cs +++ b/NostrStreamer/Program.cs @@ -50,6 +50,8 @@ internal static class Program services.AddTransient(); services.AddTransient(); services.AddHostedService(); + services.AddSingleton(); + services.AddHostedService(); // lnd services services.AddSingleton(); diff --git a/NostrStreamer/Services/BackgroundStreamManager.cs b/NostrStreamer/Services/BackgroundStreamManager.cs index 1b2a8f3..1ff6e74 100644 --- a/NostrStreamer/Services/BackgroundStreamManager.cs +++ b/NostrStreamer/Services/BackgroundStreamManager.cs @@ -21,14 +21,13 @@ public class BackgroundStreamManager : BackgroundService var streamManager = scope.ServiceProvider.GetRequiredService(); var srsApi = scope.ServiceProvider.GetRequiredService(); + var viewCounter = scope.ServiceProvider.GetRequiredService(); - var clients = await srsApi.ListClients(); - var streams = clients.Where(a => !a.Publish).GroupBy(a => a.Url); - foreach (var stream in streams) + var streams = await srsApi.ListStreams(); + foreach (var stream in streams.GroupBy(a => a.Name)) { - var viewers = 0; //stream.Select(a => a.Ip).Distinct().Count(); - var streamKey = stream.Key.Split("/").Last(); - await streamManager.UpdateViewers(streamKey, viewers); + var viewers = viewCounter.Current(stream.Key); + await streamManager.UpdateViewers(stream.Key, viewers); } } catch (Exception ex) diff --git a/NostrStreamer/Services/ViewCounter.cs b/NostrStreamer/Services/ViewCounter.cs new file mode 100644 index 0000000..773eba3 --- /dev/null +++ b/NostrStreamer/Services/ViewCounter.cs @@ -0,0 +1,77 @@ +using System.Collections.Concurrent; + +namespace NostrStreamer.Services; + +public class ViewCounter +{ + private readonly ConcurrentDictionary> _sessions = new(); + + public void Activity(string key, string token) + { + if (!_sessions.ContainsKey(key)) + { + _sessions.TryAdd(key, new()); + } + if (_sessions.TryGetValue(key, out var x)) + { + x[token] = DateTime.Now; + } + } + + public void Decay() + { + foreach (var k in _sessions.Keys) + { + if (_sessions.TryGetValue(k, out var x)) + { + _sessions[k] = x + .Where(a => a.Value > DateTime.Now.Subtract(TimeSpan.FromMinutes(2))) + .ToDictionary(a => a.Key, b => b.Value); + + if (_sessions[k].Count == 0) + { + _sessions.TryRemove(k, out _); + } + } + } + } + + public int Current(string key) + { + if (_sessions.TryGetValue(key, out var x)) + { + return x.Count; + } + + return 0; + } +} + +public class ViewCounterDecay : BackgroundService +{ + private readonly ViewCounter _viewCounter; + private readonly ILogger _logger; + + public ViewCounterDecay(ViewCounter viewCounter, ILogger logger) + { + _viewCounter = viewCounter; + _logger = logger; + } + + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + while (!stoppingToken.IsCancellationRequested) + { + try + { + _viewCounter.Decay(); + } + catch (Exception ex) + { + _logger.LogError(ex, "Something went wrong"); + } + + await Task.Delay(TimeSpan.FromMinutes(1), stoppingToken); + } + } +}