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:
“Authoring Movies With the Sprite Media Handler” describes how you use the functions provided by the sprite media handler to create and manipulate a sprite animation as a track in a QuickTime movie.
“Authoring Wired Movies” discusses how you use the sprite media handler to create and manipulate a wired sprite movie, with various types of user interactivity.
“Authoring Movies With External Movie Targets” describes how QuickTime enables you to author movies with external movie targets, using two target atoms that were introduced in QuickTime 4.
“Wiring a Movie by Adding an HREF Track” describes how you can wire a QuickTime movie by adding an HREF track to it. An HREF track is a specially named text track that contains hypertext references (HREFs), which 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.
“Adding Hypertext Links to a QuickTime Movie With a Text Track” explains how you can add a few wired actions to a movie that has a text track simply by adding a text atom extension of the type kHyperTextTextAtomType
to the end of the sample. The programming task is outlined in a series of steps.
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 |
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
.
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.
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; |
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); |
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; |
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; |
} |
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 ) ) |
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); |
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); |
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); |
} |
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:
It loads the movie containing the sprite track.
It calls GetMovieTrackCount
to determine the total number of tracks in the sprite track movie.
It loads the movie containing the modifier track (movieB
).
Listing 4-13 Loading the movies
OSErr err; |
short movieResID = 0, resFref, resID = 0, resRefNum; |
StandardFileReply reply; |
SFTypeList types; |
Movie m; |
FSSpec fss; |
Movie movieB; |
long origTrackCount; |
// prompt for a movie containing a sprite track and load it |
types[0] = MovieFileType; |
StandardGetFilePreview (nil, 1, types, &reply); |
if (!reply.sfGood) return; |
err = OpenMovieFile (&reply.sfFile, &resFref, fsRdPerm); |
if (err) return; |
err = NewMovieFromFile (&m, resFref, &movieResID, (StringPtr)nil, |
newMovieActive, ni); |
if (err) return; |
CloseMovieFile (resFref); |
// get the number of tracks |
origTrackCount = GetMovieTrackCount (m); |
// load the movie to be used as a modifier track |
FSMakeFSSpec (reply.sfFile.vRefNum, reply.sfFile.parID, "\pAdd Me", |
&fss); |
err = OpenMovieFile (&fss, &resFref, fsRdPerm); |
if (err) return; |
err = NewMovieFromFile (&movieB, resFref, &resID, (StringPtr)nil, 0, |
nil); |
if (err) return; |
CloseMovieFile (resFref); |
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):
It retrieves the sprite track’s media by calling GetTrackMedia.
It calls GetMediaInputMap
to retrieve the media’s input map.
It adds a parent atom to the input map of type kTrackModifierInput
. The ID of the atom is the reference index retrieved by the AddTrackReference
function.
It adds two child atoms, one that specifies that the input type of the modifier track is of type kTrackModifierTypeImage, and one that specifies the index of the sprite image to override.
It calls SetMediaInputMap
to update the media’s input map.
Listing 4-15 Updating the media’s input map
#define kImageIndexToOverride 1 |
Movie m, movieB; |
long referenceIndex, imageIndexToOverride; |
Track spriteTrack; |
QTAtomContainer inputMap; |
QTAtom inputAtom; |
OSType inputType; |
Media spriteMedia; |
// get the sprite media’s input map |
spriteMedia = GetTrackMedia (spriteTrack); |
GetMediaInputMap (spriteMedia, &inputMap); |
// add an atom for a modifier track |
QTInsertChild (inputMap, kParentAtomIsContainer, |
kTrackModifierInput, referenceIndex, 0, 0, nil, &inputAtom); |
// add a child atom to specify the input type |
inputType = kTrackModifierTypeImage; |
QTInsertChild (inputMap, inputAtom, kTrackModifierType, 1, 0, |
sizeof(inputType), &inputType, nil); |
// add a second child atom to specify index of image to override |
imageIndexToOverride = kImageIndexToOverride; |
QTInsertChild (inputMap, inputAtom, kSpritePropertyImageIndex, 1, 0, |
sizeof(imageIndexToOverride), &imageIndexToOverride, nil); |
// update the sprite media’s input map |
SetMediaInputMap (spriteMedia, inputMap); |
QTDisposeAtomContainer (inputMap); |
Once the media’s input map has been updated, the application can save the movie.
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.
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.
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:
go to beginning of movie
step backwards
step forwards
go to end of movie
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.
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.
The following tasks are performed in order to create the wired sprite movie:
You create a new movie file with a single sprite track, as explained in the section “Creating the Movie, Sprite Track, and Media.”
You assign the “no controller” movie controller to the movie.
You set the sprite track’s background color, idle event frequency, and hasActions
properties.
You convert PICT resources to animation codec images with transparency.
A key frame sample containing six sprites and all of their shared images is created. The sprites are assigned initial property values. A frameLoaded
event is created for the key frame.
You create some override samples which override the matrix and image index properties of the first penguin sprite.
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); |
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); |
} |
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 |
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; |
You should note the following in MakeActionSpriteMovie.c
sample code:
There are event types other than mouse-related events (for example, Idle
and frameLoaded
).
Idle events are independent of the movie’s rate, and can be gated, so they are sent at most every n
ticks. (The second penguin moves when the movie’s rate is 0, and moves only once per second because of the value of the sprite tracks idleEventFrequency
property.)
Multiple actions may be executed in response to a single event (for example, rolling over the first penguin shows and hides four different buttons).
Actions may target any sprite or track in the movie (for example, clicking on one penguin changes the graphics mode of the other).
Conditional and looping control structures are supported. (The second penguin uses the “case statement” action.)
Sprite track variables that have not been set have a default value of 0. (The second penguin’s conditional code relies on this.)
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.
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.
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 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.
kTargetRootMovie
(leaf atom, no data). This is the root movie containing the action handler.
kTargetParentMovie
(leaf atom, no data). This is the parent movie.
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.
kTargetChildMovieTrackName.
A child movie track specified by track name.
kTargetChildMovieTrackID
. A child movie track specified by track ID.
kTargetChildMovieTrackIndex
. A child movie track specified by track index.
kTargetChildMovieMovieName
. A child movie specified by the currently loaded movie’s movie name. The child movie must contain movieName
user data with the specified name.
kTargetChildMovieMovieID
. A child movie specified by the currently loaded movie’s movie ID. The child movie must contain movieID
user data with the specified ID.
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
.
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)] |
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.
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.
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.
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; |
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); |
. |
. |
. |
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); |
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); |
. |
. |
. |
} |
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; |
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; |
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); |
. |
. |
. |
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); |
. |
. |
. |
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); |
} |
© 2003, 2002 Apple Computer, Inc. All Rights Reserved. (Last updated: 2002-10-01)