//////////
//
//	File:		QTFrameStepper.c
//
//	Contains:	Functions to step frame-by-frame through a QuickTime movie.
//
//	Written by:	Tim Monroe
//				Parts based on DTSQTUtilities.c by Apple Developer Technical Support
//				and on SimpleInMovies sample code by Guillermo A. Ortiz.
//
//	Copyright:	© 1997 by Apple Computer, Inc., all rights reserved.
//
//	Change History (most recent first):
//
//	   <5>	 	11/18/98	rtm		added QTStep_GetFrameCount, for illustrative purposes only
//	   <4>	 	03/09/98	rtm		changed nextTimeMediaSample flag to nextTimeStep (to support MPEG files)
//	   <3>	 	01/05/98	rtm		revised and augmented comments
//	   <2>	 	01/02/98	rtm		some code clean-up
//	   <1>	 	12/22/97	rtm		first file
//
//	This file defines functions that you can use to step frame-by-frame through a QuickTime movie.
//	Indeed, it illustrates *two* different methods for doing this: (1) using Movie Toolbox functions
//	to advance (or retreat) to interesting times in the movie; and (2) using movie controller actions
//	to step forward or backward through a movie. To my knowledge, there are no particular advantages
//	to using one or the other method, except that the second method is (as you will see) quite a bit
//	simpler to code.
//
//	METHOD ONE: Use Movie Toolbox calls to step to interesting times in the movie. An interesting time
//	is a time value in a movie, track, or media that meets certain search conditions that you specify.
//	We'll use a very simple search condition: locate the next (or previous) sample in the movie's media.
//	Once we have an interesting time, we display the sample at that time by calling SetMovieTimeValue.
//	To implement this first method, we define three functions (which all operate on an open movie):
//
//	-> QTStep_GoToNextVideoSample: display the sample that follows the current sample in a movie
//	-> QTStep_GoToPrevVideoSample: display the sample that precedes the current sample in a movie
//	-> QTStep_GoToFirstVideoSample: display the first video sample in a movie
//
//	Internally, these functions depend on three static functions defined at the beginning of this file.
//	The code here is extremely straightforward. The only "gotcha" concerns finding the first
//	interesting time in a movie. See the description of QTStep_GetStartTimeOfFirstVideoSample for
//	details.
//
//	METHOD TWO: Use movie controller actions to step through frames in the movie. This method uses
//	the MCDoAction function with the mcActionStep and mcActionGoToTime actions. Using this method,
//	the code is considerably simpler. To implement this second method, we define three functions
//	(which all operate on a movie controller that is associated with an open movie):
//
//	-> QTStep_MCGoToNextVideoSample: display the sample that follows the current sample in a movie
//	-> QTStep_MCGoToPrevVideoSample: display the sample that precedes the current sample in a movie
//	-> QTStep_MCGoToFirstVideoSample: display the first video sample in a movie
//
//	Historical note: Method One is based loosely on sample-stepping code in the DTSQTUtilities package
//	developed by Apple Macintosh Developer Technical Support. Method Two is based on a few functions
//	in the SimpleInMovies sample code written by Guillermo A. Ortiz.
//
//////////

#include "QTFrameStepper.h"


//////////
//
// METHOD ONE: Use Movie Toolbox calls to step to interesting times in the movie.
//
//////////

//////////
//
// QTStep_GetStartTimeOfFirstVideoSample
// Return, through the theTime parameter, the starting time of the first video sample of the
// specified QuickTime movie.
//
// The "trick" here is to set the nextTimeEdgeOK flag, to indicate that you want to get the
// starting time of the beginning of the movie.
//
// If this function encounters an error, it returns a (bogus) starting time of -1. Note that
// GetMovieNextInterestingTime also returns -1 as a starting time if the search criteria
// specified in the myFlags parameter are not matched by any interesting time in the movie. 
//
//////////

static OSErr QTStep_GetStartTimeOfFirstVideoSample (Movie theMovie, TimeValue *theTime)
{
	short			myFlags;
	OSType			myTypes[1];
	
	*theTime = kBogusStartingTime;							// a bogus starting time
	if (theMovie == NULL)
		return(invalidMovie);
	
	myFlags = nextTimeMediaSample + nextTimeEdgeOK;			// we want the first sample in the movie
	myTypes[0] = VisualMediaCharacteristic;					// we want video samples

	GetMovieNextInterestingTime(theMovie, myFlags, 1, myTypes, (TimeValue)0, fixed1, theTime, NULL);
	return(GetMoviesError());
}


//////////
//
// QTStep_DrawVideoSampleAtTime
// Draw the video sample of a QuickTime movie at the specified time.
//
//////////

static OSErr QTStep_DrawVideoSampleAtTime (Movie theMovie, TimeValue theTime)
{
	short			myFlags;
	OSErr			myErr = noErr;
	
	if (theMovie == NULL)
		return(invalidMovie);
	
	// make sure that the specified time lies within the movie's temporal bounds
	if ((theTime < 0) || (theTime > GetMovieDuration(theMovie)))
		return(paramErr);
	
	SetMovieTimeValue(theMovie, theTime);
	myErr = GetMoviesError();
	if (myErr != noErr)
		goto bail;
		
	// the following calls to UpdateMovie and MoviesTask are not necessary
	// if you are handling movie controller events in your main event loop
	// (by passing the event to MCIsPlayerEvent); they don't hurt, however.
	
	// redraw the movie immediately by calling UpdateMovie and MoviesTask
	UpdateMovie(theMovie);
	myErr = GetMoviesError();
	if (myErr != noErr)
		goto bail;
		
	MoviesTask(theMovie, 0L);
	myErr = GetMoviesError();

bail:
	return(myErr);
}


