Introduction
New Revision
Below is the complete code of the MP3 Player class, which is courtoisy of Trapper Ano, who made improvements and added new functionality to this class.
Although the .NET FCL is an excellent tool for developing web/XML services and Windows applications, and has outstanding capabilities in many ways, unfortunately, it lacks multimedia capabilities. This problem is solved by using DirectX's DirectSound for complicated sound tasks (surround systems, 3D sound, etc.), and MCI for more simple tasks. MCI stands for Media Control Interface, and it exists in Windows since the first Win3.11 with 32 bit code execution abilities (according to Microsoft). The functions that enable developers to benefit from this interface are compiled in the winmm.dll which is situated in the %windir%\system32 directory. By importing functions from this library, we get access to a variety of functions for playing Wav sounds, controlling the PC speaker, and of course, for using the MCI. The most important of all these functions is MCIERROR mciSendString(LPCTSTR lpszCommand,LPTSTR lpszReturnString,UINT cchReturn, HANDLE hwndCallback);
. By using it, you can send/receive control strings for interacting with MCI, and in that way play virtually any kind of media - analog/digital video/audio, control multimedia devices, and much more.
As you can see, the MCI is very useful and is commonly used in Windows, but it has its down sides too. Its complicated to use, and is not object oriented. In case of errors, the mciSendString
method returns an integer number corresponding to the error number. If everything goes well, it returns zero. Also, any return information is dumped into a buffer, the address of which is specified as the second argument of the function, and the length of the buffer is specified as the third argument. A wrapper class makes the job of opening/playing audio through MCI much more simple. I also added events that are fired on certain occasions, to improve the efficiency of the player.
Background
The original code of this class was downloaded form the net, and then I read the links below and extended it to have the full functionality of a MP3 player (the original class could only open and play files).
There are a few good articles on using MCI, but if you want to study it in depth, there is no better way than from MSDN.
Using the code
You can create an instance of the MP3Player
quite simply:
Media.MP3Player mplayer = new MP3Player();
After that, we can open, play, pause, stop, and close a file like this:
mplayer.Open(@"C:\something.mp3");
mplayer.Play();
mplayer.Pause();
mplayer.Stop();
mplayer.Close();
You can register for a certain event that the class exposes, for example:
mplayer.OpenFile+=new MP3Player.OpenFileEventHandler(mplayer_OpenFile);
And then, write a method to handle the event. The Open
event has a file name in its EventArgs
, so you can get the name of the file that is being opened.
void mplayer_OpenFile(Object sender, OpenFileEventArgs e)
{
trackBar1.Maximum = (int)(mplayer.AudioLength/1000);
this.Text = e.FileName;
trackBar1.Value = 0;
timer1.Enabled = false;
}
Here is the complete code of the class:
#region Purpose, Credits & Terms of Use
#endregion
using System;
using System.Runtime.InteropServices;
using System.Text;
using System.Windows.Forms;
namespace KrasimirTrapper
{
public enum MCINotify
{
Success = 0x01,
Superseded = 0x02,
Aborted = 0x04,
Failure = 0x08
}
public class MP3Player : Form
{
private string Pcommand, FName, Phandle;
private bool Opened, Playing, Paused, Looping, MutedAll, MutedLeft, MutedRight;
private const int MM_MCINOTIFY = 0x03b9;
private int Err, aVolume, bVolume, lVolume, pSpeed, rVolume, tVolume, VolBalance;
private ulong Lng;
[DllImport("winmm.dll")]
private static extern int mciSendString(string command, StringBuilder buffer, int bufferSize, IntPtr hwndCallback);
protected override void WndProc(ref Message m)
{
switch (m.Msg)
{
case MM_MCINOTIFY:
if (m.WParam.ToInt32() == 1)
{
Stop();
OnSongEnd(new SongEndEventArgs());
}
break;
case (int)MCINotify.Aborted:
OnOtherEvent(new OtherEventArgs(MCINotify.Aborted));
break;
case (int)MCINotify.Failure:
OnOtherEvent(new OtherEventArgs(MCINotify.Failure));
break;
case (int)MCINotify.Success:
OnOtherEvent(new OtherEventArgs(MCINotify.Success));
break;
case (int)MCINotify.Superseded:
OnOtherEvent(new OtherEventArgs(MCINotify.Superseded));
break;
}
base.WndProc(ref m);
}
public MP3Player()
{
Opened = false;
Pcommand = string.Empty;
FName = string.Empty;
Playing = false;
Paused = false;
Looping = false;
MutedAll = MutedLeft = MutedRight = false;
rVolume = lVolume = aVolume = tVolume = bVolume = 1000;
pSpeed = 1000;
Lng = 0;
VolBalance = 0;
Err = 0;
Phandle = "MP3Player";
}
public MP3Player(string handle)
{
Opened = false;
Pcommand = string.Empty;
FName = string.Empty;
Playing = false;
Paused = false;
Looping = false;
MutedAll = MutedLeft = MutedRight = false;
rVolume = lVolume = aVolume = tVolume = bVolume = 1000;
pSpeed = 1000;
Lng = 0;
VolBalance = 0;
Err = 0;
Phandle = handle;
}
#region Volume
public int Balance
{
get
{
return VolBalance;
}
set
{
if (Opened && (value >= -1000 && value <= 1000))
{
VolBalance = value;
double vPct = Convert.ToDouble(aVolume) / 1000.0;
if (value < 0)
{
Pcommand = string.Format("setaudio {0} left volume to {1:#}", Phandle, aVolume);
if ((Err = mciSendString(Pcommand, null, 0, IntPtr.Zero)) != 0) OnError(new ErrorEventArgs(Err));
Pcommand = string.Format("setaudio {0} right volume to {1:#}", Phandle, (1000 + value) * vPct);
if ((Err = mciSendString(Pcommand, null, 0, IntPtr.Zero)) != 0) OnError(new ErrorEventArgs(Err));
}
else
{
Pcommand = string.Format("setaudio {0} right volume to {1:#}", Phandle, aVolume);
if ((Err = mciSendString(Pcommand, null, 0, IntPtr.Zero)) != 0) OnError(new ErrorEventArgs(Err));
Pcommand = string.Format("setaudio {0} left volume to {1:#}", Phandle, (1000 - value) * vPct);
if ((Err = mciSendString(Pcommand, null, 0, IntPtr.Zero)) != 0) OnError(new ErrorEventArgs(Err));
}
}
}
}
public bool MuteAll
{
get
{
return MutedAll;
}
set
{
MutedAll = value;
if (MutedAll)
{
Pcommand = String.Format("setaudio {0} off", Phandle);
if ((Err = mciSendString(Pcommand, null, 0, IntPtr.Zero)) != 0) OnError(new ErrorEventArgs(Err));
}
else
{
Pcommand = String.Format("setaudio {0} on", Phandle);
if ((Err = mciSendString(Pcommand, null, 0, IntPtr.Zero)) != 0) OnError(new ErrorEventArgs(Err));
}
}
}
public bool MuteLeft
{
get
{
return MutedLeft;
}
set
{
MutedLeft = value;
if (MutedLeft)
{
Pcommand = String.Format("setaudio {0} left off", Phandle);
if ((Err = mciSendString(Pcommand, null, 0, IntPtr.Zero)) != 0) OnError(new ErrorEventArgs(Err));
}
else
{
Pcommand = String.Format("setaudio {0} left on", Phandle);
if ((Err = mciSendString(Pcommand, null, 0, IntPtr.Zero)) != 0) OnError(new ErrorEventArgs(Err));
}
}
}
public bool MuteRight
{
get
{
return MutedRight;
}
set
{
MutedRight = value;
if (MutedRight)
{
Pcommand = String.Format("setaudio {0} right off", Phandle);
if ((Err = mciSendString(Pcommand, null, 0, IntPtr.Zero)) != 0) OnError(new ErrorEventArgs(Err));
}
else
{
Pcommand = String.Format("setaudio {0} right on", Phandle);
if ((Err = mciSendString(Pcommand, null, 0, IntPtr.Zero)) != 0) OnError(new ErrorEventArgs(Err));
}
}
}
public int VolumeAll
{
get
{
return aVolume;
}
set
{
if (Opened && (value >= 0 && value <= 1000))
{
aVolume = value;
Pcommand = String.Format("setaudio {0} volume to {1}", Phandle, aVolume);
if ((Err = mciSendString(Pcommand, null, 0, IntPtr.Zero)) != 0) OnError(new ErrorEventArgs(Err));
}
}
}
public int VolumeBass
{
get
{
return bVolume;
}
set
{
if (Opened && (value >= 0 && value <= 1000))
{
bVolume = value;
Pcommand = String.Format("setaudio {0} bass to {1}", Phandle, bVolume);
if ((Err = mciSendString(Pcommand, null, 0, IntPtr.Zero)) != 0) OnError(new ErrorEventArgs(Err));
}
}
}
public int VolumeLeft
{
get
{
return lVolume;
}
set
{
if (Opened && (value >= 0 && value <= 1000))
{
lVolume = value;
Pcommand = String.Format("setaudio {0} left volume to {1}", Phandle, lVolume);
if ((Err = mciSendString(Pcommand, null, 0, IntPtr.Zero)) != 0) OnError(new ErrorEventArgs(Err));
}
}
}
public int VolumeRight
{
get
{
return rVolume;
}
set
{
if (Opened && (value >= 0 && value <= 1000))
{
rVolume = value;
Pcommand = String.Format("setaudio {0} right volume to {1}", Phandle, rVolume);
if ((Err = mciSendString(Pcommand, null, 0, IntPtr.Zero)) != 0) OnError(new ErrorEventArgs(Err));
}
}
}
public int VolumeTreble
{
get
{
return tVolume;
}
set
{
if (Opened && (value >= 0 && value <= 1000))
{
tVolume = value;
Pcommand = String.Format("setaudio {0} treble to {1}", Phandle, tVolume);
if ((Err = mciSendString(Pcommand, null, 0, IntPtr.Zero)) != 0) OnError(new ErrorEventArgs(Err));
}
}
}
#endregion
#region Properties
public ulong AudioLength
{
get
{
if (Opened)
return Lng;
else
return 0;
}
}
public ulong CurrentPosition
{
get
{
if (Opened && Playing)
{
StringBuilder s = new StringBuilder(128);
Pcommand = String.Format("status {0} position", Phandle);
if ((Err = mciSendString(Pcommand, s, 128, IntPtr.Zero)) != 0) OnError(new ErrorEventArgs(Err));
ulong position = Convert.ToUInt64(s.ToString());
return position;
}
else
return 0;
}
}
public string FileName
{
get
{
return FName;
}
}
public string PHandle
{
get
{
return Phandle;
}
}
public bool IsOpened
{
get
{
return Opened;
}
}
public bool IsPlaying
{
get
{
return Playing;
}
}
public bool IsPaused
{
get
{
return Paused;
}
}
public bool IsLooping
{
get
{
return Looping;
}
set
{
Looping = value;
if (Opened && Playing && !Paused)
{
if (Looping)
{
Pcommand = String.Format("play {0} notify repeat", Phandle);
if ((Err = mciSendString(Pcommand, null, 0, this.Handle)) != 0) OnError(new ErrorEventArgs(Err));
}
else
{
Pcommand = String.Format("play {0} notify", Phandle);
if ((Err = mciSendString(Pcommand, null, 0, this.Handle)) != 0) OnError(new ErrorEventArgs(Err));
}
}
}
}
public int Speed
{
get
{
return pSpeed;
}
set
{
if (value >= 3 && value <= 4353)
{
pSpeed = value;
Pcommand = String.Format("set {0} speed {1}", Phandle, pSpeed);
if ((Err = mciSendString(Pcommand, null, 0, this.Handle)) != 0) OnError(new ErrorEventArgs(Err));
}
}
}
#endregion
#region Main Functions
private bool CalculateLength()
{
try
{
StringBuilder str = new StringBuilder(128);
Pcommand = "status " + Phandle + " length";
if ((Err = mciSendString(Pcommand, str, 128, IntPtr.Zero)) != 0) OnError(new ErrorEventArgs(Err));
Lng = Convert.ToUInt64(str.ToString());
return true;
}
catch
{
return false;
}
}
public void Close()
{
if (Opened)
{
Pcommand = String.Format("close {0}", Phandle);
if ((Err = mciSendString(Pcommand, null, 0, IntPtr.Zero)) != 0) OnError(new ErrorEventArgs(Err));
FName = string.Empty;
Opened = false;
Playing = false;
Paused = false;
OnCloseFile(new CloseFileEventArgs());
}
}
public bool Open(string sFileName)
{
if (!Opened)
{
Pcommand = String.Format("open \"" + sFileName + "\" type mpegvideo alias {0}", Phandle);
if ((Err = mciSendString(Pcommand, null, 0, IntPtr.Zero)) != 0) OnError(new ErrorEventArgs(Err));
FName = sFileName;
Opened = true;
Playing = false;
Paused = false;
Pcommand = String.Format("set {0} time format milliseconds", Phandle);
if ((Err = mciSendString(Pcommand, null, 0, IntPtr.Zero)) != 0) OnError(new ErrorEventArgs(Err));
Pcommand = String.Format("set {0} seek exactly on", Phandle);
if ((Err = mciSendString(Pcommand, null, 0, IntPtr.Zero)) != 0) OnError(new ErrorEventArgs(Err));
if (!CalculateLength())
return false;
OnOpenFile(new OpenFileEventArgs(sFileName));
return true;
}
else
{
this.Close();
this.Open(sFileName);
return true;
}
}
public void Pause()
{
if (Opened)
{
if (!Paused)
{
Paused = true;
Pcommand = String.Format("pause {0}", Phandle);
if ((Err = mciSendString(Pcommand, null, 0, IntPtr.Zero)) != 0) OnError(new ErrorEventArgs(Err));
OnPauseFile(new PauseFileEventArgs());
}
}
}
public void Play()
{
if (Opened)
{
if (!Playing)
{
Playing = true;
Pcommand = String.Format("play {0}{1} notify", Phandle, Looping ? " repeat" : string.Empty);
if ((Err = mciSendString(Pcommand, null, 0, this.Handle)) != 0) OnError(new ErrorEventArgs(Err));
OnPlayFile(new PlayFileEventArgs());
}
else
{
if (Paused)
{
Paused = false;
Pcommand = String.Format("play {0}{1} notify", Phandle, Looping ? " repeat" : string.Empty);
if ((Err = mciSendString(Pcommand, null, 0, this.Handle)) != 0) OnError(new ErrorEventArgs(Err));
OnPlayFile(new PlayFileEventArgs());
}
}
}
}
public void Seek(ulong milliseconds)
{
if (Opened && milliseconds <= Lng)
{
if (Playing)
{
if (Paused)
{
Pcommand = String.Format("seek {0} to {1}", Phandle, milliseconds);
if ((Err = mciSendString(Pcommand, null, 0, IntPtr.Zero)) != 0) OnError(new ErrorEventArgs(Err));
}
else
{
Pcommand = String.Format("seek {0} to {1}", Phandle, milliseconds);
if ((Err = mciSendString(Pcommand, null, 0, this.Handle)) != 0) OnError(new ErrorEventArgs(Err));
Pcommand = String.Format("play {0}{1} notify", Phandle, Looping ? " repeat" : string.Empty);
if ((Err = mciSendString(Pcommand, null, 0, this.Handle)) != 0) OnError(new ErrorEventArgs(Err));
}
}
}
}
public void Stop()
{
if (Opened && Playing)
{
Playing = false;
Paused = false;
Pcommand = String.Format("seek {0} to start", Phandle);
if ((Err = mciSendString(Pcommand, null, 0, IntPtr.Zero)) != 0) OnError(new ErrorEventArgs(Err));
Pcommand = String.Format("stop {0}", Phandle);
if ((Err = mciSendString(Pcommand, null, 0, IntPtr.Zero)) != 0) OnError(new ErrorEventArgs(Err));
OnStopFile(new StopFileEventArgs());
}
}
#endregion
#region Event Arguments
public class OpenFileEventArgs : EventArgs
{
public OpenFileEventArgs(string filename)
{
this.FileName = filename;
}
public readonly string FileName;
}
public class PlayFileEventArgs : EventArgs
{
public PlayFileEventArgs()
{
}
}
public class PauseFileEventArgs : EventArgs
{
public PauseFileEventArgs()
{
}
}
public class StopFileEventArgs : EventArgs
{
public StopFileEventArgs()
{
}
}
public class CloseFileEventArgs : EventArgs
{
public CloseFileEventArgs()
{
}
}
public class ErrorEventArgs : EventArgs
{
[DllImport("winmm.dll")]
static extern bool mciGetErrorString(int errorCode, StringBuilder errorText, int errorTextSize);
public ErrorEventArgs(int ErrorCode)
{
this.ErrorCode = ErrorCode;
StringBuilder sb = new StringBuilder(256);
mciGetErrorString(ErrorCode, sb, 256);
this.ErrorString = sb.ToString();
}
public readonly int ErrorCode;
public readonly string ErrorString;
}
public class SongEndEventArgs : EventArgs
{
public SongEndEventArgs()
{
}
}
public class OtherEventArgs : EventArgs
{
public OtherEventArgs(MCINotify Notification)
{
this.Notification = Notification;
}
public readonly MCINotify Notification;
}
#endregion
#region Event Handlers
public delegate void OpenFileEventHandler(Object sender, OpenFileEventArgs oea);
public delegate void PlayFileEventHandler(Object sender, PlayFileEventArgs pea);
public delegate void PauseFileEventHandler(Object sender, PauseFileEventArgs paea);
public delegate void StopFileEventHandler(Object sender, StopFileEventArgs sea);
public delegate void CloseFileEventHandler(Object sender, CloseFileEventArgs cea);
public delegate void ErrorEventHandler(Object sender, ErrorEventArgs eea);
public delegate void SongEndEventHandler(Object sender, SongEndEventArgs seea);
public delegate void OtherEventHandler(Object sender, OtherEventArgs oea);
public event OpenFileEventHandler OpenFile;
public event PlayFileEventHandler PlayFile;
public event PauseFileEventHandler PauseFile;
public event StopFileEventHandler StopFile;
public event CloseFileEventHandler CloseFile;
public event ErrorEventHandler Error;
public event SongEndEventHandler SongEnd;
public event OtherEventHandler OtherEvent;
protected virtual void OnOpenFile(OpenFileEventArgs oea)
{
if (OpenFile != null) OpenFile(this, oea);
}
protected virtual void OnPlayFile(PlayFileEventArgs pea)
{
if (PlayFile != null) PlayFile(this, pea);
}
protected virtual void OnPauseFile(PauseFileEventArgs paea)
{
if (PauseFile != null) PauseFile(this, paea);
}
protected virtual void OnStopFile(StopFileEventArgs sea)
{
if (StopFile != null) StopFile(this, sea);
}
protected virtual void OnCloseFile(CloseFileEventArgs cea)
{
if (CloseFile != null) CloseFile(this, cea);
}
protected virtual void OnError(ErrorEventArgs eea)
{
if (Error != null) Error(this, eea);
}
protected virtual void OnSongEnd(SongEndEventArgs seea)
{
if (SongEnd != null) SongEnd(this, seea);
}
protected virtual void OnOtherEvent(OtherEventArgs oea)
{
if (OtherEvent != null) OtherEvent(this, oea);
}
#endregion
}
}
Points of interest
The best thing about this class is that it implements events for everything including errors. In this way, we gain much efficiency.
History
This is the first version of this class. After debugging and adding some capabilities according to your requests, I hope that it will become a good way to play MP3s using .NET.