Three bugs fixed across the full stack (proto/server/core/ABI/Win/macOS/iOS): 1. Join/Leave Voice now truly subscribes/unsubscribes from the voice plane. Previously the button only toggled the local mic — receiving was always on (gated by channel membership alone). Added a protocol-level voice subscription concept: new SubscribeVoiceRequest/UnsubscribeVoiceRequest/VoiceSubscriptionResult proto messages, User.voice_subscribed field, vc_join_voice/vc_leave_voice C ABI functions, VC_EVENT_VOICE_STATE event, server-side voice_subscribed flag checked by the SFU relay recipient filter, and core-client gating of remote-stream decoder setup. All three clients rewired to subscribe+mic on Join / unsubscribe on Leave. Text chat works regardless of voice subscription. 2. Channel edit dialog now shows the channel's actual current settings. The read struct vc_channel was missing sort_order and audio fields — only the write struct vc_channel_info had them. Extended vc_channel with both (additive, no ABI break), updated the session model and list_channels marshaling to populate them, and updated all three clients' edit callers to use actual channel info instead of hardcoded defaults. 3. Channel parameter updates now automatically restart everyone's streams. Previously editing a channel's audio config persisted and broadcast a ChannelEvent::UPDATED, but no layer restarted streams — encoders/decoders are frozen at announce time. handle_channel_event now detects audio-config changes on the user's current channel and stop->starts each active local stream. The server reads the updated config on re-announce; peers wire up fresh decoders at the new ssrc. All 29 CTest tests pass; Windows DLL + C# client build clean. Apple clients not yet compile-verified (Windows environment).
130 lines
4.9 KiB
C#
130 lines
4.9 KiB
C#
using System.Runtime.InteropServices;
|
|
|
|
// Shared "walk a native array of owned-struct entries, convert to managed records, free the
|
|
// native list" pattern — identical shape for vc_device_list/vc_channel_list/vc_user_list/
|
|
// vc_stream_summary_list (all core-allocated, caller-freed; see voicecat.h's doc comments on
|
|
// each). The matching vc_free_*_list call happens INSIDE each ToManaged here, immediately
|
|
// after the conversion, so callers never need to remember to free anything themselves.
|
|
namespace VoiceCat.Interop;
|
|
|
|
internal static class Marshaling
|
|
{
|
|
public static List<DeviceInfo> ToManaged(ref VcDeviceListNative native)
|
|
{
|
|
var result = new List<DeviceInfo>((int)native.Count);
|
|
int size = Marshal.SizeOf<VcDeviceNative>();
|
|
for (nuint i = 0; i < native.Count; i++)
|
|
{
|
|
var raw = Marshal.PtrToStructure<VcDeviceNative>(native.Items + (int)i * size);
|
|
result.Add(new DeviceInfo(
|
|
Marshal.PtrToStringUTF8(raw.Id) ?? string.Empty,
|
|
Marshal.PtrToStringUTF8(raw.Name) ?? string.Empty,
|
|
raw.IsDefault != 0));
|
|
}
|
|
NativeMethods.vc_free_device_list(ref native);
|
|
return result;
|
|
}
|
|
|
|
public static List<ChannelInfo> ToManaged(ref VcChannelListNative native)
|
|
{
|
|
var result = new List<ChannelInfo>((int)native.Count);
|
|
int size = Marshal.SizeOf<VcChannelNative>();
|
|
for (nuint i = 0; i < native.Count; i++)
|
|
{
|
|
var raw = Marshal.PtrToStructure<VcChannelNative>(native.Items + (int)i * size);
|
|
result.Add(new ChannelInfo(
|
|
raw.Id,
|
|
raw.ParentId,
|
|
Marshal.PtrToStringUTF8(raw.Name) ?? string.Empty,
|
|
Marshal.PtrToStringUTF8(raw.Topic) ?? string.Empty,
|
|
raw.PasswordProtected != 0,
|
|
raw.MaxUsers,
|
|
raw.SortOrder,
|
|
ToManaged(in raw.Audio)));
|
|
}
|
|
NativeMethods.vc_free_channel_list(ref native);
|
|
return result;
|
|
}
|
|
|
|
public static List<UserInfo> ToManaged(ref VcUserListNative native)
|
|
{
|
|
var result = new List<UserInfo>((int)native.Count);
|
|
int size = Marshal.SizeOf<VcUserNative>();
|
|
for (nuint i = 0; i < native.Count; i++)
|
|
{
|
|
var raw = Marshal.PtrToStructure<VcUserNative>(native.Items + (int)i * size);
|
|
result.Add(new UserInfo(
|
|
raw.Id,
|
|
Marshal.PtrToStringUTF8(raw.Nickname) ?? string.Empty,
|
|
raw.IsGuest != 0,
|
|
raw.ChannelId,
|
|
raw.SelfMicMuted != 0,
|
|
raw.SelfDeafened != 0,
|
|
raw.ServerMuted != 0,
|
|
raw.ServerDeafened != 0,
|
|
raw.VoiceSubscribed != 0));
|
|
}
|
|
NativeMethods.vc_free_user_list(ref native);
|
|
return result;
|
|
}
|
|
|
|
public static List<StreamSummary> ToManaged(ref VcStreamSummaryListNative native)
|
|
{
|
|
var result = new List<StreamSummary>((int)native.Count);
|
|
int size = Marshal.SizeOf<VcStreamSummaryNative>();
|
|
for (nuint i = 0; i < native.Count; i++)
|
|
{
|
|
var raw = Marshal.PtrToStructure<VcStreamSummaryNative>(native.Items + (int)i * size);
|
|
result.Add(new StreamSummary(
|
|
raw.StreamId,
|
|
raw.Kind,
|
|
Marshal.PtrToStringUTF8(raw.Label) ?? string.Empty));
|
|
}
|
|
NativeMethods.vc_free_stream_summary_list(ref native);
|
|
return result;
|
|
}
|
|
|
|
public static RemoteStreamState ToManaged(in VcRemoteStreamStateNative native) => new(
|
|
native.Gain,
|
|
native.Muted != 0,
|
|
native.NoiseReduction != 0);
|
|
|
|
public static AudioConfigInfo ToManaged(in VcAudioConfigNative native) => new(
|
|
native.Codec,
|
|
native.Mode != 0,
|
|
native.SampleRate,
|
|
native.BitrateBps,
|
|
native.FrameMs,
|
|
native.Application,
|
|
native.Fec != 0,
|
|
native.ExpectedPacketLoss,
|
|
native.Dtx != 0,
|
|
native.Complexity,
|
|
native.Dred != 0);
|
|
|
|
public static PermissionsInfo ToManaged(in VcPermissionsNative native) => new(
|
|
native.CanCreateTempChannel != 0,
|
|
native.CanKick != 0,
|
|
native.CanBan != 0,
|
|
native.CanMoveUsers != 0,
|
|
native.CanAdminAccounts != 0,
|
|
native.IsAdmin != 0);
|
|
|
|
public static List<AccountInfo> ToManaged(ref VcAccountListNative native)
|
|
{
|
|
var result = new List<AccountInfo>((int)native.Count);
|
|
int size = Marshal.SizeOf<VcAccountNative>();
|
|
for (nuint i = 0; i < native.Count; i++)
|
|
{
|
|
var raw = Marshal.PtrToStructure<VcAccountNative>(native.Items + (int)i * size);
|
|
result.Add(new AccountInfo(
|
|
Marshal.PtrToStringUTF8(raw.Username) ?? string.Empty,
|
|
raw.IsAdmin != 0,
|
|
raw.CreatedAtUnixMs,
|
|
raw.LastLoginUnixMs));
|
|
}
|
|
NativeMethods.vc_free_account_list(ref native);
|
|
return result;
|
|
}
|
|
}
|