using System.Net.WebSockets; using System.Threading.Channels; namespace LarpixVoice; public class Session { public WebSocket Socket { get; } public Channel<(byte[] Buffer, int Length)> SendQueue { get; } // Ostatni znak życia (dowolna odebrana ramka - audio, wideo, ping typu 5). // Klient pinguje co 2 s, więc sesja bez ruchu przez VOICE_IDLE_TIMEOUT to // martwy polłączony TCP (zabita aplikacja, utracony NAT mapping) - taki wpis // zamiata task w Program.cs. Bez tego ghost-sesje wiszą w rosterze wiecznie // (/room/users zwraca nieżyjących uczestników -> ghost kafelki u innych, // a relay marnuje bandwidth na zamknięte sockety). private long _lastSeenUtcTicks; public DateTime LastSeenUtc => new(Interlocked.Read(ref _lastSeenUtcTicks), DateTimeKind.Utc); public Session(WebSocket socket) { Socket = socket; SendQueue = Channel.CreateBounded<(byte[] Buffer, int Length)>(new BoundedChannelOptions(256) { FullMode = BoundedChannelFullMode.Wait }); _lastSeenUtcTicks = DateTime.UtcNow.Ticks; } public void Touch() => Interlocked.Exchange(ref _lastSeenUtcTicks, DateTime.UtcNow.Ticks); }