AllAPI Network - The KPD-Team

 
Allapi Network
 API-Guide
 ApiViewer

 API List

 
API Resources
 Tips & Tricks
 VB Tutorials
 Error Lookup
 
Misc Stuff
 VB examples
 VB Tools
 VB Links
 Top Downloads
 
This Site
 Search Engine
 Contact Form
 

Donate to AllAPI.net

Using DLLs and The Windows API

Understanding the Multimedia Class

While Visual Basic is great at detaching a programmer from some of the underlying complexities of Windows itself, there are still certain areas in which the API just can't be beaten, multimedia being one of them. For example, the multimedia class you just wrote uses just one API call, mciSendString. Before we take an in-depth look at the code itself, it's probably a good idea to look at this particular API call in a little detail.

The Multimedia Control Interface

Windows itself actually consists of a number of subsystems: separate units within Windows that handle entire areas of functionality. One such area is something called the MCI. MCI stands for Multimedia Control Interface, which provides a device-independent way to use the multimedia features of Windows through code.

Device-independent? OK! In the bad old days of DOS, a programmer writing a video game, for example, would have to cope with every possible standard and type of sound and video cards in order to satisfy the games market. Device-independence, and the device drivers that Windows provides, let you hit any sound card, video card, and so on with the same code, just so long as it's supported by Windows. The MCI provides this layer of independence, putting a neat bunch of functionality between you, the programmer, and the devices which would normally be used to handle multimedia data, namely the video and sound cards.

All this theory is great, but how exactly does the MCI work, and how does it provide this independence? The answer to both questions is that the MCI is responsible for talking to the Windows device drivers, and ultimately the multimedia hardware. You, the programmer, issue commands to the MCI using the API call mciSendString. These commands are then translated into calls to the appropriate Windows device driver, something that you no longer have to worry about. To put it into VB terms then, the MCI is a built-in Windows class, which we have essentially super-classed in the earlier example.

Hang on a minute-programmers issue commands to the MCI. A little strange? Well, yes. Normally, when you deal with API calls, you are actually calling the subroutines and functions embedded in Windows in order to do something. The MCI really is an independent object. It can be programmed, and has its own programming language. When you use mciSendString you are in fact programming the MCI-just as easily as if you were firing VB commands out of the debug window.

Using mciSendString

The format of mciSendString is quite simple

<ResultCode> = mciSendString("<Command>", <ReturnString>, <ReturnLength>, <CallbackHandle>)

