Steve C. Orr

Software Engineer, Web Developer, Database Designer
 
  

 


























































































































































LANGUAGES: VB.NET

.NET Framework versions: 1 & 2

Multimedia Control

Playing audio and video is easier in version 2 of the framework, but the basic capabilities available still leave a lot to be desired. Fortunately, the free MediaPlayer component provided here demonstrates a more feature-rich solution to your multimedia needs—and it's compatible with all versions of the .NET Framework.

By Steve C. Orr

This article details a free media player control for windows forms applications.  If you're looking for an ASP.NET media player control, click here.

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 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.

Because the .NET Framework does not provide the required functionality you must bypass it and send the commands directly to Windows. The following code shows the declarations needed to implement the MCI's API functions that the MediaPlayer component requires.

PrivateDeclare 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

Figure 1: Declarations for the required Windows API functions.

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.

(advertisement)

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.

You'll need to add references to System.Design.dll and System.Drawing.dll to get the required design-time support. Specifically, they support the attribute at the beginning of Figure 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.

<EditorAttribute(GetType(System.Windows.Forms.Design.FileNameEditor), _

GetType(System.Drawing.Design.UITypeEditor))> _

  Public Property FileName() As String ns="urn:schemas-microsoft-com:office:office"

    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

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

The FileName property in Figure 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 Figure 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.

  <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

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

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 Figure 4.

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))