TweetFollow Us on Twitter


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


Loading QuickTime Movies Asynchronously

by Tim Monroe


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

Movie QTDR_GetMovieFromURL (char *theURL)
   Movie      myMovie = NULL;
   Handle      myDataRef = NULL;
   short      myFlags = newMovieActive;
   myFlags += newMovieAsyncOK;
   myDataRef = QTDR_MakeURLDataRef(theURL);
   if (myDataRef != NULL) {
      NewMovieFromDataRef(&myMovie, myFlags, NULL, myDataRef, 

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

// 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)
if (myImporter != 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
   // size the window to fit the image
   // 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

void QTFrame_ShowWindowAtPoint 
               (WindowReference theWindow, Point *thePoint)
   Rect         myRect;
   if ((theWindow == NULL) || (thePoint == NULL))
   MoveWindow(theWindow, thePoint->h, thePoint->v, false);
            GetWindowPortBounds(theWindow, &myRect));
   SetWindowPos(theWindow, 0, thePoint->h, thePoint->v, 0, 0, 
   ShowWindow(theWindow, SW_SHOW);

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)

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))
      myWindow = QTFrame_GetNextMovieWindow(myWindow);

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)

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

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)

// 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) {
   if (myWindowObject == NULL)
   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

if (myLoadState <= kMovieLoadStateError) {
   // close the movie window
   if ((**theWindowObject).fWindow != NULL)
   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

OSErr QTFrame_CheckMovieLoadState 
            (WindowObject theWindowObject)
   Movie                     myMovie = NULL;
   MovieController         myMC = NULL;
   long                        myLoadState = 0L;
   long                        myPrevState = 0L;
   OSErr                     myErr = noErr;
   if (theWindowObject == NULL)
   // if the window contains an image, we can return
   if ((**theWindowObject).fGraphicsImporter != NULL)
   // if the window does not contain a movie, we can return
   myMovie = (**theWindowObject).fMovie;
   if (myMovie == NULL)
   // if we are adjusting the window location or size, don't go any further
   if (gWeAreSizingWindow)
   myMC = (**theWindowObject).fController;
   // get the previous load state
   myPrevState = (**theWindowObject).fLoadState;
   // if we're already fully loaded and configured, we can return
   if ((myPrevState >= kMovieLoadStateComplete) && 
            (myMC != NULL))
   // get the current load state
   myLoadState = GetMovieLoadState(myMovie);
   // remember the new state
   (**theWindowObject).fLoadState = myLoadState;
   // 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)
      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 = 
         Point            myPoint;
         if (myWindow == NULL)
         // 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, 
         (**theWindowObject).fController = myMC;
         (**theWindowObject).fIsQTVRMovie = 
         // do any application-specific window object initialization
         // size the window to fit the movie and controller
         // set the movie's play hints to allow dynamic resizing
         SetMoviePlayHints(myMovie, 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 the movie is an autoplay movie, then start it playing immediately
            if (QTUtils_IsAutoPlayMovie(myMovie))
               MCDoAction(myMC, mcActionPrerollAndPlay, 
                           (void *)GetMoviePreferredRate(myMovie));
      // 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));
   // do any application-specific processing
   if (myErr == noErr)
      myErr = QTApp_CheckMovieLoadState(theWindowObject, 
               myLoadState, myPrevState);

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

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

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.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:


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


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

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)
   if ((**myWindowObject).fWindow == NULL)
   GetMovieBox(theMovie, &myMovieRect);
   if (!EmptyRect(&myMovieRect)) {
      GetMaxLoadedTimeInMovie(theMovie, &myTimeValue);
      // calculate the loading progress bar rectangle
      myLoadRect.left = myMovieRect.left;
      myLoadRect.bottom = myMovieRect.bottom; = myLoadRect.bottom - kLoaderBarHeight;
      myLoadRect.right = myLoadRect.left + 
            (((myMovieRect.right - myMovieRect.left) * 
            myTimeValue) / GetMovieDuration(theMovie));

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 = 
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

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)
   myMovie = (**theWindowObject).fMovie;
   if (myMovie == NULL)
   myAppData = (ApplicationDataHdl)
   if (myAppData == NULL)
   // we don't care about the early stages
   if (theLoadState < kMovieLoadStatePlayable)
   // display a load-progress bar, until the movie is completely loaded
   if (theLoadState < kMovieLoadStateComplete) {
      if ((**myAppData).fDrawCompleteUPP == NULL) {
         (**myAppData).fDrawCompleteUPP = 
   } else {
      if ((**myAppData).fDrawCompleteUPP != NULL) {
         // make sure the loading progress bar reaches the end
         // remove the drawing-complete procedure
         SetMovieDrawingCompleteProc(myMovie, 0L, NULL, 0L);
         (**myAppData).fDrawCompleteUPP = NULL;
         // erase the loading progress bar, now that we are at the end
         GetMovieBox(myMovie, &myRect); = myRect.bottom - kLoaderBarHeight;
            (**theWindowObject).fWindow), &myRect);
            RECT      myWinRect;
            QTFrame_ConvertMacToWinRect(&myRect, &myWinRect);
               &myWinRect, false);

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) - 
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

