MovieStuff.c

/*
    File:       MovieStuff.c
 
    Contains:       movie handling routines
                            This handles all the clever movie stuff.
                            - Sets up movie windows
                            - Tears down movie windows
                            - handles updates and events
                            - handles slaving, altering playback speed, looping etc
                            
    Written by: Jason Hodges-Harris & Don Swatman   
 
    Copyright:  Copyright © 1995-1999 by Apple Computer, Inc., All Rights Reserved.
 
                You may incorporate this Apple sample source code into your program(s) without
                restriction. This Apple sample source code has been provided "AS IS" and the
                responsibility for its operation is yours. You are not permitted to redistribute
                this Apple sample source code as "Apple sample source code" after having made
                changes. If you're going to re-distribute the source, we require that you make
                it clear in the source that the code was descended from Apple sample source
                code, but that you've made changes.
 
    Change History (most recent first):
                8/17/1999   Karl Groethe    Updated for Metrowerks Codewarror Pro 2.1
                
 
*/
 
#include <Memory.h>
#include <TextUtils.h>
#include <Movies.h>
 
#include "MovieStuff.h"
#include "WindStuff.h"
 
//==============================================
//  CallBackInfoType
//
// This structure is used to store QuickTime 
// call backs and any data they require.
// - It has a several parameters at the front,
//   then a variable size record at the end. It also
//   needs the parameters used in CallMeWhen
// - It's size is:
//      "sizeof(CallBackInfoType) + callBackDataSize - sizeof(long)"
// - Data should not contain the only reference
//   to other handles, as it can't dispose of them
// - It forms a linked list. Any new items are
//   added to the front.
// - It should be passed in the call backs 
//   reference field.
//==============================================
 
struct CallBackInfoType
{
    struct CallBackInfoType **hNextCallBackInfo; // Next in the list
    
    QTCallBack actualCallBack;    // the call back
    WindowPtr  pParentWindow;     // parent window of the movie
    long       param1;            // Used in "CallMeWhen"
    long       param2;            // Used in "CallMeWhen"
    long       param3;            // Used in "CallMeWhen"
    long       callBackDataSize;  // Size of the data attached on the end
    long       callBackDataStart; // where the data starts
};
 
typedef struct CallBackInfoType CallBackInfoType,
    *CallBackInfoPtr, **CallBackInfoHdl;
 
//==============================================
//  DocMovieInfoType
//
// Used to hold information about a movie within
// a window
// - It should be attached to the windows RefCon
//==============================================
 
struct DocMovieInfoType
{
        FSSpec          movieFileSpec;  // movies File Spec
        MovieController movieControls;  // standard movie controller
        Movie           actualMovie;        // the Movie itself!
        Boolean         movieAutoClose; // True if the window closes after movie finishes
        TimeBase              moviesTimeBase;
        TimeScale             moviesTimeScale;
        CallBackInfoHdl hFirstCallBackData; // Handle to first item of the call back info list
        WindowPtr       pSlaveWind;     // WindowPtr of this movies slave
};
 
typedef struct DocMovieInfoType DocMovieInfoType,
    *DocMovieInfoPtr, **DocMovieInfoHndl;
 
//==============================================
// Global Stuff, init and tear down             
//==============================================
 
// Prototypes of functions  used in UPPs
pascal void StartMovieCB(QTCallBack myCallBack,long ref);
pascal void AlterRate( QTCallBack myCallBack,long ref);
pascal void AlterMasterOffset( QTCallBack myCallBack,long ref);
 
// Global UPPs
QTCallBackUPP       gStartMovieCBUpp;
QTCallBackUPP       gAlterRateUpp;
QTCallBackUPP       gAlterMasterOffsetUpp;
 
//----------------------------------------------
// InitMovieGlobals
//
// Init's any globals used in MovieStuff
// i.e. the UPPs             
//----------------------------------------------
void InitMovieGlobals(void)
{
    gStartMovieCBUpp = nil;
    gAlterRateUpp    = nil;
    gAlterMasterOffsetUpp = nil;
 
//  Create routine descriptor for "StartMovieCB" QT callback routine
    gStartMovieCBUpp = NewQTCallBackProc(StartMovieCB);
 
//  Create routine descriptor for "AlterRate" QT callback routine
    gAlterRateUpp = NewQTCallBackProc(AlterRate);
 
//  Create routine descriptor for "AlterMasterOffset" QT callback routine
    gAlterMasterOffsetUpp = NewQTCallBackProc(AlterMasterOffset);
 
}
 
