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.

 

Community Search:
MacTech Search:

Software Updates via MacUpdate

Ableton Live 11.3.11 - Record music usin...
Ableton Live lets you create and record music on your Mac. Use digital instruments, pre-recorded sounds, and sampled loops to arrange, produce, and perform your music like never before. Ableton Live... Read more
Affinity Photo 2.2.0 - Digital editing f...
Affinity Photo - redefines the boundaries for professional photo editing software for the Mac. With a meticulous focus on workflow it offers sophisticated tools for enhancing, editing and retouching... Read more
SpamSieve 3.0 - Robust spam filter for m...
SpamSieve is a robust spam filter for major email clients that uses powerful Bayesian spam filtering. SpamSieve understands what your spam looks like in order to block it all, but also learns what... Read more
WhatsApp 2.2338.12 - Desktop client for...
WhatsApp is the desktop client for WhatsApp Messenger, a cross-platform mobile messaging app which allows you to exchange messages without having to pay for SMS. WhatsApp Messenger is available for... Read more
Fantastical 3.8.2 - Create calendar even...
Fantastical is the Mac calendar you'll actually enjoy using. Creating an event with Fantastical is quick, easy, and fun: Open Fantastical with a single click or keystroke Type in your event details... Read more
iShowU Instant 1.4.14 - Full-featured sc...
iShowU Instant gives you real-time screen recording like you've never seen before! It is the fastest, most feature-filled real-time screen capture tool from shinywhitebox yet. All of the features you... Read more
Geekbench 6.2.0 - Measure processor and...
Geekbench provides a comprehensive set of benchmarks engineered to quickly and accurately measure processor and memory performance. Designed to make benchmarks easy to run and easy to understand,... Read more
Quicken 7.2.3 - Complete personal financ...
Quicken makes managing your money easier than ever. Whether paying bills, upgrading from Windows, enjoying more reliable downloads, or getting expert product help, Quicken's new and improved features... Read more
EtreCheckPro 6.8.2 - For troubleshooting...
EtreCheck is an app that displays the important details of your system configuration and allow you to copy that information to the Clipboard. It is meant to be used with Apple Support Communities to... Read more
iMazing 2.17.7 - Complete iOS device man...
iMazing is the world’s favourite iOS device manager for Mac and PC. Millions of users every year leverage its powerful capabilities to make the most of their personal or business iPhone and iPad.... Read more

Latest Forum Discussions

See All

‘Junkworld’ Is Out Now As This Week’s Ne...
Epic post-apocalyptic tower-defense experience Junkworld () from Ironhide Games is out now on Apple Arcade worldwide. We’ve been covering it for a while now, and even through its soft launches before, but it has returned as an Apple Arcade... | Read more »
Motorsport legends NASCAR announce an up...
NASCAR often gets a bad reputation outside of America, but there is a certain charm to it with its close side-by-side action and its focus on pure speed, but it never managed to really massively break out internationally. Now, there's a chance... | Read more »
Skullgirls Mobile Version 6.0 Update Rel...
I’ve been covering Marie’s upcoming release from Hidden Variable in Skullgirls Mobile (Free) for a while now across the announcement, gameplay | Read more »
Amanita Design Is Hosting a 20th Anniver...
Amanita Design is celebrating its 20th anniversary (wow I’m old!) with a massive discount across its catalogue on iOS, Android, and Steam for two weeks. The announcement mentions up to 85% off on the games, and it looks like the mobile games that... | Read more »
SwitchArcade Round-Up: ‘Operation Wolf R...
Hello gentle readers, and welcome to the SwitchArcade Round-Up for September 21st, 2023. I got back from the Tokyo Game Show at 8 PM, got to the office here at 9:30 PM, and it is presently 11:30 PM. I’ve done what I can today, and I hope you enjoy... | Read more »
Massive “Dark Rebirth” Update Launches f...
It’s been a couple of months since we last checked in on Diablo Immortal and in that time the game has been doing what it’s been doing since its release in June of last year: Bringing out new seasons with new content and features. | Read more »
‘Samba De Amigo Party-To-Go’ Apple Arcad...
SEGA recently released Samba de Amigo: Party-To-Go () on Apple Arcade and Samba de Amigo: Party Central on Nintendo Switch worldwide as the first new entries in the series in ages. | Read more »
The “Clan of the Eagle” DLC Now Availabl...
Following the last paid DLC and free updates for the game, Playdigious just released a new DLC pack for Northgard ($5.99) on mobile. Today’s new DLC is the “Clan of the Eagle" pack that is available on both iOS and Android for $2.99. | Read more »
Let fly the birds of war as a new Clan d...
Name the most Norse bird you can think of, then give it a twist because Playdigious is introducing not the Raven clan, mostly because they already exist, but the Clan of the Eagle in Northgard’s latest DLC. If you find gathering resources a... | Read more »
Out Now: ‘Ghost Detective’, ‘Thunder Ray...
Each and every day new mobile games are hitting the App Store, and so each week we put together a big old list of all the best new releases of the past seven days. Back in the day the App Store would showcase the same games for a week, and then... | Read more »

