Retired Document
Important: Apple recommends that developers explore QTKit and Core Video for new development in this technology area. See QTKit Framework Reference and Core Video Programming Guide for more information.
Wired Actions and QuickTime VR Movies
This appendix explains in step-by-step detail how you can add wired actions to a QuickTime VR movie. The programming tasks involved in adding those wired actions are outlined here.
The complete sample code is available at
http://developer.apple.com/samplecode/Sample_Code/QuickTime.htm
Adding Wired Actions to a QuickTime VR Movie
There are two kinds of wired actions that you can add to QuickTime VR movies:
actions that are global to a particular node––for example, a node-specific action might be setting the pan and tilt angles that are used when the user first enters the node.
actions associated with a particular hot spot in a node––for example, a hot-spot-specific action might be playing a sound when the mouse is moved over the hot spot.
All currently supported QTVR wired actions are specific to some particular node, so the atom containers implementing the actions are placed in the node information atom container that is contained in the media sample for that node in the QTVR track. Note that wired actions can be in a sprite or Flash track.
Programming Tasks
The programming tasks at hand can be distilled into these steps:
Find a media sample in the QTVR track.
Construct some atom containers for the desired actions.
Place those action containers into the appropriate place in the media sample.
Write the modified media sample back into the QTVR track.
Put an atom into the media property atom container to enable wired action processing.
Step #1––Getting the Movie File and Adding Wired Actions
To get a movie file from the user, you call:
StandardGetFile(NULL, 1, myTypeList, &myReply); |
Now to add some wired actions to the movie file, if it is a QuickTime VR movie, you do this:
if (myReply.sfGood) |
AddVRAct_AddWiredActionsToQTVRMovie(&myReply.sfFile); |
Step #2––Constructing Some Atom Containers
You call AddVRAct_GetFirstHotSpot
, which returns through the theHotSpotID
parameter the ID of the first hot spot in the specified atom container (which is assumed to be a node information atom container).
The returned ID is not necessarily the numerically least ID; it is just the ID of the first hot spot atom in the atom container.
static OSErr AddVRAct_GetFirstHotSpot (Handle theSample, |
long *theHotSpotID) |
{ |
QTAtom myHotSpotParentAtom = 0; |
QTAtom myHotSpotAtom = 0; |
OSErr myErr = noErr; |
*theHotSpotID = 0; |
myHotSpotParentAtom = QTFindChildByIndex(theSample, |
kParentAtomIsContainer, |
kQTVRHotSpotParentAtomType, |
kIndexOne, NULL); |
if (myHotSpotParentAtom != 0) |
myHotSpotAtom = QTFindChildByIndex(theSample, myHotSpotParentAtom, |
kQTVRHotSpotAtomType, |
kIndexOne, theHotSpotID); |
return(myErr); |
} |
Now you call AddVRAct_CreateHotSpotActionContainer
, which returns through the theActions
parameter an atom container that contains a hot spot action.
Step #3––Setting the Pan Angle
Next, you set the pan angle to 10.0 degrees when the hot spot is clicked.
static OSErr AddVRAct_CreateHotSpotActionContainer |
(QTAtomContainer *theActions) |
{ |
QTAtom myEventAtom = 0; |
QTAtom myActionAtom = 0; |
long myAction; |
float myPanAngle; |
OSErr myErr = noErr; |
myErr = QTNewAtomContainer(theActions); |
. |
. |
. |
myErr = QTInsertChild(*theActions, kParentAtomIsContainer, |
kQTEventType, kQTEventMouseClick, kIndexOne, |
kZeroDataLength, NULL, &myEventAtom); |
. |
. |
. |
myErr = QTInsertChild(*theActions, myEventAtom, kAction, kIndexOne, |
kIndexOne, kZeroDataLength, NULL, |
&myActionAtom); |
if (myErr != noErr) |
goto bail; |
myAction = EndianS32_NtoB(kActionQTVRSetPanAngle); |
myErr = QTInsertChild(*theActions, myActionAtom, kWhichAction, |
kIndexOne, kIndexOne, sizeof(long), |
&myAction, NULL); |
if (myErr != noErr) |
goto bail; |
myPanAngle = 10.0; |
AddVRAct_ConvertFloatToBigEndian(&myPanAngle); |
myErr = QTInsertChild(*theActions, myActionAtom, kActionParameter, |
kIndexOne, kIndexOne, sizeof(float), &myPanAngle, |
NULL); |
. |
. |
. |
} |
Step #4––Setting the Pan Angle to 180.0 Degrees
You now call AddVRAct_CreateFrameLoadedActionContainer
, which returns through the theActions
parameter an atom container that contains a frame-loaded event action.
Next, you set the pan angle to 180.0 degrees.
static OSErr AddVRAct_CreateFrameLoadedActionContainer |
(QTAtomContainer *theActions) |
{ |
QTAtom myEventAtom = 0; |
QTAtom myActionAtom = 0; |
long myAction; |
float myPanAngle; |
OSErr myErr = noErr; |
myErr = QTNewAtomContainer(theActions); |
if (myErr != noErr) |
goto bail; |
myErr = QTInsertChild(*theActions, kParentAtomIsContainer, |
kQTEventFrameLoaded, kIndexOne, kIndexOne, |
kZeroDataLength, NULL, &myEventAtom); |
. |
. |
. |
myErr = QTInsertChild(*theActions, myEventAtom, kAction, kIndexOne, |
kIndexOne, kZeroDataLength, NULL, |
&myActionAtom); |
. |
. |
. |
myAction = EndianS32_NtoB(kActionQTVRSetPanAngle); |
myErr = QTInsertChild(*theActions, myActionAtom, kWhichAction, |
kIndexOne, kIndexOne, sizeof(long), |
&myAction, NULL); |
. |
. |
. |
myPanAngle = 180.0; |
AddVRAct_ConvertFloatToBigEndian(&myPanAngle); |
myErr = QTInsertChild(*theActions, myActionAtom, kActionParameter, |
kIndexOne, kIndexOne, sizeof(float), |
&myPanAngle, NULL); |
. |
. |
. |
} |
Step #5––Setting Actions to be Frame-Loaded
Now you call AddVRAct_SetFrameLoadedWiredActions
to set the specified actions to be a frame-loaded action. If theActions is NULL, you remove any existing frame-loaded action from theSample
.
The theSample
parameter is assumed to be a node information atom container; any actions that are global to the node should be inserted at the root level of this atom container. In addition, the container type should be the same as the event type and should have an atom ID of 1.
static OSErr AddVRAct_SetFrameLoadedWiredActions |
(Handle theSample, QTAtomContainer theActions) |
{ |
QTAtom myEventAtom = 0; |
QTAtom myTargetAtom = 0; |
OSErr myErr = noErr; |
// look for a frame-loaded action atom |
// in the specified actions atom container |
if (theActions != NULL) |
myEventAtom = QTFindChildByID(theActions, kParentAtomIsContainer, |
kQTEventFrameLoaded, kIndexOne, |
NULL); |
// look for a frame-loaded action atom |
// in the node information atom container |
myTargetAtom = QTFindChildByID(theSample, kParentAtomIsContainer, |
kQTEventFrameLoaded, kIndexOne, NULL); |
if (myTargetAtom != 0) { |
// if there is already a frame-loaded event atom in the node |
// information atom container, |
// then either replace it with the one we were passed or remove it |
if (theActions != NULL) |
myErr = QTReplaceAtom(theSample, myTargetAtom, theActions, |
myEventAtom); |
else |
myErr = QTRemoveAtom(theSample, myTargetAtom); |
} else { |
// there is no frame-loaded event atom |
// in the node information atom container, |
// so add in the one we were passed |
if (theActions != NULL) |
myErr = QTInsertChildren(theSample, kParentAtomIsContainer, |
theActions); |
} |
return(myErr); |
} |
Step #6––Setting Hot Spot Actions
Now using AddVRAct_SetWiredActionsToHotSpot
, you set the specified actions to be a hot-spot action.
If theActions is NULL, you remove any existing hot-spot actions for the specified hot spot from theSample.
static OSErr AddVRAct_SetWiredActionsToHotSpot (Handle theSample, long |
theHotSpotID, |
QTAtomContainer |
theActions) |
{ |
QTAtom myHotSpotParentAtom = 0; |
QTAtom myHotSpotAtom = 0; |
short myCount, myIndex; |
OSErr myErr = paramErr; |
myHotSpotParentAtom = QTFindChildByIndex(theSample, |
kParentAtomIsContainer, |
kQTVRHotSpotParentAtomType, |
kIndexOne, NULL); |
if (myHotSpotParentAtom == NULL) |
goto bail; |
myHotSpotAtom = QTFindChildByID(theSample, myHotSpotParentAtom, |
kQTVRHotSpotAtomType, theHotSpotID, |
NULL); |
if (myHotSpotAtom == NULL) |
goto bail; |
// see how many events are already associated |
// with the specified hot spot |
myCount = QTCountChildrenOfType(theSample, myHotSpotAtom, |
kQTEventType); |
for (myIndex = myCount; myIndex > 0; myIndex--) { |
QTAtom myTargetAtom = 0; |
// remove all the existing events |
myTargetAtom = QTFindChildByIndex(theSample, myHotSpotAtom, |
kQTEventType, myIndex, NULL); |
if (myTargetAtom != 0) { |
myErr = QTRemoveAtom(theSample, myTargetAtom); |
if (myErr != noErr) |
goto bail; |
} |
} |
if (theActions) { |
myErr = QTInsertChildren(theSample, myHotSpotAtom, theActions); |
if (myErr != noErr) |
goto bail; |
} |
bail: |
return(myErr); |
} |
Step #7––Adding a Media Property Atom
To add a media property atom to the specified media, you call AddVRAct_WriteMediaPropertyAtom
.
You assume that the data passed through the theProperty
parameter is big-endian.
static OSErr AddVRAct_WriteMediaPropertyAtom (Media theMedia, |
long thePropertyID, long thePropertySize, void *theProperty) |
{ |
QTAtomContainer myPropertyAtom = NULL; |
QTAtom myAtom = 0; |
OSErr myErr = noErr; |
// get the current media property atom |
myErr = GetMediaPropertyAtom(theMedia, &myPropertyAtom); |
if (myErr != noErr) |
goto bail; |
// if there isn't one yet, then create one |
if (myPropertyAtom == NULL) { |
myErr = QTNewAtomContainer(&myPropertyAtom); |
if (myErr != noErr) |
goto bail; |
} |
// see if there is an existing atom of the specified type; |
// if not, then create one |
myAtom = QTFindChildByID(myPropertyAtom, kParentAtomIsContainer, |
thePropertyID, kIndexOne, NULL); |
if (myAtom == NULL) { |
myErr = QTInsertChild(myPropertyAtom, kParentAtomIsContainer, |
thePropertyID, kIndexOne, kIndexZero, |
kZeroDataLength, NULL, &myAtom); |
if ((myErr != noErr) || (myAtom == NULL)) |
goto bail; |
} |
// set the data of the specified atom to the data passed in |
myErr = QTSetAtomData(myPropertyAtom, myAtom, thePropertySize, |
(Ptr)theProperty); |
if (myErr != noErr) |
goto bail; |
// write the new atom data out to the media property atom |
myErr = SetMediaPropertyAtom(theMedia, myPropertyAtom); |
bail: |
if (myPropertyAtom != NULL) |
myErr = QTDisposeAtomContainer(myPropertyAtom); |
// this kills any error report above |
return(myErr); |
} |
Step #8––Adding Wired Actions
To add some wired actions to the specified QTVR movie, you call AddVRAct_AddWiredActionsToQTVRMovie
.
Wired actions are added to a QTVR movie by adding atom containers in the appropriate locations.
static void AddVRAct_AddWiredActionsToQTVRMovie (FSSpec *theFSSpec) |
{ |
short myResID = 0; |
short myResRefNum = -1; |
Movie myMovie = NULL; |
Track myTrack = NULL; |
Media myMedia = NULL; |
TimeValue myTrackOffset; |
TimeValue myMediaTime; |
TimeValue mySampleDuration; |
TimeValue mySelectionDuration; |
TimeValue myNewMediaTime; |
QTVRSampleDescriptionHandle myQTVRDesc = NULL; |
Handle mySample = NULL; |
short mySampleFlags; |
Fixed myTrackEditRate; |
QTAtomContainer myActions = NULL; |
Boolean myHasActions; |
long myHotSpotID = 0L; |
OSErr myErr = noErr; |
Step #9––Opening the Movie and Getting the Track
You open the movie file for reading and writing and get the QTVR track from the movie.
myErr = OpenMovieFile(theFSSpec, &myResRefNum, fsRdWrPerm); |
if (myErr != noErr) |
goto bail; |
myErr = NewMovieFromFile(&myMovie, myResRefNum, &myResID, NULL, |
newMovieActive, NULL); |
if (myErr != noErr) |
goto bail; |
// find the first QTVR track in the movie; |
// this assumes that the movie is a QuickTime VR movie formatted |
// according to version 2.0 or later |
// (version 1.0 VR movies don't have a QTVR track) |
myTrack = GetMovieIndTrackType(myMovie, kIndexOne, kQTVRQTVRType, |
movieTrackMediaType); |
if (myTrack == NULL) |
goto bail; |
Step #10––Getting the First Media Sample
You call GetTrackMedia
to get the first media sample in the QTVR track.
The QTVR track contains one media sample for each node in the movie; that sample contains a node information atom container, which contains general information about the node (such as its type, its ID, its name, and a list of its hot spots).
myMedia = GetTrackMedia(myTrack); |
if (myMedia == NULL) |
goto bail; |
myTrackOffset = GetTrackOffset(myTrack); |
myMediaTime = TrackTimeToMediaTime(myTrackOffset, myTrack); |
// allocate some storage to hold the sample description |
// for the QTVR track |
myQTVRDesc = (QTVRSampleDescriptionHandle)NewHandle(4); |
if (myQTVRDesc == NULL) |
goto bail; |
mySample = NewHandle(0); |
if (mySample == NULL) |
goto bail; |
myErr = GetMediaSample(myMedia, mySample, 0, NULL, myMediaTime, NULL, |
&mySampleDuration, |
(SampleDescriptionHandle)myQTVRDesc, NULL, 1, |
NULL, &mySampleFlags); |
if (myErr != noErr) |
goto bail; |
// create an action container for frame-loaded actions |
myErr = AddVRAct_CreateFrameLoadedActionContainer(&myActions); |
if (myErr != noErr) |
goto bail; |
// add frame-loaded actions to sample |
myErr = AddVRAct_SetFrameLoadedWiredActions(mySample, myActions); |
if (myErr != noErr) |
goto bail; |
myErr = QTDisposeAtomContainer(myActions); |
if (myErr != noErr) |
goto bail; |
// find the first hot spot in the selected node |
myErr = AddVRAct_GetFirstHotSpot(mySample, &myHotSpotID); |
if ((myErr != noErr) || (myHotSpotID == 0)) |
goto bail; |
// create an action container for hot-spot actions |
myErr = AddVRAct_CreateHotSpotActionContainer(&myActions); |
if (myErr != noErr) |
goto bail; |
// add hot-spot actions to sample |
myErr = AddVRAct_SetWiredActionsToHotSpot(mySample, myHotSpotID, |
myActions); |
if (myErr != noErr) |
goto bail; |
//replace sample in media |
myTrackEditRate = GetTrackEditRate(myTrack, myTrackOffset); |
if (GetMoviesError() != noErr) |
goto bail; |
GetTrackNextInterestingTime(myTrack, nextTimeMediaSample | |
nextTimeEdgeOK, myTrackOffset, fixed1, |
NULL, &mySelectionDuration); |
if (GetMoviesError() != noErr) |
goto bail; |
myErr = DeleteTrackSegment(myTrack, myTrackOffset, |
mySelectionDuration); |
if (myErr != noErr) |
goto bail; |
myErr = BeginMediaEdits(myMedia); |
if (myErr != noErr) |
goto bail; |
myErr = AddMediaSample( myMedia, |
mySample, |
0, |
GetHandleSize(mySample), |
mySampleDuration, |
(SampleDescriptionHandle)myQTVRDesc, |
1, |
mySampleFlags, |
&myNewMediaTime); |
if (myErr != noErr) |
goto bail; |
myErr = EndMediaEdits(myMedia); |
if (myErr != noErr) |
goto bail; |
// add the media to the track |
myErr = InsertMediaIntoTrack(myTrack, myTrackOffset, myNewMediaTime, |
mySelectionDuration, myTrackEditRate); |
if (myErr != noErr) |
goto bail; |
// set the actions property atom, to enable wired action processing |
// myHasActions = true; since sizeof(Boolean) == 1, |
// there is no need to swap bytes here |
myErr = AddVRAct_WriteMediaPropertyAtom(myMedia, |
kSpriteTrackPropertyHasActions, |
sizeof(Boolean), &myHasActions); |
if (myErr != noErr) |
goto bail; |
// update the movie resource |
myErr = UpdateMovieResource(myMovie, myResRefNum, myResID, NULL); |
if (myErr != noErr) |
goto bail; |
// close the movie file |
myErr = CloseMovieFile(myResRefNum); |
bail: |
if (myActions != NULL) |
(void)QTDisposeAtomContainer(myActions); |
if (mySample != NULL) |
DisposeHandle(mySample); |
if (myQTVRDesc != NULL) |
DisposeHandle((Handle)myQTVRDesc); |
if (myMovie != NULL) |
DisposeMovie(myMovie); |
} |
Step #11––Converting to Big-Endian Format
You call AddVRAct_ConvertFloatToBigEndian
to convert the specified floating-point number to big-endian format.
void AddVRAct_ConvertFloatToBigEndian (float *theFloat) |
{ |
unsigned long *myLongPtr; |
myLongPtr = (unsigned long *)theFloat; |
*myLongPtr = EndianU32_NtoB(*myLongPtr); |
} |
Copyright © 2002, 2009 Apple Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2009-06-01