//----------------------------------------------
// KillMovieGlobals
//
// Removes the UPPs             
//----------------------------------------------
void KillMovieGlobals(void)
{
// Clear the Universal Proc Pointers
//    You don't need to do this as quiting the app will do it for you,
//    however, I have this thing about neatness.    
    if (gStartMovieCBUpp)
        DisposeRoutineDescriptor(gStartMovieCBUpp);
    if (gAlterRateUpp)
        DisposeRoutineDescriptor(gAlterRateUpp);
    if (gAlterMasterOffsetUpp)
        DisposeRoutineDescriptor(gAlterMasterOffsetUpp);
        
}
 
 
//==============================================
//
// Error Reporting
//
//==============================================
 
//----------------------------------------------
// Report Error
//
// If an error occurs, Uses DebugStr to send out
// some text passed to it, then the error number
//----------------------------------------------
 
void ReportError( Str255 errText, OSErr theErr );
void ReportError( Str255 errText, OSErr theErr )
{
    Str255 errorStr;
 
// Check there is an error
    if (theErr)
    {
// convert theErr number to text
        NumToString(theErr,errorStr);
 
// if there is error text then displat it
        if (errText != "\p")
            DebugStr(errText);
// Display error number
        DebugStr(errorStr);
    }
}
 
//----------------------------------------------
// GetAndReportError
//
//   Checks GetMoviesError and reports error if
// needed
//----------------------------------------------
OSErr GetAndReportError( Str255 errText );
OSErr GetAndReportError( Str255 errText )
{
    OSErr  theErr;
 
    theErr = GetMoviesError();       // Get Movies error number
    ReportError( errText, theErr );  // And report it
    return(theErr);                  // Oh yeh, return it to the caller
}
 
//==============================================
//
// Call Back info stuff
//
// Following routines handle the CallBackInfoHdl
// structure.
//==============================================
 
//----------------------------------------------
// AddNewCallBackInfo
//
//  Creates a new "CallBackInfoHdl", intialises and
//    returns it
// - It puts "pWindow" and "theCallBack" into the structure
// - It appends "dataSize" bytes of data from "pDataStart"
//   on to the end of the structure 
//----------------------------------------------
 
CallBackInfoHdl AddNewCallBackInfo( WindowPtr  pWindow,
                                                                        QTCallBack theCallBack,
                                                                        Ptr        pDataStart,
                                                                        long       dataSize,
                                                                        long       param1,
                                                                        long       param2,
                                                                        long       param3
                                                                    );
CallBackInfoHdl AddNewCallBackInfo( WindowPtr  pWindow,
                                                                        QTCallBack theCallBack,
                                                                        Ptr        pDataStart,
                                                                        long       dataSize,
                                                                        long       param1,
                                                                        long       param2,
                                                                        long       param3
                                                                    )
{
    CallBackInfoHdl  hNewCBInfo = nil;  // the new call back info record
    DocMovieInfoHndl hDocMovieInfo;
    long newSize;   // Size of hNewCBInfo and the data added to it
    
// Calculate how big the block needs to be
    newSize = sizeof(CallBackInfoType) + dataSize - sizeof(long);
 
// Create the call back data item
    hNewCBInfo = (CallBackInfoHdl) NewHandle (newSize);
 
// Check that we actualy did create it!
    if (hNewCBInfo)
    {
 
// Setup the structure
 
        (**hNewCBInfo).hNextCallBackInfo = nil;
        (**hNewCBInfo).actualCallBack    = theCallBack;
        (**hNewCBInfo).pParentWindow     = pWindow;
        (**hNewCBInfo).param1 = param1;
        (**hNewCBInfo).param2 = param2;
        (**hNewCBInfo).param3 = param3;
        (**hNewCBInfo).callBackDataSize  = 0;
 
// Add in the data (if there is any)
 
        if (dataSize && pDataStart)
        {
            (**hNewCBInfo).callBackDataSize  = dataSize;
            BlockMove( pDataStart, &((**hNewCBInfo).callBackDataStart), dataSize);
        }
 
// Now, link it into the window data structure (attach it to the front of the chain)
 
        hDocMovieInfo = (DocMovieInfoHndl)GetWRefCon ( pWindow );
        (**hNewCBInfo).hNextCallBackInfo     = (**hDocMovieInfo).hFirstCallBackData;
        (**hDocMovieInfo).hFirstCallBackData = hNewCBInfo;
    }
    
    return( hNewCBInfo );
}
 
