fc0f510c7a2cb24978d04a0c4806ed4c71c53201
[WebKit-https.git] / Source / WebCore / platform / mac / WebVideoFullscreenHUDWindowController.mm
1 /*
2  * Copyright (C) 2009-2017 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 #import "config.h"
26 #import "WebVideoFullscreenHUDWindowController.h"
27
28 #if ENABLE(VIDEO)
29
30 #import "FloatConversion.h"
31 #import <WebCore/HTMLVideoElement.h>
32 #import <WebCoreSystemInterface.h>
33 #import <pal/spi/cg/CoreGraphicsSPI.h>
34 #import <pal/spi/mac/QTKitSPI.h>
35 #import <wtf/SoftLinking.h>
36
37 SOFT_LINK_FRAMEWORK(QTKit)
38
39 SOFT_LINK_CLASS(QTKit, QTHUDBackgroundView)
40 SOFT_LINK_CLASS(QTKit, QTHUDButton)
41 SOFT_LINK_CLASS(QTKit, QTHUDSlider)
42 SOFT_LINK_CLASS(QTKit, QTHUDTimeline)
43
44 #define QTHUDBackgroundView getQTHUDBackgroundViewClass()
45 #define QTHUDButton getQTHUDButtonClass()
46 #define QTHUDSlider getQTHUDSliderClass()
47 #define QTHUDTimeline getQTHUDTimelineClass()
48
49 using namespace WebCore;
50
51 namespace WebCore {
52
53 enum class MediaUIControl {
54     Timeline,
55     Slider,
56     PlayPauseButton,
57     ExitFullscreenButton,
58     RewindButton,
59     FastForwardButton,
60     VolumeUpButton,
61     VolumeDownButton,
62 };
63
64 }
65
66 @interface WebVideoFullscreenHUDWindowController (Private) <NSWindowDelegate>
67
68 - (void)updateTime;
69 - (void)timelinePositionChanged:(id)sender;
70 - (float)currentTime;
71 - (void)setCurrentTime:(float)currentTime;
72 - (double)duration;
73
74 - (void)volumeChanged:(id)sender;
75 - (float)maxVolume;
76 - (float)volume;
77 - (void)setVolume:(float)volume;
78 - (void)decrementVolume;
79 - (void)incrementVolume;
80
81 - (void)updatePlayButton;
82 - (void)togglePlaying:(id)sender;
83 - (BOOL)playing;
84 - (void)setPlaying:(BOOL)playing;
85
86 - (void)rewind:(id)sender;
87 - (void)fastForward:(id)sender;
88
89 - (NSString *)remainingTimeText;
90 - (NSString *)elapsedTimeText;
91
92 - (void)exitFullscreen:(id)sender;
93 @end
94
95 @interface WebVideoFullscreenHUDWindow : NSWindow
96 @end
97
98 @implementation WebVideoFullscreenHUDWindow
99
100 - (id)initWithContentRect:(NSRect)contentRect styleMask:(NSUInteger)aStyle backing:(NSBackingStoreType)bufferingType defer:(BOOL)flag
101 {
102     UNUSED_PARAM(aStyle);
103     self = [super initWithContentRect:contentRect styleMask:NSWindowStyleMaskBorderless backing:bufferingType defer:flag];
104     if (!self)
105         return nil;
106
107     [self setOpaque:NO];
108     [self setBackgroundColor:[NSColor clearColor]];
109     [self setLevel:NSPopUpMenuWindowLevel];
110     [self setAcceptsMouseMovedEvents:YES];
111     [self setIgnoresMouseEvents:NO];
112     [self setMovableByWindowBackground:YES];
113
114     return self;
115 }
116
117 - (BOOL)canBecomeKeyWindow
118 {
119     return YES;
120 }
121
122 - (void)cancelOperation:(id)sender
123 {
124     UNUSED_PARAM(sender);
125     [[self windowController] exitFullscreen:self];
126 }
127
128 - (void)center
129 {
130     NSRect hudFrame = [self frame];
131     NSRect screenFrame = [[NSScreen mainScreen] frame];
132     [self setFrameTopLeftPoint:NSMakePoint(screenFrame.origin.x + (screenFrame.size.width - hudFrame.size.width) / 2,
133                                            screenFrame.origin.y + (screenFrame.size.height - hudFrame.size.height) / 6)];
134 }
135
136 - (void)keyDown:(NSEvent *)event
137 {
138     [super keyDown:event];
139     [[self windowController] fadeWindowIn];
140 }
141
142 - (BOOL)resignFirstResponder
143 {
144     return NO;
145 }
146
147 - (BOOL)performKeyEquivalent:(NSEvent *)event
148 {
149     // Block all command key events while the fullscreen window is up.
150     if ([event type] != NSEventTypeKeyDown)
151         return NO;
152     
153     if (!([event modifierFlags] & NSEventModifierFlagCommand))
154         return NO;
155     return YES;
156 }
157
158 @end
159
160 static const CGFloat windowHeight = 59;
161 static const CGFloat windowWidth = 438;
162
163 static const NSTimeInterval HUDWindowFadeOutDelay = 3;
164
165 @implementation WebVideoFullscreenHUDWindowController
166
167 - (id)init
168 {
169     NSWindow *window = [[WebVideoFullscreenHUDWindow alloc] initWithContentRect:NSMakeRect(0, 0, windowWidth, windowHeight) styleMask:NSWindowStyleMaskBorderless backing:NSBackingStoreBuffered defer:NO];
170     self = [super initWithWindow:window];
171     [window setDelegate:self];
172     [window release];
173     if (!self)
174         return nil;
175     [self windowDidLoad];
176     return self;
177 }
178
179 - (void)dealloc
180 {
181     ASSERT(!_timelineUpdateTimer);
182     ASSERT(!_area);
183     ASSERT(!_isScrubbing);
184     [_timeline release];
185     [_remainingTimeText release];
186     [_elapsedTimeText release];
187     [_volumeSlider release];
188     [_playButton release];
189     [super dealloc];
190 }
191
192 - (void)setArea:(NSTrackingArea *)area
193 {
194     if (area == _area)
195         return;
196     [_area release];
197     _area = [area retain];
198 }
199
200 - (void)keyDown:(NSEvent *)event
201 {
202     NSString *charactersIgnoringModifiers = [event charactersIgnoringModifiers];
203     if ([charactersIgnoringModifiers length] == 1) {
204         switch ([charactersIgnoringModifiers characterAtIndex:0]) {
205             case ' ':
206                 [self togglePlaying:nil];
207                 return;
208             case NSUpArrowFunctionKey:
209                 if ([event modifierFlags] & NSEventModifierFlagOption)
210                     [self setVolume:[self maxVolume]];
211                 else
212                     [self incrementVolume];
213                 return;
214             case NSDownArrowFunctionKey:
215                 if ([event modifierFlags] & NSEventModifierFlagOption)
216                     [self setVolume:0];
217                 else
218                     [self decrementVolume];
219                 return;
220             default:
221                 break;
222         }
223     }
224
225     [super keyDown:event];
226 }
227
228 - (id <WebVideoFullscreenHUDWindowControllerDelegate>)delegate
229 {
230     return _delegate;
231 }
232
233 - (void)setDelegate:(id <WebVideoFullscreenHUDWindowControllerDelegate>)delegate
234 {
235     _delegate = delegate;
236 }
237
238 - (void)scheduleTimeUpdate
239 {
240     [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(unscheduleTimeUpdate) object:self];
241
242     // First, update right away, then schedule future update
243     [self updateTime];
244     [self updatePlayButton];
245
246     [_timelineUpdateTimer invalidate];
247     [_timelineUpdateTimer release];
248
249     // Note that this creates a retain cycle between the window and us.
250     _timelineUpdateTimer = [[NSTimer timerWithTimeInterval:0.25 target:self selector:@selector(updateTime) userInfo:nil repeats:YES] retain];
251     [[NSRunLoop currentRunLoop] addTimer:_timelineUpdateTimer forMode:NSRunLoopCommonModes];
252 }
253
254 - (void)unscheduleTimeUpdate
255 {
256     [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(unscheduleTimeUpdate) object:nil];
257
258     [_timelineUpdateTimer invalidate];
259     [_timelineUpdateTimer release];
260     _timelineUpdateTimer = nil;
261 }
262
263 - (void)fadeWindowIn
264 {
265     NSWindow *window = [self window];
266     if (![window isVisible])
267         [window setAlphaValue:0];
268
269     [window makeKeyAndOrderFront:self];
270     [[window animator] setAlphaValue:1];
271     [self scheduleTimeUpdate];
272
273     [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(fadeWindowOut) object:nil];
274     if (!_mouseIsInHUD && [self playing])   // Don't fade out when paused.
275         [self performSelector:@selector(fadeWindowOut) withObject:nil afterDelay:HUDWindowFadeOutDelay];
276 }
277
278 - (void)fadeWindowOut
279 {
280     [NSCursor setHiddenUntilMouseMoves:YES];
281     [[[self window] animator] setAlphaValue:0];
282     [self performSelector:@selector(unscheduleTimeUpdate) withObject:nil afterDelay:1];
283 }
284
285 - (void)closeWindow
286 {
287     [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(fadeWindowOut) object:nil];
288     [self unscheduleTimeUpdate];
289     NSWindow *window = [self window];
290     [[window contentView] removeTrackingArea:_area];
291     [self setArea:nil];
292     [window close];
293     [window setDelegate:nil];
294     [self setWindow:nil];
295 }
296
297 static NSControl *createMediaUIControl(MediaUIControl controlType)
298 {
299     switch (controlType) {
300     case MediaUIControl::Timeline: {
301         NSSlider *slider = [[QTHUDTimeline alloc] init];
302         [[slider cell] setContinuous:YES];
303         return slider;
304     }
305     case MediaUIControl::Slider: {
306         NSButton *slider = [[QTHUDSlider alloc] init];
307         [[slider cell] setContinuous:YES];
308         return slider;
309     }
310     case MediaUIControl::PlayPauseButton: {
311         NSButton *button = [[QTHUDButton alloc] init];
312         [button setImage:[NSImage imageNamed:@"NSPlayTemplate"]];
313         [button setAlternateImage:[NSImage imageNamed:@"NSPauseQTPrivateTemplate"]];
314
315         [[button cell] setShowsStateBy:NSContentsCellMask];
316         [button setBordered:NO];
317         return button;
318     }
319     case MediaUIControl::ExitFullscreenButton: {
320         NSButton *button = [[QTHUDButton alloc] init];
321         [button setImage:[NSImage imageNamed:@"NSExitFullScreenTemplate"]];
322         [button setBordered:NO];
323         return button;
324     }
325     case MediaUIControl::RewindButton: {
326         NSButton *button = [[QTHUDButton alloc] init];
327         [button setImage:[NSImage imageNamed:@"NSRewindTemplate"]];
328         [button setBordered:NO];
329         return button;
330     }
331     case MediaUIControl::FastForwardButton: {
332         NSButton *button = [[QTHUDButton alloc] init];
333         [button setImage:[NSImage imageNamed:@"NSFastForwardTemplate"]];
334         [button setBordered:NO];
335         return button;
336     }
337     case MediaUIControl::VolumeUpButton: {
338         NSButton *button = [[QTHUDButton alloc] init];
339         [button setImage:[NSImage imageNamed:@"NSAudioOutputVolumeHighTemplate"]];
340         [button setBordered:NO];
341         return button;
342     }
343     case MediaUIControl::VolumeDownButton: {
344         NSButton *button = [[QTHUDButton alloc] init];
345         [button setImage:[NSImage imageNamed:@"NSAudioOutputVolumeLowTemplate"]];
346         [button setBordered:NO];
347         return button;
348     }
349     }
350
351     ASSERT_NOT_REACHED();
352     return nil;
353 }
354
355 static NSControl *createControlWithMediaUIControlType(MediaUIControl controlType, NSRect frame)
356 {
357     NSControl *control = createMediaUIControl(controlType);
358     control.frame = frame;
359     return control;
360 }
361
362 static NSTextField *createTimeTextField(NSRect frame)
363 {
364     NSTextField *textField = [[NSTextField alloc] initWithFrame:frame];
365     [textField setTextColor:[NSColor whiteColor]];
366     [textField setBordered:NO];
367     [textField setFont:[NSFont boldSystemFontOfSize:10]];
368     [textField setDrawsBackground:NO];
369     [textField setBezeled:NO];
370     [textField setEditable:NO];
371     [textField setSelectable:NO];
372     return textField;
373 }
374
375 static NSView *createMediaUIBackgroundView()
376 {
377     id view = [[QTHUDBackgroundView alloc] init];
378
379     const CGFloat quickTimePlayerHUDHeight = 59;
380     const CGFloat quickTimePlayerHUDContentBorderPosition = 38;
381
382 #pragma clang diagnostic push
383 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
384     [view setContentBorderPosition:quickTimePlayerHUDContentBorderPosition / quickTimePlayerHUDHeight];
385 #pragma clang diagnostic pop
386
387     return view;
388 }
389
390 - (void)windowDidLoad
391 {
392     static const CGFloat horizontalMargin = 10;
393     static const CGFloat playButtonWidth = 41;
394     static const CGFloat playButtonHeight = 35;
395     static const CGFloat playButtonTopMargin = 4;
396     static const CGFloat volumeSliderWidth = 50;
397     static const CGFloat volumeSliderHeight = 13;
398     static const CGFloat volumeButtonWidth = 18;
399     static const CGFloat volumeButtonHeight = 16;
400     static const CGFloat volumeUpButtonLeftMargin = 4;
401     static const CGFloat volumeControlsTopMargin = 13;
402     static const CGFloat exitFullscreenButtonWidth = 25;
403     static const CGFloat exitFullscreenButtonHeight = 21;
404     static const CGFloat exitFullscreenButtonTopMargin = 11;
405     static const CGFloat timelineWidth = 315;
406     static const CGFloat timelineHeight = 14;
407     static const CGFloat timelineBottomMargin = 7;
408     static const CGFloat timeTextFieldWidth = 54;
409     static const CGFloat timeTextFieldHeight = 13;
410     static const CGFloat timeTextFieldHorizontalMargin = 7;
411
412     NSWindow *window = [self window];
413     ASSERT(window);
414
415     NSView *background = createMediaUIBackgroundView();
416
417     [window setContentView:background];
418     _area = [[NSTrackingArea alloc] initWithRect:[background bounds] options:NSTrackingMouseEnteredAndExited | NSTrackingActiveAlways owner:self userInfo:nil];
419     [background addTrackingArea:_area];
420     [background release];    
421
422     NSView *contentView = [window contentView];
423
424     CGFloat center = CGFloor((windowWidth - playButtonWidth) / 2);
425     _playButton = (NSButton *)createControlWithMediaUIControlType(MediaUIControl::PlayPauseButton, NSMakeRect(center, windowHeight - playButtonTopMargin - playButtonHeight, playButtonWidth, playButtonHeight));
426     ASSERT([_playButton isKindOfClass:[NSButton class]]);
427     [_playButton setTarget:self];
428     [_playButton setAction:@selector(togglePlaying:)];
429     [contentView addSubview:_playButton];
430
431     CGFloat closeToRight = windowWidth - horizontalMargin - exitFullscreenButtonWidth;
432     NSControl *exitFullscreenButton = createControlWithMediaUIControlType(MediaUIControl::ExitFullscreenButton, NSMakeRect(closeToRight, windowHeight - exitFullscreenButtonTopMargin - exitFullscreenButtonHeight, exitFullscreenButtonWidth, exitFullscreenButtonHeight));
433     [exitFullscreenButton setAction:@selector(exitFullscreen:)];
434     [exitFullscreenButton setTarget:self];
435     [contentView addSubview:exitFullscreenButton];
436     [exitFullscreenButton release];
437     
438     CGFloat volumeControlsBottom = windowHeight - volumeControlsTopMargin - volumeButtonHeight;
439     CGFloat left = horizontalMargin;
440     NSControl *volumeDownButton = createControlWithMediaUIControlType(MediaUIControl::VolumeDownButton, NSMakeRect(left, volumeControlsBottom, volumeButtonWidth, volumeButtonHeight));
441     [contentView addSubview:volumeDownButton];
442     [volumeDownButton setTarget:self];
443     [volumeDownButton setAction:@selector(setVolumeToZero:)];
444     [volumeDownButton release];
445
446     left += volumeButtonWidth;
447     _volumeSlider = createControlWithMediaUIControlType(MediaUIControl::Slider, NSMakeRect(left, volumeControlsBottom + CGFloor((volumeButtonHeight - volumeSliderHeight) / 2), volumeSliderWidth, volumeSliderHeight));
448     [_volumeSlider setValue:[NSNumber numberWithDouble:[self maxVolume]] forKey:@"maxValue"];
449     [_volumeSlider setTarget:self];
450     [_volumeSlider setAction:@selector(volumeChanged:)];
451     [contentView addSubview:_volumeSlider];
452
453     left += volumeSliderWidth + volumeUpButtonLeftMargin;
454     NSControl *volumeUpButton = createControlWithMediaUIControlType(MediaUIControl::VolumeUpButton, NSMakeRect(left, volumeControlsBottom, volumeButtonWidth, volumeButtonHeight));
455     [volumeUpButton setTarget:self];
456     [volumeUpButton setAction:@selector(setVolumeToMaximum:)];
457     [contentView addSubview:volumeUpButton];
458     [volumeUpButton release];
459
460     _timeline = createMediaUIControl(MediaUIControl::Timeline);
461
462     [_timeline setTarget:self];
463     [_timeline setAction:@selector(timelinePositionChanged:)];
464     [_timeline setFrame:NSMakeRect(CGFloor((windowWidth - timelineWidth) / 2), timelineBottomMargin, timelineWidth, timelineHeight)];
465     [contentView addSubview:_timeline];
466
467     _elapsedTimeText = createTimeTextField(NSMakeRect(timeTextFieldHorizontalMargin, timelineBottomMargin, timeTextFieldWidth, timeTextFieldHeight));
468     [_elapsedTimeText setAlignment:NSTextAlignmentLeft];
469     [contentView addSubview:_elapsedTimeText];
470
471     _remainingTimeText = createTimeTextField(NSMakeRect(windowWidth - timeTextFieldHorizontalMargin - timeTextFieldWidth, timelineBottomMargin, timeTextFieldWidth, timeTextFieldHeight));
472     [_remainingTimeText setAlignment:NSTextAlignmentRight];
473     [contentView addSubview:_remainingTimeText];
474
475     [window recalculateKeyViewLoop];
476     [window setInitialFirstResponder:_playButton];
477     [window center];
478 }
479
480 - (void)updateVolume
481 {
482     [_volumeSlider setFloatValue:[self volume]];
483 }
484
485 - (void)updateTime
486 {
487     [self updateVolume];
488
489     [_timeline setFloatValue:[self currentTime]];
490     [_timeline setValue:[NSNumber numberWithDouble:[self duration]] forKey:@"maxValue"];
491
492     [_remainingTimeText setStringValue:[self remainingTimeText]];
493     [_elapsedTimeText setStringValue:[self elapsedTimeText]];
494 }
495
496 - (void)endScrubbing
497 {
498     ASSERT(_isScrubbing);
499     _isScrubbing = NO;
500     if (HTMLVideoElement* videoElement = [_delegate videoElement])
501         videoElement->endScrubbing();
502 }
503
504 - (void)timelinePositionChanged:(id)sender
505 {
506     UNUSED_PARAM(sender);
507     [self setCurrentTime:[_timeline floatValue]];
508     if (!_isScrubbing) {
509         _isScrubbing = YES;
510         if (HTMLVideoElement* videoElement = [_delegate videoElement])
511             videoElement->beginScrubbing();
512         static NSArray *endScrubbingModes = [[NSArray alloc] initWithObjects:NSDefaultRunLoopMode, NSModalPanelRunLoopMode, nil];
513         // Schedule -endScrubbing for when leaving mouse tracking mode.
514         [[NSRunLoop currentRunLoop] performSelector:@selector(endScrubbing) target:self argument:nil order:0 modes:endScrubbingModes];
515     }
516 }
517
518 - (float)currentTime
519 {
520     return [_delegate videoElement] ? [_delegate videoElement]->currentTime() : 0;
521 }
522
523 - (void)setCurrentTime:(float)currentTime
524 {
525     if (![_delegate videoElement])
526         return;
527     [_delegate videoElement]->setCurrentTime(currentTime);
528     [self updateTime];
529 }
530
531 - (double)duration
532 {
533     return [_delegate videoElement] ? [_delegate videoElement]->duration() : 0;
534 }
535
536 - (float)maxVolume
537 {
538     // Set the volume slider resolution
539     return 100;
540 }
541
542 - (void)volumeChanged:(id)sender
543 {
544     UNUSED_PARAM(sender);
545     [self setVolume:[_volumeSlider floatValue]];
546 }
547
548 - (void)setVolumeToZero:(id)sender
549 {
550     UNUSED_PARAM(sender);
551     [self setVolume:0];
552 }
553
554 - (void)setVolumeToMaximum:(id)sender
555 {
556     UNUSED_PARAM(sender);
557     [self setVolume:[self maxVolume]];
558 }
559
560 - (void)decrementVolume
561 {
562     if (![_delegate videoElement])
563         return;
564
565     float volume = [self volume] - 10;
566     [self setVolume:MAX(volume, 0)];
567 }
568
569 - (void)incrementVolume
570 {
571     if (![_delegate videoElement])
572         return;
573
574     float volume = [self volume] + 10;
575     [self setVolume:std::min(volume, [self maxVolume])];
576 }
577
578 - (float)volume
579 {
580     return [_delegate videoElement] ? [_delegate videoElement]->volume() * [self maxVolume] : 0;
581 }
582
583 - (void)setVolume:(float)volume
584 {
585     if (![_delegate videoElement])
586         return;
587     if ([_delegate videoElement]->muted())
588         [_delegate videoElement]->setMuted(false);
589     [_delegate videoElement]->setVolume(volume / [self maxVolume]);
590     [self updateVolume];
591 }
592
593 - (void)updatePlayButton
594 {
595     [_playButton setIntValue:[self playing]];
596 }
597
598 - (void)updateRate
599 {
600     BOOL playing = [self playing];
601
602     // Keep the HUD visible when paused.
603     if (!playing)
604         [self fadeWindowIn];
605     else if (!_mouseIsInHUD) {
606         [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(fadeWindowOut) object:nil];
607         [self performSelector:@selector(fadeWindowOut) withObject:nil afterDelay:HUDWindowFadeOutDelay];
608     }
609     [self updatePlayButton];
610 }
611
612 - (void)togglePlaying:(id)sender
613 {
614     UNUSED_PARAM(sender);
615     [self setPlaying:![self playing]];
616 }
617
618 - (BOOL)playing
619 {
620     HTMLVideoElement* videoElement = [_delegate videoElement];
621     if (!videoElement)
622         return NO;
623
624     return !videoElement->canPlay();
625 }
626
627 - (void)setPlaying:(BOOL)playing
628 {
629     HTMLVideoElement* videoElement = [_delegate videoElement];
630
631     if (!videoElement)
632         return;
633
634     if (playing)
635         videoElement->play();
636     else
637         videoElement->pause();
638 }
639
640 static NSString *timeToString(double time)
641 {
642     ASSERT_ARG(time, time >= 0);
643
644     if (!std::isfinite(time))
645         time = 0;
646
647     int seconds = narrowPrecisionToFloat(std::abs(time));
648     int hours = seconds / (60 * 60);
649     int minutes = (seconds / 60) % 60;
650     seconds %= 60;
651
652     if (hours)
653         return [NSString stringWithFormat:@"%d:%02d:%02d", hours, minutes, seconds];
654
655     return [NSString stringWithFormat:@"%02d:%02d", minutes, seconds];    
656 }
657
658 - (NSString *)remainingTimeText
659 {
660     HTMLVideoElement* videoElement = [_delegate videoElement];
661     if (!videoElement)
662         return @"";
663
664     double remainingTime = 0;
665
666     if (std::isfinite(videoElement->duration()) && std::isfinite(videoElement->currentTime()))
667         remainingTime = videoElement->duration() - videoElement->currentTime();
668
669     return [@"-" stringByAppendingString:timeToString(remainingTime)];
670 }
671
672 - (NSString *)elapsedTimeText
673 {
674     if (![_delegate videoElement])
675         return @"";
676
677     return timeToString([_delegate videoElement]->currentTime());
678 }
679
680 // MARK: NSResponder
681
682 - (void)mouseEntered:(NSEvent *)theEvent
683 {
684     UNUSED_PARAM(theEvent);
685     // Make sure the HUD won't be hidden from now
686     _mouseIsInHUD = YES;
687     [self fadeWindowIn];
688 }
689
690 - (void)mouseExited:(NSEvent *)theEvent
691 {
692     UNUSED_PARAM(theEvent);
693     _mouseIsInHUD = NO;
694     [self fadeWindowIn];
695 }
696
697 - (void)rewind:(id)sender
698 {
699     UNUSED_PARAM(sender);
700     if (![_delegate videoElement])
701         return;
702     [_delegate videoElement]->rewind(30);
703 }
704
705 - (void)fastForward:(id)sender
706 {
707     UNUSED_PARAM(sender);
708     if (![_delegate videoElement])
709         return;
710 }
711
712 - (void)exitFullscreen:(id)sender
713 {
714     UNUSED_PARAM(sender);
715     if (_isEndingFullscreen)
716         return;
717     _isEndingFullscreen = YES;
718     [_delegate requestExitFullscreen]; 
719 }
720
721 // MARK: NSWindowDelegate
722
723 - (void)windowDidExpose:(NSNotification *)notification
724 {
725     UNUSED_PARAM(notification);
726     [self scheduleTimeUpdate];
727 }
728
729 - (void)windowDidClose:(NSNotification *)notification
730 {
731     UNUSED_PARAM(notification);
732     [self unscheduleTimeUpdate];
733 }
734
735 @end
736
737 #endif