Wired Actions and QuickTime VR Movies

This appendix explains in step-by-step detail how you can add wired actions to a QuickTime VR movie. The programming tasks involved in adding those wired actions are outlined here.

The complete sample code is available at

http://developer.apple.com/samplecode/Sample_Code/QuickTime.htm

Adding Wired Actions to a QuickTime VR Movie

There are two kinds of wired actions that you can add to QuickTime VR movies:

All currently supported QTVR wired actions are specific to some particular node, so the atom containers implementing the actions are placed in the node information atom container that is contained in the media sample for that node in the QTVR track. Note that wired actions can be in a sprite or Flash track.

Programming Tasks

The programming tasks at hand can be distilled into these steps:

  1. Find a media sample in the QTVR track.

  2. Construct some atom containers for the desired actions.

  3. Place those action containers into the appropriate place in the media sample.

  4. Write the modified media sample back into the QTVR track.

  5. Put an atom into the media property atom container to enable wired action processing.

Step #1––Getting the Movie File and Adding Wired Actions

To get a movie file from the user, you call:

    StandardGetFile(NULL, 1, myTypeList, &myReply);

Now to add some wired actions to the movie file, if it is a QuickTime VR movie, you do this:

    if (myReply.sfGood)
        AddVRAct_AddWiredActionsToQTVRMovie(&myReply.sfFile);

Step #2––Constructing Some Atom Containers

You call AddVRAct_GetFirstHotSpot, which returns through the theHotSpotID parameter the ID of the first hot spot in the specified atom container (which is assumed to be a node information atom container).

The returned ID is not necessarily the numerically least ID; it is just the ID of the first hot spot atom in the atom container.

static OSErr AddVRAct_GetFirstHotSpot (Handle theSample,
                                     long *theHotSpotID)
{
    QTAtom              myHotSpotParentAtom = 0;
    QTAtom              myHotSpotAtom = 0;
    OSErr               myErr = noErr;
 
    *theHotSpotID = 0;
 
    myHotSpotParentAtom = QTFindChildByIndex(theSample,
                                            kParentAtomIsContainer,
                                            kQTVRHotSpotParentAtomType,
                                            kIndexOne, NULL);
    if (myHotSpotParentAtom != 0)
        myHotSpotAtom = QTFindChildByIndex(theSample, myHotSpotParentAtom,
                                            kQTVRHotSpotAtomType,
                                            kIndexOne, theHotSpotID);
 
    return(myErr);
}

Now you call AddVRAct_CreateHotSpotActionContainer, which returns through the theActions parameter an atom container that contains a hot spot action.

Step #3––Setting the Pan Angle

Next, you set the pan angle to 10.0 degrees when the hot spot is clicked.

static OSErr AddVRAct_CreateHotSpotActionContainer
                     (QTAtomContainer *theActions)
{
 
    QTAtom          myEventAtom = 0;
    QTAtom          myActionAtom = 0;
    long            myAction;
    float           myPanAngle;
    OSErr           myErr = noErr;
 
    myErr = QTNewAtomContainer(theActions);
    .
    .
    .
 
    myErr = QTInsertChild(*theActions, kParentAtomIsContainer,
                            kQTEventType, kQTEventMouseClick, kIndexOne,
                            kZeroDataLength, NULL, &myEventAtom);
    .
    .
    .
 
    myErr = QTInsertChild(*theActions, myEventAtom, kAction, kIndexOne,
                            kIndexOne, kZeroDataLength, NULL,
                            &myActionAtom);
    if (myErr != noErr)
        goto bail;
 
    myAction = EndianS32_NtoB(kActionQTVRSetPanAngle);
    myErr = QTInsertChild(*theActions, myActionAtom, kWhichAction,
                         kIndexOne, kIndexOne, sizeof(long),
                         &myAction, NULL);
    if (myErr != noErr)
        goto bail;
 
    myPanAngle = 10.0;
    AddVRAct_ConvertFloatToBigEndian(&myPanAngle);
    myErr = QTInsertChild(*theActions, myActionAtom, kActionParameter,
                         kIndexOne, kIndexOne, sizeof(float), &myPanAngle,
                         NULL);
    .
    .
    .
}

Step #4––Setting the Pan Angle to 180.0 Degrees

You now call AddVRAct_CreateFrameLoadedActionContainer, which returns through the theActions parameter an atom container that contains a frame-loaded event action.

Next, you set the pan angle to 180.0 degrees.

