blob: c61ae7cc91f8b81f6c20cdd468d648326fe405f5 [file] [log] [blame]
/*
* Copyright (C) 2009 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#if ENABLE(VIDEO)
#import "WebVideoFullscreenHUDWindowController.h"
#import <QTKit/QTKit.h>
#import "WebKitSystemInterface.h"
#import "WebTypesInternal.h"
#import <wtf/RetainPtr.h>
#import <limits>
using namespace std;
#define HAVE_MEDIA_CONTROL (!defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD))
@interface WebVideoFullscreenHUDWindowController (Private) <NSWindowDelegate>
- (void)keyDown:(NSEvent *)event;
- (void)updateTime;
- (void)timelinePositionChanged:(id)sender;
- (float)currentTime;
- (void)setCurrentTime:(float)currentTime;
- (double)duration;
- (double)maxVolume;
- (void)volumeChanged:(id)sender;
- (double)volume;
- (void)setVolume:(double)volume;
- (void)togglePlaying:(id)sender;
- (BOOL)playing;
- (void)setPlaying:(BOOL)playing;
- (void)rewind:(id)sender;
- (void)fastForward:(id)sender;
- (NSString *)remainingTimeText;
- (NSString *)elapsedTimeText;
- (void)exitFullscreen:(id)sender;
@end
//
// HUD Window
//
@interface WebVideoFullscreenHUDWindow : NSWindow
@end
@implementation WebVideoFullscreenHUDWindow
- (id)initWithContentRect:(NSRect)contentRect styleMask:(NSUInteger)aStyle backing:(NSBackingStoreType)bufferingType defer:(BOOL)flag
{
UNUSED_PARAM(aStyle);
self = [super initWithContentRect:contentRect styleMask:NSBorderlessWindowMask backing:bufferingType defer:flag];
if (!self)
return nil;
[self setOpaque:NO];
[self setBackgroundColor:[NSColor clearColor]];
[self setLevel:NSPopUpMenuWindowLevel];
[self setAcceptsMouseMovedEvents:YES];
[self setIgnoresMouseEvents:NO];
[self setMovableByWindowBackground:YES];
[self setHidesOnDeactivate:YES];
return self;
}
- (BOOL)canBecomeKeyWindow
{
return YES;
}
- (void)cancelOperation:(id)sender
{
[[self windowController] exitFullscreen:self];
}
- (void)center
{
NSRect hudFrame = [self frame];
NSRect screenFrame = [[NSScreen mainScreen] frame];
[self setFrameTopLeftPoint:NSMakePoint(screenFrame.origin.x + (screenFrame.size.width - hudFrame.size.width) / 2,
screenFrame.origin.y + (screenFrame.size.height - hudFrame.size.height) / 6)];
}
- (void)keyDown:(NSEvent *)event
{
[super keyDown:event];
[[self windowController] fadeWindowIn];
}
- (BOOL)resignFirstResponder
{
return NO;
}
- (BOOL)performKeyEquivalent:(NSEvent *)event
{
// Block all command key events while the fullscreen window is up.
if ([event type] != NSKeyDown)
return NO;
if (!([event modifierFlags] & NSCommandKeyMask))
return NO;
return YES;
}
@end
//
// HUD Window Controller
//
static const CGFloat windowHeight = 59;
static const CGFloat windowWidth = 438;
static const NSTimeInterval HUDWindowFadeOutDelay = 3;
@implementation WebVideoFullscreenHUDWindowController
- (id)init
{
NSWindow* window = [[WebVideoFullscreenHUDWindow alloc] initWithContentRect:NSMakeRect(0, 0, windowWidth, windowHeight)
styleMask:NSBorderlessWindowMask backing:NSBackingStoreBuffered defer:NO];
self = [super initWithWindow:window];
[window setDelegate:self];
[window release];
if (!self)
return nil;
[self windowDidLoad];
return self;
}
- (void)dealloc
{
ASSERT(!_timelineUpdateTimer);
#if !defined(BUILDING_ON_TIGER)
ASSERT(!_area);
#endif
[_timeline release];
[_remainingTimeText release];
[_elapsedTimeText release];
[_volumeSlider release];
[_playButton release];
[super dealloc];
}
#if !defined(BUILDING_ON_TIGER)
- (void)setArea:(NSTrackingArea *)area
{
if (area == _area)
return;
[_area release];
_area = [area retain];
}
#endif
- (void)keyDown:(NSEvent *)event
{
if ([[event characters] isEqualToString:@" "])
[_playButton performClick:self];
else
[super keyDown:event];
}
- (id<WebVideoFullscreenHUDWindowControllerDelegate>)delegate
{
return _delegate;
}
- (void)setDelegate:(id<WebVideoFullscreenHUDWindowControllerDelegate>)delegate
{
_delegate = delegate;
}
- (void)scheduleTimeUpdate
{
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(unscheduleTimeUpdate) object:self];
// First, update right away, then schedule future update
[self updateTime];
[self updateRate];
[_timelineUpdateTimer invalidate];
[_timelineUpdateTimer release];
// Note that this creates a retain cycle between the window and us.
_timelineUpdateTimer = [[NSTimer timerWithTimeInterval:0.25 target:self selector:@selector(updateTime) userInfo:nil repeats:YES] retain];
#if defined(BUILDING_ON_TIGER)
[[NSRunLoop currentRunLoop] addTimer:_timelineUpdateTimer forMode:(NSString*)kCFRunLoopCommonModes];
#else
[[NSRunLoop currentRunLoop] addTimer:_timelineUpdateTimer forMode:NSRunLoopCommonModes];
#endif
}
- (void)unscheduleTimeUpdate
{
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(unscheduleTimeUpdate) object:nil];
[_timelineUpdateTimer invalidate];
[_timelineUpdateTimer release];
_timelineUpdateTimer = nil;
}
- (void)fadeWindowIn
{
NSWindow *window = [self window];
if (![window isVisible])
[window setAlphaValue:0];
[window makeKeyAndOrderFront:self];
#if defined(BUILDING_ON_TIGER)
[window setAlphaValue:1];
#else
[[window animator] setAlphaValue:1];
#endif
[self scheduleTimeUpdate];
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(fadeWindowOut) object:nil];
if (!_mouseIsInHUD && [self playing]) // Don't fade out when paused.
[self performSelector:@selector(fadeWindowOut) withObject:nil afterDelay:HUDWindowFadeOutDelay];
}
- (void)fadeWindowOut
{
[NSCursor setHiddenUntilMouseMoves:YES];
#if defined(BUILDING_ON_TIGER)
[[self window] setAlphaValue:0];
#else
[[[self window] animator] setAlphaValue:0];
#endif
[self performSelector:@selector(unscheduleTimeUpdate) withObject:nil afterDelay:1];
}
- (void)closeWindow
{
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(fadeWindowOut) object:nil];
[self unscheduleTimeUpdate];
NSWindow *window = [self window];
#if !defined(BUILDING_ON_TIGER)
[[window contentView] removeTrackingArea:_area];
[self setArea:nil];
#endif
[window close];
[window setDelegate:nil];
[self setWindow:nil];
}
#ifndef HAVE_MEDIA_CONTROL
enum {
WKMediaUIControlPlayPauseButton,
WKMediaUIControlRewindButton,
WKMediaUIControlFastForwardButton,
WKMediaUIControlExitFullscreenButton,
WKMediaUIControlVolumeDownButton,
WKMediaUIControlSlider,
WKMediaUIControlVolumeUpButton,
WKMediaUIControlTimeline
};
#endif
static NSControl *createControlWithMediaUIControlType(int controlType, NSRect frame)
{
#ifdef HAVE_MEDIA_CONTROL
NSControl *control = WKCreateMediaUIControl(controlType);
[control setFrame:frame];
return control;
#else
if (controlType == WKMediaUIControlSlider)
return [[NSSlider alloc] initWithFrame:frame];
return [[NSControl alloc] initWithFrame:frame];
#endif
}
static NSTextField *createTimeTextField(NSRect frame)
{
NSTextField *textField = [[NSTextField alloc] initWithFrame:frame];
[textField setTextColor:[NSColor whiteColor]];
[textField setBordered:NO];
[textField setFont:[NSFont systemFontOfSize:10]];
[textField setDrawsBackground:NO];
[textField setBezeled:NO];
[textField setEditable:NO];
[textField setSelectable:NO];
return textField;
}
- (void)windowDidLoad
{
static const CGFloat kMargin = 9;
static const CGFloat kMarginTop = 9;
static const CGFloat kButtonSize = 25;
static const CGFloat kButtonMiniSize = 16;
NSWindow *window = [self window];
ASSERT(window);
#ifdef HAVE_MEDIA_CONTROL
NSView *background = WKCreateMediaUIBackgroundView();
#else
NSView *background = [[NSView alloc] init];
#endif
[window setContentView:background];
#if !defined(BUILDING_ON_TIGER)
_area = [[NSTrackingArea alloc] initWithRect:[background bounds] options:NSTrackingMouseEnteredAndExited|NSTrackingActiveAlways owner:self userInfo:nil];
[background addTrackingArea:_area];
#endif
[background release];
NSView *contentView = [[self window] contentView];
CGFloat top = windowHeight - kMarginTop;
CGFloat center = (windowWidth - kButtonSize) / 2;
_playButton = createControlWithMediaUIControlType(WKMediaUIControlPlayPauseButton, NSMakeRect(center, top - kButtonSize, kButtonSize, kButtonSize));
[_playButton setTarget:self];
[_playButton setAction:@selector(togglePlaying:)];
[contentView addSubview:_playButton];
CGFloat closeToRight = windowWidth - 2 * kMargin - kButtonMiniSize;
NSControl *exitFullscreenButton = createControlWithMediaUIControlType(WKMediaUIControlExitFullscreenButton, NSMakeRect(closeToRight, top - kButtonSize / 2 - kButtonMiniSize / 2, kButtonMiniSize, kButtonMiniSize));
[exitFullscreenButton setAction:@selector(exitFullscreen:)];
[exitFullscreenButton setTarget:self];
[contentView addSubview:exitFullscreenButton];
[exitFullscreenButton release];
CGFloat left = kMargin;
NSControl *volumeDownButton = createControlWithMediaUIControlType(WKMediaUIControlVolumeDownButton, NSMakeRect(left, top - kButtonSize / 2 - kButtonMiniSize / 2, kButtonMiniSize, kButtonMiniSize));
[contentView addSubview:volumeDownButton];
[volumeDownButton setTarget:self];
[volumeDownButton setAction:@selector(decrementVolume:)];
[volumeDownButton release];
static const int volumeSliderWidth = 50;
left = kMargin + kButtonMiniSize;
_volumeSlider = createControlWithMediaUIControlType(WKMediaUIControlSlider, NSMakeRect(left, top - kButtonSize / 2 - kButtonMiniSize / 2, volumeSliderWidth, kButtonMiniSize));
[_volumeSlider setValue:[NSNumber numberWithDouble:[self maxVolume]] forKey:@"maxValue"];
[_volumeSlider setTarget:self];
[_volumeSlider setAction:@selector(volumeChanged:)];
[contentView addSubview:_volumeSlider];
left = kMargin + kButtonMiniSize + volumeSliderWidth + kButtonMiniSize / 2;
NSControl *volumeUpButton = createControlWithMediaUIControlType(WKMediaUIControlVolumeUpButton, NSMakeRect(left, top - kButtonSize / 2 - kButtonMiniSize / 2, kButtonMiniSize, kButtonMiniSize));
[volumeUpButton setTarget:self];
[volumeUpButton setAction:@selector(incrementVolume:)];
[contentView addSubview:volumeUpButton];
[volumeUpButton release];
static const int timeTextWidth = 50;
static const int sliderHeight = 13;
static const int sliderMarginFixup = 4;
#ifdef HAVE_MEDIA_CONTROL
_timeline = WKCreateMediaUIControl(WKMediaUIControlTimeline);
#else
_timeline = [[NSSlider alloc] init];
#endif
[_timeline setTarget:self];
[_timeline setAction:@selector(timelinePositionChanged:)];
[_timeline setFrame:NSMakeRect(kMargin + timeTextWidth + kMargin/2, kMargin - sliderMarginFixup, windowWidth - 2 * (kMargin - sliderMarginFixup) - kMargin * 2 - 2 * timeTextWidth, sliderHeight)];
[contentView addSubview:_timeline];
static const int timeTextHeight = 11;
_elapsedTimeText = createTimeTextField(NSMakeRect(kMargin, kMargin, timeTextWidth, timeTextHeight));
[contentView addSubview:_elapsedTimeText];
_remainingTimeText = createTimeTextField(NSMakeRect(windowWidth - kMargin - timeTextWidth, kMargin, timeTextWidth, timeTextHeight));
[contentView addSubview:_remainingTimeText];
[window recalculateKeyViewLoop];
[window setInitialFirstResponder:_playButton];
[window center];
}
/*
* Bindings
*
*/
- (void)updateVolume
{
[_volumeSlider setDoubleValue:[self volume]];
}
- (void)updateTime
{
[self updateVolume];
[_timeline setFloatValue:[self currentTime]];
[(NSSlider*)_timeline setMaxValue:[self duration]];
[_remainingTimeText setStringValue:[self remainingTimeText]];
[_elapsedTimeText setStringValue:[self elapsedTimeText]];
}
- (void)fastForward
{
}
- (void)timelinePositionChanged:(id)sender
{
[self setCurrentTime:[_timeline floatValue]];
}
- (float)currentTime
{
return [_delegate mediaElement] ? [_delegate mediaElement]->currentTime() : 0;
}
- (void)setCurrentTime:(float)currentTime
{
if (![_delegate mediaElement])
return;
WebCore::ExceptionCode e;
[_delegate mediaElement]->setCurrentTime(currentTime, e);
}
- (double)duration
{
return [_delegate mediaElement] ? [_delegate mediaElement]->duration() : 0;
}
- (double)maxVolume
{
// Set the volume slider resolution
return 100;
}
- (void)volumeChanged:(id)sender
{
[self setVolume:[_volumeSlider doubleValue]];
}
- (void)decrementVolume:(id)sender
{
if (![_delegate mediaElement])
return;
double volume = [self volume] - 10;
[self setVolume:max(volume, 0.)];
}
- (void)incrementVolume:(id)sender
{
if (![_delegate mediaElement])
return;
double volume = [self volume] + 10;
[self setVolume:min(volume, [self maxVolume])];
}
- (double)volume
{
return [_delegate mediaElement] ? [_delegate mediaElement]->volume() * [self maxVolume] : 0;
}
- (void)setVolume:(double)volume
{
if (![_delegate mediaElement])
return;
WebCore::ExceptionCode e;
if ([_delegate mediaElement]->muted())
[_delegate mediaElement]->setMuted(false);
[_delegate mediaElement]->setVolume(volume / [self maxVolume], e);
}
- (void)updateRate
{
[_playButton setIntValue:[self playing]];
}
- (void)togglePlaying:(id)sender
{
BOOL nowPlaying = [self playing];
[self setPlaying:!nowPlaying];
// Keep HUD visible when paused
if (!nowPlaying)
[self fadeWindowIn];
else if (!_mouseIsInHUD) {
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(fadeWindowOut) object:nil];
[self performSelector:@selector(fadeWindowOut) withObject:nil afterDelay:HUDWindowFadeOutDelay];
}
}
- (BOOL)playing
{
if (![_delegate mediaElement])
return false;
return ![_delegate mediaElement]->canPlay();
}
- (void)setPlaying:(BOOL)playing
{
if (![_delegate mediaElement])
return;
if (playing)
[_delegate mediaElement]->play();
else
[_delegate mediaElement]->pause();
}
static NSString *timeToString(double time)
{
if (!isfinite(time))
time = 0;
int seconds = (int)fabsf(time);
int hours = seconds / (60 * 60);
int minutes = (seconds / 60) % 60;
seconds %= 60;
if (hours) {
if (hours > 9)
return [NSString stringWithFormat:@"%s%02d:%02d:%02d", (time < 0 ? "-" : ""), hours, minutes, seconds];
else
return [NSString stringWithFormat:@"%s%01d:%02d:%02d", (time < 0 ? "-" : ""), hours, minutes, seconds];
}
else
return [NSString stringWithFormat:@"%s%02d:%02d", (time < 0 ? "-" : ""), minutes, seconds];
}
static NSString *stringToTimeTextAttributed(NSString *string, NSTextAlignment align)
{
NSShadow *blackShadow = [[NSShadow alloc] init];
[blackShadow setShadowColor:[NSColor blackColor]];
[blackShadow setShadowBlurRadius:0];
[blackShadow setShadowOffset:NSMakeSize(0, -1)];
NSMutableParagraphStyle *style = [[NSMutableParagraphStyle alloc] init];
[style setAlignment:align];
NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:blackShadow, NSShadowAttributeName, style, NSParagraphStyleAttributeName, nil];
[style release];
[blackShadow release];
NSAttributedString *attrString = [[NSAttributedString alloc] initWithString:string attributes:dict];
return [attrString autorelease];
}
- (NSString *)remainingTimeText
{
if (![_delegate mediaElement])
return @"";
// Negative number
return stringToTimeTextAttributed(timeToString([_delegate mediaElement]->currentTime() - [_delegate mediaElement]->duration()), NSLeftTextAlignment);
}
- (NSString *)elapsedTimeText
{
if (![_delegate mediaElement])
return @"";
return stringToTimeTextAttributed(timeToString([_delegate mediaElement]->currentTime()), NSRightTextAlignment);
}
/*
* Tracking area callbacks
*
*/
- (void)mouseEntered:(NSEvent *)theEvent
{
// Make sure the HUD won't be hidden from now
_mouseIsInHUD = YES;
[self fadeWindowIn];
}
- (void)mouseExited:(NSEvent *)theEvent
{
_mouseIsInHUD = NO;
[self fadeWindowIn];
}
/*
* Other Interface callbacks
*
*/
- (void)rewind:(id)sender
{
if (![_delegate mediaElement])
return;
[_delegate mediaElement]->rewind(30);
}
- (void)fastForward:(id)sender
{
if (![_delegate mediaElement])
return;
}
- (void)exitFullscreen:(id)sender
{
if (_isEndingFullscreen)
return;
_isEndingFullscreen = YES;
[_delegate requestExitFullscreen];
}
/*
* Window callback
*
*/
- (void)windowDidExpose:(NSNotification *)notification
{
[self scheduleTimeUpdate];
}
- (void)windowDidClose:(NSNotification *)notification
{
[self unscheduleTimeUpdate];
}
@end
#endif