Introduction
I little while ago, I found myself writing a media player, and consequently was looking for an easy way to play MP3, Wav, midi, and other file types as well. I came across the WindowsMediaPlayerClass
. It can be found in the COM library WMP.dll. However, it provided no features for fading or cross-fading songs. So, I wrote a more advanced wrapper class for it that uses WindowsMediaPlayerClass
objects to fade and cross-fade songs.
Using the code
To use my code, download the source files and add the Media Player.cs file to your existing/new project. Then you must add a reference to the COM library WMPLib. Its description is Windows Media Player, and the file name is wmp.dll. Finally, make sure the reference's attribute Embed Interop Types is set to false. This is necessary for VS2010, I am not sure about earlier versions.
The class uses two WindowsMediaPlayerClass
objects to play files and to enable cross-fading. When you are using the class, it appears only as if there is one player though. With the exception of events, which I will describe later.
The code has five main methods: Play
, Pause
, Stop
, Resume
, and Fade
. These are the only public methods. Play
is fairly basic; if the player is paused, it resumes play. If the player is stopped, it plays the file specified. Depending on whether you have set fade to true, the player will either just use player one for playing without fading or cross-fading. However, if you want fading/cross-fading, the player alternates between the two internal players, fading them in and out as is relevant.
Pause
simply pauses the current internal player. If a fade is specified, it starts the fade process. Stop
does the same thing except it stops the internal player completely, making it lose its current position. Also, Stop
will set the player state to stopped so the first subsequent call to Play
will play the new specified file. The first call after Pause
will make the player continue playing the current track from the last position. The first call to Play
after Pause
will call Resume
, so Resume
and Play
can be used interchangeably. The final method Fade
simply starts a fade from the current volume to a new specified volume.
The main code that is useful is Crossfade
, which fades one song up and another down in a set period of time, thus creating a linear cross-fade effect. The code uses a timer ticking every 500 milliseconds to adjust the volumes of the two internal players by a small amount, specified before the cross-fade starts, each time a tick event occurs. The volume may be changed during a fade/cross-fade; however, a sudden jump in volume would be heard at the end of the fade/cross-fade if you do. It is advisable, therefore, not to change the volume during a fade/cross-fade.
The first chunk of code in CrossfadeTimer_Tick
determines whether it is the start of a cross-fade. If it is, then it starts the new player at volume 0, ready to fade up. The code is as follows:
if (CrossfadeTotalRunTime <= 0)
{
switch (StoppingPlayer)
{
case Players.Player1:
PlayingPlayer = Players.Player2;
Player2.volume = 0;
Player2.URL = Player2Song;
Player2.play();
break;
case Players.Player2:
PlayingPlayer = Players.Player1;
Player1.volume = 0;
Player1.URL = Player1Song;
Player1.play();
break;
}
}
The next chunk runs while the cross-fade is occurring. It adjusts the volume of both the stopping and playing players, fading the playing up and stopping down. This is the cross-fade.
else if(CrossfadeTotalRunTime < (CrossfadeTime * 1000))
{
switch (PlayingPlayer)
{
case Players.Player1:
{
Player1.volume = (int)(CrossfadeVolumeAdjustment *
((float)CrossfadeTotalRunTime / 1000));
Player2.volume = (int)(Volume - (CrossfadeVolumeAdjustment *
(float)(CrossfadeTotalRunTime / 1000)));
}
break;
case Players.Player2:
{
Player1.volume = (int)(Volume - (CrossfadeVolumeAdjustment *
((float)CrossfadeTotalRunTime / 1000)));
Player2.volume = (int)(CrossfadeVolumeAdjustment *
((float)CrossfadeTotalRunTime / 1000));
}
break;
}
}
The final chunk runs when the fade is over. It does not stop the stopping player, but lets it run till its media ends. Also, it makes sure the player's volume of the internal player is the same as the volume set by the user. This is the reason a change in volume during a fade/cross-fade will result in a sudden jump at the end of the fade. The final volume used is just set; if it is different, there is no smooth fade between the two volumes. To implement that, one would increase the apparent length of the cross-fade/fade, and would be harder to implement than not changing the volume in the first place.
else if (CrossfadeTotalRunTime >= (CrossfadeTime * 1000))
{
CrossfadeTimer.Enabled = false;
CrossfadeTimer.Stop();
InCrossfade = false;
switch (PlayingPlayer)
{
case Players.Player1:
Player1.volume = Volume;
Player2.volume = 0;
break;
case Players.Player2:
Player2.volume = Volume;
Player1.volume = 0;
break;
}
}
The final thing to note is that events such as media-end are fired even if externally you have only just called Play
. This is because separate events are fired for each player, but externally, they appear as one player, which sounds confusing, but is useful because, if say, you are playing a playlist of cross-fading tracks, you will still get the media-end event even after the next track has started playing. This allows easier programming of play counts and similar song attributes.