static OSErr AddVRAct_CreateFrameLoadedActionContainer
                         (QTAtomContainer *theActions)
{
    QTAtom          myEventAtom = 0;
    QTAtom          myActionAtom = 0;
    long            myAction;
    float           myPanAngle;
    OSErr           myErr = noErr;
 
    myErr = QTNewAtomContainer(theActions);
    if (myErr != noErr)
        goto bail;
 
    myErr = QTInsertChild(*theActions, kParentAtomIsContainer,
                         kQTEventFrameLoaded, kIndexOne, kIndexOne,
                         kZeroDataLength, NULL, &myEventAtom);
    .
    .
    .
 
    myErr = QTInsertChild(*theActions, myEventAtom, kAction, kIndexOne,
                         kIndexOne, kZeroDataLength, NULL,
                         &myActionAtom);
    .
    .
    .
 
    myAction = EndianS32_NtoB(kActionQTVRSetPanAngle);
    myErr = QTInsertChild(*theActions, myActionAtom, kWhichAction,
                         kIndexOne, kIndexOne, sizeof(long),
                         &myAction, NULL);
    .
    .
    .
 
    myPanAngle = 180.0;
    AddVRAct_ConvertFloatToBigEndian(&myPanAngle);
    myErr = QTInsertChild(*theActions, myActionAtom, kActionParameter,
                         kIndexOne, kIndexOne, sizeof(float),
                         &myPanAngle, NULL);
    .
    .
    .
}

Step #5––Setting Actions to be Frame-Loaded

Now you call AddVRAct_SetFrameLoadedWiredActions to set the specified actions to be a frame-loaded action. If theActions is NULL, you remove any existing frame-loaded action from theSample.

The theSample parameter is assumed to be a node information atom container; any actions that are global to the node should be inserted at the root level of this atom container. In addition, the container type should be the same as the event type and should have an atom ID of 1.

static OSErr AddVRAct_SetFrameLoadedWiredActions
                         (Handle theSample, QTAtomContainer theActions)
{
    QTAtom          myEventAtom = 0;
    QTAtom          myTargetAtom = 0;
    OSErr           myErr = noErr;
 
    // look for a frame-loaded action atom
    // in the specified actions atom container
    if (theActions != NULL)
        myEventAtom = QTFindChildByID(theActions, kParentAtomIsContainer,
                                     kQTEventFrameLoaded, kIndexOne,
                                     NULL);
 
    // look for a frame-loaded action atom
    // in the node information atom container
    myTargetAtom = QTFindChildByID(theSample, kParentAtomIsContainer,
                                    kQTEventFrameLoaded, kIndexOne, NULL);
    if (myTargetAtom != 0) {
        // if there is already a frame-loaded event atom in the node
        // information atom container,
        // then either replace it with the one we were passed or remove it
        if (theActions != NULL)
            myErr = QTReplaceAtom(theSample, myTargetAtom, theActions,
                                    myEventAtom);
        else
            myErr = QTRemoveAtom(theSample, myTargetAtom);
    }  else {
        // there is no frame-loaded event atom
        // in the node information atom container,
        // so add in the one we were passed
        if (theActions != NULL)
            myErr = QTInsertChildren(theSample, kParentAtomIsContainer,
                                    theActions);
    }
 
    return(myErr);
}

Step #6––Setting Hot Spot Actions

Now using AddVRAct_SetWiredActionsToHotSpot, you set the specified actions to be a hot-spot action.

If theActions is NULL, you remove any existing hot-spot actions for the specified hot spot from theSample.

static OSErr AddVRAct_SetWiredActionsToHotSpot (Handle theSample, long
                                                 theHotSpotID,
                                                 QTAtomContainer
                                                 theActions)
{
    QTAtom                  myHotSpotParentAtom = 0;
    QTAtom                  myHotSpotAtom = 0;
                            short   myCount, myIndex;
    OSErr                   myErr = paramErr;
 
    myHotSpotParentAtom = QTFindChildByIndex(theSample,
                                            kParentAtomIsContainer,
                                            kQTVRHotSpotParentAtomType,
                                            kIndexOne, NULL);
    if (myHotSpotParentAtom == NULL)
        goto bail;
 
    myHotSpotAtom = QTFindChildByID(theSample, myHotSpotParentAtom,
                                 kQTVRHotSpotAtomType, theHotSpotID,
                                 NULL);
    if (myHotSpotAtom == NULL)
        goto bail;
 
    // see how many events are already associated
    // with the specified hot spot
 
    myCount = QTCountChildrenOfType(theSample, myHotSpotAtom,
                                     kQTEventType);
 
    for (myIndex = myCount; myIndex > 0; myIndex--) {
        QTAtom              myTargetAtom = 0;
 
        // remove all the existing events
        myTargetAtom = QTFindChildByIndex(theSample, myHotSpotAtom,
                                         kQTEventType, myIndex, NULL);
        if (myTargetAtom != 0) {
            myErr = QTRemoveAtom(theSample, myTargetAtom);
            if (myErr != noErr)
                goto bail;
        }
    }
 
    if (theActions) {
        myErr = QTInsertChildren(theSample, myHotSpotAtom, theActions);
        if (myErr != noErr)
            goto bail;
    }
 
bail:
    return(myErr);
}

