/*
* VideoKit
* Copyright © 2025 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 => device.GetMediaDeviceFlags(out var flags).Throw() == Status.Ok ? flags.HasFlag(MediaDeviceFlags.EchoCancellation) : default;
///
/// Enable or disable acoustic echo cancellation (AEC).
///
public bool echoCancellation {
get => device.GetAudioDeviceEchoCancellation(out var echoCancellation) == Status.Ok ? echoCancellation : default;
set => device.SetAudioDeviceEchoCancellation(value);
}
///
/// Audio sample rate.
///
public int sampleRate {
get => device.GetAudioDeviceSampleRate(out var sampleRate).Throw() == Status.Ok ? sampleRate : default;
set => device.SetAudioDeviceSampleRate(value).Throw();
}
///
/// Audio channel count.
///
public int channelCount {
get => device.GetAudioDeviceChannelCount(out var channelCount).Throw() == Status.Ok ? channelCount : default;
set => device.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 { // #24
get {
var order = 0;
if (!defaultForMediaType)
order += 1;
if (location == Location.External)
order += 10;
if (location == Location.Unknown)
order += 100;
return order;
}
}
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
}
}