Building your own audio player with .NET – part 3

This is the third and final part of the tutorial on building a platform-independent audio app on .NET. In the first part of this tutorial, we talked about setting up the general project structure and enabling audio playback capabilities on Windows. The second part of the tutorial spoke about adding the ability to play audio on Linux, while also enabling the library to pull the specific code, based on the operating system the software is running on. Today, we will talk about enabling audio capabilities on Mac.

As it has been mentioned before, .NET is a great platform-independent technology for building software. However, due to its platform-independent nature, it lacks some of the most basic capabilities, which were too different in implementation on different operating systems. One of these is the ability to natively play audio.

The goal of this three-part tutorial is to build our own library that will enable us to use basic playback capabilities without any additional third-party dependencies whatsoever.

Adding Mac implementation of IPlayer interface

If you have been following the previous parts of this tutorial, you will remember that we have been using the following interface that all of our OS-specific player classes implement:

namespace NetCoreAudio.Interfaces
  public interface IPlayer
    Task Play(string fileName);
    Task Pause();
    Task Resume();
    Task Stop();

We already have classes called WindowsPlayer and LinuxPlayer which implement this interface. For this exercise, we will add a new class and will call it MacPlayer.

Once created, we will modify the logic that selects the correct implementation of the IPlayer based on the operating system the application is running on:

if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
  _internalPlayer = new WindowsPlayer();
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
  _internalPlayer = new LinuxPlayer();
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
  _internalPlayer = new MacPlayer();

Running Bash commands on Mac

The principles of running audio on Mac are similar to how it’s done on Linux, as described in part two. Both of the operating systems are based on Unix; therefore many of their internal components are similar.

Unlike Linux, Mac OS doesn’t use ALSA architecture and therefore it doesn’t come with aplay. However, it comes with a command line utility of its own, known as afplay.

The basic syntax of using afplay is very similar to aplay. Assuming that we have an audio file called “audio.mp3”, we can run the following command to play it:

afplay audio.mp3

Just like we did with the Linux implementation, we can launch bash from the Process class and use this command to start the playback. Literally, the only difference here is that we are using “afplay” instead of “aplay”.

Even pausing and resuming the playback is done in the same way. The following string can be used inside of the Pause() method.

$"kill -STOP {_process.Id}"

Likewise, the following string can be used inside of the Resume() method:

$"kill -CONT {_process.Id}"

As the name of the audio player utility is the only difference between the two player implementations, it would be wise to move most of the code that we have previously written for LinuxPlayer into a shared base class that both LinuxPlayer and MacPlayer will inherit from. We will only need to have a virtual field to store the name of our bash command utility and populate it with either aplay or afplay, depending on which derived class is being used.

Wrapping up

This was the final part of a three-part tutorial on how to play audio on .NET. What we didn’t do in this tutorial was delve into detailed line-by-line implementation. This is to allow you to figure things out for yourself while having just enough information to be able to do so.

If you want to see how these principles are implemented in practice, you can check NetCoreAudio repository on GitHub. It is also now used on the NuGet Gallery, so you can use it in your own .NET Core projects.

Part 1 of the tutorial

Part 2 of the tutorial