OSErr QTAL_SetTrackToPreload (Track theTrack)
   TimeValue      myTime = 0L;
   TimeValue      myDuration = 0L;
   long               myFlags = 0L;
   long               myHints = 0L;
   OSErr            myErr = noErr;
   if (theTrack == NULL)
   // 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, 
   myErr = GetMoviesError();

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

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;
                           myImageDesc = NULL;
   OSErr                  myErr = noErr;
   // set up the PICT rectangle = 0;
   myRect.left = 0;
   myRect.right = theBarWidth;
   myRect.bottom = kLoaderBarHeight;
   // create the loader bar PICT
   myPicture = OpenPicture(&myRect);
   if (myPicture != NULL) {
      // convert it to image data compressed by the animation compressor
      myErr = 
            theKeyColor, NULL, &myImageDesc, 
      if (myErr != noErr)
         goto bail;
      // add it to the key sample
      myErr = SpriteUtils_AddCompressedImageToKeyFrameSample(
            theKeySample, myImageDesc, 
            *myCompressedPicture, theID, theRegistrationPoint, 
   if (myPicture != NULL)
   if (myCompressedPicture != NULL)
   if (myImageDesc != NULL)

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
   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

            kParentAtomIsContainer, kQTEventIdle, kActionCase, 
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
            myActionAtom, kFirstParam, 0, NULL, &myParamAtom);
   if (myParamAtom != 0) {
      // if ...
      WiredUtils_AddConditionalAtom(mySpriteData, myParamAtom, 
            1, &myConditionalAtom);
      if (myConditionalAtom != 0) {
            myConditionalAtom, &myExpressionAtom);
         if (myExpressionAtom != 0) {
            QTAtom      myOperatorAtom = 0;
            // ... kOperandMaxLoadedTimeInMovie < kOperandMovieDuration
            myErr = WiredUtils_AddOperatorAtom(mySpriteData, 
                  myExpressionAtom, kOperatorLessThan, 
            if (myOperatorAtom != 0) {
                  myOperatorAtom, kOperandMaxLoadedTimeInMovie, 1, 
                  NULL, 0);
                  myOperatorAtom, kOperandMovieDuration, 2, NULL, 
         //       TranslateSprite ...
            myConditionalAtom, &myActionListAtom);
         if (myActionListAtom != 0) {
                  myActionListAtom, kActionSpriteTranslate, 
            if (myNewActionAtom != 0) {
               QTAtom   myNewParamAtom = 0;
               long   myDurPerPixel = theDuration / theWidth;
               // first parameter: (kOperandMaxLoadedTimeInMovie / myDurPerPixel) 
               // - theWidth
                  myNewActionAtom, kFirstParam, 0, NULL, 
               if (myNewParamAtom != 0) {
                  QTAtom   myExpressionAtomSub = 0;
                  QTAtom   myExpressionAtomMin = 0;
                  QTAtom   myOperatorAtomSub = 0;
                  QTAtom   myOperatorAtomDiv = 0;
                  QTAtom   myOperandAtom = 0;
                  QTAtom   myNewOperandAtom = 0;
                  (mySpriteData, myNewParamAtom, 
                  if (myExpressionAtomSub != 0) {
                        myExpressionAtomSub, kOperatorSubtract, 
                     if (myOperatorAtomSub != 0) {
                        // the minuend
                        myOperatorAtomSub, kOperandAtomType, 1, 1, 
                        0, NULL, &myOperandAtom);
                        if (myOperandAtom != 0) {
                        QTInsertChild(mySpriteData, myOperandAtom, 
                              kOperandExpression, 1, 1, 0, NULL, 
                              (mySpriteData, myNewOperandAtom, 
                           if (myExpressionAtomMin != 0) {
                              myExpressionAtomMin, kOperatorDivide, 
                              if (myOperatorAtomDiv != 0) {
                                    (mySpriteData, myOperatorAtomDiv, 
                                    kOperandMaxLoadedTimeInMovie, 1, 
                                    NULL, 0);
                                    (mySpriteData, myOperatorAtomDiv, 
                                    kOperandConstant, 2, NULL, 
                           // the subtrahend
                              myOperatorAtomSub, kOperandConstant, 2, 
                              NULL, (float)theWidth);
               // second parameter: 0
               myFixed = EndianU32_NtoB(0);
                     myNewActionAtom, kSecondParam, sizeof(Fixed), 
                     &myFixed, NULL);
               // third parameter: true
               myBoolean = true;
                  myNewActionAtom, kThirdParam, sizeof(myBoolean), 
                  &myBoolean, NULL);
      // else if ...
      WiredUtils_AddConditionalAtom(mySpriteData, myParamAtom, 
            2, &myConditionalAtom);
      if (myConditionalAtom != 0) {
         // ... (1)
         WiredUtils_AddExpressionContainerAtomType(mySpriteData, myConditionalAtom, 
            if (myExpressionAtom != 0)
               WiredUtils_AddOperandAtom(mySpriteData, myExpressionAtom, kOperandConstant, 1, NULL, 
         //      kActionTrackSetEnabled false
               myConditionalAtom, &myActionListAtom);
         if (myActionListAtom != 0)
               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

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 = 
   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);
   // close the movie export component
   if (myExporter != NULL)

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


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.


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 The views expressed here are not necessarily shared by his employer.

Apple Inc.
Microsoft Corpora
Google Inc.

MacTech Search:
Community Search:

Software Updates via MacUpdate

NeoOffice 2014.6 - Mac-tailored, OpenOff...
NeoOffice is a complete office suite for OS X. With NeoOffice, users can view, edit, and save OpenOffice documents, PDF files, and most Microsoft Word, Excel, and PowerPoint documents. NeoOffice 3.x... Read more
LibreOffice - Free Open Source o...
LibreOffice is an office suite (word processor, spreadsheet, presentations, drawing tool) compatible with other major office suites. The Document Foundation is coordinating development and... Read more
CleanApp 5.0.0 Beta 5 - Application dein...
CleanApp is an application deinstaller and archiver.... Your hard drive gets fuller day by day, but do you know why? CleanApp 5 provides you with insights how to reclaim disk space. There are... Read more
Monolingual 1.6.2 - Remove unwanted OS X...
Monolingual is a program for removing unnecesary language resources from OS X, in order to reclaim several hundred megabytes of disk space. It requires a 64-bit capable Intel-based Mac and at least... Read more
NetShade 6.1 - Browse privately using an...
NetShade is an Internet security tool that conceals your IP address on the web. NetShade routes your Web connection through either a public anonymous proxy server, or one of NetShade's own dedicated... Read more
calibre 2.13 - Complete e-library manage...
Calibre is a complete e-book library manager. Organize your collection, convert your books to multiple formats, and sync with all of your devices. Let Calibre be your multi-tasking digital librarian... Read more
Mellel 3.3.7 - Powerful word processor w...
Mellel is the leading word processor for OS X and has been widely considered the industry standard since its inception. Mellel focuses on writers and scholars for technical writing and multilingual... Read more
ScreenFlow 5.0.1 - Create screen recordi...
Save 10% with the exclusive MacUpdate coupon code: AFMacUpdate10 Buy now! ScreenFlow is powerful, easy-to-use screencasting software for the Mac. With ScreenFlow you can record the contents of your... Read more
Simon 4.0 - Monitor changes and crashes...
Simon monitors websites and alerts you of crashes and changes. Select pages to monitor, choose your alert options, and customize your settings. Simon does the rest. Keep a watchful eye on your... Read more
BBEdit 11.0.2 - Powerful text and HTML e...
BBEdit is the leading professional HTML and text editor for the Mac. Specifically crafted in response to the needs of Web authors and software developers, this award-winning product provides a... Read more

Latest Forum Discussions

See All

This Week at 148Apps: December 15-19, 20...
Happy Holidays from 148Apps!   How do you know what apps are worth your time and money? Just look to the review team at 148Apps. We sort through the chaos and find the apps you’re looking for. The ones we love become Editor’s Choice, standing out... | Read more »
New Version of Tempo Smart Calendar Help...
New Version of Tempo Smart Calendar Helps You Plan Your Day Without Opening the App Posted by Jessica Fisher on December 22nd, 2014 [ permalink ] | Read more »
Sparkle - Advanced Cross-Synthesis (Mus...
Sparkle - Advanced Cross-Synthesis 1.0 Device: iOS Universal Category: Music Price: $6.99, Version: 1.0 (iTunes) Description: Sparkle is a tool for advanced spectral hybridizations made of several algorithms that operate on frequency... | Read more »
DICETINY is coming to iOS in 2015
DICETINY is coming to iOS in 2015 Posted by Jessica Fisher on December 22nd, 2014 [ permalink ] Fakedice has announced that they’re bringing their digital board game DICETINY to iOS. | Read more »
It Came From Canada: The Witcher Battle...
The Witcher 3: Wild Hunt may still be a few months away, but very soon players will be able to get a new taste of the acclaimed Polish RPG on their mobile devices with The Witcher Battle Arena. While it trades open-world exploration for compact... | Read more »
The Babies Get Lost Again in the New Azt...
The Babies Get Lost Again in the New Aztec Ruins Update for Light in the Dark Posted by Jessica Fisher on December 22nd, 2014 [ permalink ] | Read more »
Living Room 3D for IKEA Makes All In-App...
Living Room 3D for IKEA Makes All In-App Purchases Free Until 2015 Posted by Jessica Fisher on December 22nd, 2014 [ permalink ] | Read more »
Crossbow Warrior – The Legend of William...
Crossbow Warrior – The Legend of William Tell Review By Lee Hamlet on December 22nd, 2014 Our Rating: :: MISSES THE MARKUniversal App - Designed for iPhone and iPad Crossbow Warrior details the entertaining adventures of legendary... | Read more »
A New Update Races onto Asphalt 8: Airbo...
A New Update Races onto Asphalt 8: Airborne Posted by Jessica Fisher on December 22nd, 2014 [ permalink ] Universal App - Designed for iPhone and iPad | Read more »
Workflow: Powerful Automation Made Simpl...
Workflow: Powerful Automation Made Simple Review By Campbell Bird on December 22nd, 2014 Our Rating: :: GO WITH THE FLOWUniversal App - Designed for iPhone and iPad This powerful app lets users accomplish multiple tasks at touch of... | Read more »

Price Scanner via

13-inch 2.4GHz Retina MacBook Pro (Apple refu...
The Apple Store has previous-generation Apple Certified Refurbished 13″ 2.4GHz/128GB Retina MacBook Pros available for $999. Apple’s one-year warranty is standard, and shipping is free: - 13″ 2.4GHz/... Read more
Apple resellers offer free overnight shipping
The Apple Store is now offering free next-day shipping on all in stock items if ordered before 12/23/14 at 10:00am PT. Local store pickup is also available within an hour of ordering for any in stock... Read more
Holiday sales continue: MacBook Airs for up t...
 B&H Photo has 2014 MacBook Airs on sale for up to $120 off MSRP, for a limited time, for the Thanksgiving/Christmas Holiday shopping season. Shipping is free, and B&H charges NY sales tax... Read more
Holiday sale continues: 13-inch Retina MacBoo...
 B&H Photo has new 13″ MacBook Pros on sale for up to $150 off MSRP as part of their Holiday pricing. Shipping is free, and B&H charges NY sales tax only: - 13″ 2.5GHz MacBook Pro: $979 save... Read more
Holiday sale continues: 15-inch Retina MacBoo...
 B&H Photo has the new 2014 15″ Retina MacBook Pros on sale for up to $300 off MSRP for a limited time. Shipping is free, and B&H charges NY sales tax only: - 15″ 2.2GHz Retina MacBook Pro: $... Read more
Holiday sale: 13-inch 128GB MacBook Air for $...
 Best Buy has the 2014 13-inch 1.4GHz 128GB MacBook Air on sale for $849.99, or $150 off MSRP, on their online store. Choose free home shipping or free local store pickup (if available). Price valid... Read more
13-inch 2.6GHz Retina MacBook Pro on sale for...
Best Buy has lowered their price on the 2014 13″ 2.6GHz/128GB Retina MacBook Pro to $1149.99 on their online store for a limited time. That’s $150 off MSRP and the lowest price available for this... Read more
Kodak Returns to CES With New Consumer Produ...
Former photography colossus Kodak is returning to CES for the first time in three years where the Kodak booth (#21818 South Hall 1) will showcase a wide range of innovative, imaging-related products... Read more
Invaluable Launches New Eponymously -Named A...
Invaluable, the world’s largest online live auction marketplace, hhas announced the official launch of the Invaluable app for iPad, now available for download in the iTunes App Store. Invaluable... Read more
IDC Reveals Worldwide Mobile Enterprise Appli...
International Data Corporation (IDC) last week hosted the IDC FutureScape: Worldwide Mobile Enterprise Applications and Solutions 2015 Predictions Web conference. The session provided organizations... Read more

Jobs Board

*Apple* Store Leader Program (US) - Apple, I...
…Summary Learn and grow as you explore the art of leadership at the Apple Store. You'll master our retail business inside and out through training, hands-on experience, Read more
Project Manager, *Apple* Financial Services...
**Job Summary** Apple Financial Services (AFS) offers consumers, businesses and educational institutions ways to finance Apple purchases. We work with national and Read more
*Apple* Retail - Multiple Positions (US) - A...
Sales Specialist - Retail Customer Service and Sales Transform Apple Store visitors into loyal Apple customers. When customers enter the store, you're also the Read more
*Apple* Retail - Multiple Positions (US) - A...
Sales Specialist - Retail Customer Service and Sales Transform Apple Store visitors into loyal Apple customers. When customers enter the store, you're also the Read more
*Apple* Retail - Multiple Positions (US) - A...
Job Description: Sales Specialist - Retail Customer Service and Sales Transform Apple Store visitors into loyal Apple customers. When customers enter the store, Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.