TweetFollow Us on Twitter

Loaded

Volume Number: 18 (2002)
Issue Number: 9
Column Tag: QuickTime Toolkit

Loaded

Loading QuickTime Movies Asynchronously

by Tim Monroe

Introduction

Typically, we use the NewMovieFromFile function to load a QuickTime movie from a file stored locally. In an earlier article, when we were investigating data references, we saw how to use the NewMovieFromDataRef function to load a movie specified by a URL that picks out a file located on a remote server. (See "Somewhere I'll Find You" in MacTech, October 2000.) In all cases, we have called these functions synchronously. This means that we wait until the function completes and returns a movie to us before we continue on to attach a movie controller to the movie, create a window to hold the movie and the movie controller bar, size and position the movie window appropriately, and so forth.

The drawback with opening movies synchronously is that there may be a perceptible lag as QuickTime retrieves the movie data from the specified location, especially if the data is on a remote server and the user has a relatively slow connection to the network. In version 4.1, QuickTime introduced a mechanism for loading movies asynchronously. In this case, the movie-loading functions (NewMovieFromFile, NewMovieFromDataRef, and their siblings) return almost immediately with a valid movie identifier. This movie, however, might be empty or incomplete, if its data is still being retrieved from a remote location (or a particularly slow local storage device). Before we can actually do anything with the newly-opened movie, we need to wait until at least the movie atom has become available. We can determine this by continually checking the movie load state, an indication of the state of the loading process and hence what operations we can safely perform on the movie. When the movie load state reaches a particular threshold, we can continue on with our standard procedure for displaying the movie in a window on the screen and allowing the user to interact with the movie.

In this article, we're going to learn how to load movies asynchronously. We'll see how to modify our calls to the NewMovieFrom... functions and how to check the movie load state as the movie data loads. The end result of this tinkering should be a more responsive application, since the user will no longer have to wait until a movie is sufficiently loaded for our application to continue processing. Instead, the user can work with any movies that are already open, or indeed open still other movies.

We'll also take the opportunity to add a few bells and whistles to our application. In particular, we'll see how to display a progress bar that indicates how much of the movie data has become available, as shown in Figure 1. (The purple bar grows from left to right in proportion to the amount of movie data loaded.) We do this by drawing on top of each movie frame, using a movie drawing-complete procedure. Inside this procedure, we call the GetMaxLoadedTimeInMovie function to determine how much of the movie has loaded, and then we scale the progress bar accordingly.


Figure 1: An application movie loading progress bar

It's also possible for a QuickTime movie to provide its own loading progress bar, without any assistance from the playback application. We can accomplish this by adding a wired sprite track or a wired Flash track to the movie. The key element here is the kOperandMaxLoadedTimeInMovie wired operand, which returns the same information as the GetMaxLoadedTimeInMovie function. Figure 2 shows a QuickTime movie that draws its own loading progress bar; here the loading progress bar is a sprite whose horizontal position changes based on the movie load time.


Figure 2: A sprite movie loading progress bar

Our sample application this month is called QTAsynchLoad; it's based on the version of QTShell that uses Carbon events (which we developed in "Event Horizon", MacTech, May 2002). Figure 3 shows the Test menu of QTAsynchLoad.


Figure 3: The Test menu of QTAsynchLoad

The first menu item prompts the user for a URL and then opens the movie file picked out by that URL. (I borrowed the code for this directly from the QTDataRef application we developed in the data references article cited earlier.) This is useful for testing that our new code really does load remote movies asynchronously. The second menu item adds to an existing movie a sprite track that displays the movie loading progress bar shown in Figure 2. The third menu item exports a QuickTime VR panorama movie as a Fast Start movie file with a low-resolution image track; this track is loaded first and acts as a kind of preview track for the entire panorama, as we'll see later.

Asynchronous Movie Loading

Let's begin by reviewing our current movie loading strategy. When a user selects the "Open..." command in the File menu, we present a file-opening dialog box and then pass the selected file to the OpenMovieFile function, like this:

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

OpenMovieFile opens the file with the specified access permissions (here, with read-write access). If it's successful, we then call NewMovieFromFile to load the movie in that file:

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

If NewMovieFromFile is successful, we then perform these actions:

  • Create a window to hold the movie and movie controller bar.

  • Create a window object to hold information about the movie and movie window.

  • Set the movie graphics world to the new movie window.

  • Create a new movie controller for the new movie.

  • Resize the window to exactly fit the movie and movie controller bar.

  • Set the window position from information stored in the movie user data.

  • Make the window visible.

  • Start the movie playing, if it's an auto-play movie.

Currently, all this is accomplished inside of our framework function QTFrame_OpenMovieInWindow.

Allowing Asynchronous Movie Loading

The important point here is that our call to NewMovieFromFile will not return until enough of the movie is available that QuickTime considers the movie to be configurable and playable. We can override this default behavior by including the newMovieAsyncOK flag when we call NewMovieFromFile, like this:

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

The newMovieAsyncOK flag tells NewMovieFromFile that it should create a new movie and return it to the caller as quickly as possible, even if the new movie is empty (that is, contains no tracks, no movie atom, no user data, and so forth). In general, for local movie files the new movie is a fully valid movie, complete with tracks and movie user data. That is to say, the movie loading occurs exactly as if the newMovieAsyncOK flag had not been specified. This flag simply grants permission to QuickTime to use asynchronous loading if possible; it does not force the movie to be loaded asynchronously.

The more interesting case is therefore when we open a movie stored remotely, which we can do by passing a URL to the QTDR_GetMovieFromURL function we defined in an earlier article. To enable asynchronous movie loading, we once again add the newMovieAsyncOK flag, as shown in Listing 1.

Listing 1: Opening a movie specified by a URL

QTDR_GetMovieFromURL
Movie QTDR_GetMovieFromURL (char *theURL)
{
   Movie      myMovie = NULL;
   Handle      myDataRef = NULL;
   short      myFlags = newMovieActive;
#if ALLOW_ASYNCH_LOADING
   myFlags += newMovieAsyncOK;
#endif
   myDataRef = QTDR_MakeURLDataRef(theURL);
   if (myDataRef != NULL) {
      NewMovieFromDataRef(&myMovie, myFlags, NULL, myDataRef, 
            URLDataHandlerSubType);
      DisposeHandle(myDataRef);
   }
   return(myMovie);
}

You'll notice the ALLOW_ASYNCH_LOADING compiler flag; once again, I'm going to try to rework our source code so that the existing synchronous movie-loading behavior can be re-enabled by changing the value of that flag.

Checking the Movie Load State

Since the movie returned by NewMovieFromDataRef (or its siblings) may be empty, we can't just launch into our standard sequence of calls for creating and configuring a new movie window. Instead, we need to wait until the movie reaches the proper load state. We can get the current load state by calling GetMovieLoadState, like this:

myLoadState = GetMovieLoadState(myMovie);
GetMovieLoadState currently returns one of these values:
enum {
   kMovieLoadStateError                     = -1L,
   kMovieLoadStateLoading                  = 1000,
   kMovieLoadStateLoaded                     = 2000,
   kMovieLoadStatePlayable                  = 10000,
   kMovieLoadStatePlaythroughOK            = 20000,
   kMovieLoadStateComplete                  = 100000L
};

If GetMovieLoadState returns kMovieLoadStateError, then an error has occurred during the movie load process. This probably means that a URL could not be successfully resolved, but it could also mean that the indicated file is not a QuickTime movie file (or other file type that can be imported as a QuickTime movie).

