MyDocument.m
/* |
File: MyDocument.m |
Abstract: MyDocument is a NSDocument subclass. Each document contains |
a movie playlist, which is simply a list of movies to be |
played in sequence. A PlaylistItem object is created for each |
movie in the play list. Each PlaylistItem contains a QTMovie |
object for the movie to be played. |
Version: 1.0 |
Disclaimer: IMPORTANT: This Apple software is supplied to you by |
Apple 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 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. |
Copyright (C) 2010 Apple Inc. All Rights Reserved. |
*/ |
#import "MyDocument.h" |
#import "PlaylistItem.h" |
#import <QTKit/QTKit.h> |
// private methods |
@interface MyDocument () |
- (void)setPlaylist:(NSMutableArray *)newPlaylist; |
- (void)handleLoadStateChanged; |
- (void)updateSelectedMovieIsPlaying; |
- (void)updateSelectedMovieCurrentTime; |
- (void)documentChanged; |
@end |
@implementation MyDocument |
- (void)dealloc |
{ |
[mSelectedPlaylistIndexes release]; |
[mPlaylist release]; |
[super dealloc]; |
} |
- (NSString *)windowNibName |
{ |
return @"MyDocument"; |
} |
- (void)windowControllerDidLoadNib:(NSWindowController *) aController |
{ |
[super windowControllerDidLoadNib:aController]; |
if (nil == [self playlist]) |
[self setPlaylist:[NSMutableArray array]]; |
// set the play button to not change its title when it's highlighted |
NSButtonCell *playButtonCell = (NSButtonCell *)[playButton cell]; |
if ([playButtonCell isKindOfClass:[NSButtonCell class]]) |
[playButtonCell setHighlightsBy:([playButtonCell highlightsBy] & ~NSContentsCellMask)]; |
} |
- (void)close |
{ |
// unselect everything in the playlist to release any open movies. |
[self setSelectedPlaylistIndexes:[NSIndexSet indexSet]]; |
[super close]; |
} |
// Creates and returns a data object that contains the contents of the document playlist |
- (NSData *)dataOfType:(NSString *)typeName error:(NSError **)outError |
{ |
return [NSKeyedArchiver archivedDataWithRootObject:[self playlist]]; |
if ( outError != NULL ) { |
*outError = [NSError errorWithDomain:NSOSStatusErrorDomain code:unimpErr userInfo:NULL]; |
} |
return nil; |
} |
// Sets the contents of this document by reading from media data in the playlist |
- (BOOL)readFromData:(NSData *)data ofType:(NSString *)typeName error:(NSError **)outError |
{ |
[self setPlaylist:[NSKeyedUnarchiver unarchiveObjectWithData:data]]; |
return YES; |
if ( outError != NULL ) { |
*outError = [NSError errorWithDomain:NSOSStatusErrorDomain code:unimpErr userInfo:NULL]; |
} |
return YES; |
} |
// Split View delegate method |
// This keeps the source list on the left from resizing when the window is resized |
- (BOOL)splitView:(NSSplitView *)splitView shouldAdjustSizeOfSubview:(NSView *)subview { |
if ([subview isKindOfClass:[NSScrollView class]]) return NO; |
else return YES; |
} |
#pragma mark PlayList |
@synthesize playlist = mPlaylist; |
- (void)setPlaylist:(NSMutableArray *)newPlaylist |
{ |
newPlaylist = [newPlaylist mutableCopy]; |
[mPlaylist release]; |
mPlaylist = newPlaylist; |
} |
- (void)documentChanged |
{ |
[self updateChangeCount:NSChangeDone]; |
} |
- (void)insertObject:(id)object inPlaylistAtIndex:(NSUInteger)index |
{ |
[mPlaylist insertObject:object atIndex:index]; |
[self documentChanged]; |
} |
- (void)removeObjectFromPlaylistAtIndex:(NSUInteger)index |
{ |
[mPlaylist removeObjectAtIndex:index]; |
[self documentChanged]; |
} |
@dynamic selectedPlaylistIndexes; |
- (NSIndexSet *)selectedPlaylistIndexes |
{ |
return [[mSelectedPlaylistIndexes copy] autorelease]; |
} |
- (void)setSelectedPlaylistIndexes:(NSIndexSet *)selectedPlaylistIndexes |
{ |
if (((selectedPlaylistIndexes == nil) != (mSelectedPlaylistIndexes == nil)) |
|| (mSelectedPlaylistIndexes && ![mSelectedPlaylistIndexes isEqualToIndexSet:selectedPlaylistIndexes])) |
{ |
selectedPlaylistIndexes = [selectedPlaylistIndexes copy]; |
[mSelectedPlaylistIndexes release]; |
mSelectedPlaylistIndexes = selectedPlaylistIndexes; |
// open a new movie corresponding to the changed selection |
NSUInteger selectedIndex = [mSelectedPlaylistIndexes firstIndex]; |
NSURL *selectedMovieURL = nil; |
if (selectedIndex != NSNotFound) |
{ |
selectedMovieURL = [[[self playlist] objectAtIndex:selectedIndex] URL]; |
} |
QTMovie *newSelectedMovie = nil; |
if (selectedMovieURL) |
{ |
if ([selectedMovieURL isEqual:[mSelectedMovie attributeForKey:QTMovieURLAttribute]]) |
{ |
newSelectedMovie = mSelectedMovie; |
} |
else |
{ |
NSError *error = nil; |
/* |
Opting into QuickTime X |
Specify the QTMovieOpenForPlaybackAttribute movie attribute to opt into the more efficient media |
capabilities provided in QuickTime X. This indicates whether a QTMovie object will be used only |
for playback and not for editing or exporting. |
*/ |
newSelectedMovie = [QTMovie movieWithAttributes:[NSDictionary dictionaryWithObjectsAndKeys: |
selectedMovieURL, QTMovieURLAttribute, |
[NSNumber numberWithBool:YES], QTMovieOpenForPlaybackAttribute, |
nil] error:&error]; |
if (!newSelectedMovie) |
[self presentError:error]; |
} |
} |
if (newSelectedMovie != mSelectedMovie) |
{ |
NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter]; |
if (mSelectedMovie) |
{ |
/* register for movie notfications: |
QTMovieLoadStateDidChangeNotification |
-Sent when the load state of a movie has changed. |
QTMovieDidEndNotification |
-Sent when the movie is “done” or at its end. |
QTMovieRateDidChangeNotification |
-Sent when the rate of a movie has changed. |
*/ |
[notificationCenter removeObserver:self name:QTMovieLoadStateDidChangeNotification object:mSelectedMovie]; |
[notificationCenter removeObserver:self name:QTMovieDidEndNotification object:mSelectedMovie]; |
[notificationCenter removeObserver:self name:QTMovieRateDidChangeNotification object:mSelectedMovie]; |
} |
[self setSelectedMovie:newSelectedMovie]; |
if (mSelectedMovie) |
{ |
[notificationCenter addObserver:self selector:@selector(movieLoadStateChanged:) name:QTMovieLoadStateDidChangeNotification object:mSelectedMovie]; |
[notificationCenter addObserver:self selector:@selector(movieDidEnd:) name:QTMovieDidEndNotification object:mSelectedMovie]; |
[notificationCenter addObserver:self selector:@selector(movieRateChanged:) name:QTMovieRateDidChangeNotification object:mSelectedMovie]; |
if (!mCheckMovieTimeTimer) |
mCheckMovieTimeTimer = [[NSTimer scheduledTimerWithTimeInterval:0.25 target:self selector:@selector(checkMovieTime:) userInfo:nil repeats:YES] retain]; |
} |
else |
{ |
[mCheckMovieTimeTimer invalidate]; |
[mCheckMovieTimeTimer release]; |
mCheckMovieTimeTimer = nil; |
} |
// reset selectedMovieDuration |
// this will be updated to the new movie's duration in handleLoadStateChanged once the |
// movie's load state is QTMovieLoadStateLoaded |
[self setSelectedMovieDuration:0.0]; |
[self handleLoadStateChanged]; |
[self updateSelectedMovieCurrentTime]; |
[self updateSelectedMovieIsPlaying]; |
[mSelectedMovie autoplay]; |
} |
} |
} |
// display the open panel and allow the user to choose movies to add to the playlist |
- (IBAction)addPlaylistItem:(id)sender |
{ |
NSOpenPanel *openPanel = [NSOpenPanel openPanel]; |
[openPanel setAllowsMultipleSelection:YES]; |
[openPanel setAllowedFileTypes:[QTMovie movieFileTypes:QTIncludeCommonTypes]]; |
[openPanel beginSheetModalForWindow:[self windowForSheet] completionHandler:^(NSInteger result) { |
if (NSFileHandlingPanelOKButton == result) |
{ |
for (NSURL *URL in [openPanel URLs]) |
{ |
[self insertObject:[PlaylistItem playlistItemWithURL:URL] inPlaylistAtIndex:[[self playlist] count]]; |
} |
} |
}]; |
} |
#pragma mark Movie |
/* |
Because opening a movie file or URL may involve reading and processing large amounts of movie |
data, QTKit may take a non-negligible amount of time to make a QTMovie object ready for |
inspection and playback. Accordingly, you need to pay attention to the movie’s load states when |
opening a movie file or URL. These are the defined movie load states: |
QTMovieLoadStateError = -1L, // an error occurred while loading the movie |
QTMovieLoadStateLoading = 1000, // the movie is loading |
QTMovieLoadStateLoaded = 2000, // the movie atom has loaded; it's safe to query movie properties |
QTMovieLoadStatePlayable = 10000, // the movie has loaded enough media data to begin playing |
QTMovieLoadStatePlaythroughOK = 20000, //the movie has loaded enough media data to play through to end |
QTMovieLoadStateComplete = 100000L // the movie has loaded completely |
*/ |
- (void)handleLoadStateChanged |
{ |
QTMovie *movie = [self selectedMovie]; |
if (movie) |
{ |
NSInteger loadState = [[movie attributeForKey:QTMovieLoadStateAttribute] longValue]; |
if (loadState == QTMovieLoadStateError) |
{ |
// an error occurred while loading the movie |
[self presentError:[movie attributeForKey:QTMovieLoadStateErrorAttribute]]; |
} |
if (loadState >= QTMovieLoadStateLoaded) |
{ |
// now that the movie is loaded, it can return information about its structure |
// at this point, it is safe to get the movie's duration |
QTTime duration = [movie duration]; |
NSTimeInterval timeInterval; |
QTGetTimeInterval(duration, &timeInterval); |
[self setSelectedMovieDuration:timeInterval]; |
} |
} |
} |
// This method called when the load state of a movie has changed |
- (void)movieLoadStateChanged:(NSNotification *)notification |
{ |
[self handleLoadStateChanged]; |
} |
@synthesize selectedMovie = mSelectedMovie; |
@dynamic selectedMovieIsPlaying; |
- (BOOL)selectedMovieIsPlaying |
{ |
return mSelectedMovieIsPlaying; |
} |
- (void)setSelectedMovieIsPlaying:(BOOL)selectedMovieIsPlaying |
{ |
// mSelectedMovieIsPlaying will be automatically updated by the QTMovieRateDidChangeNotification |
mSelectedMovieIsPlaying = selectedMovieIsPlaying; |
[[self selectedMovie] setRate:(selectedMovieIsPlaying ? 1.0f : 0.0f)]; |
} |
- (void)updateSelectedMovieIsPlaying |
{ |
QTMovie *movie = [self selectedMovie]; |
[self setSelectedMovieIsPlaying:(movie && [movie rate] != 0.0f)]; |
} |
@synthesize selectedMovieDuration = mSelectedMovieDuration; |
@dynamic selectedMovieCurrentTime; |
- (NSTimeInterval)selectedMovieCurrentTime |
{ |
return mSelectedMovieCurrentTime; |
} |
- (void)setSelectedMovieCurrentTime:(NSTimeInterval)selectedMovieCurrentTime |
{ |
if (mSelectedMovieCurrentTime != selectedMovieCurrentTime) |
{ |
// Sets the movie’s current time setting to time. |
mSelectedMovieCurrentTime = selectedMovieCurrentTime; |
[[self selectedMovie] setCurrentTime:QTMakeTimeWithTimeInterval(mSelectedMovieCurrentTime)]; |
} |
} |
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)theKey |
{ |
BOOL automatic = NO; |
// specify that we support manual key-value observer notification |
// for the selectedMovieCurrentTime property (via the |
// updateSelectedMovieCurrentTime: method) |
if ([theKey isEqualToString:@"selectedMovieCurrentTime"]) |
{ |
automatic = NO; |
} |
else |
{ |
automatic = [super automaticallyNotifiesObserversForKey:theKey]; |
} |
return automatic; |
} |
- (void)updateSelectedMovieCurrentTime |
{ |
NSTimeInterval selectedMovieCurrentTime = 0.0; |
QTMovie *movie = [self selectedMovie]; |
if (movie) |
{ |
QTGetTimeInterval([movie currentTime], &selectedMovieCurrentTime); |
} |
// set mSelectedMovieCurrentTime directly rather than calling setSelectedMovieCurrentTime: |
// since that will also call -[QTMovie setCurrentTime:], which will affect movie playback if |
// called while the movie is playing |
[self willChangeValueForKey:@"selectedMovieCurrentTime"]; |
mSelectedMovieCurrentTime = selectedMovieCurrentTime; |
[self didChangeValueForKey:@"selectedMovieCurrentTime"]; |
} |
// Sent when the rate of a movie has changed (QTMovieRateDidChangeNotification) |
- (void)movieRateChanged:(NSNotification *)notification |
{ |
[self updateSelectedMovieIsPlaying]; |
} |
// timer routine called periodically to update the selected movie current time |
- (void)checkMovieTime:(NSTimer *)timer |
{ |
[self updateSelectedMovieCurrentTime]; |
} |
// called for QTMovieDidEndNotification when the movie is “done” or at its end. |
- (void)movieDidEnd:(NSNotification *)note |
{ |
NSUInteger selectedIndex = [[self selectedPlaylistIndexes] firstIndex]; |
NSUInteger playlistCount = [[self playlist] count]; |
if ((selectedIndex != NSNotFound) && (++selectedIndex < playlistCount)) |
[self setSelectedPlaylistIndexes:[[[NSIndexSet alloc] initWithIndex:selectedIndex] autorelease]]; |
} |
#pragma mark Editing |
// removes an item from the playlist |
- (IBAction)cut:(id)sender |
{ |
[self copy:self]; |
NSUInteger selectedIndex = [[self selectedPlaylistIndexes] firstIndex]; |
if (selectedIndex != NSNotFound) |
[self removeObjectFromPlaylistAtIndex:selectedIndex]; |
} |
// copy a movie item from the playlist |
- (IBAction)copy:(id)sender |
{ |
NSIndexSet *selectedPlaylistIndexes = [self selectedPlaylistIndexes]; |
if (selectedPlaylistIndexes && ([selectedPlaylistIndexes count] > 0)) |
{ |
NSArray *selectedPlaylistItems = [[self playlist] objectsAtIndexes:selectedPlaylistIndexes]; |
// get the general pasteboard |
NSPasteboard *pboard = [NSPasteboard generalPasteboard]; |
// clear the contents |
[pboard clearContents]; |
// write the objects |
[pboard writeObjects:selectedPlaylistItems]; |
} |
} |
// paste an item into the playlist |
- (IBAction)paste:(id)sender |
{ |
NSPasteboard *pboard = [NSPasteboard generalPasteboard]; |
NSArray *desiredClasses = [NSArray arrayWithObject:[PlaylistItem class]]; |
NSArray *readPlaylistItems = [pboard readObjectsForClasses:desiredClasses options:nil]; |
for (PlaylistItem *playlistItem in readPlaylistItems) { |
[self insertObject:playlistItem inPlaylistAtIndex:[[self playlist] count]]; |
} |
} |
- (BOOL)validateMenuItem:(NSMenuItem *)item |
{ |
BOOL isValid = NO; |
SEL itemAction = [item action]; |
if (itemAction == @selector(paste:)) |
{ |
NSPasteboard *pboard = [NSPasteboard generalPasteboard]; |
NSArray *desiredClasses = [NSArray arrayWithObject:[PlaylistItem class]]; |
isValid = [pboard canReadObjectForClasses:desiredClasses options:nil]; |
} |
else if ((itemAction == @selector(cut:)) || (itemAction == @selector(copy:))) |
{ |
NSIndexSet *selectedPlaylistIndexes = [self selectedPlaylistIndexes]; |
isValid = ((nil != selectedPlaylistIndexes) && ([selectedPlaylistIndexes count] > 0)); |
} |
else |
{ |
isValid = [super validateMenuItem:item]; |
} |
return isValid; |
} |
@end |
Copyright © 2011 Apple Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2011-09-12