using System.Collections.Concurrent; using ErsatzTV.Core.Domain; using ErsatzTV.Core.Interfaces.Streaming; namespace ErsatzTV.Core.Streaming; public class DirectStreamSessionTracker : IDirectStreamSessionTracker { private readonly ConcurrentDictionary> _sessions = new(); public IDisposable Register(string channelNumber, StreamingMode streamingMode) { var session = new DirectStreamSession(Guid.NewGuid(), channelNumber, streamingMode, DateTimeOffset.Now); ConcurrentDictionary channelSessions = _sessions.GetOrAdd(channelNumber, _ => new ConcurrentDictionary()); OnRegisteringSession(); channelSessions.TryAdd(session.Id, session); return new Registration(this, session); } protected virtual void OnRegisteringSession() { } public bool IsActive(string channelNumber) => GetViewerCount(channelNumber) > 0; public int GetViewerCount(string channelNumber) => _sessions.TryGetValue(channelNumber, out ConcurrentDictionary channelSessions) ? channelSessions.Count : 0; public IReadOnlyCollection GetActiveSessions() => _sessions.Values.SelectMany(s => s.Values).ToList(); public IReadOnlyCollection GetActiveSessions(string channelNumber) => _sessions.TryGetValue(channelNumber, out ConcurrentDictionary channelSessions) ? channelSessions.Values.ToList() : []; private void Remove(DirectStreamSession session) { if (!_sessions.TryGetValue(session.ChannelNumber, out ConcurrentDictionary channelSessions)) { return; } channelSessions.TryRemove(session.Id, out _); } private sealed class Registration(DirectStreamSessionTracker tracker, DirectStreamSession session) : IDisposable { private int _disposed; public void Dispose() { if (Interlocked.Exchange(ref _disposed, 1) == 0) { tracker.Remove(session); } } } }