Add adaptive packet loss handling
Build and test / test (macos-latest) (push) Canceled after 0s
Build and test / test (ubuntu-24.04) (push) Canceled after 0s
Build and test / test (windows-latest) (push) Canceled after 0s
Build and test / apple-client (push) Canceled after 0s

This commit is contained in:
2026-09-22 16:54:49 +02:00
parent 7c4708c3de
commit 7b0c003ad4
35 changed files with 518 additions and 60 deletions
+40
View File
@@ -10,6 +10,34 @@ namespace VoiceCat.Tests;
public sealed class MediaRelayTests
{
[Fact]
public async Task AutomaticModeReportsAuthenticatedSenderUplinkLossWithCap()
{
var clock = new ManualClock();
await using var fixture = new ServerFixture(timeProvider: clock);
Client admin = await ChannelManagementTests.AdminAsync(fixture);
using (var store = new VoiceCat.Server.Data.AccountStore(Path.Combine(fixture.Directory, "voicecat.db")))
{
Channel lobby = store.LoadChannels().Single(channel => channel.Id == 1);
lobby.Audio.PacketLossMode = PacketLossMode.PacketLossAutoFast;
Assert.True(await ChannelManagementTests.ResultAsync(admin, new() { EditChannel = new() { Channel = lobby } }));
}
await using var alice = await VoicePeer.AttachAsync(fixture, admin);
await using var bob = await VoicePeer.ConnectAsync(fixture, "Bob");
StreamAnnounceResult stream = await alice.AnnounceAsync(StreamKind.StreamMic);
await alice.SendAsync(alice.Seal(stream.Ssrc, [1]));
await bob.ReceiveVoiceAsync();
for (int i = 0; i < 99; i++) alice.Seal(stream.Ssrc, [2]);
clock.Advance(TimeSpan.FromSeconds(3));
await alice.SendAsync(alice.Seal(stream.Ssrc, [3]));
PacketLossUpdate update = (await admin.ReadUntilAsync(envelope => envelope.PacketLossUpdate is not null)).PacketLossUpdate;
Assert.Equal(1U, update.ChannelId);
Assert.Equal(99U, update.MeasuredPercent);
Assert.Equal(30U, update.AppliedPercent);
}
[Fact]
public async Task DisconnectInvalidatesBothBindingAndActiveStreams()
{
@@ -162,6 +190,10 @@ public sealed class MediaRelayTests
{
Client client = await fixture.ConnectAsync();
await client.LoginAsync(nickname);
return await AttachAsync(fixture, client);
}
internal static async Task<VoicePeer> AttachAsync(ServerFixture fixture, Client client)
{
var peer = new VoicePeer(client, fixture.Server.MediaEndPoint, await client.TakeMediaCryptoAsync());
client.Send(new() { UdpBinding = new() { UdpToken = client.Authentication!.UdpToken } });
Assert.True((await client.ReadUntilAsync(e => e.UdpBinding is not null)).UdpBinding.Ack);
@@ -217,4 +249,12 @@ public sealed class MediaRelayTests
}
public async ValueTask DisposeAsync() { udp.Dispose(); crypto.Dispose(); await Client.DisposeAsync(); }
}
private sealed class ManualClock : TimeProvider
{
private long timestamp;
public override long TimestampFrequency => 1_000;
public override long GetTimestamp() => Volatile.Read(ref timestamp);
internal void Advance(TimeSpan duration) => Interlocked.Add(ref timestamp, (long)duration.TotalMilliseconds);
}
}