TweetFollow Us on Twitter

Jan 00 QTToolkit

Volume Number: 16 (2000)
Issue Number: 1
Column Tag: QuickTime Toolkit

QuickTime Toolkit

by Tim Monroe

Using Movie Controllers for QuickTime Movie Playback and Editing

The State of the Art

It's been almost ten years since Apple introduced QuickTime, its software architecture for creating and playing back multimedia content. In that time, QuickTime has progressed from a Mac-only tool for playing movies roughly the size of postage stamps into the de facto standard on Macintosh and Windows computers for the creation, delivery, and playback of digital media, including video, sound, music, 3D graphics, virtual reality, sprite animation, and more. The range of abilities that QuickTime has gained over the years is truly staggering. It now supports real-time streaming of audio and video over the Internet and LANs, a full-featured video effects and transitions architecture, vector drawing capabilities, and the ability to associate actions to sprites, text, hot spots in QuickTime VR movies, and 3D objects.

It's high time, then, that MacTech should begin to devote some regular space to discussing QuickTime from a programmer's point of view, and this is the first of a series of articles that will focus on using the QuickTime application programming interfaces to create and play back digital media. Given the wide range of capabilities provided by the QuickTime APIs, you can guess that there will be no shortage of topics for us to investigate over the coming months and years. Admittedly, QuickTime can perhaps be a bit overwhelming if you try to understand it all at once. So we'll adopt a fairly leisurely approach and try to dissect the QuickTime architecture a little bit at a time, starting with its high-level interfaces and then gradually working down to lower-level capabilities to add to our knowledge of the entire architecture. Eventually, we'll be creating QuickTime files and writing custom QuickTime components just like the pros.

In this article, we'll learn how to open and display QuickTime movies, and how to manage the user's interactions with those movies (such as starting and pausing movies, zooming in and out in QuickTime VR movies, and similar operations). This is a relatively simple task, and it's one that involves adding a fairly small amount of code to a basic working application. So, to really earn our money today, we're going to complicate things. In addition to showing how to support basic movie playback and editing in a Macintosh application, we're also going to show how to do it in an application that runs on Microsoft Windows. In other words, we want the code we write here to compile and execute both on the Macintosh operating system and on the major flavors of the Windows operating system (to wit: Windows 95, Windows 98, and Windows NT).

Now this might sound like heresy, especially for a publication like MacTech whose mission is to report on and facilitate software development for Macintosh computers. But it's really just as consistent with MacTech's mission as its regular coverage of the Internet or of Java and other cross-platform languages and tools. Moreover, it's fair to say that the cross-platform parity exhibited by the QuickTime application programming interfaces since version 3.0 is largely responsible for QuickTime's recent explosion in popularity and is crucial for its continued success. So, everything we're going to do in these articles will be completely cross-platform.

In point of fact, however, once you learn to pay attention to a few recurring issues like the endianness of multi-byte data, writing QuickTime code that is compatible with multiple platforms really isn't so hard. Indeed, part of the reason that QuickTime runs so well on Windows is that a good bit of the Macintosh programming concepts (including handles, resources, and file system specifications) were implemented on Windows as part of the QuickTime Media Layer. The hardest part of getting your QuickTime code to run on Windows may simply be creating a basic application shell or framework to hold that code. Accordingly, we'll spend a good bit of time in this article discussing just that issue.

Movie Controllers

Before we start looking at our source code, however, let's take a minute to make clear what it is that we want to achieve. For the moment, we'll be content to build an application that can open and display QuickTime movies in windows on the screen. The Windows version of our basic application will have a "frame window" that contains a menu bar and within which we can open and display one or more movie windows. Figure 1 shows the appearance of the application's frame window before the user has opened any QuickTime movies.


Figure 1.The frame window of the Windows application

A movie window will contain all the standard window parts (in particular, a title bar and close box), the movie itself, and a special set of controls called the movie controller bar. Figure 2 shows a typical Macintosh movie window, and Figure 3 shows a Windows version of the same movie window. Both of these windows show the standard movie controller bar along the bottom edge of the window.


Figure 2.A movie window on the Macintosh.


Figure 3.A movie window on Windows.

The movie controller bar allows the user to control the playback of the movie and to navigate within it. For instance, the user can use the fast-forward button to play the movie forward at an accelerated rate. Or, the user can drag the position thumb to set the current location in the movie.

Some kinds of QuickTime movies use a different movie controller bar. For instance, QuickTime VR movies are not typically played frame-by-frame in a linear fashion. For these movies, you'll see the movie controller bar shown in Figure 4, which contains controls that allow the user to zoom in and out and to perform other operations on the movie.


Figure 4.The movie controller bar for a QuickTime VR movie.

The movie controller bar is created and managed by a software component called a movie controller component (or, more briefly, a movie controller). Now here's the really fun part: once you've opened a QuickTime movie (using a few simple QuickTime functions), you can call a couple more functions to create and attach a movie controller to your movie. Thereafter, the movie controller (and not your application) draws the movie controller bar and manages all events associated with the movie. Your application doesn't need to know how to jump to a new location in the movie or how to start and stop the movie playback. It simply needs to pass any events it receives to the movie controller component before acting on them itself. The movie controller intercepts any events that apply to it and reacts to them appropriately.

