Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

Simple MCI Player

4.88/5 (36 votes)
28 Jul 2016CPOL6 min read 1   14.5K  
A simple MCI player for playing music with the appropriate environment.
The Project on GitHub MCIplayer

(If you can't extract the files, just change the extension from .zip to .rar.)

MCIplayer

Introduction

This is a very simple music player that uses the MCI multimedia command string to interact with the device. The MCI interface is fairly powerful, and can be very useful. You can play almost any audio file (video files too, but I haven't covered this part in my player) including MP3, Wave, MIDI, ASF, and more.

Background

This link helped me a lot. It was very useful to get started with MCI.

You can find the commands used in this application here.

Using the code 

The application has a player class that handles all the interactions with the MCI device. A tag reader class reads specific information about the sound file (title, artist, album, and so on). And, the environment file handles the main form.

Player class

Declaration of variables used in the Player class:

C#
Random randomNumber;
private StringBuilder msg;  // MCI Error message
private StringBuilder returnData;  // MCI return data
private int error;
private string Pcommand;  // String that holds the MCI command
private ListView playlist;  // ListView as a playlist with the song path
public int NowPlaying { get; set; }
public bool Paused { get; set; }
public bool Loop { get; set; }
public bool Shuffle { get; set; }

First, there is an object of type Random that returns a random number that I'm using for the shuffle mode. Two StringBuilders are used by the MCI device: the first (msg) is used by mciGetErrorString to get the message for the given int value that mciSendString returns (mciSendString returns an integer value, 0 for a successful operation and a number for a failed operation). returnData is used when I'm expecting a return message (when trying to get the status: "playing", "paused", and so on). Pcommand is just a string that holds the command that I'm sending via mciSendString. The ListView playlist is a very important variable because it holds a list of all file paths in the playlist and I access all file paths via the index (0 for the first song, 1 for the second, and so on). NowPlaying holds the index of the ListView playlist that represents the song that's playing. The boolean variables are just used to watch the status of the player.

DllImport
C#
[DllImport("winmm.dll")]
private static extern int mciSendString(string strCommand, 
        StringBuilder strReturn, int iReturnLength, IntPtr hwndCallback);
[DllImport("winmm.dll")]
public static extern int mciGetErrorString(int errCode, 
              StringBuilder errMsg, int buflen);

You must import the MCI methods.

Class constructor
C#
public Player(ListView pl)
{
    randomNumber = new Random();
    playlist = pl;
    NowPlaying = 0;
    Loop = false;
    Shuffle = true;
    Paused= false;
    msg = new StringBuilder(128);
    returnData = new StringBuilder(128);
}

You must pass a reference to a ListView when creating a new player class, and this is the connection between the form and the class. All other variables are set appropriate for the needs.

Close and Open methods
C#
public void Close()
{
    Pcommand = "close MediaFile";
    mciSendString(Pcommand, null, 0, IntPtr.Zero);
}


public bool Open(string sFileName)
{
    Close();
    // Try to open as mpegvideo 
    Pcommand = "open \"" + sFileName + 
               "\" type mpegvideo alias MediaFile";
    error = mciSendString(Pcommand, null, 0, IntPtr.Zero);
    if (error != 0)
    {
        // Let MCI deside which file type the song is
        Pcommand = "open \"" + sFileName + 
                   "\" alias MediaFile";
        error = mciSendString(Pcommand, null, 0, IntPtr.Zero);
        if (error == 0)
            return true;
        else
            return false;
    }
    else
        return true;
}

The Close method does nothing special, it just closes the device that's open. Even if trying to close an already closed device, nothing bad will happen.

The Open method takes a string as input (argument), and it will try to open the given file path. But first, it closes the device just to be sure that there won't be two songs running at the same time. In the next step, it tries to open the file as type mpegvideo. The mciSendString method will return 0 for a successful operation, and a non-zero value for a failed operation. When an operation fails, in the next step, the MCI device decides which type the file is. Even if this operation fails, it returns false. For all other solutions, it returns true.

Play method
C#
public bool Play(int track)
{
    if (Open(playlist.Items[track].SubItems[1].Text))
    {
        Pcommand = "play MediaFile";
        error = mciSendString(Pcommand, null, 0, IntPtr.Zero);
        if (error == 0)
        {
            NowPlaying = track;
            return true;
        }
        else
        {
            Close();
            return false;
        }
    }
    else
        return false;
}

The Play method takes an integer value as argument that represents the song in the playlist. It passes the file path of the song to the Open method. If it returns true, then the operation was successful and the song was loaded into the MCI device, else it failed to load the song and it will return false and won't play the song. In the next step, it sends the play command and tries to play the song. If MCI returns 0, the operation was successful, it sets the NowPlaying variable to the index of the track that is now playing, and returns 0, else if it doesn't return 0, it closes the device that was successfully opened but couldn't be played, and returns false.

Pause, Stop, Resume methods
C#
public void Pause()
{
    if (Paused)
    {
        Resume();
        Paused = false;
    }
    else if(IsPlaying())
    {
        Pcommand = "pause MediaFile";
        error = mciSendString(Pcommand, null, 0, IntPtr.Zero);
        Paused = true;
    }
}

public void Stop()
{
    Pcommand = "stop MediaFile";
    error = mciSendString(Pcommand, null, 0, IntPtr.Zero);
    Paused = false;
    Close();
}

public void Resume()
{
    Pcommand = "resume MediaFile";
    error = mciSendString(Pcommand, null, 0, IntPtr.Zero);
}

The Pause method first checks if the paused variable is true (if it's true, then the player is paused). Then, it calls the Resume method and sets the paused variable to false (because the player isn't paused anymore). If the player isn't paused, then it checks if the player is playing (I'll explain this method in just a moment). If it's playing, then it pauses the player and sets the paused variable to true.

The Stop method just stops the device, sets the paused variable to false, and closes the open MCI device.

The Resume method just continues with playing when the player is paused, and is called only from the Pause method.

IsPlaying method
C#
public bool IsPlaying()
{
    Pcommand = "status MediaFile mode";
    error = mciSendString(Pcommand, returnData, 128, IntPtr.Zero);
    if (returnData.Length == 7 && 
              returnData.ToString().Substring(0, 7) == "playing")
        return true;
    else
        return false;
}

This method sends the status mode command to the MCI device. The return value is set in the returnData variable. The value in the returnData variable can be playing, paused, open ... and so on, but I only need information whether the device is playing or not. So, it checks if the string length is equal to 7 and if the value in the string is "playing". If so, then it returns true; for all other situations, it returns false.

Song length and current position
C#
public int GetCurentMilisecond()
{
    Pcommand = "status MediaFile position";
    error = mciSendString(Pcommand, returnData, 
                          returnData.Capacity, IntPtr.Zero);
    return int.Parse(returnData.ToString());
}

public void SetPosition(int miliseconds)
{
    if (IsPlaying())
    {
        Pcommand = "play MediaFile from " + miliseconds.ToString();
        error = mciSendString(Pcommand, null, 0, IntPtr.Zero);
    }
    else
    {
        Pcommand = "seek MediaFile to " + miliseconds.ToString();
        error = mciSendString(Pcommand, null, 0, IntPtr.Zero);
    }
}

public int GetSongLenght()
{
    if (IsPlaying())
    {
        Pcommand = "status MediaFile length";
        error = mciSendString(Pcommand, returnData, returnData.Capacity, IntPtr.Zero);
        return int.Parse(returnData.ToString());
    }
    else
        return 0;
}

GetCurrentMilisecond returns the position of the song in milliseconds. It sends the status position command and sets the return message in the returnData variable. The message is a string, so it must be parsed into an integer.

The SetPosition method takes an integer argument that represents the position in milliseconds to that the song should be set. It first checks if the song is playing. If it's playing, then it sends the play from command with the value in milliseconds. Else, it sends the seek command that just moves the position of the song to the given value, but it won't play from there (this is useful if the song is paused or something like that).

The GetSongLength method returns the length of the song in milliseconds. First, it checks if the song is playing; if it's playing, then it sends the status length command and sets the return value into the returnData variable. Then, it must be parsed into an integer.

Volume and Balance
C#
public bool SetVolume(int volume)
{
    if (volume >= 0 && volume <= 1000)
    {
        Pcommand = "setaudio MediaFile volume to " + volume.ToString();
        error = mciSendString(Pcommand, null, 0, IntPtr.Zero);
        return true;
    }
    else
        return false;
}

public bool SetBalance(int balance)
{
    if (balance >= 0 && balance <= 1000)
    {
        Pcommand = "setaudio MediaFile left volume to " + (1000 - balance).ToString();
        error = mciSendString(Pcommand, null, 0, IntPtr.Zero);
        Pcommand = "setaudio MediaFile right volume to " + balance.ToString();
        error = mciSendString(Pcommand, null, 0, IntPtr.Zero);
        return true;
    }
    else
        return false;
}

Both methods take an integer value as argument that represents the volume/balance to that the player should be set. The value can be between 0 and 1000. If the value is in between these values, then the method sends the command and returns true; else it returns false.

GetSong method
C#
public int GetSong(bool previous)
{
    if (Shuffle)
    {
        int i;
        if (playlist.Items.Count == 1)
            return 0;
        while (true)
        {
            i = randomNumber.Next(playlist.Items.Count);
            if (i != NowPlaying)
                return i;
        }
    }
    else if (Loop && !previous)
    {
        if (NowPlaying == playlist.Items.Count - 1)
            return 0;
        else
            return NowPlaying + 1;
    }
    else if (Loop && previous)
    {
        if (NowPlaying == 0)
            return playlist.Items.Count - 1;
        else
            return NowPlaying - 1;
    }
    else
    {
        if (previous)
        {
            if (NowPlaying != 0)
                return NowPlaying - 1;
            else
                return 0;
        }
        else
        {
            if (NowPlaying != playlist.Items.Count - 1)
                return NowPlaying + 1;
            else
                return 0;
        }
    }
}

The method takes a bool argument: true if the player should go backwards, and false if it should go forwards. If shuffle is on, then it first checks if there is only one song in the playlist and returns this one if it's so. If there is more than one song, it returns a random value in between 0 and playlist.Items.Count. But I've butted it all into a while loop to avoid the situation that it returns the same song twice. The rest of the method returns the appropriate values for the situation (if it's set to loop, if it goes forward or backward ...)

mciGetErrorString

I haven't used this method in this class to avoid messages popping up. This would be just annoying to the user. But if you're interested in the error messages:

C#
bool a = mciGetErrorString(error, returnData, 128);

error is the int value that the MCI method returns, and returnData is a StringBuilder that will hold the error message.

Bugs

If you run into any kind of bug, for a swift resolution please log an issue on the GitHub page and I'll try to provide a fix. Here the Link to the Issue section MCIplayer/Issues

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)