< Previous PageNext Page > Hide TOC

Authoring Wired Movies and Sprite Animations

This chapter describes how you can author wired movies and sprite animations using the sprite media handler. You need to be familiar with the material in the previous two chapters, “Chapter 2, QuickTime Sprites, Sprite Animation and Wired Movies,” and “Chapter 3, Sprite Media Handler,” in order to take advantage of the techniques described in this chapter.

You use the functions provided by the sprite media handler to create and manipulate a sprite animation as a track in a QuickTime movie. You can also use the functions provided by the sprite media handler to create and manipulate a wired sprite movie, with various types of user interactivity. The chapter is illustrated with code snippets from the sample program, QTWiredSprites.c, which is listed in full in Appendix A of this book.

The chapter is divided into the following major sections:

For API reference information about the constants and functions available to your application, refer to the QuickTime API Reference, which is available at

http://developer.apple.com/documentation/Quicktime/QuickTime.html

Authoring Movies With the Sprite Media Handler

The sprite media handler provides functions that allow an application to create and manipulate a sprite animation as a track in a QuickTime movie.

The following sections are illustrated with code from the sample program QTWiredSprites.c, which creates a 320 by 240 pixel QuickTime movie with one sprite track. The sprite track contains six sprites, including two penguins and four buttons. The sample program, which takes advantage of wired sprites, is explained in greater detail “Authoring Wired Movies.” A partial code listing is available in Appendix A. You can download the full sample code at QuickTime website at

http://www.apple.com/quicktime/developers/samplecode.html#sprites.

Defining a Key Frame Sample

To create a sprite track in a QuickTime movie, you must first create the movie itself, a track to contain the sprites, and the track’s media.

After doing this, you can define a key frame sample. A key frame sample defines the number of sprites, their initial property values, and the shared image data used by the sprites in the key frame sample and in all override samples that follow the key frame sample. The sample code discussed in this section creates a single key frame sample and shows how to add images for other sprites, as well as actions for other sprites.

Creating the Movie, Sprite Track, and Media

Listing 4-1 shows a code fragment from the sample code QTWiredSprites.c. This sample code, which is available in Appendix A, illustrates how you can create a new movie file that calls a sample code function, AddSpriteTrackToMovie, which is responsible for creating a sprite track and adding it to the movie.

Listing 4-1  Creating a sprite track movie

// Create a QuickTime movie containing a wired sprites track
.
.
.
    // create a new movie file and set its controller type
    // ask the user for the name of the new movie file
    StandardPutFile("\pSprite movie file name:", "\pSprite.mov",
                    &myReply);
    if (!myReply.sfGood)
        goto bail;
 
    // create a movie file for the destination movie
    myErr = CreateMovieFile(&myReply.sfFile, FOUR_CHAR_CODE('TVOD'), 0,
                            myFlags, &myResRefNum, &myMovie);
    if (myErr != noErr)
        goto bail;
 
    // select the "no controller" movie controller
    myType = EndianU32_NtoB(myType);
    SetUserDataItem(GetMovieUserData(myMovie), &myType, sizeof(myType),
                    kUserDataMovieControllerType, 1);

The following code fragment from AddSpriteTrackToMovie (Listing 4-2) creates a new track and new media, and creates an empty key frame sample. AddSpriteTrackToMovie then calls BeginMediaEdits (Listing 4-4) to prepare to add samples to the track’s media.

Listing 4-2  Creating a track and media

// create the sprite track and media
 
    myTrack = NewMovieTrack(myMovie, ((long)kSpriteTrackWidth << 16),
                            ((long)kSpriteTrackHeight << 16), kNoVolume);
    myMedia = NewTrackMedia(myTrack, SpriteMediaType,
                            kSpriteMediaTimeScale, NULL, 0);
 
// create a new, empty key frame sample
    myErr = QTNewAtomContainer(&mySample);
    if (myErr != noErr)
        goto bail;
 
    myKeyColor.red = 0xffff;    // white
    myKeyColor.green = 0xffff;
    myKeyColor.blue = 0xffff;

Adding Images to the Key Frame Sample

The AddPICTImageToKeyFrameSample function (Listing 4-3) adds images to the key frame sample.

Listing 4-3  Adding images to the key frame sample

// add images to the key frame sample
    AddPICTImageToKeyFrameSample(mySample, kGoToBeginningButtonUp,
                &myKeyColor, kGoToBeginningButtonUpIndex, NULL, NULL);
    AddPICTImageToKeyFrameSample(mySample, kGoToBeginningButtonDown,
                &myKeyColor, kGoToBeginningButtonDownIndex, NULL, NULL);
    AddPICTImageToKeyFrameSample(mySample, kGoToEndButtonUp, &myKeyColor,
                                    kGoToEndButtonUpIndex, NULL, NULL);
...
    AddPICTImageToKeyFrameSample(mySample, kPenguinForward, &myKeyColor,
                                    kPenguinForwardIndex, NULL, NULL);
    AddPICTImageToKeyFrameSample(mySample, kPenguinLeft, &myKeyColor,
                                    kPenguinLeftIndex, NULL, NULL);
    AddPICTImageToKeyFrameSample(mySample, kPenguinRight, &myKeyColor,
                                    kPenguinRightIndex, NULL, NULL);
    AddPICTImageToKeyFrameSample(mySample, kPenguinClosed, &myKeyColor,
                                    kPenguinClosedIndex, NULL, NULL);
 
    for (myIndex = kPenguinDownRightCycleStartIndex, myResID =
            kWalkDownRightCycleStart;
         myIndex <= kPenguinDownRightCycleEndIndex;
         myIndex++, myResID++)
    AddPICTImageToKeyFrameSample(mySample, myResID, &myKeyColor, myIndex,
                                 NULL, NULL);

Adding More Images for Other Sprites

To add more images to other sprites, you assign group IDs to those images, using the AssignImageGroupIDsToKeyFrame function. You then create the sprite track, add it to theMovie, and then begin to add samples to the tracks’ media, as shown in Listing 4-4.

Listing 4-4  Adding more images to other sprites and specifying button actions

// assign group IDs to the images
    AssignImageGroupIDsToKeyFrame(mySample);
 
    // add samples to the sprite track's media
    //
 
 
    BeginMediaEdits(myMedia);
 
    // go to beginning button with no actions
    myErr = QTNewAtomContainer(&myBeginButton);
    if (myErr != noErr)
        goto bail;
    myLocation.h    = (1 * kSpriteTrackWidth / 8) - (kStartEndButtonWidth
                                                    / 2);
    myLocation.v    = (4 * kSpriteTrackHeight / 5) -
                                    (kStartEndButtonHeight / 2);
    isVisible       = false;
    myLayer         = 1;
    myIndex         = kGoToBeginningButtonUpIndex;
    myErr = SetSpriteData(myBeginButton, &myLocation, &isVisible,
                            &myLayer, &myIndex, NULL, NULL, myActions);
    if (myErr != noErr)
        goto bail;

