MP3 Player Dashboard
Volume Number: 17
Issue Number: 3
Column Tag: Multimedia
Putting an MP3 Player in the Dashboard
By Ron Davis
Adding Code To Play MP3 Files Into The PowerPlant
Dashboard Example
To me, one of the coolest things in programming is when something that someone else
wrote works easily. You make one call and something you know took a lot of doing just
happens, as advertised. Unfortunately this is a rare thing in the world of the
programmer, but in this article you'll see one of those things happen, thanks to the
QuickTime team. We'll see how to write an application that plays MP3 files, with an
absurdly small amount of code.
One caveat before we start. There is more than one way to play an MP3 file from code
on the Mac; this is just one. I recently read a comment in an article in the Game
Developer Magazine that is relevant here, "Fast, cheap, good - pick two." This article
will show you how to do it fast and cheap. My personal opinion is that in most cases it's
still pretty good, but there is a problem with QuickTime playback on cooperative
operating systems that causes skipping in some cases. The implementation this article
uses will skip, especially when it is in the background and you are doing other things
in the front. The good news is that it won't skip under Mac OS X, and we'll be writing a
Carbon application.
Starting with the PowerPlant Dashboard
We're going to use Metrowerk's application framework PowerPlant to implement this
application. Why PowerPlant? Because I like it and it gives us a starting place. The
starting place is the Appearance Stationary. Create a new Project. Click the "Mac OS
PowerPlant Stationary" wizard. Tell CodeWarrior where you want to save. When the
Stationary window comes up, expand Carbon, and select Appearance. CW will then
create a folder on your drive with the Dashboard application in it. Dashboard is a
simple application with one window. It is this window we are going to turn into our
MP3 player's controller.
Let's start out in Constructor and make our controls. Open the AppResources.ppob file.
Double click "Appearance Window". Delete all of the objects in the window. Now add 3
LCmdBevelButtons, "Play", "Stop", "Pause". We're going to use LCmdBevelButtons so
we don't have to add listening code to our class. Instead, we can just hook into the
Command handling mechanism already in place. When configuring each button, you
need to change three fields: the Pane ID, Title, and Command Number. When you get
back to the project, you will need to add constants for these commands. Table 1 shows
the values I used.
Title Const Pane ID Command Number
Play cmd_Play 'play' 1000
Stop cmd_Stop 'stop' 1010
Pause cmd_Pause 'paus' 1020
Table 1. Constant and command values.
Figure 1. Controller Window in Constructor.
Tin Constructor, the controller window looks like Figure 1. Since we used
LCmdBevelButtons, we have to add menu items with the command numbers. Add a new
menu in Constructor. Call the menu "Controls." Give it three commands with titles and
command numbers just like the buttons. Don't forget to add the menu to the Menu Bar
(or it won't show up in your application).
At this point, you can run the application and a window will show up with all of the
buttons disabled.
Writing the Code
Now we need to write some code to make our player work. Everything is going to
happen in the CAppearanceApp class. We'll handle opening the MP3 file and the Play,
Stop and Pause controls from here. In order to do this, we are going to have to add a few
routines and instance variables. Open the header for CAppearanceApp. We need to add a
variable that holds the FSSpec of the currently playing MP3 file and a QuickTime
Movie variable of the currently playing MP3. The standard appearance application
doesn't keep track of the window once it creates it, but we need to make sure there is
only one window open, since we can only play one MP3 at a time. For this reason, we
need to add an instance variable to hold the window. This "one MP3 at a time
limitation is ours, not QuickTime's. You could play multiple sound files at once if you
wanted.
QuickTime treats an MP3 just like a movie. As a matter of fact, you could use a movie
controller to play the MP3 and you wouldn't need our command buttons. On the other
hand, you would lose some level of control and you would have to display something on
the screen. Our method will allow us to play an MP3 with only the UI we want, or no UI
at all.
There are two other things that need to be added to the class declaration of
CAppearanceApp. We'll be writing two new methods, ChooseFile and SetUpMP3. Listing
1 shows the complete CAppearanceApp class.
Listing 1: Declaring our custom class
CAppearanceApp,h
class CAppearanceApp : public LApplication {
public:
CAppearanceApp();
virtual ~CAppearanceApp();
virtual Boolean ObeyCommand(
CommandT
inCommand,
void*
ioParam = nil);
virtual void FindCommandStatus(
CommandT
inCommand,
Boolean&
outEnabled,
Boolean&
outUsesMark,
UInt16&
outMark,
outName);
virtual void ChooseFile();
void SetUpMP3( bool
startPlaying );
protected:
virtual void StartUp();
void RegisterClasses();
LWindow* [TOKEN:27991]indow;
FSSpec mMP3File;
Movie mMovie;
CAppearanceApp::CAppearanceApp()
:mWindow(NULL), mMovie(NULL)
bool cantRun = false;
// Register ourselves with the Appearance Manager
if (UEnvironment::HasFeature(env_HasAppearance)) {
::RegisterAppearanceClient();
}
long result;
if (::Gestalt(gestaltQuickTime, &result) != noErr ) {
// put a dialog here that says we need QT
cantRun = true;
} else
::Gestalt(gestaltQuickTimeVersion, &result);
if ( result < 0x0400 )
if ( cantRun )
// you should put up an alert here telling the user why.
ExitToShell();
}
RegisterClasses();
EnterMovies();
}
The first thing we do is initialize our mWindow and mMovie variables to NULL. We are
going to check them later to determine if the window or movie has already been
created, so they had better be NULL the first time we check it.
We need to check for QuickTime. We do this with the Gestalt calls in the center. I went
ahead and checked for QuickTime version as well. If we don't have QuickTime, we exit
the program right here. If we do have it, we register our classes and call EnterMovies.
EnterMovies is the QuickTime initialization call and must be made before calling any
other QuickTime Routines.
Listing 3 contains the new CAppearanceApp::Startup routine. The default behavior of
Appearance is to open a new window at start up. But what does New mean in our app?
This isn't a recording app; there are no new MP3s to be created. So what we are going
to do is stop New from happening at all. While we're at it, we'll make Open happen in
its place, which will let us open an MP3 file and play it.
Listing 3: The start-up routine
CAppearanceApp::StartUp
void CAppearanceApp::StartUp()
ObeyCommand(cmd_Open, nil);
}
Now we need to make sure the New menu command isn't even available to the user and
that the commands for our controls are enabled. Go to FindCommandStatus and change
the cmd_new to cmd_open. Then add cases for the constants we defined for our
commands. When it is done, your FindCommandStatus should look like Listing 4.
Listing 4: Adjusting our menu items
CAppearanceApp::FindCommandStatus
void CAppearanceApp::FindCommandStatus(