I’ve been on a spiritual search for a cross-platform solution that allows developers to play and capture audio using .NET 5. It’s a journey filled with many highs and lows, mostly cursing at my screen and ruing the day I ever embarked on this mission. That said, I’ve found a way to play audio using a cross-platform API, even though the approach itself can differ across Windows, macOS, and Linux. This post will show the process we can take to get audio functionality in our .NET console applications.
The Painful Obvious Truth
As a professional for many years, I’ve learned that it’s best to use the platform and avoid abstractions, including database engines, web frameworks, and mobile platforms. While generalizations can increase code reuse, they can also limit functionality to the lowest common denominator. So my journey to find a cross-platform library that was able to play audio cross-platform using .NET purely had doomed from the start.
Through my searches, I found a library called NetCoreAudio. While I was looking through the project’s code, the real solution was painfully obvious.
Use the Operating System and its ability to play audio. –Inner Monologue
Being a macOS user, I could rely on a system utility to play audio from .NET and manage the child process’s lifecycle. NetCoreAudio attempts to create a cross-platform abstraction, but it reduces the native operating system utility’s functionality in the process. Additionally, I found that stopping audio playback was wonky, and the API didn’t take advantage of async/await
and CancellationToken
constructs.
We can do better!
AFPlay on macOS
Afplay
is a macOS command-line utility that allows users to play an audio file via the terminal.
This utility has many optional flags that can alter the playback of an audio file.
There’s a lot of great functionality in this utility, and we should take advantage of as much as possible. Be careful with the volume flag; I scared the hell out of my wife and dogs by setting the max value.
Implementing The Afplay C# Wrapper
Folks might feel opposed to wrapping OS utilities, but the .NET library defers many of its cryptographic and networking tasks to the underlying operating system. We should embrace the uniqueness of our platform and utilize its strengths.
In my case, I primarily work on macOS, so I’ll be writing a .NET wrapper for macOS users only. If you need to adapt a wrapper for a specific operating system, I recommend looking at NetCoreAudio and porting its functionality.
Knowing I had to work with processes, I decided to defer process management responsibilities to potentially two .NET OSS projects: SimpleExec
and CliWrap
. I would recommend you use CliWrap if you need CancellationToken
support. Both libraries are excellent abstractions of the Process
class.
Let’s take a look at the example project first.
We can call the Audio
class’ Play
method and pass all of the utility parameters accepted by afplay
. Additionally, we are using async/await
syntax to ensure that an audio file only moves on to the next line was the file has completed playing. Finally, we can pass a CancellationToken
to our Play
method to abruptly stop playing the audio file. The utility has no pause and resume functionality, which I wish it did.
Let’s take a look at the implementation.
We have a fully-featured C# wrapper around a native audio player in just a few lines of code. CliWrap and macOS are doing much of the heavy lifting here.
Users of macOS can clone the sample GitHub repository.
Conclusion
While it would be nice to have a pure C# solution that works regardless of the operating system, it’s impossible to achieve that goal (as far as I know). Given each OS has its own set of permissions and access to hardware, this solution seems to be the best I can muster. NetCoreAudio is an excellent library, and folks should look at it if they need a cross-platform solution. Still, folks should also consider writing their own wrapper using CliWrap to get better async/await
support.
Thanks for reading, and I hope you found this post helpful. Please leave a comment below.