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