Introduction
When writing a desktop application, it sometimes becomes necessary to play some audio files. .NET/WPF comes with two classes trying to achieve
this goal: SoundPlayer
and
MediaPlayer
Unfortunately, both classes come with some (severe) limitations that make them hard to use under certain (not so uncommon) circumstances. This article will provide
a replacement for both classes. It'll also provide some more details on the limitations and problems associated with these two classes.
Using the code
Before going into more detail, let's jump ahead and take a look at the final class. It's called AudioPlayer
. Here's how to use it:
AudioPlayer myAudioPlayer = new AudioPlayer(...);
myAudioPlayer.Play();
This simply plays the audio file. The audio file is specified as an argument to the constructor. You can either use an absolute or relative file path on the file system,
or choose a .NET assembly resource. If you want to use a resource, you first need to set its "Build Action" to "Embedded Resource". To do this,
right-click the audio file in Solution Explorer and choose "Properties". This will open the "Properties" pane where you can select
the appropriate build action.

Then you can create a AudioPlayer
instance like this:
AudioPlayer myAudioPlayer = new AudioPlayer(Assembly.GetExecutingAssembly(),
"MyRootNamespace", "myfolder/myfile.mp3");
Besides Play()
, AudioPlayer
contains at lot of other useful stuff. Here is its outline:
public class AudioPlayer {
public bool IsPlaying { get; }
public bool IsLooped { get; set; }
public double Volume { get; set; }
public Duration Length { get; }
public TimeSpan Position { get; set; }
public event EventHandler<EventArgs> PlaybackEnded;
public AudioPlayer(string fileName, bool looping = false);
public AudioPlayer(Assembly assembly, string assemblyNamespace, string mediaFile,
bool looping = false);
public void Play();
public void Stop();
}
The meaning and usage of each method/property should be straightforward.
The demo project contains example code for playing audio files from the file system as well as playing files from .NET assembly resources. You can find the
code in MainWindow.xaml.cs in the "AudioPlayerDemo" project.
Comparison of SoundPlayer and MediaPlayer
As mentioned earlier, the .NET classes SoundPlayer
and MediaPlayer
come with some limitations. These limitations are listed here for your interest.
Feature | SoundPlayer | MediaPlayer |
Play multiple sounds at the same time | All SoundPlayer instances share one single "audio channel", i.e., you can't play multiple sounds at the same time, even if you have
multiple SoundPlayer instances. This also results in a problem when you try to repeatedly play the same sound rapidly. (Think of the click sound
of the click wheel on your iPod, if you own one.) In this case, the playback "delays" for some time. | Can play multiple sounds at the same time. |
---|
Supports looping | Yes | Only through MediaEnded event with explicitly stopping and playing the sound. |
---|
Supports loading audio files from resources | Yes | No |
---|
Can play formats other than .wav (e.g., MP3s) | No, .wav only. | Yes, including .mp3. |
---|
Supports easy re-play | Yes, simply call Play() again to play the sound again. | No, requires the user to reset the playing position with Stop() before being able to play the sound again. |
---|
Behind the scenes
Now that we've established the limitations of both SoundPlayer
and MediaPlayer
, let's dive a little bit deeper into resolving these limitations.
This section explains the problems that were encountered during the implementation of AudioPlayer
. Reading this section isn't required for using AudioPlayer
,
so you can skip it if you're just interested in using AudioPlayer
.
Let's get started. The only severe limitation of MediaPlayer
, in my opinion, is that it can't load audio files from .NET assembly resources. (The help page
MediaPlayer
clearly states this - but unfortunately provides no alternative solution.) So, I implemented AudioPlayer
as a wrapper
around MediaPlayer
(and not around SoundPlayer
).
Supporting "easy re-play" and "looping" was implemented easily enough, so I won't go into the details for these features. See the attached demo project for details.
Exporting resources to use them with MediaPlayer
The bigger problem was playing audio files from .NET assembly resources. As a workaround, the basic idea was to export
a resource into a temporary file. This can easily be achieved by a code similar to this:
public void ExportResource(Assembly assembly, string assemblyNamespace, string mediaFile) {
string fullFileName = assemblyNamespace + "." + mediaFile;
string tmpFile = Path.Combine(this.m_resourceTempDir, fullFileName);
using (Stream input = assembly.GetManifestResourceStream(fullFileName)) {
using (Stream file = File.OpenWrite(tmpFile)) {
CopyStream(input, file);
}
}
}
We can then use this temporary file for MediaPlayer
and we're done - are we not? Unfortunately, not.
Deleting temporary files
The temporary file needs to be deleted when the application closes at the latest - and that's not as easy to implement as it sounds.
The following list lists all approaches that I've tried and also describes if and why they don't work:
- Use
Path.GetTempFileName
: This doesn't solve the problem as the temporary file
won't be deleted when the application closes. - Use
CreateFile
together with FILE_FLAG_DELETE_ON_CLOSE
: Doesn't work because
every file handle to this file needs to be opened with FILE_FLAG_DELETE_ON_CLOSE
being set. However, we have no control over how MediaPlayer
opens its files. - Remember each temporary file that was created and delete it when it is no longer used: This approach works, but also not as easily as it sounds. More on that below.
So, remembering all created temporary files is the way to go. The problem now is: When do we delete these files? There are two possibilities:
- Remember each created file in its associated
AudioPlayer
instance and delete it from its destructor/finalizer. - Keep a list of all created files in a single place (i.e., a singleton class) and delete all files from within an "application closing event" handler.
Unfortunately, this approach doesn't work out-of-the-box because MediaPlayer
holds a file handle to the temporary file. And as long as it holds this handle,
we can't delete the file. There is, however, the method MediaPlayer.Close()
that closes this handle.
Now, MediaPlayer
inherits from DispatcherObject
and therefore only allows modifications from the thread that created the MediaPlayer
instance. This includes the method Close()
, which is unfortunate because every destructor/finalizer as well as every AppDomain.ProcessExit
handler runs
on a separate thread. So, Close()
can't be called from either of them.