So, the first lesson we need to take to heart is this: we can get all the basic movie playback capabilities simply by creating a movie controller, attaching it to our movie, and then giving it the first shot at handling any events we receive. And as if that weren't enough, the movie controller also provides an extremely easy way for us to perform basic editing operations on movies that support them. (Not all movies support cutting, copying, or pasting of movie segments; for instance, QuickTime VR movies do not.)

The Application Framework

Now it's time for a bit of a detour. As mentioned earlier, QuickTime provides an extensive set of services for handling digital media like sound, video, sprite animation, and the like. But of course we'll need to use other services to handle the basic graphical user interface for our QuickTime-savvy application (windows, menus, dialog boxes, and so forth). Since you're an experienced Macintosh programmer, you're already familiar with the ideas underlying event-driven programming on the Macintosh. Remember, though, that we want to support QuickTime programming on both Macintosh and Windows systems. So we'll need to address separately the issues specific to each operating system, while trying to factor out as much code as possible to share between the two systems.

Our general approach will go like this: we'll create two files, MacFramework.c and WinFramework.c, that handle the basic application services that are specific to the Macintosh and Windows operating systems, respectively. These services include starting up and shutting down the application, handling events and messages, creating windows, opening files dropped onto the application icon, and so forth. We won't delve very much into these framework files in this article, since there isn't very much in them of interest for QuickTime programmers. Suffice it to say that the Macintosh framework would look very familiar to anyone who cut their Mac programming eyeteeth on MacTech's "Getting Started" series (or on some similarly good source); it uses standard event-driven programming techniques to handle the user's actions. And the Windows framework is a very straightforward implementation of the multiple document interface (MDI) specification defined by Microsoft for creating and managing one or more document windows within a general frame window.

What's distinctive about MacFramework.c and WinFramework.c is that they have been carefully designed to call functions defined in a third file, ComFramework.c, for most QuickTime services or other services that are not system-specific. ComFramework.c also defines a number of functions that are substantially the same on both platforms but which may require several short platform-dependent blocks (introduced by the compiler flags TARGET_OS_MAC and TARGET_OS_WIN32).

Keep in mind that (in this article, at least) we want to support only the most basic playback and editing of QuickTime movies, which is exactly what is provided by the basic framework. In future articles, however, we'll want to add other capabilities to our applications. For instance, we'll want to handle some new menus, in addition to the standard File and Edit menus; and we'll want to perform some application-specific tasks at idle time (perhaps change the pan angle of a QuickTime VR movie). To make it easy to add such capabilities, we create yet another file, called ComApplication.c, which defines a number of functions that are called at particular times by the basic framework. For instance, after the framework does any necessary menu adjusting for the File and Edit menus (enabling certain menu items and disabling others), it calls the function QTApp_AdjustMenus, defined in ComApplication.c, to allow us to adjust any application-specific menus. Since we don't have any application-specific tasks to perform, for the moment at least, we can ignore ComApplication.c and instead turn our attention to the file ComFramework.c.

Handling Movie Windows

To get a taste for how our basic framework works, let's begin by considering how we want to manage our application's movie windows. On the Macintosh, a movie window is of type WindowPtr; on Windows, a movie window is of type HWND. To simplify the code that handles movie windows, we define a custom type that refers to either a Macintosh movie window or a Windows movie window, like this:

#if TARGET_OS_MAC
typedef WindowPtr				WindowReference;
#endif
#if TARGET_OS_WIN32
typedef HWND						WindowReference;
#endif

We need to maintain some information for each movie window displayed by our application. We'll use the standard technique of defining a structure to hold this information and allocating an instance of that structure for each open movie window. Let's call this instance a "window object record".

typedef struct {
	WindowReference				fWindow;
	Movie								fMovie;
	MovieController 			fController;
	FSSpec								fFileFSSpec;
	short								fFileResID;
	short								fFileRefNum;
	Boolean							fCanResizeWindow;
	Boolean							fIsDirty;
	Boolean							fIsQTVRMovie;
	QTVRInstance					fInstance;
	OSType								fObjectType;
	Handle								fAppData;
} WindowObjectRecord,		*WindowObjectPtr, **WindowObject;

