If you've ever tried to find classes in the .NET Framework that allow you to play audio and video, you may have noticed the gaping hole where this functionality should be.

If you think Microsoft has finally gotten around to fixing this glaring omission in version 2 of the framework, you’re only partially correct. While some basic audio capabilities will be provided (as described later in this article,) they still leave a lot to be desired. On the other hand, the free MediaPlayer component provided with this article demonstrates a more feature-rich solution to your multimedia needs-and it’s compatible with all versions of the .NET Framework.

The Multimedia Control Interface (MCI) is an aging (but sturdy) standard implemented by Microsoft to provide a common way to send commands to the dizzying array of audio and video devices supported by Windows. Before this standard, every video card and sound card had their own custom APIs, which made multimedia development quite cumbersome. That functionality is implemented inside the Windows Mutli-Media Dynamic Link Library named WinMM.DLL, which became a standard part of Windows many years ago.

By harnessing the power of the Multimedia Control Interface (MCI) and the Windows API, it’s possible to get around the limitations of the .NET Framework and achieve rich media functionality.

By harnessing the power of the Multimedia Control Interface (MCI) and the Windows API, it’s possible to get around the limitations of the .NET Framework and achieve rich media functionality. Of course this kind of low-level coding comes with its perils, so it’s a good idea to encapsulate such intricacy into a component of its own, as demonstrated here.

Since the .NET Framework does not provide the required functionality you must bypass it and send the commands directly to Windows. Listing 1 shows the declarations needed to implement the MCI’s API functions that the MediaPlayer component requires.

mciSendString is the primary function used by the MediaPlayer component. It will execute all of the multimedia functionality. It accepts carefully formatted strings such as “play CD from 3 to 6,” which plays tracks 3 through 6 of the current audio CD. The MediaPlayer component encapsulates the various string options to provide a more modern, simplified, and error-resistant programmatic interface. This function returns an unsigned integer that will contain zero upon success. If the function call should fail it will return an error number. The mciGetErrorString function accepts the error number and returns details about the error.

The mciSendString function doesn’t recognize long file names, so file names must be converted to their short forms before they are parameterized. The GetShortPathName function provides the conversion of long file names to short file names.

Creating the Component

To create a component in Visual Studio .NET, you first need to create a new Class Library project. In the Solution Explorer, right-click on the project and select “Add Component.” Visual Studio then creates a class filled only with the necessary component designer-generated code. The most notable line is the one that specifies that the class inherits from System.ComponentModel.Component.

If your needs are simple, you might be able to scrape by with the new audio capabilities of the .NET Framework version 2.0.

You’ll need to add a reference to System.Design.dll and System.Drawing.dll to get the required design-time support. Specifically, they support the attribute at the beginning of Listing 2. In this example, the EditorAttribute ensures that the FileName property of the component will permit browsing at design time. That is, an ellipsis button will appear next to the property in the properties window, and clicking it will allow the developer to choose an appropriate media file for the control. Of course the FileName property can instead be set at run time if preferred.

The FileName property in Listing 2 accepts the full path and file name to a media file. It ensures the file has a supported file extension and converts the path to the short version since that’s what the MCI functions support. It also accepts a special file name of “CD” to allow the playing of an audio CD instead of a file.

Since the state of the current media file must be managed carefully, there are several methods and properties that take care of such details, as shown in Listing 3.

The Browsable attribute is specified to ensure the Opened property doesn’t appear in the Properties window at design time, since this property is only relevant at run time.

The Open method ensures a valid media file has been specified, then carefully constructs a valid MCI string to send to the mciSendString API function.

Likewise, the Close method builds the necessary mci string to close the media file.

It’s not necessary for the developer using the MediaPlayer component to open and close a media file since this is handled automatically. The Open and Close methods are provided purely as a means for optimization. Since it can take a significant number of milliseconds to open a media file before playing it, you might choose to open the file in advance for a snappier response when it’s time to play.

Play Time

Of course, the most fundamental functionality of the MediaPlayer component is to actually play the media file. Two overloaded versions of the Play method are provided, as shown in Listing 4.

The first Play method optionally accepts a file name to play. If no file name is provided, it uses the value of the FileName property that should have been set beforehand. After verifying a valid media file has been specified, the required MCI string is constructed and sent as a parameter to the mciSendString function. This causes the media to start playing.

The second Play method is intended for playing audio CDs. It accepts a parameter for the track number to be played, and another Boolean parameter that specifies whether it should stop after that track, or whether following tracks should continue to play once the first one is completed.