This needs a bit of explanation. The <ResultCode> is a long integer, and varies depending on the command issued. The <Command> part (notice it is in quotes, so it's passed as a string literal) is the command you're sending to the MCI-such as Play to play a file, Open to open one, and so on. We'll look at exactly which commands the MCI understands later on, but for now let's cover the rest of the parameters.

Some MCI commands actually return a string value to your program. The MCI Status command, for example, can return a string telling your code whether a file is Stopped, Paused, Playing and so on. The string variable you place here will contain that return string.

The API call needs to know just how much data it can put in this string variable, so the next parameter passed is a number that is the length of the string. For this reason, if you are issuing a command to the MCI which returns a string, you must pass a fixed length string variable to the call and tell the mciSendString just how long that string is.

Dim sReturnString As String * 255

Dim nReturn As Long

nReturn = mciSendString("status waveaudio mode", sReturnString, 255, 0)

Don't worry about what this specific command does at this point, but notice the use of the fixed length string. Adding the * 255 to the declaration of sReturnString tells VB to fix its length to 255 characters.

The final parameter, the <CallbackHandle>, is something a bit specialist, which we're not going to be using in this book. However, to satisfy your curiosity, here's a brief description of what it means.

Callback Functions in Visual Basic

Callback functions really only used to apply to those writing code in C, C++, Delphi, or some other low-level compiled language-and not Visual Basic. However, as of version 5, Visual Basic now lets you use callbacks in your code, without requiring special add-ins that were the only way to make it work in earlier versions.

When you use API subroutines and functions normally, your code has no way of knowing what's going on while that routine is running. You have to wait until it ends, then examine the return value. The principle idea of a callback is that an API function can call routines that are inside your Visual Basic code, while the API routine is running.

To do this, you have to create a Public function inside a VB code module, which has the correct number and type of parameters-as expected by the API routine. Then, when you call the API routine, you send it a pointer (the address in memory) of your VB callback function. To do this, you make use of the new Visual Basic AddressOf operator:

nResult = SomeAPIFunction(ParamOne, ParamTwo, AddressOf MyCallback)

As the API routine executes, it will call your VB function and send it the relevant parameters. This is often used to update status bars, get lists of system fonts, and other varied tasks.

As I said earlier, we won't be covering callbacks in this book. They provide an extra layer of complexity in your code, and even more ways to crash your system. However, the Visual Basic help files do give some examples if you'd like to experiment further.

Opening the Media File

Before you can tell the MCI what to do, you have to tell it which file you want to do it on. This is like using disk files. You must start by sending the Open command before you can do anything else.

The first part of this code should be self explanatory: you tell the Open command the name of the file that you want to open. This is a standard filename like c:\video.avi.

Open <filename> Type <typestring> Alias <aname>

...

...

'Issue commands to do something to the file

...

...

Close <aname>

After the Type keyword, you need to tell Windows what kind of file you are dealing with. The standard Windows ones are WaveAudio for WAV files, AVIVideo for AVI files and Sequencer for MID files.

Finally, you can tell the MCI to give the file you just opened a name, an Alias, which you will use from now on to refer to the open file. It's rather like naming a variable, in that the name can be almost anything you want. For example:

Open c:\video.avi Type AVIVideo Alias Peter

If you sent this to the MCI with MCISendString, it would tell the MCI to open a file called c:\video.avi as a Microsoft video file and that, in future MCI commands, we'll refer to this file using the name Peter.

Once opened, normal MCI commands can be issued using the alias to play the file, stop it, pause it, find out its status and so on. For example:

Play Peter

Pause Peter

Stop Peter

There are literally hundreds of combinations of MCI commands that you can use, and we'll take a look at the most common a little later.

Once you have done your stuff with a file, you need to close it down by sending the Close command, followed by the alias of the file you are dealing with.

nReturn = mciSendString("Close Peter", "",0,0)

Let's take a look at how the code does what it does now, and see more of what our new class can do.

Try It Out - Displaying Status and Position for a Multimedia File

1 Open the TestMM.vbp project we just created if it isn't already open.

2 We're going to add some controls to see how the Status and Position properties of our MMedia class can be used. Add a ProgressBar, a Label, and a Timer control to your form, so it looks like this:

If you can't find the progress bar in your toolbox, add it by selecting Components from the Project menu, and check the Microsoft Windows Common Controls 5.0 option box.

3 Open the properties window for the Timer control, and set its Enabled property to False, and its Interval property to 500. Clear the caption for the label control at the same time.

4 Double-click on the Load and play a file button to open the code window for its Click event. Add these highlighted lines to the code:

Private Sub Command1_Click()

...

'other existing stuff here

...

If CommonDialog1.Filename <> "" Then

Multimedia.Wait = False

Multimedia.mmOpen CommonDialog1.Filename

ProgressBar1.Value = 0

ProgressBar1.Max = Multimedia.Length

Timer1.Enabled = True

Multimedia.mmPlay

End If

End Sub

5 Back in the form, double-click the Timer1 control to open the code window for its Timer event. Add this code to it:

Private Sub Timer1_Timer()

ProgressBar1.Value = Multimedia.Position

Label1 = "Status: " & Multimedia.Status

If ProgressBar1.Value = ProgressBar1.Max Then

Multimedia.mmClose

Timer1.Enabled = False

End If

End Sub

As it stands now, we have a minor problem. We defined the variable that refers to instance of our MMedia class within the Command1_Click() event routine. Now we want to refer to it from our Timer1_Timer() event routine as well.

6 In the Click event for the command button, select the line that declares the Multimedia variable, press Ctrl-X to cut it to the clipboard. This removes it from the Command1_Click() event routine. Then select (General) in the top left drop-down list in the code window, and hit Ctrl-V to paste it into the General Declarations section, where it will be available to all the code in this form.

7 OK, we're ready to go. Click the Load and play a file button, and select a multimedia file as before. For a change this time, we've chosen one of the 'Welcome to Windows 95' videos from the Windows CD-ROM.

8 You can see the progress bar now shows how far through the file we are, and the status-in this case playing. When the video is finished, you'll see this change to stopped.

How It Works

We'll use this example to show you in more detail how some parts of the MMedia class work, and how we've used them. Firstly, the code that runs when you click the Load and play a file button.

We added four lines to the routine you saw in the earlier example. These are all concerned with setting up the controls on the form, and the properties of the Multimedia object, before we start playing the file.

If CommonDialog1.Filename <> "" Then

Multimedia.Wait = False

Multimedia.mmOpen CommonDialog1.Filename

ProgressBar1.Value = 0

ProgressBar1.Max = Multimedia.Length

Timer1.Enabled = True

Multimedia.mmPlay

End If

Our MMedia class, and hence our Multimedia object, has a property named Wait. This determines if the code in our VB program will continue to run (multitask) while the file is playing, or just stop and wait for it to finish, like it did in the earlier example. The mmPlay method of the class, which we will shortly use to start the file playing, looks at the value of a private variable in the class named bWait. If it's True, it includes the command wait in the mciSendString call:

Public Sub mmPlay() 'in the MMedia class

Dim nReturn As Long

If sAlias = "" Then Exit Sub

If bWait Then

nReturn = mciSendString("Play " & sAlias & " Wait", "", 0, 0)

Else

nReturn = mciSendString("Play " & sAlias, "", 0, 0)

End If

End Sub

And how does bWait get set to the correct value? Remember from our discussions on class modules, that we can supply property routines which allow the values of the internal variables to be set and read just like a normal Visual Basic control's properties:

Property Get Wait() As Boolean

'Routine to return the value of the object's wait property.

Wait = bWait

End Property

Property Let Wait(bWaitValue As Boolean)

'Routine to set the value of the object's wait property

bWait = bWaitValue

End Property

The next step is to open the file we want to play. To do this, we use the MMedia class' mmOpen method.

Opening the File

First off, we declare a couple of local variables to hold temporary values. We'll see what these are for shortly. Then there's a lot of code devoted to building up the command string, before we send it to the MCI.

Public Sub mmOpen(ByVal sTheFile As String)

Dim nReturn As Long

Dim sType As String

If sAlias <> "" Then

mmClose

End If

Select Case UCase$(Right$(sTheFile, 3))

Case "WAV"

sType = "Waveaudio"

Case "AVI"

sType = "AviVideo"

Case "MID"

sType = "Sequencer"

Case Else

Exit Sub

End Select

sAlias = Right$(sTheFile, 3) & Minute(Now)

If InStr(sTheFile, " ") Then sTheFile = Chr(34) & sTheFile & Chr(34)

nReturn = mciSendString("Open " & sTheFile & " ALIAS " & sAlias _

& " TYPE " & sType & " wait", "", 0, 0)

End Sub

First the mmOpen routine checks a class level/module level variable called sAlias.

If sAlias <> "" Then

mmClose

End If

Whenever you deal with the MCI, it's a good idea to assign aliases to each file you have open. Here, the MMedia class works out a name for the alias for you and stores it in sAlias. When you next go to open a file with mmOpen, or set the file name property, the code can check this and call another routine which closes the first file down. Closing multimedia files down when they are no longer needed frees up memory and speeds up playback, so it's always a good idea.

A familiar Select Case construct is then used to figure out the file type. When you open a file with the MCI, you need to tell it what type of data the file holds. The Select Case construct here does that, storing the MCI type name in the sType variable declared at the start of the routine.

Select Case UCase$(Right$(sTheFile, 3))

Case "WAV"

sType = "Waveaudio"

Case "AVI"

sType = "AviVideo"

Case "MID"

sType = "Sequencer"

Case Else

Exit Sub

End Select

At this point, any previously opened file has been closed, and the type name of the new file has been stored in sType. All that remains is to decide on an alias for the file, and then open it. Aliases must be unique, since the MCI can cope with having more than one file open-and even playing. Since we don't want to force unnecessary complexity on the user of the class, it makes sense if the class decides on its own alias.

sAlias = Right$(sTheFile, 3) & Minute(Now)

It does this by taking the right three characters of the file name and appending the minutes part of the current system time. So if it was 16:15 when you opened c:\video.avi, then the alias the class would come up with would be AVI15. The newly calculated alias is then stored in the sAlias module level variable that we checked when the procedure first started. The value in sAlias is then used throughout the rest of the module whenever we need to do something to the open file-like play it.

So, armed with the file type in one variable, the alias in a second, and the file name in a third, we can finally send the Open command to the MCI. The only fly in the ointment is the need to surround file names which contain spaces with quotation marks (") first:

If InStr(sTheFile, " ") Then sTheFile = Chr(34) & sTheFile & Chr(34)

nReturn = mciSendString("Open " & sTheFile & " ALIAS " & sAlias & " TYPE " & sType & " wait", "", 0, 0)

The command sent is Open, followed by the file name, followed by ALIAS then the new alias (held in sAlias), followed by TYPE and the type, and finally, the word wait.

The wait statement on the end here tells the API not to let our VB code continue running until it has finished loading the file. Without this, on a fast machine with a slow hard disk, problems can occur-you might try to play the file before it has loaded, simply because the code is running a lot faster than the hard disk. Notice that this isn't the same as the Wait property that we looked at earlier, which controls whether our program continues to run while the file is playing, and not while it's loading.

So, now, we can set up the progress bar ready to show the progress of the file's playback. But we need to know how long it is, so that we can set the progress bar's Max value. And of course, we'll need to know the relative position in the file at regular intervals so that we can update it while the file is playing. To recap, here's the Command1_Click event again. So far we've only managed to open the file that we want to play:

If CommonDialog1.Filename <> "" Then

Multimedia.Wait = False

Multimedia.mmOpen CommonDialog1.Filename

ProgressBar1.Value = 0

ProgressBar1.Max = Multimedia.Length

Timer1.Enabled = True

Multimedia.mmPlay

End If

The highlighted code above is the bit that looks after setting up the progress bar. We set it's current Value to 0, which resets it from the last time it was used (i.e. if another file has been played previously). Then we set its Max value to length of the file we've just opened, using the Length property of our Multimedia object.

Getting the File Length

We can use mciSendString to return values, as well as to set them. The Length property of our MMedia class is read-only, because we haven't provided a Property Let routine. We wouldn't expect to be able to set the length of the file anyway. However, we do have a Property Get which returns the length of the currently open file:

Property Get Length() As Single

Dim nReturn As Long, nLength As Integer

Dim sLength As String * 255

If sAlias = "" Then

Length = 0

Exit Property

End If

nReturn = mciSendString("Status " & sAlias & " Length", sLength, 255, 0)

nLength = InStr(sLength, Chr$(0))

Length = Val(Left$(sLength, nLength - 1))

End Property

First sAlias is checked to see if a file has been opened. If it hasn't, then the value returned from the property procedure is 0. If a file has been opened, then the MCI Status Length command is used to find out how long it is. You needn't worry about how the length of the file is measured, since any unit of measurement will be suitable for setting up the progress bar. Just for your interest, though, you can set the unit of measurement to various values, ranging from frames in a video clip to milliseconds in a sound file. However, a complete rundown of the options is a little beyond the scope of what we are trying to achieve here.

The Status command is a rather special MCI command and can be used in conjunction with keywords like Length, Position, and Mode to find out a great deal of information about the current file. It returns this information in a fixed length string variable which is passed to mciSendString after the MCI command. In this example, the return string is called sLength and is declared to be 255 characters long.

Of course, you're not always going to get 255 characters back from the Status command, especially if you only want to know the length of the file. The unused space in the string is filled with the character 0, making it easy for us to use VB's InStr function to find out exactly how long the returned data is, and pull it out.

nReturn = mciSendString("Status " & sAlias & " Length", sLength, 255, 0)

nLength = InStr(sLength, Chr$(0))

Length = Val(Left$(sLength, nLength - 1))

Here, the position of the first Chr(0) is stored in nLength, since InStr returns the character position at which the search data is located, or 0 if it can't find what you are looking for. This now gives us enough information to pull the characters from the left side of the fixed length string, convert them to a number (rather than a string) and return that value ready to go into the Length property.

Getting the Current Position

When the file is actually playing, the Status Position command can be used repeatedly to find out exactly where in the file playback has reached. The code to return the value of the Position property looks strangely familiar:

Property Get Position() As Single

Dim nReturn As Integer, nLength As Integer

Dim sPosition As String * 255

If sAlias = "" Then Exit Property

nReturn = mciSendString("Status " & sAlias & " Position", sPosition, _

255, 0)

nLength = InStr(sPosition, Chr$(0))

Position = Val(Left$(sPosition, nLength - 1))

End Property

The only real difference this time is that instead of sending Status Length to the MCI, we are sending Status Position.

Getting the Current Status

To get the text message for the status, sometimes called the mode, we query the class' Status property. This is done using another Property Get routine, almost identical to the Position property we've just seen. The only differences are that we send the command Status Mode instead of Status Length or Status Position to the mciSendString function. And, of course, we don't need to convert the result to a number, because it's supposed to be a text string:

...

nReturn = mciSendString("Status " & sAlias & " Mode", sStatus, 255, 0)

nLength = InStr(sStatus, Chr$(0))

Status = Left$(sStatus, nLength - 1)

...

OK, let's go back to that Command1_Click event again, and see where we are. So far, we've set the Wait property, opened the file, and set up the progress bar. Before we actually start playing the file, we'll set our Timer control ticking. You'll see what it does in just a moment. Then, finally, we'll play the file by calling the mmPlay method of our Multimedia object:

If CommonDialog1.Filename <> "" Then

Multimedia.Wait = False

Multimedia.mmOpen CommonDialog1.Filename

ProgressBar1.Value = 0

ProgressBar1.Max = Multimedia.Length

Timer1.Enabled = True

Multimedia.mmPlay

End If

Starting the File Playing

Here, again, is the complete code for the mmPlay method of our MMedia class. To execute it, we just need to call it in our Multimedia object. It first checks to see we have a file open by examining the sAlias variable, then if all is well it executes the MCI Play command. We've already seen the way we set and use the Wait property, which controls whether the command is actually Play Wait or just Play.

Public Sub mmPlay() 'in the MMedia class

Dim nReturn As Long

If sAlias = "" Then Exit Sub

If bWait Then

nReturn = mciSendString("Play " & sAlias & " Wait", "", 0, 0)

Else

nReturn = mciSendString("Play " & sAlias, "", 0, 0)

End If

End Sub

Updating the Progress Bar and Label Controls

Our final task is to update the progress bar and the label on the form, as the file is playing. We've already covered all the stuff we need to do this with. Before we set the file playing, we enabled a Timer control with an Interval of 500. So it will fire every half a second. Each time it does, the code in the Timer1_Timer() event routine runs:

Private Sub Timer1_Timer()

ProgressBar1.Value = Multimedia.Position

Label1 = "Status: " & Multimedia.Status

If ProgressBar1.Value = ProgressBar1.Max Then

Multimedia.mmClose

Timer1.Enabled = False

End If

End Sub

You're probably well ahead of me by now. All this code does is take the value of the Multimedia object's Position property and assign it to the ProgressBar control's Value property, updating the display on the form. Then it gets the Multimedia object's current Status property, which is a text string, and pops it into the Label1 control on the form. Remember, this is happening twice a second.

Simply retrieving these properties, of course, runs the Property Get routines we wrote in the class, which in turn use the MCI Status Position and Status Mode commands each time. The only other thing is to stop it all happening when we get to the end of the file. This is done by comparing the progress bar's current and maximum values. When they're the same, it's time to close the file using the Multimedia object's mmClose method, then disable the timer to prevent this routine running again until another file is opened.

Our MCI Commands Summary

To end, here's a list of the MCI commands used in our MMedia class:

Command

Description

Play

Plays a file.

Pause

Pauses playback, ready to start up again at any time.

Stop

Stops a file-you need to seek to a position to continue playback.

Seek

Followed by a number, seeks to that position in the file.

Status Mode

Returns a string indicating what the file is doing (i.e. Stopped, Paused, Playing, Ready).

Status Position

Returns a number indicating the position through the file that playback has reached.

Status Length

Returns the length of the file and helps to put the number returned from Status Position into some meaningful context.

Close

Closes the file and frees up the memory it previously occupied.

The MCI supports a few more commands than this, and also a number of specialized ones for each file format. What I hope to have shown you is how even apparently complex topics, such as multimedia, really aren't that hard if you drop down to the API. Also, that the API itself is not the daunting mother of all nightmares that many VB programmers make it out to be.

Summary

This chapter should have provided you with a glimpse of the power that lies beyond the strict limits of Visual Basic, as defined in the language itself. This is a huge subject, and so what we've done is touch on the main principles of using the Windows API. With power comes responsibility, because once you choose to operate outside the confines of Visual Basic, you have to look after yourself. It pays to develop a deep understanding of all the components of Windows, so you can write Visual Basic programs that are truly well-behaved members of the Windows desktop community.

In our brief tour we covered:

    How Windows and Visual Basic fit together
    The Windows API
    How to use the Windows API to extend the power of Visual Basic
    How to write well-behaved Windows applications

Page [<< PREV] 1 2 3 4
Back to Tutorials - Main

 

 


Copyright © 1998-2007, The Mentalis.org Team - Privacy statement
Did you find a bug on this page? Tell us!
This site is located at http://allapi.mentalis.org/