TweetFollow Us on Twitter

Modern Times

Volume Number: 20 (2004)
Issue Number: 5
Column Tag: Programming

QuickTime Toolkit

by Tim Monroe

Modern Times

Updating the QTShell Application Framework

Introduction

We began this series of articles by developing a simple C-based application called QTShell that runs on both Windows and Macintosh operating systems. QTShell can open and display QuickTime movies, and it supports the standard movie editing operations. Over the past several years, we've gradually tinkered with QTShell to add various capabilities. For instance, in an earlier article ("2001: A Space Odyssey" in MacTech, January 2001), we upgraded the Macintosh portions of the code to use the Navigation Services APIs instead of the Standard File Package APIs we used originally (and still use in our Windows code). This was an important step on the road to full Carbonization, which allowed QTShell to run natively on Mac OS X as well as on Mac OS 8 and 9. And in another article ("Event Horizon" in MacTech, May 2002), we saw how to switch to the Carbon event model of processing events.

In this article, I want to present several more enhancements to QTShell. The Navigation Services functions that it currently calls, NavGetFile and NavPutFile, are now deprecated; they still work just fine, but they are no longer recommended. By moving to the more modern APIs provided by Navigation Services 3.0, we can pave the way for support for Unicode filenames and for displaying the Save As dialog box as a sheet, as in Figure 1.


Figure 1: The Save As sheet

This is a nicer interface than the dialog box displayed by NavPutFile, which is shown in Figure 2.


Figure 2: The Save As dialog box

I also want to show how to convert QTShell to use the movie storage functions introduced in QuickTime 6. A movie storage container is simply any container that can be addressed using a data reference, for instance a file or a block of memory. Currently QTShell works only with files specified using file system specification records (of type FSSpec). In an earlier article ("Somewhere I'll Find You" in MacTech, October, 2000), however, we how to open local and remote movie files using NewMovieFromDataRef with file and URL data references. It would be nice to operate on all QuickTime movie data using a single set of APIs, and that's what the movie storage functions provide. Instead of calling OpenMovieFile and specifying a file using an FSSpec, we can call OpenMovieStorage and specify a storage container using a data reference. Then, when it's time to save changes to a movie, we can call UpdateMovieInStorage instead of UpdateMovieResource. And so on. To complement these storage APIs, QuickTime 6.4 introduced a large number of data reference utilities that can create data references from data of type FSSpec, CFString, FSRef, CFURL and a handful of other types.

The ultimate goal in moving to the new Navigation Services APIs and the movie storage APIs is to be able to expunge all traces of file system specification records from QTShell. The main problem with FSSpecs is that they cannot represent files with non-ASCII Unicode names or names longer than 63 characters. These other data types -- CFString, FSRef, and CFURL -- can easily represent Unicode filenames and very long filenames.

Unfortunately, the complete removal of FSSpec data values from QTShell and all the associated utilities files that our applications depend upon, on both Macintosh and Windows, would require an overhaul that is beyond the scope of this article. But we'll do enough of the groundwork that finally making the jump to an FSSpec-free application will not be too difficult.

File Selection

Let's begin by getting rid of our calls to NavGetFile and NavPutFile. Navigation Services 3.0 and later replace these functions with a handful of functions that allow greater control over the file-selection process. They allow us to display the file-saving dialog box as a sheet (as in Figure 1) and they support retrieving information about selected files in the form of an FSRef, which supports Unicode and long filenames.

Choosing a File to Open

Currently QTShell calls the NavGetFile function to display the standard file-opening dialog box, shown in Figure 3. NavGetFile handles everything involved in displaying the dialog box and handling user actions in the box. When it exits, it fills out a record of type NavReplyRecord that contains information about the selected file, if any.


Figure 3: The file-opening dialog box

In Navigation Services 3.0, this scheme was changed significantly but not enough to cause major upheavals in our existing source code. We still need to get the default options for the dialog box, but now we need to call NavGetDefaultDialogCreationOptions, not NavGetDefaultDialogOptions:

NavGetDefaultDialogCreationOptions(&myOptions);
myOptions.optionFlags -= kNavNoTypePopup;
myOptions.optionFlags -= kNavAllowMultipleFiles;
myOptions.modality = kWindowModalityAppModal;
myOptions.clientName = CFStringCreateWithPascalString(NULL, 
   gAppName, GetApplicationTextEncoding());

This departs from our existing code in several ways. First, the clientName field is a CFString, not a Pascal string. We can create that string from an existing Pascal string by calling CFStringCreateWithPascalString. Later we'll need to release the string like this:

CFRelease(myOptions.clientName);

The other interesting option is the modality field, which can take these values:

enum {
   kWindowModalityNone                  = 0,
   kWindowModalitySystemModal           = 1,
   kWindowModalityAppModal              = 2,
   kWindowModalityWindowModal           = 3
};

As you can see, we use the kWindowModalityAppModal constant, which causes the dialog box to prevent user interaction with all other windows in the application. A sheet would use the kWindowModalityWindowModal constant, which blocks user interaction with just one other window (the one the sheet is attached to).

Once we've set up the dialog box options, we create the dialog box by calling NavCreateGetFileDialog:

myErr = NavCreateGetFileDialog(&myOptions, NULL, 
      myEventUPP, NULL, (NavObjectFilterUPP)theFilterProc,
      (void*)myOpenList, &myDialogRef);

This call however does not display the dialog box to the user. This gives us an opportunity to further customize the appearance of the dialog box by calling NavCustomControl (as we'll do in a few moments). Once we've customized the box to our liking, we show it to the user by calling NavDialogRun.

When NavDialogRun returns, we can call NavDialogGetReply to retrieve a NavReplyRecord record that contains information about the selected file. We then proceed as before by getting an FSSpec for the selected file, which we return to the caller. Listing 1 shows the new definition of QTFrame_GetOneFileWithPreview.

Listing 1: Eliciting a movie file from the user

QTFrame_GetOneFileWithPreview
OSErr QTFrame_GetOneFileWithPreview (short theNumTypes, 
      QTFrameTypeListPtr theTypeList, FSSpecPtr theFSSpecPtr, 
      void *theFilterProc)
{
#if TARGET_OS_WIN32
   StandardFileReply            myReply;
#endif
#if TARGET_OS_MAC
   NavDialogRef                     myDialogRef = NULL;
   NavReplyRecord                  myReply;
   NavTypeListHandle            myOpenList = NULL;
   NavEventUPP                     myEventUPP = 
                           NewNavEventUPP(QTFrame_HandleNavEvent);
   NavDialogCreationOptions   myOptions;
#endif
   OSErr                              myErr = noErr;
   
   if (theFSSpecPtr == NULL)
      return(paramErr);
   
   // deactivate any frontmost movie window
   QTFrame_ActivateController(QTFrame_GetFrontMovieWindow(), 
      false);
#if TARGET_OS_WIN32
   // prompt the user for a file
   StandardGetFilePreview((FileFilterUPP)theFilterProc, 
      theNumTypes, (ConstSFTypeListPtr)theTypeList,
      &myReply);
   if (!myReply.sfGood)
      return(userCanceledErr);
   
   // make an FSSpec record
   myErr = FSMakeFSSpec(myReply.sfFile.vRefNum, 
         myReply.sfFile.parID, myReply.sfFile.name, 
         theFSSpecPtr);
#endif
#if TARGET_OS_MAC
   // specify the options for the dialog box
   NavGetDefaultDialogCreationOptions(&myOptions);
   myOptions.optionFlags -= kNavNoTypePopup;
   myOptions.optionFlags -= kNavAllowMultipleFiles;
   myOptions.modality = kWindowModalityAppModal;
   myOptions.clientName = CFStringCreateWithPascalString
                  (NULL, gAppName, GetApplicationTextEncoding());
   
   // create a handle to an 'open' resource
   myOpenList = (NavTypeListHandle)
         QTFrame_CreateOpenHandle(kApplicationSignature, 
            theNumTypes, theTypeList);
   if (myOpenList != NULL)
      HLock((Handle)myOpenList);
   
   // prompt the user for a file
   myErr = NavCreateGetFileDialog(&myOptions, NULL, 
         myEventUPP, NULL, (NavObjectFilterUPP)theFilterProc, 
         (void*)myOpenList, &myDialogRef);
   if ((myErr == noErr) && (myDialogRef != NULL)) {
      AEDesc                  myLocation = {typeNull, NULL};
   
      // if no open-file location exists, use ~/Movies
      if (QTFrame_GetCurrentFileLocationDesc(&myLocation, 
         kGetFileLoc) == noErr)
         NavCustomControl(myDialogRef, kNavCtlSetLocation, 
            (void *)&myLocation);   
      myErr = NavDialogRun(myDialogRef);
      if (myErr == noErr) {
         myErr = NavDialogGetReply(myDialogRef, &myReply);
         if ((myErr == noErr) && myReply.validRecord) {
            AEKeyword      myKeyword;
            DescType         myActualType;
            Size               myActualSize = 0;
            
            // get the FSSpec for the selected file
            if (theFSSpecPtr != NULL)
               myErr = AEGetNthPtr(&(myReply.selection), 1, 
                  typeFSS, &myKeyword, &myActualType, 
                  theFSSpecPtr, sizeof(FSSpec), &myActualSize);
            NavDisposeReply(&myReply);
         }
      }
      
       NavDialogDispose(myDialogRef);
   }
   
   // clean up
   if (myOpenList != NULL) {
      HUnlock((Handle)myOpenList);
      DisposeHandle((Handle)myOpenList);
   }
      
   if (myOptions.clientName != NULL)
      CFRelease(myOptions.clientName);
      
   DisposeNavEventUPP(myEventUPP);
#endif
 
   return(myErr);
}

Choosing a Filename to Save

The changes required to upgrade our existing file-selection routine QTFrame_PutFile are entirely analogous to those considered in the previous section. We need to replace NavPutFile by the combination of NavCreatePutFileDialog, NavDialogRun, NavDialogGetReply, and NavDialogDispose. There is only one "gotcha" here, and it's a big one: the FSSpec that we get when we call AEGetNthPtr no longer specifies the file we want to save the movie into (as it did with NavPutFile); rather, it specifies the directory that contains the file. I'm guessing that this was changed to better support values of type FSRef, which cannot specify non-existent files. The preferred way to respond to NavDialogGetReply is apparently to ask for the parent directory of the chosen filename in the form of an FSRef and then to create the file by calling FSRefCreateFileUnicode, which takes the parent directory and a Unicode filename. Since we are retaining our dependence on FSSpec values, we need to jump though a hoop or two.

What we need to do is find the directory ID of the parent directory returned to us, so that we can create an FSSpec record for the chosen file itself. Listing 2 shows some File Manager voodoo that accomplishes this.

Listing 2: Finding the directory ID of a file's parent directory

QTFrame_PutFile
myErr = AEGetNthPtr(&(myReply.selection), 1, typeFSS,
               &myKeyword, &myActualType, &myDirSpec, 
               sizeof(FSSpec), &myActualSize);
if (myErr == noErr)   {
   myFileName = NavDialogGetSaveFileName(myDialogRef);
   if (myFileName != NULL) {
      CInfoPBRec      myPB;
      myPB.dirInfo.ioVRefNum = myDirSpec.vRefNum;
      myPB.dirInfo.ioDrDirID = myDirSpec.parID;   
      myPB.dirInfo.ioNamePtr = myDirSpec.name;
      myPB.dirInfo.ioFDirIndex = 0;
      myPB.dirInfo.ioCompletion = NULL;
                   
      myErr = PBGetCatInfoSync(&myPB);
      if (myErr == noErr) {
         CFStringGetPascalString(myFileName, myString, 
                  sizeof(FSSpec), GetApplicationTextEncoding());
         myErr = FSMakeFSSpec(myPB.dirInfo.ioVRefNum, 
                  myPB.dirInfo.ioDrDirID, myString, &myMovSpec);
         if (myErr == fnfErr)
            myErr = noErr;
      }
      if (myErr == noErr)
         *theFSSpecPtr = myMovSpec;
   }
}

The trick here is to know that on entry to the PBGetCatInfoSync call, the ioDrDirID field should be set to the directory ID of the parent directory of the directory containing the chosen file, which is what is contained in the FSSpec returned by AEGetNthPtr; on exit that field will contain the directory ID of the directory itself (not its parent). Once we've retrieved that directory ID, we can then call FSMakeFSSpec to create an FSSpec for the file itself.

Showing the Save Changes Dialog Box

QTShell uses one other Navigation Services function, NavAskSaveChanges, in the Macintosh version of the QTFrame_DestroyMovieWindow function. We need to replace this with the newer NavCreateAskSaveChangesDialog. Listing 3 shows the key changed portions of QTFrame_DestroyMovieWindow.

Listing 3: Closing a movie window

QTFrame_DestroyMovieWindow   
if ((**myWindowObject).fIsDirty) {
   Str255                                 myString;
   NavAskSaveChangesAction         myAction;
   NavDialogCreationOptions      myOptions;
   NavUserAction                     myResult;
   NavEventUPP                        myEventUPP = 
                           NewNavEventUPP(QTFrame_HandleNavEvent);
   NavDialogRef                        myDialogRef = NULL;
   // get the title of the window
   GetWTitle(theWindow, myString);
      
   // install the application and document names
   NavGetDefaultDialogCreationOptions(&myOptions);
   myOptions.clientName = 
         CFStringCreateWithPascalString(NULL, gAppName, 
         GetApplicationTextEncoding());
   myOptions.saveFileName = 
         CFStringCreateWithPascalString(NULL, myString, 
         GetApplicationTextEncoding());      
   // specify the action
   myAction = gShuttingDown ? 
                        kNavSaveChangesQuittingApplication : 
                        kNavSaveChangesClosingDocument;
      
   // display the "Save changes" dialog box
   myErr = NavCreateAskSaveChangesDialog(&myOptions, 
            myAction, myEventUPP, NULL, &myDialogRef);
   if ((myErr == noErr) && (myDialogRef != NULL)) {
      myErr = NavDialogRun(myDialogRef);
      if (myErr == noErr) {
         myResult = NavDialogGetUserAction(myDialogRef);      
         switch (myResult) {
            case kNavUserActionSaveChanges:
               // save the data in the window
               QTFrame_UpdateMovieFile(theWindow);
               break;
                  
            case kNavUserActionCancel:
               // do not close the window, and do not quit the application
               gShuttingDown = false;
               return(false);
                  
            case kNavUserActionDontSaveChanges:
               // discard any unsaved changes (that is, don't do anything)
               break;
         }
      }
      
         NavDialogDispose(myDialogRef);
   }
      
   if (myOptions.clientName != NULL)
      CFRelease(myOptions.clientName);
      
   if (myOptions.saveFileName != NULL)
      CFRelease(myOptions.saveFileName);
         
   DisposeNavEventUPP(myEventUPP);
}

Setting the Default Location

Let's end this discussion by making sure that the directory displayed in the file opening and saving dialog boxes is a reasonable default. The Navigation Services functions will always display the most recent directory selected by the user when choosing a file to open or save into. This information is saved on a per-application and per-user basis, in the application's preference file. So, if a user saves a movie file on the Desktop, the next time he or she opens the file-saving dialog box, the Desktop will be the directory shown -- even if the user has quit the application and later relaunched it.

Our application doesn't need to create or read that preferences file explicitly because the Navigation Services functions take care of all that automatically. The only time that we might want to poke our noses into that file is when the user launches our application for the very first time. In that case, there will be no saved directory information. The default behavior of the Navigation Services APIs is to display the Documents folder in the user's home directory.

It's actually quite easy to change that default value to something more useful, perhaps the Movies folder in the user's home directory. To do this, we can use the Preferences APIs to read values out of the preferences file, which is called QTShell.plist and is stored in the Preferences folder that is inside of the Library folder in the user's home directory.

A preferences file is organized as a set of key-value pairs. The key is of type CFString and the value can be any Core Foundation property list type. Navigation Services maintains at least two items in that file, addressed using these keys:

AppleNavServices:PutFile:0:Path
AppleNavServices:GetFile:0:Path

The values associated with these keys are the locations of the directories most recently displayed in the file-saving and file-opening dialog boxes.

For present purposes, we don't need to read the values associated with those keys. Rather, all we need to do is determine whether a specific key exists in the preferences file. If it does, we'll let Navigation Services handle the setting of the directory displayed in the corresponding dialog box. But if one or the other of these keys does not have a value in the preferences file, we'll step in and set the location shown in the dialog box to the better default location, the Movies folder in the user's home directory. Listing 4 shows our definition of QTFrame_GetCurrentFileLocationDesc, which does this. We'll pass in one of these application-defined constants:

#define kPutFileLoc                     1
#define kGetFileLoc                     2

QTFrame_GetCurrentFileLocationDesc then calls CFPreferencesCopyAppValue to find the appropriate preference item. If it exists, we return paramErr to the caller to indicate that a preference item already exists for the specified dialog box. Otherwise we'll construct an AEDesc value for the desired folder and pass that back to the caller.

Listing 4: Setting the default file location

QTFrame_GetCurrentFileLocationDesc
#if TARGET_OS_MAC
OSErr QTFrame_GetCurrentFileLocationDesc
   (AEDescPtr theLocation, short theFileType)
{
   CFStringRef               myLocKey;
   CFPropertyListRef      myLoc;
   FSRef                        myFSRef;
   FSSpec                        myFSSpec;
   OSErr                        myErr = noErr;
   
   if (theLocation == NULL)
      return(paramErr);
      
   if (theFileType == kPutFileLoc)
      myLocKey = CFSTR("AppleNavServices:PutFile:0:Path");
   else
      myLocKey = CFSTR("AppleNavServices:GetFile:0:Path");
   // see whether our application's Preferences plist already contains a file location
   myLoc = CFPreferencesCopyAppValue(myLocKey, 
                              kCFPreferencesCurrentApplication);
   if (myLoc != NULL) {
      // there is an existing location
      CFRelease(myLoc);
      myErr = paramErr;
   } else {
      // there is no existing location; return a descriptor for ~/Movies
      myErr = FSFindFolder(kUserDomain, 
         kMovieDocumentsFolderType, kCreateFolder, &myFSRef);
      
      if (myErr == noErr)
         myErr = FSGetCatalogInfo(&myFSRef, kFSCatInfoNone,
                         NULL, NULL, &myFSSpec, NULL);
      if (myErr == noErr)
           myErr = AECreateDesc(typeFSS, &myFSSpec, 
                           sizeof(FSSpec), theLocation);
   }
   
   return(myErr);
}
#endif

All that remains is to call this function inside of QTFrame_GetOneFileWithPreview and QTFrame_PutFile. If you look back at Listing 3, you'll see these lines of code immediately preceding the call to NavDialogRun:

if (QTFrame_GetCurrentFileLocationDesc(&myLocation, 
            kGetFileLoc) == noErr)
   NavCustomControl(myDialogRef, kNavCtlSetLocation, 
            (void *)&myLocation);   

Movie Storage Functions

QuickTime 6.0 introduced a set of functions called the movie storage APIs. The fundamental idea here is dead simple: instead of being restricted to opening, updating, creating, and deleting movie files, we should be able to perform these operations on any containers that hold movie data. As you know, the most general means of picking out movie data is by using a data reference. Accordingly, the movie storage APIs allow us to operate on movie data using data references and their associated data handlers.

Let's consider an example. Our application currently opens a movie specified by a file system specification record by calling OpenMovieFile, like this:

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

If successful, OpenMovieFile returns a file reference number, which we use in all subsequent operations on the movie file. For instance, we can read the movie from that file using this code:

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

When we later want to save the user's changes to a movie, we call UpdateMovieResource, passing in the file reference number and the movie resource ID:

myErr = UpdateMovieResource(myMovie, 
   (**myWindowObject).fFileRefNum, 
   (**myWindowObject).fFileResID, NULL);

Using the new movie storage APIs, we can use OpenMovieStorage to open movie data specified by a data reference:

myErr = OpenMovieStorage(myDataRef, myDataRefType, 
      kDataHCanRead + kDataHCanWrite, &myDataHandler);

If successful, OpenMovieStorage returns an instance of a data handler, which we use in all subsequent operations on the movie container. For instance, we can save the user's changes to a movie using this code:

myErr = UpdateMovieInStorage(myMovie, 
      (**myWindowObject).fDataHandler);

Here's a list of the new movie storage APIs:

    CreateMovieStorage (replaces CreateMovieFile)

    OpenMovieStorage (replaces OpenMovieFile)

    NewMovieFromStorageOffset (replaces NewMovieFromFile)

    CloseMovieStorage (replaces CloseMovieFile)

    DeleteMovieStorage (replaces DeleteMovieFile)

    AddMovieToStorage (replaces AddMovieResource)

    PutMovieIntoStorage (replaces PutMovieIntoFile)

    UpdateMovieInStorage (replaces UpdateMovieResource)

    FlattenMovieDataToDataRef (replaces FlattenMovieData)

It's actually quite easy to upgrade QTShell to use these new functions. In this section, we'll see how to do this.

Maintaining Movie Storage Identifiers

First, as you probably have guessed from the snippet of code that calls UpdateMovieInStorage, we need to add a few fields to our window object record to keep track of the data reference, its type, and the data handler associated with the storage container.

typedef struct {
   WindowReference                fWindow;
   Movie                          fMovie;
   MovieController                fController;
   GraphicsImportComponent        fGraphicsImporter;      
   FSSpec                         fFileFSSpec;
   short                          fFileResID;
   short                          fFileRefNum;
   Boolean                        fCanResizeWindow;
   Boolean                        fIsDirty;
   Boolean                        fIsQTVRMovie;
   QTVRInstance                   fInstance;
   OSType                         fObjectType;
   Handle                         fAppData;
#if USE_DATA_REF_FUNCTIONS
   Handle                         fDataRef;
   OSType                         fDataRefType;
   DataHandler                    fDataHandler;
#endif
} WindowObjectRecord, *WindowObjectPtr, **WindowObject;

Notice that we use the compiler flag USE_DATA_REF_FUNCTIONS to conditionalize our code. This allows us to switch back to using the file-based functions if the need arises.

Opening a Movie

Perhaps the trickiest part of migrating to the movie storage functions is deciding how to open a movie storage container. Our file-based code calls OpenMovieFile and then NewMovieFromFile. So we might expect to call OpenMovieStorage and then NewMovieFromStorageOffset. But that's not quite right. NewMovieFromStorageOffset requires us to specify an offset to the movie atom within the storage container. In most cases we don't know what that offset is. Further, if we simply pass an offset of 0, we won't be able to open any QuickTime movie files that are not Fast Start files (where the movie atom is the first atom in the file). So we need a different strategy.

What seems to work is to call OpenMovieStorage and then NewMovieFromDataRef. Listing 5 shows a section of our revised version of QTFrame_OpenMovieInWindow.

Listing 5: Opening a movie

QTFrame_OpenMovieInWindow
#if USE_DATA_REF_FUNCTIONS
myErr = QTNewDataReferenceFromFSSpec(&myFSSpec, 0, 
               &myDataRef, &myDataRefType);
if (myErr != noErr)
   goto bail;
      
// ideally, we'd like read and write permission, but we'll settle for read-only permission
myErr = OpenMovieStorage(myDataRef, myDataRefType, 
               kDataHCanRead + kDataHCanWrite, &myDataHandler);
if (myErr != noErr)
   myErr = OpenMovieStorage(myDataRef, myDataRefType, 
               kDataHCanRead, &myDataHandler);
      
// if we couldn't open the file with even just read-only permission, bail....
if (myErr != noErr)
   goto bail;
// now fetch the first movie from the file
myErr = NewMovieFromDataRef(&myMovie, newMovieActive, 
               &myResID, myDataRef, myDataRefType);
if (myErr != noErr)
   goto bail;
#else
// ideally, we'd like read and write permission, but we'll settle for read-only permission
myErr = OpenMovieFile(&myFSSpec, &myRefNum, fsRdWrPerm);
if (myErr != noErr)
   myErr = OpenMovieFile(&myFSSpec, &myRefNum, fsRdPerm);
// if we couldn't open the file with even just read-only permission, bail....
if (myErr != noErr)
   goto bail;
// now fetch the first movie from the file
myResID = 0;
myErr = NewMovieFromFile(&myMovie, myRefNum, &myResID, 
               NULL, newMovieActive, NULL);
if (myErr != noErr)
   goto bail;
#endif

Then we need to save the movie storage identifiers in our window object record, like this:

#if USE_DATA_REF_FUNCTIONS
   (**myWindowObject).fDataRef = myDataRef;
   (**myWindowObject).fDataRefType = myDataRefType;
   (**myWindowObject).fDataHandler = myDataHandler;
#endif

Saving Changes to a Movie

To save a user's changes to a movie back into its storage container, we can call UpdateMovieInStorage. Listing 6 shows the lines we've altered in the function QTFrame_UpdateMovieFile.

Listing 6: Updating a movie's storage

QTFrame_UpdateMovieFile
#if USE_DATA_REF_FUNCTIONS
if ((**myWindowObject).fDataHandler == NULL)      
   myErr = QTFrame_SaveAsMovieFile(theWindow);
else   
   myErr = UpdateMovieInStorage(myMovie,
                  (**myWindowObject).fDataHandler);
#else
if ((**myWindowObject).fFileRefNum == kInvalidFileRefNum)
   myErr = QTFrame_SaveAsMovieFile(theWindow);
else   
   myErr = UpdateMovieResource(myMovie, 
               (**myWindowObject).fFileRefNum, 
               (**myWindowObject).fFileResID, NULL);
#endif

Closing a Movie

When we're finished working with a movie, we can close it by calling CloseMovieStorage. We also need to dispose of the data reference and the data handler instance associated with the movie. Listing 7 shows the changed lines in the function QTFrame_CloseWindowObject.

Listing 7: Closing a movie

QTFrame_CloseWindowObject
#if USE_DATA_REF_FUNCTIONS
if ((**theWindowObject).fDataHandler != NULL) {
   CloseMovieStorage((**theWindowObject).fDataHandler);
   CloseComponent((**theWindowObject).fDataHandler);
   (**theWindowObject).fDataHandler = NULL;
}
   
if ((**theWindowObject).fDataRef != NULL) {
   DisposeHandle((**theWindowObject).fDataRef);
   (**theWindowObject).fDataRef = NULL;
}
#else
// close the movie file
if ((**theWindowObject).fFileRefNum != kInvalidFileRefNum) {
   CloseMovieFile((**theWindowObject).fFileRefNum);
   (**theWindowObject).fFileRefNum = kInvalidFileRefNum;
}
#endif

Conclusion

Part of the price of delivering a modern QuickTime application is the inevitable need to continually upgrade its underpinnings as the operating system and user interface APIs evolve, or indeed as QuickTime itself evolves. In this article, we've seen how to use the currently recommended functions for selecting files and for opening and operating on movie data. The next step, which we have not taken here, would be to systematically replace all uses of the FSSpec data type by uses of the FSRef data type. This would give us a thoroughly modern application capable of opening movie files with Unicode or very long filenames.


Tim Monroe is a member of the QuickTime engineering team at Apple. You can contact him at monroe@mactech.com. The views expressed here are not necessarily shared by his employer.

 

Community Search:
MacTech Search:

Software Updates via MacUpdate

iFFmpeg 6.4.2 - Convert multimedia files...
iFFmpeg is a comprehensive media tool to convert movie, audio and media files between formats. The FFmpeg command line instructions can be very hard to master/understand, so iFFmpeg does all the hard... Read more
EtreCheck 3.4.2 - For troubleshooting yo...
EtreCheck is an app that displays the important details of your system configuration and allow you to copy that information to the Clipboard. It is meant to be used with Apple Support Communities to... Read more
Carbon Copy Cloner 4.1.17 - Easy-to-use...
Carbon Copy Cloner backups are better than ordinary backups. Suppose the unthinkable happens while you're under deadline to finish a project: your Mac is unresponsive and all you hear is an ominous,... Read more
VueScan 9.5.81 - Scanner software with a...
VueScan is a scanning program that works with most high-quality flatbed and film scanners to produce scans that have excellent color fidelity and color balance. VueScan is easy to use, and has... Read more
Hopper Disassembler 4.2.10- - Binary dis...
Hopper Disassembler is a binary disassembler, decompiler, and debugger for 32- and 64-bit executables. It will let you disassemble any binary you want, and provide you all the information about its... Read more
Viber 6.8.6 - Send messages and make cal...
Viber lets you send free messages and make free calls to other Viber users, on any device and network, in any country! Viber syncs your contacts, messages and call history with your mobile device, so... Read more
Viber 6.8.6 - Send messages and make cal...
Viber lets you send free messages and make free calls to other Viber users, on any device and network, in any country! Viber syncs your contacts, messages and call history with your mobile device, so... Read more
Carbon Copy Cloner 4.1.17 - Easy-to-use...
Carbon Copy Cloner backups are better than ordinary backups. Suppose the unthinkable happens while you're under deadline to finish a project: your Mac is unresponsive and all you hear is an ominous,... Read more
EtreCheck 3.4.2 - For troubleshooting yo...
EtreCheck is an app that displays the important details of your system configuration and allow you to copy that information to the Clipboard. It is meant to be used with Apple Support Communities to... Read more
Hopper Disassembler 4.2.10- - Binary dis...
Hopper Disassembler is a binary disassembler, decompiler, and debugger for 32- and 64-bit executables. It will let you disassemble any binary you want, and provide you all the information about its... Read more

Latest Forum Discussions

See All

Eden: Renaissance (Games)
Eden: Renaissance 1.0 Device: iOS Universal Category: Games Price: $4.99, Version: 1.0 (iTunes) Description: Eden: Renaissance is a thrilling turn-based puzzle adventure set in a luxurious world, offering a deep and moving... | Read more »
Glyph Quest Chronicles guide - how to ma...
Glyph Quest returns with a new free-to-play game, Glyph Quest Chronicles. Chronicles offers up more of the light-hearted, good humored fantasy fun that previous games featured, but with a few more refined tricks up its sleeve. It's a clever mix of... | Read more »
Catch yourself a Lugia and Articuno in P...
Pokémon Go Fest may have been a bit of a disaster, with Niantic offering fans full refunds and $100 worth of in-game curency to apologize for the failed event, but that hasn't ruined trainers' chances of catching new legendary Pokémon. Lugia nad... | Read more »
The best deals on the App Store this wee...
There are quite a few truly superb games on sale on the App Store this week. If you haven't played some of these, many of which are true classics, now's the time to jump on the bandwagon. Here are the deals you need to know about. [Read more] | Read more »
Realpolitiks Mobile (Games)
Realpolitiks Mobile 1.0 Device: iOS Universal Category: Games Price: $5.99, Version: 1.0 (iTunes) Description: PLEASE NOTE: The game might not work properly on discontinued 1GB of RAM devices (iPhone 5s, iPhone 6, iPhone 6 Plus, iPad... | Read more »
Layton’s Mystery Journey (Games)
Layton’s Mystery Journey 1.0.0 Device: iOS Universal Category: Games Price: $15.99, Version: 1.0.0 (iTunes) Description: THE MUCH-LOVED LAYTON SERIES IS BACK WITH A 10TH ANNIVERSARY INSTALLMENT! Developed by LEVEL-5, LAYTON’S... | Read more »
Full Throttle Remastered (Games)
Full Throttle Remastered 1.0 Device: iOS Universal Category: Games Price: $4.99, Version: 1.0 (iTunes) Description: Originally released by LucasArts in 1995, Full Throttle is a classic graphic adventure game from industry legend Tim... | Read more »
Stunning shooter Morphite gets a new tra...
Morphite is officially landing on iOS in September. The game looks like the space shooter we've been needing on mobile, and we're going to see if it fits the bill quite shortly. The game's a collaborative effort between Blowfish Studios, We're Five... | Read more »
Layton's Mystery Journey arrives to...
As you might recall, Layton's Mystery Journey is headed to iOS and Android -- tomorrow! To celebrate the impending launch, Level-5's released a new trailer, complete with an adorable hamster. [Read more] | Read more »
Sidewords (Games)
Sidewords 1.0 Device: iOS Universal Category: Games Price: $2.99, Version: 1.0 (iTunes) Description: Grab a cup of coffee and relax with Sidewords. Sidewords is part logic puzzle, part word game, all original. No timers. No... | Read more »

Price Scanner via MacPrices.net

Apple Move Away from White Label Event Apps C...
DoubleDutch, Inc., a global provider of Live Engagement Marketing (LEM) solutions, has made a statement in the light of a game-changing announcement from Apple at this year’s WWDC conference.... Read more
70 Year Old Artist Creates Art Tools for the...
New Hampshire-based developer Pirate’s Moon has announced MyArtTools 1.1.3, the update to their precision drawing app, designed by artist Richard Hoeper exclusively for use with the 12.9-inch iPad... Read more
Sale! New 2017 13-inch 2.3GHz MacBook Pros fo...
Amazon has new 2017 13″ 2.3GHz/128GB MacBook Pros on sale today for $150 off MSRP including free shipping. Their prices are the lowest available for these models from any reseller: – 13″ 2.3GHz/128GB... Read more
13″ 2.3GHz/128GB Space Gray MacBook Pro on sa...
MacMall has the 13″ 2.3GHz/128GB Space Gray MacBook Pro (MPXQ2LL/A) on sale for $1219 including free shipping. Their price is $80 off MSRP. Read more
Clearance 2016 12-inch Retina MacBooks, Apple...
Apple recently dropped prices on Certified Refurbished 2016 12″ Retina MacBooks, with models now available starting at $1019. Apple will include a standard one-year warranty with each MacBook, and... Read more
Save or Share
FotoJet Designer, is a simple but powerful new graphic design apps available on both Mac and Windows. With FotoJet Designer’s 900+ templates, thousands of resources, and powerful editing tools you... Read more
Logo Maker Shop iOS App Lets Businesses Get C...
A newly released app is designed to help business owners to get creative with their branding by designing their own logos. With more than 1,000 editable templates, Logo Maker Shop 1.0 provides the... Read more
Sale! New 15-inch MacBook Pros for up to $150...
Amazon has the new 2017 15″ MacBook Pros on sale for up to $150 off MSRP including free shipping: – 15″ 2.8GHz MacBook Pro Space Gray: $2249 $150 off MSRP – 15″ 2.89Hz MacBook Pro Space Gray: $2779 $... Read more
DEVONthink To Go 2.1.7 For iOS Brings Usabili...
DEVONtechnologies has updated DEVONthink To Go, the iOS companion to DEVONthink for Mac, with enhancements and bug fixes. Version 2.1.7 adds an option to clear the Global Inbox and makes the grid... Read more
15-inch 2.2GHz Retina MacBook Pro, Apple refu...
Apple has Certified Refurbished 2015 15″ 2.2GHz Retina MacBook Pros available for $1699. That’s $300 off MSRP, and it’s the lowest price available for a 15″ MacBook Pro. An Apple one-year warranty is... Read more

Jobs Board

*Apple* Solutions Consultant (ASC) - Poole -...
Job Summary The people here at Apple don't just create products - they create the kind of wonder that's revolutionised entire industries. It's the diversity of those Read more
SW Engineer *Apple* TV - Apple Inc. (United...
Changing the world is all in a day's work at Apple . If you love innovation, here's your chance to make a career of it. You'll work hard. But the job comes with more Read more
Frameworks Engineering Manager, *Apple* Wat...
Frameworks Engineering Manager, Apple Watch Job Number: 41632321 Santa Clara Valley, California, United States Posted: Jun. 15, 2017 Weekly Hours: 40.00 Job Summary Read more
Product Manager - *Apple* Pay on the *Appl...
Job Summary Apple is looking for a talented product manager to drive the expansion of Apple Pay on the Apple Online Store. This position includes a unique Read more
*Apple* Retail - Multiple Positions - Apple...
SalesSpecialist - Retail Customer Service and SalesTransform Apple Store visitors into loyal Apple customers. When customers enter the store, you're also the Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.