Notice that the first field of this structure, fWindow, is of type WindowReference, which (as you've just seen) is a WindowPtr on the Mac and an HWND on Windows. The fMovie and fController fields identify the movie and movie controller. The next three fields maintain information about the location of the movie file on disk and in memory. The three fields after that indicate whether the movie window can be resized (which is almost always true), whether the movie data has changed since it was opened or last saved, and whether the movie is a QuickTime VR movie. If the movie is a QuickTime VR movie, the fInstance field holds the QTVR instance associated with the movie. The fObjectType field holds an arbitrary identifier that is unique to our application; we use this field just to make sure that we've got a valid window object. Finally, the fAppData field holds a handle to any application-specific data. For now, we won't need to use this field.

When the user selects a movie file to open, we need to allocate a window object record and attach it to the window in which the movie is opened. The standard Macintosh way to do this is to use the SetWRefCon function to set the window's reference constant, an application-specific 32-bit value, to the handle to the window object record. Windows provides a similar capability to attach an application-specific 32-bit value to a window, with the SetWindowLong function. Listing 1 shows the code we use for creating a window object.

Listing 1: Creating a window object

QTFrame_CreateWindowObject

void QTFrame_CreateWindowObject (WindowReference theWindow)
{
	WindowObject			myWindowObject = NULL;

	if (theWindow == NULL)
		return;
		
	// allocate space for a window object record and fill in some of its fields
	myWindowObject = (WindowObject)NewHandleClear(sizeof(WindowObjectRecord));
	if (myWindowObject != NULL) {
		(**myWindowObject).fWindow = theWindow;
		(**myWindowObject).fController = NULL;
		(**myWindowObject).fObjectType = kMovieControllerObject;
		(**myWindowObject).fInstance = NULL;
		(**myWindowObject).fCanResizeWindow = true;
		(**myWindowObject).fIsDirty = false;
		(**myWindowObject).fAppData = NULL;
	}
	
	// associate myWindowObject (which may be NULL) with the window
#if TARGET_OS_MAC
	SetWRefCon(theWindow, (long)myWindowObject);
#endif
#if TARGET_OS_WIN32
	SetWindowLong(theWindow, GWL_USERDATA, (LPARAM)myWindowObject);
	
	// associate a GrafPort with this window 
	CreatePortAssociation(theWindow, NULL, 0L);
#endif
	
	// set the current port to the new window
	MacSetPort(QTFrame_GetPortFromWindowReference(theWindow));
}

Internally, QuickTime does some of its drawing using QuickDraw, the collection of system software routines that perform graphic operations on the user's screen (and elsewhere). And, as you know, QuickDraw does all of its drawing within the current graphics port. On the Macintosh, there is a very close connection between a WindowPtr and a graphics port, but there is no such connection between Windows HWNDs and graphics ports. So, when our application is running on Windows, we need to call the CreatePortAssociation function to create a connection between the HWND and a graphics port (of type GrafPtr).

Once we've called CreatePortAssociation to associate a graphics port with an HWND, we can subsequently call the GetNativeWindowPort function to get a pointer to the graphics port that was associated with that window. Listing 2 defines a function that we can call from either Macintosh or Windows code to get a window's graphics port. (Now you can understand the last line in Listing 1, which sets the current graphics port to the port associated with the specified window.)

Listing 2: Getting the graphics port associated with a window

QTFrame_GetPortFromWindowReference

GrafPtr QTFrame_GetPortFromWindowReference (WindowReference theWindow)
{
#if TARGET_OS_MAC
	return((GrafPtr)GetWindowPort(theWindow));
#endif
#if TARGET_OS_WIN32
	return(GetNativeWindowPort(theWindow));
#endif
}

Let's look briefly at a few other small utilities that we'll use extensively in our framework code. Keep in mind that our general goal here is to provide utilities that insulate us from the specific details of any particular operating system. One thing we'll need to do fairly often is retrieve the window-specific data that we've stored in the window object record associated with a given movie window. We can use the QTFrame_GetWindowObjectFromWindow function, defined in Listing 3 to do this. Aside from a few sanity checks (namely the calls to QTFrame_IsAppWindow and QTFrame_IsWindowObjectOurs), this function is essentially the reverse of attaching the window object record to a window.

Listing 3: Getting the window-specific data associated with a window

QTFrame_GetWindowObjectFromWindow

WindowObject QTFrame_GetWindowObjectFromWindow (WindowReference theWindow)
{
	WindowObject		myWindowObject = NULL;

	if (!QTFrame_IsAppWindow(theWindow))
		return(NULL);
			
#if TARGET_OS_MAC
	myWindowObject = (WindowObject)GetWRefCon(theWindow);
#endif
#if TARGET_OS_WIN32
	myWindowObject = (WindowObject)GetWindowLong(theWindow, GWL_USERDATA);
#endif

	// make sure this is a window object
	if (!QTFrame_IsWindowObjectOurs(myWindowObject))
		return(NULL);
		
	return(myWindowObject);
}

Finally, we'll often need to iterate through all open movie windows. On Windows, this is fairly simple, since the operating system provides an easy way for us to ask just for the first (or next) child of the MDI frame window, which we know to be a movie window. On the Macintosh, it's a bit harder, since we need to skip over any dialog windows or other types of windows that might be in our window list. We can use the functions QTFrame_GetFrontAppWindow and QTFrame_GetNextAppWindow, defined in Listings 4 and 5, to step through all open windows that belong to our application.

Listing 4: Getting the first application window

QTFrame_GetFrontAppWindow

WindowReference QTFrame_GetFrontAppWindow (void)
{
#if TARGET_OS_MAC
	return(FrontWindow());
#endif
#if TARGET_OS_WIN32
	return(GetWindow(ghWnd, GW_HWNDFIRST));
#endif
}

One thing to notice in Listing 5 is that we did not find the next window by reading the nextWindow field of the window record, as used to be standard practice. Instead, we've used the accessor function GetNextWindow defined in the header file MacWindows.h. Here we're treating the window record as an opaque data structure and thereby facilitating our eventual move to Carbon-compatible APIs.

Listing 5: Getting the next application window

QTFrame_GetNextAppWindow

WindowReference QTFrame_GetNextAppWindow (WindowReference theWindow)
{
#if TARGET_OS_MAC
	return(theWindow == NULL ? NULL : GetNextWindow(theWindow));
#endif
#if TARGET_OS_WIN32
	return(GetWindow(theWindow, GW_HWNDNEXT));
#endif
}

To find the front movie window, on the Macintosh, we'll just walk through the window list until we find the first window with a non-NULL window object attached to it, as shown in Listing 6.

Listing 6: Getting the first movie window

QTFrame_GetFrontMovieWindow

WindowReference QTFrame_GetFrontMovieWindow (void)
{
	WindowReference			myWindow;

#if TARGET_OS_MAC
	myWindow = QTFrame_GetFrontAppWindow();
	while ((myWindow != NULL) && (QTFrame_GetWindowObjectFromWindow(myWindow) == NULL))
		myWindow = QTFrame_GetNextAppWindow(myWindow);
#endif

#if TARGET_OS_WIN32
	myWindow = (HWND)SendMessage(ghWndMDIClient, WM_MDIGETACTIVE, 0, 0L);
#endif

	return(myWindow);
}

And to get the movie window that follows a specific movie window, we'll continue walking through the window list until we find the next window with a non-NULL window object, as shown in Listing 7.

Listing 7: Getting the next movie window

QTFrame_GetNextMovieWindow

WindowReference QTFrame_GetNextMovieWindow (WindowReference theWindow)
{
	WindowReference			myWindow;

#if TARGET_OS_MAC
	myWindow = QTFrame_GetNextAppWindow(theWindow);
	while ((myWindow != NULL) && (QTFrame_GetWindowObjectFromWindow(myWindow) == NULL))
		myWindow = QTFrame_GetNextAppWindow(myWindow);
#endif

#if TARGET_OS_WIN32
	myWindow = GetWindow(theWindow, GW_HWNDNEXT);
#endif

	return(myWindow);
}

Handling Menus

Now we want to do the same thing for menus that we've done for windows, namely develop a unified way to refer to menus and menu items, so that we can (for instance) enable and disable menu items, or process the user's selection of menu items, in a platform-neutral manner. Happily, this is a simpler task than the one we just solved.

On the Macintosh, a particular menu item is specified using two pieces of information, the menu ID and the index of the item in the specified menu. On Windows, a menu item is specified by a single 16-bit "menu item identifier", which is an arbitrary value that we associate with the menu item. Because the value on Windows is arbitrary, we'll construct it by setting the high-order 8 bits to the Macintosh menu ID and the low-order 8-bits to the index of the menu item in the menu.

Suppose we use the following values for our resource and menu IDs in our Macintosh resource file:

#define kMenuBarResID					128
#define kAppleMenuResID				128
#define kFileMenuResID					129
#define kEditMenuResID					130

Then we can define our menu item identifiers like this:

#define IDS_FILEMENU					33024	// (kFileMenuResID<<8)+0
#define IDM_FILENEW						33025	// (kFileMenuResID<<8)+1
#define IDM_FILEOPEN					33026
#define IDM_FILECLOSE					33027
#define IDM_FILESAVE					33028
#define IDM_FILESAVEAS					33029
#define IDM_EXIT							33031

#define IDS_EDITMENU					33280	// (kEditMenuResID<<8)+0
#define IDM_EDITUNDO					33281	// (kEditMenuResID<<8)+1
#define IDM_EDITCUT						33283
#define IDM_EDITCOPY					33284
#define IDM_EDITPASTE					33285
#define IDM_EDITCLEAR					33286
#define IDM_EDITSELECTALL			33288

You might be wondering why we didn't just define, for instance, IDS_FILEMENU as (kFileMenuResID<<8)+0. In fact this works fine when developing either Mac or Windows applications using CodeWarrior, but it doesn't seem to work when using Microsoft Developer Studio on Windows machines. Go figure (literally!). Also, for those of you with calculators that don't have a "<<" key, bit-shifting left by 8 is the same as multiplying by 256.

In effect, we're simply adopting the Windows method of specifying menu items, but doing so in a manner that allows us to retrieve the Macintosh menu ID and menu item index from the arbitrary menu item identifier, using these macros:

#define MENU_IDENTIFIER(menuID,menuItem)	((menuID<<8)+(menuItem))
#define MENU_ID(menuIdentifier)				((menuIdentifier&0xff00)>>8)
#define MENU_ITEM(menuIdentifier)			((menuIdentifier&0x00ff))

Now we need to devise a way of referring to menus themselves in a cross-platform way. On the Macintosh, we access menus using variables and parameters of type MenuHandle, while on Windows, we use variables and parameters of type HMENU. Once again, we'll define a custom type that refers to either a Macintosh menu or a Windows menu, like this:

#if TARGET_OS_MAC
typedef MenuHandle				MenuReference;
#endif
#if TARGET_OS_WIN32
typedef HMENU						MenuReference;
#endif

Let's see how this all fits together in practice. Suppose we want to enable or disable a particular menu item. For instance, the user may have edited a movie window, in which case we want to make sure that the Undo menu item in the Edit menu is enabled. We can use the function QTFrame_SetMenuItemState defined in Listing 8.

Listing 8: Enabling or disabling a menu item

QTFrame_SetMenuItemState

void QTFrame_SetMenuItemState (MenuReference theMenu, UInt16 theMenuItem, short theState)
{
#if TARGET_OS_MAC
	if (theState == kEnableMenuItem)
		EnableMenuItem(theMenu, MENU_ITEM(theMenuItem));
	else
		DisableMenuItem(theMenu, MENU_ITEM(theMenuItem));
#endif
#if TARGET_OS_WIN32
	EnableMenuItem(theMenu, (UINT)theMenuItem, (UINT)theState);
#endif
}

On Windows, QTFrame_SetMenuItemState uses the Windows function EnableMenuItem to set the specified menu item to the desired state. On the Macintosh, it calls either of the two Macintosh functions EnableMenuItem or DisableMenuItem, depending on the value of theState.

QuickTime Support

Now let's get back to the task at hand, which is showing how to open and display QuickTime movies, and how to handle basic editing operations on those movies.

Opening a Movie File

Let's suppose that you've already got a file system specification record that indicates which movie file the user wants to open. You might have called the Standard File Package or (on Macintosh) the newer Navigation Services to elicit a file from the user, or you might have gotten the file specification from an Open Document Apple Event. At this point, you can call the function OpenMovieFile to open the movie file:

myErr = OpenMovieFile(&myFSSpec, &myRefNum, fsRdWrPerm);

Next you need to load the movie data from the file. You can do this by calling the NewMovieFromFile function, as follows:

myResID = 0;
myErr = NewMovieFromFile(&myMovie, myRefNum, &myResID, NULL, newMovieActive, NULL);

Now you need to create a window in which to display the movie. This operation is platform-specific: on the Macintosh, you can call the NewCWindow function, or on Windows you can call the CreateWindowEx function. Once you've successfully created a window, you just need to pass that window to the QTFrame_CreateWindowObject function defined in Listing 1, which (as you saw) allocates some memory to hold information needed by our frameworks and attaches the window object record to the window.

Attaching a Movie Controller

So, at this point, we've opened the movie file and read the movie data from it. We've also created a window (which is not yet visible), created a window object, and attached the window object to the window. Before we can make the movie window visible, we need to create a movie controller and attach it to the window. We do this by calling the framework function QTFrame_SetupController:

myMC = QTFrame_SetupController(myMovie, myWindow, true);

The QTFrame_SetupController function is a tad lengthy, so we won't show it all here. But it does only four things of any real interest: it creates a new movie controller for the specified movie, it enables movie controller editing, it adds a grow box to the movie controller bar, and it installs a movie controller action filter function. We'll discuss movie editing a little later; for now, let's see how to do the other three tasks.

Before you can create a movie controller, you need to determine the rectangle within which the movie is going to be displayed. Our basic application draws the movie at its natural size, which we can obtain by calling the GetMovieBox function (making sure that the top-left corner of the movie is at 0,0):

GetMovieBox(myMovie, &myRect);
MacOffsetRect(&myRect, -myRect.left, -myRect.top);
SetMovieBox(myMovie, &myRect);

Then we can create a movie controller with this single line of code:

myMC = NewMovieController(myMovie, &myRect, mcTopLeftMovie);

Even though we've opened the move at its natural size, we'd like to allow the user to resize the movie window. By default, the movie controller does not provide this capability, so we need to do a little work to enable movie resizing. The first thing we need to do is find the upper bounds for the movie window size. In other words, we need to find the largest rectangle that can contain a movie window. We could set an arbitrary upper bounds for our movie window size, but instead we'll allow the user to resize a movie to fit as much of the available screen space as possible. On the Macintosh, we can get this area like this:

gMCResizeBounds = (**GetGrayRgn()).rgnBBox;

(This line of code isn't Carbon-compliant, of course. We still have a little bit of work to do before our Macintosh framework is fully Carbonized.) On Windows, we can call the GetDesktopWindow and GetWindowRect functions to get the size of the desktop. Note that GetWindowRect returns a structure of type RECT, which on Windows consists of four long integers. So we need to convert the RECT into a Rect, like this:

RECT				myRect;

GetWindowRect(GetDesktopWindow(), &myRect);
OffsetRect(&myRect, -myRect.left, -myRect.top);
gMCResizeBounds.top = (short)myRect.top;
gMCResizeBounds.left = (short)myRect.left;
gMCResizeBounds.right = (short)myRect.right;
gMCResizeBounds.bottom = (short)myRect.bottom;

Then, we can enable window resizing by calling the MCDoAction function with the mcActionSetGrowBoxBounds parameter:

MCDoAction(myMC, mcActionSetGrowBoxBounds, &gMCResizeBounds);

MCDoAction is a general-purpose tool for getting a movie controller to perform various actions. (If you take a look in the header file Movies.h, you'll see well over 60 defined movie controller actions.) We'll use MCDoAction extensively throughout this series of articles. For the moment, though, it's important to know that before the movie controller performs any action initiated by MCDoAction, it informs your application of the pending action and indeed allows your application to cancel that action. To get these notifications of pending actions, you need to install a movie controller action filter function, like this:

MCSetActionFilterWithRefCon(myMC, NewMCActionFilterWithRefConProc(QTApp_MCActionFilterProc), (long)myWindowObject);

The first parameter is the movie controller to which you want to attach a filter function. The second parameter is a universal procedure pointer for the filter function itself. The third parameter is an application-specific reference constant that is passed to the filter function whenever it is called. As you can see, we're passing the window object to the filter function so that we can gain access to the movie window's data inside that function.

Listing 9 shows a typical movie controller action filter function. This function handles only one action, mcActionControllerSizeChanged, which the movie controller sends to our filter function whenever the user has dragged the resize box in the controller bar. Our job is then to resize the associated movie window.

Listing 9: Intercepting movie controller actions

QTFrame_SetMenuItemState

PASCAL_RTN Boolean QTApp_MCActionFilterProc (MovieController 		theMC, short theAction, void *theParams, long theRefCon)
{
#pragma unused(theMC, theParams)

	Boolean				isHandled = false;
	WindowObject		myWindowObject = NULL;
	
	myWindowObject = (WindowObject)theRefCon;
	if (myWindowObject == NULL)
		return(isHandled);
		
	switch (theAction) {
		// handle window resizing
		case mcActionControllerSizeChanged:
			QTFrame_SizeWindowToMovie(myWindowObject);
			break;

		default:
			break;
			
	} // switch (theAction)
	
	return(isHandled);	
}

A movie controller action filter function should return false as its function result if it wants the movie controller to handle the action specified by the theAction parameter. Conversely, it should return true if it doesn't want the movie controller to handle the action. In the case of the mcActionControllerSizeChanged action, we return false to let the controller do any processing it needs to.

Handling Events

Once we've got a movie file opened in a window and have attached a movie controller to it, we need to let the movie controller handle any events that apply to it. (For instance, if the user hits the space bar, the movie controller should start or stop the movie, depending on its current playing state.) The main thing we need to do is pass the event to the MCIsPlayerEvent function, but how we do this differs between the Mac and Windows platforms.

On Macintosh, we get events in our main event loop by calling WaitNextEvent. When we retrieve an event in this way, we don't know which, if any, movie controller it might apply to. So, we'll just pass the event to all open movie controllers until we find one that accepts it. Listing 10 shows how we do this.

Listing 10: Handling events on the Macintosh

QTFrame_CheckMovieControllers

static Boolean QTFrame_CheckMovieControllers (EventRecord *theEvent)
{	
	WindowPtr					myWindow;
	WindowObject				myWindowObject;
	MovieController			myMC;
	
	myWindow = QTFrame_GetFrontMovieWindow();
	while (myWindow != NULL) {
	 	myWindowObject = QTFrame_GetWindowObjectFromWindow(myWindow);
		if (myWindowObject != NULL) {
			myMC = (**myWindowObject).fController;
			if (myMC != NULL)
				if (MCIsPlayerEvent(myMC, theEvent))
					return(true);
		}
		
		myWindow = QTFrame_GetNextMovieWindow(myWindow);
	}

	return(false);
}

On Windows, however, system and user actions are handled somewhat differently. Windows sends messages describing those actions directly to the window procedure of the target window, so we know in advance which movie controller the action might apply to. The only "gotcha" is that MCIsPlayerEvent wants to get Macintosh-style events, not Windows-style messages. So, we need to call the function WinEventToMacEvent to translate the Windows message into a Macintosh event, as shown in Listing 11.

Listing 11: Handling events on Windows

QTFrame_MovieWndProc

WinEventToMacEvent(&myMsg, &myMacEvent);
// pass the Mac event to the movie controller if the movie window isn't minimized
if (!IsIconic(theWnd))
	MCIsPlayerEvent(myMC, (EventRecord *)&myMacEvent);

Editing a Movie

Some QuickTime movie controllers support the typical cut, copy, paste, and clear editing operations through a handful of high-level functions that are very easy to use. By default, a newly-created movie controller has editing turned off, so we need to explicitly turn it on, like this:

MCEnableEditing(myMC, true);

If you're interested in finding out whether a particular movie controller supports editing, you can inspect the value returned by MCEnableEditing, which is non-zero if the specified movie controller does not support editing. We'll ignore that return value here, since we'll dynamically determine whether a movie controller supports editing whenever we adjust our application's menus.

When the user selects an item in our Edit menu (or performs some equivalent keyboard operation), we simply need to call the corresponding movie controller function. For instance, if the user selects the Cut menu item, we'll call the MCCut function and also set a flag to indicate that the movie's data has changed:

myMovie = MCCut(myMC);
(**myWindowObject).fIsDirty = true;

MCCut removes the current movie selection from the movie associated with the specified movie controller. Moreover, MCCut returns the cut movie selection to us; this is useful if we want to allow the user to paste that segment back into the same movie (or indeed into some other movie). We need to call the PutMovieOnScrap function if we want to allow that segment to be pasted. Listing 12 shows our entire function for handling the Edit menu.

Listing 12: Handling items in the Edit menu

QTFrame_HandleEditMenuItem

void QTFrame_HandleEditMenuItem (WindowReference theWindow, 		UInt16 theMenuItem)
{
	WindowObject			myWindowObject = NULL;
	MovieController		myMC = NULL;
	Movie						myMovie = NULL;
	
	myWindowObject = QTFrame_GetWindowObjectFromWindow(theWindow);
	myMC = QTFrame_GetMCFromWindow(theWindow);
	
	// make sure we have a valid movie controller and a valid window object
	if ((myMC == NULL) || (myWindowObject == NULL))
		return;

	switch (theMenuItem) {
		case IDM_EDITUNDO:
			MCUndo(myMC);
			(**myWindowObject).fIsDirty = true;	break;
		case IDM_EDITCUT:
			myMovie = MCCut(myMC);
			(**myWindowObject).fIsDirty = true;	break;
		case IDM_EDITCOPY:
			myMovie = MCCopy(myMC);				break;
		case IDM_EDITPASTE:
			MCPaste(myMC, NULL);
			(**myWindowObject).fIsDirty = true;	break;
		case IDM_EDITCLEAR:
			MCClear(myMC);
			(**myWindowObject).fIsDirty = true;	break;
		case IDM_EDITSELECTALL:
			QTUtils_SelectAllMovie(myMC);		break;
		default:								break;
	} // switch (theMenuItem)
	
	// place any cut or copied movie segment onto the global scrap
	if (myMovie != NULL) {
		PutMovieOnScrap(myMovie, 0L);
		DisposeMovie(myMovie);
	}
}

The movie controller also provides an easy way for us to determine which Edit menu items need to be enabled or disabled at any time. The function MCGetControllerInfo returns a 32-bit set of flags that we can inspect to see the current status of the movie controller, such as whether the user has edited the movie. So, to enable or disable the Undo menu command, we can use code like this:

MCGetControllerInfo(myMC, &myFlags);
if (myFlags & mcInfoUndoAvailable)
	QTFrame_SetMenuItemState(myMenu, IDM_EDITUNDO,
			kEnableMenuItem);
else
	QTFrame_SetMenuItemState(myMenu, IDM_EDITUNDO, kDisableMenuItem);

See the function QTFrame_AdjustMenus in ComFramework.c for the complete story on adjusting all the Edit (and File) menu items.

Conclusion

Well, we've accomplished what we set out to do, namely show how to open QuickTime movie files in movie windows and handle user interactions with those movies. Along the way, we also took a fairly long look at some of the techniques we can use, here and in the future, to make sure that our code operates identically on both Macintosh and Windows operating systems. By spending the time now getting to understand how to use the platform-independent utilities defined in ComFramework.c, we have (I hope) made it easier for us to spend our time later focussing on the QuickTime-specific tasks we want to accomplish.

Before we close up shop, let's take a minute to discuss the source code that accompanies this month's article. I have included three project files created with Metrowerk's CodeWarrrior IDE version 4.0.2. These projects build PowerPC, 68K, and Windows versions of an application called QTShell, which is our bare-bones QuickTime viewing and editing application. I have also included the file QTShell.mak, which you can use with Microsoft Developer Studio to build a Windows application, if you prefer programming on a Windows machine. (The CodeWarrior project files should also work when using CodeWarrior on Windows, but I have not tested this.)

No matter which platform or development environment you develop on, you'll need to obtain the QuickTime-specific header files, libraries, and (on Windows) resource-building tools. These are available on the QuickTime Software Development Kit (SDK) CD, which I highly recommend if you really want to do some serious QuickTime programming. The same header files, libraries, and tools are found on the CD included in the excellent book Discovering QuickTime by George Towner (available through the DevDepot at http://www.devdepot.com). Finally, you can download the necessary files from the QuickTime web site at http://www.apple.com/quicktime/developers.

Credits

The basic Windows framework for the QTShell source code (as contained in the file WinFramework.c) is based on an earlier sample code package by Brian Friedkin, called MDIPlayer. The basic Macintosh framework (as contained in the file MacFramework.c) and the factoring of any application-specific code into the file ComAppliction.c is based on an earlier sample code package by Kent Sandvik, called MovieShell.


Tim Monroe (monroe@apple.com) is a software engineer on Apple's QuickTime team. He is currently developing sample code and utilities for the QuickTime software development kit.

 

Community Search:
MacTech Search:

Software Updates via MacUpdate

Latest Forum Discussions

See All

Whitethorn Games combines two completely...
If you have ever gone fishing then you know that it is a lesson in patience, sitting around waiting for a bite that may never come. Well, that's because you have been doing it wrong, since as Whitehorn Games now demonstrates in new release Skate... | Read more »
Call of Duty Warzone is a Waiting Simula...
It's always fun when a splashy multiplayer game comes to mobile because they are few and far between, so I was excited to see the notification about Call of Duty: Warzone Mobile (finally) launching last week and wanted to try it out. As someone who... | Read more »
Albion Online introduces some massive ne...
Sandbox Interactive has announced an upcoming update to its flagship MMORPG Albion Online, containing massive updates to its existing guild Vs guild systems. Someone clearly rewatched the Helms Deep battle in Lord of the Rings and spent the next... | Read more »
Chucklefish announces launch date of the...
Chucklefish, the indie London-based team we probably all know from developing Terraria or their stint publishing Stardew Valley, has revealed the mobile release date for roguelike deck-builder Wildfrost. Developed by Gaziter and Deadpan Games, the... | Read more »
Netmarble opens pre-registration for act...
It has been close to three years since Netmarble announced they would be adapting the smash series Solo Leveling into a video game, and at last, they have announced the opening of pre-orders for Solo Leveling: Arise. [Read more] | Read more »
PUBG Mobile celebrates sixth anniversary...
For the past six years, PUBG Mobile has been one of the most popular shooters you can play in the palm of your hand, and Krafton is celebrating this milestone and many years of ups by teaming up with hit music man JVKE to create a special song for... | Read more »
ASTRA: Knights of Veda refuse to pump th...
In perhaps the most recent example of being incredibly eager, ASTRA: Knights of Veda has dropped its second collaboration with South Korean boyband Seventeen, named so as it consists of exactly thirteen members and a video collaboration with Lee... | Read more »
Collect all your cats and caterpillars a...
If you are growing tired of trying to build a town with your phone by using it as a tiny, ineffectual shover then fear no longer, as Independent Arts Software has announced the upcoming release of Construction Simulator 4, from the critically... | Read more »
Backbone complete its lineup of 2nd Gene...
With all the ports of big AAA games that have been coming to mobile, it is becoming more convenient than ever to own a good controller, and to help with this Backbone has announced the completion of their 2nd generation product lineup with their... | Read more »
Zenless Zone Zero opens entries for its...
miHoYo, aka HoYoverse, has become such a big name in mobile gaming that it's hard to believe that arguably their flagship title, Genshin Impact, is only three and a half years old. Now, they continue the road to the next title in their world, with... | Read more »

Price Scanner via MacPrices.net

Deal Alert! B&H Photo has Apple’s 14-inch...
B&H Photo has new Gray and Black 14″ M3, M3 Pro, and M3 Max MacBook Pros on sale for $200-$300 off MSRP, starting at only $1399. B&H offers free 1-2 day delivery to most US addresses: – 14″ 8... Read more
Department Of Justice Sets Sights On Apple In...
NEWS – The ball has finally dropped on the big Apple. The ball (metaphorically speaking) — an antitrust lawsuit filed in the U.S. on March 21 by the Department of Justice (DOJ) — came down following... Read more
New 13-inch M3 MacBook Air on sale for $999,...
Amazon has Apple’s new 13″ M3 MacBook Air on sale for $100 off MSRP for the first time, now just $999 shipped. Shipping is free: – 13″ MacBook Air (8GB RAM/256GB SSD/Space Gray): $999 $100 off MSRP... Read more
Amazon has Apple’s 9th-generation WiFi iPads...
Amazon has Apple’s 9th generation 10.2″ WiFi iPads on sale for $80-$100 off MSRP, starting only $249. Their prices are the lowest available for new iPads anywhere: – 10″ 64GB WiFi iPad (Space Gray or... Read more
Discounted 14-inch M3 MacBook Pros with 16GB...
Apple retailer Expercom has 14″ MacBook Pros with M3 CPUs and 16GB of standard memory discounted by up to $120 off Apple’s MSRP: – 14″ M3 MacBook Pro (16GB RAM/256GB SSD): $1691.06 $108 off MSRP – 14... Read more
Clearance 15-inch M2 MacBook Airs on sale for...
B&H Photo has Apple’s 15″ MacBook Airs with M2 CPUs (8GB RAM/256GB SSD) in stock today and on clearance sale for $999 in all four colors. Free 1-2 delivery is available to most US addresses.... Read more
Clearance 13-inch M1 MacBook Airs drop to onl...
B&H has Apple’s base 13″ M1 MacBook Air (Space Gray, Silver, & Gold) in stock and on clearance sale today for $300 off MSRP, only $699. Free 1-2 day shipping is available to most addresses in... Read more
New promo at Visible: Buy a new iPhone, get $...
Switch to Visible, and buy a new iPhone, and Visible will take $10 off their monthly Visible+ service for 24 months. Visible+ is normally $45 per month. With this promotion, the cost of Visible+ is... Read more
B&H has Apple’s 13-inch M2 MacBook Airs o...
B&H Photo has 13″ MacBook Airs with M2 CPUs and 256GB of storage in stock and on sale for $100 off Apple’s new MSRP, only $899. Free 1-2 day delivery is available to most US addresses. Their... Read more
Take advantage of Apple’s steep discounts on...
Apple has a full line of 16″ M3 Pro and M3 Max MacBook Pros available, Certified Refurbished, starting at $2119 and ranging up to $600 off MSRP. Each model features a new outer case, shipping is free... Read more

Jobs Board

Medical Assistant - Surgical Oncology- *Apple...
Medical Assistant - Surgical Oncology- Apple Hill Location: WellSpan Medical Group, York, PA Schedule: Full Time Sign-On Bonus Eligible Remote/Hybrid Regular Apply Read more
Omnichannel Associate - *Apple* Blossom Mal...
Omnichannel Associate - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple 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
Operations Associate - *Apple* Blossom Mall...
Operations Associate - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple Read more
Business Analyst | *Apple* Pay - Banco Popu...
Business Analyst | Apple PayApply now " Apply now + Apply Now + Start applying with LinkedIn Start + Please wait Date:Mar 19, 2024 Location: San Juan-Cupey, PR Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.