/*
* VideoKit
* Copyright © 2026 Yusuf Olokoba. All Rights Reserved.
*/
#nullable enable
namespace VideoKit {
using System;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using AOT;
using UnityEngine;
using Internal;
using MediaDeviceFlags = Internal.VideoKit.MediaDeviceFlags;
using Status = Internal.VideoKit.Status;
///
/// Audio input device.
///
public sealed class AudioDevice : MediaDevice {
#region --Properties--
///
/// Whether acoustic echo cancellation is supported.
///
public bool echoCancellationSupported => handle.GetMediaDeviceFlags(out var flags).Throw() == Status.Ok ? flags.HasFlag(MediaDeviceFlags.EchoCancellation) : default;
///
/// Enable or disable acoustic echo cancellation (AEC).
///
public bool echoCancellation {
get => handle.GetAudioDeviceEchoCancellation(out var echoCancellation) == Status.Ok ? echoCancellation : default;
set => handle.SetAudioDeviceEchoCancellation(value);
}
///
/// Audio sample rate.
///
public int sampleRate {
get => handle.GetAudioDeviceSampleRate(out var sampleRate).Throw() == Status.Ok ? sampleRate : default;
set => handle.SetAudioDeviceSampleRate(value).Throw();
}
///
/// Audio channel count.
///
public int channelCount {
get => handle.GetAudioDeviceChannelCount(out var channelCount).Throw() == Status.Ok ? channelCount : default;
set => handle.SetAudioDeviceChannelCount(value).Throw();
}
#endregion
#region --Streaming--
///
/// Start running.
/// NOTE: This requires an active VideoKit plan.
///
/// Delegate to receive audio buffers.
public unsafe void StartRunning(Action handler) => StartRunning((IntPtr sampleBuffer) => {
handler(new AudioBuffer(sampleBuffer));
});
#endregion
#region --Discovery--
///
/// Check the current microphone permission status.
///
/// Request permissions if the user has not yet been asked.
/// Current microphone permissions status.
public static Task CheckPermissions(bool request = true) => CheckPermissions(
VideoKit.PermissionType.Microphone,
request
);
///
/// Discover available audio input devices.
///
/// Configure the application's global audio session for audio device discovery. This is required for discovering audio devices on iOS.
public static async Task Discover(bool configureAudioSession = true) {
// Check session
await VideoKitClient.Instance!.CheckSession();
// Configure audio session
if (configureAudioSession)
VideoKit.ConfigureAudioSession();
// Discover
var tcs = new TaskCompletionSource();
var handle = GCHandle.Alloc(tcs, GCHandleType.Normal);
try {
VideoKit.DiscoverAudioDevices(OnDiscoverDevices, (IntPtr)handle).Throw();
return await tcs.Task;
} catch {
handle.Free();
throw;
}
}
#endregion
#region --Operations--
private int priority => location switch {
var _ when defaultForMediaType => -1000,
Location.External => -1,
Location.Internal => 0,
Location.Unknown => 1,
_ => 2
};
internal AudioDevice(IntPtr device, bool strong = true) : base(device, strong: strong) { }
public override string ToString() => $"AudioDevice(uniqueId=\"{uniqueId}\", name=\"{name}\")";
[MonoPInvokeCallback(typeof(VideoKit.MediaDeviceDiscoveryHandler))]
private static unsafe void OnDiscoverDevices(IntPtr context, IntPtr devices, int count) {
try {
// Check
if (!VideoKit.IsAppDomainLoaded)
return;
// Get tcs
var handle = (GCHandle)context;
var tcs = handle.Target as TaskCompletionSource;
handle.Free();
// Complete task
var microphones = Enumerable
.Range(0, count)
.Select(idx => ((IntPtr*)devices)[idx])
.Select(device => new AudioDevice(device, strong: true))
.OrderBy(device => device.priority)
.ToArray();
tcs?.SetResult(microphones);
} catch (Exception ex) {
Debug.LogException(ex);
}
}
#endregion
}
}