//////////
//
// QTStep_DrawVideoSampleNextOrPrev
// Draw the next or previous video sample of a QuickTime movie.
// If theRate is 1, the next video sample is drawn; if theRate is -1, the previous sample is drawn.
//
//////////

static OSErr QTStep_DrawVideoSampleNextOrPrev (Movie theMovie, Fixed theRate)
{
	TimeValue		myCurrTime;
	TimeValue		myNextTime;
	short			myFlags;
	OSType			myTypes[1];
	OSErr			myErr = noErr;
	
	if (theMovie == NULL)
		return(invalidMovie);
	
	myFlags = nextTimeStep;									// we want the next frame in the movie's media
	myTypes[0] = VisualMediaCharacteristic;					// we want video samples
	myCurrTime = GetMovieTime(theMovie, NULL);

	GetMovieNextInterestingTime(theMovie, myFlags, 1, myTypes, myCurrTime, theRate, &myNextTime, NULL);
	myErr = GetMoviesError();
	if (myErr != noErr)
		return(myErr);
		
	myErr = QTStep_DrawVideoSampleAtTime(theMovie, myNextTime);
	
	return(myErr);
}


//////////
//
// QTStep_GoToFirstVideoSample
// Draw the first video sample of a QuickTime movie.
//
//////////

OSErr QTStep_GoToFirstVideoSample (Movie theMovie)
{
	TimeValue		myTime;
	OSErr			myErr = noErr;
	
	if (theMovie == NULL)
		return(invalidMovie);
		
	myErr = QTStep_GetStartTimeOfFirstVideoSample(theMovie, &myTime);
	if (myErr != noErr)
		return(myErr);
		
	myErr = QTStep_DrawVideoSampleAtTime(theMovie, myTime);
	return(myErr);
}


//////////
//
// QTStep_GoToNextVideoSample
// Draw the next video sample of a QuickTime movie.
//
//////////

OSErr QTStep_GoToNextVideoSample (Movie theMovie)
{
	return(QTStep_DrawVideoSampleNextOrPrev(theMovie, fixed1));
}


//////////
//
// QTStep_GoToPrevVideoSample
// Draw the previous video sample of a QuickTime movie.
//
//////////

OSErr QTStep_GoToPrevVideoSample (Movie theMovie)
{
	return(QTStep_DrawVideoSampleNextOrPrev(theMovie, FixMul(Long2Fix(-1), fixed1)));
}


//////////
//
// METHOD TWO: Use movie controller actions to step thru frames in the movie.
//
//////////

//////////
//
// QTStep_MCGoToFirstVideoSample
// Draw the first video sample of the QuickTime movie associated with the specified movie controller.
//
//////////

OSErr QTStep_MCGoToFirstVideoSample (MovieController theMC)
{
	TimeRecord		myTimeRecord;
	Movie 			myMovie = NULL;
	OSErr 			myErr = noErr;
	
	if (theMC == NULL)
		return(paramErr);
		
	myMovie = MCGetMovie(theMC);
	if (myMovie == NULL)
		return(paramErr);
	
	myTimeRecord.value.hi = 0;
	myTimeRecord.value.lo = 0;
	myTimeRecord.base = 0;
	myTimeRecord.scale = GetMovieTimeScale(myMovie);
	myErr = GetMoviesError();
	if (myErr != noErr)
		return(myErr);
	
	return(MCDoAction(theMC, mcActionGoToTime, &myTimeRecord));
}


//////////
//
// QTStep_MCGoToNextVideoSample
// Draw the next video sample of the QuickTime movie associated with the specified movie controller.
//
//////////

OSErr QTStep_MCGoToNextVideoSample (MovieController theMC)
{
	short			myStep = 1;				// advance the movie one frame
	
	if (theMC == NULL)
		return(paramErr);
		
	return(MCDoAction(theMC, mcActionStep, (Ptr)myStep));
}


//////////
//
// QTStep_MCGoToPrevVideoSample
// Draw the previous video sample of the QuickTime movie associated with the specified movie controller.
//
//////////

OSErr QTStep_MCGoToPrevVideoSample (MovieController theMC)
{
	short			myStep = -1;			// back the movie up one frame
	
	if (theMC == NULL)
		return(paramErr);
		
	return(MCDoAction(theMC, mcActionStep, (Ptr)myStep));
}


///////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Frame utilities.
//
// These functions illustrate some other useful things you might want to do with movie frames;
// they are not used elsewhere in this file.
//
///////////////////////////////////////////////////////////////////////////////////////////////////////////

//////////
//
// QTStep_GetFrameCount
// Get the number of frames in the specified movie track. We return the value -1 if
// an error occurs and we cannot determine the number of frames in the track.
//
// Based (loosely) on frame-counting code in ConvertToMovie Jr.c.
// 
// We count the frames in the track by stepping through all of its interesting times
// (the places where the track displays a new sample).
//
//////////

long QTStep_GetFrameCount (Track theTrack)
{	
	long		myCount = -1;
	short		myFlags;
	TimeValue	myTime = 0;
	
	if (theTrack == NULL)
		goto bail;
		
	// we want to begin with the first frame (sample) in the track
	myFlags = nextTimeMediaSample + nextTimeEdgeOK;

	while (myTime >= 0) {
		myCount++;
		
		// look for the next frame in the track; when there are no more frames,
		// myTime is set to -1, so we'll exit the while loop
		GetTrackNextInterestingTime(theTrack, myFlags, myTime, fixed1, &myTime, NULL);
		
		// after the first interesting time, don't include the time we're currently at
		myFlags = nextTimeStep;
	}

bail:
	return(myCount);
}