Adding Sprites to the Key Frame Sample

The AddSpriteTrackToMovie function adds the sprites with their initial property values to the key frame sample, as shown in Listing 4-5. The key frame contains four buttons and two penguins.

If the withBackgroundPicture parameter is true, the function adds a background sprite. The function initializes the background sprite’s properties, including setting the layer property to kBackgroundSpriteLayerNum to indicate that the sprite is a background sprite. The function calls SetSpriteData (Listing 4-6), which adds the appropriate property atoms to the spriteData atom container. Then, AddSpriteTrackToMovie calls AddSpriteToSample (Listing 4-7) to add the atoms in the spriteData atom container to the key frame sample atom container.

AddSpriteTrackToMovie adds the other sprites to the key frame sample and then calls AddSpriteSampleToMedia (Listing 4-8) to add the key frame sample to the media.

Listing 4-5  Creating more key frame sprite media

// add actions to the six sprites
    //
    // add go to beginning button
    myErr = QTCopyAtom(myBeginButton, kParentAtomIsContainer,
                        &myBeginActionButton);
    if (myErr != noErr)
        goto bail;
 
    AddSpriteSetImageIndexAction(myBeginActionButton,
                                kParentAtomIsContainer,
                                kQTEventMouseClick, 0, NULL, 0, 0, NULL,
                                kGoToBeginningButtonDownIndex, NULL);
    AddSpriteSetImageIndexAction(myBeginActionButton,
                                kParentAtomIsContainer,
                                kQTEventMouseClickEnd, 0, NULL, 0, 0,
                                NULL, kGoToBeginningButtonUpIndex, NULL);
    AddMovieGoToBeginningAction(myBeginActionButton,
                                kParentAtomIsContainer,
                                kQTEventMouseClickEndTriggerButton);
    AddSpriteSetVisibleAction(myBeginActionButton,
                                kParentAtomIsContainer,
                                kQTEventMouseEnter, 0, NULL, 0, 0, NULL,
                                true, NULL);
    AddSpriteSetVisibleAction(myBeginActionButton,
                                kParentAtomIsContainer,
                                kQTEventMouseExit, 0, NULL, 0, 0, NULL,
                                false, NULL);
    AddSpriteToSample(mySample, myBeginActionButton,
                        kGoToBeginningSpriteID);
    QTDisposeAtomContainer(myBeginActionButton);
 
    // add go to prev button
    myErr = QTCopyAtom(myPrevButton, kParentAtomIsContainer,
                        &myPrevActionButton);
    .
    .
    .
 
    AddSpriteSetImageIndexAction(myPrevActionButton,
                                kParentAtomIsContainer,
                                kQTEventMouseClick, 0, NULL, 0, 0, NULL,
                                kGoToPrevButtonDownIndex, NULL);
    AddSpriteSetImageIndexAction(myPrevActionButton,
                                kParentAtomIsContainer,
                                kQTEventMouseClickEnd, 0, NULL, 0, 0,
                                NULL, kGoToPrevButtonUpIndex, NULL);
    AddMovieStepBackwardAction(myPrevActionButton,
                                kParentAtomIsContainer,
                                kQTEventMouseClickEndTriggerButton);
    AddSpriteSetVisibleAction(myBeginActionButton,
                                kParentAtomIsContainer,
                                kQTEventMouseEnter, 0, NULL, 0, 0, NULL,
                                true, NULL);
    AddSpriteSetVisibleAction(myBeginActionButton,
                                kParentAtomIsContainer,
                                kQTEventMouseExit, 0, NULL, 0, 0, NULL,
                                false, NULL);
    AddSpriteToSample(mySample, myPrevActionButton, kGoToPrevSpriteID);
 
    QTDisposeAtomContainer(myPrevActionButton);
 
    // add go to next button
    myErr = QTCopyAtom(myNextButton, kParentAtomIsContainer,
                        &myNextActionButton);
    .
    .
    .

For each new property value that is passed into it as a parameter, the SetSpriteData function (Listing 4-6) calls QTFindChildByIndex to find the appropriate property atom. If the property atom already exists in the QT atom container, SetSpriteData calls QTSetAtomData to update the property’s value. If the property atom does not exist in the container, SetSpriteData calls QTInsertChild to insert a new property atom.

Listing 4-6  The SetSpriteData function

OSErr SetSpriteData (QTAtomContainer sprite, Point *location,
    short *visible, short *layer, short *imageIndex)
{
    OSErr   err = noErr;
    QTAtom  propertyAtom;
 
    if (location) {
        MatrixRecordmatrix;
 
        // set up the value for the matrix property
        SetIdentityMatrix (&matrix);
        matrix.matrix[2][0] = ((long)location->h << 16);
        matrix.matrix[2][1] = ((long)location->v << 16);
 
        // if no matrix atom is in the container, insert a new one
        if ((propertyAtom = QTFindChildByIndex (sprite, 0,
            kSpritePropertyMatrix, 1, nil)) == 0)
            FailOSErr (QTInsertChild (sprite, 0, kSpritePropertyMatrix,
                1, 0, sizeof(MatrixRecord), &matrix, nil))
        // otherwise, replace the atom’s data else
            FailOSErr (QTSetAtomData (sprite, propertyAtom,
                sizeof(MatrixRecord), &matrix));
    }
 
    // ...
    // handle other properties in a similar fashion
    // ...
 
    return err;
}

The AddSpriteToSample function (Listing 4-7) checks to see whether a sprite has already been added to a sample. If not, the function calls QTInsertChild to create a new sprite atom in the atom container that represents the sample. Then, AddSpriteToSample calls QTInsertChildren to insert the atoms in the sprite atom container as children of the newly created atom in the sample container.

Listing 4-7  The AddSpriteToSample function

OSErr AddSpriteToSample (QTAtomContainer theSample,
    QTAtomContainer theSprite, short spriteID)
{
    OSErr err = noErr;
    QTAtom newSpriteAtom;
 
    FailIf (QTFindChildByID (theSample, 0, kSpriteAtomType, spriteID,
                                nil), paramErr);
 
    FailOSErr (QTInsertChild (theSample, 0, kSpriteAtomType, spriteID,
                                0, 0, nil, &newSpriteAtom)); // index of
                                                    // zero means append
    FailOSErr (QTInsertChildren (theSample, newSpriteAtom, theSprite));
 
    .
    .
    .
}

The AddSpriteSampleToMedia function, shown in Listing 4-8, calls AddMediaSample to add either a key frame sample or an override sample to the sprite media.

Listing 4-8  The AddSpriteSampleToMedia function

