• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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