//----------------------------------------------
//  RemoveCallBackInfo
//
//  Scans down the CallBackInfoHdl chain and disposes
// of each one
//----------------------------------------------
 
void RemoveCallBackInfo ( CallBackInfoHdl hFirstItem );
void RemoveCallBackInfo ( CallBackInfoHdl hFirstItem )
{
    CallBackInfoHdl  hNextCBInfo;
    CallBackInfoHdl  hDisposeData;
 
    if (hFirstItem)
    {
// Scan down the chain of items, disposing as we go
        hNextCBInfo = hFirstItem;
        while (hNextCBInfo)
        {
            hDisposeData = hNextCBInfo;
            hNextCBInfo  = (**hNextCBInfo).hNextCallBackInfo;
    
        // Dispose of the call back
            DisposeCallBack( (**hDisposeData).actualCallBack );
 
        // Dispose of the call back info block
            DisposeHandle( (Handle) hDisposeData);
        }
    }
        
}
 
//----------------------------------------------
//  GetDataFromCBInfo
//
// Extracts the data from a CallBackInfoHdl and
// moves the data into it. Checks the stored structure
// is smaller than requested size. Ideally they should
// be the same size.
//----------------------------------------------
 
Boolean GetDataFromCBInfo ( CallBackInfoHdl  hCallBackInfo,
                                                        Ptr    pData,
                                                        long   maxDataSize   );
Boolean GetDataFromCBInfo ( CallBackInfoHdl  hCallBackInfo,
                                                        Ptr    pData,
                                                        long   maxDataSize   )
{
    Boolean isOk = false;
    
// Check that we have a call back,
//                   and we have some where to put the data,
//                       and it's bigger than 0
    if (hCallBackInfo && pData && maxDataSize)
 
// Check the call back data is smaller than the new structure 
        if ( (**hCallBackInfo).callBackDataSize  <= maxDataSize)
        {
 
// Move the data to pData
            BlockMove ( &((**hCallBackInfo).callBackDataStart),
                                    pData,
                                    (**hCallBackInfo).callBackDataSize );
            isOk = true;
        }
    return ( isOk );
}
 
//----------------------------------------------
//  IsMoviePlaying
//
// Find out if any movie is playing
//----------------------------------------------
 
Boolean IsMoviePlaying (void)
{
    DocMovieInfoHndl hDocMovieInfo; // The movie we're checkings info
    Boolean movieLive = false; // set to true if there is a live window
    short   windCount;
    
// Scan the window list until we've found a live window
    for (windCount = 0;(windCount < kMaxWindows) && !movieLive; windCount++ )
// If this window is open
        if (gTheWinds[windCount])
            {
// Get information about the movie
                hDocMovieInfo = (DocMovieInfoHndl)GetWRefCon (gTheWinds[windCount]);
 
// Use IsMovieDone to see if it's still running
                if (!IsMovieDone((**hDocMovieInfo).actualMovie) )
                    movieLive = true;
            }
    return (movieLive);
}
 
//----------------------------------------------
// UpdateMovieWindow
//----------------------------------------------
void UpdateMovieWindow (  WindowPtr pWindow   )
{
// Get the movie from the ref con and update it. Easy.
    UpdateMovie( (**(DocMovieInfoHndl)GetWRefCon( pWindow )).actualMovie);
}
 
//----------------------------------------------
// Load a movie into memory
//
//  Use standard file to get and load a movie
//----------------------------------------------
void    LoadOneMovie( DocMovieInfoHndl hDocMovieInfo );
void    LoadOneMovie( DocMovieInfoHndl hDocMovieInfo )
{
    Movie         myNewMovie = nil;   // The new movie
    StandardFileReply   newMovieFile;   // Standard File Reply about the  movies file
    Str255        myMovieName;        // The Movies name
    OSErr         error;
    SFTypeList    myTypes = {MovieFileType}; // File types we want Standard File to get
    short         myMovieResFile;     // Movie files ref number
    short         myMovieResID = 0;   // Load the first movie res
    Boolean       movieChanged;       // Set to true if movie was changed durring 
                                                                        // loading to resolve references
        
// Use standard file to get the fsSpec etc.
    StandardGetFilePreview(nil,1,myTypes,&newMovieFile);
    if (newMovieFile.sfGood)
    {
// Open the movie file
        error = OpenMovieFile( &newMovieFile.sfFile, &myMovieResFile, fsRdPerm);
        if (error==noErr)
        {
// Store the file spec
            (**hDocMovieInfo).movieFileSpec = newMovieFile.sfFile;
 
// Move Movie data in to memory
            error = NewMovieFromFile( &myNewMovie, myMovieResFile, &myMovieResID,
                                      myMovieName, newMovieActive, &movieChanged);
 
// Close the file as we succeded in opening it
            CloseMovieFile (myMovieResFile);
        }
    }
    (**hDocMovieInfo).actualMovie = myNewMovie; // Return myNewMovie
}
 
