Basic Instinct
Volume Number: 19 (2003)
Issue Number: 2
Column Tag: QuickTime Toolkit
Basic Instinct
Developing QuickTime Applications with REALbasic
by Tim Monroe
Introduction
REALbasic is a software development environment published by REAL Software, Inc. It was first introduced in 1998 and quickly made a name for itself as an easy-to-use, visual, rapid application development tool. REALbasic was inspired to some degree by Microsoft's Visual Basic, the de facto standard for the rapid development of small- to medium-sized applications on Windows. In 1999, REALbasic 1.0 received the prestigious Apple Design Award (ADA) for Best New Product; moreover, it placed second that year in two other categories: Most Innovative Product and Best Macintosh User Experience. In 2000, REALbasic placed second once again for Best Macintosh User Experience, and in 2001 it placed second for Best Mac OS X Technology Adoption.
Those are some heady accolades, and they should be sufficient to pique our interest in REALbasic. It happens that I was on the panel of judges that decided the ADAs in 1999, so I've been aware of REALbasic -- and curious about it -- for much of its existence. I didn't get a chance to do any serious work with it until version 3.5, when I set out to see how to build a QuickTime-savvy application using REALbasic. Already in that version the QuickTime support was reasonably well developed; nonetheless, it proved difficult to do even very simple things like save changes to an edited movie. I'm happy to see that the current version, REALbasic 4.5, incorporates many good improvements and fixes to the QuickTime support.
In this article, I want to take a look at using REALbasic to develop QuickTime applications. As in the preceding few QuickTime Toolkit articles, I want to see how to build a multi-window movie playback and editing application. Let's call this application BasicMovie. I also want to see how easy it is to extend our application to handle potentially more complicated tasks. Any software development environment worth its salt ought to allow us virtually unlimited access to the QuickTime APIs. So we'll want to see whether REALbasic can accommodate our needs here.
We're going to restrict ourselves to building a Carbon application, which can run under Mac OS X and under any earlier Macintosh systems that have CarbonLib installed. We won't discuss any Windows issues, even though REALbasic can build a Windows application from the same project files we'll use to assemble the Macintosh version. Currently the REALbasic IDE runs only on Macintosh computers, and there is no capability for two-machine debugging that would allow us to step through the source code of our Windows application. REAL Software recently announced that a Windows IDE is forthcoming, so perhaps we're better off postponing our look at using REALbasic to build QuickTime applications for Windows. Besides, we'll have our hands full just getting BasicMovie to work as desired on the Mac.
REALbasic Overview
REALbasic began life as a shareware product called CrossBasic, written by Andrew Barry. It was intended as an object-oriented evolution of the BASIC language, coupled with an interactive visual interface builder. CrossBasic was acquired by FYI Software and rebranded as "REALbasic" in 1998. FYI Software later renamed itself to REAL Software.
Using REALbasic, we create our application's windows and dialog boxes by dragging instances of the built-in objects from the Tools palette into a window or dialog box. Figure 1 shows the Tools palette in REALbasic version 4.5.
Figure 1: The Tools palette
Here we have access to many of the standard user interface controls and widgets: buttons, pop-up menus, disclosure triangles, radio buttons, check boxes, scrollbars, sliders, list boxes, progress bars, tabbed panels, static text, image panes, edit fields, separator lines, and grouping boxes. The Tools palette also contains representations of timers, TCP/IP sockets, serial connections, sprite drawing surfaces, and 3D imaging panes. Of special interest for us now is the MoviePlayer control, which we'll use to display and play back QuickTime movies.
Once these objects are instantiated in a window or dialog box, we can attach code to those instances that is executed when various events occur. For example, a push button is associated with an Action event, which is triggered when the user clicks the push button. A push button also has other events associated with it, such as the MouseEnter and MouseExit events.
Our code is written in a language called REALbasic. This is not your father's BASIC, with its numbered lines and GOTO statements for flow control. Rather, it's a highly-structured, object-oriented language. Objects have constructors and destructors, as well as a variety of built-in methods and properties. We can define custom methods and properties, and we can subclass existing classes. We'll get plenty of practice working with this language later in this article.
There are several ways we can extend REALbasic beyond its built-in objects and methods. We can access functions provided by the underlying operating system by using the declare statement, which names an external function and indicates the library it's contained in. We can also add libraries of compiled C code to our application by building a REALbasic plug-in. We will investigate both of these extensions later on.
The Project
Let's get started building a QuickTime-savvy REALbasic application. When we launch REALbasic, we'll see two windows (which are both, curiously enough, named "Untitled") and two palettes. One of these palettes is the Tools palette (Figure 1 above) and the other is a Properties palette, which displays the properties of the currently selected object. The initial project window, shown in Figure 2, already contains two items, a window object (named "Window1") and a menu.
Figure 2: The initial project window
The initial document window is shown in Figure 3. It's just a blank window at this point.
Figure 3: The initial document window
Click on the document window. The Properties palette changes to indicate the initial properties of the window (Figure 4).
Figure 4: The properties of the initial document window
In the Properties palette, change the name to "MovieWindow". Now save the project under the name "BasicMovie" in the location of your choice. The title of the project window changes to "BasicMovie.rb", which is the name of the new project file on disk. Figure 5 shows the new project window.
Figure 5: The updated project window
Adjusting the Build Settings
Now we need to set the name of the application we're going to build. Select the "Build Settings..." menu item in the File menu. The dialog box shown in Figure 6 is displayed.
Figure 6: The Build Settings dialog box
Now choose the "Macintosh Settings" panel using the pop-up menu and set the application name as shown in Figure 7. Click OK.
Figure 7: The Build Settings dialog box
Already we can build and run the application. Select "Build Application" in the File menu. REALbasic will build the application and then switch to a Finder window in which the newly-built application is highlighted. (You can disable this default behavior, if you prefer.) Run the application. A blank document window is displayed, which we can move around on the screen, minimize, or close. The only application-specific menu item that's enabled is the Quit item, which works as expected.
We can streamline this process by running the application within the REALbasic IDE itself. Choose the Run menu item in the Debug menu. The application runs just as it did previously. The only visible difference is that the application menu is named "REALbasic", not "BasicMovie". When running in the IDE, we can set breakpoints in our code, step through functions and methods, and watch the values of our variables.
Setting Up the Menus
Let's set up our application menus. Double-click the Menu object in the main project window. The Application Menu window opens (Figure 8).
Figure 8: The Application Menu window
First, we'll add an item to the Apple menu, to display our application's About box. If we click on the Apple symbol, a text field appears underneath it, as shown in Figure 9. Click in the text field and it will turn blue; at this point, the Properties window will reflect the settings for the selected menu item.
Figure 9: The text field
In the Properties window, set the text of the item to "About BasicMovie" and set the name to "AppleAboutBasicMovie", as shown in Figure 10.
Figure 10: The About BasicMovie menu item properties
In the same way, add the standard menu items to the File menu: New, Open, Close, Save, and Save As. (Be sure to remove the "..." character from the name of the Save As menu item.) Figure 11 shows the settings for the New item in the File menu; notice that we can specify the command-key equivalent in the Behavior pane.
Figure 11: The New menu item properties
The Edit menu already contains most of the usual items: Undo, Cut, Copy, Paste, and Clear, together with their standard keyboard equivalents. All we need to do here is add a separator line and the "Select All" and "Select None" items, with Command-A and Command-B as shortcuts. Notice that REALbasic automatically names these two new items EditSelectAll and EditSelectNone.
Finally, let's add a new menu named "Movie". Here's where we'll put our application-specific menu items. For this article, let's add four menu items that mirror items in QuickTime Player's Movie menu, as shown in Figure 12.
Figure 12: The Movie menu items
Once again, let's run the application in the IDE by choosing Run from the Debug menu. The File, Edit, and Movie menus now appear, but they and all their items are still disabled, as we can see in Figure 13.
Figure 13: The disabled menus and menu items
Adding an Application Object to the Project
Clearly, we need to enable and disable the menus and their items according to the state of the application and any open movie windows. The New and Open menu items in the File menu should always be enabled, as should the "About BasicMovie" item in the Apple menu. To enable these items, which do not depend on the state of any open movie windows, we need to add an application object to our project. This object will receive events that pertain to the state of the application (such as its becoming active or inactive) and will handle all menus that do not apply to specific movie windows.
To add an application object, select "New Class" from the File menu. In the Properties window, set the name to "App" and the superclass to Application. The project window now looks like the one in Figure 14.
Figure 14: The project window with the application object
Double-click on the App object in the project window; a window named "Code Editor" appears (Figure 15). On the left side of the window is a Browser pane, which groups events and objects into six categories.
Figure 15: The Code Editor window for the application object
Click on the disclosure triangle of the Events category; we'll see the nine types of events that can be sent directly to our application object (Figure 16).
Figure 16: The events for an application object
Now select the EnableMenuItems event and add three lines of code so that the right side of the window contains the code in Listing 1.
Listing 1: Enabling application-wide menu items
EnableMenuItems
Sub EnableMenuItems()
FileNew.Enable
FileOpen.Enable
AppleAboutBasicMovie.Enable
End Sub
This says that EnableMenuItems is a subroutine (that is, does not return a value to the caller) with no parameters that calls the Enable method on the three objects that represent our menu items. If we run BasicMovie now, we'll see that the File menu is enabled, along with the New and Open menu items. In addition, the "About BasicMovie" item is enabled in the Apple menu. None of these menu items does anything when we select it, however. Let's change that.
Adding the About Box
Let's add an About box to the application. Select the project window and choose "New Window" from REALbasic's File menu. Reconfigure the properties of the new window as shown in Figure 17. Notice that the name of this window is "AboutBox".
Figure 17: The About box properties
Drag a Canvas object from the Tools palette into the new window and set the backdrop to be our standard penguin image (which we have added to the project file using the Import menu command -- or by just dragging the image file into the project window). The Canvas object should have the properties shown in Figure 18.
Figure 18: The Canvas properties
Next add a static text item and a button to the window; reconfigure them so that the About box window looks like Figure 19.
Figure 19: The About box layout
One nice feature of REALbasic is the ease with which we can associate help tags with objects in windows. You may have noticed (in Figure 17) that we specified some text for the help tag of the Canvas we added to the About box. When we eventually display the About box and the user rests the cursor over the penguin image, the help tag appears, as seen in Figure 20.
Figure 20: The About box with a help tag
But how do we display the About box? Select the App object in the project window once again and double-click it to open its Code Editor window. Now select "New Menu Handler..." in the Edit menu. Select "AppleAboutBasicMenu" in the pop-up menu (Figure 21) and click OK.
Figure 21: Specifying a new menu handler
A new item will appear in the "Menu Handlers" category in the Browser pane. Select that new item and add these two lines of code to the code pane:
dim w as AboutBox
w = New AboutBox
Henceforth, when the user selects the "About BasicMovie" menu item, this menu handler will be called and will display the About box. All that remains is to make sure that the About box is dismissed when the user clicks the OK button (or types a keyboard equivalent). To do this, double-click the OK button in the About box and add these two lines of code to the code pane for PushButton1 (the default name of that button):
self.Close
EnableMenuItems
This tells the window containing the push button to close itself and then to update the state of the application's menus. (Without the call to EnableMenuItems, some of our application's menus might remain disabled.) Once again, run the application and verify that the About box works as expected.
REALbasic's Movie Classes
We've already got a good taste of what it's like to work with REALbasic. We've added some menus and menu items to our menu bar, and we've added some code to the application object to enable some of those menu items. We've added a new window to our application (the About box) and we've attached some code to a push button (to dismiss the About box). Let's move on to consider the QuickTime support offered by REALbasic.
In fact, REALbasic offers quite an impressive array of built-in QuickTime services. Most importantly, it supports a MoviePlayer control that allows us to open and display QuickTime movies, with or without a movie controller bar. It also supports a NotePlayer control that we can use to play musical notes (using a part of QuickTime called the QuickTime Music Architecture). REALbasic provides a way to create a QuickTime movie from two images and a QuickTime video effect, and it provides classes that we can use to create tracks and movies from scratch. There is even a class that allows us to read and write a movie's user data.
It would be nice to illustrate each of these capabilities, but our focus in this article is to see how to use REALbasic to open move files and play their movies in windows on the screen. As a result, we'll work only with the MoviePlayer control and the user data classes.
Adding the MoviePlayer Control
It's dead simple to turn our empty document window into a movie window: just drag a MoviePlayer control from the Tools palette into the document window and then resize it so that the control completely fills the window's content area. Figure 22 shows our document window.
Figure 22: The document window with a MoviePlayer control
Set the MoviePlayer control's properties as shown in Figure 23. Notice that the control's name is "mMoviePlayer".
Figure 23: The MoviePlayer control properties
If we simply wanted to play a particular QuickTime movie, we could import it into the project (in the same way we imported the penguin picture) and then select that movie in the Movie pop-up menu in the "Initial State" pane of the Properties window. But that's not how we want BasicMovie to work; rather, we want the user to open a movie file using the Open menu item (which displays the standard file-opening dialog box) or by dropping the movie file onto the application's icon. So we need to configure our application so that no movie window is opened at application launch time. We do this by selecting the "Project Settings..." menu item in REALbasic's Edit menu. Use the settings shown in Figure 24.
Figure 24: The BasicMovie project settings
Setting Openable File Types
Next we need to tell REALbasic what kinds of files our application can open. As usual, we want BasicMovie to be able to open QuickTime movie files and Flash files. Select "File Types..." from the Edit menu and then click the Add button in the dialog box. Add a file type whose name is "video/quicktime" and whose type and creator are 'MooV' and 'TVOD'. Add another file type whose name is "video/Flash" and whose type and creator are 'SWFL' and 'SWF2'. Figure 25 shows the resulting dialog box.
Figure 25: The openable file types
The name here is actually arbitrary; all that really matters is the file type, creator, and filename extensions. REALbasic is nice enough to include a large number of predefined MIME types in a pop-up menu. If these MIME type names scare you, you're free to use other names.
Opening Movie Files
We are now ready to handle the Open menu item in the File menu and the Open Document AppleEvent. Both of these are handled by our application object, App. Open the Code Editor window for App (by double-clicking its name in the project window) and open the Events and Menu Handlers categories. Select the OpenDocument event; we see the following skeleton:
Sub OpenDocument(item As FolderItem)
End Sub
The OpenDocument subroutine is passed an object of type FolderItem, which represents an item in the file system (that is, a file, folder, or application). In our case, it will represent a movie file, which we want our application to open and display in a movie window. Listing 2 shows how we'll handle the Open Document event.
Listing 2: Handling the Open Document AppleEvent
OpenDocument
Sub OpenDocument(item As FolderItem)
Dim result As Integer
result = OpenMovieInWindow(item)
End Sub
Nothing special here: we just pass the FolderItem to an application-defined method, OpenMovieInWindow (which we'll consider in a moment).
Now select the FileOpen menu handler and add code so that it looks like Listing 3.
Listing 3: Handling the Open menu item
Action
Function Action As Boolean
Dim item As FolderItem
Dim result As Integer
item = GetOpenFolderItem("video/quicktime;video/Flash")
if item <> nil then
result = OpenMovieInWindow(item)
end if
End Function
Here we call the REALbasic method GetOpenFolderItem, which presents the standard Navigation Services file-opening dialog box and filters out all files except QuickTime movie files and Flash files. (The file types specified in the GetOpenFolderItem call must be already specified in the File Types dialog box, as above). If the user selects a file, a FolderItem representing it is returned in as the function result; otherwise, nil is returned.
So what does OpenMovieInWindow need to do? First of all, it needs to create a new movie window:
Dim w As MovieWindow
w = New MovieWindow
Recall that we haven't specified a movie to display in the movie window. We can get a movie object from the folder item, however, by executing its OpenEditableMovie method:
Dim em As EditableMovie
em = item.OpenEditableMovie
REALbasic provides several classes for working with QuickTime movies. The Movie class represents a QuickTime movie that can be displayed to the user and played back, but which cannot be edited. Since we want BasicMovie to be able to edit movies in the standard ways, we'll use the EditableMovie class, which is a subclass of the Movie class. EditableMovie provides methods we'll use to cut, copy, and paste movie segments and to save a changed movie into a movie file.
Now that we've got an editable movie, we can assign it to the movie control like this:
w.mMoviePlayer.movie = em
We can set the title of the movie window to the name of the movie:
w.Title = item.Name
And we can resize the movie window to exactly contain the movie, by executing a method defined on the window object:
w.SizeWindowToMovie
We'll consider the SizeWindowToMovie function later.
The last thing we need to do is keep track of the FolderItem and EditableMovie objects associated with the movie window. We'll do this by adding two custom properties to the movie window object. Select the movie window Code Editor window and then choose "New Property" from the Edit menu. A sheet drops down in which we can declare our new property (Figure 26).
Figure 26: The New Property dialog box
Notice that we've unchecked the Private check box, so that other objects can access this property. To keep track of the editable movie, add a property "eMovie as EditableMovie".
Configuring a Movie Window
We've opened a QuickTime movie file and assigned the movie in it to a REALbasic MoviePlayer control. The movie window is not yet visible, however, since we need to do a little more configuring so that it works as desired. First, even though we've opened the movie as an object of type EditableMovie, we still need to tell the MoviePlayer control that the movie is editable, like this:
w.mMoviePlayer.EditingEnabled = true
Next, we want to make sure that the movie controller bar contains a grow box, so that the user can resize the movie window. To my knowledge, however, REALbasic does not provide any built-in methods for showing or hiding the grow box, and there's no corresponding property associated with the MoviePlayer control. Are we therefore doomed to have movie windows that can't be resized? Happily, no. REALbasic provides several ways for us to call code that is external to REALbasic's runtime engine. First, we can write a library of functions and link it into our compiled application. (This library is called a REALbasic plug-in, and we'll look at how to construct one of them later.) We can also call functions that are contained in some external library, but which is not linked in which our application. For instance, we can call QuickTime functions like MCDoAction or Mac OS Toolbox functions like SetWindowModified. On Windows, we can call external DLLs.
In this second case, where we want to call external functions not linked in to our application, we need to indicate to REALbasic where the functions are located. We can use the declare statement for this, as shown in Listing 4.
Listing 4: Declaring an external function
MCDoAction
Function MCDoAction(mc As Integer, action As Integer,
value As MemoryBlock) As Integer
declare Function MCDoAction Lib "Apple;Carbon;Multimedia"
(mc as Integer, action as Integer, value as Ptr)
As Integer
return MCDoAction(mc, action, value)
End Function
Here, we're declaring that the function MCDoAction is found in the library "Apple:Carbon:Multimedia" and that it takes three parameters of the specified types. We're also telling REALbasic what to call when our code calls MCDoAction.
Once we've included this declaration in our project, we can call MCDoAction as if it were a built-in REALbasic function. The trick, right now, is to get the parameters right. If you recall our C language calls to MCDoAction, you'll remember that the last parameter is of type (void *), a pointer to a block of information that contains the desired value for the specified action. REALbasic does not support C language types, but it does provide a data type called MemoryBlock for accessing an arbitrary block of memory.
For present purposes -- enabling the grow box in the controller bar -- we need to pass a rectangle to MCDoAction that specifies the bounds within which the movie window can grow. A rectangle occupies 8 bytes of memory, so we'll declare a new memory block like this:
Dim rect As MemoryBlock
rect = NewMemoryBlock(8)
Then we need to assign values to the bytes in that block of memory. We do this by specifying the byte offset within the memory block:
rect.short(0) = -32000
rect.short(2) = -32000
rect.short(4) = 32000
rect.short(6) = 32000
This specifies the largest possible rectangle in the QuickDraw coordinate plane. We can now call MCDoAction to enable the grow box:
mc = w.mMoviePlayer.QTMovieController
result = MCDoAction(mc, 25, rect)
Notice that we can get the QuickTime movie controller associated with a MoviePlayer control by executing its QTMovieController method.
We can finish off by making the movie window visible:
w.Visible = true
Adding a Module
What's that hard-coded value "25" in the call to MCDoAction just above? It's the value of the constant mcActionSetGrowBoxBounds, which is defined in the C-language header file Movies.h. It would be nicer if we could just use the symbolic constant, to make our code more readable:
result = MCDoAction(mc, mcActionSetGrowBoxBounds, rect)
To accomplish this, we can add a new module to our project. A REALbasic module is a collection of constants, properties, and methods that are accessible to all objects in the project. Select "New Module" in REALbasic's File menu and rename the new module to "QuickTime" in the Properties window. To add a new constant, select "New Constant..." in the Edit menu. Figure 27 shows how we can define the mcActionSetGrowBoxBounds constant.
Figure 27: The New Constant dialog box
We want to add our declared methods to this new module. To add a new method, select "New Method..." in the Edit menu and fill in the dialog box, as shown in Listing 28.
Figure 28: The New Method dialog box
If you are familiar with using MCDoAction in C code, you'll know that even though the last parameter is declared as a (void *), that parameter is sometimes interpreted as a Boolean value or an integer. For example, when we want to enable keyboard event handling for the movie controller, we execute this code:
MCDoAction(theMC, mcActionSetKeysEnabled, (void *)true);
To do this same thing within REALbasic, we need to define a second version of MCDoAction that accepts integer values, not memory blocks, as the last parameter. Let's call it MCDoActionVal (Listing 5).
Listing 5: Declaring an external function (non-pointer version)
MCDoActionVal
Function MCDoActionVal(mc As Integer, action As Integer,
value As Integer) As Integer
Declare Function MCDoAction Lib "Apple;Carbon;Multimedia"
(mc as Integer, action as Integer, value as Integer)
As Integer
return MCDoAction(mc, action, value)
End Function
This is identical to the original version of MCDoAction except for the type of the third parameter. We'll use MCDoActionVal inside of OpenMovieInWindow, like this:
result = MCDoActionVal(mc, mcActionSetKeysEnabled, 1)
Movie Playback
BasicMovie can now open QuickTime movie files in windows on the screen. REALbasic handles the standard window operations, such as window dragging, minimizing, and closing. And the MoviePlayer control handles user clicks on the buttons in the controller bar (to start or stop a movie, step forward or backward, adjust the volume, and so forth). But there are a few things we still need to do in order to get BasicMovie to work as desired.
Sizing Movie Windows
Remember that when we opened a movie file, we enabled the movie controller bar's grow box, by issuing the mcActionSetGrowBoxBounds action. When the user resizes the movie controller using the grow box, a window's MoviePlayer object receives a ControllerSizeChanged event. In response to that event, BasicMovie executes the window's SizeWindowToMovie method, defined in Listing 6. (You'll recall that we also call SizeWindowToMovie when we first open a movie window.)
Listing 6: Handling window resizing
SizeWindowToMovie
Sub SizeWindowToMovie ()
Dim origWidth As Integer
origWidth = self.Width
// the controller size has changed, so update the window size
self.Width = mMoviePlayer.ControllerWidth
self.Height = mMoviePlayer.ControllerHeight
// enforce a minimum window width
if self.Width = 0 then
self.Width = origWidth
end if
End Sub
As you can see, we set the Width and Height properties of the window (self) to the current width and height of the movie controller. It's important that the MoviePlayer control's LockLeft, LockRight, LockTop, and LockBottom properties are all set to true (as in Figure 23), so that the MoviePlayer control stays "attached" to the edges of the window.
Ensuring Smooth Playback
If we use our current version of BasicMovie to open a QuickTime movie that has some interactive elements (perhaps some wired sprites moving around inside the movie) and just sit back and watch, we'll notice that things soon -- get -- real choppy --and -- slow. For some reason, whenever there is no keyboard input or mouse movement, the movie controller associated with the MoviePlayer control is not getting tasked often enough, so our interactive elements slow down to a jerky mess. A similar effect can be seen with QuickTime VR movies: if we click the mouse button inside the movie window to pan the movie and then drag the cursor outside the movie window, the panning will soon slow down and become choppy.
This behavior is annoying and will (I hope) get fixed in a future version of REALbasic, but it's easy enough to work around. REALbasic provides a Timer control that can execute code periodically. I have determined that attaching a Timer control to a movie window -- even without executing any code in response to the Timer firing -- causes the movie controller to get called at a specified period. To attach a Timer control to our movie window, drag the stopwatch icon from the Tools palette into the movie window, as shown in Figure 29.
Figure 29: A Timer control in the movie window
The Timer control is a non-visible control, so it won't show up when we open a movie in a window at runtime. I've set the Timer's Period property to 33 milliseconds, so it is called about 30 times a second.
Handling Keyboard Input
Recall that we send the mcActionSetKeysEnabled action to the movie controller when we open a movie window, to allow the user to use keyboard actions to control a movie. For instance, we need to enable keyboard actions so that BasicMovie toggles the movie playback state when the user presses the spacebar. And we need to enable keyboard actions to be able to zoom in or out of a QuickTime VR movie using the Shift and Control keys.
The good news is that once we've enabled keyboard actions, keyboard events are routed to the movie controller and handled correctly. The bad news is that this happens only when our application is run inside the REALbasic IDE. If we build a standalone application and launch it in the Finder, keyboard events do not appear to be sent to the movie controller. Hitting the spacebar will not start or stop the movie; holding down the Shift and Control keys will not zoom a QuickTime VR movie in or out; pressing the up or down arrow keys will not increase or decrease the movie volume.
As far as I can determine, this is definitely a bug in REALbasic's runtime library, but there is a sort of workaround that might be useful in certain circumstances. All we need to do is add some code to the KeyDown event handler in our movie window object. For instance, to start or stop a movie when the user presses the spacebar, we can use the KeyDown handler defined in Listing 7.
Listing 7: Handling spacebar presses
KeyDown
Function KeyDown(Key As String) As Boolean
if Key = Chr(32) then // space key
if isPlaying then
mMovieplayer.Stop
else
mMoviePlayer.Play
end if
end if
End Function
Here we look for the space key (whose ASCII value is 32) and inspect the custom window property isPlaying to determine whether to stop or start the movie. We'd need to add a line of code to each of the Play and Stop event handlers for the MoviePlayer control to set this property appropriately. For instance, we could add this line to the Play action:
isPlaying = true
If we make these changes, the spacebar will correctly start or stop the movie, even when we build BasicMovie as a standalone application and run it outside of the REALbasic IDE.
To get the Shift and Control keys to work properly in a QuickTime VR movie, we need to take a slightly different strategy. The reason for this is that Shift and Control key presses are not sent to the KeyDown event handler. Instead, we'll need to explicitly poll for them, by adding some code to the Action handler for the Timer control, as shown in Listing 8.
Listing 8: Handling Shift and Control presses
Action
Sub Action
if isQTVRMovie then
if MouseX >= 0 and MouseX <= mMoviePlayer.Width then
if MouseY >= 0 and MouseY <= mMoviePlayer.Height then
// check for Shift key down
if keyboard.asyncShiftKey then
mMoviePlayer.QTVRZoom = mMoviePlayer.QTVRZoom - 5
end if
// check for Control key down
if keyboard.asyncControlKey then
mMoviePlayer.QTVRZoom = mMoviePlayer.QTVRZoom + 5
end if
end if // MouseY
end if // MouseX
end if
End Sub
MouseX and MouseY are properties of the Window class that give us the current x and y coordinates of the cursor, in pixels, measured from the top-left corner of the window. Here we use them to see whether the cursor is within the movie window. If it is, we execute the asyncShiftKey or asynchControlKey methods of the Keyboard object, which is a built-in object that represents the current state of the keyboard. As you can see, if either the Shift or Control key is down, we adjust the QTVRZoom property of the movie accordingly.
Of course, this extra processing is required only for QuickTime VR movies, so we check the window property isQTVRMovie before polling the keyboard. We can set that property when we first open a movie file, by executing the code in Listing 9. A movie counts as a QuickTime VR movie if the number of nodes it contains is greater than 0.
Listing 9: Determining whether a movie is a QuickTime VR movie
OpenMovieInWindow
w.isQTVRMovie = false
if w.mMoviePlayer.QTVRNodeCount > 0 then
w.isQTVRMovie = true
end if
The trouble with these various workarounds is that we are slowly but surely replicating logic that is already contained within QuickTime's movie controllers. And of course we've hardly even scratched the surface of the keyboard events that the movie controllers know how to handle. For instance, the up and down arrow keys adjust the volume of a linear QuickTime movie but change the tilt angle of a QuickTime VR movie; and the left and right arrow keys move forward and backward a frame in a linear movie but change the pan angle of a QuickTime VR movie. We'd need another 30 or so lines of code to handle the four arrow keys for these two types of movies. And that wouldn't yet handle the situation where the user holds down the Shift key while pressing, say, the right arrow key -- which should step the movie forward one frame while extending the current selection to include the frame stepped over.
The important point here is that the movie controllers know how to handle a wide variety of keyboard-related events, and we shouldn't need to replicate that knowledge within our REALbasic applications. There may be a simple workaround to this problem, but I haven't been able to find one.
Movie Editing
Let's consider now how to handle the items in the Edit menu. There are just two things we need to do: adjust the items in the menu according to the state of the movie in the frontmost movie window, and also handle user selections of items in the menu.
Adjusting the Edit Menu
In our C-language sample application QTShell, we adjust the items in the Edit menu by calling MCGetControllerInfo and then reading the settings of the bits in the flags long word it returns. We can use this same strategy in BasicMovie. We'll add a custom method to our QuickTime module, as well as a handful of constants defining the bits in the flags long word. To begin, we allocate a memory block and pass it to MCGetControllerInfo:
flags = NewMemoryBlock(4)
result = MCGetControllerInfo(mc, flags)
Once we've done that, we can adjust the menus quite easily; for instance, here's how we would adjust the Paste menu item:
if BitwiseAnd(flags.long(0), mcInfoPasteAvailable) =
mcInfoPasteAvailable then
EditPaste.Enable
end if
Listing 10 shows the EnableMenuItems method associated with a movie window; it adjusts all window-specific menu items, including some items in the File and Movie menus.
Listing 10: Adjusting the menus
EnableMenuItems
Sub EnableMenuItems()
Dim flags As MemoryBlock
Dim result As Integer
// configure the File menu
FileClose.Enable
FileSaveAs.Enable
if IsWindowModified(self.MacWindowPtr) then
FileSave.Enable
end if
// configure the Edit menu
flags = NewMemoryBlock(4)
result = MCGetControllerInfo(mMoviePlayer.QTMovieController, flags)
if BitwiseAnd(flags.long(0), mcInfoCutAvailable) =
mcInfoCutAvailable then
EditCut.Enable
end if
if BitwiseAnd(flags.long(0), mcInfoCopyAvailable) =
mcInfoCopyAvailable then
EditCopy.Enable
end if
if BitwiseAnd(flags.long(0), mcInfoClearAvailable) =
mcInfoClearAvailable then
EditClear.Enable
end if
if BitwiseAnd(flags.long(0), mcInfoPasteAvailable) =
mcInfoPasteAvailable then
EditPaste.Enable
end if
if BitwiseAnd(flags.long(0), mcInfoUndoAvailable) =
mcInfoUndoAvailable then
EditUndo.Enable
end if
if BitwiseAnd(flags.long(0), mcInfoEditingEnabled) =
mcInfoEditingEnabled then
EditSelectAll.Enable
EditSelectNone.Enable
end if
// configure the Movie menu
if not isQTVRMovie then
MoviePlaySelectionOnly.Enable
MoviePlayAllFrames.Enable
MovieGoToPosterFrame.Enable
MovieSetPosterFrame.Enable
MoviePlaySelectionOnly.Checked =
mMoviePlayer.PlaySelection
MoviePlayAllFrames.Checked = isPlayAllFrames
end if
End Sub
Notice that we add a check mark to the "Play Selection Only" menu item in the movie menu if the PlaySelection property of the MoviePlayer control is true. There is no built-in property reflecting the play-all-frames state of a movie, so we need to determine that when we open a movie, by looking at the movie's user data. More on that later.
Handling Items in the Edit Menu
REALbasic provides MoviePlayer control methods for handling the standard Edit menu items. All we need to do, therefore, is add menu handlers for the Edit menu items to our movie window that calls those built-in methods. For instance, our handler for the Copy method contains a single line of code:
mMoviePlayer.Copy
Most of the remaining Edit menu items change the modification state of the movie window, which is reflected in the close button of the window. In Figure 30, you'll see a dot inside the red close button, indicating that the window contents have been modified since they were last saved.
Figure 30: A modified movie window
Ideally, we'd like BasicMovie to set a window's modification state correctly. We can do so by adding two new methods to our QuickTime module, IsWindowModified and SetWindowModified. When we handle an Edit menu item that changes the modification state of a window, we want to call SetWindowModified, as shown in Listing 11, which handles the Cut menu item.
Listing 11: Handling the Cut menu item
Action
Function Action As Boolean
dim osErr As Integer
mMoviePlayer.Cut
osErr = SetWindowModified(self.MacWindowPtr, true)
End Function
I'll leave it as an exercise for the reader to define menu handlers for the Undo and Paste items. We can handle the "Select All" menu item with these two lines of code:
mMoviePlayer.SelStart = 0
mMoviePlayer.SelLength = eMovie.Duration
And we can handle the "Select None" menu item with just one line of code:
mMoviePlayer.SelLength = 0
(I'll also leave it as an exercise for the really interested reader to add the appropriate calls to SetWindowModified to the QTShell sample application.)
File Manipulation
We've got the machinery in place to determine whether a movie in a movie window has been edited. This is useful when we need to determine whether to enable the Save menu item in the File menu (as we saw in Listing 10). It's also useful when the user decides to close a movie window. If the movie has been edited, we need to display a dialog box allowing the user to save or discard any changes, or to cancel the close operation altogether. Figure 31 shows the appropriate dialog box.
Figure 31: The Save Changes dialog box (window closing)
Similarly, if the user decides to quit BasicMovie and any of the open movie windows has been modified, we want to display the dialog box shown in Figure 32.
Figure 32: The Save Changes dialog box (application quitting)
As this point, it becomes clear that REALbasic lacks any extensive document-handling behaviors. There is no built-in capability to track modified windows, and there is no automatic prompting of the user to save or discard changes to a document when its window is to be closed. Moreover, there are no built-in methods to save document data, revert to the most recently-saved version of a document, save a document under a new name, and so forth. Let's add some of these capabilities to BasicMovie.
Building the "Save Changes" Dialog Boxes
It's actually fairly easy to build the dialog boxes in Figures 31 and 32. Let's begin by adding a new window to our project file. Resize the window so that it is 362 by 98 pixels, and set it to be a movable modal window with no title. To display the caution icon in the window, add a Canvas object to the window and define its Paint method as shown in Listing 12.
Listing 12: Drawing the Caution icon
Paint
Sub Paint(g As Graphics)
g.DrawCautionIcon 0,0
End Sub
DrawCautionIcon is a method of the Graphics class, an instance of which occupies the Canvas object.
Next, add a Static Text object to the window and set its text to the string "Do you want to save changes to the document "^0" before ^1?" The strings "^0" and "^1" will be replaced by the appropriate text when the dialog box is displayed to the user.
Finally, add three push buttons to the window, with the labels "Save", "Don't Save", and "Cancel". Figure 33 shows the layout of the dialog box.
Figure 33: The layout of the Save Changes dialog box
Add a new property to the window, ButtonPressed As String. This property keeps track of which of these three buttons the user has clicked. When the user clicks the Save button, we want to remember that and then close the dialog box, as shown in Listing 13.
Listing 13: Handling clicks on the Save button
Action
Sub Action
ButtonPressed = "Save"
Hide
End Sub
Ditto for the "Don't Save" button, as shown in Listing 14.
Listing 14: Handling clicks on the Don't Save button
Action
Sub Action
ButtonPressed = "DontSave"
Hide
End Sub
Finally, when the user clicks the Cancel button, we execute the code in Listing 15.
Listing 15: Handling clicks on the Cancel button
Action
Sub Action
ButtonPressed = "Cancel"
App.isQuitting = false
Hide
End Sub
Notice that we also set the isQuitting property of the application object App to false, to cancel any quitting that might be in progress. The isQuitting property is initialized to false when the application starts up (inside the application's Open method) and is set to true in the menu handler for the Quit menu item. We need to know whether we're quitting the application or just closing a window when we set the text of the Save Changes dialog box, as you can see in Listing 16. This Open event handler is called when that dialog box is about to be opened and displayed to the user.
Listing 16: Opening a Save Changes dialog box
Open
Sub Open()
// insert the window title into the dialog box
StaticText1.Text = Replace(StaticText1.Text, "^0",
App.ClosingWindowTitle)
// insert the action description into the dialog box
if App.isQuitting then
StaticText1.Text = Replace(StaticText1.Text, "^1",
"quitting this application")
else
StaticText1.Text = Replace(StaticText1.Text, "^1",
"closing it")
end if
End Sub
ClosingWindowTitle is a property of the application object App; it holds a string that is the title of the movie window about to be closed.
Closing a Movie Window
When the user elects to close a movie window (by clicking in the window's close box, or by selecting the Close or Quit menu item), REALbasic sends a CancelClose event to the window object. CancelClose should return true to prevent the window from closing or false to allow it to be closed. Of course, we can just call IsWindowModified to see if the window's data has been modified since the movie was last opened or saved. If the window's data has not been modified, we can return false immediately. But if the window's data has been modified, we want to display the Save Changes dialog box, using this code:
SaveChanges.ShowModal
ShowModal causes the specified dialog box to be displayed modally; the calling method is suspended until the dialog box closes or becomes invisible. As we've seen, each of the dialog's three push buttons calls the Hide method to hide the dialog box. We can then look at the ButtonPressed property to determine which button was pressed by the user, as shown in Listing 17.
Listing 17: Handling the CancelClose event
CancelClose
Function CancelClose()
// check the movie-changed flag
if IsWindowModified(self.MacWindowPtr) = true then
App.ClosingWindowTitle = me.Title
SaveChanges.ShowModal // display dialog box and wait for input
Select Case SaveChanges.ButtonPressed
case "DontSave"
case "Cancel"
SaveChanges.Close // close the dialog box
return true // cancel the window closing
case "Save" // save the document
me.SaveFile(me.Title, false)
end Select
SaveChanges.Close // close the dialog box
end if
return false // do not cancel the window closing
End Function
This might be confusing to those of us accustomed to C-language switch statements. The REALbasic Select Case statement looks for a matching case statement and then executes all statements following it, up to the next case statement. In this case, since no statements follow the case statement with the "DontSave" label, execution will jump to the end Select statement and continue from there. So if the user presses the "Don't Save" button, the Save Changes dialog box will be closed and then the movie window will be closed (since the CancelClose function will return false); no changes will be saved.
Saving Movie Changes
When the user elects to save changes to the movie in a movie window (either by choosing the Save menu item or by clicking the Save button in the Save Changes dialog box), we'll call SaveFile, a custom method defined by the movie window. SaveFile takes two parameters, which specify the name of the movie file and whether the Save As dialog box should be displayed (to allow the user to save the movie under a new file name). In the case of normal Save operations, the second parameter is false (as in Listing 17). Listing 18 shows our definition of SaveFile. The key step is the call to the movie's CommitChanges method, which writes the current movie atom out to the movie file (thereby saving any changes to the movie). CommitChanges returns the value true if the operation succeeded; in that case, we want to reset the window modification state.
Listing 18: Saving a file
SaveFile
Sub SaveFile(FileName As String,
DisplaySaveDialog As Boolean)
Dim item as FolderItem
Dim dialog as saveAsDialog
Dim result as Boolean
Dim osErr as Integer
item = Nil
if Document = Nil or DisplaySaveDialog then
dialog = new saveAsDialog
dialog.filter = "video/quicktime"
item = dialog.showModalWithin(self)
if item <> Nil then // if the user clicked Save
Title = item.Name
Document = item
end if
end if
if Document <> Nil then
if eMovie.CommitChanges then
osErr = SetWindowModified(self.MacWindowPtr, false)
else
Beep
end if
end if
End Sub
If for some reason the attempt to save the changes fails, we call the Beep method to play the user's alert sound.
The final project window is shown in Figure 34.
Figure 34: The project window (final version)
REALbasic Plug-Ins
We've now got BasicMovie functioning pretty well. It's able to open QuickTime movie files and Flash files and display their contents in windows on the screen. It also provides the basic movie editing and document management capabilities that we've come to expect from a QuickTime-savvy application. The only major glitch we've encountered so far is that the REALbasic runtime engine for standalone applications does not seem to pass keyboard events to the movie controller, which severely limits our ability to support some of the standard user interactions with movies. We can bodge together some code that handles many of those events, but a full and adequate treatment would seem to require fixes in the REALbasic runtime libraries.
For many applications that open and play QuickTime movies, this level of functionality might be quite sufficient. But we might also want to bring more of QuickTime's capabilities to bear in our REALbasic applications. For instance, we might want to build QuickTime movies on a sample-by-sample basis. Or we might want to be able to intercept FSCommands issued by a Flash track and react accordingly. Or we might want to capture video from a camera attached to a computer and broadcast that video to a nearby computer. REALbasic does not (to my knowledge) provide any built-in classes that allow us to do these things easily. It does however provide a means for us to add these capabilities to our applications, by building a REALbasic plug-in. This is a code module of a specific format that adds custom classes, controls, or global methods to the REALbasic environment. In this section, we'll see how to build a simple plug-in to handle the items in BasicMovie's Movie menu (Figure 35).
Figure 35: The Movie menu of BasicMovie
It's refreshingly easy to build a REALbasic plug-in in order to use parts of QuickTime that are not accessible using REALbasic's built-in classes, at least if you're familiar with the CodeWarrior development environment. REAL Software provides a software development kit (SDK) that contains all the required header and glue code files, documentation, and sample projects for CodeWarrior. With very little work, we'll adapt one of those sample projects to serve as a QuickTime plug-in.
Playing Selections and All Frames
To appreciate better when we need to write a REALbasic plug-in, let's begin by considering how to handle the first two items in the Movie menu. It's very easy to handle the "Play Selection Only" item, since REALbasic's MoviePlayer control has the PlaySelection property; if the property is set to true, then movie playback will be limited to the segment of the movie defined by the MoviePlayer's SelStart and SelLength properties. Listing 19 shows how we can handle that menu item.
Listing 19: Handling the "Play Selection Only" menu item
Action
Function Action As Boolean
mMoviePlayer.PlaySelection =
not mMoviePlayer.PlaySelection
return true
End Function
All we need to do is toggle the current value of the PlaySelection property. REALbasic pays attention to the property's value and does the right thing when the movie is played.
It's a little bit more complicated to handle the "Play All Frames" menu item, since there is no built-in MoviePlayer property for that playback option. What we'll do, then, is add a custom property to the movie window, isPlayAllFrames. When the user selects the "Play All Frames" menu item, we'll toggle the current value of that property. We also need to send the mcActionSetPlayEveryFrame action to the movie controller with the appropriate value, as shown in Listing 20.
Listing 20: Handling the "Play All Frames" menu item
Action
Function Action As Boolean
Dim result As Integer
isPlayAllFrames = not isPlayAllFrames
if isPlayAllFrames then
result = MCDoActionVal(mMoviePlayer.QTMovieController,
mcActionSetPlayEveryFrame, 1)
else
result = MCDoActionVal(mMoviePlayer.QTMovieController,
mcActionSetPlayEveryFrame, 0)
end if
End Function
There's one last thing we need to do here, which is initialize a movie's isPlayAllFrames property based on data stored in the movie file. If a movie has a piece of user data of type 'AllF' whose value is true, then the movie should be put in play-all-frames mode when it is first opened. Happily, the MoviePlayer control has a UserData property that represents the movie's user data. We can extract the play-all-frames setting as shown in Listing 21.
Listing 21: Getting a movie's play-all-frames setting
OpenMovieInWindow
Dim userData As String
userData = ""
ignore = em.UserData.GetUserData("AllF", 1, userData)
w.isPlayAllFrames = (userData = Chr(1))
if w.isPlayAllFrames then
result = MCDoActionVal(w.mMoviePlayer.QTMovieController,
mcActionSetPlayEveryFrame, 1)
else
result = MCDoActionVal(w.mMoviePlayer.QTMovieController,
mcActionSetPlayEveryFrame, 0)
end if
The GetUserData method returns the user data as a String. We're looking for a 1-byte value of 0x01, so we need to compare the string returned by GetUserData with the character whose ASCII value is 1.
The lesson to be learned form this little digression is that we can accomplish quite a bit using just the built-in methods and properties of the MoviePlayer control and its associated classes. And we can fairly easily access QuickTime functions using the mechanism for declaring external functions (like MCDoActionVal used just above). But for any really complex QuickTime processing, we're better off building a plug-in. Plug-ins are almost always more efficient and they make it easier to reuse code in multiple projects. So let's see how to build one.
Setting Up the Plug-In Project
First, download the plug-in SDK from the REAL Software web site (the exact location is given at the end of this article). The SDK consists of a folder named "Plugins SDK". Within this folder is a folder of example projects. Duplicate the example called "Long Processor" and rename the folder and the files within the folder to your liking. I used the name "QTExtensions".
Currently the example projects are supplied only as CodeWarrior projects. For the time being, we'll be concerned only with the pluginCarbon target, which is what is required for use with our Carbon application BasicMovie. Figure 36 shows the project window for QTExtensions.
Figure 36: The project file for QTExtensions
Notice that I've added the library QuickTimeLib to the project. To make our development easier, let's adjust the project settings so that the output directory is the folder named "Plugins" inside the REALbasic folder. That's where REALbasic expects to find compiled plug-ins.
Defining Global Methods
The easiest plug-in to write is one that defines a global method -- a method that can be executed by any object in the application. Open the file QTExtensions.cpp and remove all the existing code except for the initial include statement:
#include "rb_plugin.h"
Add the following include statement:
#include <Movies.h>
To define a global method, we need three things. First, we need to provide a method definition for the global method. This tells REALbasic the name of the method, its parameters, and the help text that is displayed in a Tips window. Listing 22 shows our method definitions for the QTExtensions plug-in.
Listing 22: Defining a global method
REALmethodDefinition GoToPosterFrameMethod =
{ (REALproc)GoToPosterFrame, REALnoImplementation,
"GoToPosterFrame(mp As MoviePlayer) As Boolean" };
REALmethodDefinition SetPosterToFrameMethod =
{ (REALproc)SetPosterToFrame, REALnoImplementation,
"SetPosterToFrame(mp As MoviePlayer) As Boolean" };
Second, we need to register the global methods, inside the plug-in's PlugInEntry function (Listing 23). The PlugInEntry function is executed whenever the plug-in is loaded.
Listing 23: Registering global methods
PluginEntry
void PluginEntry(void)
{
REALRegisterMethod(&GoToPosterFrameMethod);
REALRegisterMethod(&SetPosterToFrameMethod);
}
The last thing we need to do is implement the global methods themselves. Listing 24 shows our definition of the SetPosterToFrame method. Notice that this method is passed a parameter of type REALmoviePlayer. We can extract the movie controller from that object by calling the REALgetMoviePlayerController function (which is provided by the plug-in glue code).
Listing 24: Setting the poster frame
SetPosterToFrame
static Boolean SetPosterToFrame (REALmoviePlayer instance)
{
QT::Movie myMovie = NULL;
QT::MovieController myMC = NULL;
// get the movie controller from the REALmoviePlayer instance
myMC = REALgetMoviePlayerController(instance);
if (myMC == NULL)
return(false);
// get the movie from the movie controller
myMovie = MCGetMovie(myMC);
if (myMovie == NULL)
return(false);
QTInfo_SetPosterToFrame(myMovie, myMC);
return(true);
}
SetPosterToFrame calls the function QTInfo_SetPosterToFrame (defined in Listing 25) to actually set the movie poster frame to the current frame.
Listing 25: Setting the movie poster time to the current movie time
QTInfo_SetPosterToFrame
OSErr QTInfo_SetPosterToFrame (Movie theMovie,
MovieController theMC)
{
TimeValue myTime;
ComponentResult myErr = noErr;
// stop the movie from playing
myErr = MCDoAction(theMC, mcActionPlay, (void *)0L);
if (myErr != noErr)
goto bail;
myTime = GetMovieTime(theMovie, NULL);
SetMoviePosterTime(theMovie, myTime);
myErr = MCMovieChanged(theMC, theMovie);
bail:
return((OSErr)myErr);
}
In fact we've seen this function before, when we took a look at getting and setting movie information (in "The Informant" in MacTech, August 2000). The global method GoToPosterFrame, defined similarly to SetPosterToFrame, calls QTInfo_GoToPosterFrame (which is also defined in that earlier article).
At this point, we can build the plug-in. Since we've configured the CodeWarrior IDE to install the plug-in into the REALbasic plug-ins folder, our new plug-in will be available to us the next time we launch REALbasic.
Calling Global Methods
To use these global methods in BasicMovie, we simply call them just like any other method, passing the appropriate parameters. For instance, we can handle the "Go To Poster Frame" menu item with the code in Listing 26.
Listing 26: Handling the "Go To Poster Frame" menu item
MovieGoToPosterFrame
Function Action As Boolean
Dim result As Boolean
result = GoToPosterFrame(mMoviePlayer)
return result
End Function
And we can handle the "Set Poster Frame" menu item with the code shown in Listing 27.
Listing 27: Handling the "Set Poster Frame" menu item
MovieSetPosterFrame
Function Action As Boolean
Dim result As Boolean
Dim osErr As Integer
result = SetPosterToFrame(mMoviePlayer)
osErr = SetWindowModified(self.MacWindowPtr, true)
return result
End Function
Easy, huh?
Installing a Movie Controller Action Filter Function
REALbasic plug-ins are most useful when we want to use QuickTime functions that require a callback procedure of some sort, since (to my knowledge) there is no way define such callbacks using the REALbasic language. A good example here is the movie controller action filter function, which we have employed repeatedly throughout this series of articles. For instance, to respond to FSCommands emitted by a Flash movie, we can look for actions of type mcActionDoScript in our filter function. Or to handle clicks on the custom button in the controller bar, we can look for actions of type mcActionCustomButtonClick.
It's easy enough to define a movie controller action filter function in our plug-in. Listing 28 shows a simple filter function that beeps every time we resize the movie window.
Listing 28: Handling movie controller actions
MyActionFilter
static pascal Boolean MyActionFilter
(MovieController theMC, short theAction,
void *theParams, long theRefCon)
{
#pragma unused(theMC, theParams, theRefCon)
if (theAction == mcActionControllerSizeChanged)
SysBeep(30);
return(false);
}
All that remains is for our application to install the filter function, by calling MCSetActionFilterWithRefCon. In the plug-in code, we'll define a global method that does this (Listing 29).
Listing 28: Handling movie controller actions
MyActionFilter
static pascal Boolean MyActionFilter
(MovieController theMC, short theAction,
void *theParams, long theRefCon)
{
#pragma unused(theMC, theParams, theRefCon)
if (theAction == mcActionControllerSizeChanged)
SysBeep(30);
return(false);
}
All that remains is for our application to install the filter function, by calling MCSetActionFilterWithRefCon. In the plug-in code, we'll define a global method that does this (Listing 29).
Listing 29: Installing the movie controller action filter function
InstallActionFilter
static Boolean InstallActionFilter
(REALmoviePlayer instance)
{
MovieController myMC = NULL;
ComponentResult myErr = noErr;
// get the movie controller from the MoviePlayer instance
myMC = REALgetMoviePlayerController(instance);
if (myMC != NULL) {
// install an action filter
MCSetActionFilterWithRefCon(myMC,
NewMCActionFilterWithRefConUPP(MyActionFilter),
(long)instance);
}
return(myErr == 0);
}
Then, of course, we need to register InstallActionFilter inside PluginEntry:
REALRegisterMethod(&InstallActionFilterMethod);
And we need to provide a method definition:
REALmethodDefinition InstallActionFilterMethod =
{ (REALproc)InstallActionFilter, REALnoImplementation,
"InstallActionFilter(mp As MoviePlayer) As Boolean" };
Now we can build our plug-in and then call InstallActionFilter within BasicMovie (perhaps in the OpenMovieInWindow method).
ignore = InstallActionFilter(w.mMoviePlayer)
Everything works fine, at least insofar as our filter function gets installed and is called at the appropriate times. We soon discover, however, that some of the behaviors of the MoviePlayer control have stopped working properly. In particular, the ControllerSizeChanged event is now never received by the MoviePlayer control, so that our movie windows do not get resized properly when the user drags the grow box in the controller bar.
I strongly suspect that REALbasic's MoviePlayer control installs its own movie controller action filter procedure to look for mcActionControllerSizeChanged actions and then issue its ControllerSizeChanged event in response. This is all well and good, except that QuickTime currently provides no support for having multiple filter functions installed for a single movie controller. In other words, since REALbasic (apparently) installs a movie controller action filter procedure, our plug-in cannot do the same without thereby replacing the REALbasic procedure. This then breaks any REALbasic functionality that is tied to its filter function.
If my guess is correct, it is effectively impossible for a REALbasic plug-in to install a movie controller action filter function and hence impossible for our REALbasic applications to take advantage of any QuickTime capabilities that require such filter functions. This is not good.
Conclusion
It's easy to see why REALbasic has garnered such glowing praise and such a loyal developer community. It's extremely simple to use REALbasic to create an application's user interface, and it's a snap to attach code to elements in that user interface. It's also fairly easy to call external functions or create plug-in modules to handle specialized tasks. So we can use REALbasic to handle the user interaction and our existing C code -- packaged as a plug-in -- to do the heavy lifting underneath.
Moreover, the QuickTime support offered by REALbasic's MoviePlayer control (and associated classes) is reasonably good. With very little work, we were able to open and display QuickTime movies in windows on the screen. And we've seen how to handle simple movie editing and document control.
Nonetheless, we've run into at least three issues that have hampered our efforts to replicate the full functionality of our benchmark Carbon sample application, QTShell. First of all, we had to install a Timer control in our movie window to ensure that interactive media handlers receive a steady flow of events. This is the kind of thing that the runtime engine should handle automatically. REALbasic's engineers need to rethink how -- and how often -- they are tasking the movie controller associated with a MoviePlayer control.
The second problem concerns the processing of keyboard events when BasicMovie is running as a standalone application. As we've seen, keyboard events are handled just fine when BasicMovie runs inside of the REALbasic IDE but are ignored when it's running as a standalone application. We've also seen how to work around this problem, to some degree. But the workaround is ultimately unsatisfactory, I think, as it requires us to duplicate logic that is already in QuickTime's movie controllers. If this is a bug (and not just some confusion on my part), it's a bug that is best fixed by REAL Software.
Finally, we decided that REALbasic probably installs a movie controller action filter procedure, which effectively prevents our plug-ins from doing the same without breaking some of REALbasic's built-in behaviors. If true, this significantly limits our ability to take advantage of the full richness of QuickTime's APIs in our REALbasic applications.
Credits and References
Thanks are due to Lorin Rivers and Stacie Mayes at REAL Software, Inc. for their assistance in providing a review copy of REALbasic and easy access to REAL's technical support. A very special thanks is due to Erick Tejkowski for answering a number of questions. Erick also reviewed this article and suggested the technique for handling the Shift and Control keys in a QuickTime VR movie (Listing 8).
The REAL Software web site (http://www.realsoftware.com) contains a wealth of information about REALbasic. You can download the REALbasic Plug-ins SDK from http://www.realsoftware.com/realbasic/plugins.html.
Tim Monroe in a member of the QuickTime engineering team. You can contact him at monroe@apple.com. The views expressed here are not necessarily shared by his employer.