/* * 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 } }