If GetMovieLoadState returns kMovieLoadStateLoading, then QuickTime has found the specified file (or data stream) and is searching for the movie atom in it. If GetMovieLoadState returns kMovieLoadStateLoaded, then QuickTime has found the movie atom, but there may not be enough media data available to begin playing the movie. (It's not clear to me that GetMovieLoadState ever actually returns kMovieLoadStateLoaded; I suspect that this state has fallen into disuse.)

When a movie's load state reaches kMovieLoadStatePlayable, the movie atom is available and enough of the media data is available that the movie can be prerolled and started. This is the threshold that is most interesting to us, since we can then look at the movie user data (which is stored in the movie atom) to determine the desired window location of the movie window; we can also make Movie Toolbox calls like GetMovieTrackCount to determine the number of tracks in the movie or GetMovieBox to determine the size of the movie.

GetMovieLoadState returns kMovieLoadStatePlaythroughOK when QuickTime thinks that it has enough of the media data that the entire movie can be played through to the end without stopping. Finally, GetMovieLoadState returns kMovieLoadStateComplete when all of the movie's media data is available.

Modifying the Application Framework

If we allow a movie to be opened asynchronously, then we need to wait until the movie reaches at least the kMovieLoadStatePlayable load state before we assign a movie controller to it and undertake any other configuration of the movie or movie controller. The first thing we need to do, then, is revise our framework function QTFrame_OpenMovieInWindow. Listing 2 shows the relevant portions of our new version of this function. As you can see, we open a new, invisible window, allocate a new window object, and set the movie (or graphics importer) graphics world to the new window. Then, if the user is opening an image file instead of a movie file, we proceed to set up the image window, as we did previously.

Listing 2: Opening a movie in a new window

QTFrame_OpenMovieInWindow
// create a new window to display the movie in
myWindow = QTFrame_CreateMovieWindow();
if (myWindow == NULL)
   goto bail;
myWindowObject = QTFrame_GetWindowObjectFromWindow(myWindow);
if (myWindowObject == NULL)
   goto bail;
// set the window title
QTFrame_SetWindowTitleFromFSSpec(myWindow, &myFSSpec, true);
// make sure the movie or image file uses the window GWorld
if (myMovie != NULL)
   SetMovieGWorld(myMovie, 
         (CGrafPtr)QTFrame_GetPortFromWindowReference(myWindow), 
         NULL);
if (myImporter != NULL)
   GraphicsImportSetGWorld(myImporter, 
         (CGrafPtr)QTFrame_GetPortFromWindowReference(myWindow), 
         NULL);
// store movie info in the window record
(**myWindowObject).fMovie = myMovie;
(**myWindowObject).fController = NULL;
(**myWindowObject).fGraphicsImporter = myImporter;
(**myWindowObject).fFileResID = myResID;
(**myWindowObject).fFileRefNum = myRefNum;
(**myWindowObject).fCanResizeWindow = true;
(**myWindowObject).fIsDirty = false;
(**myWindowObject).fIsQTVRMovie = false;
(**myWindowObject).fInstance = NULL;
(**myWindowObject).fAppData = NULL;
(**myWindowObject).fFileFSSpec = myFSSpec;
if ((**myWindowObject).fGraphicsImporter != NULL) {
   Point         myPoint = {kDefaultWindowX, kDefaultWindowY};
   // do any application-specific window object initialization
   QTApp_SetupWindowObject(myWindowObject);
   // size the window to fit the image
   QTFrame_SizeWindowToMovie(myWindowObject);
   // show the window
   QTFrame_ShowWindowAtPoint(myWindow, &myPoint);
}

Mostly we've just removed our movie-specific processing, which we want to defer until we know the movie is playable. We've also added a call to the QTFrame_ShowWindowAtPoint function, which makes the image or movie window visible. Listing 3 shows our definition of QTFrame_ShowWindowAtPoint.

Listing 3: Making a window visible

QTFrame_ShowWindowAtPoint
void QTFrame_ShowWindowAtPoint 
               (WindowReference theWindow, Point *thePoint)
{
#if TARGET_OS_MAC
   Rect         myRect;
#endif
   if ((theWindow == NULL) || (thePoint == NULL))
      return;
#if TARGET_OS_MAC
   MoveWindow(theWindow, thePoint->h, thePoint->v, false);
   MacShowWindow(theWindow);
   SelectWindow(theWindow);
   InvalWindowRect(theWindow, 
            GetWindowPortBounds(theWindow, &myRect));
#endif
#if TARGET_OS_WIN32
   SetWindowPos(theWindow, 0, thePoint->h, thePoint->v, 0, 0, 
            SWP_NOZORDER | SWP_NOSIZE);
   ShowWindow(theWindow, SW_SHOW);
   UpdateWindow(theWindow);
#endif
}

At this point, we need to decide where in our application code we want to call GetMovieLoadState. On Macintosh operating systems, our applications call QTFrame_CheckMovieControllers to give each open movie controller a chance to handle an event. This is also a good place to check the current load state of a movie and to respond to any changes in the movie load state. Listing 4 shows our current version of QTFrame_CheckMovieControllers.

Listing 4: Checking the movie controllers (original)

QTFrame_CheckMovieControllers
static Boolean QTFrame_CheckMovieControllers 
            (EventRecord *theEvent)
{   
   WindowPtr            myWindow = NULL;
   MovieController      myMC = NULL;
   myWindow = QTFrame_GetFrontMovieWindow();
   while (myWindow != NULL) {
      myMC = QTFrame_GetMCFromWindow(myWindow);
      if (myMC != NULL)
         if (MCIsPlayerEvent(myMC, theEvent))
            return(true);
      myWindow = QTFrame_GetNextMovieWindow(myWindow);
   }
   return(false);
}

Listing 5 shows our revised version of QTFrame_CheckMovieControllers. We've added a call to QTFrame_CheckMovieLoadState, and we've revised the looping so that QTFrame_CheckMovieLoadState can safely destroy the movie window if it determines that an error has occurred in the movie loading (that is, if GetMovieLoadState returns kMovieLoadStateError).

Listing 5: Checking the movie controllers (revised)

QTFrame_CheckMovieControllers
static Boolean QTFrame_CheckMovieControllers 
            (EventRecord *theEvent)
{   
   WindowPtr            myWindow = NULL;
   MovieController      myMC = NULL;
   myWindow = QTFrame_GetFrontMovieWindow();
   while (myWindow != NULL) {
      WindowPtr         myNextWindow = QTFrame_GetNextMovieWindow
                                                   (myWindow);
      QTFrame_CheckMovieLoadState(
                  QTFrame_GetWindowObjectFromWindow(myWindow));
      myMC = QTFrame_GetMCFromWindow(myWindow);
      if (myMC != NULL)
         if (MCIsPlayerEvent(myMC, theEvent))
            return(true);
      myWindow = myNextWindow;
   }
   return(false);
}

On Windows operating systems, messages are sent directly to the window procedure of a movie window, where we translate the message into a Mac-style event. Here we'll call QTFrame_CheckMovieLoadState from within QTFrame_MovieWndProc, as shown in Listing 6.

Listing 6: Checking the movie controller (Windows)

QTFrame_MovieWndProc
// translate a Windows event to a Mac event
WinEventToMacEvent(&myMsg, &myMacEvent);
// let the application-specific code have a chance to intercept the event
myIsHandled = QTApp_HandleEvent(&myMacEvent);
if (myWindowObject != NULL) {
   QTFrame_CheckMovieLoadState(myWindowObject);
   if (myWindowObject == NULL)
      return(0);
   myMC = (**myWindowObject).fController;      // refresh our local variable
}
// pass the Mac event to the movie controller
if (!myIsHandled)
   if (myMC != NULL)
      if (!IsIconic(theWnd))
         myIsHandled = MCIsPlayerEvent(myMC, 
                                 (EventRecord *)&myMacEvent);

As you can see, we call QTFrame_CheckMovieLoadState and then immediately check to see whether the window object has been destroyed. Again, this is to protect ourselves in case an error occurs when attempting to load the movie. Also, we refresh the local variable myMC, since QTFrame_CheckMovieLoadState might have created a new movie controller for the movie.

Handling Load State Changes

So, all we need to do now is define the function QTFrame_CheckMovieLoadState. This function calls GetMovieLoadState and then inspects the returned movie load state to determine how to proceed. As we've already mentioned, we want to close the movie window that we created in QTFrame_OpenMovieInWindow if the movie load state is kMovieLoadStateError. Listing 7 shows the code we execute in this case.

Listing 7: Handling a movie load error

QTFrame_CheckMovieLoadState
if (myLoadState <= kMovieLoadStateError) {
   // close the movie window
   if ((**theWindowObject).fWindow != NULL)
      QTFrame_DestroyMovieWindow((**theWindowObject).fWindow);
   myErr = invalidMovie;
}

Here we test to see whether the movie load state is less than or equal to kMovieLoadStateError. Apple reserves the right to define other movie load states in the future, so all that really matters is whether the load state has reached a certain threshold. That's why we'll test for ranges of values in our code.

When an error has not occurred, we next want to see whether we've reached the playable state. If not, we need to task the movie so that it gets time to load:

else if (myLoadState < kMovieLoadStatePlayable) {
   // task the movie so it gets time to load
   MoviesTask(myMovie, 1);
}

The first stage at which we do something interesting is when the movie is playable. At this point, we need to create and configure a movie controller if it has not already been created. Here's where we'll put most of the code that we cut out of our synchronous version of QTFrame_OpenMovieInWindow. Listing 8 shows our complete definition of QTFrame_CheckMovieLoadState.

Listing 8: Checking the movie load state

QTFrame_CheckMovieLoadState
OSErr QTFrame_CheckMovieLoadState 
            (WindowObject theWindowObject)
{
   Movie                     myMovie = NULL;
   MovieController         myMC = NULL;
   long                        myLoadState = 0L;
   long                        myPrevState = 0L;
   OSErr                     myErr = noErr;
   if (theWindowObject == NULL)
      return(paramErr);
   // if the window contains an image, we can return
   if ((**theWindowObject).fGraphicsImporter != NULL)
      return(noErr);
   // if the window does not contain a movie, we can return
   myMovie = (**theWindowObject).fMovie;
   if (myMovie == NULL)
      return(paramErr);
#if TARGET_OS_WIN32
   // if we are adjusting the window location or size, don't go any further
   if (gWeAreSizingWindow)
      return(noErr);
#endif
   myMC = (**theWindowObject).fController;
#if ALLOW_ASYNCH_LOADING
   // get the previous load state
   myPrevState = (**theWindowObject).fLoadState;
#endif
   // if we're already fully loaded and configured, we can return
   if ((myPrevState >= kMovieLoadStateComplete) && 
            (myMC != NULL))
      return(noErr);
   // get the current load state
   myLoadState = GetMovieLoadState(myMovie);
#if ALLOW_ASYNCH_LOADING
   // remember the new state
   (**theWindowObject).fLoadState = myLoadState;
#endif
   // process the movie according to its current load state
   if (myLoadState <= kMovieLoadStateError) {
      // an error occurred while attempting to load the movie; close the movie window
      if ((**theWindowObject).fWindow != NULL)
         QTFrame_DestroyMovieWindow(
               (**theWindowObject).fWindow);
      myErr = invalidMovie;
   } else if (myLoadState < kMovieLoadStatePlayable) {
      // we're not playable yet; task the movie so it gets time to load
      MoviesTask(myMovie, 1);
   } else {
      // we are now playable;
      // if we haven't set up the movie and movie controller, do so now
      if (myMC == NULL) {
         WindowReference      myWindow = 
                                          (**theWindowObject).fWindow;
         Point            myPoint;
         if (myWindow == NULL)
            return(paramErr);
         // set the default progress procedure for the movie
         SetMovieProgressProc(myMovie, (MovieProgressUPP)-1, 0);
         // make sure that the movie is active
         SetMovieActive(myMovie, true);
         // create and configure the movie controller
         myMC = QTFrame_SetupController(myMovie, myWindow, 
            true);
         (**theWindowObject).fController = myMC;
         (**theWindowObject).fIsQTVRMovie = 
            QTUtils_IsQTVRMovie(myMovie);
         // do any application-specific window object initialization
         QTApp_SetupWindowObject(theWindowObject);
         // size the window to fit the movie and controller
         QTFrame_SizeWindowToMovie(theWindowObject);
         // set the movie's play hints to allow dynamic resizing
         SetMoviePlayHints(myMovie, hintsAllowDynamicResize, 
               hintsAllowDynamicResize);
         // set the movie's position, if it has a 'WLOC' user data atom
         QTUtils_GetWindowPositionFromFile(myMovie, &myPoint);
         // show the window
         QTFrame_ShowWindowAtPoint(myWindow, &myPoint);
         if (myMC != NULL) {
            // if the movie is a play-all-frames movie, tell the movie controller
            if (QTUtils_IsPlayAllFramesMovie(myMovie))
               MCDoAction(myMC, mcActionSetPlayEveryFrame, 
                           (void *)true);
#if !ALLOW_ASYNCH_LOADING
            // if the movie is an autoplay movie, then start it playing immediately
            if (QTUtils_IsAutoPlayMovie(myMovie))
               MCDoAction(myMC, mcActionPrerollAndPlay, 
                           (void *)GetMoviePreferredRate(myMovie));
#endif
         }
      }
#if ALLOW_ASYNCH_LOADING
      // if we can play through to the end and we have an autoplay movie, start it playing
      if (myLoadState >= kMovieLoadStatePlaythroughOK) {
         if ((myPrevState < kMovieLoadStatePlaythroughOK) && 
            (myMC != NULL)) {
            // if the movie is an autoplay movie, then start it playing immediately
            if (QTUtils_IsAutoPlayMovie(myMovie))
               MCDoAction(myMC, mcActionPrerollAndPlay, 
                           (void *)GetMoviePreferredRate(myMovie));
         }
      }
#endif
   }
   // do any application-specific processing
   if (myErr == noErr)
      myErr = QTApp_CheckMovieLoadState(theWindowObject, 
               myLoadState, myPrevState);
   return(myErr);
}

You'll notice that, on Windows, we check the global variable gWeAreSizingWindow to see whether we are in the middle of resizing a movie window; if so, we don't continue. This helps us avoid problems when we call QTFrame_ShowWindowAtPoint (which, on Windows, will cause several messages to get sent to the movie, which will eventually trigger a stack overflow).

Notice also that, near the end of QTFrame_CheckMovieLoadState, we call the function QTApp_CheckMovieLoadState, which is defined in the file ComApplication.c. This provides an easy way for us to implement application-specific movie loading behaviors without having to change the underlying framework. In a moment, we'll see how to use this function to control the loader progress bar we draw on top of loading movies.

Adjusting Menu Items

There is one final change we should make to support asynchronous movie loading: we need to prevent the user from exporting or saving a movie file if the movie is not yet fully loaded. That is to say, we should count a movie as savable only if all the media data is available. So we'll add a few lines of code to the QTFrame_AdjustMenus function. Listing 9 shows the segment of QTFrame_AdjustMenus that adjusts the Save and Save As menu items.

Listing 9: Adjusting the Save and Save As menu items

QTFrame_AdjustMenus
if (myWindowObject != NULL) {
   QTFrame_SetMenuItemState(myMenu, IDM_FILESAVEAS, 
            kEnableMenuItem);
   QTFrame_SetMenuItemState(myMenu, IDM_FILESAVE, 
            (**myWindowObject).fIsDirty ? kEnableMenuItem : 
                                                         kDisableMenuItem);
#if ALLOW_ASYNCH_LOADING
   // a movie is savable only if it's completely loaded
   if ((**myWindowObject).fMovie != NULL) {
      if (GetMovieLoadState((**myWindowObject).fMovie) 
                                 < kMovieLoadStateComplete) {
         QTFrame_SetMenuItemState(myMenu, IDM_FILESAVEAS, 
            kDisableMenuItem);
         QTFrame_SetMenuItemState(myMenu, IDM_FILESAVE, 
            kDisableMenuItem);
      }
   }
#endif
} else {
   QTFrame_SetMenuItemState(myMenu, IDM_FILESAVEAS, 
            kDisableMenuItem);
   QTFrame_SetMenuItemState(myMenu, IDM_FILESAVE, 
            kDisableMenuItem);
}

If your application supports other menu items that require all the media data to be available (such as Export or Publish), you should adjust those items as well.

Movie Drawing-Complete Procedures

We've learned how to load QuickTime movies asynchronously, on both Macintosh and Windows operating systems. Now let's see how to add the movie loading progress bar we illustrated earlier, in Figure 1. Figure 4 shows another example of the progress bar at work.


Figure 4: A movie load progress bar on a QuickTime movie

You might have noticed that the standard movie controller already gives us this information, by filling in the time slider rectangle in the movie controller bar. Our progress bar is somewhat more general, however, since it also works for movies that use the QuickTime VR movie controller or the no-interface movie controller. Figure 5 shows our load progress bar drawn on top of a QuickTime VR movie.


Figure 5: A movie load progress bar on a QuickTime VR movie

It's actually quite simple to determine the size of the progress bar while a movie is downloading, using the GetMaxLoadedTimeInMovie function. This function returns the duration of the part of the movie that has already downloaded. For example:

GetMaxLoadedTimeInMovie(theMovie, &myTimeValue);

Here, myTimeValue will contain the duration, in movie time units, of the part of the movie that is available. For example, if the movie's duration is 18000 (say, 30 seconds long with a time scale of 600), then GetMaxLoadedTimeInMovie will return 4500 if the movie is one-quarter downloaded. So if we know the dimensions of the movie (myMovieRect), we can calculate the rectangle for the progress bar like this:

myLoadRect.left = myMovieRect.left;
myLoadRect.bottom = myMovieRect.bottom;
myLoadRect.top = myLoadRect.bottom - kLoaderBarHeight;
myLoadRect.right = myLoadRect.left + 
            (((myMovieRect.right - myMovieRect.left) * 
            myTimeValue) / GetMovieDuration(theMovie));

GetMaxLoadedTimeInMovie returns positive values only once the movie has become playable. If the movie load state is less than kMovieLoadStatePlayable, the returned value is always 0. And of course once the load state reaches kMovieLoadStateComplete, the returned value will be the duration of the movie.

Drawing on Top of a Movie

We can draw our progress bar on top of a movie using a movie drawing-complete procedure. This is a callback function that is executed whenever QuickTime has finished drawing a new frame of a movie. A movie drawing-complete procedure can do all sorts of fun things. In the present case, we'll just fill the progress bar rectangle with a solid color:

PaintRect(&myLoadRect);

Then we'll outline the rectangle with a different color:

MacFrameRect(&myLoadRect);

Our movie drawing-complete procedure is declared like this:

PASCAL_RTN OSErr QTAL_MovieDrawingCompleteProc 
            (Movie theMovie, long theRefCon)

The first parameter is the movie, of course, and the second parameter is an application-specific reference constant. As usual, we'll specify the movie's window object as the reference constant. Listing 10 shows our movie drawing-complete procedure.

Listing 10: Drawing on top of a movie

QTAL_MovieDrawingCompleteProc
PASCAL_RTN OSErr QTAL_MovieDrawingCompleteProc 
            (Movie theMovie, long theRefCon)
{
   Rect                  myMovieRect;
   Rect                  myLoadRect;
   TimeValue         myTimeValue = 0L;
   RGBColor            myOrigColor;
   RGBColor            myLoadColor = {0x6666, 0x6666, 0xcccc};
   RGBColor            myRectColor = {0xeeee, 0xeeee, 0xeeee};
   GrafPtr             mySavedPort;
   WindowObject      myWindowObject = (WindowObject)theRefCon;
   if (myWindowObject == NULL)
      return(paramErr);
   if ((**myWindowObject).fWindow == NULL)
      return(paramErr);
   GetPort(&mySavedPort);
   MacSetPort(QTFrame_GetPortFromWindowReference(
            (**myWindowObject).fWindow));
   GetMovieBox(theMovie, &myMovieRect);
   if (!EmptyRect(&myMovieRect)) {
      GetForeColor(&myOrigColor);   
      RGBForeColor(&myLoadColor);
      GetMaxLoadedTimeInMovie(theMovie, &myTimeValue);
      // calculate the loading progress bar rectangle
      myLoadRect.left = myMovieRect.left;
      myLoadRect.bottom = myMovieRect.bottom;
      myLoadRect.top = myLoadRect.bottom - kLoaderBarHeight;
      myLoadRect.right = myLoadRect.left + 
            (((myMovieRect.right - myMovieRect.left) * 
            myTimeValue) / GetMovieDuration(theMovie));
      PaintRect(&myLoadRect);
      RGBForeColor(&myRectColor);
      MacFrameRect(&myLoadRect);
      RGBForeColor(&myOrigColor);
   }
   MacSetPort(mySavedPort);
   return(noErr);
}

We take the trouble to set the current graphics port (first saving and later restoring the current graphics port) to ensure that we're drawing into our movie's graphics port. In all likelihood, the current port when our procedure is called is indeed the movie's graphics port; but the documentation doesn't guarantee this, so it's good to be careful.

Installing a Drawing-Complete Procedure

Now we need to see how to activate and deactivate our movie drawing-complete procedure. To activate our procedure, we'll call the SetMovieDrawingCompleteProc function, passing it the movie identifier, a flag, a universal procedure pointer for our procedure, and the desired reference constant:

(**myAppData).fDrawCompleteUPP = 
   NewMovieDrawingCompleteUPP(QTAL_MovieDrawingCompleteProc);
SetMovieDrawingCompleteProc(myMovie, movieDrawingCallAlways, 
         (**myAppData).fDrawCompleteUPP, (long)theWindowObject);

The second parameter indicates how often we want our drawing-complete procedure to be called; it should be one of these two values:

enum {
   movieDrawingCallWhenChanged      = 0,
   movieDrawingCallAlways            = 1
};

The movieDrawingCallAlways flag indicates that we want QuickTime to call our procedure every time the movie is tasked (that is, every time our application calls MoviesTask, either directly or indirectly). The movieDrawingCallWhenChanged flag indicates that we want QuickTime to call our procedure only when the movie has changed (that is, something new was actually drawn into the movie's graphics world). As you can see, we use the movieDrawingCallAlways flag so that our procedure is called as often as possible, whether or not the movie image has changed.

We want to install our movie drawing-complete procedure when the movie first becomes playable, and we want to uninstall it when the movie data is finished downloading. We can do all of this inside our application-specific function QTApp_CheckMovieLoadState, as shown in Listing 11.

Listing 11: Installing and uninstalling the movie drawing-complete procedure

QTApp_CheckMovieLoadState
OSErr QTApp_CheckMovieLoadState 
            (WindowObject theWindowObject, long theLoadState, 
            long thePrevState)
{
#pragma unused(thePrevState)
   ApplicationDataHdl      myAppData = NULL;
   Movie                        myMovie = NULL;
   Rect                           myRect;
   OSErr                        myErr = noErr;
   if (theWindowObject == NULL)
      return(paramErr);
   myMovie = (**theWindowObject).fMovie;
   if (myMovie == NULL)
      return(paramErr);
   myAppData = (ApplicationDataHdl)
            (**theWindowObject).fAppData;
   if (myAppData == NULL)
      return(paramErr);
   // we don't care about the early stages
   if (theLoadState < kMovieLoadStatePlayable)
      return(noErr);
#if ALLOW_ASYNCH_LOADING
   // display a load-progress bar, until the movie is completely loaded
   if (theLoadState < kMovieLoadStateComplete) {
      if ((**myAppData).fDrawCompleteUPP == NULL) {
         (**myAppData).fDrawCompleteUPP = 
            NewMovieDrawingCompleteUPP
                        (QTAL_MovieDrawingCompleteProc);
         SetMovieDrawingCompleteProc(myMovie, 
            movieDrawingCallAlways, 
            (**myAppData).fDrawCompleteUPP, 
            (long)theWindowObject);
      }
   } else {
      if ((**myAppData).fDrawCompleteUPP != NULL) {
         // make sure the loading progress bar reaches the end
         QTAL_MovieDrawingCompleteProc(myMovie, 
            (long)theWindowObject);
         // remove the drawing-complete procedure
         SetMovieDrawingCompleteProc(myMovie, 0L, NULL, 0L);
         DisposeMovieDrawingCompleteUPP(
            (**myAppData).fDrawCompleteUPP);
         (**myAppData).fDrawCompleteUPP = NULL;
         // erase the loading progress bar, now that we are at the end
         GetMovieBox(myMovie, &myRect);
         myRect.top = myRect.bottom - kLoaderBarHeight;
#if TARGET_OS_MAC
         InvalWindowRect(
            QTFrame_GetWindowFromWindowReference(
            (**theWindowObject).fWindow), &myRect);
#endif
#if TARGET_OS_WIN32
         {
            RECT      myWinRect;
            QTFrame_ConvertMacToWinRect(&myRect, &myWinRect);
            InvalidateRect((**theWindowObject).fWindow, 
               &myWinRect, false);
         }
#endif
      }
   }
#endif
   return(myErr);
}

Once the movie data is completely downloaded, we remove the movie drawing-complete procedure and then erase the progress bar rectangle. Otherwise the progress bar would remain visible until the movie was next redrawn.

You should be aware that some of the media handlers used to play back a movie may need to use less efficient code paths when a movie drawing-complete procedure is installed. In the current case, since we are waiting for the movie's media data to download and are probably not playing the movie yet, this is less of a concern.

Loader Tracks

Now let's see how we can attach a progress bar to a movie, so that it displays its own status as the movie data is downloaded to the user's computer. The basic idea is extremely simple: we'll create a new sprite track that contains a single wired sprite. The image for this sprite is just the progress bar, shown in Figure 6.


Figure 6: The sprite image for the loader sprite

We'll set the sprite's initial position so that the right side of the loader bar is at the left edge of the movie box. Then we'll attach some wiring to the sprite that, on idle events, checks the amount of movie data currently loaded and moves the sprite to the right by the appropriate amount. Finally, when the movie data is fully downloaded, the sprite will deactivate its own track, so that the loader sprite disappears.

Creating the Sprite Track

We've had plenty of experience creating sprite tracks and adding wired actions to them, so we can be brief here. (If you need a refresher, see the QuickTime Toolkit articles from March to July, 2001.) We create the sprite track by calling NewMovieTrack and NewTrackMedia, using the dimensions of the original movie to determine the size of the sprite track:

GetMovieBox(theMovie, &myRect);
myWidth = Long2Fix(myRect.right - myRect.left);
myHeight = Long2Fix(kLoaderBarHeight);

Then we adjust the sprite track matrix so that the progress bar is drawn at the bottom of the movie box:

GetTrackMatrix(myTrack, &myMatrix);
TranslateMatrix(&myMatrix, 0, 
            Long2Fix(myRect.bottom - kLoaderBarHeight));
SetTrackMatrix(myTrack, &myMatrix);

At this point, we call an application function to add the sprite samples to the new sprite media:

myErr = QTAL_AddSpriteLoaderSamplesToMedia(myMedia, 
            myDuration, myRect.right - myRect.left);

Then we call InsertMediaIntoTrack, as usual, to add the new media samples to the track. We finish up by adjusting the sprite track properties so that the new sprite track is the frontmost track (that is, has the lowest track layer) and so that that track is loaded before any other tracks in the movie.

QTAL_SetTrackProperties(myMedia, 15);
SetTrackLayer(myTrack, kMaxLayerNumber);
SetTrackLayer(myTrack, QTAL_GetLowestLayerInMovie(theMovie) - 
            1);
myErr = QTAL_SetTrackToPreload(myTrack);

QTAL_SetTrackProperties and QTAL_GetLowestLayerInMovie are versions of functions that we've encountered previously, in the aforementioned articles. QTAL_SetTrackToPreload is a very simple function that sets the specified track to preload -- that is, to be loaded entirely into memory when the movie is opened. This by itself isn't such a big deal, as our sprite loader track will be fairly small (barely a thousand bytes) and would probably have been loaded entirely into RAM anyway. The main advantage to setting a track to preload is that FlattenMovieData places the data for any tracks marked to preload before the data for other tracks in the movie. This means that our sprite loader track will be downloaded first and hence able to display its progress bar as early as possible. Listing 12 shows our definition of QTAL_SetTrackToPreload.

Listing 12: Setting a track to preload

QTAL_SetTrackToPreload
OSErr QTAL_SetTrackToPreload (Track theTrack)
{
   TimeValue      myTime = 0L;
   TimeValue      myDuration = 0L;
   long               myFlags = 0L;
   long               myHints = 0L;
   OSErr            myErr = noErr;
   if (theTrack == NULL)
      return(invalidTrack);
   // get the current track load settings
   GetTrackLoadSettings(theTrack, &myTime, &myDuration, 
            &myFlags, &myHints);
   myErr = GetMoviesError();
   if (myErr != noErr)
      goto bail;
   myFlags = preloadAlways;
   myTime = -1;
   // set the new track load settings
   SetTrackLoadSettings(theTrack, myTime, myDuration, myFlags, 
            myHints);
   myErr = GetMoviesError();
bail:
   return(myErr);
}

The key step here is calling SetTrackLoadSettings with the myFlags parameter set to include the preloadAlways flag.

Adding the Loader Sprite Image

The QTAL_AddSpriteLoaderSamplesToMedia function performs two main tasks: (1) it adds the progress bar image to the sprite sample, and (2) it adds wiring to the loader sprite. Let's tackle the first task here.

When we've previously constructed sprite tracks, we've usually read the sprite images from an existing location (typically the application's resource fork). In this case, however, we don't know the width of the sprite image in advance, so we'll need to create it dynamically, once we know the width of the movie we are adding the loader track to. We'll adapt the existing utility ICUtils_RecompressPictureWithTransparency to fit our needs; the resulting function, QTAL_AddLoaderBarPICTToKeyFrameSample, is shown in Listing 13. Parts of this function will remind you of QTAL_MovieDrawingCompleteProc (Listing 10).

Listing 13: Adding the image for the loader bar sprite

QTAL_AddLoaderBarPICTToKeyFrameSample
OSErr QTAL_AddLoaderBarPICTToKeyFrameSample 
            (QTAtomContainer theKeySample, long theBarWidth, 
            RGBColor *theKeyColor, QTAtomID theID, 
            FixedPoint *theRegistrationPoint, 
            StringPtr theImageName)
{
   Rect                     myRect;
   RGBColor               myOrigColor;
   RGBColor               myLoadColor = {0x6666, 0x6666, 0xcccc};
   RGBColor               myRectColor = {0xeeee, 0xeeee, 0xeeee};
   PicHandle            myPicture = NULL;
   Handle                  myCompressedPicture = NULL;
   ImageDescriptionHandle   
                           myImageDesc = NULL;
   OSErr                  myErr = noErr;
   // set up the PICT rectangle
   myRect.top = 0;
   myRect.left = 0;
   myRect.right = theBarWidth;
   myRect.bottom = kLoaderBarHeight;
   // create the loader bar PICT
   myPicture = OpenPicture(&myRect);
   if (myPicture != NULL) {
      GetForeColor(&myOrigColor);   
      RGBForeColor(&myLoadColor);
      PaintRect(&myRect);
      RGBForeColor(&myRectColor);
      MacFrameRect(&myRect);
      RGBForeColor(&myOrigColor);
      ClosePicture();
      // convert it to image data compressed by the animation compressor
      myErr = 
            ICUtils_RecompressPictureWithTransparency(myPicture, 
            theKeyColor, NULL, &myImageDesc, 
            &myCompressedPicture);
      if (myErr != noErr)
         goto bail;
      // add it to the key sample
      HLock(myCompressedPicture);
      myErr = SpriteUtils_AddCompressedImageToKeyFrameSample(
            theKeySample, myImageDesc, 
            GetHandleSize(myCompressedPicture), 
            *myCompressedPicture, theID, theRegistrationPoint, 
            theImageName);
   }
bail:
   if (myPicture != NULL)
      KillPicture(myPicture);
   if (myCompressedPicture != NULL)
      DisposeHandle(myCompressedPicture);
   if (myImageDesc != NULL)
      DisposeHandle((Handle)myImageDesc);
   return(myErr);
}

Wiring the Loader Sprite

All that remains is to add the appropriate wiring to the sprite, to cause it to move gradually to the right as the media data is downloaded; we also need to disable the sprite track once the media data is fully downloaded. In pseudo-code, our wiring will look like this:

if kOperandMaxLoadedTimeInMovie < kOperandMovieDuration
   TranslateSprite xPos, 0, true
else
   EnableTrack false

Here, the horizontal position of the sprite (or xPos, in the pseudo-code) is calculated thus:

xPos = (kOperandMaxLoadedTimeInMovie / myDurPerPixel) - theWidth)

where myDurPerPixel is simply the movie duration divided by the movie width. Listing 14 shows the portion of QTAL_AddSpriteLoaderSamplesToMedia that constructs the wired action atom.

Listing 14: Adding actions to the loader bar sprite

QTAL_AddSpriteLoaderSamplesToMedia
WiredUtils_AddQTEventAndActionAtoms(mySpriteData, 
            kParentAtomIsContainer, kQTEventIdle, kActionCase, 
            &myActionAtom);
if (myActionAtom != 0) {
   QTAtom         myParamAtom = 0;
   QTAtom         myConditionalAtom = 0;
   QTAtom         myExpressionAtom = 0;
   // add a parameter atom to the kActionCase action atom; this will serve as a parent to hold the 
   expression and action atoms
   
   WiredUtils_AddActionParameterAtom(mySpriteData, 
            myActionAtom, kFirstParam, 0, NULL, &myParamAtom);
   if (myParamAtom != 0) {
      // if ...
      WiredUtils_AddConditionalAtom(mySpriteData, myParamAtom, 
            1, &myConditionalAtom);
      if (myConditionalAtom != 0) {
         WiredUtils_AddExpressionContainerAtomType(mySpriteData, 
            myConditionalAtom, &myExpressionAtom);
         if (myExpressionAtom != 0) {
            QTAtom      myOperatorAtom = 0;
            // ... kOperandMaxLoadedTimeInMovie < kOperandMovieDuration
            myErr = WiredUtils_AddOperatorAtom(mySpriteData, 
                  myExpressionAtom, kOperatorLessThan, 
                  &myOperatorAtom);
            if (myOperatorAtom != 0) {
               WiredUtils_AddOperandAtom(mySpriteData, 
                  myOperatorAtom, kOperandMaxLoadedTimeInMovie, 1, 
                  NULL, 0);
               WiredUtils_AddOperandAtom(mySpriteData, 
                  myOperatorAtom, kOperandMovieDuration, 2, NULL, 
                  0);
            }
         }
         //       TranslateSprite ...
         WiredUtils_AddActionListAtom(mySpriteData, 
            myConditionalAtom, &myActionListAtom);
         if (myActionListAtom != 0) {
            WiredUtils_AddActionAtom(mySpriteData, 
                  myActionListAtom, kActionSpriteTranslate, 
                  &myNewActionAtom);
            if (myNewActionAtom != 0) {
               QTAtom   myNewParamAtom = 0;
               long   myDurPerPixel = theDuration / theWidth;
               // first parameter: (kOperandMaxLoadedTimeInMovie / myDurPerPixel) 
               // - theWidth
               WiredUtils_AddActionParameterAtom(mySpriteData, 
                  myNewActionAtom, kFirstParam, 0, NULL, 
                  &myNewParamAtom);
               if (myNewParamAtom != 0) {
                  QTAtom   myExpressionAtomSub = 0;
                  QTAtom   myExpressionAtomMin = 0;
                  QTAtom   myOperatorAtomSub = 0;
                  QTAtom   myOperatorAtomDiv = 0;
                  QTAtom   myOperandAtom = 0;
                  QTAtom   myNewOperandAtom = 0;
                  WiredUtils_AddExpressionContainerAtomType
                  (mySpriteData, myNewParamAtom, 
                  &myExpressionAtomSub);
                  if (myExpressionAtomSub != 0) {
                     WiredUtils_AddOperatorAtom(mySpriteData, 
                        myExpressionAtomSub, kOperatorSubtract, 
                        &myOperatorAtomSub);
                     if (myOperatorAtomSub != 0) {
                        // the minuend
                        QTInsertChild(mySpriteData, 
                        myOperatorAtomSub, kOperandAtomType, 1, 1, 
                        0, NULL, &myOperandAtom);
                        if (myOperandAtom != 0) {
                        QTInsertChild(mySpriteData, myOperandAtom, 
                              kOperandExpression, 1, 1, 0, NULL, 
                              &myNewOperandAtom);
                           WiredUtils_AddExpressionContainerAtomType
                              (mySpriteData, myNewOperandAtom, 
                              &myExpressionAtomMin);
                           if (myExpressionAtomMin != 0) {
                           WiredUtils_AddOperatorAtom(mySpriteData, 
                              myExpressionAtomMin, kOperatorDivide, 
                              &myOperatorAtomDiv);
                              if (myOperatorAtomDiv != 0) {
                                 WiredUtils_AddOperandAtom
                                    (mySpriteData, myOperatorAtomDiv, 
                                    kOperandMaxLoadedTimeInMovie, 1, 
                                    NULL, 0);
                                 WiredUtils_AddOperandAtom
                                    (mySpriteData, myOperatorAtomDiv, 
                                    kOperandConstant, 2, NULL, 
                                    (float)myDurPerPixel);
                                 }
                              }
                           }
                           // the subtrahend
                           WiredUtils_AddOperandAtom(mySpriteData, 
                              myOperatorAtomSub, kOperandConstant, 2, 
                              NULL, (float)theWidth);
                        }
                     }
                  }
               // second parameter: 0
               myFixed = EndianU32_NtoB(0);
               WiredUtils_AddActionParameterAtom(mySpriteData, 
                     myNewActionAtom, kSecondParam, sizeof(Fixed), 
                     &myFixed, NULL);
               // third parameter: true
               myBoolean = true;
               WiredUtils_AddActionParameterAtom(mySpriteData, 
                  myNewActionAtom, kThirdParam, sizeof(myBoolean), 
                  &myBoolean, NULL);
            }
         }
      }
      // else if ...
      WiredUtils_AddConditionalAtom(mySpriteData, myParamAtom, 
            2, &myConditionalAtom);
      if (myConditionalAtom != 0) {
         // ... (1)
         WiredUtils_AddExpressionContainerAtomType(mySpriteData, myConditionalAtom, 
         &myExpressionAtom);
         
            if (myExpressionAtom != 0)
               WiredUtils_AddOperandAtom(mySpriteData, myExpressionAtom, kOperandConstant, 1, NULL, 
               1.0);
               
         //      kActionTrackSetEnabled false
         WiredUtils_AddActionListAtom(mySpriteData, 
               myConditionalAtom, &myActionListAtom);
         if (myActionListAtom != 0)
            WiredUtils_AddTrackSetEnabledAction(mySpriteData, 
               myActionListAtom, 0, 0, NULL, 0, false);
      }
   }
}

QuickTime VR Movie Loading

Before we close, let's take a quick look at one additional topic related to loading movies, how to specify a preview track in a QuickTime VR panoramic movie. As you know (at least if you read "Virtuosity" in MacTech, June 2002), the image data for a panorama is contained in a panorama image track inside a QuickTime VR movie file. Each sample in the panorama image track represents one section, or tile, of the image data. For cylindrical panoramas, a tile is a vertical slice of the image. For cubic panoramas, a tile is usually an entire face of the cube. Chopping the panorama image data into tiles allows QuickTime VR to display parts of the panorama to the user without having the entire image data in memory.

This is relevant to us now because QuickTime downloads a panoramic movie one tile at a time. This means that, on suitably slow network connections and with suitably narrow tiles, the panorama image data that's been downloaded at some point might fill only part of the movie window. By default, QuickTime fills the remainder of the movie window with a black and gray grid pattern, as shown in Figure 7. (This pattern is sometimes called the "holodeck" pattern, after a similar grid effect seen in some Star Trek episodes.) As new tiles are downloaded, they overlay the grid.


Figure 7: The grid pattern for unloaded tiles

It's possible to achieve a better user experience by including a low-resolution image track in the panorama. This is a video track that (typically) shows the same location as the full resolution track but which occupies a small fraction of the space of the full resolution track. The low-resolution image track is loaded fairly quickly and is hence often called a low-resolution preview track (or just preview track). As the high-resolution tiles arrive, they are drawn on top of the low-resolution track, in just the same way that the high-resolution tiles are drawn on top of the grid. Figure 8 shows a low-resolution image track (on the left) and the high-resolution tiles (on the right).


Figure 8: A low-resolution image track and some

high-resolution tiles

Hot spots in the panorama will be active under the low-resolution track but inactive under the grid pattern. For this reason at least, it's usually best to include a low-resolution image track in any panoramas we create. Most QuickTime VR authoring tools provide some means of attaching these low-resolution tracks to a movie, but it's easy enough to do it ourselves. If we've got a panorama that does not contain a low-resolution image track, we can add one to it by exporting the movie using the QuickTime VR flattener. This is a movie export component that prepares a QuickTime VR movie for Fast Start downloading and provides the option of including a low-resolution image track. Listing 15 shows our definition of the QTVRUtils_FlattenMovieForStreaming function, which exports the specified movie into a new file. (See "In and Out" in MacTech, May 2000 for a more extensive discussion of movie exporting.)

Listing 15: Flattening a QuickTime VR movie

QTVRUtils_FlattenMovieForStreaming
OSErr QTVRUtils_FlattenMovieForStreaming (Movie theMovie, 
            FSSpecPtr theFSSpecPtr)
{
   ComponentDescription      myCompDesc;
   MovieExportComponent      myExporter = NULL;
   long                              myFlags = 
            createMovieFileDeleteCurFile | showUserSettingsDialog 
            | movieFileSpecValid;
   ComponentResult               myErr = badComponentType;
   // find and open a movie exporter that can flatten a QuickTime VR movie file
   myCompDesc.componentType = MovieExportType;
   myCompDesc.componentSubType = MovieFileType;
   myCompDesc.componentManufacturer = 
            kQTVRFlattenerManufacturer;
   myCompDesc.componentFlags = 0;
   myCompDesc.componentFlagsMask = 0;
   myExporter = OpenComponent(
            FindNextComponent(NULL, &myCompDesc));
   if (myExporter == NULL)
      goto bail;
   // use the default progress procedure
   SetMovieProgressProc(theMovie, (MovieProgressUPP)-1L, 0);
   // export the movie into a file
   myErr = ConvertMovieToFile(   theMovie, NULL, theFSSpecPtr,
                  MovieFileType, sigMoviePlayer, smSystemScript, 
                  NULL, myFlags, myExporter);
bail:
   // close the movie export component
   if (myExporter != NULL)
      CloseComponent(myExporter);
   return((OSErr)myErr);
}

Since we include the showUserSettingsDialog flag in the myFlags parameter passed to ConvertMovieToFile, the user will be presented with the export settings dialog box, shown in Figure 9.


Figure 9: The export settings dialog box

If the user clicks the Options button, the dialog box shown in Figure 10 will be presented, allowing the user to determine the resolution of the preview track and whether it is blurred or pixilated. If the user unchecks the "Create Preview" button, no preview track is created.


Figure 10: The Options dialog box

Conclusion

The theme of this article was loading QuickTime movies. We've learned how to modify our basic QuickTime movie playback application to support loading movies asynchronously. The changes required here are indeed fairly simple, but they pay big dividends. First and foremost, our application can now continue processing while a movie is being loaded. Also, we've now got the machinery in place to do application-specific processing while a movie loads, such as displaying a progress bar showing how much of the movie is loaded.

We've also taken a look at a couple of ways to enhance a movie's own loading behavior. We saw how to add a sprite track that displays a loader bar, and we saw how to add a low-resolution preview track to a QuickTime VR panorama.

Credits

The technique for adding the sprite loader bar to a QuickTime movie is based on some wiring developed by Bill Meikle and Gary Alexander.


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.

 
AAPL
$99.02
Apple Inc.
+1.35
MSFT
$43.97
Microsoft Corpora
-0.53
GOOG
$590.60
Google Inc.
+1.58

MacTech Search:
Community Search:

Software Updates via MacUpdate

OS X Yosemite Wallpaper 1.0 - Desktop im...
OS X Yosemite Wallpaper is the gorgeous new background image for Apple's upcoming OS X 10.10 Yosemite. This wallpaper is available for all screen resolutions with a source file that measures 5,418... Read more
Acorn 4.4 - Bitmap image editor. (Demo)
Acorn is a new image editor built with one goal in mind - simplicity. Fast, easy, and fluid, Acorn provides the options you'll need without any overhead. Acorn feels right, and won't drain your bank... Read more
Bartender 1.2.20 - Organize your menu ba...
Bartender lets you organize your menu bar apps. Features: Lets you tidy your menu bar apps how you want. See your menu bar apps when you want. Hide the apps you need to run, but do not need to... Read more
TotalFinder 1.6.2 - Adds tabs, hotkeys,...
TotalFinder is a universally acclaimed navigational companion for your Mac. Enhance your Mac's Finder with features so smart and convenient, you won't believe you ever lived without them. Tab-based... Read more
Vienna 3.0.0 RC 2 :be5265e: - RSS and At...
Vienna is a freeware and Open-Source RSS/Atom newsreader with article storage and management via a SQLite database, written in Objective-C and Cocoa, for the OS X operating system. It provides... Read more
VLC Media Player 2.1.5 - Popular multime...
VLC Media Player is a highly portable multimedia player for various audio and video formats (MPEG-1, MPEG-2, MPEG-4, DivX, MP3, OGG, ...) as well as DVDs, VCDs, and various streaming protocols. It... Read more
Default Folder X 4.6.7 - Enhances Open a...
Default Folder X attaches a toolbar to the right side of the Open and Save dialogs in any OS X-native application. The toolbar gives you fast access to various folders and commands. You just click... Read more
TinkerTool 5.3 - Expanded preference set...
TinkerTool is an application that gives you access to additional preference settings Apple has built into Mac OS X. This allows to activate hidden features in the operating system and in some of the... Read more
Audio Hijack Pro 2.11.0 - Record and enh...
Audio Hijack Pro drastically changes the way you use audio on your computer, giving you the freedom to listen to audio when you want and how you want. Record and enhance any audio with Audio Hijack... Read more
Intermission 1.1.1 - Pause and rewind li...
Intermission allows you to pause and rewind live audio from any application on your Mac. Intermission will buffer up to 3 hours of audio, allowing users to skip through any assortment of audio... Read more

Latest Forum Discussions

See All

Traps n’ Gemstones Review
Traps n’ Gemstones Review By Campbell Bird on July 28th, 2014 Our Rating: :: CASTLEVANIA JONESUniversal App - Designed for iPhone and iPad Fight mummies, dig tunnels, and ride a runaway minecart to discover ancient secrets in this... | Read more »
The Phantom PI Mission Apparition Review
The Phantom PI Mission Apparition Review By Jordan Minor on July 28th, 2014 Our Rating: :: GHOSTS BUSTEDUniversal App - Designed for iPhone and iPad The Phantom PI is an exceedingly clever and well-crafted adventure game.   | Read more »
More Stubies Are Coming Your Way in a Ne...
More Stubies Are Coming Your Way in a New Update Posted by Jessica Fisher on July 28th, 2014 [ permalink ] Universal App - Designed for iPhone and iPad | Read more »
The Great Prank War Review
The Great Prank War Review By Nadia Oxford on July 28th, 2014 Our Rating: :: PRANKING IS SERIOUS BUSINESSUniversal App - Designed for iPhone and iPad Though short, The Great Prank War offers an interesting and fun mix of action and... | Read more »
Marvel Contest of Champions Announced at...
Marvel Contest of Champions Announced at Comic-Con Posted by Jennifer Allen on July 28th, 2014 [ permalink ] Announced over the weekend at San Diego Comic-Con was the fairly exciting looking Marvel Contest of Champions. | Read more »
Teenage Mutant Ninja Turtles Review
Teenage Mutant Ninja Turtles Review By Jennifer Allen on July 28th, 2014 Our Rating: :: DULL SWIPINGUniversal App - Designed for iPhone and iPad The pizza power is weak when it comes to this Teenage Mutant Ninja Turtles game.   | Read more »
Exploration Focused Puzzle Game Beatbudd...
Exploration Focused Puzzle Game Beatbuddy Set to Make Transition from PC to iOS this September Posted by Jennifer Allen on July 28th, 2014 [ permalink ] | Read more »
PlanetHD
PlanetHD By Nadia Oxford on July 28th, 2014 Our Rating: :: SPACE MADNESSUniversal App - Designed for iPhone and iPad PlanetHD will keep players busy for a while, though its unpredictable physics are a handful to deal with.   | Read more »
This Week at 148Apps: July 21-25, 2014
Another Week of Expert App Reviews   At 148Apps, we help you sort through the great ocean of apps to find the ones we think you’ll like and the ones you’ll need. Our top picks become Editor’s Choice, our stamp of approval for apps with that little... | Read more »
Reddme for iPhone - The Reddit Client (...
Reddme for iPhone - The Reddit Client 1.0 Device: iOS iPhone Category: News Price: $.99, Version: 1.0 (iTunes) Description: Reddme for iPhone is an iOS 7-optimized Reddit client that offers a refreshing new way to experience Reddit... | Read more »

Price Scanner via MacPrices.net

13-inch 2.5GHz MacBook Pro on sale for $1099,...
Best Buy has the 13″ 2.5GHz MacBook Pro available for $1099.99 on their online store. Choose free shipping or free instant local store pickup (if available). Their price is $100 off MSRP. Price is... Read more
Roundup of Apple refurbished MacBook Pros, th...
The Apple Store has Apple Certified Refurbished 13″ and 15″ MacBook Pros available for up to $400 off the cost of new models. Apple’s one-year warranty is standard, and shipping is free. Their prices... Read more
Record Mac Shipments In Q2/14 Confound Analys...
A Seeking Alpha Trefis commentary notes that Apple’s fiscal Q3 2014 results released July 22, beat market predictions on earnings, although revenues were slightly lower than anticipated. Apple’s Mac’... Read more
Intel To Launch Core M Silicon For Use In Not...
Digitimes’ Monica Chen and Joseph Tsai, report that Intel will launch 14nm-based Core M series processors specifically for use in fanless notebook/tablet 2-in-1 models in Q4 2014, with many models to... Read more
Apple’s 2014 Back to School promotion: $100 g...
 Apple’s 2014 Back to School promotion includes a free $100 App Store Gift Card with the purchase of any new Mac (Mac mini excluded), or a $50 Gift Card with the purchase of an iPad or iPhone,... Read more
iMacs on sale for $150 off MSRP, $250 off for...
Best Buy has iMacs on sale for up to $160 off MSRP for a limited time. Choose free home shipping or free instant local store pickup (if available). Prices are valid for online orders only, in-store... Read more
Mac minis on sale for $100 off MSRP, starting...
Best Buy has Mac minis on sale for $100 off MSRP. Choose free shipping or free instant local store pickup. Prices are for online orders only, in-store prices may vary: 2.5GHz Mac mini: $499.99 2.3GHz... Read more
Global Tablet Market Grows 11% in Q2/14 Notwi...
Worldwide tablet sales grew 11.0 percent year over year in the second quarter of 2014, with shipments reaching 49.3 million units according to preliminary data from the International Data Corporation... Read more
New iPhone 6 Models to Have Staggered Release...
Digitimes’ Cage Chao and Steve Shen report that according to unnamed sources in Apple’s upstream iPhone supply chain, the new 5.5-inch iPhone will be released several months later than the new 4.7-... Read more
New iOS App Helps People Feel Good About thei...
Mobile shoppers looking for big savings at their favorite stores can turn to the Goodshop app, a new iOS app with the latest coupons and deals at more than 5,000 online stores. In addition to being a... Read more

Jobs Board

Sr Software Lead Engineer, *Apple* Online S...
Sr Software Lead Engineer, Apple Online Store Publishing Systems Keywords: Company: Apple Job Code: E3PCAK8MgYYkw Location (City or ZIP): Santa Clara Status: Full Read more
*Apple* Solutions Consultant (ASC) - Apple (...
**Job Summary** The ASC is an Apple employee who serves as an Apple brand ambassador and influencer in a Reseller's store. The ASC's role is to grow Apple Read more
Sr. Product Leader, *Apple* Store Apps - Ap...
**Job Summary** Imagine what you could do here. At Apple , great ideas have a way of becoming great products, services, and customer experiences very quickly. Bring Read more
*Apple* Solutions Consultant (ASC) - Apple (...
**Job Summary** The ASC is an Apple employee who serves as an Apple brand ambassador and influencer in a Reseller's store. The ASC's role is to grow Apple Read more
*Apple* Solutions Consultant (ASC) - Apple (...
**Job Summary** The ASC is an Apple employee who serves as an Apple brand ambassador and influencer in a Reseller's store. The ASC's role is to grow Apple Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.