OSErr AddSpriteSampleToMedia (Media theMedia, QTAtomContainer sample,
    TimeValue duration, Boolean isKeyFrame)
{
    OSErr err = noErr;
    SampleDescriptionHandle sampleDesc = nil;
 
    FailMemErr (sampleDesc = (SampleDescriptionHandle) NewHandleClear(
                                sizeof(SampleDescription)));
 
    FailOSErr (AddMediaSample (theMedia, (Handle) sample, 0,
                                GetHandleSize(sample), duration,
                                sampleDesc, 1,
                                isKeyFrame ? 0 : mediaSampleNotSync,
                                nil));
 
bail:
    if (sampleDesc)
        DisposeHandle ((Handle)sampleDesc);
 
    return err;
}

Adding More Actions to Other Sprites

To set the movie’s looping mode to palindrome, you add an action that is triggered when the key frame is loaded, as shown in Listing 4-9. This action is triggered every time the key frame is reloaded.

Listing 4-9  Adding more actions to other sprites

        loopingFlags = loopTimeBase | palindromeLoopTimeBase;
        FailOSErr( AddMovieSetLoopingFlagsAction( sample,
                    kParentAtomIsContainer,
                    kQTEventFrameLoaded, loopingFlags ) )

Adding Sample Data in Compressed Form

To add the sample data in a compressed form, you use a QuickTime DataCodec to perform the compression, as shown in Listing 4-10. You replace the sample utility AddSpriteSampleToMedia call with a call to the sample utility AddCompressedSpriteSampleToMedia.

Listing 4-10  Adding the key frame sample in compressed form

/* AddSpriteSampleToMedia(myMedia, mySample, kSpriteMediaFrameDuration,
            true, NULL);    */
AddCompressedSpriteSampleToMedia(myMedia, mySample,
            kSpriteMediaFrameDuration, true, zlibDataCompressorSubType,
            NULL);

Defining Override Samples

Once you have defined a key frame sample for the sprite track, you can add any number of override samples to modify sprite properties.

Listing 4-11 shows the portion of the AddSpriteTrackToMovie function that adds override samples to the sprite track to make the first penguin sprite appear to waddle and move across the screen. For each override sample, the function modifies the first penguin sprite’s image index and location. The function calls SetSpriteData to update the appropriate property atoms in the sprite atom container. Then, the function calls AddSpriteToSample to add the sprite atom container to the sample atom container. After all of the modifications have been made to the override sample, the function calls AddSpriteSampleToMedia to add the override sample to the media.

After adding all of the override samples to the media, AddSpriteTrackToMovie calls EndMediaEdits to indicate that it is done adding samples to the media. Then, AddSpriteTrackToMovie calls InsertMediaIntoTrack to insert the new media segment into the track.

Listing 4-11  Adding override samples to move penguin one and change its image index

// original penguin one location
    myLocation.h    = (3 * kSpriteTrackWidth / 8) - (kPenguinWidth / 2);
    myLocation.v    = (kSpriteTrackHeight / 4) - (kPenguinHeight / 2);
 
    myDelta = (kSpriteTrackHeight / 2) / kNumOverrideSamples;
    myIndex = kPenguinDownRightCycleStartIndex;
 
    for (i = 1; i <= kNumOverrideSamples; i++) {
        QTRemoveChildren(mySample, kParentAtomIsContainer);
        QTNewAtomContainer(&myPenguinOneOverride);
 
        myLocation.h += myDelta;
        myLocation.v += myDelta;
        myIndex++;
        if (myIndex > kPenguinDownRightCycleEndIndex)
            myIndex = kPenguinDownRightCycleStartIndex;
 
        SetSpriteData(myPenguinOneOverride, &myLocation, NULL, NULL,
                        &myIndex, NULL, NULL, NULL);
        AddSpriteToSample(mySample, myPenguinOneOverride,
                            kPenguinOneSpriteID);
        AddSpriteSampleToMedia(myMedia, mySample,
                                kSpriteMediaFrameDuration, false, NULL);
        QTDisposeAtomContainer(myPenguinOneOverride);
    }
 
    EndMediaEdits(myMedia);
 
    // add the media to the track
    InsertMediaIntoTrack(myTrack, 0, 0, GetMediaDuration(myMedia),
                            fixed1);

Setting Properties of the Sprite Track

Besides adding key frame samples and override samples to the sprite track, you may want to set one or more global properties of the sprite track. For example, if you want to define a background color for your sprite track, you must set the sprite track’s background color property. You do this by creating a leaf atom of type kSpriteTrackPropertyBackgroundColor whose data is the desired background color.

After adding the override samples, AddSpriteTrackToMovie adds a background color to the sprite track, as shown in Listing 4-12. The function calls QTNewAtomContainer to create a new atom container for sprite track properties. AddSpriteTrackToMovie adds a new atom of type kSpriteTrackPropertyBackgroundColor to the container and calls SpriteMediaSetSpriteProperty to set the sprite track’s property.

After adding a background color, AddSpriteTrackToMovie notifies the movie controller that the sprite track has actions. If the hasActions parameter is true, this function calls QTNewAtomContainer to create a new atom container for sprite track properties. AddSpriteTrackToMovie adds a new atom of type kSpriteTrackPropertyHasActions to the container and calls SpriteMediaSetSpriteProperty to set the sprite track’s property.

Finally, after specifying that the sprite track has actions, AddSpriteTrackToMovie notifies the sprite track to generate QTIdleEvents by adding a new atom of type kSpriteTrackPropertyQTIdleEventsFrequency to the container. This new atom specifies the frequency of QTEvent occurrences.

Listing 4-12  Adding sprite track properties, including a background color, actions, and frequency

{
        QTAtomContainer     myTrackProperties;
        RGBColor            myBackgroundColor;
 
        // add a background color to the sprite track
        myBackgroundColor.red = EndianU16_NtoB(0x8000);
        myBackgroundColor.green = EndianU16_NtoB(0);
        myBackgroundColor.blue = EndianU16_NtoB(0xffff);
 
        QTNewAtomContainer(&myTrackProperties);
        QTInsertChild(myTrackProperties, 0,
                        kSpriteTrackPropertyBackgroundColor, 1, 1,
                        sizeof(RGBColor), &myBackgroundColor, NULL);
 
        // tell the movie controller that this sprite track has actions
        hasActions = true;
        QTInsertChild(myTrackProperties, 0,
                        kSpriteTrackPropertyHasActions, 1, 1,
                        sizeof(hasActions), &hasActions, NULL);
 
        // tell the sprite track to generate QTIdleEvents
        myFrequency = EndianU32_NtoB(60);
        QTInsertChild(myTrackProperties, 0,
                    kSpriteTrackPropertyQTIdleEventsFrequency, 1, 1,
                        sizeof(myFrequency), &myFrequency, NULL);
        myErr = SetMediaPropertyAtom(myMedia, myTrackProperties);
        if (myErr != noErr)
            goto bail;
 
        QTDisposeAtomContainer(myTrackProperties);
    }

Retrieving Sprite Data From a Modifier Track

The sample program AddReferenceTrack.c illustrates how you can modify a movie to use a modifier track for a sprite’s image data. The sample program prompts the user for a movie that contains a single sprite track. Then, it adds a track from a second movie to the original movie as a modifier track. The modifier track overrides the image data for a selected image index.

