/*
* VideoKit
* Copyright © 2026 Yusuf Olokoba. All Rights Reserved.
*/
namespace VideoKit {
using System;
using System.Linq;
using System.Threading.Tasks;
using UnityEngine;
using Internal;
///
/// VideoKit audio manager for streaming audio from audio devices.
///
[Tooltip(@"VideoKit audio manager for streaming audio from audio devices.")]
[HelpURL(@"https://videokit.ai/reference/videokitaudiomanager")]
[DisallowMultipleComponent]
public sealed class VideoKitAudioManager : MonoBehaviour {
#region --Enumerations--
///
/// Audio sample rate.
///
public enum SampleRate : int {
///
/// Match Unity's audio DSP sample rate.
///
MatchUnity = 0,
///
/// 8KHz.
///
_8000 = 8000,
///
/// 16KHz.
///
_16000 = 16000,
///
/// 22.05KHz.
///
_22050 = 22050,
///
/// 24KHz.
///
_24000 = 24000,
///
/// 44.1KHz.
///
_44100 = 44100,
///
/// 48KHz.
///
_48000 = 48000,
}
///
/// Audio channel count.
///
public enum ChannelCount : int {
///
/// Match Unity's audio DSP channel count.
///
MatchUnity = 0,
///
/// Mono audio.
///
[InspectorName(@"Mono")]
_1 = 1,
///
/// Stereo audio.
///
[InspectorName(@"Stereo")]
_2 = 2,
}
#endregion
#region --Inspector--
[Header(@"Configuration")]
///
/// Configure the application audio session on awake.
/// This only applies on iOS.
///
[Tooltip(@"Configure the application audio session on awake. This only applies on iOS.")]
public bool configureOnAwake = true;
[Header(@"Format")]
///
/// Audio sample rate.
///
[Tooltip(@"Audio sample rate.")]
public SampleRate sampleRate = SampleRate._44100;
///
/// Audio channel count.
///
[Tooltip(@"Audio channel count.")]
public ChannelCount channelCount = ChannelCount._1;
///
/// Request echo cancellation if the device supports it.
///
[Tooltip(@"Request echo cancellation if the device supports it.")]
public bool echoCancellation = false;
#endregion
#region --Client API--
///
/// Get or set the audio device used for streaming.
///
public AudioDevice device {
get => _device;
set {
// Switch mic without disposing output
// We deliberately skip configuring the mic like we do in `StartRunning`
if (running) {
_device.StopRunning();
_device = value;
_device?.StartRunning(OnSampleBuffer);
}
// Handle trivial case
else
_device = value;
}
}
///
/// Whether the audio device is running.
///
public bool running => _device?.running ?? false;
///
/// Event raised when a new audio buffer is available.
///
public event Action OnAudioBuffer;
///
/// Start streaming audio.
///
public async void StartRunning() => await StartRunningAsync();
///
/// Start streaming audio.
///
public async Task StartRunningAsync() {
// Check
if (!isActiveAndEnabled)
throw new InvalidOperationException(@"VideoKit: Audio manager failed to start running because component is disabled");
// Check
if (running)
return;
// Request microphone permissions
var permissions = await AudioDevice.CheckPermissions(request: true);
if (permissions != MediaDevice.PermissionStatus.Authorized)
throw new InvalidOperationException(@"VideoKit: User did not grant microphone permissions");
// Check device
var devices = await AudioDevice.Discover(configureAudioSession: false);
_device ??= devices.FirstOrDefault(); // configure once in `Awake` instead.
if (_device == null)
throw new InvalidOperationException(@"VideoKit: Audio manager failed to start running because no audio device is available");
// Configure microphone
_device.sampleRate = sampleRate == SampleRate.MatchUnity ? AudioSettings.outputSampleRate : (int)sampleRate;
_device.channelCount = channelCount == ChannelCount.MatchUnity ? (int)AudioSettings.speakerMode : (int)channelCount;
_device.echoCancellation = echoCancellation; // devices can say they don't support AEC even when they do
// Start running
_device.StartRunning(OnSampleBuffer);
// Listen for events
var events = VideoKitEvents.Instance;
events.onPause += OnPause;
events.onResume += OnResume;
}
///
/// Stop streaming audio.
///
public void StopRunning() {
// Stop listening for events
var events = VideoKitEvents.OptionalInstance;
if (events != null) {
events.onPause -= OnPause;
events.onResume -= OnResume;
}
// Stop
if (running)
_device.StopRunning();
}
#endregion
#region --Operations--
private AudioDevice _device;
private void Awake() {
if (configureOnAwake)
VideoKit.ConfigureAudioSession();
}
private void OnSampleBuffer(AudioBuffer audioBuffer) => OnAudioBuffer?.Invoke(audioBuffer);
private void OnPause() => _device?.StopRunning();
private void OnResume() {
if (_device != null)
_device.StartRunning(OnSampleBuffer);
}
private void OnDestroy() => StopRunning();
#endregion
}
}