Plugin.Maui.Audio

repository·main·Indexed 43 days ago

https://github.com/jfversluis/plugin.maui.audio

A .NET MAUI plugin providing capabilities to play, record, and stream audio. It features an AudioManager for creating AudioPlayers, AudioRecorders, and AudioStreamers, with support for various formats across Android, iOS, MacCatalyst, and Windows. The library includes a processing pipeline using PcmAudioHandler and IPcmAudioListener for real-time audio analysis, such as silence detection and decibel monitoring via built-in listeners like SilenceListener, DecibelListener, and RmsListener.

Tokens
8.8K
Snippets
24
Records
34
Agent score
56%

What's inside Plugin.Maui.Audio

  1. Handle Audio Focus and Interruptions

    main

    The plugin automatically manages audio focus (Android) and audio interruptions (iOS/macOS) by default.

    Android Audio Focus

    • Requests focus on Play().
    • Abandons focus on Pause() or Stop().
    • Responds to changes: Handles permanent loss (stops), temporary loss (pauses/resumes), and ducking (lowers volume to 20%).
    • Disable management: Set ManageAudioFocus = false in AudioPlayerOptions for manual control.

    iOS/macOS Interruptions

    • Registers for notifications on creation.
    • Responds to interruptions: Pauses on 'interruption began' and resumes on 'interruption ended'.
    • Disable management: Set HandleAudioInterruptions = false in AudioPlayerOptions for manual control.
  2. How AudioManager works in Plugin.Maui.Audio

    main

    The AudioManager is the central entry point for the plugin. It is used to create AudioPlayers, AudioRecorders, and AudioStreamers. You can interact with it using either Dependency Injection (recommended) or via the AudioManager.Current singleton property.

    // Using Dependency Injection
    var audioPlayer = audioManager.CreatePlayer(stream);
    
    // Using Straight Usage (Singleton)
    var audioPlayer = AudioManager.Current.CreatePlayer(stream);
  3. Manage iOS/macOS Audio Session Lifetime

    main

    On iOS and macOS, the AVAudioSession is a shared resource. You can control how the session behaves when a player is disposed using the SessionLifetime property in AudioPlayerOptions:

    ValueBehavior
    KeepSessionAlive (default)Session remains active after disposal; other apps' audio stays interrupted.
    EndSessionSession is deactivated on disposal.
    EndSessionAndNotifyOthersSession is deactivated on disposal AND other apps are notified to resume audio.

    Best Practice for short sounds (notifications, TTS): Use EndSessionAndNotifyOthers and dispose the player when playback finishes to avoid interrupting other apps.

    var player = audioManager.CreatePlayer(
        await FileSystem.OpenAppPackageFileAsync("notification.mp3"),
        new AudioPlayerOptions
        {
    #if IOS || MACCATALYST
            SessionLifetime = SessionLifetime.EndSessionAndNotifyOthers
    #endif
        });
    
    void OnPlaybackEnded(object? sender, EventArgs e)
    {
        player.PlaybackEnded -= OnPlaybackEnded;
        player.Dispose();
    }
    
    player.PlaybackEnded += OnPlaybackEnded;
    player.Play();
  4. How audio analysis and manipulation works collectively

    main

    The library uses a hierarchical pipeline to process audio data for analysis and manipulation. The flow is as follows:

    AudioStreamer (provides raw PCM) $\rightarrow$ PcmAudioHandler (converts raw PCM to ordered samples) $\rightarrow$ AudioListeners (listens to and analyzes samples) $\rightarrow$ PcmAudioHelpers (provides utility methods for analysis).

    Important Implementation Note

    Audio listeners do not receive data from AudioRecorder. To perform analysis, you must:

    1. Keep a reference to an AudioStreamer.
    2. Subscribe to its OnAudioCaptured event.
    3. Call StartAsync() on the streamer to supply PCM data.

    Warning: Avoid running AudioRecorder and AudioStreamer simultaneously, especially on Android, as they compete for microphone capture sessions. If you need both recording and analysis, manually save the PCM data provided by the AudioStreamer.

    // Create and retain AudioStreamer
    audioStreamer = audioManager.CreateStreamer();
    
    // Create PcmAudioHandler
    pcmAudioHandler = new PcmAudioHandler(44100, ChannelType.Mono, BitDepth.Pcm16bit);
    
    // Create listener
    silenceListener = new SilenceListener
    {
       MinimalSilenceTimescanInMilliseconds = 200,
       SilenceThresholdInDb = -40
    };
    silenceListener.IsSilentChanged += (sender, args) =>
    {
       dispatcher.Dispatch(() =>
       {
          IsSilent = args.IsSilent;
       });
    };
    
    // Subscribe the SilenceListener to PcmAudioHandler
    pcmAudioHandler.Subscribe(silenceListener);
    
    // Connect PcmAudioHandler to AudioStreamer for incoming audio
    audioStreamer.OnAudioCaptured += (sender, args) =>
    {
       pcmAudioHandler.HandlePcmAudio(args.Audio);
    };
    
    // Start supplying PCM data to the listeners
    await audioStreamer.StartAsync();
  5. PCM Audio recording details

    main

    When recording or streaming PCM audio (WAV format), consider the following platform-specific recommendations:

    • Sample Rates:
      • Android: 44100Hz is recommended.
      • iOS/macOS: 48000Hz is recommended.
      • All platforms allow customization (e.g., 8000Hz, 16000Hz).
    • Channels:
      • Mono (1 channel) and Stereo (2 channels) are supported on all platforms. Mono is recommended for maximum compatibility.
    • Bit Depth:
      • 8-bit and 16-bit PCM are supported (16-bit is recommended).
      • Windows streaming only supports 16-bit PCM.
  6. Configure AudioManager with Dependency Injection

    main

    To use Dependency Injection, register the AudioManager in your MauiProgram.cs using the AddAudio() extension method on the MauiAppBuilder. You can then inject IAudioManager into your classes (e.g., ViewModels).

    // In MauiProgram.cs
    builder.AddAudio();
    
    // In your class
    public class AudioPlayerViewModel
    {
        readonly IAudioManager audioManager;
    
        public AudioPlayerViewModel(IAudioManager audioManager)
        {
            this.audioManager = audioManager;
        }
    
        public async Task PlayAudioAsync()
        {
            var audioPlayer = audioManager.CreatePlayer(await FileSystem.OpenAppPackageFileAsync("ukelele.mp3"));
            audioPlayer.Play();
        }
    }
  7. Record from a specific device on Android

    main

    By default, Android uses AudioSource.Mic. To use a specific device (like a Bluetooth headset or USB mic), set the PreferredDevice property in AudioRecorderOptions.

    Requirements:

    • Android API 23+: Required for WAV recording.
    • Android API 28+: Required for AAC recording.
    • Permissions: On Android 12+ (API 31+), you must include android.permission.BLUETOOTH_CONNECT in your AndroidManifest.xml to discover Bluetooth devices.
    #if ANDROID
    using Android.Content;
    using Android.Media;
    
    var audioManager = (AudioManager)Android.App.Application.Context.GetSystemService(Context.AudioService)!;
    var btDevice = audioManager.GetDevices(GetDevicesTargets.Inputs)
        .FirstOrDefault(d => d.Type == AudioDeviceType.BluetoothSco);
    
    var recorder = audioMgr.CreateRecorder(
        new AudioRecorderOptions
        {
            PreferredDevice = btDevice
        });
    #endif
  8. Configure platform permissions for AudioStreamer

    main

    Each platform requires specific configuration to allow microphone access and audio streaming.

    Android

    Add the following to your AndroidManifest.xml inside the <manifest> tag:

    <uses-permission android:name="android.permission.RECORD_AUDIO"/>
    <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

    iOS

    Add the following to your Info.plist inside the <dict> tag:

    <key>NSMicrophoneUsageDescription</key>
    <string>The [app name] wants to use your microphone to record audio.</string>

    Note: To stream in the background, add the UIBackgroundModes key with the audio string.

    MacCatalyst

    Add the following to your Info.plist inside the <dict> tag:

    <key>NSMicrophoneUsageDescription</key>
    <string>The [app name] wants to use your microphone to record audio.</string>

    If distributing the app, you must also add an entitlement in Entitlements.plist:

    <key>com.apple.security.device.audio-input</key>
    <true/>

    Windows

    Add the following to your Package.appxmanifest inside the <Capabilities> tag:

    <DeviceCapability Name="microphone"/>
  9. Record from a Bluetooth microphone on iOS/macCatalyst

    main

    To enable Bluetooth HFP (Hands-Free Profile) recording on iOS or macCatalyst, you must set CategoryOptions to AllowBluetooth in AudioRecorderOptions.

    Note: Bluetooth HFP recording is limited to voice-quality audio (8–16 kHz, mono).

    To select a specific Bluetooth device, you must first configure and activate a temporary AVAudioSession to discover available inputs, then pass the PreferredInput to the recorder.

    #if IOS || MACCATALYST
    using AVFoundation;
    
    // 1. Configure session to discover inputs
    var audioSession = AVAudioSession.SharedInstance();
    audioSession.SetCategory(AVAudioSessionCategory.Record, 
        AVAudioSessionCategoryOptions.AllowBluetooth, out _);
    audioSession.SetActive(true, out _);
    
    // 2. Find the Bluetooth HFP input
    var btInput = audioSession.AvailableInputs? 
        .FirstOrDefault(i => i.PortType == AVAudioSession.PortBluetoothHfp);
    
    // 3. Create recorder with preferred input
    var recorder = audioManager.CreateRecorder(
        new AudioRecorderOptions
        {
            CategoryOptions = AVAudioSessionCategoryOptions.AllowBluetooth,
            PreferredInput = btInput
        });
    #endif
  10. Stream audio using AudioStreamer

    main

    The AudioStreamer class allows you to stream raw audio data from a microphone via an event handler. Unlike AudioRecorder, which saves to a file, AudioStreamer provides raw linear PCM audio data (similar to a WAV file without a header) in real-time.

    To use it, create an instance via IAudioManager.CreateStreamer() and subscribe to the OnAudioCaptured event to receive AudioStreamEventArgs. The args.Audio property contains the byte array of the captured data.

    public class AudioStreamerViewModel
    {
        readonly IAudioManager audioManager;
        readonly IAudioStreamer audioStreamer;
    
        public AudioStreamerViewModel(IAudioManager audioManager)
        {
            this.audioManager = audioManager;
            this.audioStreamer = audioManager.CreateStreamer();
            this.audioStreamer.OnAudioCaptured += OnAudioStreamerCapturedData;
        }
    
        public async Task StartStreamingAsync()
        {
            await audioStreamer.StartAsync();
        }
    
        public async Task StopStreamingAsync()
        {
            await audioStreamer.StopAsync();
        }
    
        private void OnAudioStreamerCapturedData(object sender, AudioStreamEventArgs args)
        {
            // args.Audio is a byte array with linear PCM audio data
        }
    }
  11. Record from a specific device on Windows

    main

    On Windows, use the AudioDeviceId property in AudioRecorderOptions to select a specific capture device.

    Warning: Unlike Android, if an invalid or disconnected device ID is provided, MediaCapture will throw an exception rather than falling back to the default device.

    #if WINDOWS
    using Windows.Devices.Enumeration;
    using Windows.Media.Devices;
    
    var devices = await DeviceInformation.FindAllAsync(MediaDevice.GetAudioCaptureSelector());
    var usbMic = devices.FirstOrDefault(d => d.Name.Contains("USB"));
    
    var recorder = audioManager.CreateRecorder(
        new AudioRecorderOptions
        {
            AudioDeviceId = usbMic?.Id
        });
    #endif
  12. Configure platform permissions for AudioRecorder

    main

    Recording audio requires specific permissions and configuration in platform-specific manifest files.

    Android

    Add these to AndroidManifest.xml:

    <uses-permission android:name="android.permission.RECORD_AUDIO"/>
    <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

    Note: For Bluetooth device selection on Android 12+, also add android.permission.BLUETOOTH_CONNECT.

    iOS / MacCatalyst

    Add these to Info.plist:

    <key>NSMicrophoneUsageDescription</key>
    <string>The [app name] wants to use your microphone to record audio.</string>

    To record in the background on iOS, add:

    <key>UIBackgroundModes</key>
    <array>
      <string>audio</string>
    </array>

    For MacCatalyst distribution, add the following to Entitlements.plist:

    <key>com.apple.security.device.audio-input</key>
    <true/>

    Windows

    Add this to Package.appxmanifest inside the Capabilities tag:

    <DeviceCapability Name="microphone"/>