//----------------------------------------------
// ServiceMovieTasks
//
// Used to Handle the null event. You need to handle
// this to make the movie play!
// - First it sees if the movie controller has an event
// - Scans the window list looking for movies
// - If the movie has finished then close it if the
//   flags are set that way
// - If any movies are active, then call MoviesTask
//   to make them play
// - Finally return true if the event was handled by the
//   movie controller
//----------------------------------------------
Boolean ServiceMovieTasks ( WindowPtr pWindow, const EventRecord *theEvent )
{
#pragma unused ( pWindow )
 
    Boolean doneProccessing = false;  // goes to true if MCIsPlayerEvent handles event
    DocMovieInfoHndl hDocMovieInfo;   // Current windows movie info
    short   windCount;
    Boolean needMovieTasks = false;   // Goes true if there is a movie running
    Boolean moviePlaying;
    
// Scan window list
    for (windCount = 0;windCount < kMaxWindows; windCount++ )
        if (gTheWinds[windCount])
            {
// Get movie information for this window
                hDocMovieInfo = (DocMovieInfoHndl)GetWRefCon (gTheWinds[windCount]);
 
// Handle any events to this windows movie controller if it's got one
                if (!doneProccessing)
                    if ((**hDocMovieInfo).movieControls)
                        if (MCIsPlayerEvent((**hDocMovieInfo).movieControls, theEvent))
                            doneProccessing = true;
    
// Next bit checks to see if a window has finished playing (by using
// IsMovieDone) and closes it if auto close has been set for it
                moviePlaying = !IsMovieDone((**hDocMovieInfo).actualMovie);
                if (   (**hDocMovieInfo).movieAutoClose && (!moviePlaying) )
                    CloseOurWindow( windCount );
                else
                    if (moviePlaying)
                        needMovieTasks = true; // Hey there's a movie running
            }
 
// If we have an active movie, then service all movies
    if (needMovieTasks)
        MoviesTask(nil,0);
 
    return ( doneProccessing ); 
}
 
//----------------------------------------------
//  CloseMovieWindow
//
// Close a movie window and destroys associated
// structures. If this is a Master window,
// sets "*pSlaveWindow" to the slave WindowPtr
//----------------------------------------------
OSErr CloseMovieWindow( WindowPtr pWindow,
                                                WindowPtr *pSlaveWindow )
{
    DocMovieInfoHndl hDocMovieInfo;
    
    *pSlaveWindow = nil;
 
// Get movie structure
    hDocMovieInfo = (DocMovieInfoHndl)GetWRefCon(pWindow);
 
    if (hDocMovieInfo)
    {
// Set the slave WindowPtr
        *pSlaveWindow = (**hDocMovieInfo).pSlaveWind;
 
// dispose movie
        DisposeMovie((**hDocMovieInfo).actualMovie);
    
// dispose of the controller if it exists
        if ((**hDocMovieInfo).movieControls)
            DisposeMovieController ( (**hDocMovieInfo).movieControls );
    
// dispose of call back structures
        RemoveCallBackInfo ( (**hDocMovieInfo).hFirstCallBackData );
        
// dispose movie structure
        DisposeHandle( (Handle)hDocMovieInfo );
    }
    return ( noErr );
}
 
