TweetFollow Us on Twitter

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.

 

Community Search:
MacTech Search:

Software Updates via MacUpdate

Latest Forum Discussions

See All

Tokkun Studio unveils alpha trailer for...
We are back on the MMORPG news train, and this time it comes from the sort of international developers Tokkun Studio. They are based in France and Japan, so it counts. Anyway, semantics aside, they have released an alpha trailer for the upcoming... | Read more »
Win a host of exclusive in-game Honor of...
To celebrate its latest Jujutsu Kaisen crossover event, Honor of Kings is offering a bounty of login and achievement rewards kicking off the holiday season early. [Read more] | Read more »
Miraibo GO comes out swinging hard as it...
Having just launched what feels like yesterday, Dreamcube Studio is wasting no time adding events to their open-world survival Miraibo GO. Abyssal Souls arrives relatively in time for the spooky season and brings with it horrifying new partners to... | Read more »
Ditch the heavy binders and high price t...
As fun as the real-world equivalent and the very old Game Boy version are, the Pokemon Trading Card games have historically been received poorly on mobile. It is a very strange and confusing trend, but one that The Pokemon Company is determined to... | Read more »
Peace amongst mobile gamers is now shatt...
Some of the crazy folk tales from gaming have undoubtedly come from the EVE universe. Stories of spying, betrayal, and epic battles have entered history, and now the franchise expands as CCP Games launches EVE Galaxy Conquest, a free-to-play 4x... | Read more »
Lord of Nazarick, the turn-based RPG bas...
Crunchyroll and A PLUS JAPAN have just confirmed that Lord of Nazarick, their turn-based RPG based on the popular OVERLORD anime, is now available for iOS and Android. Starting today at 2PM CET, fans can download the game from Google Play and the... | Read more »
Digital Extremes' recent Devstream...
If you are anything like me you are impatiently waiting for Warframe: 1999 whilst simultaneously cursing the fact Excalibur Prime is permanently Vault locked. To keep us fed during our wait, Digital Extremes hosted a Double Devstream to dish out a... | Read more »
The Frozen Canvas adds a splash of colou...
It is time to grab your gloves and layer up, as Torchlight: Infinite is diving into the frozen tundra in its sixth season. The Frozen Canvas is a colourful new update that brings a stylish flair to the Netherrealm and puts creativity in the... | Read more »
Back When AOL WAS the Internet – The Tou...
In Episode 606 of The TouchArcade Show we kick things off talking about my plans for this weekend, which has resulted in this week’s show being a bit shorter than normal. We also go over some more updates on our Patreon situation, which has been... | Read more »
Creative Assembly's latest mobile p...
The Total War series has been slowly trickling onto mobile, which is a fantastic thing because most, if not all, of them are incredibly great fun. Creative Assembly's latest to get the Feral Interactive treatment into portable form is Total War:... | Read more »

Price Scanner via MacPrices.net

Early Black Friday Deal: Apple’s newly upgrad...
Amazon has Apple 13″ MacBook Airs with M2 CPUs and 16GB of RAM on early Black Friday sale for $200 off MSRP, only $799. Their prices are the lowest currently available for these newly upgraded 13″ M2... Read more
13-inch 8GB M2 MacBook Airs for $749, $250 of...
Best Buy has Apple 13″ MacBook Airs with M2 CPUs and 8GB of RAM in stock and on sale on their online store for $250 off MSRP. Prices start at $749. Their prices are the lowest currently available for... Read more
Amazon is offering an early Black Friday $100...
Amazon is offering early Black Friday discounts on Apple’s new 2024 WiFi iPad minis ranging up to $100 off MSRP, each with free shipping. These are the lowest prices available for new minis anywhere... Read more
Price Drop! Clearance 14-inch M3 MacBook Pros...
Best Buy is offering a $500 discount on clearance 14″ M3 MacBook Pros on their online store this week with prices available starting at only $1099. Prices valid for online orders only, in-store... Read more
Apple AirPods Pro with USB-C on early Black F...
A couple of Apple retailers are offering $70 (28%) discounts on Apple’s AirPods Pro with USB-C (and hearing aid capabilities) this weekend. These are early AirPods Black Friday discounts if you’re... Read more
Price drop! 13-inch M3 MacBook Airs now avail...
With yesterday’s across-the-board MacBook Air upgrade to 16GB of RAM standard, Apple has dropped prices on clearance 13″ 8GB M3 MacBook Airs, Certified Refurbished, to a new low starting at only $829... Read more
Price drop! Apple 15-inch M3 MacBook Airs now...
With yesterday’s release of 15-inch M3 MacBook Airs with 16GB of RAM standard, Apple has dropped prices on clearance Certified Refurbished 15″ 8GB M3 MacBook Airs to a new low starting at only $999.... Read more
Apple has clearance 15-inch M2 MacBook Airs a...
Apple has clearance, Certified Refurbished, 15″ M2 MacBook Airs now available starting at $929 and ranging up to $410 off original MSRP. These are the cheapest 15″ MacBook Airs for sale today at... Read more
Apple drops prices on 13-inch M2 MacBook Airs...
Apple has dropped prices on 13″ M2 MacBook Airs to a new low of only $749 in their Certified Refurbished store. These are the cheapest M2-powered MacBooks for sale at Apple. Apple’s one-year warranty... Read more
Clearance 13-inch M1 MacBook Airs available a...
Apple has clearance 13″ M1 MacBook Airs, Certified Refurbished, now available for $679 for 8-Core CPU/7-Core GPU/256GB models. Apple’s one-year warranty is included, shipping is free, and each... Read more

Jobs Board

Seasonal Cashier - *Apple* Blossom Mall - J...
Seasonal Cashier - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple Read more
Seasonal Fine Jewelry Commission Associate -...
…Fine Jewelry Commission Associate - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) Read more
Seasonal Operations Associate - *Apple* Blo...
Seasonal Operations Associate - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Read more
Hair Stylist - *Apple* Blossom Mall - JCPen...
Hair Stylist - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple Blossom Read more
Cashier - *Apple* Blossom Mall - JCPenney (...
Cashier - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple Blossom Mall Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.