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 |
Abstract: A NSDocument subclass that implements a fullscreen movie player. |
Version: 2.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) 2009 Apple Inc. All Rights Reserved. |
*/ |
#import "MyDocument.h" |
#import "FullScreenWindow.h" |
#import "FullScreenOverlayWindowController.h" |
#import <QTKit/QTKit.h> |
@interface MyDocument () |
- (void)handleLoadStateChanged; |
- (void)updateMovieRate; |
@property(getter=isFullscreen) BOOL fullscreen; |
- (void)repositionOverlayWindow; |
@end |
@implementation MyDocument |
- (void)dealloc |
{ |
// Note that we must not be in fullscreen mode when entering this method, for a variety of reasons |
// We take care of that in -close below |
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; |
[nc removeObserver:self name:QTMovieLoadStateDidChangeNotification object:mMovie]; |
[nc removeObserver:self name:QTMovieRateDidChangeNotification object:mMovie]; |
[mMovie release]; |
[mFullscreenWindowController release]; |
[mFullscreenOverlayWindowController release]; |
[mFullscreenScreen release]; |
[super dealloc]; |
} |
#pragma mark NSDocument overrides |
- (void)close |
{ |
[self setFullscreen:NO]; |
[super close]; |
} |
// Initially, we have only one window. This may change if we enter fullscreen. |
- (NSString *)windowNibName |
{ |
return @"MyDocument"; |
} |
- (void)windowControllerDidLoadNib:(NSWindowController *)windowController |
{ |
// set the play button to not change its title when it's highlighted |
NSButtonCell *playPauseButtonCell = (NSButtonCell *)[playPauseButton cell]; |
if ([playPauseButtonCell isKindOfClass:[NSButtonCell class]]) |
[playPauseButtonCell setHighlightsBy:([playPauseButtonCell highlightsBy] & ~NSContentsCellMask)]; |
} |
- (BOOL)readFromURL:(NSURL *)absoluteURL ofType:(NSString *)typeName error:(NSError **)outError |
{ |
NSDictionary *attrs = [NSDictionary dictionaryWithObjectsAndKeys: |
absoluteURL, QTMovieURLAttribute, |
/* Set the QTMovieOpenForPlaybackAttribute attribute to indicate that you intend to use movie playback methods (such as -play or -stop, |
or corresponding movie view methods such as -play: or -pause:) to control the movie, but do not intend to use other methods that edit, |
export, or in any way modify the movie. Knowing that you need playback services only may allow QTMovie to use more efficient code paths |
for some media files. */ |
[NSNumber numberWithBool:YES], QTMovieOpenForPlaybackAttribute , |
/* Set the QTMovieOpenAsyncRequiredAttribute attribute to indicate that all operations necessary to open the movie file (or other container) |
and create a valid QTMovie object must occur asynchronously. That is to say, the methods +movieWithAttributes:error: and -initWithAttributes:error: |
must return almost immediately, performing any lengthy operations on another thread. Your application can monitor the movie load state to |
determine the progress of those operations.*/ |
[NSNumber numberWithBool:YES], QTMovieOpenAsyncRequiredAttribute, |
nil]; |
[self willChangeValueForKey:@"movie"]; |
mMovie = [[QTMovie alloc] initWithAttributes:attrs error:outError]; |
[self didChangeValueForKey:@"movie"]; |
if (mMovie) { |
// Register to receive movie load state and rate change notifications |
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; |
[nc addObserver:self |
selector:@selector(movieLoadStateChanged:) |
name:QTMovieLoadStateDidChangeNotification |
object:mMovie]; |
[nc addObserver:self |
selector:@selector(movieRateChanged:) |
name:QTMovieRateDidChangeNotification |
object:mMovie]; |
// Check movie load state immediately in case the movie is available already |
[self handleLoadStateChanged]; |
} |
return (mMovie != nil); |
} |
- (NSWindow *)windowForSheet |
{ |
return [self isFullscreen] ? [mFullscreenWindowController window] : [super windowForSheet]; |
} |
#pragma mark Movie |
@synthesize movie = mMovie; |
- (void)movieLoadStateChanged:(NSNotification *)notification |
{ |
[self handleLoadStateChanged]; |
} |
- (void)movieRateChanged:(NSNotification *)notification |
{ |
[self updateMovieRate]; |
} |
- (void)handleLoadStateChanged |
{ |
QTMovie *movie = [self movie]; |
QTMovieLoadState loadState = [[movie attributeForKey:QTMovieLoadStateAttribute] integerValue]; |
/* |
The QuickTime movie load states are defined as follows (see QTMovie.h): |
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 the end |
QTMovieLoadStateComplete = 100000L // the movie has loaded completely |
*/ |
if (loadState == QTMovieLoadStateError) { |
// What goes here is app-specific: |
// You can query QTMovieLoadStateErrorAttribute to get the error code, if it matters |
// If the movie failed to open because it was instantiated with QTMovieOpenAsyncRequiredAttribute and it cannot be opened asynchronously, you can try again without that attribute if you require a movie object. |
// You might also need to undo some operations done in the other state handlers |
// In this case, we just put up an error dialog and close ourselves (in -loadStateErrorSheetDidEnd:) |
NSError *err = [movie attributeForKey:QTMovieLoadStateErrorAttribute]; |
[self presentError:err modalForWindow:[self windowForSheet] delegate:self didPresentSelector:@selector(loadStateErrorSheetDidEnd:returnCode:contextInfo:) contextInfo:NULL]; |
} |
if (loadState >= QTMovieLoadStateLoaded) { |
// Can query properties here |
// For instance, if you need to size a QTMovieView based on the movie's natural size, you can do so now |
QTTime duration = [movie duration]; |
[durationTextField setStringValue:QTStringFromTime(duration)]; |
} |
if (loadState >= QTMovieLoadStatePlayable) { |
// Can start movie playing here, if appropriate |
[movie setRate:mMovieRate]; |
} |
} |
- (void)loadStateErrorSheetDidEnd:(NSAlert *)alert returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo |
{ |
[self close]; |
} |
@dynamic movieIsPlaying; |
- (BOOL)movieIsPlaying |
{ |
return (mMovieRate != 0.f); |
} |
- (void)setMovieIsPlaying:(BOOL)movieIsPlaying |
{ |
QTMovie *movie = [self movie]; |
// Cache the desired rate in case the movie is not yet playable |
// If that is the case, we update the movie's rate in -handleLoadStateChanged |
mMovieRate = movieIsPlaying ? 1.f : 0.f; |
QTMovieLoadState loadState = [[movie attributeForKey:QTMovieLoadStateAttribute] integerValue]; |
if (loadState >= QTMovieLoadStatePlayable) |
[movie setRate:mMovieRate]; |
} |
+ (BOOL)automaticallyNotifiesObserversForMovieIsPlaying |
{ |
// setMovieIsPlaying: does not need to automatically notify key value observers because |
// the value returned by movieIsPlaying is actually updated in updateMovieRate |
return NO; |
} |
- (void)updateMovieRate |
{ |
QTMovie *movie = [self movie]; |
[self willChangeValueForKey:@"movieIsPlaying"]; |
mMovieRate = [movie rate]; |
[self didChangeValueForKey:@"movieIsPlaying"]; |
} |
#pragma mark Fullscreen |
@synthesize fullscreen = mFullscreen; |
- (void)setFullscreen:(BOOL)isFullscreen |
{ |
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; |
if (isFullscreen == mFullscreen) |
return; |
mFullscreen = isFullscreen; |
if (mFullscreen) { |
// Since we want to ensure that in fullscreen mode we always cover exactly the area of a single screen, register for screen config notifications |
[nc addObserver:self selector:@selector(screenParametersDidChange:) name:NSApplicationDidChangeScreenParametersNotification object:NSApp]; |
mFullscreenScreen = [[movieWindow screen] retain]; |
NSRect screenRect = [mFullscreenScreen frame]; |
// Create a window to cover the screen |
FullScreenWindow *fullscreenWindow = [[[FullScreenWindow alloc] initWithContentRect:screenRect |
styleMask:NSBorderlessWindowMask |
backing:NSBackingStoreBuffered |
defer:NO] |
autorelease]; |
[fullscreenWindow setBackgroundColor:[NSColor blackColor]]; |
// Create window controllers for the fullscreen window and control overlay window |
mFullscreenWindowController = [[NSWindowController alloc] initWithWindow:fullscreenWindow]; |
[self addWindowController:mFullscreenWindowController]; |
mFullscreenOverlayWindowController = [[FullScreenOverlayWindowController alloc] init]; |
[self addWindowController:mFullscreenOverlayWindowController]; |
[fullscreenWindow addChildWindow:[mFullscreenOverlayWindowController window] ordered:NSWindowAbove]; |
[self repositionOverlayWindow]; |
// Move the movie view into fullscreen |
[[movieView retain] autorelease]; // in case the superview has the only retain |
[movieView removeFromSuperviewWithoutNeedingDisplay]; |
[[fullscreenWindow contentView] addSubview:movieView]; |
mSavedMovieViewRect = [movieView frame]; // remember the current rect/size |
[movieView setFrame:[[fullscreenWindow contentView] bounds]]; |
// Bring the fullscreen and overlay windows to the front |
[movieWindow orderOut:self]; |
[mFullscreenOverlayWindowController showWindow:self]; |
[mFullscreenWindowController showWindow:self]; |
// Hide the dock and menu bar, saving previous presentation options |
mSavedPresentationOptions = [NSApp presentationOptions]; |
[NSApp setPresentationOptions:(NSApplicationPresentationAutoHideDock | NSApplicationPresentationAutoHideMenuBar)]; |
} else { |
[nc removeObserver:self name:NSApplicationDidChangeScreenParametersNotification object:NSApp]; |
[mFullscreenScreen release]; |
mFullscreenScreen = nil; |
// Move movie view back to the main document window |
[[movieView retain] autorelease]; // in case the superview has the only retain |
[movieView removeFromSuperviewWithoutNeedingDisplay]; |
[movieView setFrame:mSavedMovieViewRect]; |
[[movieWindow contentView] addSubview:movieView]; |
// Get rid of the fullscreen windows |
[mFullscreenWindowController close]; |
[mFullscreenWindowController release]; |
mFullscreenWindowController = nil; |
[mFullscreenOverlayWindowController close]; |
[mFullscreenOverlayWindowController release]; |
mFullscreenOverlayWindowController = nil; |
// Bring the main movie window back to the front |
[movieWindow makeKeyAndOrderFront:self]; |
// Restore previous presentation options |
[NSApp setPresentationOptions:mSavedPresentationOptions]; |
} |
} |
- (IBAction)toggleFullscreen:(id)sender |
{ |
[self setFullscreen:![self isFullscreen]]; |
} |
- (void)screenParametersDidChange:(NSNotification *)notification |
{ |
if ([[NSScreen screens] containsObject:mFullscreenScreen]) { |
// The screen is still there, but may have changed resolution |
NSWindow *fullscreenWindow = [mFullscreenWindowController window]; |
NSRect screenRect = [mFullscreenScreen frame]; |
if (!NSEqualRects([fullscreenWindow frame], screenRect)) { |
[fullscreenWindow setFrame:screenRect display:YES]; |
[self repositionOverlayWindow]; |
} |
} else { |
// That other screen is gone now. Our best bet is just to break out of fullscreen mode. |
[self setFullscreen:NO]; |
} |
} |
- (void)repositionOverlayWindow |
{ |
NSRect fullscreenRect = [[mFullscreenWindowController window] frame]; |
NSWindow *overlayWindow = [mFullscreenOverlayWindowController window]; |
NSRect overlayRect = [overlayWindow frame]; |
[overlayWindow setFrameOrigin:NSMakePoint(NSMinX(fullscreenRect) + ((0.5f * NSWidth(fullscreenRect)) - (0.5f * NSWidth(overlayRect))), 0.15f * NSHeight(fullscreenRect))]; |
} |
@end |
Copyright © 2011 Apple Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2011-09-12