Add managed UIKit iOS client
.NET port / test (macos-latest) (push) Canceled after 0s
.NET port / test (ubuntu-24.04) (push) Canceled after 0s
.NET port / test (windows-latest) (push) Canceled after 0s
.NET port / apple-client (push) Canceled after 0s
.NET port / cpp-conformance (push) Canceled after 0s
.NET port / test (macos-latest) (push) Canceled after 0s
.NET port / test (ubuntu-24.04) (push) Canceled after 0s
.NET port / test (windows-latest) (push) Canceled after 0s
.NET port / apple-client (push) Canceled after 0s
.NET port / cpp-conformance (push) Canceled after 0s
This commit is contained in:
@@ -0,0 +1,63 @@
|
||||
using AVFoundation;
|
||||
using Foundation;
|
||||
|
||||
namespace VoiceCat.iOS;
|
||||
|
||||
internal enum IosAudioPreset { VoiceChat, StereoMicrophone, MonoMicrophone, Advanced }
|
||||
|
||||
internal sealed class IosAudioRouter
|
||||
{
|
||||
internal static IosAudioRouter Shared { get; } = new();
|
||||
private readonly NSUserDefaults defaults = NSUserDefaults.StandardUserDefaults;
|
||||
internal IosAudioPreset Preset { get; private set; } = IosAudioPreset.VoiceChat;
|
||||
internal bool ForceSpeaker { get; set; }
|
||||
internal bool VoiceProcessing { get; set; } = true;
|
||||
internal bool AutomaticGainControl { get; set; } = true;
|
||||
internal int CaptureChannels => Preset == IosAudioPreset.StereoMicrophone ? 2 : 1;
|
||||
internal bool UsesVoiceProcessing => VoiceProcessing && CaptureChannels == 1 && Preset != IosAudioPreset.MonoMicrophone;
|
||||
|
||||
private IosAudioRouter()
|
||||
{
|
||||
NSNotificationCenter.DefaultCenter.AddObserver(AVAudioSession.RouteChangeNotification, _ => Recover());
|
||||
NSNotificationCenter.DefaultCenter.AddObserver(AVAudioSession.InterruptionNotification, note => HandleInterruption(note));
|
||||
NSNotificationCenter.DefaultCenter.AddObserver(AVAudioSession.MediaServicesWereResetNotification, _ => Recover());
|
||||
}
|
||||
|
||||
internal void Load()
|
||||
{
|
||||
string? preset = defaults.StringForKey("cat.voice.audio.preset");
|
||||
if (Enum.TryParse(preset, true, out IosAudioPreset value)) Preset = value;
|
||||
ForceSpeaker = defaults.BoolForKey("cat.voice.audio.forceSpeaker");
|
||||
VoiceProcessing = defaults.ValueForKey(new NSString("cat.voice.audio.voiceProcessing")) is null || defaults.BoolForKey("cat.voice.audio.voiceProcessing");
|
||||
AutomaticGainControl = defaults.ValueForKey(new NSString("cat.voice.audio.agc")) is null || defaults.BoolForKey("cat.voice.audio.agc");
|
||||
}
|
||||
|
||||
internal void SelectPreset(IosAudioPreset preset)
|
||||
{
|
||||
Preset = preset; defaults.SetString(preset.ToString(), "cat.voice.audio.preset"); defaults.Synchronize();
|
||||
if (IosAudioEngine.Shared.IsConnected) IosAudioEngine.Shared.Reconfigure();
|
||||
}
|
||||
|
||||
internal void Apply()
|
||||
{
|
||||
AVAudioSession session = AVAudioSession.SharedInstance();
|
||||
AVAudioSessionCategoryOptions options = AVAudioSessionCategoryOptions.AllowBluetooth;
|
||||
if (Preset is IosAudioPreset.StereoMicrophone or IosAudioPreset.MonoMicrophone)
|
||||
options = AVAudioSessionCategoryOptions.AllowBluetoothA2DP | AVAudioSessionCategoryOptions.DefaultToSpeaker;
|
||||
if (!session.SetCategory(AVAudioSessionCategory.PlayAndRecord, AVAudioSessionMode.Default, options, out NSError? categoryError))
|
||||
throw new InvalidOperationException(categoryError.LocalizedDescription);
|
||||
session.SetPreferredSampleRate(48_000, out _);
|
||||
session.SetPreferredIOBufferDuration(0.02, out _);
|
||||
if (!session.SetActive(true, AVAudioSessionSetActiveOptions.NotifyOthersOnDeactivation, out NSError? activeError))
|
||||
throw new InvalidOperationException(activeError.LocalizedDescription);
|
||||
session.OverrideOutputAudioPort(ForceSpeaker ? AVAudioSessionPortOverride.Speaker : AVAudioSessionPortOverride.None, out _);
|
||||
}
|
||||
|
||||
internal void Deactivate() => AVAudioSession.SharedInstance().SetActive(false, AVAudioSessionSetActiveOptions.NotifyOthersOnDeactivation, out _);
|
||||
private void Recover() { if (IosAudioEngine.Shared.IsConnected) UIApplication.SharedApplication.BeginInvokeOnMainThread(IosAudioEngine.Shared.Reconfigure); }
|
||||
private void HandleInterruption(NSNotification note)
|
||||
{
|
||||
NSNumber? type = note.UserInfo?[new NSString("AVAudioSessionInterruptionTypeKey")] as NSNumber;
|
||||
if ((AVAudioSessionInterruptionType)(type?.UInt32Value ?? 0) == AVAudioSessionInterruptionType.Ended) Recover();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user