//----------------------------------------------
//   OpenMovieWindow
//
// - Opens a movie
// - Creates and initialises the windows movie
//   structure "DocMovieInfoHndl"
// - Creates (if wanted) a movie controller
// - Sizes the window to fit the movie and controller
// - Shows the window
//----------------------------------------------
OSErr OpenMovieWindow ( WindowPtr pWindow,
                                                Boolean   doesAutoClose,
                                                Boolean   hasControler  )
{
    OSErr theErr = noErr;
    DocMovieInfoHndl hDocMovieInfo = nil; // new window movie information
    Rect moviesRect;   // Size of the movie
    Rect newWindRect;  // New Windows rect (movie + Controllers rect)
    
    if (!pWindow)
    {  // Error !!!!
        DebugStr("\pHey, this window doesn't exists!!");
        theErr = userCanceledErr;  // Should be something more appropriate
    }
    else
    {
 
// allocate new movie doc structure
        hDocMovieInfo = (DocMovieInfoType**) NewHandle (sizeof(DocMovieInfoType));
        if (hDocMovieInfo == nil)
        { // Error !!!!
            DebugStr("\pError allocating doc handle");
            theErr = userCanceledErr;  // Should be something more appropriate
        }
        else
        {
// Set up the hDocMovieInfo record
            (**hDocMovieInfo).movieAutoClose = doesAutoClose;
            (**hDocMovieInfo).hFirstCallBackData  = nil;
            (**hDocMovieInfo).movieControls = nil;
            (**hDocMovieInfo).actualMovie   = nil;
            (**hDocMovieInfo).pSlaveWind    = nil;
            
// set window refcon to movie doc handle
            SetWRefCon( pWindow, (long)hDocMovieInfo );
            SetPort(pWindow);
                    
// load movie refs into doc structure
            LoadOneMovie ( hDocMovieInfo );
            if ((**hDocMovieInfo).actualMovie == nil)
            { // Cancel button pressed or Error !!!!
                theErr = userCanceledErr;  // Should be something more appropriate
                return(theErr);
            }
            else
            {
// get timebase from movie
                (**hDocMovieInfo).moviesTimeBase = GetMovieTimeBase((**hDocMovieInfo).actualMovie);
                theErr = GetAndReportError( "\pError!! GetMovieTimeBase");
 
// get timescale from movie
                if (!theErr)
                {
                    (**hDocMovieInfo).moviesTimeScale = GetMovieTimeScale((**hDocMovieInfo).actualMovie);
                    theErr = GetAndReportError( "\pError!! Getting movie time scale");
                }
 
                if (!theErr)
                    if (!hasControler)
                    {
    // Don't want a controller, so just
        // Get movies rect as this is the size of the window
                        GetMovieBox((**hDocMovieInfo).actualMovie, &newWindRect);
                    }
                    else
                    {
    // We want a movie controller so create it beneath the movie
        // Get the movies rect as this is the size we want to put it in
                        GetMovieBox((**hDocMovieInfo).actualMovie, &moviesRect);
    
        //Create a controller and put below the movie. as moviesRect is the size of the movie,
        // the controller will fall outside the rect
                        (**hDocMovieInfo).movieControls = NewMovieController ( (**hDocMovieInfo).actualMovie,
                                                                                                                                     &moviesRect,
                                                                                                                                     mcTopLeftMovie);
                        if ((**hDocMovieInfo).movieControls == nil)
                        { // Failed to create controller Error !!!!
                            theErr = userCanceledErr;  // Should be something more appropriate
                            return(theErr);
                        }
                        else
                        {
    // Get movieControllers rect. Because it is attached the movie, it will return
    // the controller and movies size
                            theErr = MCGetControllerBoundsRect((**hDocMovieInfo).movieControls, &newWindRect);
                        }
                    }
 
                if (!theErr)
                {
    // Size the window to fit the movie and controller
                    SizeWindow( pWindow,newWindRect.right, newWindRect.bottom,true);
                }
            }
        }
    }
    
    return ( theErr );
}
 
//==============================================
// The time base call back procs
//==============================================
 
//----------------------------------------------
//   AlterMasterOffset
//
// QuickTime callback used to set the SetTimeBaseValue.
// - Can be used to make the movie loop.
// - Takes a value in seconds in the data of
//   the CallBackInfoHdl passed in "ref"
//----------------------------------------------
 
pascal void AlterMasterOffset(QTCallBack myCallBack,long ref)
{
    DocMovieInfoHndl hDocMovieInfo;
    WindowPtr pWindow;
    TimeValue newTimeValue; // Where we want to move to
    OSErr     theErr;
 
// Extract where we want to move to from (CallBackInfoHdl)ref
    if (GetDataFromCBInfo( ( CallBackInfoHdl)ref,
                                                 (Ptr) &newTimeValue, sizeof ( TimeValue ) ))
    {
        pWindow = (**(CallBackInfoHdl)ref).pParentWindow;  // Get the parent window
        hDocMovieInfo = (DocMovieInfoHndl)GetWRefCon ( pWindow ); // Get the movie info
// Move our position in the movie
        SetTimeBaseValue ( (**hDocMovieInfo).moviesTimeBase,
                                             newTimeValue,
                                             (**hDocMovieInfo).moviesTimeScale);
// Finally must reschedule the call back so that it happens again
        theErr = CallMeWhen ( myCallBack,
                                                    gAlterMasterOffsetUpp, ref,
                                                    (**(CallBackInfoHdl)ref).param1,
                                                    (**(CallBackInfoHdl)ref).param2,
                                                    (**(CallBackInfoHdl)ref).param3);
    }
}
 