To solve this problem, one usually uses the DispatcherObject
's Dispatcher
to invoke the method on the owning thread.
Unfortunately, using Dispatcher.Invoke()
from within a destructor or a AppDomain.ProcessExit
event handler doesn't work.
Nothing happens on these calls so they can't be used in this context.
Our own MediaPlayer thread
The only solution to this problem I could think of is: Create a separate thread that creates and closes MediaPlayer
instances.
The basic implementation of the thread's run method would look like this:
private void RunThread() {
CreateMyMediaPlayerInstances();
WaitForThreadShutdown();
foreach (MediaPlayer player in this.m_myMediaPlayerInstances) {
player.Close();
}
}
Now, the easiest way to do this is to use a Dispatcher
on the thread.
This Dispatcher
then would be used to create and manipulate the MediaPlayer
instances. With this, the implementation would look like this:
private PlayerThread() {
Thread playerThread = new Thread(RunThread);
playerThread.IsBackground = true;
playerThread.SetApartmentState(ApartmentState.STA);
playerThread.Start();
AppDomain.CurrentDomain.ProcessExit += (s, e) => {
Dispatcher.FromThread(playerThread).InvokeShutdown();
playerThread.Join();
RemoveAllTemporaryFiles();
};
}
private void RunThread() {
Dispatcher.Run();
foreach (MediaPlayer player in this.m_myMediaPlayerInstances) {
player.Close();
}
}
Unfortunately (yet again), Dispatcher.InvokeShutdown()
doesn't work from within a AppDomain.ProcessExit
event handler. Just nothing happens
and also Dispatcher.Run()
never returns. (I filed a bug report
with Microsoft on this issue but I fear they will say it's by design.)
I also tried various other solutions such as repeatedly calling Dispatcher.ExitAllFrames()
or using Dispatcher.PushFrame(new DispatcherFrame(true))
but without any luck.
So, the final solution was to write my own "Dispatcher" implementation (called EventQueue
).
Note: It seems that MediaPlayer
uses DispatcherTimer
for its events (at least for MediaPlayer.MediaEnded
). However, because we're not using a Dispatcher
on the player thread, these timers are never
evaluated/executed and thus the events are never fired. Therefore, these events need to be "simulated" with DispatcherTimer
s in AudioPlayer
(which doesn't run
on the player thread and therefore can use DispatcherTimer
s).
Source code repository
Besides the download provided here at CodeProject, you can find the Mercurial repository for this
article here: https://bitbucket.org/skrysmanski/audioplayer.
History