Retired Document
Important: This sample code may not represent best practices for current development. The project may use deprecated symbols and illustrate technologies and techniques that are no longer recommended.
MyDocument.m
/* |
File: MyDocument.m |
Description: Document-specific code for thread-safe movie export test application. |
Author: QuickTime Engineering |
Copyright: © Copyright 2003-2004 Apple Computer, Inc. All rights reserved. |
Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple Computer, Inc. |
("Apple") in consideration of your agreement to the following terms, and your |
use, installation, modification or redistribution of this Apple software |
constitutes acceptance of these terms. If you do not agree with these terms, |
please do not use, install, modify or redistribute this Apple software. |
In consideration of your agreement to abide by the following terms, and subject |
to these terms, Apple grants you a personal, non-exclusive license, under AppleÕs |
copyrights in this original Apple software (the "Apple Software"), to use, |
reproduce, modify and redistribute the Apple Software, with or without |
modifications, in source and/or binary forms; provided that if you redistribute |
the Apple Software in its entirety and without modifications, you must retain |
this notice and the following text and disclaimers in all such redistributions of |
the Apple Software. Neither the name, trademarks, service marks or logos of |
Apple Computer, Inc. may be used to endorse or promote products derived from the |
Apple Software without specific prior written permission from Apple. Except as |
expressly stated in this notice, no other rights or licenses, express or implied, |
are granted by Apple herein, including but not limited to any patent rights that |
may be infringed by your derivative works or by other works in which the Apple |
Software may be incorporated. |
The Apple Software is provided by Apple on an "AS IS" basis. APPLE MAKES NO |
WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED |
WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN |
COMBINATION WITH YOUR PRODUCTS. |
IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR |
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE |
GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) |
ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR DISTRIBUTION |
OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY OF CONTRACT, TORT |
(INCLUDING NEGLIGENCE), STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN |
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
Change History (most recent first): <2> dts 12/07/05 removed the check for progressOpExportMovie in the export progress proc. |
QT will now set higher values that are as of yet not defined and returning |
a paramErr when we didn't specifically see progressOpExportMovie caused |
exports to fail even though there was really no problem. |
<1> dts 02/09/04 added stricter checking for safe components as QT 6.5 did not |
correctly return a componentNotThreadSafeErr when exporting |
a safe media type (eg. video) containing an unsafe encoding format |
such as Sorenson. |
initial release |
*/ |
////////// |
// |
// header files |
// |
////////// |
#import "MyDocument.h" |
#import "WorkerThread.h" |
#import "ThreadData.h" |
#import "ProgressDialog.h" |
#import <fcntl.h> |
////////// |
// |
// non-Controller function declarations |
// |
////////// |
static OSErr exportTheMovie (ThreadData *threadData); |
static pascal OSErr movieExportProgressProc (Movie movie, short message, short operation, Fixed percentDone, long refCon); |
void workerActionRoutine (void *refcon, WorkerRequestRef request); |
void workerCancelRoutine (void *refcon, WorkerRequestRef request); |
void workerResponseMainThreadCallback (void *refcon, WorkerRequestRef request); |
static Boolean codecDoesTemporal( OSType codecType ); |
static Boolean isPotentiallySafeComponent( OSType inComponentType, OSType inComponentSubType ); |
////////// |
// |
// static global variables |
// |
////////// |
static MovieProgressUPP gMovieProgressProcUPP = NULL; // UPP for our progress procedure |
static unsigned long gOSVersion = 0; |
static QTAtomContainer gExportSettings = NULL; // the current export settings |
////////// |
// |
// MyDocument implementation |
// |
////////// |
@implementation MyDocument |
- (id)init |
{ |
WorkerThreadRef outWorker = NULL; |
[super init]; |
if (self) { |
progressDialog = [[ProgressDialog alloc] init]; |
_threadModelTag = USE_POSIX_THREAD; // use POSIX threads by default |
_onlySafeComponents = YES; // use only thread-safe components by default |
_ignoreUnsafeTypes = YES; // remove tracks containing unsafe media |
// allocate progress proc UPP, if necessary |
if (gMovieProgressProcUPP == NULL) |
gMovieProgressProcUPP = NewMovieProgressUPP(movieExportProgressProc); |
// get the current version of the OS we're running on |
Gestalt(gestaltSystemVersion, &gOSVersion); |
if (gOSVersion < 0x00001030) { |
// thread-safe components are available only in 10.3 (Panther) and beyond; for earlier systems, |
// don't allow POSIX threads to export movies |
_threadModelTag = USE_MAIN_THREAD; |
_onlySafeComponents = NO; |
} |
// create a worker thread handler |
createWorkerThread( |
workerActionRoutine, |
workerCancelRoutine, |
workerResponseMainThreadCallback, |
(void *)self, |
&outWorker); |
_worker = outWorker; |
} |
return self; |
} |
- (void)dealloc |
{ |
// remove the worker thread handler |
releaseWorkerThread(_worker); |
// release any memory still held by _currThreadData |
[self releaseThreadData:_currThreadData]; |
[progressDialog release]; |
[movieView setMovie:nil]; // not sure this is needed |
[super dealloc]; |
} |
- (NSString *)windowNibName |
{ |
return @"MyDocument"; |
} |
- (void)windowControllerDidLoadNib:(NSWindowController *) aController |
{ |
[super windowControllerDidLoadNib:aController]; |
} |
- (NSData *)dataRepresentationOfType:(NSString *)aType |
{ |
return nil; |
} |
- (BOOL)loadDataRepresentation:(NSData *)data ofType:(NSString *)aType |
{ |
return YES; |
} |
////////// |
// |
// menu validator and handlers |
// |
////////// |
- (BOOL)validateMenuItem:(NSMenuItem *)item { |
BOOL isValid = NO; |
SEL action = [item action]; |
if ((action == @selector(usePOSIXThreads:)) || |
(action == @selector(useMainThread:))) { |
[item setState: ([item tag] == _threadModelTag) ? NSOnState : NSOffState]; |
isValid = YES; |
// disable POSIX threads on systems earlier than 10.3 |
if (gOSVersion < 0x00001030) |
if (action == @selector(usePOSIXThreads:)) |
isValid = NO; |
} |
if (action == @selector(toggleThreadGuard:)) { |
[item setState: _onlySafeComponents ? NSOnState : NSOffState]; |
isValid = YES; |
} |
if (action == @selector(ignoreUnsafeTypes:)) { |
[item setState: _ignoreUnsafeTypes ? NSOnState : NSOffState]; |
isValid = (_threadModelTag == USE_POSIX_THREAD); |
} |
return isValid; |
} |
- (IBAction)usePOSIXThreads:(id)sender |
{ |
_threadModelTag = USE_POSIX_THREAD; |
} |
- (IBAction)useMainThread:(id)sender |
{ |
_threadModelTag = USE_MAIN_THREAD; |
} |
- (IBAction)toggleThreadGuard:(id)sender |
{ |
_onlySafeComponents = !_onlySafeComponents; |
} |
- (IBAction)ignoreUnsafeTypes:(id)sender |
{ |
_ignoreUnsafeTypes = !_ignoreUnsafeTypes; |
} |
- (IBAction)doButton:(id)sender |
{ |
if (!_selectedMovie) |
[self selectMovie:sender]; |
else |
[self doExport:sender]; |
} |
- (IBAction)selectMovie:(id)sender |
{ |
NSOpenPanel *oPanel = [NSOpenPanel openPanel]; |
NSArray *array = [NSArray arrayWithObjects:@"mov", @"mp4", nil]; |
NSString *filename = nil; |
FSRef fileRef; |
NSMovie *movie = nil; |
Movie qtMovie = NULL; |
short fileResNum = 0; |
int result; |
Handle inputDataRef = NULL; |
OSType inputDataRefType = 0; |
OSErr err = noErr; |
// elicit a movie file from the user |
[oPanel setCanChooseDirectories:NO]; |
[oPanel setCanChooseFiles:YES]; |
[oPanel setAllowsMultipleSelection:NO]; |
result = [oPanel runModalForTypes:array]; |
if (result != NSOKButton) |
return; |
// make sure we got a non-zero length filename |
filename = [[oPanel filenames] objectAtIndex:0]; |
if (filename == nil) |
return; |
if ([filename length] == 0) |
return; |
// open the movie file with read-only permission and load the movie from it |
err = FSPathMakeRef([filename fileSystemRepresentation], &fileRef, NULL); |
if (err == noErr) |
err = QTNewDataReferenceFromFSRef(&fileRef, 0, &inputDataRef, &inputDataRefType); |
if (err == noErr) |
err = NewMovieFromDataRef(&qtMovie, newMovieActive, &fileResNum, inputDataRef, inputDataRefType); |
if (qtMovie != NULL) { |
NSRect windowFrame = [[movieView window] frame]; |
NSPoint topLeft = NSMakePoint(NSMinX(windowFrame), NSMaxY(windowFrame)); |
NSSize movieSize = [self windowContentSizeForMovie:qtMovie]; |
movie = [[NSMovie alloc] initWithMovie:qtMovie]; |
// set size of the document window |
windowFrame.origin = topLeft; |
windowFrame.origin.y -= movieSize.height; |
windowFrame.size = movieSize; |
[[movieView window] setFrame:windowFrame display: YES animate: YES]; |
// set the movie view's movie |
[movieView setMovie:movie]; |
[movie release]; // not sure if we need this |
_selectedMovie = YES; |
[sender setTitle:@"Export"]; |
[[sender window] setTitle:[filename lastPathComponent]]; |
} |
if (inputDataRef) // toss our dataRef |
DisposeHandle(inputDataRef); |
} |
- (IBAction)doExport:(id)sender |
{ |
NSSavePanel *sPanel = [NSSavePanel savePanel]; |
ThreadData *threadData = NULL; |
ComponentDescription compDesc; |
Component movieExporterComponent = 0; |
ComponentInstance sc = NULL; |
SCSpatialSettings ss; |
// SCTemporalSettings ts; |
Movie origMovie = NULL; |
Movie movie = NULL; |
MovieExportComponent exporter = NULL; |
Boolean canceled = false; |
Boolean usingClone = false; |
FSRef fileRef; |
int result; |
OSErr err = noErr; |
// set the dialog window title |
[sPanel setTitle:@"Export Movie As:"]; |
// elicit an output file from the user |
result = [sPanel runModal]; |
if (result == NSFileHandlingPanelCancelButton) |
return; |
// allocate the per-thread storage we'll need to communicate with the thread |
threadData = calloc(1, sizeof(ThreadData)); |
if (threadData == NULL) |
return; |
// configure the thread data to the current settings |
threadData->threadModelTag = _threadModelTag; |
threadData->onlySafeComps = _onlySafeComponents; |
threadData->progressDialog = progressDialog; |
// clean up any existing _currThreadData |
[self releaseThreadData:_currThreadData]; |
_currThreadData = threadData; |
// create a data reference for the output movie file |
err = FSPathMakeRef([[sPanel filename] fileSystemRepresentation], &fileRef, NULL); |
if (err == fnfErr) { |
// if the file does not yet exist, then let's create the file |
int fd; |
fd = open([[sPanel filename] fileSystemRepresentation], O_CREAT | O_RDWR, 0600); |
if (fd < 0) |
return; |
close(fd); |
err = FSPathMakeRef([[sPanel filename] fileSystemRepresentation], &fileRef, NULL); |
} |
if (err) |
return; |
err = QTNewDataReferenceFromFSRef(&fileRef, 0, &threadData->dataRef, &threadData->dataRefType); |
compDesc.componentType = MovieExportType; |
compDesc.componentSubType = kQTFileTypeMovie; |
compDesc.componentManufacturer = kAppleManufacturer; |
compDesc.componentFlags = 0; |
compDesc.componentFlagsMask = cmpIsMissing; |
movieExporterComponent = FindNextComponent(NULL, &compDesc); |
if (movieExporterComponent == NULL) |
return; |
err = OpenAComponent(movieExporterComponent, &exporter); |
if (err) |
return; |
origMovie = [[movieView movie] QTMovie]; |
if ((_threadModelTag == USE_POSIX_THREAD) && _ignoreUnsafeTypes) { |
Handle cloneHandle = NewHandle(0); |
// do not export any tracks that are not thread safe |
if (cloneHandle) { |
// first, clone the original movie with PutMovieIntoHandle and NewMovieFromHandle |
err = PutMovieIntoHandle(origMovie, cloneHandle); |
if (err == noErr) { |
err = NewMovieFromHandle(&movie, cloneHandle, newMovieActive, NULL); |
if ((err == noErr) && (movie != NULL)) { |
long trackCount = GetMovieTrackCount(movie); |
long count; |
usingClone = true; |
// delete all tracks of unsafe types |
for (count = 1; count <= trackCount; count++) { |
Track track = GetMovieIndTrack(movie, count); |
Media media = GetTrackMedia(track); |
SampleDescriptionHandle desc; |
OSType theMediaType; |
OSType theCodecType; |
if (track) { |
GetMediaHandlerDescription(media, &theMediaType, NULL, NULL); |
desc = (SampleDescriptionHandle)NewHandle(0); |
GetMediaSampleDescription(media, 1, desc); |
theCodecType = (**desc).dataFormat; |
DisposeHandle((Handle)desc); |
if (!isPotentiallySafeComponent(MediaHandlerType, theMediaType) || !isPotentiallySafeComponent(decompressorComponentType, theCodecType)) { |
DisposeMovieTrack(track); |
count--; |
} |
} |
} |
} |
} |
DisposeHandle(cloneHandle); |
} |
if (err || (movie == NULL) || (GetMovieTrackCount(movie) < 1)) { |
// could not clone the movie, or we deleted ALL the tracks in the movie; just use the original and hope for the best.... |
NSBeep(); |
DisposeMovie(movie); // dispose the movie created from the cloneHandle |
movie = origMovie; |
usingClone = false; |
} |
} else { |
movie = origMovie; |
} |
if (gExportSettings == NULL) { |
// set up some initial default settings; use Standard Compression just to help build atom container |
err = OpenADefaultComponent( StandardCompressionType, StandardCompressionSubType, &sc ); |
if (err) |
goto bail; |
ss.codecType = kDVCNTSCCodecType; |
ss.codec = NULL; |
ss.depth = 0; |
ss.spatialQuality = codecNormalQuality; |
SCSetInfo(sc, scSpatialSettingsType, &ss); |
#if 0 |
// not appropriate for non-temporal codecs |
if ( codecDoesTemporal( ss.codecType ) ) { |
ts.temporalQuality = codecNormalQuality; |
ts.frameRate = 30L<<16; |
ts.keyFrameRate = 30; |
} else { |
ts.temporalQuality = 0; |
ts.frameRate = 0; |
ts.keyFrameRate = 0; |
} |
SCSetInfo( sc, scTemporalSettingsType, &ts ); |
#endif |
SCGetSettingsAsAtomContainer(sc, &gExportSettings); |
CloseComponent(sc); |
// first time thru, set a few settings |
if (gExportSettings != NULL) { |
UInt32 aLong = 0; |
UInt8 aChar; |
QTAtom videAtom = 0; |
QTAtom saveAtom = 0; |
// YES: video |
aChar = true; |
err = QTInsertChild( gExportSettings, kParentAtomIsContainer, kQTSettingsMovieExportEnableVideo, 1, 0, sizeof(aChar), &aChar, nil ); |
// NO: audio |
// aChar = false; |
// err = QTInsertChild( gExportSettings, kParentAtomIsContainer, kQTSettingsMovieExportEnableSound, 1, 0, sizeof(aChar), &aChar, nil ); |
// NO: save as Fast Start |
err = QTInsertChild( gExportSettings, kParentAtomIsContainer, kQTSettingsMovieExportSaveOptions, 1, 0, 0, nil, &saveAtom ); |
aChar = false; |
err = QTInsertChild( gExportSettings, saveAtom, kQTSettingsMovieExportSaveForInternet, 1, 0, sizeof(aChar), &aChar, nil ); |
// video options |
videAtom = QTFindChildByID( gExportSettings, kParentAtomIsContainer, kQTSettingsVideo, 1, nil ); |
if (videAtom == 0) |
err = QTInsertChild( gExportSettings, kParentAtomIsContainer, kQTSettingsVideo, 1, 0, 0, nil, &videAtom ); |
aLong = FixRatio(320,1); |
aLong = EndianU32_NtoB(aLong); |
err = QTInsertChild( gExportSettings, videAtom, movieExportWidth, 1, 0, sizeof(aLong), &aLong, nil ); |
aLong = FixRatio(240,1); |
aLong = EndianU32_NtoB(aLong); |
err = QTInsertChild( gExportSettings, videAtom, movieExportHeight, 1, 0, sizeof(aLong), &aLong, nil ); |
} |
} |
if (gExportSettings == NULL) |
return; |
// wrap 'em up, I'll take 'em.... |
err = MovieExportSetSettingsFromAtomContainer(exporter, gExportSettings); |
// get the export settings from the user |
err = MovieExportDoUserDialog(exporter, movie, NULL, 0, 0, &canceled); |
if (canceled || (err != noErr)) |
goto bail; |
QTDisposeAtomContainer(gExportSettings); |
gExportSettings = NULL; |
// get the selected export settings as an atom container (so we can pass them to the thread) |
err = MovieExportGetSettingsAsAtomContainer(exporter, &gExportSettings); |
if (err) |
goto bail; |
threadData->exportSettings = gExportSettings; |
// put the movie into a handle (so we can pass it to the thread) |
threadData->movieHandle = NewHandle(0); |
err = PutMovieIntoHandle(movie, threadData->movieHandle); |
if (err) |
goto bail; |
// show the progress dialog box as a sheet |
[progressDialog setThreadData:threadData]; |
[progressDialog startProgressTimer]; |
[[progressDialog statusField] setStringValue:@""]; |
[NSApp beginSheet:[progressDialog progressPanel] |
modalForWindow:[movieView window] |
modalDelegate:nil |
didEndSelector:nil |
contextInfo:nil]; |
switch (_threadModelTag) { |
case USE_MAIN_THREAD: |
// export the movie on the main thread |
// in theory, we could force threadData->onlySafeComps to be false here, |
// in which case we can dispense with the retry logic just below; |
// for demo/testing purposes, I'm going to follow the user's explicit selection |
exportTheMovie(threadData); |
if (threadData->retry) { |
[[progressDialog statusField] setStringValue:@"Retrying on main thread with any components!"]; |
threadData->onlySafeComps = false; |
exportTheMovie(threadData); |
} |
[progressDialog setProgressTimer:nil]; |
[[progressDialog progressPanel] close]; |
[NSApp endSheet:[progressDialog progressPanel]]; |
break; |
case USE_POSIX_THREAD: |
// export the movie on a pthread; create, configure, and send a new worker request |
err = createWorkerRequest(_worker, (WorkerRequestRef *)&threadData->request); |
if (err == noErr) { |
setWorkerRequestThreadData(threadData->request, threadData); |
setWorkerRequestDoc(threadData->request, (UInt32)self); |
setWorkerRequestProgressDialog(threadData->request, (UInt32)progressDialog); |
sendWorkerRequest(threadData->request); |
} |
break; |
} |
bail: |
// if we are using a cloned movie, we can now get rid of it |
if (usingClone) |
DisposeMovie(movie); |
if (exporter) |
CloseComponent(exporter); |
} |
- (NSSize)windowContentSizeForMovie:(Movie)qtMovie |
{ |
NSSize size; |
Rect rect; |
GetMovieNaturalBoundsRect(qtMovie, &rect); |
size.width = (float)(rect.right - rect.left); |
size.height = (float)(rect.bottom - rect.top); |
// enforce a minimum width (important for sound-only movies) |
if (size.width == 0) { |
NSRect frameRect; |
frameRect = [[movieView superview] frame]; |
size.width = (float)(frameRect.size.width); |
size.width -= 2 * kMoviePaneOffset; |
} |
size.width += 2 * kMoviePaneOffset; |
size.height += kMoviePaneOffset + kMovieBottomOffset; |
if ([movieView isControllerVisible]) { |
NSSize minSize; |
size.height += kMovieControllerBarHeight; |
minSize = [[movieView window] minSize]; |
minSize.width += 66; |
minSize.height += kMovieControllerBarHeight; |
[[movieView window] setMinSize:minSize]; |
} |
return size; |
} |
- (void)releaseThreadData:(ThreadData *)threadData |
{ |
if (threadData == NULL) |
return; |
if (threadData->movieHandle != NULL) |
DisposeHandle(threadData->movieHandle); |
if (_currThreadData == threadData) |
_currThreadData = NULL; |
free(threadData); |
} |
@end |
static OSErr exportTheMovie (ThreadData *threadData) |
{ |
ComponentDescription compDesc; |
Component movieExporterComponent = 0; |
MovieExportComponent exporter = NULL; |
Movie movie = NULL; |
ComponentResult err = noErr; |
if (threadData == NULL) |
return paramErr; |
if (threadData->movieHandle == NULL) |
return paramErr; |
// override the thread mode -- for testing/demo purposes only! |
if (threadData->onlySafeComps) { |
CSSetComponentsThreadMode(kCSAcceptThreadSafeComponentsOnlyMode); |
} else { |
CSSetComponentsThreadMode(kCSAcceptAllComponentsMode); |
} |
// open a movie from the movie handle |
err = NewMovieFromHandle(&movie, threadData->movieHandle, newMovieActive, NULL); |
if (err != noErr) { |
if (err == componentNotThreadSafeErr) |
threadData->retry = true; |
goto bail; |
} |
// get and configure a movie export component |
compDesc.componentType = MovieExportType; |
compDesc.componentSubType = kQTFileTypeMovie; |
compDesc.componentManufacturer = kAppleManufacturer; |
compDesc.componentFlags = 0; |
compDesc.componentFlagsMask = cmpIsMissing; |
movieExporterComponent = FindNextComponent(NULL, &compDesc); |
if (movieExporterComponent == NULL) |
return invalidComponentID; |
err = OpenAComponent(movieExporterComponent, &exporter); |
if (err != noErr) { |
if (err == componentNotThreadSafeErr) |
threadData->retry = true; |
goto bail; |
} |
err = MovieExportSetSettingsFromAtomContainer(exporter, threadData->exportSettings); |
if (err != noErr) { |
if (err == componentNotThreadSafeErr) |
threadData->retry = true; |
goto bail; |
} |
// set progress proc |
MovieExportSetProgressProc(exporter, gMovieProgressProcUPP, (long)threadData); |
if (threadData->cancelled) |
goto bail; |
// export the movie |
err = MovieExportToDataRef(exporter, threadData->dataRef, threadData->dataRefType, movie, NULL, 0, GetMovieDuration(movie)); |
if (err == componentNotThreadSafeErr) { |
// could not export on the current thread with only thread-safe components; retry on the main thread with any components |
threadData->retry = true; |
} |
bail: |
if (exporter != NULL) |
CloseComponent(exporter); |
if (movie != NULL) |
DisposeMovie(movie); |
return err; |
} |
static pascal OSErr movieExportProgressProc (Movie movie, short message, short operation, Fixed percentDone, long refCon) |
{ |
ThreadData *threadData = (ThreadData *)refCon; |
NSWindow *progressWindow; // the progress dialog sheet |
if (threadData == nil) return paramErr; |
progressWindow = [threadData->progressDialog progressPanel]; |
// if we're on the main thread, look for clicks on the Cancel button in the progress dialog box |
if (threadData->threadModelTag == USE_MAIN_THREAD) { |
// NOTE: we really ought to track the mouse from mouse-down to mouse-up; here we just look for |
// mouse-up events in side the Cancel button....you could do better! |
NSEvent *event = nil; |
event = [progressWindow nextEventMatchingMask:NSLeftMouseUpMask |
untilDate:[NSDate distantPast] |
inMode:NSDefaultRunLoopMode |
dequeue:YES]; |
if (event) { |
NSRect cancelButtonRect = [threadData->progressDialog cancelButtonRect]; |
if ([event window] == progressWindow) |
if (NSPointInRect([event locationInWindow], cancelButtonRect)) |
threadData->cancelled = YES; |
} |
} |
switch (message) { |
case movieProgressOpen: |
threadData->percentage = 0; |
break; |
case movieProgressUpdatePercent: |
// percentDone is a Fixed value between 0 and fixed1; scale it to an integer between 0 and 100 |
threadData->percentage = Fix2Long(FixMul(percentDone, Long2Fix(100))); |
// update the progress bar, if we're being called on the main thread |
if (threadData->threadModelTag == USE_MAIN_THREAD) { |
[threadData->progressDialog updateProgressDialog:nil]; |
} |
break; |
case movieProgressClose: |
// nothing here yet |
break; |
} |
if (threadData->cancelled) |
return userCanceledErr; |
else |
return noErr; |
} |
////////// |
// |
// worker thread routines |
// |
////////// |
// The workerActionRoutine is called on the worker thread to do the work. |
// It should not return until the work is done or cancelled. Make sure you |
// make only thread-safe calls in this routine! |
void workerActionRoutine (void *refcon, WorkerRequestRef request) |
{ |
ThreadData *threadData = nil; |
if (request == NULL) return; |
getWorkerRequestThreadData(request, (void **)&threadData); |
if (threadData != NULL) |
exportTheMovie(threadData); |
} |
// The workerCancelRoutine is called on the main thread to try to cancel work in progress. |
// If possible, it should just set a flag that will cause the action routine to exit early. |
void workerCancelRoutine (void *refcon, WorkerRequestRef request) |
{ |
ThreadData *threadData = nil; |
if (request == NULL) return; |
getWorkerRequestThreadData(request, (void **)&threadData); |
if (threadData != NULL) |
threadData->cancelled = true; |
} |
// The workerResponseMainThreadCallback is called on the main thread after the work is done or cancelled. |
// It can call wasWorkerRequestCancelled to find out which. After the results of the work are used, |
// it should release any memory associated with the request. |
// (This is a good place to release request-local memory because it's certain to be called for each request.) |
void workerResponseMainThreadCallback (void *refcon, WorkerRequestRef request) |
{ |
UInt32 doc = 0L; |
UInt32 prog = 0L; |
ThreadData *threadData = NULL; |
ProgressDialog *progDial = nil; |
MyDocument *docCtrlr = nil; |
if (request == NULL) return; |
getWorkerRequestThreadData(request, (void **)&threadData); |
if (threadData == NULL) |
return; |
getWorkerRequestDoc(request, &doc); |
docCtrlr = (MyDocument *)doc; |
if (wasWorkerRequestCancelled(request)) { |
// the request was cancelled; clean-up any thread-specific data |
[docCtrlr releaseThreadData:threadData]; |
} else { |
// the request completed, but we might still need to retry on the main thread |
getWorkerRequestProgressDialog(request, &prog); |
progDial = (ProgressDialog *)prog; |
if (threadData->retry) { |
// we need to retry the export on the main thread with any components |
threadData->retry = false; |
[[progDial statusField] setStringValue:@"Retrying on main thread with any components!"]; |
threadData->onlySafeComps = false; |
threadData->threadModelTag = USE_MAIN_THREAD; |
exportTheMovie(threadData); |
threadData->threadModelTag = USE_POSIX_THREAD; |
} |
[progDial setProgressTimer:nil]; |
[[progDial progressPanel] close]; |
[NSApp endSheet:[progDial progressPanel]]; |
// open the exported movie in a new window |
// TO BE SUPPLIED |
} |
releaseWorkerRequest(request); |
} |
static Boolean codecDoesTemporal( OSType codecType ) |
{ |
ComponentDescription cd = { compressorComponentType, 0, 0, codecInfoDoesTemporal, codecInfoDoesTemporal | cmpIsMissing }; |
cd.componentSubType = codecType; |
return ( 0 != FindNextComponent( 0, &cd ) ); |
} |
static Boolean isPotentiallySafeComponent( OSType inComponentType, OSType inComponentSubType ) |
{ |
Component comp = NULL; |
ComponentDescription cd = { inComponentType, 0, 0, 0, cmpIsMissing }; |
ComponentDescription compDesc; |
cd.componentSubType = inComponentSubType; |
comp = FindNextComponent( 0, &cd ); |
while ( comp != NULL ) { |
GetComponentInfo(comp, &compDesc, NULL, NULL, NULL); |
if ( compDesc.componentFlags & cmpThreadSafe ) |
return (true); |
comp = FindNextComponent( comp, &cd ); |
} |
return ( false ); |
} |
Copyright © 2006 Apple Computer, Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2006-01-03