Price Scanner via MacPrices.net

Apple AirPods 2 with USB-C now in stock and o...
Amazon has Apple’s 2023 AirPods Pro with USB-C now in stock and on sale for $199.99 including free shipping. Their price is $50 off MSRP, and it’s currently the lowest price available for new AirPods... Read more
New low prices: Apple’s 15″ M2 MacBook Airs w...
Amazon has 15″ MacBook Airs with M2 CPUs and 512GB of storage in stock and on sale for $1249 shipped. That’s $250 off Apple’s MSRP, and it’s the lowest price available for these M2-powered MacBook... Read more
New low price: Clearance 16″ Apple MacBook Pr...
B&H Photo has clearance 16″ M1 Max MacBook Pros, 10-core CPU/32-core GPU/1TB SSD/Space Gray or Silver, in stock today for $2399 including free 1-2 day delivery to most US addresses. Their price... Read more
Switch to Red Pocket Mobile and get a new iPh...
Red Pocket Mobile has new Apple iPhone 15 and 15 Pro models on sale for $300 off MSRP when you switch and open up a new line of service. Red Pocket Mobile is a nationwide service using all the major... Read more
Apple continues to offer a $350 discount on 2...
Apple has Studio Display models available in their Certified Refurbished store for up to $350 off MSRP. Each display comes with Apple’s one-year warranty, with new glass and a case, and ships free.... Read more
Apple’s 16-inch MacBook Pros with M2 Pro CPUs...
Amazon is offering a $250 discount on new Apple 16-inch M2 Pro MacBook Pros for a limited time. Their prices are currently the lowest available for these models from any Apple retailer: – 16″ MacBook... Read more
Closeout Sale: Apple Watch Ultra with Green A...
Adorama haș the Apple Watch Ultra with a Green Alpine Loop on clearance sale for $699 including free shipping. Their price is $100 off original MSRP, and it’s the lowest price we’ve seen for an Apple... Read more
Use this promo code at Verizon to take $150 o...
Verizon is offering a $150 discount on cellular-capable Apple Watch Series 9 and Ultra 2 models for a limited time. Use code WATCH150 at checkout to take advantage of this offer. The fine print: “Up... Read more
New low price: Apple’s 10th generation iPads...
B&H Photo has the 10th generation 64GB WiFi iPad (Blue and Silver colors) in stock and on sale for $379 for a limited time. B&H’s price is $70 off Apple’s MSRP, and it’s the lowest price... Read more
14″ M1 Pro MacBook Pros still available at Ap...
Apple continues to stock Certified Refurbished standard-configuration 14″ MacBook Pros with M1 Pro CPUs for as much as $570 off original MSRP, with models available starting at $1539. Each model... Read more

Jobs Board

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
Retail Key Holder- *Apple* Blossom Mall - Ba...
Retail Key Holder- APPLE BLOSSOM MALL Brand: Bath & Body Works Location: Winchester, VA, US Location Type: On-site Job ID: 03YM1 Job Area: Store: Sales and Support 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
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.