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.


Community Search:
MacTech Search:

Software Updates via MacUpdate

Apple Safari 11.0 - Apple's Web bro...
Note: The direct download link is currently unavailable. It is available in the OS X 10.12.6 release, as well as in the Apple Security Updates. Apple Safari is Apple's web browser that comes with... Read more
Xcode 9.0 - Integrated development envir...
Xcode includes everything developers need to create great applications for Mac, iPhone, iPad, and Apple Watch. Xcode provides developers a unified workflow for user interface design, coding, testing... Read more
BetterTouchTool 2.302 - Customize Multi-...
BetterTouchTool adds many new, fully customizable gestures to the Magic Mouse, Multi-Touch MacBook trackpad, and Magic Trackpad. These gestures are customizable: Magic Mouse: Pinch in / out (zoom... Read more
Apple iOS 11 - The latest version of App...
iOS 11 sets a new standard for what is already the world’s most advanced mobile operating system. It makes iPhone better than before. It makes iPad more capable than ever. And now it opens up both to... Read more
NTFS 15.0.911 - $19.95
NTFS breaks down the barriers between Windows and macOS. Paragon NTFS effectively solves the communication problems between the Mac system and NTFS. Write, edit, copy, move, delete files on NTFS... Read more
Airfoil 5.6.3 - Send audio from any app...
Airfoil allows you to send any audio to AirPort Express units, Apple TVs, and even other Macs and PCs, all in sync! It's your audio - everywhere. With Airfoil you can take audio from any... Read more
ExpanDrive 6.0.16 - Access cloud storage...
ExpanDrive builds cloud storage in every application, acts just like a USB drive plugged into your Mac. With ExpanDrive, you can securely access any remote file server directly from the Finder or... Read more
Smultron 9.4.2 - Easy-to-use, powerful t...
Smultron 9 is an elegant and powerful text editor that is easy to use. Use it to create or edit any text document. Everything from a web page, a note or a script to any single piece of text or code.... Read more
Typinator 7.3 - Speedy and reliable text...
Typinator turbo-charges your typing productivity. Type a little. Typinator does the rest. We've all faced projects that require repetitive typing tasks. With Typinator, you can store commonly used... Read more
coconutBattery 3.6.4 - Displays info abo...
With coconutBattery you're always aware of your current battery health. It shows you live information about your battery such as how often it was charged and how is the current maximum capacity in... Read more

The best games to play while you wait fo...
SteamWorld Dig 2 is out this week on PC and Switch, and people are understandably excited. This clever series by Image and Form combines our favorite metroidvania mechanics with an esquisite universe, excellent storytelling, and true wit. While... | Read more »
Drag'n'Boom beginner's gu...
Have you ever wanted to burn and pillage a village as a bloodthirsty dragon? If you answered yes to that question, Drag'n'Boom offers you the perfect chance to do so, casting you as an adorable little dragon that wants to set humankind aflame. It... | Read more »
Thimbleweed Park (Games)
Thimbleweed Park 1.0.0 Device: iOS Universal Category: Games Price: $9.99, Version: 1.0.0 (iTunes) Description: A brand new adventure game from Ron Gilbert and Gary Winnick, creators of the classics Monkey Island and Maniac Mansion!... | Read more »
The best simulation games on mobile
There's nothing like a good sim -- from the seemingly ridiculous to the incredibly mundane, you can be there's a simulation game out there for your every whim. [Read more] | Read more »
INKS guide - how to create works of pinb...
INKS puts a clever new spin on everyone's favorite classic arcade game, pinball. The core mechanics are the same -- keep a little ball pinging around the board for as long as possible without letting it fall into the precarious holes in the board.... | Read more »
Warbands: Bushido (Games)
Warbands: Bushido 1.0 Device: iOS Universal Category: Games Price: $3.99, Version: 1.0 (iTunes) Description: Warbands:Bushido is a miniatures board game with cards, miniatures, dice and beautiful terrains to fight on, with both... | Read more »
The best mobile games like Divinity: Ori...
Divinity: Original Sin 2 launched this week to the excitement of RPG fans everywhere. The game, which derives a lot of of its story and mechanics from old-school isometric RPGs and Dungeons & Dragons, has unseated PlayerUnknown's... | Read more »
Iron Marines guide - beginner tips and t...
Iron Marines is a brilliant RTS title that feels a bit like Starcraft. It's got a sci-fi setting and some of the most spectacular strategy mechanics we've seen in mobile games to date. With that said, the RTS genre can be a bit tricky to break... | Read more »
The best new games we played this week -...
The work week can be tough, but on the bright side, it's almost overandthere are bunches of brand new games to try out this weekend. This week definitely makes up for last week's sleepiness ten-fold. We've got one of the finest RTS game on mobile... | Read more »
Through the Ages (Games)
Through the Ages 1.0.60 Device: iOS Universal Category: Games Price: $9.99, Version: 1.0.60 (iTunes) Description: The offical adaptation of Vlaada Chvátil’s strategy classic, the second best board game ever by Board Game Geek website... | Read more »

Price Scanner via

Apple Refurbished 3TB Time Capsule for $279,...
Apple has Certified Refurbished 3TB Time Capsules available for $279 including free shipping plus Apple’s standard one-year warranty. Their price is $120 off MSRP. Read more
19% off Smart Battery Cases for iPhone 7
Amazon has both Black and White Smart Battery Cases for iPhone 7s available for $80.41 including free shipping. Their price is $18.59, or 19%, off MSRP. Read more
Back on sale: 10.5-inch 64GB iPad Pros for $5...
MacMall has 10.5″ 64GB Apple iPad Pros on sale again for $599 including free shipping. That’s $50 off MSRP and the lowest price available for this model from any reseller. Read more
Verizon offers Certified Preowned 16GB iPhone...
Verizon has the 16GB iPhone 6, Certified Preowned, available for $259.99 or $10.83 per month for 24 months. Service plan required. According to Verizon, “All CPO devices have been reconditioned to... Read more
Preorder new iPhone 8 at US Cellular, and tak...
Preorder the new iPhone 8 or iPhone 8 Plus at US Cellular, and take $50 off the prepaid price: – 64GB iPhone 8: $649.99 – 128GB iPhone 8: $799.99 – 64GB iPhone 8 Plus: $749.99 – 128GB iPhone 8 Plus... Read more
12-inch and 9-inch Apple iPad Pros, Certified...
Apple has Certified Refurbished 2016 12″ WiFi iPad Pros available starting at $589. An Apple one-year warranty is included with each model, and shipping is free: – 32GB 12″ iPad Pro WiFi: $589... Read more
QuickerTek Announces Solar PV Chargers for US...
Wichita, Kansas based QuickerTek has announced its new 30 Watt and 60 Watt USB Type-C Solar Juicz Chargers. These solar panels are the only products of their kind, featuring the USB 3.1 adapter cable... Read more
Apple refurbished 128GB iPhone 6s and 6s Plus...
Apple has Certified Refurbished 128GB iPhone 6s and 6s Plus’ available for up to $100 off the price of new models. Space Gray, Silver, Gold, and Rose Gold models are available. Each phone comes... Read more
13-inch 2.3GHz Silver MacBook Pros on sale fo...
B&H Photo has 2017 13″ 2.3GHz Silver MacBook Pros in stock today and on sale for $100 off MSRP, each including free shipping plus NY & NJ sales tax only: – 13-inch 2.3GHz/128GB Silver... Read more
12-inch 64GB iPad Pros available for $749, $5...
MacMall has 12″ 64GB iPad Pros on sale for $749 including free shipping. Their price is $50 off MSRP. Read more

Jobs Board

*Apple* Solutions Consultant - Apple Inc. (U...
…about helping others on a team while also delighting customers? As an Apple Solutions Consultant (ASC), you will discover customers needs and help connect them Read more
Software/Data Engineer, *Apple* Media Produ...
Job Summary Apple Media Products is the team behind the App Store, Apple Music, iTunes, and many other high profile products on iPhone, Mac and AppleTV. Our Data Read more
SW Engineer , *Apple* Media - Apple Inc. (U...
Job Summary Our team is responsible for exposing Apple Media content and services to the world, and building the infrastructure for next generation internal and Read more
*Apple* Data Center Site Selection and Strat...
Job Summary As Apple 's products and services scale the globe, the Data Center Affairs team works behind the scenes to secure infrastructure for Apple 's data Read more
*Apple* Professional Learning Specialist - A...
Job Summary The Apple Professional Learning Specialist is a full-time position for one year with Apple in the Yuma, AZ area. This position requires a high Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.