1/* 2 * Copyright (C) 2009 Apple Inc. All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions 6 * are met: 7 * 1. Redistributions of source code must retain the above copyright 8 * notice, this list of conditions and the following disclaimer. 9 * 2. Redistributions in binary form must reproduce the above copyright 10 * notice, this list of conditions and the following disclaimer in the 11 * documentation and/or other materials provided with the distribution. 12 * 13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' 14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS 17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF 23 * THE POSSIBILITY OF SUCH DAMAGE. 24 */ 25 26#if ENABLE(VIDEO) 27 28#import "WebVideoFullscreenController.h" 29 30#import "WebTypesInternal.h" 31#import "WebVideoFullscreenHUDWindowController.h" 32#import "WebWindowAnimation.h" 33#import <QTKit/QTKit.h> 34#import <WebCore/HTMLMediaElement.h> 35#import <WebCore/SoftLinking.h> 36#import <objc/objc-runtime.h> 37#import <wtf/UnusedParam.h> 38 39SOFT_LINK_FRAMEWORK(QTKit) 40SOFT_LINK_CLASS(QTKit, QTMovieView) 41 42SOFT_LINK_POINTER(QTKit, QTMovieRateDidChangeNotification, NSString *) 43 44#define QTMovieRateDidChangeNotification getQTMovieRateDidChangeNotification() 45 46@interface WebVideoFullscreenWindow : NSWindow 47#if !defined(BUILDING_ON_LEOPARD) && !defined(BUILDING_ON_TIGER) 48<NSAnimationDelegate> 49#endif 50{ 51 SEL _controllerActionOnAnimationEnd; 52 WebWindowScaleAnimation *_fullscreenAnimation; // (retain) 53} 54- (void)animateFromRect:(NSRect)startRect toRect:(NSRect)endRect withSubAnimation:(NSAnimation *)subAnimation controllerAction:(SEL)controllerAction; 55@end 56 57@interface WebVideoFullscreenController(HUDWindowControllerDelegate) <WebVideoFullscreenHUDWindowControllerDelegate> 58@end 59 60@implementation WebVideoFullscreenController 61- (id)init 62{ 63 // Do not defer window creation, to make sure -windowNumber is created (needed by WebWindowScaleAnimation). 64 NSWindow *window = [[WebVideoFullscreenWindow alloc] initWithContentRect:NSZeroRect styleMask:NSBorderlessWindowMask backing:NSBackingStoreBuffered defer:NO]; 65 self = [super initWithWindow:window]; 66 [window release]; 67 if (!self) 68 return nil; 69 [self windowDidLoad]; 70 return self; 71 72} 73- (void)dealloc 74{ 75 ASSERT(!_backgroundFullscreenWindow); 76 ASSERT(!_fadeAnimation); 77 [[NSNotificationCenter defaultCenter] removeObserver:self]; 78 [super dealloc]; 79} 80 81- (WebVideoFullscreenWindow *)fullscreenWindow 82{ 83 return (WebVideoFullscreenWindow *)[super window]; 84} 85 86- (void)windowDidLoad 87{ 88 WebVideoFullscreenWindow *window = [self fullscreenWindow]; 89 QTMovieView *view = [[getQTMovieViewClass() alloc] init]; 90 [view setFillColor:[NSColor clearColor]]; 91 [window setContentView:view]; 92 [view setControllerVisible:NO]; 93 [view setPreservesAspectRatio:YES]; 94 if (_mediaElement) 95 [view setMovie:_mediaElement->platformMedia().qtMovie]; 96 [window setHasShadow:YES]; // This is nicer with a shadow. 97 [window setLevel:NSPopUpMenuWindowLevel-1]; 98 [view release]; 99} 100 101- (WebCore::HTMLMediaElement*)mediaElement; 102{ 103 return _mediaElement.get(); 104} 105 106- (void)setMediaElement:(WebCore::HTMLMediaElement*)mediaElement; 107{ 108 _mediaElement = mediaElement; 109 if ([self isWindowLoaded]) { 110 QTMovieView *movieView = (QTMovieView *)[[self fullscreenWindow] contentView]; 111 QTMovie *movie = _mediaElement->platformMedia().qtMovie; 112 113 ASSERT(movieView && [movieView isKindOfClass:[getQTMovieViewClass() class]]); 114 ASSERT(movie); 115 [movieView setMovie:movie]; 116 [[NSNotificationCenter defaultCenter] addObserver:self 117 selector:@selector(rateChanged:) 118 name:QTMovieRateDidChangeNotification 119 object:movie]; 120 } 121} 122 123- (id <WebVideoFullscreenControllerDelegate>)delegate 124{ 125 return _delegate; 126} 127 128- (void)setDelegate:(id <WebVideoFullscreenControllerDelegate>)delegate; 129{ 130 _delegate = delegate; 131} 132 133- (CGFloat)clearFadeAnimation 134{ 135 [_fadeAnimation stopAnimation]; 136 CGFloat previousAlpha = [_fadeAnimation currentAlpha]; 137 [_fadeAnimation setWindow:nil]; 138 [_fadeAnimation release]; 139 _fadeAnimation = nil; 140 return previousAlpha; 141} 142 143- (void)windowDidExitFullscreen 144{ 145 [self clearFadeAnimation]; 146 [[self window] close]; 147 [self setWindow:nil]; 148 SetSystemUIMode(_savedUIMode, _savedUIOptions); 149 [_hudController setDelegate:nil]; 150 [_hudController release]; 151 _hudController = nil; 152 [_backgroundFullscreenWindow close]; 153 [_backgroundFullscreenWindow release]; 154 _backgroundFullscreenWindow = nil; 155 156 [self autorelease]; // Associated -retain is in -exitFullscreen. 157 _isEndingFullscreen = NO; 158} 159 160- (void)windowDidEnterFullscreen 161{ 162 [self clearFadeAnimation]; 163 164 ASSERT(!_hudController); 165 _hudController = [[WebVideoFullscreenHUDWindowController alloc] init]; 166 [_hudController setDelegate:self]; 167 168 GetSystemUIMode(&_savedUIMode, &_savedUIOptions); 169 SetSystemUIMode(kUIModeAllSuppressed , 0); 170 [NSCursor setHiddenUntilMouseMoves:YES]; 171 172 // Give the HUD keyboard focus initially 173 [_hudController fadeWindowIn]; 174} 175 176- (NSRect)mediaElementRect 177{ 178 return _mediaElement->screenRect(); 179} 180 181#pragma mark - 182#pragma mark Exposed Interface 183 184static void constrainFrameToRatioOfFrame(NSRect *frameToConstrain, const NSRect *frame) 185{ 186 // Keep a constrained aspect ratio for the destination window 187 double originalRatio = frame->size.width / frame->size.height; 188 double newRatio = frameToConstrain->size.width / frameToConstrain->size.height; 189 if (newRatio > originalRatio) { 190 double newWidth = originalRatio * frameToConstrain->size.height; 191 double diff = frameToConstrain->size.width - newWidth; 192 frameToConstrain->size.width = newWidth; 193 frameToConstrain->origin.x += diff / 2; 194 } else { 195 double newHeight = frameToConstrain->size.width / originalRatio; 196 double diff = frameToConstrain->size.height - newHeight; 197 frameToConstrain->size.height = newHeight; 198 frameToConstrain->origin.y += diff / 2; 199 } 200} 201 202static NSWindow *createBackgroundFullscreenWindow(NSRect frame, int level) 203{ 204 NSWindow *window = [[NSWindow alloc] initWithContentRect:frame styleMask:NSBorderlessWindowMask backing:NSBackingStoreBuffered defer:NO]; 205 [window setOpaque:YES]; 206 [window setBackgroundColor:[NSColor blackColor]]; 207 [window setLevel:level]; 208 [window setHidesOnDeactivate:YES]; 209 [window setReleasedWhenClosed:NO]; 210 return window; 211} 212 213- (void)setupFadeAnimationIfNeededAndFadeIn:(BOOL)fadeIn 214{ 215 CGFloat initialAlpha = fadeIn ? 0 : 1; 216 if (_fadeAnimation) { 217 // Make sure we support queuing animation if the previous one isn't over yet 218 initialAlpha = [self clearFadeAnimation]; 219 } 220 if (!_forceDisableAnimation) 221 _fadeAnimation = [[WebWindowFadeAnimation alloc] initWithDuration:0.2 window:_backgroundFullscreenWindow initialAlpha:initialAlpha finalAlpha:fadeIn ? 1 : 0]; 222} 223 224- (void)enterFullscreen:(NSScreen *)screen; 225{ 226 if (!screen) 227 screen = [NSScreen mainScreen]; 228 229 NSRect frame = [self mediaElementRect]; 230 NSRect endFrame = [screen frame]; 231 constrainFrameToRatioOfFrame(&endFrame, &frame); 232 233 // Create a black window if needed 234 if (!_backgroundFullscreenWindow) 235 _backgroundFullscreenWindow = createBackgroundFullscreenWindow([screen frame], [[self window] level]-1); 236 else 237 [_backgroundFullscreenWindow setFrame:[screen frame] display:NO]; 238 239 [self setupFadeAnimationIfNeededAndFadeIn:YES]; 240 if (_forceDisableAnimation) { 241 // This will disable scale animation 242 frame = NSZeroRect; 243 } 244 [[self fullscreenWindow] animateFromRect:frame toRect:endFrame withSubAnimation:_fadeAnimation controllerAction:@selector(windowDidEnterFullscreen)]; 245 246 [_backgroundFullscreenWindow orderWindow:NSWindowBelow relativeTo:[[self fullscreenWindow] windowNumber]]; 247} 248 249- (void)exitFullscreen 250{ 251 if (_isEndingFullscreen) 252 return; 253 _isEndingFullscreen = YES; 254 [_hudController closeWindow]; 255 256 NSRect endFrame = [self mediaElementRect]; 257 258 [self setupFadeAnimationIfNeededAndFadeIn:NO]; 259 if (_forceDisableAnimation) { 260 // This will disable scale animation 261 endFrame = NSZeroRect; 262 } 263 264 // We have to retain ourselves because we want to be alive for the end of the animation. 265 // If our owner releases us we could crash if this is not the case. 266 // Balanced in windowDidExitFullscreen 267 [self retain]; 268 269 [[self fullscreenWindow] animateFromRect:[[self window] frame] toRect:endFrame withSubAnimation:_fadeAnimation controllerAction:@selector(windowDidExitFullscreen)]; 270} 271 272#pragma mark - 273#pragma mark Window callback 274 275- (void)_requestExit 276{ 277 if (_mediaElement) 278 _mediaElement->exitFullscreen(); 279 _forceDisableAnimation = NO; 280} 281 282- (void)requestExitFullscreenWithAnimation:(BOOL)animation 283{ 284 if (_isEndingFullscreen) 285 return; 286 287 _forceDisableAnimation = !animation; 288 [self performSelector:@selector(_requestExit) withObject:nil afterDelay:0]; 289 290} 291 292- (void)requestExitFullscreen 293{ 294 [self requestExitFullscreenWithAnimation:YES]; 295} 296 297- (void)fadeHUDIn 298{ 299 [_hudController fadeWindowIn]; 300} 301 302#pragma mark - 303#pragma mark QTMovie callbacks 304 305- (void)rateChanged:(NSNotification *)unusedNotification 306{ 307 UNUSED_PARAM(unusedNotification); 308 [_hudController updateRate]; 309} 310 311@end 312 313@implementation WebVideoFullscreenWindow 314 315- (id)initWithContentRect:(NSRect)contentRect styleMask:(NSUInteger)aStyle backing:(NSBackingStoreType)bufferingType defer:(BOOL)flag 316{ 317 UNUSED_PARAM(aStyle); 318 self = [super initWithContentRect:contentRect styleMask:NSBorderlessWindowMask backing:bufferingType defer:flag]; 319 if (!self) 320 return nil; 321 [self setOpaque:NO]; 322 [self setBackgroundColor:[NSColor clearColor]]; 323 [self setHidesOnDeactivate:YES]; 324 [self setIgnoresMouseEvents:NO]; 325 [self setAcceptsMouseMovedEvents:YES]; 326 return self; 327} 328 329- (void)dealloc 330{ 331 ASSERT(!_fullscreenAnimation); 332 [super dealloc]; 333} 334 335- (BOOL)resignFirstResponder 336{ 337 return NO; 338} 339 340- (BOOL)canBecomeKeyWindow 341{ 342 return NO; 343} 344 345- (void)mouseDown:(NSEvent *)theEvent 346{ 347 UNUSED_PARAM(theEvent); 348} 349 350- (void)cancelOperation:(id)sender 351{ 352 UNUSED_PARAM(sender); 353 [[self windowController] requestExitFullscreen]; 354} 355 356- (void)animatedResizeDidEnd 357{ 358 // Call our windowController. 359 if (_controllerActionOnAnimationEnd) 360 [[self windowController] performSelector:_controllerActionOnAnimationEnd]; 361 _controllerActionOnAnimationEnd = NULL; 362} 363 364// 365// This function will animate a change of frame rectangle 366// We support queuing animation, that means that we'll correctly 367// interrupt the running animation, and queue the next one. 368// 369- (void)animateFromRect:(NSRect)startRect toRect:(NSRect)endRect withSubAnimation:(NSAnimation *)subAnimation controllerAction:(SEL)controllerAction 370{ 371 _controllerActionOnAnimationEnd = controllerAction; 372 373 BOOL wasAnimating = NO; 374 if (_fullscreenAnimation) { 375 wasAnimating = YES; 376 377 // Interrupt any running animation. 378 [_fullscreenAnimation stopAnimation]; 379 380 // Save the current rect to ensure a smooth transition. 381 startRect = [_fullscreenAnimation currentFrame]; 382 [_fullscreenAnimation release]; 383 _fullscreenAnimation = nil; 384 } 385 386 if (NSIsEmptyRect(startRect) || NSIsEmptyRect(endRect)) { 387 // Fakely end the subanimation. 388 [subAnimation setCurrentProgress:1.0]; 389 // And remove the weak link to the window. 390 [subAnimation stopAnimation]; 391 392 [self setFrame:endRect display:NO]; 393 [self makeKeyAndOrderFront:self]; 394 [self animatedResizeDidEnd]; 395 return; 396 } 397 398 if (!wasAnimating) { 399 // We'll downscale the window during the animation based on the higher resolution rect 400 BOOL higherResolutionIsEndRect = startRect.size.width < endRect.size.width && startRect.size.height < endRect.size.height; 401 [self setFrame:higherResolutionIsEndRect ? endRect : startRect display:NO]; 402 } 403 404 ASSERT(!_fullscreenAnimation); 405 _fullscreenAnimation = [[WebWindowScaleAnimation alloc] initWithHintedDuration:0.2 window:self initalFrame:startRect finalFrame:endRect]; 406 [_fullscreenAnimation setSubAnimation:subAnimation]; 407 [_fullscreenAnimation setDelegate:self]; 408 409 // Make sure the animation has scaled the window before showing it. 410 [_fullscreenAnimation setCurrentProgress:0]; 411 [self makeKeyAndOrderFront:self]; 412 413 [_fullscreenAnimation startAnimation]; 414} 415 416- (void)animationDidEnd:(NSAnimation *)animation 417{ 418#if !defined(BUILDING_ON_TIGER) // Animations are never threaded on Tiger. 419 if (![NSThread isMainThread]) { 420 [self performSelectorOnMainThread:@selector(animationDidEnd:) withObject:animation waitUntilDone:NO]; 421 return; 422 } 423#endif 424 if (animation != _fullscreenAnimation) 425 return; 426 427 // The animation is not really over and was interrupted 428 // Don't send completion events. 429 if ([animation currentProgress] < 1.0) 430 return; 431 432 // Ensure that animation (and subanimation) don't keep 433 // the weak reference to the window ivar that may be destroyed from 434 // now on. 435 [_fullscreenAnimation setWindow:nil]; 436 437 [_fullscreenAnimation autorelease]; 438 _fullscreenAnimation = nil; 439 440 [self animatedResizeDidEnd]; 441} 442 443- (void)mouseMoved:(NSEvent *)theEvent 444{ 445 [[self windowController] fadeHUDIn]; 446} 447 448- (void)resignKeyWindow 449{ 450 [super resignKeyWindow]; 451 [[self windowController] requestExitFullscreenWithAnimation:NO]; 452} 453 454@end 455 456#endif /* ENABLE(VIDEO) */ 457