diff --git a/clients/apple/dotnet/VoiceCat.iOS/AppModel.cs b/clients/apple/dotnet/VoiceCat.iOS/AppModel.cs
index 77a7d98..fe76eb5 100644
--- a/clients/apple/dotnet/VoiceCat.iOS/AppModel.cs
+++ b/clients/apple/dotnet/VoiceCat.iOS/AppModel.cs
@@ -98,6 +98,11 @@ internal sealed class AppModel
Status = restoring ? "Reconnecting…" : "Connecting…"; Notify();
lifetime?.Cancel(); lifetime?.Dispose(); lifetime = new();
VoiceCatClient next = new("VoiceCat-iOS", "0.0.1", storage.TofuPath);
+ next.ConnectionStateChanged += state =>
+ {
+ if (state == ClientConnectionState.Disconnected && next.ConnectionFailure is { } failure)
+ Console.Error.WriteLine($"VoiceCat control connection failed: {failure}");
+ };
client = next;
try
{
@@ -118,8 +123,10 @@ internal sealed class AppModel
}
catch (Exception exception)
{
- IsConnecting = false; Status = exception.Message; Notify();
+ System.Diagnostics.Debug.WriteLine($"Connection failed: {exception}");
+ IsConnecting = false; Status = exception.Message;
await next.DisposeAsync(); if (ReferenceEquals(client, next)) client = null;
+ Notify();
if (restoring && !explicitDisconnect) ScheduleReconnect();
else throw;
}
diff --git a/clients/apple/dotnet/VoiceCat.iOS/VoiceCat.iOS.csproj b/clients/apple/dotnet/VoiceCat.iOS/VoiceCat.iOS.csproj
index 510f70e..ec80c2c 100644
--- a/clients/apple/dotnet/VoiceCat.iOS/VoiceCat.iOS.csproj
+++ b/clients/apple/dotnet/VoiceCat.iOS/VoiceCat.iOS.csproj
@@ -32,6 +32,10 @@
Static
true
+
+
+ Function
+
Static
true
diff --git a/clients/apple/dotnet/build-ios-device.sh b/clients/apple/dotnet/build-ios-device.sh
index 3aa90d4..cdfee7c 100755
--- a/clients/apple/dotnet/build-ios-device.sh
+++ b/clients/apple/dotnet/build-ios-device.sh
@@ -30,6 +30,18 @@ if [ -n "${VOICECAT_CODESIGN_PROVISION:-}" ]; then set -- "$@" -p:CodesignProvis
app="$root/clients/apple/dotnet/VoiceCat.iOS/bin/$configuration/net10.0-ios27.0/ios-arm64/VoiceCat.iOS.app"
[ -d "$app" ] || { echo "device app was not produced at $app" >&2; exit 1; }
+/usr/bin/strings "$app/VoiceCat.Codec.dll" | /usr/bin/grep -q GetMainProgramHandle || {
+ echo "device codec does not contain the iOS static-library resolver" >&2
+ exit 1
+}
+/usr/bin/nm -gU "$app/VoiceCat.iOS" | /usr/bin/grep -q _vcm_decoder_create || {
+ echo "device executable does not export the statically linked media codec" >&2
+ exit 1
+}
+/usr/bin/nm -gU "$app/VoiceCat.iOS" | /usr/bin/grep -q _vcm_rnnoise_create || {
+ echo "device executable does not export the statically linked RNNoise processor" >&2
+ exit 1
+}
mkdir -p "$output"
/usr/bin/ditto "$app" "$output/VoiceCat.iOS.app"
/usr/bin/codesign --verify --deep --strict "$output/VoiceCat.iOS.app"
diff --git a/dotnet/Directory.Build.props b/dotnet/Directory.Build.props
index 8dbab2e..d07004b 100644
--- a/dotnet/Directory.Build.props
+++ b/dotnet/Directory.Build.props
@@ -8,7 +8,6 @@
true
- $(DefineConstants);VOICECAT_IOS_STATIC
ios-arm64;iossimulator-arm64
packages.ios.lock.json
diff --git a/dotnet/src/VoiceCat.Codec/NativeMethods.cs b/dotnet/src/VoiceCat.Codec/NativeMethods.cs
index b8d54a7..a782994 100644
--- a/dotnet/src/VoiceCat.Codec/NativeMethods.cs
+++ b/dotnet/src/VoiceCat.Codec/NativeMethods.cs
@@ -1,14 +1,19 @@
using System.Runtime.InteropServices;
+using System.Reflection;
namespace VoiceCat.Codec;
internal static unsafe partial class NativeMethods
{
-#if VOICECAT_IOS_STATIC
- private const string Library = "__Internal";
-#else
+ static NativeMethods()
+ {
+ if (OperatingSystem.IsIOS()) NativeLibrary.SetDllImportResolver(typeof(NativeMethods).Assembly, ResolveIosStaticLibrary);
+ }
+
+ private static nint ResolveIosStaticLibrary(string libraryName, Assembly assembly, DllImportSearchPath? searchPath) =>
+ libraryName == "voicecat_media" ? NativeLibrary.GetMainProgramHandle() : 0;
+
private const string Library = "voicecat_media";
-#endif
[LibraryImport(Library, EntryPoint = "vcm_opus_version")]
internal static partial nint Version();
[LibraryImport(Library, EntryPoint = "vcm_opus_error")]
diff --git a/dotnet/src/VoiceCat.Core/VoiceCatClient.cs b/dotnet/src/VoiceCat.Core/VoiceCatClient.cs
index b807a4a..e13317a 100644
--- a/dotnet/src/VoiceCat.Core/VoiceCatClient.cs
+++ b/dotnet/src/VoiceCat.Core/VoiceCatClient.cs
@@ -43,6 +43,7 @@ public sealed partial class VoiceCatClient : IAsyncDisposable
public event Action? ConnectionStateChanged;
public ClientConnectionState State { get { lock (stateGate) return state; } }
+ public Exception? ConnectionFailure { get; private set; }
public Task Completion => reader;
public AuthResult? Authentication { get { lock (stateGate) return authentication?.Clone(); } }
public ServerHello? ServerHello { get { lock (stateGate) return hello?.Clone(); } }
@@ -72,6 +73,7 @@ public sealed partial class VoiceCatClient : IAsyncDisposable
ObjectDisposedException.ThrowIf(disposed.IsCancellationRequested, this);
if (control is not null) throw new InvalidOperationException("Disconnect before reconnecting.");
started = true;
+ ConnectionFailure = null;
connectionLifetime = CancellationTokenSource.CreateLinkedTokenSource(disposed.Token);
using var connecting = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, connectionLifetime.Token);
CancellationToken token = connecting.Token;
@@ -201,7 +203,7 @@ public sealed partial class VoiceCatClient : IAsyncDisposable
if (message.Disconnect is not null) { connection.CompleteWrites(); break; }
}
}
- catch (Exception exception) when (exception is IOException or OperationCanceledException or SocketException or ObjectDisposedException) { failure = exception; }
+ catch (Exception exception) { failure = exception; ConnectionFailure = exception; }
finally
{
connectionLifetime?.Cancel();
diff --git a/dotnet/src/VoiceCat.Dsp/RnnoiseProcessor.cs b/dotnet/src/VoiceCat.Dsp/RnnoiseProcessor.cs
index 2d60007..2fbd67d 100644
--- a/dotnet/src/VoiceCat.Dsp/RnnoiseProcessor.cs
+++ b/dotnet/src/VoiceCat.Dsp/RnnoiseProcessor.cs
@@ -1,21 +1,26 @@
using System.Runtime.InteropServices;
+using System.Reflection;
using Microsoft.Win32.SafeHandles;
namespace VoiceCat.Dsp;
public sealed unsafe partial class RnnoiseProcessor : IDisposable
{
-#if VOICECAT_IOS_STATIC
- private const string NativeLibrary = "__Internal";
-#else
private const string NativeLibrary = "voicecat_media";
-#endif
public const int SampleRate = 48000;
public const int FrameSamples = 480;
private readonly RnnoiseHandle handle;
private readonly float[] input = new float[FrameSamples];
private readonly float[] output = new float[FrameSamples];
+ static RnnoiseProcessor()
+ {
+ if (OperatingSystem.IsIOS()) System.Runtime.InteropServices.NativeLibrary.SetDllImportResolver(typeof(RnnoiseProcessor).Assembly, ResolveIosStaticLibrary);
+ }
+
+ private static nint ResolveIosStaticLibrary(string libraryName, Assembly assembly, DllImportSearchPath? searchPath) =>
+ libraryName == NativeLibrary ? System.Runtime.InteropServices.NativeLibrary.GetMainProgramHandle() : 0;
+
public RnnoiseProcessor()
{
handle = new(Create());