Listing 4-13 shows the first part of the main function of the sample program. It performs the following tasks:

Once the two movies have been loaded, the sample program retrieves the first track, which is the sprite track, from the original movie, and sets the selection to the start of the movie (Listing 4-14). The sample program iterates through all the tracks in the modifier movie, disposing of all non-video tracks.

Next, the sample program calls AddMovieSelection to add the modifier track to the original movie. Finally, the sample program calls AddTrackReference to associate the modifier track with the sprite track it will modify. AddTrackReference returns an index of the added reference in the referenceIndex variable.

Listing 4-14  Adding the modifier track to the movie

Movie           m;
TimeValue       oldDuration;
Movie           movieB;
long            i, origTrackCount, referenceIndex;
Track           newTrack, spriteTrack;
 
// get the first track in original movie and position at the start
spriteTrack = GetMovieIndTrack (m, 1);
SetMovieSelection (m, 0 ,0);
 
// remove all tracks except video in modifier movie
for (i = 1; i <= GetMovieTrackCount (movieB); i++)
{
    Track t = GetMovieIndTrack (movieB, i);
    OSType aType;
 
    GetMediaHandlerDescription (GetTrackMedia(t), &aType, nil, nil);
    if (aType != VideoMediaType)
    {
        DisposeMovieTrack (t);
        i--;
    }
}
 
// add the modifier track to original movie
oldDuration = GetMovieDuration (m);
AddMovieSelection (m, movieB);
DisposeMovie (movieB);
 
// truncate the movie to the length of the original track
DeleteMovieSegment (m, oldDuration,
    GetMovieDuration (m) - oldDuration);
 
// associate the modifier track with the original sprite track
newTrack = GetMovieIndTrack (m, origTrackCount + 1);
AddTrackReference (spriteTrack, newTrack, kTrackModifierReference,
    &referenceIndex);

Besides adding a reference to the modifier track, the sample program must update the sprite media’s input map to describe how the modifier track should be interpreted by the sprite track. The sample program performs the following tasks (Listing 4-15):

Once the media’s input map has been updated, the application can save the movie.

Authoring Wired Movies

In addition to providing functions that allow you to create and manipulate a sprite animation as a track in a QuickTime movie, the sprite media handler also provides functions that allow your application to create and manipulate a wired sprite movie, with various types of user interactivity.

The following sections are illustrated, as with the previous section, with code from the sample program QTWiredSprites.c, which shows how to create a sample wired sprite movie containing one sprite track. (A partial code listing is available in Appendix A. You can download the full sample code at QuickTime Web site at http://www.apple.com/quicktime/developers/samplecode.html#sprites.)

The sample code creates a 320 by 240 pixel wired movie with one sprite track that contains six sprites, two of which are penguins and four of which are buttons. Figure 4-1 shows two penguins at the outset of the movie, with the buttons invisible.


Figure 4-1  Two penguins from a sample program

Two penguins from a sample program

Initially, the four buttons in the wired movie are invisible. When the mouse enters or “rolls over” a button, it appears, as shown in Figure 4-2.


Figure 4-2  Two penguins and four buttons, indicating various directions in the movie

Two penguins and four buttons, indicating various directions in the movie

When the mouse is clicked inside a button, its images change to its “pressed” image. When the mouse is released, its image is changed back to its “unpressed” image. If the mouse is released inside the button, it triggers an action. The buttons perform the following set of actions:

Actions of the First Penguin

The first penguin shows all of the buttons when the mouse enters it, and hides them when the mouse exits. The first penguin is the only sprite which has properties that are overriden by the override sprite samples. These samples override its matrix in order to move it, and its image index in order to make it waddle.

When you mouse-click on the second penguin, the penguin changes its image index to its “eyes closed” image. When the mouse is released, it changes back to its normal image. This makes the penguin’s eyes appear to blink when clicked on. When the mouse is released over the penguin, several other actions are triggered. Both penguins’ graphics states are toggled between copyMode and blendMode, and the movie’s rate is toggled between 0 and 1.

Actions of the Second Penguin

The second penguin moves once per second. This occurs whether the movie’s rate is currently 0 or 1 because it is being triggered by a QuickTime idle event. When the penguin receives the idle event, it changes its matrix using an action which uses min, max, delta, and wraparound options.

The movie’s looping mode is set to palindrome by a kQTEventFrameLoaded event.

Creating a Wired Sprite Movie

The following tasks are performed in order to create the wired sprite movie:

Assigning the No Controller to the Movie

The following code fragment (in Listing 4-16) assigns the “no controller” movie controller to the movie. You make this assignment if you don’t want the standard QuickTime movie controller to appear. In this code sample, you want to create a set of sprite buttons in order to control user interaction with the penguins in the movie.

There may also be other occasions when it is useful to make a “no controller” assignment. For example, if you are creating non-linear movies—such as Hypercard stacks where you access the cards in the stack by clicking on buttons by sprites—you may wish to create your own sprite buttons.

Listing 4-16  Assigning the no controller movie controller

    // select the "no controller" movie controller
    myType = EndianU32_NtoB(myType);
    SetUserDataItem(GetMovieUserData(myMovie), &myType, sizeof(myType),
                        kUserDataMovieControllerType, 1);

Setting Up the Sprite Track’s Properities

Listing 4-17 shows a code fragment that sets the sprite track’s background color, idle event frequency and its hasActions properties.

Listing 4-17  Setting the background color, idle event frequency and hasActions properties of the sprite track

// set the sprite track properties
    {
        QTAtomContainer     myTrackProperties;
        RGBColor            myBackgroundColor;
 
        // add a background color to the sprite track
        myBackgroundColor.red = EndianU16_NtoB(0x8000);
        myBackgroundColor.green = EndianU16_NtoB(0);
        myBackgroundColor.blue = EndianU16_NtoB(0xffff);
 
        QTNewAtomContainer(&myTrackProperties);
        QTInsertChild(myTrackProperties, 0,
                        kSpriteTrackPropertyBackgroundColor, 1, 1,
                        sizeof(RGBColor), &myBackgroundColor, NULL);
 
// tell the movie controller that this sprite track has actions
        hasActions = true;
        QTInsertChild(myTrackProperties, 0,
                        kSpriteTrackPropertyHasActions, 1, 1,
                    sizeof(hasActions), &hasActions, NULL);
 
        // tell the sprite track to generate QTIdleEvents
        myFrequency = EndianU32_NtoB(60);
        QTInsertChild(myTrackProperties, 0,
                        kSpriteTrackPropertyQTIdleEventsFrequency, 1, 1,
                            sizeof(myFrequency), &myFrequency, NULL);
        myErr = SetMediaPropertyAtom(myMedia, myTrackProperties);
        if (myErr != noErr)
            goto bail;
 
        QTDisposeAtomContainer(myTrackProperties);
    }

Adding an Event Handler to the Penguin

The AddPenguinTwoConditionalActions routine adds logic to our penguin. Using this routine, you can transform the penguin into a two-state button that plays/pauses the movie.

We are relying on the fact that a GetVariable for a variableID which has never been set will return 0. If we need another default value, we could initialize it using the frameLoaded event.

A higher level description of the logic is:

    On MouseUpInside
       If (GetVariable(DefaultTrack, 1) = 0)
          SetMovieRate(1)
          SetSpriteGraphicsMode(DefaultSprite, { blend, grey } )
          SetSpriteGraphicsMode(GetSpriteByID(DefaultTrack, 5), {
                                ditherCopy, white } )
          SetVariable(DefaultTrack, 1, 1)
       ElseIf (GetVariable(DefaultTrack, 1) = 1)
          SetMovieRate(0)
          SetSpriteGraphicsMode(DefaultSprite, { ditherCopy, white })
          SetSpriteGraphicsMode(GetSpriteByID(DefaultTrack, 5), { blend,
                                                    grey })
          SetVariable(DefaultTrack, 1, 0)
       Endif
    End

Adding a Series of Actions to the Penguins

The following code fragment in Listing 4-18 shows how you can add a key frame with four buttons, enabling our penguins to move through a series of actions.

Listing 4-18  Adding a key frame with four buttons, enabling a series of actions for our two penguins

// add actions to the six sprites
    // add go to beginning button
    myErr = QTCopyAtom(myBeginButton, kParentAtomIsContainer,
                        &myBeginActionButton);
    if (myErr != noErr)
        goto bail;
 
    AddSpriteSetImageIndexAction(myBeginActionButton,
                    kParentAtomIsContainer, kQTEventMouseClick, 0, NULL,
                    0, 0, NULL, kGoToBeginningButtonDownIndex, NULL);
    AddSpriteSetImageIndexAction(myBeginActionButton,
                    kParentAtomIsContainer, kQTEventMouseClickEnd, 0,
                    NULL, 0, 0, NULL, kGoToBeginningButtonUpIndex, NULL);
    AddSpriteToSample(mySample, myPrevActionButton, kGoToPrevSpriteID);
                        NULL);
    AddSpriteToSample(mySample, myNextActionButton, kGoToNextSpriteID);
    QTDisposeAtomContainer(myNextActionButton);