Solid audio CD support requires a few other helper methods (shown in Listing 5) to manage details such as opening and closing the CD door and verifying that an audio CD is present.

Another useful method the MediaPlayer component provides is the ability to retrieve information about the current state of any media file. The GetStatus method in Listing 6 can retrieve information about the length of the current CD, the number of tracks, the current position, track length, and the current play mode.

This function optionally accepts a track number parameter (for retrieving details about a specific track of an audio CD) and a MediaInfoOption enumeration that specifies the kind of media information being requested. This enumeration is defined here, along with a few necessary private variable declarations.

#Region " variable declarations "
    Private m_Filename As String
    Private m_Opened As Boolean
    Private m_Type As String = String.Empty
    Public Enum MediaInfoOption
        playmode = 0
        cdlength = 1
        cdnumtracks = 2
        cdcurrentposition = 3
        cdtracklength = 4
    End Enum
#End Region

The final few methods provide less exciting functionality that is nonetheless necessary for any quality media component: The ability to Stop and Pause a track, and the ability to retrieve error details in the case of an unexpected exception. Listing 7 shows the implementation details.

Now that you have all the code for the MediaPlayer component, and have compiled it into a class library DLL, you can add it to your Visual Studio Toolbox by simply dragging it there from Windows Explorer.

Then you can drag one or more of the MediaPlayer components onto any Windows Form. Using the component can be as easy as this:

MediaPlayer1.Play(“c:\SomeSong.mp3”)

Figure 1 shows a sample application that uses the MediaPlayer component. The component and sample application are both available for download.

