Search code examples
c#.netmaui

Using IDisposable to dispose an AVAudioPlayer


I am new to IDisposable and trying to figure out what I am doing wrong in my MAUI app. I have the following code where I initialize an AVAudioPlayer, and methods to Play and Dispose (irrelevant methods are disregarded below).

IVoicePlayer inherits IDisposable

public class VoicePlayer : IVoicePlayer
{
    AVAudioPlayer player;
    private bool isDisposed;

    public VoicePlayer(byte[] audioBytes)
    {
        var data = NSData.FromArray(audioBytes);
        player = AVAudioPlayer.FromData(data)
                 ?? throw new Exception("Unable to create AVAudioPlayer from data.");

        //player.EnableRate = true;
        player.PrepareToPlay();
    }

    public void Play()
    {
        isFinishedPlaying = false;
        player.Play();
    }

    protected virtual void Dispose(bool disposing)
    {
        if (isDisposed)
            return;

        if (disposing)
        {
            // Stop the player and release the resources
            // if (player != null)
            // {
            //     if (player.Playing)
            //     {
            //         player.Stop();
            //     }
            //     player.Dispose();
            //     player = null;
            // }

            player.Stop();
            player.Dispose();
        }

        isDisposed = true;
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    // Destructor to catch cases where Dispose was not called
    ~VoicePlayer()
    {
        Dispose(false);
    }
}

My issue is that when I use VoicePlayer as follows in my ViewModel, the audio never plays - when I have breakpoints in the Dispose method, it plays the audio while I'm waiting to step in, this makes me think that Dispose is getting called before the audio even gets a chance to play out of Debug mode.

using (var voicePlayer = new VoicePlayer(record.Bytes))
{
    voicePlayer.Play();
}

But if I execute without the using statement it plays fine, but it seems the destructor is never called so the player is never released.

var voicePlayer = new VoicePlayer(record.Bytes);
voicePlayer.Play();

What I am trying to figure out is a way to Dispose the player once playback finishes.


Solution

  • There's a FinishedPlaying event you can try like so...

    Add a method to handle the event...

    void HandleFinishedPlaying(object sender, AVStatusEventArgs e) 
    {
        DisposeAudioPlayer();
    }
    

    Straight after the player is initialised, wire up the handler to the event...

    player.FinishedPlaying += HandleFinishedPlaying;
    

    Define a method to dispose the player on the main thread...

    void DisposeAudioPlayer()
    {
        if (player == null)
            return;
    
        MainThread.BeginInvokeOnMainThread(() => {
            player.Stop();
            player.FinishedPlaying -= HandleFinishedPlaying;
            player.Dispose();
            player = null;
        });        
    }