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.
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.
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.
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))
End
If
End
If
End
Sub
Figure 4: Two overloaded versions
of the Play method are provided; one to play a media file, another to play audio
CDs.
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 (as shown in
Figure
5) to manage
details such as opening and closing the CD door and verifying that an audio CD is
present.
Figure 5: Two overloaded versions
of the Play method are provided; one to play a media file, another to play audio
CDs.
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 Figure 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.
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
Figure 6: The GetStatus method of
the MediaPlayer component lets you retrieve detailed information about the current
music CD, track, or media file.
This function optionally accepts a track number parameter (for retrieving details
about a specific track of an audio CD) and a MediaInfoOption
enumeration value that specifies the kind of media information being requested.
This enumeration is defined here, along with a few necessary private variable declarations:
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. The following code 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 7 shows a sample application that uses the MediaPlayer component.
The component and sample application are both available for download.
Figure 7: The included sample application
demonstrates all you need to get started with the MediaPlayer component.
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
(see
Table 1).
|
|
.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 Arrays
|
Files only
|
|
Supported Functions
|
Synchronous, Asynchronous, Infinite Loop
|
Asynchronous, Pause, Stop, CD Audio (single or multiple tracks), Open/Close CD Door,
Detect Audio CD, retrieve error details, retrieve track details & status
|
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.
One capability the .NET Framework does provide that's beyond the capabilities of
the MediaPlayer component is the ability to play a
.wav
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, and 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.
So, if your needs are simple, you might be able to scrape by with the new audio
capabilities of the .NET Framework version 2.0, but otherwise, the MediaPlayer component
described in this article provides a rich set of features—yet 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 sample code in this article is available for
download.
This article was originally
published in CODE Magazine.