Home | History | Annotate | Download | only in WebView
      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'' AND ANY
     14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
     15  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
     16  * DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY
     17  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
     18  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
     19  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
     20  * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     21  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
     22  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     23  */
     24 
     25 #if ENABLE(VIDEO)
     26 
     27 #import "WebVideoFullscreenHUDWindowController.h"
     28 
     29 #import "WebKitSystemInterface.h"
     30 #import "WebTypesInternal.h"
     31 #import <JavaScriptCore/RetainPtr.h>
     32 #import <JavaScriptCore/UnusedParam.h>
     33 #import <WebCore/HTMLMediaElement.h>
     34 
     35 using namespace WebCore;
     36 using namespace std;
     37 
     38 static inline CGFloat webkit_CGFloor(CGFloat value)
     39 {
     40     if (sizeof(value) == sizeof(float))
     41         return floorf(value);
     42     return floor(value);
     43 }
     44 
     45 #define HAVE_MEDIA_CONTROL (!defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD))
     46 
     47 @interface WebVideoFullscreenHUDWindowController (Private) <NSWindowDelegate>
     48 
     49 - (void)updateTime;
     50 - (void)timelinePositionChanged:(id)sender;
     51 - (float)currentTime;
     52 - (void)setCurrentTime:(float)currentTime;
     53 - (double)duration;
     54 
     55 - (void)volumeChanged:(id)sender;
     56 - (double)maxVolume;
     57 - (double)volume;
     58 - (void)setVolume:(double)volume;
     59 - (void)decrementVolume;
     60 - (void)incrementVolume;
     61 
     62 - (void)updatePlayButton;
     63 - (void)togglePlaying:(id)sender;
     64 - (BOOL)playing;
     65 - (void)setPlaying:(BOOL)playing;
     66 
     67 - (void)rewind:(id)sender;
     68 - (void)fastForward:(id)sender;
     69 
     70 - (NSString *)remainingTimeText;
     71 - (NSString *)elapsedTimeText;
     72 
     73 - (void)exitFullscreen:(id)sender;
     74 @end
     75 
     76 @interface WebVideoFullscreenHUDWindow : NSWindow
     77 @end
     78 
     79 @implementation WebVideoFullscreenHUDWindow
     80 
     81 - (id)initWithContentRect:(NSRect)contentRect styleMask:(NSUInteger)aStyle backing:(NSBackingStoreType)bufferingType defer:(BOOL)flag
     82 {
     83     UNUSED_PARAM(aStyle);
     84     self = [super initWithContentRect:contentRect styleMask:NSBorderlessWindowMask backing:bufferingType defer:flag];
     85     if (!self)
     86         return nil;
     87 
     88     [self setOpaque:NO];
     89     [self setBackgroundColor:[NSColor clearColor]];
     90     [self setLevel:NSPopUpMenuWindowLevel];
     91     [self setAcceptsMouseMovedEvents:YES];
     92     [self setIgnoresMouseEvents:NO];
     93     [self setMovableByWindowBackground:YES];
     94 
     95     return self;
     96 }
     97 
     98 - (BOOL)canBecomeKeyWindow
     99 {
    100     return YES;
    101 }
    102 
    103 - (void)cancelOperation:(id)sender
    104 {
    105     [[self windowController] exitFullscreen:self];
    106 }
    107 
    108 - (void)center
    109 {
    110     NSRect hudFrame = [self frame];
    111     NSRect screenFrame = [[NSScreen mainScreen] frame];
    112     [self setFrameTopLeftPoint:NSMakePoint(screenFrame.origin.x + (screenFrame.size.width - hudFrame.size.width) / 2,
    113                                            screenFrame.origin.y + (screenFrame.size.height - hudFrame.size.height) / 6)];
    114 }
    115 
    116 - (void)keyDown:(NSEvent *)event
    117 {
    118     [super keyDown:event];
    119     [[self windowController] fadeWindowIn];
    120 }
    121 
    122 - (BOOL)resignFirstResponder
    123 {
    124     return NO;
    125 }
    126 
    127 - (BOOL)performKeyEquivalent:(NSEvent *)event
    128 {
    129     // Block all command key events while the fullscreen window is up.
    130     if ([event type] != NSKeyDown)
    131         return NO;
    132 
    133     if (!([event modifierFlags] & NSCommandKeyMask))
    134         return NO;
    135 
    136     return YES;
    137 }
    138 
    139 @end
    140 
    141 static const CGFloat windowHeight = 59;
    142 static const CGFloat windowWidth = 438;
    143 
    144 static const NSTimeInterval HUDWindowFadeOutDelay = 3;
    145 
    146 @implementation WebVideoFullscreenHUDWindowController
    147 
    148 - (id)init
    149 {
    150     NSWindow *window = [[WebVideoFullscreenHUDWindow alloc] initWithContentRect:NSMakeRect(0, 0, windowWidth, windowHeight)
    151                             styleMask:NSBorderlessWindowMask backing:NSBackingStoreBuffered defer:NO];
    152     self = [super initWithWindow:window];
    153     [window setDelegate:self];
    154     [window release];
    155     if (!self)
    156         return nil;
    157     [self windowDidLoad];
    158     return self;
    159 }
    160 
    161 - (void)dealloc
    162 {
    163     ASSERT(!_timelineUpdateTimer);
    164 #if !defined(BUILDING_ON_TIGER)
    165     ASSERT(!_area);
    166 #endif
    167     ASSERT(!_isScrubbing);
    168     [_timeline release];
    169     [_remainingTimeText release];
    170     [_elapsedTimeText release];
    171     [_volumeSlider release];
    172     [_playButton release];
    173     [super dealloc];
    174 }
    175 
    176 #if !defined(BUILDING_ON_TIGER)
    177 - (void)setArea:(NSTrackingArea *)area
    178 {
    179     if (area == _area)
    180         return;
    181     [_area release];
    182     _area = [area retain];
    183 }
    184 #endif
    185 
    186 - (void)keyDown:(NSEvent *)event
    187 {
    188     NSString *charactersIgnoringModifiers = [event charactersIgnoringModifiers];
    189     if ([charactersIgnoringModifiers length] == 1) {
    190         switch ([charactersIgnoringModifiers characterAtIndex:0]) {
    191             case ' ':
    192                 [self togglePlaying:nil];
    193                 return;
    194             case NSUpArrowFunctionKey:
    195                 if ([event modifierFlags] & NSAlternateKeyMask)
    196                     [self setVolume:[self maxVolume]];
    197                 else
    198                     [self incrementVolume];
    199                 return;
    200             case NSDownArrowFunctionKey:
    201                 if ([event modifierFlags] & NSAlternateKeyMask)
    202                     [self setVolume:0];
    203                 else
    204                     [self decrementVolume];
    205                 return;
    206             default:
    207                 break;
    208         }
    209     }
    210 
    211     [super keyDown:event];
    212 }
    213 
    214 - (id <WebVideoFullscreenHUDWindowControllerDelegate>)delegate
    215 {
    216     return _delegate;
    217 }
    218 
    219 - (void)setDelegate:(id <WebVideoFullscreenHUDWindowControllerDelegate>)delegate
    220 {
    221     _delegate = delegate;
    222 }
    223 
    224 - (void)scheduleTimeUpdate
    225 {
    226     [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(unscheduleTimeUpdate) object:self];
    227 
    228     // First, update right away, then schedule future update
    229     [self updateTime];
    230     [self updatePlayButton];
    231 
    232     [_timelineUpdateTimer invalidate];
    233     [_timelineUpdateTimer release];
    234 
    235     // Note that this creates a retain cycle between the window and us.
    236     _timelineUpdateTimer = [[NSTimer timerWithTimeInterval:0.25 target:self selector:@selector(updateTime) userInfo:nil repeats:YES] retain];
    237 #if defined(BUILDING_ON_TIGER)
    238     [[NSRunLoop currentRunLoop] addTimer:_timelineUpdateTimer forMode:(NSString *)kCFRunLoopCommonModes];
    239 #else
    240     [[NSRunLoop currentRunLoop] addTimer:_timelineUpdateTimer forMode:NSRunLoopCommonModes];
    241 #endif
    242 }
    243 
    244 - (void)unscheduleTimeUpdate
    245 {
    246     [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(unscheduleTimeUpdate) object:nil];
    247 
    248     [_timelineUpdateTimer invalidate];
    249     [_timelineUpdateTimer release];
    250     _timelineUpdateTimer = nil;
    251 }
    252 
    253 - (void)fadeWindowIn
    254 {
    255     NSWindow *window = [self window];
    256     if (![window isVisible])
    257         [window setAlphaValue:0];
    258 
    259     [window makeKeyAndOrderFront:self];
    260 #if defined(BUILDING_ON_TIGER)
    261     [window setAlphaValue:1];
    262 #else
    263     [[window animator] setAlphaValue:1];
    264 #endif
    265     [self scheduleTimeUpdate];
    266 
    267     [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(fadeWindowOut) object:nil];
    268     if (!_mouseIsInHUD && [self playing])   // Don't fade out when paused.
    269         [self performSelector:@selector(fadeWindowOut) withObject:nil afterDelay:HUDWindowFadeOutDelay];
    270 }
    271 
    272 - (void)fadeWindowOut
    273 {
    274     [NSCursor setHiddenUntilMouseMoves:YES];
    275 #if defined(BUILDING_ON_TIGER)
    276     [[self window] setAlphaValue:0];
    277 #else
    278     [[[self window] animator] setAlphaValue:0];
    279 #endif
    280     [self performSelector:@selector(unscheduleTimeUpdate) withObject:nil afterDelay:1];
    281 }
    282 
    283 - (void)closeWindow
    284 {
    285     [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(fadeWindowOut) object:nil];
    286     [self unscheduleTimeUpdate];
    287     NSWindow *window = [self window];
    288 #if !defined(BUILDING_ON_TIGER)
    289     [[window contentView] removeTrackingArea:_area];
    290     [self setArea:nil];
    291 #endif
    292     [window close];
    293     [window setDelegate:nil];
    294     [self setWindow:nil];
    295 }
    296 
    297 #ifndef HAVE_MEDIA_CONTROL
    298 enum {
    299     WKMediaUIControlPlayPauseButton,
    300     WKMediaUIControlRewindButton,
    301     WKMediaUIControlFastForwardButton,
    302     WKMediaUIControlExitFullscreenButton,
    303     WKMediaUIControlVolumeDownButton,
    304     WKMediaUIControlSlider,
    305     WKMediaUIControlVolumeUpButton,
    306     WKMediaUIControlTimeline
    307 };
    308 #endif
    309 
    310 static NSControl *createControlWithMediaUIControlType(int controlType, NSRect frame)
    311 {
    312 #ifdef HAVE_MEDIA_CONTROL
    313     NSControl *control = WKCreateMediaUIControl(controlType);
    314     [control setFrame:frame];
    315     return control;
    316 #else
    317     if (controlType == WKMediaUIControlSlider)
    318         return [[NSSlider alloc] initWithFrame:frame];
    319     return [[NSControl alloc] initWithFrame:frame];
    320 #endif
    321 }
    322 
    323 static NSTextField *createTimeTextField(NSRect frame)
    324 {
    325     NSTextField *textField = [[NSTextField alloc] initWithFrame:frame];
    326     [textField setTextColor:[NSColor whiteColor]];
    327     [textField setBordered:NO];
    328     [textField setFont:[NSFont boldSystemFontOfSize:10]];
    329     [textField setDrawsBackground:NO];
    330     [textField setBezeled:NO];
    331     [textField setEditable:NO];
    332     [textField setSelectable:NO];
    333     return textField;
    334 }
    335 
    336 - (void)windowDidLoad
    337 {
    338     static const CGFloat horizontalMargin = 10;
    339     static const CGFloat playButtonWidth = 41;
    340     static const CGFloat playButtonHeight = 35;
    341     static const CGFloat playButtonTopMargin = 4;
    342     static const CGFloat volumeSliderWidth = 50;
    343     static const CGFloat volumeSliderHeight = 13;
    344     static const CGFloat volumeButtonWidth = 18;
    345     static const CGFloat volumeButtonHeight = 16;
    346     static const CGFloat volumeUpButtonLeftMargin = 4;
    347     static const CGFloat volumeControlsTopMargin = 13;
    348     static const CGFloat exitFullscreenButtonWidth = 25;
    349     static const CGFloat exitFullscreenButtonHeight = 21;
    350     static const CGFloat exitFullscreenButtonTopMargin = 11;
    351     static const CGFloat timelineWidth = 315;
    352     static const CGFloat timelineHeight = 14;
    353     static const CGFloat timelineBottomMargin = 7;
    354     static const CGFloat timeTextFieldWidth = 54;
    355     static const CGFloat timeTextFieldHeight = 13;
    356     static const CGFloat timeTextFieldHorizontalMargin = 7;
    357 
    358     NSWindow *window = [self window];
    359     ASSERT(window);
    360 
    361 #ifdef HAVE_MEDIA_CONTROL
    362     NSView *background = WKCreateMediaUIBackgroundView();
    363 #else
    364     NSView *background = [[NSView alloc] init];
    365 #endif
    366     [window setContentView:background];
    367 #if !defined(BUILDING_ON_TIGER)
    368     _area = [[NSTrackingArea alloc] initWithRect:[background bounds] options:NSTrackingMouseEnteredAndExited | NSTrackingActiveAlways owner:self userInfo:nil];
    369     [background addTrackingArea:_area];
    370 #endif
    371     [background release];
    372 
    373     NSView *contentView = [window contentView];
    374 
    375     CGFloat center = webkit_CGFloor((windowWidth - playButtonWidth) / 2);
    376     _playButton = (NSButton *)createControlWithMediaUIControlType(WKMediaUIControlPlayPauseButton, NSMakeRect(center, windowHeight - playButtonTopMargin - playButtonHeight, playButtonWidth, playButtonHeight));
    377     ASSERT([_playButton isKindOfClass:[NSButton class]]);
    378     [_playButton setTarget:self];
    379     [_playButton setAction:@selector(togglePlaying:)];
    380     [contentView addSubview:_playButton];
    381 
    382     CGFloat closeToRight = windowWidth - horizontalMargin - exitFullscreenButtonWidth;
    383     NSControl *exitFullscreenButton = createControlWithMediaUIControlType(WKMediaUIControlExitFullscreenButton, NSMakeRect(closeToRight, windowHeight - exitFullscreenButtonTopMargin - exitFullscreenButtonHeight, exitFullscreenButtonWidth, exitFullscreenButtonHeight));
    384     [exitFullscreenButton setAction:@selector(exitFullscreen:)];
    385     [exitFullscreenButton setTarget:self];
    386     [contentView addSubview:exitFullscreenButton];
    387     [exitFullscreenButton release];
    388 
    389     CGFloat volumeControlsBottom = windowHeight - volumeControlsTopMargin - volumeButtonHeight;
    390     CGFloat left = horizontalMargin;
    391     NSControl *volumeDownButton = createControlWithMediaUIControlType(WKMediaUIControlVolumeDownButton, NSMakeRect(left, volumeControlsBottom, volumeButtonWidth, volumeButtonHeight));
    392     [contentView addSubview:volumeDownButton];
    393     [volumeDownButton setTarget:self];
    394     [volumeDownButton setAction:@selector(setVolumeToZero:)];
    395     [volumeDownButton release];
    396 
    397     left += volumeButtonWidth;
    398     _volumeSlider = createControlWithMediaUIControlType(WKMediaUIControlSlider, NSMakeRect(left, volumeControlsBottom + webkit_CGFloor((volumeButtonHeight - volumeSliderHeight) / 2), volumeSliderWidth, volumeSliderHeight));
    399     [_volumeSlider setValue:[NSNumber numberWithDouble:[self maxVolume]] forKey:@"maxValue"];
    400     [_volumeSlider setTarget:self];
    401     [_volumeSlider setAction:@selector(volumeChanged:)];
    402     [contentView addSubview:_volumeSlider];
    403 
    404     left += volumeSliderWidth + volumeUpButtonLeftMargin;
    405     NSControl *volumeUpButton = createControlWithMediaUIControlType(WKMediaUIControlVolumeUpButton, NSMakeRect(left, volumeControlsBottom, volumeButtonWidth, volumeButtonHeight));
    406     [volumeUpButton setTarget:self];
    407     [volumeUpButton setAction:@selector(setVolumeToMaximum:)];
    408     [contentView addSubview:volumeUpButton];
    409     [volumeUpButton release];
    410 
    411 #ifdef HAVE_MEDIA_CONTROL
    412     _timeline = WKCreateMediaUIControl(WKMediaUIControlTimeline);
    413 #else
    414     _timeline = [[NSSlider alloc] init];
    415 #endif
    416     [_timeline setTarget:self];
    417     [_timeline setAction:@selector(timelinePositionChanged:)];
    418     [_timeline setFrame:NSMakeRect(webkit_CGFloor((windowWidth - timelineWidth) / 2), timelineBottomMargin, timelineWidth, timelineHeight)];
    419     [contentView addSubview:_timeline];
    420 
    421     _elapsedTimeText = createTimeTextField(NSMakeRect(timeTextFieldHorizontalMargin, timelineBottomMargin, timeTextFieldWidth, timeTextFieldHeight));
    422     [_elapsedTimeText setAlignment:NSLeftTextAlignment];
    423     [contentView addSubview:_elapsedTimeText];
    424 
    425     _remainingTimeText = createTimeTextField(NSMakeRect(windowWidth - timeTextFieldHorizontalMargin - timeTextFieldWidth, timelineBottomMargin, timeTextFieldWidth, timeTextFieldHeight));
    426     [_remainingTimeText setAlignment:NSRightTextAlignment];
    427     [contentView addSubview:_remainingTimeText];
    428 
    429     [window recalculateKeyViewLoop];
    430     [window setInitialFirstResponder:_playButton];
    431     [window center];
    432 }
    433 
    434 - (void)updateVolume
    435 {
    436     [_volumeSlider setDoubleValue:[self volume]];
    437 }
    438 
    439 - (void)updateTime
    440 {
    441     [self updateVolume];
    442 
    443     [_timeline setFloatValue:[self currentTime]];
    444     [_timeline setValue:[NSNumber numberWithDouble:[self duration]] forKey:@"maxValue"];
    445 
    446     [_remainingTimeText setStringValue:[self remainingTimeText]];
    447     [_elapsedTimeText setStringValue:[self elapsedTimeText]];
    448 }
    449 
    450 - (void)endScrubbing
    451 {
    452     ASSERT(_isScrubbing);
    453     _isScrubbing = NO;
    454     if (HTMLMediaElement* mediaElement = [_delegate mediaElement])
    455         mediaElement->endScrubbing();
    456 }
    457 
    458 - (void)timelinePositionChanged:(id)sender
    459 {
    460     [self setCurrentTime:[_timeline floatValue]];
    461     if (!_isScrubbing) {
    462         _isScrubbing = YES;
    463         if (HTMLMediaElement* mediaElement = [_delegate mediaElement])
    464             mediaElement->beginScrubbing();
    465         static NSArray *endScrubbingModes = [[NSArray alloc] initWithObjects:NSDefaultRunLoopMode, NSModalPanelRunLoopMode, nil];
    466         // Schedule -endScrubbing for when leaving mouse tracking mode.
    467         [[NSRunLoop currentRunLoop] performSelector:@selector(endScrubbing) target:self argument:nil order:0 modes:endScrubbingModes];
    468     }
    469 }
    470 
    471 - (float)currentTime
    472 {
    473     return [_delegate mediaElement] ? [_delegate mediaElement]->currentTime() : 0;
    474 }
    475 
    476 - (void)setCurrentTime:(float)currentTime
    477 {
    478     if (![_delegate mediaElement])
    479         return;
    480     WebCore::ExceptionCode e;
    481     [_delegate mediaElement]->setCurrentTime(currentTime, e);
    482     [self updateTime];
    483 }
    484 
    485 - (double)duration
    486 {
    487     return [_delegate mediaElement] ? [_delegate mediaElement]->duration() : 0;
    488 }
    489 
    490 - (double)maxVolume
    491 {
    492     // Set the volume slider resolution
    493     return 100;
    494 }
    495 
    496 - (void)volumeChanged:(id)sender
    497 {
    498     [self setVolume:[_volumeSlider doubleValue]];
    499 }
    500 
    501 - (void)setVolumeToZero:(id)sender
    502 {
    503     [self setVolume:0];
    504 }
    505 
    506 - (void)setVolumeToMaximum:(id)sender
    507 {
    508     [self setVolume:[self maxVolume]];
    509 }
    510 
    511 - (void)decrementVolume
    512 {
    513     if (![_delegate mediaElement])
    514         return;
    515 
    516     double volume = [self volume] - 10;
    517     [self setVolume:max(volume, 0.)];
    518 }
    519 
    520 - (void)incrementVolume
    521 {
    522     if (![_delegate mediaElement])
    523         return;
    524 
    525     double volume = [self volume] + 10;
    526     [self setVolume:min(volume, [self maxVolume])];
    527 }
    528 
    529 - (double)volume
    530 {
    531     return [_delegate mediaElement] ? [_delegate mediaElement]->volume() * [self maxVolume] : 0;
    532 }
    533 
    534 - (void)setVolume:(double)volume
    535 {
    536     if (![_delegate mediaElement])
    537         return;
    538     WebCore::ExceptionCode e;
    539     if ([_delegate mediaElement]->muted())
    540         [_delegate mediaElement]->setMuted(false);
    541     [_delegate mediaElement]->setVolume(volume / [self maxVolume], e);
    542     [self updateVolume];
    543 }
    544 
    545 - (void)updatePlayButton
    546 {
    547     [_playButton setIntValue:[self playing]];
    548 }
    549 
    550 - (void)updateRate
    551 {
    552     BOOL playing = [self playing];
    553 
    554     // Keep the HUD visible when paused.
    555     if (!playing)
    556         [self fadeWindowIn];
    557     else if (!_mouseIsInHUD) {
    558         [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(fadeWindowOut) object:nil];
    559         [self performSelector:@selector(fadeWindowOut) withObject:nil afterDelay:HUDWindowFadeOutDelay];
    560     }
    561     [self updatePlayButton];
    562 }
    563 
    564 - (void)togglePlaying:(id)sender
    565 {
    566     [self setPlaying:![self playing]];
    567 }
    568 
    569 - (BOOL)playing
    570 {
    571     HTMLMediaElement* mediaElement = [_delegate mediaElement];
    572     if (!mediaElement)
    573         return NO;
    574 
    575     return !mediaElement->canPlay();
    576 }
    577 
    578 - (void)setPlaying:(BOOL)playing
    579 {
    580     HTMLMediaElement* mediaElement = [_delegate mediaElement];
    581 
    582     if (!mediaElement)
    583         return;
    584 
    585     if (playing)
    586         mediaElement->play(mediaElement->processingUserGesture());
    587     else
    588         mediaElement->pause(mediaElement->processingUserGesture());
    589 }
    590 
    591 static NSString *timeToString(double time)
    592 {
    593     ASSERT_ARG(time, time >= 0);
    594 
    595     if (!isfinite(time))
    596         time = 0;
    597 
    598     int seconds = fabs(time);
    599     int hours = seconds / (60 * 60);
    600     int minutes = (seconds / 60) % 60;
    601     seconds %= 60;
    602 
    603     if (hours)
    604         return [NSString stringWithFormat:@"%d:%02d:%02d", hours, minutes, seconds];
    605 
    606     return [NSString stringWithFormat:@"%02d:%02d", minutes, seconds];
    607 }
    608 
    609 - (NSString *)remainingTimeText
    610 {
    611     HTMLMediaElement* mediaElement = [_delegate mediaElement];
    612     if (!mediaElement)
    613         return @"";
    614 
    615     return [@"-" stringByAppendingString:timeToString(mediaElement->duration() - mediaElement->currentTime())];
    616 }
    617 
    618 - (NSString *)elapsedTimeText
    619 {
    620     if (![_delegate mediaElement])
    621         return @"";
    622 
    623     return timeToString([_delegate mediaElement]->currentTime());
    624 }
    625 
    626 // MARK: NSResponder
    627 
    628 - (void)mouseEntered:(NSEvent *)theEvent
    629 {
    630     // Make sure the HUD won't be hidden from now
    631     _mouseIsInHUD = YES;
    632     [self fadeWindowIn];
    633 }
    634 
    635 - (void)mouseExited:(NSEvent *)theEvent
    636 {
    637     _mouseIsInHUD = NO;
    638     [self fadeWindowIn];
    639 }
    640 
    641 - (void)rewind:(id)sender
    642 {
    643     if (![_delegate mediaElement])
    644         return;
    645     [_delegate mediaElement]->rewind(30);
    646 }
    647 
    648 - (void)fastForward:(id)sender
    649 {
    650     if (![_delegate mediaElement])
    651         return;
    652 }
    653 
    654 - (void)exitFullscreen:(id)sender
    655 {
    656     if (_isEndingFullscreen)
    657         return;
    658     _isEndingFullscreen = YES;
    659     [_delegate requestExitFullscreen];
    660 }
    661 
    662 // MARK: NSWindowDelegate
    663 
    664 - (void)windowDidExpose:(NSNotification *)notification
    665 {
    666     [self scheduleTimeUpdate];
    667 }
    668 
    669 - (void)windowDidClose:(NSNotification *)notification
    670 {
    671     [self unscheduleTimeUpdate];
    672 }
    673 
    674 @end
    675 
    676 #endif
    677