//----------------------------------------------
//   AlterRate
//
// QuickTime callback used to set the movies rate.
// - Alters the speed of playback.
// - Takes a value in the data of the CallBackInfoHdl
//   passed in "ref"
//----------------------------------------------
 
pascal void AlterRate( QTCallBack myCallBack,long ref)
{
#pragma unused ( myCallBack )
 
    DocMovieInfoHndl hDocMovieInfo;
    WindowPtr pWindow;
    long      newMovieRate;
 
// Extract the new speed we want the movie to run at
    if (GetDataFromCBInfo ( ( CallBackInfoHdl)ref, (Ptr) &newMovieRate, sizeof ( long ) ))
    {
        pWindow = (**(CallBackInfoHdl)ref).pParentWindow;  // Get the parent window
        hDocMovieInfo = (DocMovieInfoHndl)GetWRefCon ( pWindow ); // Get the movie info
 
 
// Change the movies rate
        SetMovieRate((**hDocMovieInfo).actualMovie, newMovieRate);
    }
 
    return;
}
 
//----------------------------------------------
//   StartMovieCB
//
// QuickTime callback used to start a movie.
// - Takes a value (Movie) in the data of the CallBackInfoHdl
//   passed in "ref" which is the movie you want to start.
//----------------------------------------------
 
 
pascal void StartMovieCB(QTCallBack myCallBack,long ref)
{
#pragma unused ( myCallBack )
 
    Movie movieToStart;
        
    if (GetDataFromCBInfo ( ( CallBackInfoHdl)ref, (Ptr) &movieToStart, sizeof ( Movie ) ))
    {
        SetMovieRate(movieToStart,65536);
    }
}
 
//==============================================
// Set up the various options
//==============================================
 
//----------------------------------------------
// SetupMovieRate
//
// - Sets up the call backs to change the play back speed
//   of the movie.
// - "delayBeforeChange" gives the time in seconds when
//   the first speed change happens or a constant that
//   tells it to change at 1/3 (& 2/3) of the movies 
//   duration.
//----------------------------------------------
 
