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
@@ -71,8 +71,8 @@ internal sealed class AdministrationWindowController : NSWindowController
private async void ServerMute(object? sender, EventArgs args) { User? user = User(); if (user is not null) Show(await client.SetServerMuteAsync(user.Id, !user.ServerMuted, user.ServerDeafened)); }
private async void ServerDeafen(object? sender, EventArgs args) { User? user = User(); if (user is not null) Show(await client.SetServerMuteAsync(user.Id, user.ServerMuted, !user.ServerDeafened)); }
private void Tune(object? sender, EventArgs args) { User? user = User(); if (user is not null) PerUserTuning.Run(client, user); }
private async void CreateChannel(object? sender, EventArgs args) { ChannelEdit? edit = ChannelEditor.Run(null, client.Channels); if (edit is not null) { Show(await client.CreateChannelAsync(edit.Channel, edit.Password)); Refresh(); } }
private async void EditChannel(object? sender, EventArgs args) { Channel? channel = Channel(); if (channel is null) return; ChannelEdit? edit = ChannelEditor.Run(channel, client.Channels); if (edit is not null) { Show(await client.EditChannelAsync(edit.Channel, edit.Password)); Refresh(); } }
private async void CreateChannel(object? sender, EventArgs args) { ChannelEdit? edit = ChannelEditor.Run(null, client.Channels, client.SupportsAdaptivePacketLoss); if (edit is not null) { Show(await client.CreateChannelAsync(edit.Channel, edit.Password)); Refresh(); } }
private async void EditChannel(object? sender, EventArgs args) { Channel? channel = Channel(); if (channel is null) return; ChannelEdit? edit = ChannelEditor.Run(channel, client.Channels, client.SupportsAdaptivePacketLoss); if (edit is not null) { Show(await client.EditChannelAsync(edit.Channel, edit.Password)); Refresh(); } }
private async void DeleteChannel(object? sender, EventArgs args)
{
Channel? channel = Channel(); if (channel is null || channel.Id == 1) return;
+19 -9
View File
@@ -7,7 +7,7 @@ namespace VoiceCat.Mac;
internal static class ChannelEditor
{
internal static ChannelEdit? Run(Channel? existing, IReadOnlyList<Channel> channels)
internal static ChannelEdit? Run(Channel? existing, IReadOnlyList<Channel> channels, bool supportsAdaptivePacketLoss)
{
var view = new NSView(new CGRect(0, 0, 520, 500));
var name = Field(existing?.Name ?? "", 360); var topic = Field(existing?.Topic ?? "", 330);
@@ -25,13 +25,21 @@ internal static class ChannelEditor
Add(view, "Maximum users (0=none)", maximum, 240); Add(view, "Sort order", order, 210); Add(view, "Password", password, 180);
Add(view, "Mode", mode, 150); Add(view, "Sample rate", rate, 120); Add(view, "Frame ms", frame, 90); Add(view, "Application", application, 60); Add(view, "Bitrate", bitrate, 30);
var advanced = new NSView(new CGRect(0, 0, 520, 105));
var fec = Check("In-band FEC", existing?.Audio?.Fec ?? true, 0, 75); var dtx = Check("DTX", existing?.Audio?.Dtx ?? true, 130, 75);
var dred = Check("Deep redundancy", existing?.Audio?.Dred ?? false, 230, 75);
var loss = Field((existing?.Audio?.ExpectedPacketLoss ?? 5).ToString(), 40); loss.Frame = new CGRect(155, 40, 100, 24);
var complexity = Field((existing?.Audio?.Complexity ?? 10).ToString(), 10); complexity.Frame = new CGRect(155, 10, 100, 24);
advanced.AddSubview(fec); advanced.AddSubview(dtx); advanced.AddSubview(dred); Add(advanced, "Packet loss %", loss, 40); Add(advanced, "Complexity 010", complexity, 10);
var container = new NSView(new CGRect(0, 0, 520, 620)); view.Frame = new CGRect(0, 110, 520, 500); container.AddSubview(view); container.AddSubview(advanced);
var advanced = new NSView(new CGRect(0, 0, 520, 175));
var automaticLoss = Check("Automatic packet loss", existing?.Audio?.PacketLossMode != PacketLossMode.PacketLossManual, 155, 140);
automaticLoss.Frame = new CGRect(155, 140, 200, 24);
automaticLoss.Enabled = supportsAdaptivePacketLoss;
var lossSpeed = Picker(["Fast", "Balanced", "Stable"], 105);
lossSpeed.SelectItem(existing?.Audio?.PacketLossMode switch { PacketLossMode.PacketLossAutoFast => 0, PacketLossMode.PacketLossAutoStable => 2, _ => 1 });
var loss = Field((existing?.Audio?.ExpectedPacketLoss ?? 5).ToString(), 70); loss.Frame = new CGRect(155, 70, 100, 24);
var complexity = Field((existing?.Audio?.Complexity ?? 10).ToString(), 40); complexity.Frame = new CGRect(155, 40, 100, 24);
var fec = Check("In-band FEC", existing?.Audio?.Fec ?? true, 0, 10); var dtx = Check("DTX", existing?.Audio?.Dtx ?? true, 130, 10);
var dred = Check("Deep redundancy", existing?.Audio?.Dred ?? false, 230, 10);
void UpdateLossControls() { loss.Enabled = !On(automaticLoss); lossSpeed.Enabled = supportsAdaptivePacketLoss && On(automaticLoss); }
automaticLoss.Activated += (_, _) => UpdateLossControls(); UpdateLossControls();
advanced.AddSubview(automaticLoss); Add(advanced, "Adaptation speed", lossSpeed, 105); Add(advanced, "Packet loss %", loss, 70); Add(advanced, "Complexity 010", complexity, 40);
advanced.AddSubview(fec); advanced.AddSubview(dtx); advanced.AddSubview(dred);
var container = new NSView(new CGRect(0, 0, 520, 690)); view.Frame = new CGRect(0, 180, 520, 500); container.AddSubview(view); container.AddSubview(advanced);
var alert = new NSAlert { MessageText = existing is null ? "Create channel" : "Edit channel", AccessoryView = container };
alert.AddButton(existing is null ? "Create" : "Save"); alert.AddButton("Cancel"); if (alert.RunModal() != 1000) return null;
if (string.IsNullOrWhiteSpace(name.StringValue) || !uint.TryParse(maximum.StringValue, out uint max) || !int.TryParse(order.StringValue, out int sort) ||
@@ -45,7 +53,9 @@ internal static class ChannelEditor
Type = type.IndexOfSelectedItem == 1 ? ChannelType.ChannelTemporary : ChannelType.ChannelPermanent,
Audio = new AudioConfig { Codec = 0, Mode = mode.IndexOfSelectedItem == 1 ? ChannelMode.ModeStereo : ChannelMode.ModeMono,
SampleRate = sampleRate, FrameMs = frameMs, Application = (OpusApplication)(int)application.IndexOfSelectedItem, BitrateBps = bitrateValue,
Fec = On(fec), Dtx = On(dtx), Dred = On(dred), ExpectedPacketLoss = packetLoss, Complexity = complexityValue }
Fec = On(fec), Dtx = On(dtx), Dred = On(dred), ExpectedPacketLoss = packetLoss, Complexity = complexityValue,
PacketLossMode = On(automaticLoss) ? lossSpeed.IndexOfSelectedItem switch
{ 0 => PacketLossMode.PacketLossAutoFast, 2 => PacketLossMode.PacketLossAutoStable, _ => PacketLossMode.PacketLossAutoBalanced } : PacketLossMode.PacketLossManual }
}, password.StringValue);
}