Add adaptive packet loss handling
This commit is contained in:
@@ -39,6 +39,8 @@ public sealed partial class VoiceCatClient : IAsyncDisposable
|
||||
private long nextRequest;
|
||||
private AuthResult? authentication;
|
||||
private ServerHello? hello;
|
||||
private uint adaptiveLossChannel;
|
||||
private int adaptiveLossPercent = -1;
|
||||
private ClientConnectionState state;
|
||||
|
||||
public event Action<ClientConnectionState>? ConnectionStateChanged;
|
||||
@@ -47,6 +49,7 @@ public sealed partial class VoiceCatClient : IAsyncDisposable
|
||||
public Task Completion => reader;
|
||||
public AuthResult? Authentication { get { lock (stateGate) return authentication?.Clone(); } }
|
||||
public ServerHello? ServerHello { get { lock (stateGate) return hello?.Clone(); } }
|
||||
public bool SupportsAdaptivePacketLoss { get { lock (stateGate) return hello?.Features.Contains(ProtocolFeatures.AdaptivePacketLoss) == true; } }
|
||||
public IReadOnlyList<Channel> Channels { get { lock (stateGate) return channels.Values.Select(c => c.Clone()).ToArray(); } }
|
||||
public IReadOnlyList<User> Users { get { lock (stateGate) return users.Values.Select(u => u.Clone()).ToArray(); } }
|
||||
public bool TryReadEvent(out Envelope? envelope) => events.Reader.TryRead(out envelope);
|
||||
@@ -94,7 +97,9 @@ public sealed partial class VoiceCatClient : IAsyncDisposable
|
||||
pins.Pin(host, port, certificatePin);
|
||||
}
|
||||
reader = ReadAsync(control, connectionLifetime.Token);
|
||||
Envelope response = await RequestAsync(new() { ClientHello = new() { ProtoVersion = 2, ClientName = clientName, ClientVersion = clientVersion } }, token).ConfigureAwait(false);
|
||||
var clientHello = new ClientHello { ProtoVersion = 2, ClientName = clientName, ClientVersion = clientVersion };
|
||||
clientHello.Features.Add(ProtocolFeatures.AdaptivePacketLoss);
|
||||
Envelope response = await RequestAsync(new() { ClientHello = clientHello }, token).ConfigureAwait(false);
|
||||
if (response.ServerHello?.ProtoVersion != 2) throw new IOException("Unsupported server protocol.");
|
||||
lock (stateGate) hello = response.ServerHello.Clone();
|
||||
keepalive = KeepaliveAsync(connectionLifetime.Token);
|
||||
@@ -147,7 +152,14 @@ public sealed partial class VoiceCatClient : IAsyncDisposable
|
||||
var info = new StreamInfo { StreamId = response.StreamId, Ssrc = response.Ssrc, Kind = kind, Audio = response.EffectiveAudio.Clone(), Label = label };
|
||||
try
|
||||
{
|
||||
lock (stateGate) { if (State != ClientConnectionState.Connected) throw new InvalidOperationException("Client disconnected during stream negotiation."); Audio.AddLocalStream(info, captureChannels); localStreams[info.StreamId] = info; }
|
||||
lock (stateGate)
|
||||
{
|
||||
if (State != ClientConnectionState.Connected) throw new InvalidOperationException("Client disconnected during stream negotiation.");
|
||||
Audio.AddLocalStream(info, captureChannels); localStreams[info.StreamId] = info;
|
||||
User? self = authentication is null ? null : users.GetValueOrDefault(authentication.Self.Id, authentication.Self);
|
||||
if (self is not null && self.ChannelId == adaptiveLossChannel && adaptiveLossPercent >= 0)
|
||||
Audio.SetExpectedPacketLoss(adaptiveLossPercent);
|
||||
}
|
||||
return info.Clone();
|
||||
}
|
||||
catch { if (State == ClientConnectionState.Connected) Send(new() { StreamStop = new() { StreamId = info.StreamId } }); throw; }
|
||||
@@ -226,13 +238,31 @@ public sealed partial class VoiceCatClient : IAsyncDisposable
|
||||
if (message.ChannelEvent is not null)
|
||||
{
|
||||
if (message.ChannelEvent.Kind == ChannelEvent.Types.Kind.Deleted) channels.Remove(message.ChannelEvent.DeletedId);
|
||||
else if (message.ChannelEvent.Channel is not null) channels[message.ChannelEvent.Channel.Id] = message.ChannelEvent.Channel.Clone();
|
||||
else if (message.ChannelEvent.Channel is not null)
|
||||
{
|
||||
PacketLossMode previousMode = channels.GetValueOrDefault(message.ChannelEvent.Channel.Id)?.Audio.PacketLossMode
|
||||
?? PacketLossMode.PacketLossManual;
|
||||
channels[message.ChannelEvent.Channel.Id] = message.ChannelEvent.Channel.Clone();
|
||||
User? self = authentication is null ? null : users.GetValueOrDefault(authentication.Self.Id, authentication.Self);
|
||||
if (self?.ChannelId == message.ChannelEvent.Channel.Id && previousMode != message.ChannelEvent.Channel.Audio.PacketLossMode)
|
||||
{ adaptiveLossChannel = 0; adaptiveLossPercent = -1; }
|
||||
}
|
||||
}
|
||||
if (message.UserEvent is not null)
|
||||
{
|
||||
if (message.UserEvent.Kind == UserEvent.Types.Kind.Left) users.Remove(message.UserEvent.LeftId);
|
||||
else if (message.UserEvent.User is not null) users[message.UserEvent.User.Id] = message.UserEvent.User.Clone();
|
||||
}
|
||||
if (message.PacketLossUpdate is not null && authentication is not null)
|
||||
{
|
||||
User self = users.GetValueOrDefault(authentication.Self.Id, authentication.Self);
|
||||
if (self.ChannelId == message.PacketLossUpdate.ChannelId)
|
||||
{
|
||||
adaptiveLossChannel = self.ChannelId;
|
||||
adaptiveLossPercent = checked((int)message.PacketLossUpdate.AppliedPercent);
|
||||
Audio.SetExpectedPacketLoss(checked((int)message.PacketLossUpdate.AppliedPercent));
|
||||
}
|
||||
}
|
||||
if (authentication is not null && (message.ServerState is not null || message.UserEvent is not null))
|
||||
{
|
||||
User self = users.GetValueOrDefault(authentication.Self.Id, authentication.Self);
|
||||
@@ -273,7 +303,7 @@ public sealed partial class VoiceCatClient : IAsyncDisposable
|
||||
media = null;
|
||||
mediaCrypto?.Dispose(); mediaCrypto = null;
|
||||
connectionLifetime?.Dispose(); connectionLifetime = null;
|
||||
lock (stateGate) { authentication = null; hello = null; channels.Clear(); users.Clear(); }
|
||||
lock (stateGate) { authentication = null; hello = null; channels.Clear(); users.Clear(); adaptiveLossChannel = 0; adaptiveLossPercent = -1; }
|
||||
lock (stateGate)
|
||||
{
|
||||
foreach (var id in localStreams.Keys) Audio.RemoveLocalStream(id);
|
||||
|
||||
Reference in New Issue
Block a user