Step #7––Adding a Media Property Atom

To add a media property atom to the specified media, you call AddVRAct_WriteMediaPropertyAtom.

You assume that the data passed through the theProperty parameter is big-endian.

static OSErr AddVRAct_WriteMediaPropertyAtom (Media theMedia,
             long thePropertyID, long thePropertySize, void *theProperty)
{
    QTAtomContainer             myPropertyAtom = NULL;
    QTAtom                  myAtom = 0;
    OSErr               myErr = noErr;
 
    // get the current media property atom
    myErr = GetMediaPropertyAtom(theMedia, &myPropertyAtom);
    if (myErr != noErr)
        goto bail;
 
    // if there isn't one yet, then create one
    if (myPropertyAtom == NULL) {
        myErr = QTNewAtomContainer(&myPropertyAtom);
        if (myErr != noErr)
            goto bail;
    }
 
    // see if there is an existing atom of the specified type;
    // if not, then create one
    myAtom = QTFindChildByID(myPropertyAtom, kParentAtomIsContainer,
                            thePropertyID, kIndexOne, NULL);
    if (myAtom == NULL) {
        myErr = QTInsertChild(myPropertyAtom, kParentAtomIsContainer,
                             thePropertyID, kIndexOne, kIndexZero,
                             kZeroDataLength, NULL, &myAtom);
        if ((myErr != noErr) || (myAtom == NULL))
            goto bail;
    }
 
    // set the data of the specified atom to the data passed in
    myErr = QTSetAtomData(myPropertyAtom, myAtom, thePropertySize,
                         (Ptr)theProperty);
    if (myErr != noErr)
        goto bail;
 
    // write the new atom data out to the media property atom
    myErr = SetMediaPropertyAtom(theMedia, myPropertyAtom);
 
bail:
    if (myPropertyAtom != NULL)
        myErr = QTDisposeAtomContainer(myPropertyAtom);
    // this kills any error report above
 
    return(myErr);
}

Step #8––Adding Wired Actions

To add some wired actions to the specified QTVR movie, you call AddVRAct_AddWiredActionsToQTVRMovie.

Wired actions are added to a QTVR movie by adding atom containers in the appropriate locations.