. . .
    // add go to end button
    myErr = QTCopyAtom(myEndButton, kParentAtomIsContainer,
                        &myEndActionButton);
    if (myErr != noErr)
        goto bail;
        kParentAtomIsContainer, kQTEventMouseExit, 0, NULL, 0, 0, NULL,
            false,
. . .
 
    // add penguin one
    myErr = QTCopyAtom(myPenguinOne, kParentAtomIsContainer,
                        &myPenguinOneAction);
    if (myErr != noErr)
    AddSpriteSetVisibleAction(myBeginActionButton, goto bail;
 
    // show the buttons on mouse enter and hide them on mouse exit
    AddSpriteSetVisibleAction(myPenguinOneAction, kParentAtomIsContainer,
                                kQTEventMouseEnter, 0, NULL, 0,
                                kTargetSpriteID, (void
                                *)kGoToBeginningSpriteID, true, NULL);
    AddSpriteSetVisibleAction(myPenguinOneAction, kParentAtomIsContainer,
                                kQTEventMouseExit, 0, NULL, 0,
                                kTargetSpriteID, (void
                                *)kGoToBeginningSpriteID, false, NULL);
. . .
    // add penguin two
    myErr = QTCopyAtom(myPenguinTwo, kParentAtomIsContainer,
                        &myPenguinTwoAction);
    if (myErr != noErr)
        goto bail;
 
    // blink when clicked on
    AddSpriteSetImageIndexAction(myPenguinTwoAction,
                                kParentAtomIsContainer,
                                kQTEventMouseClick, 0, NULL, 0, 0, NULL,
                                kPenguinClosedIndex, NULL);
. . .
    // add go to next button
    myErr = QTCopyAtom(myNextButton, kParentAtomIsContainer,
                        &myNextActionButton);
    if (myErr != noErr)
        goto bail;

Important Things to Note in the Sample Code

You should note the following in MakeActionSpriteMovie.c sample code:

Authoring Movies With External Movie Targets

QuickTime enables you to author movies with external movie targets. To accomplish this, two target atoms were introduced in QuickTime 4, as explained in the following section.

Specifying an External Movie Target for an Action

In order to specify that an action is to target an element of an external movie, the external movie must be identified by either its name or its ID. To do this, two new target atom types have been introduced; these atoms are used in addition to the existing target atoms, which may specify that the element is a particular Track or object within a Track such as a Sprite.

Note: A movie ID may be specified by an expression.

The additional target atoms provided in QuickTime 4:

[(ActionTargetAtoms)] =
    <kActionTarget>
 
        <kTargetMovieName>
            [Pstring MovieName]
        OR
        <kTargetMovieID>
            [long MovieID]
            OR
            [(kExpressionAtoms)]

To tag a movie with a name or ID, you add a user data item of type 'plug' to the movie’s user data. The index of the user data does not matter. The data specifies the name or ID.

You add a user data item of type 'plug' to the movie’s user data with its data set to

"Movieid=MovieName"

where MovieName is the name of the movie.

You add a user data item of type'plug' to the movie’s user data with its data set to

"Movieid=MovieID"

where the ID is a signed long integer.

The QuickTime plug-in additionally supports EMBED tag parameters, which allow you to override a movie’s name or ID within an HTML page.

Target Atoms for Embedded Movies

Target atoms accommodate embedded movies. These target atoms allow for paths to be specified in a hierarchical movie tree.

Target movies may be an external movie, the default movie, or any movie embedded within another movie. Targets are specified by using a movie path that may include parent and child movie relationships, and may additionally include track and track object target atoms as needed.

By using embedded kActionTarget atoms along with parent and child movie target atoms, you can build up paths for movie targets. QuickTime looks for these embedded kActionTarget atoms only when evaluating a movie target, and any movie target type may contain a sibling kActionTarget atom.

Paths begin from the current movie, which is the movie containing the object that is handling an event. You may go up the tree using a kTargetParentMovie atom, or down the tree using one of five new child movie atoms. You may use a kTargetRootMovie atom as a shortcut to get to the top of the tree containing an embedded movie and may use the movieByName and movieByID atoms to specify a root external movie.

The target atoms are

There are five ways to specify an embedded child movie. Three of them specify movie track properties. Two specify properties of the currently loaded movie in a movie track.

Supporting External Movie Targets

If you want your application to support external movie targets, you need to resolve the movie names or IDs to actual movie references. This is accomplished by installing a Movie Controller filter proc for each Movie Controller that filters for the mcActionGetExternalMovie action.

The parameter to the mcActionGetExternalMovie Movie Controller action is a pointer to a QTGetExternalMovieRecord.

First, you look at the targetType field. If it is set to kTargetMovieName, then return the movie and Movie Controller for the movie named by the MovieName field. If it is set to kTargetMovieID, then return the movie and Movie Controller for the movie with ID equal to the MovieID field.

If you cannot find a matching movie, then set theMovie and theController to nil.

Specifying String Parameters to Actions and Operands

While QuickTime 3 supported only leaf data constants for string parameters, QuickTime 4 lets you can specify string parameters using leaf data constants, a numeric expression, or a sprite track variable.

Expressions are converted to strings, as are sprite track variable s if they contain a floating-point number instead of a string. Note that although string variables are stored as C strings, they will be automatically converted to Pascal strings as needed.

This applies to the following action and operand parameters:

    C String Parameters
        kActionGoToURL, 1
        kActionStatusString, 1
        kActionAddChannelSubscription, 2
        kActionAddChannelSubscription, 3
        kActionRemoveChannelSubscription, 1
        kOperandSubscribedToChannel, 1
 
    Pascal String Parameters
        kActionMovieGoToTimeByName, 1
        kActionMovieSetSelectionByName, 1
        kActionMovieSetSelectionByName, 2
        kActionPushCurrentTimeWithLabel, 1
        kActionPopAndGotoLabeledTime, 1
        kActionDebugStr, 1
        kActionAddChannelSubscription, 1
 
 
    Extended Grammar:
        [(C String Parameter)] =
                [CString]
            OR
                [(ExpressionAtoms)]
            OR
                [(SpriteTrackVariableOperandAtoms)]
 
        [(Pascal String Parameter)] =
                [Pascal String]
            OR
                [(ExpressionAtoms)]
            OR
                [(SpriteTrackVariableOperandAtoms)]
 
 
        [(SpriteTrackVariableOperandAtoms)] =
            <kOperandSpriteTrackVariable>, 1, 1
                [(ActionTargetAtoms)]
                kActionParameter, 1, 1
                    [(spriteVariableID)]
                    OR
                    [(ExpressionAtoms)]

Wiring a Movie by Adding an HREF Track

QuickTime enables you to wire a movie by adding an HREF track to it. An HREF track is a specially named text track that contains hypertext references (HREFs). These references are in the form of URLs and can point to any kind of data that a URL can specify, such as a Web page, a QuickTime movie, or a JavaScript script.

A URL can be invoked automatically when the movie plays, or it can be invoked interactively when the user clicks the mouse inside a movie. The URL can be targeted to a particular frame, or to the QuickTime movie itself, or it can be untargeted, in which case it will load in the default browser window.

You can only add one HREF track per movie. If you add more than one HREF track to a movie, QuickTime uses the first one it finds and ignore any others.

The syntax for an HREF track sample is:

[nn:nn:nn.n]
A<URL> T<frame>

where [nn:nn:nn.n] is a timestamp in hours:minutes:seconds.timeunits and where timeunits are in the time scale of the track. The URL becomes active at the time specified and remains active until the next timestamp. If there is no following timestamp, the URL remains active. The URL is active even if the movie is not playing.

A<URL> specifies the URL. If the A is omitted, the user must click inside the movie during the period when the URL is active to cause the URL to load. If the A is present, the URL loads automatically when the movie is played.

The URL can be absolute or relative to the movie.

Important: A relative URL is relative to the movie, not the Web page that contains the movie. If the movie and its parent Web page are in the same directory, this is the same thing, but if the movie and the Web page are not in the same directory, the difference is crucial.

The URL can include an internal anchor name as well as a path and filename (for example, “../HTML/Page1.htm#StepOne”).

T<frame> is an optional parameter. If a target frame is specified, the URL is targeted to that frame. If the frame does not exist, it is created.

If no target frame is specified, the URL will load in the default browser frame. If the movie is playing in a Web page, the Web page is replaced by the specified URL. If the movie is playing in an application such as QuickTime Player, the URL loads in the default browser (which will be invoked if it is not already running).

If the target frame “myself” is specified, the URL is opened by QuickTime within the application playing the movie, such as the QuickTime plug-in or QuickTime Player. This works properly only if the URL points to a file that QuickTime can handle directly, such as a QuickTime movie or an AIFF audio file.

Creating an HREF Track

An HREF track can be created using a text editor and imported using a movie import component, or it can be created programmatically.

You create an HREF track programmatically by creating a text track, adding the URLs as text samples, then setting the name of the track to “HREFTrack”. The track name is stored in the user data atom. The following code snippet shows how to change the name of a text track to “HREFTrack”.

Str255  trackNameStr = “HREFTrack”
UserDat ud = GetTrackUserData(theTrack);
OSType  dataType = FOUR_CHAR_CODE('name');
 
RemoveUserData(ud, dataType, 1);    // remove existing name, if any
SetUserDataItem(ud, &trackNameStr[1], trackNameStr[0], dataType, 0);

Because the HREF track is a text track, it will display its samples as on-screen text while the movie plays. This is normally desirable during debugging. To turn the text off, you disable the track by calling the SetTrackEnabled function.

When flattening a movie that contains an HREF track, be careful not to accidentally delete the disabled text track by calling FlattenMovie with the flattenActiveTracksOnly flag.

Adding Hypertext Links to a QuickTime Movie With a Text Track

This section illustrates how you can add a few wired actions to a movie that has a text track. In particular, it adds two go-to-URL actions to parts of the text track.

A text media sample is a 16-bit length word followed by the text of that sample. Optionally, one or more atoms of additional data (called text atom extensions) may follow the text in the sample. The length word specifies the total number of bytes in the text and in any text atom extensions that follow the text. These text atom extensions are organized as “classic” atom structures: a 32-bit length field, followed by a 32-bit type field, followed by the data in the atom. Here, the length field specifies the total length of the atom (that is, 8 plus the number of bytes in the data.) All the data in the text extension atom must be in big-endian format.

To add hypertext actions to a text sample, you simply add a text atom extension of the type kHyperTextTextAtomType to the end of the sample; the data of the text atom extension is the container that holds the information about the actions. The programming tasks are outlined in these steps.

Step #1––Creating a Movie With a Text Track and Hypertext Links

You create a text movie with hypertext links.

    if (myReply.sfGood) {
        myErr = AddHTAct_CreateTextMovie(&myReply.sfFile);
        if (myErr == noErr)
            myErr = AddHTAct_AddHyperTextToTextMovie(&myReply.sfFile);
    }

To create a movie with a text track, you call AddHTAct_CreateTextMovie.

static OSErr AddHTAct_CreateTextMovie (FSSpec *theFSSpec)
{
    short           myResRefNum = -1;
    short           myResID = movieInDataForkResID;
    Movie           myMovie = NULL;
    Track           myTrack = NULL;
    Media           myMedia = NULL;
    MediaHandler    myHandler = NULL;
    Rect            myTextBox;
    RGBColor        myTextColor =   {0x6666, 0xCCCC, 0xCCCC};
    RGBColor        myBackColor =   {0x3333, 0x6666, 0x6666};
    RGBColor        myHiliteColor = {0x0000, 0x0000, 0x0000};
    long            myDisplayFlags = 0;
    short           myHiliteStart = 0;
    short           myHiliteEnd = 0;
    TimeValue       myNewMediaTime;
    TimeValue       myScrollDelay = 0;
#if TARGET_OS_MAC
    char            myText[] = "\rPlease take me to Apple or CNN";
#else
    char            myText[] = "\nPlease take me to Apple or CNN";
#endif
    long            myFlags = createMovieFileDeleteCurFile | createMovieFileDontCreateResFile | newMovieActive;
    OSErr           myErr = noErr;

Step #2––Creating a New Text Movie

Now you create a new text movie.

    myErr = CreateMovieFile(theFSSpec, FOUR_CHAR_CODE(‘TVOD’), 0,
                            myFlags, &myResRefNum, &myMovie);
    .
    .
    .
    myTrack = NewMovieTrack(myMovie, FixRatio(kWidth320, 1),
                            FixRatio(kHeight240, 1), kTrackVolumeZero);
 
    myMedia = NewTrackMedia(myTrack, TextMediaType, kTimeScale600, NULL,
                            0);
    if ((myTrack == NULL) || (myMedia == NULL))
        goto bail;
 
    myErr = BeginMediaEdits(myMedia);
    .
    .
    .
 
    myHandler = GetMediaHandler(myMedia);
    .
    .
    .

Step #3––Adding a Text Sample to the Movie

You add a text sample to the movie, as follows:

    MacSetRect(&myTextBox, 0, 0, kWidth320, kHeight240);
    MacInsetRect(&myTextBox, kTextBoxInset, kTextBoxInset);
 
    myErr = (OSErr)TextMediaAddTextSample(  myHandler,
                                            myText,
                                            strlen(myText),
                                            kFontIDSymbol,
                                            kSize48,
                                            kFacePlain,
                                            &myTextColor,
                                            &myBackColor,
                                            teCenter,
                                            &myTextBox,
                                            myDisplayFlags,
                                            myScrollDelay,
                                            myHiliteStart,
                                            myHiliteEnd,
                                            &myHiliteColor,
                                            kTimeScale600,
                                            &myNewMediaTime);
    if (myErr != noErr)
        goto bail;
 
    myErr = EndMediaEdits(myMedia);
    .
    .
    .
 
    // add the media to the track, at time 0
    myErr = InsertMediaIntoTrack(myTrack, kTrackStartTimeZero,
                                myNewMediaTime, kTimeScale600, fixed1);
    .
    .
    .
 
    // add the movie resource
    myErr = AddMovieResource(myMovie, myResRefNum, &myResID, NULL);
    .
    .
    .
 
bail:
    if (myResRefNum != -1)
        CloseMovieFile(myResRefNum);
 
    if (myMovie != NULL)
        DisposeMovie(myMovie);
 
    return(myErr);
}

You call AddHTAct_CreateHyperTextActionContainer, which returns, through the theActions parameter, an atom container that contains some hypertext actions.

static OSErr AddHTAct_CreateHyperTextActionContainer (QTAtomContainer *theActions)
{
    QTAtom          myEventAtom = 0;
    QTAtom          myActionAtom = 0;
    QTAtom          myHyperTextAtom = 0;
    QTAtom          myWiredObjectsAtom = 0;
    long            myAction;
    long            mySelStart1 = 19;
    long            mySelEnd1 = 24;
    long            mySelStart2 = 28;
    long            mySelEnd2 = 31;
    long            myValue;
    char            myAppleURL[64] = "\pwww.apple.com\0";
    char            myCnnURL[64] = "\pwww.cnn.com\0";
    OSErr           myErr = noErr;
 
    myErr = QTNewAtomContainer(theActions);
    .
    .
    .
    // create a wired objects atom
    myErr = QTInsertChild(*theActions, kParentAtomIsContainer,
                            kTextWiredObjectsAtomType, kIndexOne,
                            kIndexZero, kZeroDataLength, NULL,
                            &myWiredObjectsAtom);
 

Step #4––Adding a Hypertext Link

Now you add a hypertext link to the wired objects atom: ID 1

    myErr = QTInsertChild(*theActions, myWiredObjectsAtom, kHyperTextItemAtomType, kIDOne, kIndexZero, kZeroDataLength, NULL, &myHyperTextAtom);
    .
    .
    .
 
    myValue = EndianS32_NtoB(mySelStart1);
    myErr = QTInsertChild(*theActions, myHyperTextAtom, kRangeStart,
                            kIDOne, kIndexZero, sizeof(long), &myValue,
                            NULL);
    .
    .
    .
    myValue = EndianS32_NtoB(mySelEnd1);
    myErr = QTInsertChild(*theActions, myHyperTextAtom, kRangeEnd,
                            kIDOne, kIndexZero, sizeof(long), &myValue,
                            NULL);
    .
    .
    .
 
    // add an event atom to the hypertext atom;
    // the event type can be any of the five mouse events: down, up,
    // trigger, enter, exit
    myErr = QTInsertChild(*theActions, myHyperTextAtom, kQTEventType,
                            kQTEventMouseClick, kIndexZero,
                            kZeroDataLength, NULL, &myEventAtom);
    .
    .
    .
    myErr = QTInsertChild(*theActions, myEventAtom, kAction, kIndexOne,
                        kIndexOne, kZeroDataLength, NULL, &myActionAtom);
    .
    .
    .
    myAction = EndianS32_NtoB(kActionGoToURL);
    myErr = QTInsertChild(*theActions, myActionAtom, kWhichAction,
                            kIndexOne, kIndexOne, sizeof(long),
                            &myAction, NULL);
    .
    .
    .
    myErr = QTInsertChild(*theActions, myActionAtom, kActionParameter,
                            kIndexOne, kIndexOne, myAppleURL[0] + 1,
                            &myAppleURL[1], NULL);

Now we add a hypertext link to the wired objects atom: ID 2

    myErr = QTInsertChild(*theActions, myWiredObjectsAtom,
                            kHyperTextItemAtomType, kIDTwo, kIndexZero,
                            kZeroDataLength, NULL, &myHyperTextAtom);
    .
    .
    .
 
    myValue = EndianS32_NtoB(mySelStart2);
    myErr = QTInsertChild(*theActions, myHyperTextAtom, kRangeStart,
                            kIDOne, kIndexZero, sizeof(long), &myValue,
                            NULL);
    .
    .
    .
 
    myValue = EndianS32_NtoB(mySelEnd2);
    myErr = QTInsertChild(*theActions, myHyperTextAtom, kRangeEnd,
                            kIDOne, kIndexZero, sizeof(long), &myValue,
                            NULL);
    .
    .
    .
 
    // add an event atom to the hypertext atom;
    // the event type can be any of the five mouse events: down, up,
    // trigger, enter, exit
    myErr = QTInsertChild(*theActions, myHyperTextAtom, kQTEventType,
                            kQTEventMouseClick, kIndexZero,
                            kZeroDataLength, NULL, &myEventAtom);
    .
    .
    .
    myErr = QTInsertChild(*theActions, myEventAtom, kAction, kIndexOne,
                            kIndexOne, kZeroDataLength, NULL,
                            &myActionAtom);
    .
    .
    .
    myAction = EndianS32_NtoB(kActionGoToURL);
    myErr = QTInsertChild(*theActions, myActionAtom, kWhichAction,
                            kIndexOne, kIndexOne, sizeof(long),
                            &myAction, NULL);
    .
    .
    .
    myErr = QTInsertChild(*theActions, myActionAtom, kActionParameter,
                            kIndexOne, kIndexOne, myCnnURL[0] + 1,
                            &myCnnURL[1], NULL);
    .
    .
    .
}

Step #5––Adding a Specified Atom Container

You call AddHTAct_AddHyperActionsToSample to add the specified atom container to the end of the specified media sample.

Hypertext actions are stored at the end of the sample as a normal text atom extension. In this case, the text atom type is kHyperTextTextAtomType and the data is the container data.

static OSErr AddHTAct_AddHyperActionsToSample (Handle theSample, QTAtomContainer theActions)
{
    Ptr         myPtr = NULL;
    long        myHandleLength;
    long        myContainerLength;
    long        myNewLength;
    OSErr       myErr = noErr;
 
    myHandleLength = GetHandleSize(theSample);
    myContainerLength = GetHandleSize((Handle)theActions);
 
    myNewLength = (long)(sizeof(long) + sizeof(OSType) +
                    myContainerLength);
 
    SetHandleSize(theSample, (myHandleLength + myNewLength));
    myErr = MemError();
    if (myErr != noErr)
        goto bail;
 
    HLock(theSample);
 
    // get a pointer to the beginning of the new block of space added to
    // the sample by the previous call to SetHandleSize; we need to
    // format that space as a text atom extension
    myPtr = *theSample + myHandleLength;
 
    // set the length of the text atom extension
    *(long *)myPtr = EndianS32_NtoB((long)(sizeof(long) + sizeof(OSType)
                                    + myContainerLength));
    myPtr += (sizeof(long));
 
    // set the type of the text atom extension
    *(OSType *)myPtr = EndianS32_NtoB(kHyperTextTextAtomType);
    myPtr += (sizeof(OSType));
 
    // set the data of the text atom extension;
    // we assume that this data is already in big-endian format
    HLock((Handle)theActions);
    BlockMove(*theActions, myPtr, myContainerLength);
 
    HUnlock((Handle)theActions);
    HUnlock(theSample);
    .
    .
    .
}

To add some hypertext actions to the specified text movie, you call AddHTAct_AddHyperTextToTextMovie.

static OSErr AddHTAct_AddHyperTextToTextMovie (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;
    TextDescriptionHandle           myTextDesc = NULL;
    Handle                          mySample = NULL;
    short                           mySampleFlags;
    Fixed                           myTrackEditRate;
    QTAtomContainer                 myActions = NULL;
    OSErr                           myErr = noErr;

At this point you open the movie file and get the first text track from the movie and then find the first text track in 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;
 
 
    myTrack = GetMovieIndTrackType(myMovie, kIndexOne, TextMediaType,
                                    movieTrackMediaType);
    if (myTrack == NULL)
        goto bail;

Step #6––Getting the First Media Sample

Now you get the first media sample in the text track.

    myMedia = GetTrackMedia(myTrack);
    .
    .
    .
 
    myTrackOffset = GetTrackOffset(myTrack);
    myMediaTime = TrackTimeToMediaTime(myTrackOffset, myTrack);
 
    // allocate some storage to hold the sample description for the text
    // track
    myTextDesc = (TextDescriptionHandle)NewHandle(4);
    if (myTextDesc == NULL)
        goto bail;
 
    mySample = NewHandle(0);
    if (mySample == NULL)
        goto bail;
 
    myErr = GetMediaSample(myMedia, mySample, 0, NULL, myMediaTime, NULL,
                            &mySampleDuration,
                            (SampleDescriptionHandle)myTextDesc, NULL, 1,
                            NULL, &mySampleFlags);
    if (myErr != noErr)
        goto bail;

Step #7––Adding Hypertext Actions

Now you add hypertext actions to the first media sample.

    // create an action container for hypertext actions
    myErr = AddHTAct_CreateHyperTextActionContainer(&myActions);
    .
    .
    .
 
    // add hypertext actions actions to sample
    myErr = AddHTAct_AddHyperActionsToSample(mySample, myActions);
    .
    .
    .

Step #8––Replacing the Sample

Now you replace sample in the media.

    myTrackEditRate = GetTrackEditRate(myTrack, myTrackOffset);
    .
    .
    .
 
    GetTrackNextInterestingTime(myTrack, nextTimeMediaSample |
                                nextTimeEdgeOK, myTrackOffset, fixed1,
                                NULL, &mySelectionDuration);
    .
    .
    .
 
    myErr = DeleteTrackSegment(myTrack, myTrackOffset,
                                mySelectionDuration);
    .
    .
    .
    myErr = BeginMediaEdits(myMedia);
    .
    .
    .
 
    myErr = AddMediaSample( myMedia,
                            mySample,
                            0,
                            GetHandleSize(mySample),
                            mySampleDuration,
                            (SampleDescriptionHandle)myTextDesc,
                            1,
                            mySampleFlags,
                            &myNewMediaTime);
    .
    .
    .
 
    myErr = EndMediaEdits(myMedia);
    .
    .
    .
    // add the media to the track
    myErr = InsertMediaIntoTrack(myTrack, myTrackOffset, myNewMediaTime,
                                    mySelectionDuration,myTrackEditRate);
    .
    .
    .

Step #9––Updating the Movie Resource

Finally, you update the movie resource.

    myErr = UpdateMovieResource(myMovie, myResRefNum, myResID, NULL);
    .
    .
    .
 
    // close the movie file
    myErr = CloseMovieFile(myResRefNum);
 
bail:
    if (myActions != NULL)
        (void)QTDisposeAtomContainer(myActions);
 
    if (mySample != NULL)
        DisposeHandle(mySample);
 
    if (myTextDesc != NULL)
        DisposeHandle((Handle)myTextDesc);
 
    if (myMovie != NULL)
        DisposeMovie(myMovie);
 
    return(myErr);
}



< Previous PageNext Page > Hide TOC


© 2003, 2002 Apple Computer, Inc. All Rights Reserved. (Last updated: 2002-10-01)


Did this document help you?
Yes: Tell us what works for you.
It’s good, but: Report typos, inaccuracies, and so forth.
It wasn’t helpful: Tell us what would have helped.