OSErr SetupMovieRate( WindowPtr pWindow,  short delayBeforeChange  )
{
    OSErr            theErr = noErr;
    QTCallBack           theNewCallBack;     // Call Back
    long                     theCallBackOffset;  // When we want to change the rate
    TimeValue              theMovieLen;        // Length of the movie
    DocMovieInfoHndl hDocMovieInfo;      // Info about the windows movie
    CallBackInfoHdl  hTempCallBackInfo;  // Call back information
    long             newMovieRate;       // What the new rate will be
 
    hDocMovieInfo = (DocMovieInfoHndl)GetWRefCon (pWindow);
    if (hDocMovieInfo != nil)
    {
// Set up the time base value
        SetTimeBaseValue( (**hDocMovieInfo).moviesTimeBase,
                                            0,
                                            (**hDocMovieInfo).moviesTimeScale);
 
        
// If we're changing by a thirds, we need the movie's length
        if (delayBeforeChange == kOneThird)
            theMovieLen  = GetMovieDuration((**hDocMovieInfo).actualMovie);
 
// ---- First call back - slow playback rate to x2 normal -----
 
// Work out length of delay before callbacks start
        if (delayBeforeChange == kOneThird)
            theCallBackOffset = theMovieLen*0.3333;
        else
            theCallBackOffset = delayBeforeChange * (**hDocMovieInfo).moviesTimeScale;
 
// Create a new call back
        theNewCallBack = NewCallBack((**hDocMovieInfo).moviesTimeBase,callBackAtTime);
 
        newMovieRate = 131072;      // speed up rate to x2 normal
 
// Put the call back into call back chain ( and append newMovieRate )
        hTempCallBackInfo = AddNewCallBackInfo( pWindow,
                                                                                        theNewCallBack,
                                                                                        (Ptr)&newMovieRate, sizeof ( long ),
                                                                                        triggerTimeFwd,
                                                                                        theCallBackOffset,
                                                                                        (**hDocMovieInfo).moviesTimeScale  );
 
// Attach the call back to the movie's time structures
        theErr = CallMeWhen ( theNewCallBack,
                                                        gAlterRateUpp,
                                                        (long)hTempCallBackInfo,
                                                        (**hTempCallBackInfo).param1,
                                                        (**hTempCallBackInfo).param2,
                                                        (**hTempCallBackInfo).param3 );
        if (theErr)
            ReportError( "\pError !! CallMeWhen", theErr );
        else
        {
 
// ---  Second call back - slow playback rate to 1/2 normal ----
 
// Work out length of delay before callbacks start
            if (delayBeforeChange == kOneThird)
                theCallBackOffset = theMovieLen*0.6666;
            else
                theCallBackOffset = (2 * delayBeforeChange) * (**hDocMovieInfo).moviesTimeScale;
    
// Create a new call back
            theNewCallBack = NewCallBack((**hDocMovieInfo).moviesTimeBase,callBackAtTime);
    
            newMovieRate = 32768;       // speed up rate to 1/2 normal      
 
// Put the call back into call back chain ( and append newMovieRate )
            hTempCallBackInfo = AddNewCallBackInfo( pWindow,
                                                                                            theNewCallBack,
                                                                                            (Ptr)&newMovieRate, sizeof ( long ),
                                                                                            triggerTimeFwd,
                                                                                            theCallBackOffset,
                                                                                            (**hDocMovieInfo).moviesTimeScale  );
    
// Attach the call back to the movie's time structures
            theErr = CallMeWhen ( theNewCallBack,
                                                        gAlterRateUpp,
                                                        (long)hTempCallBackInfo,
                                                        (**hTempCallBackInfo).param1,
                                                        (**hTempCallBackInfo).param2,
                                                        (**hTempCallBackInfo).param3 );
            if (theErr)
                ReportError( "\pError !! CallMeWhen", theErr );
        }
    }
    return ( theErr );  
}
 
//----------------------------------------------
//  SetupLoop
//
// Set up a loop in the movie.
// - It uses call backs, to change the position of
//   the movie "loopWhen" seconds into it and moves it
//   to "loopTo" seconds
//----------------------------------------------
 
OSErr SetupLoop ( WindowPtr pWindow,
                                    short    loopWhen,
                                    short    loopTo  )
{
    OSErr  theErr = noErr;
    DocMovieInfoHndl hDocMovieInfo; // Info about the windows movie
    long                 callBackWhen;  // When we want to start loop
    CallBackInfoHdl  hTempCallBackInfo; // Call back information
    QTCallBack           tempCallBack;  // Call Back
    TimeValue        newTimeValue;  // Where we want to loop to
 
    hDocMovieInfo = (DocMovieInfoHndl)GetWRefCon (pWindow);
 
// Calculate when the movie should 'jump'
    callBackWhen = loopWhen*(**hDocMovieInfo).moviesTimeScale;
 
// Creates a call back
    tempCallBack = NewCallBack((**hDocMovieInfo).moviesTimeBase, callBackAtTime);
 
// Set up where it's going to loop to
    newTimeValue = loopTo * (**hDocMovieInfo).moviesTimeScale;
 
// Add the call back to the call back list
    hTempCallBackInfo = AddNewCallBackInfo( pWindow,
                                                                                    tempCallBack,
                                                                                    (Ptr) &newTimeValue, sizeof ( TimeValue ),
                                                                                    triggerTimeFwd,
                                                                                    callBackWhen,
                                                                                    (**hDocMovieInfo).moviesTimeScale  );
 
// Attach the call back to the movie's time structures
    theErr = CallMeWhen( tempCallBack,
                                             gAlterMasterOffsetUpp, (long)hTempCallBackInfo,
                                            (**hTempCallBackInfo).param1,
                                            (**hTempCallBackInfo).param2,
                                            (**hTempCallBackInfo).param3 );
 
    return ( theErr );
}
 