static void AddVRAct_AddWiredActionsToQTVRMovie (FSSpec *theFSSpec)
{
    short                           myResID = 0;
    short                           myResRefNum = -1;
    Movie                           myMovie = NULL;
    Track                           myTrack = NULL;
    Media                           myMedia = NULL;
    TimeValue                       myTrackOffset;
    TimeValue                       myMediaTime;
    TimeValue                       mySampleDuration;
    TimeValue                       mySelectionDuration;
    TimeValue                       myNewMediaTime;
    QTVRSampleDescriptionHandle     myQTVRDesc = NULL;
    Handle                          mySample = NULL;
    short                           mySampleFlags;
    Fixed                           myTrackEditRate;
    QTAtomContainer                 myActions = NULL;
    Boolean                         myHasActions;
    long                            myHotSpotID = 0L;
    OSErr                           myErr = noErr;
 

Step #9––Opening the Movie and Getting the Track

You open the movie file for reading and writing and get the QTVR track from the movie.

    myErr = OpenMovieFile(theFSSpec, &myResRefNum, fsRdWrPerm);
    if (myErr != noErr)
        goto bail;
 
    myErr = NewMovieFromFile(&myMovie, myResRefNum, &myResID, NULL,
                            newMovieActive, NULL);
    if (myErr != noErr)
        goto bail;
 
    // find the first QTVR track in the movie;
    // this assumes that the movie is a QuickTime VR movie formatted
    // according to version 2.0 or later
    // (version 1.0 VR movies don't have a QTVR track)
    myTrack = GetMovieIndTrackType(myMovie, kIndexOne, kQTVRQTVRType,
                                movieTrackMediaType);
    if (myTrack == NULL)
        goto bail;

Step #10––Getting the First Media Sample

You call GetTrackMedia to get the first media sample in the QTVR track.

The QTVR track contains one media sample for each node in the movie; that sample contains a node information atom container, which contains general information about the node (such as its type, its ID, its name, and a list of its hot spots).

    myMedia = GetTrackMedia(myTrack);
    if (myMedia == NULL)
        goto bail;
 
    myTrackOffset = GetTrackOffset(myTrack);
    myMediaTime = TrackTimeToMediaTime(myTrackOffset, myTrack);
 
    // allocate some storage to hold the sample description
    // for the QTVR track
    myQTVRDesc = (QTVRSampleDescriptionHandle)NewHandle(4);
    if (myQTVRDesc == NULL)
        goto bail;
 
    mySample = NewHandle(0);
    if (mySample == NULL)
        goto bail;
 
    myErr = GetMediaSample(myMedia, mySample, 0, NULL, myMediaTime, NULL,
                            &mySampleDuration,
                            (SampleDescriptionHandle)myQTVRDesc, NULL, 1,
                            NULL, &mySampleFlags);
    if (myErr != noErr)
        goto bail;
// create an action container for frame-loaded actions
    myErr = AddVRAct_CreateFrameLoadedActionContainer(&myActions);
    if (myErr != noErr)
        goto bail;
 
    // add frame-loaded actions to sample
    myErr = AddVRAct_SetFrameLoadedWiredActions(mySample, myActions);
    if (myErr != noErr)
        goto bail;
 
    myErr = QTDisposeAtomContainer(myActions);
    if (myErr != noErr)
        goto bail;
// find the first hot spot in the selected node
    myErr = AddVRAct_GetFirstHotSpot(mySample, &myHotSpotID);
    if ((myErr != noErr) || (myHotSpotID == 0))
        goto bail;
 
    // create an action container for hot-spot actions
    myErr = AddVRAct_CreateHotSpotActionContainer(&myActions);
    if (myErr != noErr)
        goto bail;
 
    // add hot-spot actions to sample
    myErr = AddVRAct_SetWiredActionsToHotSpot(mySample, myHotSpotID,
                                             myActions);
    if (myErr != noErr)
        goto bail;
//replace sample in media
myTrackEditRate = GetTrackEditRate(myTrack, myTrackOffset);
    if (GetMoviesError() != noErr)
        goto bail;
 
    GetTrackNextInterestingTime(myTrack, nextTimeMediaSample |
                                nextTimeEdgeOK, myTrackOffset, fixed1,
                                NULL, &mySelectionDuration);
    if (GetMoviesError() != noErr)
        goto bail;
 
    myErr = DeleteTrackSegment(myTrack, myTrackOffset,
                             mySelectionDuration);
    if (myErr != noErr)
        goto bail;
 
    myErr = BeginMediaEdits(myMedia);
    if (myErr != noErr)
        goto bail;
 
    myErr = AddMediaSample( myMedia,
                            mySample,
                            0,
                            GetHandleSize(mySample),
                            mySampleDuration,
                            (SampleDescriptionHandle)myQTVRDesc,
                            1,
                            mySampleFlags,
                            &myNewMediaTime);
    if (myErr != noErr)
        goto bail;
 
    myErr = EndMediaEdits(myMedia);
    if (myErr != noErr)
        goto bail;
 
    // add the media to the track
    myErr = InsertMediaIntoTrack(myTrack, myTrackOffset, myNewMediaTime,
                                mySelectionDuration, myTrackEditRate);
    if (myErr != noErr)
        goto bail;
    // set the actions property atom, to enable wired action processing
    // myHasActions = true; since sizeof(Boolean) == 1,
    // there is no need to swap bytes here
    myErr = AddVRAct_WriteMediaPropertyAtom(myMedia,
                                        kSpriteTrackPropertyHasActions,
                                        sizeof(Boolean), &myHasActions);
    if (myErr != noErr)
        goto bail;
// update the movie resource
    myErr = UpdateMovieResource(myMovie, myResRefNum, myResID, NULL);
    if (myErr != noErr)
        goto bail;
 
    // close the movie file
    myErr = CloseMovieFile(myResRefNum);
 
bail:
    if (myActions != NULL)
        (void)QTDisposeAtomContainer(myActions);
 
    if (mySample != NULL)
        DisposeHandle(mySample);
 
    if (myQTVRDesc != NULL)
        DisposeHandle((Handle)myQTVRDesc);
 
    if (myMovie != NULL)
        DisposeMovie(myMovie);
}

Step #11––Converting to Big-Endian Format

You call AddVRAct_ConvertFloatToBigEndian to convert the specified floating-point number to big-endian format.

void AddVRAct_ConvertFloatToBigEndian (float *theFloat)
{
    unsigned long               *myLongPtr;
 
    myLongPtr = (unsigned long *)theFloat;
    *myLongPtr = EndianU32_NtoB(*myLongPtr);
}