Implement custom view counters
This commit is contained in:
@ -16,9 +16,10 @@ public class PlaylistController : Controller
|
|||||||
private readonly IServiceScopeFactory _scopeFactory;
|
private readonly IServiceScopeFactory _scopeFactory;
|
||||||
private readonly HttpClient _client;
|
private readonly HttpClient _client;
|
||||||
private readonly SrsApi _srsApi;
|
private readonly SrsApi _srsApi;
|
||||||
|
private readonly ViewCounter _viewCounter;
|
||||||
|
|
||||||
public PlaylistController(Config config, IMemoryCache cache, ILogger<PlaylistController> logger, IServiceScopeFactory scopeFactory,
|
public PlaylistController(Config config, IMemoryCache cache, ILogger<PlaylistController> logger, IServiceScopeFactory scopeFactory,
|
||||||
HttpClient client, SrsApi srsApi)
|
HttpClient client, SrsApi srsApi, ViewCounter viewCounter)
|
||||||
{
|
{
|
||||||
_config = config;
|
_config = config;
|
||||||
_cache = cache;
|
_cache = cache;
|
||||||
@ -26,10 +27,11 @@ public class PlaylistController : Controller
|
|||||||
_scopeFactory = scopeFactory;
|
_scopeFactory = scopeFactory;
|
||||||
_client = client;
|
_client = client;
|
||||||
_srsApi = srsApi;
|
_srsApi = srsApi;
|
||||||
|
_viewCounter = viewCounter;
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("{variant}/{pubkey}.m3u8")]
|
[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);
|
var key = await GetStreamKey(pubkey);
|
||||||
if (string.IsNullOrEmpty(key))
|
if (string.IsNullOrEmpty(key))
|
||||||
@ -74,6 +76,7 @@ public class PlaylistController : Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
Response.Body.Close();
|
Response.Body.Close();
|
||||||
|
_viewCounter.Activity(key, hlsCtx);
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("{pubkey}.m3u8")]
|
[HttpGet("{pubkey}.m3u8")]
|
||||||
@ -92,6 +95,7 @@ public class PlaylistController : Controller
|
|||||||
Response.StatusCode = 404;
|
Response.StatusCode = 404;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Response.ContentType = "application/x-mpegurl";
|
Response.ContentType = "application/x-mpegurl";
|
||||||
await using var sw = new StreamWriter(Response.Body);
|
await using var sw = new StreamWriter(Response.Body);
|
||||||
|
|
||||||
@ -113,7 +117,9 @@ public class PlaylistController : Controller
|
|||||||
await sw.WriteLineAsync(
|
await sw.WriteLineAsync(
|
||||||
$"#EXT-X-STREAM-INF:{string.Join(",", allArgs)},CODECS=\"avc1.640028,mp4a.40.2\"");
|
$"#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());
|
await sw.WriteLineAsync(u.ToString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -50,6 +50,8 @@ internal static class Program
|
|||||||
services.AddTransient<StreamManager>();
|
services.AddTransient<StreamManager>();
|
||||||
services.AddTransient<SrsApi>();
|
services.AddTransient<SrsApi>();
|
||||||
services.AddHostedService<BackgroundStreamManager>();
|
services.AddHostedService<BackgroundStreamManager>();
|
||||||
|
services.AddSingleton<ViewCounter>();
|
||||||
|
services.AddHostedService<ViewCounterDecay>();
|
||||||
|
|
||||||
// lnd services
|
// lnd services
|
||||||
services.AddSingleton<LndNode>();
|
services.AddSingleton<LndNode>();
|
||||||
|
@ -21,14 +21,13 @@ public class BackgroundStreamManager : BackgroundService
|
|||||||
|
|
||||||
var streamManager = scope.ServiceProvider.GetRequiredService<StreamManager>();
|
var streamManager = scope.ServiceProvider.GetRequiredService<StreamManager>();
|
||||||
var srsApi = scope.ServiceProvider.GetRequiredService<SrsApi>();
|
var srsApi = scope.ServiceProvider.GetRequiredService<SrsApi>();
|
||||||
|
var viewCounter = scope.ServiceProvider.GetRequiredService<ViewCounter>();
|
||||||
|
|
||||||
var clients = await srsApi.ListClients();
|
var streams = await srsApi.ListStreams();
|
||||||
var streams = clients.Where(a => !a.Publish).GroupBy(a => a.Url);
|
foreach (var stream in streams.GroupBy(a => a.Name))
|
||||||
foreach (var stream in streams)
|
|
||||||
{
|
{
|
||||||
var viewers = 0; //stream.Select(a => a.Ip).Distinct().Count();
|
var viewers = viewCounter.Current(stream.Key);
|
||||||
var streamKey = stream.Key.Split("/").Last();
|
await streamManager.UpdateViewers(stream.Key, viewers);
|
||||||
await streamManager.UpdateViewers(streamKey, viewers);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
77
NostrStreamer/Services/ViewCounter.cs
Normal file
77
NostrStreamer/Services/ViewCounter.cs
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
using System.Collections.Concurrent;
|
||||||
|
|
||||||
|
namespace NostrStreamer.Services;
|
||||||
|
|
||||||
|
public class ViewCounter
|
||||||
|
{
|
||||||
|
private readonly ConcurrentDictionary<string, Dictionary<string, DateTime>> _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<ViewCounterDecay> _logger;
|
||||||
|
|
||||||
|
public ViewCounterDecay(ViewCounter viewCounter, ILogger<ViewCounterDecay> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user