//----------------------------------------------
// SetupSlaveMovie
//
// This sets up the slaved movie
// - "slaveAheadBy" sets the duration that the
//   slave  will lead the master (in seconds)
// - "slaveDelayStart" sets the duration that the
//   slave start will be delayed after the master starts
// - If both values are set the same, the slave will start
//   n seconds behind the master and run in sync
//----------------------------------------------
 
 
OSErr SetupSlaveMovie ( WindowPtr pMasterWindow,
                                                WindowPtr pSlaveWindow,
                                                short     slaveAheadBy,
                                                short     slaveDelayStart )
{
    OSErr                    theErr = noErr;
    DocMovieInfoHndl hDocMasterInfo;  // masters info
    DocMovieInfoHndl hDocSlaveInfo;   // slaves info
    long             slaveTimeOffset; // difference between the two movies
    long                 slaveStartDelay; // when we've got to start the slave
    CallBackInfoHdl  hTempCallBackInfo; // Call back information
    QTCallBack           tempCallBack;    // call back
    Movie            movieToStart;    // Put into the call back
    TimeValue              theMovieLen;     // Length of the movie
 
// Get the info about the two movies
    hDocMasterInfo = (DocMovieInfoHndl)GetWRefCon (pMasterWindow);
    hDocSlaveInfo  = (DocMovieInfoHndl)GetWRefCon (pSlaveWindow);
 
// Attach the slave window onto the master
    (**hDocMasterInfo).pSlaveWind = pSlaveWindow;
 
// Set the master movies time base value
    SetTimeBaseValue((**hDocMasterInfo).moviesTimeBase,0,(**hDocMasterInfo).moviesTimeScale);
 
// slave second movie to first
    SetMovieMasterTimeBase( (**hDocSlaveInfo).actualMovie,
                                                    (**hDocMasterInfo).moviesTimeBase,
                                                    nil );
 
// --- Setup the slave so it will lead the master ---
    if (slaveAheadBy != kInSync)
    {
// Calculate how big the offset should be
        if (slaveAheadBy == kOneThird)
        {
    // get the movie's length
            theMovieLen  = GetMovieDuration((**hDocSlaveInfo).actualMovie);
            slaveTimeOffset  = theMovieLen*0.3333;
        }
        else
            slaveTimeOffset  = slaveAheadBy*(**hDocMasterInfo).moviesTimeScale;
            
// Set the slaves time base offset
        SetTimeBaseValue( (**hDocSlaveInfo).moviesTimeBase,
                                            slaveTimeOffset,
                                            (**hDocSlaveInfo).moviesTimeScale);
        theErr = GetAndReportError( "\pError !! - Offsetting timebase" );
    }
 
// --- Put in the call back to start the slave movie (delayed if requested )--
 
// Set up slaveStartDelay
    if (slaveDelayStart == kInSync)
        slaveStartDelay = 0;
    else
        if (slaveDelayStart == kOneThird)
        {
    // get the movie's length
            theMovieLen  = GetMovieDuration((**hDocMasterInfo).actualMovie);
            slaveStartDelay = theMovieLen*0.3333;
        }
        else
            slaveStartDelay = slaveDelayStart * (**hDocMasterInfo).moviesTimeScale;
 
// Create a new call back
    tempCallBack = NewCallBack((**hDocMasterInfo).moviesTimeBase,callBackAtTime);
 
// Link the call back into the chain
    movieToStart = (**hDocSlaveInfo).actualMovie;
    hTempCallBackInfo = AddNewCallBackInfo( pMasterWindow,
                                                                                    tempCallBack,
                                                                                    (Ptr) &movieToStart, sizeof(Movie),
                                                                                    triggerTimeFwd,
                                                                                    slaveStartDelay,
                                                                                    (**hDocMasterInfo).moviesTimeScale  );
 
// Attach the call back to the movie's time structures
    theErr = CallMeWhen ( tempCallBack, gStartMovieCBUpp,
                                                (long)hTempCallBackInfo,
                                                (**hTempCallBackInfo).param1,
                                                (**hTempCallBackInfo).param2,
                                                (**hTempCallBackInfo).param3 );
 
    ReportError( "\pError!! - Creating movie start call back", theErr );
 
    return ( theErr );  
}
 
 
//----------------------------------------------
// StartMovieWindow
//
// Start a movie in pWindow
//----------------------------------------------
OSErr StartMovieWindow( WindowPtr pWindow,
                                                Boolean   fromBegining  )
{
    DocMovieInfoHndl hDocMovieInfo;
    
    if (pWindow)
    {
        hDocMovieInfo = (DocMovieInfoHndl)GetWRefCon (pWindow);
 
// Set up the movie and start it
        if (fromBegining)
            GoToBeginningOfMovie((**hDocMovieInfo).actualMovie);
 
        SetMovieActive((**hDocMovieInfo).actualMovie,true);
        StartMovie((**hDocMovieInfo).actualMovie);
    }
    return( noErr );
}