![Figure 1: The sample application demonstrates all you need to get started with the MediaPlayer component.](https://codemag.com/Article/Image/0601051/Figure 1.TIF)

What’s New in Version 2?

Version 2 of the .NET Framework finally includes some basic support for playing audio files. For example, the following Visual Basic 2005 line of code plays a wave file:

My.Computer.Audio.Play(“c:\MySound.wav”)

Beta 2 of the .NET Framework version 2 suffers from some serious limitations that will hopefully be improved upon before the final release. For example, the Play method only supports PCM formatted wave files. The framework still lacks support for MP3s, WMA files, or video files of any kind. Therefore the MediaPlayer component included with this article remains a superior way to play media files in most ways.

One capability the .NET Framework does provide that’s beyond the capabilities of the MediaPlayer component is the ability to play a wave file directly from stream (or byte array) instead of a file.

With My.Computer.Audio
  .Play(MyStream, AudioPlayMode.WaitToComplete)
End With

In addition to the synchronous WaitToComplete option, the AudioPlayMode enumeration also allows you to play an audio file asynchronously or to loop the audio file infinitely.

That exhausts the new media capabilities of the .NET Framework version 2.0. As of beta 2, there is no support for pausing a currently playing audio file or for playing music CDs. There is also no support for retrieving any kind of information about a currently playing track, such as the remaining play time or total length of the track. Discouragingly, the beta 2 functionality also lacks support for playing more than one media file at a time. However, the free MediaPlayer component included with this article has none of these limitations. And since the source code is included, if you do run into any limitations you can extend it to support virtually anything you need.

In Conclusion

If your needs are simple, you might be able to scrape by with the new audio capabilities of the .NET Framework version 2.0.

The MediaPlayer component detailed in this article provides a rich set of features, and yet it only scratches the surface of what the MCI can provide. You might choose to extend the control by adding in capabilities to record, fast forward & rewind, play videos within specific windows, etc. You might also turn the component into a control with a user interface of your choosing.

The source code for the MediaPlayer control and the associated sample application are available for download in Visual Studio 2003 format. You can use the MediaPlayer.dll from any version of the .NET Framework.

Listing 1: Declare the required Windows API functions

    Private Declare Function mciSendString Lib _
    "winmm.dll" Alias "mciSendStringA" _
        (ByVal lpstrCommand As String, _
        ByVal lpstrReturnString As String, _
        ByVal uReturnLength As Long, _
        ByVal hwndCallback As Long) As UInt32

    Private Declare Function mciGetErrorString 
    Lib "winmm" Alias _
        "mciGetErrorStringA" _
        (ByVal dwError As UInt32, _
        ByVal lpstrBuffer As String, _
        ByVal uLength As Long) As Long

    Private Declare Function GetShortPathName _
    Lib "kernel32" Alias "GetShortPathNameA" _
        (ByVal lpszLongPathName As String, _
        ByVal lpszShortPath As String, _
        ByVal cchBuffer As Long) As Long

Listing 2: The FileName property ensures a supported media file has been selected, and makes the necessary preparations for playing it

<EditorAttribute(GetType
(System.Windows.Forms.Design.FileNameEditor), 
GetType(System.Drawing.Design.UITypeEditor))> _
  Public Property FileName() As String
    Get
      Return m_Filename
    End Get
    Set(ByVal Value As String)
      If Not Value Is Nothing Then
        If Value.Trim.ToUpper = "CD" Then
          If m_Filename <> "cd" Then
            If m_Opened Then Me.Close()
            m_Filename = "cd"
            m_Type = String.Empty
          End If
        Else
          'make sure the file exists
          If IO.File.Exists(Value) Then
            'translate to short filename format
            Dim lretval As Long
            Dim strShortFileName As String = Space(256)
            lretval = GetShortPathName(Value, strShortFileName,256)
            strShortFileName = strShortFileName.Replace(Chr(0), _
              Chr(32)).Trim.ToLower

            'if they have changed the filename then...
            If m_Filename <> strShortFileName Then
              'close any file that might still be open
              If m_Opened Then
                Me.Stop()
                Me.Close()
              End If
              m_Filename = strShortFileName

              'Figure out what kind of file it is
              If Me.FileName.EndsWith(".wav") OrElse _
                  Me.FileName.EndsWith(".mp3") OrElse _
                  Me.FileName.EndsWith(".wma") Then
                m_Type = "waveaudio"
              Else
                If Me.FileName.EndsWith(".mid") OrElse _
                    Me.FileName.EndsWith(".midi") Then
                  m_Type = "sequencer"
                Else
                  If Me.FileName.EndsWith(".avi") Or _
                  Me.FileName.EndsWith(".mpg") Or _
                  Me.FileName.EndsWith(".wmv") Then
                    m_Type = "digitalvideo"
                  Else
                    Throw New _
                    IO.FileLoadException("Unrecognized file type")
                  End If
                End If
              End If
            End If
          Else
            Throw New IO.FileNotFoundException
          End If
        End If
      End If
    End Set
  End Property

Listing 3: The opened/closed state of the media file is automatically mangaged by the MediaPlayer component to prevent ugly exceptions

  <System.ComponentModel.Browsable(False)> _
  Public ReadOnly Property Opened() As Boolean
      Get
        Return m_Opened
      End Get
  End Property

  Public Sub Open(Optional ByVal Filename As String = "")
      Dim retval As UInt32

      If Filename <> "" Then
        Me.FileName = Filename
      End If

      If Me.FileName = "" Then
        Dim e As New IO.FileNotFoundException
        Throw e
        Exit Sub
      End If

      Dim s As String
      If Me.FileName = "cd" Then
        s = "open CDAudio alias cd wait shareable"
        retval = mciSendString(s, "", 0, 0)
      Else
        s = "open " & Me.FileName & " type " & m_Type
        s = s & " alias " & Me.FileName & " wait"
        retval = mciSendString(s, "", 0, 0)
      End If

      m_Opened = True
  End Sub

  Public Sub Close()
      If Me.FileName <> "" Then
        Dim retval As UInt32
        Me.Stop()
        retval = mciSendString("close " & Me.FileName, "", 0, 0)
        If retval.ToString <> "0" Then
          Debug.Write(GetMCIErrorString(retval))
        End If
      End If
      m_Opened = False
  End Sub

Listing 4: Two overloaded versions of the Play method are provided; one to play a media file, another to play audio CDs

Public Sub Play(Optional ByVal Filename As String = "")
    Dim retval As UInt32

    If Filename <> "" Then
      Me.FileName = Filename
    End If

    If Me.FileName = "cd" Then
      Me.Play(CByte(0))
    Else
      If Me.FileName = "" Or m_Type = "" Then
        Dim e As New IO.FileNotFoundException
        Throw e
      End If

      If Not m_Opened Then Me.Open()
      retval = mciSendString("play " & Me.FileName, "", 0, 0)
    End If
End Sub

Public Sub Play(ByVal TrackNum As Byte, _
    Optional ByVal AutoStop As Boolean = True)
    Dim retval As UInt32

    Me.FileName = "cd"
    Me.Open()
    If TrackNum > 0 Then
      retval = mciSendString("set cd time format tmsf wait", _
        "", 0, 0)
      If AutoStop Then
        retval = mciSendString("play cd from " & _
        CStr(TrackNum) & " to " & CStr(TrackNum + 1), _
        "", 0, 0)
      Else
        retval = mciSendString("play cd from " & _
        CStr(TrackNum), "", 0, 0)
      End If
      Debug.Write(GetMCIErrorString(retval))
    Else
      If retval.ToString = "0" Then
        retval = mciSendString("play cd", "", 0, 0)
      Else
        Throw New Exception(GetMCIErrorString(retval))
      End If
    End If
End Sub

Listing 5: Methods to manage audio CDs

Public Function AudioCDPresent() As Boolean
    Dim ret As UInt32
    Dim strReturn As String = New String(CChar(" "), 127)

    ret = mciSendString("status cd media present", _
      strReturn, Len(strReturn), 0)

    strReturn = strReturn.Replace(Chr(0), Chr(32)).Trim.ToLower

    Return CBool(strReturn)
End Function

Public Sub OpenCDDoor()
    Dim retval As UInt32

    Me.FileName = "cd"
    If Not m_Opened Then Me.Open()
    retval = mciSendString("set cd door open", "", 0, 0)
    If retval.ToString <> "0" Then
      Debug.Write(GetMCIErrorString(retval))
    End If
End Sub

Public Sub CloseCDDoor()
    Dim retval As UInt32

    Me.FileName = "cd"
    If Not m_Opened Then Me.Open()
    retval = mciSendString("set cd door closed", "", 0, 0)
    If retval.ToString <> "0" Then
      Debug.Write(GetMCIErrorString(retval))
    End If
End Sub

Listing 6: The GetStatus method of the MediaPlayer component lets you retrieve detailed information about the current music CD, track, or media file

Public Function GetStatus(Optional ByVal action _
    As MediaInfoOption = MediaInfoOption.playmode, _
    Optional ByVal track As Integer = 0) As String

    Dim ret As UInt32
    Dim strReturn As String = New String(CChar(" "), 127)

    Select Case action
      Case MediaInfoOption.cdlength
        ' get length of the cd
        ret = mciSendString("status cd length wait", strReturn, _
          Len(strReturn), 0)
      Case MediaInfoOption.cdnumtracks
        ' get number of tracks on cd
        ret = mciSendString("status cd number of tracks wait", _
          strReturn, Len(strReturn), 0)
      Case MediaInfoOption.cdcurrentposition
        ' get current position on cd
        ret = mciSendString("status cd position", strReturn, _
          Len(strReturn), 0)
      Case MediaInfoOption.cdtracklength
        ' get length of requested track
        ret = mciSendString("status cd length track " _
          & CStr(track), strReturn, Len(strReturn), 0)
      Case MediaInfoOption.playmode
        ' get the current mode
        ret = mciSendString("status " & Me.FileName & " mode", _
          strReturn, Len(strReturn), 0)
    End Select

    If ret.ToString <> "0" Then
      Throw New Exception(GetMCIErrorString(ret))
    Else
      strReturn = strReturn.Replace(Chr(0), Chr(32)).Trim.ToLower
      Return strReturn
    End If
End Function

Listing 7: You can Stop or Pause media files in the middle of a song, and retreive details about any unanticipated errors


Public Sub Pause()
    If Me.FileName <> "" Then
      Dim retval As UInt32
      retval = mciSendString("pause " & Me.FileName, "", 0, 0)
    End If
End Sub

Public Sub [Stop]()
    If Me.FileName <> "" Then
      Dim retval As UInt32
      retval = mciSendString("stop " & Me.FileName, "", 0, 0)
      retval = mciSendString("seek " & Me.FileName & _
        " to start", "", 0, 0)
    End If
End Sub

Private Function GetMCIErrorString(ByVal ErrorCode As UInt32) _
    As String

    Dim buffer As String = Space(256)
    mciGetErrorString(ErrorCode, buffer, Len(buffer))
    Return buffer.Substring(InStr(buffer, _
      vbNullChar) - 1)
End Function

Table 1: While version 2 of the .NET Framework (beta 2) provides handy support for playing audio streams and byte arrays, the MediaPlayer component outdoes the standard framework functionality in virtually every other way.

.NET Framework 2.0 Media Functionality MediaPlayer Component
Supported File Types WAV only (must be PCI Formatted)WAV, MP3, WMA, AVI, MPG, WMV, MID, MIDI, Music CDs
Input Types Files, Streams, Byte ArraysFiles only
Supported Functions Synchronous, Asynchronous, Infinite LoopAsynchronous, Pause, Stop, CD Audio (single or multiple tracks), Open/Close CD Door, Detect Audio CD, retrieve error details, retrieve track details & status