1/*
2 * Copyright (C) 2010 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 COMPUTER, INC. ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#include "config.h"
27
28#if ENABLE(VIDEO)
29
30#include "FullscreenVideoController.h"
31
32#include "WebKitDLL.h"
33#include <ApplicationServices/ApplicationServices.h>
34#include <WebCore/BitmapInfo.h>
35#include <WebCore/Font.h>
36#include <WebCore/FontSelector.h>
37#include <WebCore/GraphicsContext.h>
38#include <WebCore/TextRun.h>
39#include <WebKitSystemInterface/WebKitSystemInterface.h>
40#include <windowsx.h>
41#include <wtf/StdLibExtras.h>
42
43using namespace std;
44using namespace WebCore;
45
46static const float timerInterval = 0.033;
47
48// HUD Size
49static const int windowHeight = 59;
50static const int windowWidth = 438;
51
52// Margins and button sizes
53static const int margin = 9;
54static const int marginTop = 9;
55static const int buttonSize = 25;
56static const int buttonMiniSize = 16;
57static const int volumeSliderWidth = 50;
58static const int timeSliderWidth = 310;
59static const int sliderHeight = 8;
60static const int volumeSliderButtonSize = 10;
61static const int timeSliderButtonSize = 8;
62static const int textSize = 11;
63static const float initialHUDPositionY = 0.9; // Initial Y position of HUD in percentage from top of screen
64
65// Background values
66static const int borderRadius = 12;
67static const int borderThickness = 2;
68
69// Colors
70static const unsigned int backgroundColor = 0xA0202020;
71static const unsigned int borderColor = 0xFFA0A0A0;
72static const unsigned int sliderGutterColor = 0xFF141414;
73static const unsigned int sliderButtonColor = 0xFF808080;
74static const unsigned int textColor = 0xFFFFFFFF;
75
76HUDButton::HUDButton(HUDButtonType type, const IntPoint& position)
77    : HUDWidget(IntRect(position, IntSize()))
78    , m_type(type)
79    , m_showAltButton(false)
80{
81    const char* buttonResource = 0;
82    const char* buttonResourceAlt = 0;
83    switch (m_type) {
84    case PlayPauseButton:
85        buttonResource = "fsVideoPlay";
86        buttonResourceAlt = "fsVideoPause";
87        break;
88    case TimeSliderButton:
89        break;
90    case VolumeUpButton:
91        buttonResource = "fsVideoAudioVolumeHigh";
92        break;
93    case VolumeSliderButton:
94        break;
95    case VolumeDownButton:
96        buttonResource = "fsVideoAudioVolumeLow";
97        break;
98    case ExitFullscreenButton:
99        buttonResource = "fsVideoExitFullscreen";
100        break;
101    }
102
103    if (buttonResource) {
104        m_buttonImage = Image::loadPlatformResource(buttonResource);
105        m_rect.setWidth(m_buttonImage->width());
106        m_rect.setHeight(m_buttonImage->height());
107    }
108    if (buttonResourceAlt)
109        m_buttonImageAlt = Image::loadPlatformResource(buttonResourceAlt);
110}
111
112void HUDButton::draw(GraphicsContext& context)
113{
114    Image* image = (m_showAltButton && m_buttonImageAlt) ? m_buttonImageAlt.get() : m_buttonImage.get();
115    context.drawImage(image, DeviceColorSpace, m_rect.location());
116}
117
118HUDSlider::HUDSlider(HUDSliderButtonShape shape, int buttonSize, const IntRect& rect)
119    : HUDWidget(rect)
120    , m_buttonShape(shape)
121    , m_buttonSize(buttonSize)
122    , m_buttonPosition(0)
123    , m_dragStartOffset(0)
124{
125}
126
127void HUDSlider::draw(GraphicsContext& context)
128{
129    // Draw gutter
130    IntSize radius(m_rect.height() / 2, m_rect.height() / 2);
131    context.fillRoundedRect(m_rect, radius, radius, radius, radius, Color(sliderGutterColor), DeviceColorSpace);
132
133    // Draw button
134    context.setStrokeColor(Color(sliderButtonColor), DeviceColorSpace);
135    context.setFillColor(Color(sliderButtonColor), DeviceColorSpace);
136
137    if (m_buttonShape == RoundButton) {
138        context.drawEllipse(IntRect(m_rect.location().x() + m_buttonPosition, m_rect.location().y() - (m_buttonSize - m_rect.height()) / 2, m_buttonSize, m_buttonSize));
139        return;
140    }
141
142    // Draw a diamond
143    FloatPoint points[4];
144    float half = static_cast<float>(m_buttonSize) / 2;
145    points[0].setX(m_rect.location().x() + m_buttonPosition + half);
146    points[0].setY(m_rect.location().y());
147    points[1].setX(m_rect.location().x() + m_buttonPosition + m_buttonSize);
148    points[1].setY(m_rect.location().y() + half);
149    points[2].setX(m_rect.location().x() + m_buttonPosition + half);
150    points[2].setY(m_rect.location().y() + m_buttonSize);
151    points[3].setX(m_rect.location().x() + m_buttonPosition);
152    points[3].setY(m_rect.location().y() + half);
153    context.drawConvexPolygon(4, points, true);
154}
155
156void HUDSlider::drag(const IntPoint& point, bool start)
157{
158    if (start) {
159        // When we start, we need to snap the slider position to the x position if we clicked the gutter.
160        // But if we click the button, we need to drag relative to where we clicked down. We only need
161        // to check X because we would not even get here unless Y were already inside.
162        int relativeX = point.x() - m_rect.location().x();
163        if (relativeX >= m_buttonPosition && relativeX <= m_buttonPosition + m_buttonSize)
164            m_dragStartOffset = point.x() - m_buttonPosition;
165        else
166            m_dragStartOffset = m_rect.location().x() + m_buttonSize / 2;
167    }
168
169    m_buttonPosition = max(0, min(m_rect.width() - m_buttonSize, point.x() - m_dragStartOffset));
170}
171
172FullscreenVideoController::FullscreenVideoController()
173    : m_hudWindow(0)
174    , m_videoWindow(0)
175    , m_playPauseButton(HUDButton::PlayPauseButton, IntPoint((windowWidth - buttonSize) / 2, marginTop))
176    , m_timeSliderButton(HUDButton::TimeSliderButton, IntPoint(0, 0))
177    , m_volumeUpButton(HUDButton::VolumeUpButton, IntPoint(margin + buttonMiniSize + volumeSliderWidth + buttonMiniSize / 2, marginTop + (buttonSize - buttonMiniSize) / 2))
178    , m_volumeSliderButton(HUDButton::VolumeSliderButton, IntPoint(0, 0))
179    , m_volumeDownButton(HUDButton::VolumeDownButton, IntPoint(margin, marginTop + (buttonSize - buttonMiniSize) / 2))
180    , m_exitFullscreenButton(HUDButton::ExitFullscreenButton, IntPoint(windowWidth - 2 * margin - buttonMiniSize, marginTop + (buttonSize - buttonMiniSize) / 2))
181    , m_volumeSlider(HUDSlider::RoundButton, volumeSliderButtonSize, IntRect(IntPoint(margin + buttonMiniSize, marginTop + (buttonSize - buttonMiniSize) / 2 + buttonMiniSize / 2 - sliderHeight / 2), IntSize(volumeSliderWidth, sliderHeight)))
182    , m_timeSlider(HUDSlider::DiamondButton, timeSliderButtonSize, IntRect(IntPoint(windowWidth / 2 - timeSliderWidth / 2, windowHeight - margin - sliderHeight), IntSize(timeSliderWidth, sliderHeight)))
183    , m_hitWidget(0)
184    , m_movingWindow(false)
185    , m_timer(this, &FullscreenVideoController::timerFired)
186{
187}
188
189FullscreenVideoController::~FullscreenVideoController()
190{
191    if (movie())
192        movie()->exitFullscreen();
193}
194
195QTMovieWin* FullscreenVideoController::movie() const
196{
197    return m_mediaElement ? reinterpret_cast<QTMovieWin*>(m_mediaElement->platformMedia().qtMovie) : 0;
198}
199
200void FullscreenVideoController::setMediaElement(HTMLMediaElement* mediaElement)
201{
202    if (mediaElement == m_mediaElement)
203        return;
204
205    m_mediaElement = mediaElement;
206    if (!m_mediaElement) {
207        // Can't do full-screen, just get out
208        exitFullscreen();
209    }
210}
211
212void FullscreenVideoController::enterFullscreen()
213{
214    if (!movie())
215        return;
216
217    m_videoWindow = movie()->enterFullscreen(this);
218
219    RECT windowRect;
220    GetClientRect(m_videoWindow, &windowRect);
221    m_fullscreenSize.setWidth(windowRect.right - windowRect.left);
222    m_fullscreenSize.setHeight(windowRect.bottom - windowRect.top);
223
224    createHUDWindow();
225}
226
227void FullscreenVideoController::exitFullscreen()
228{
229    if (movie())
230        movie()->exitFullscreen();
231
232    m_videoWindow = 0;
233    SetWindowLongPtr(m_hudWindow, 0, 0);
234    DestroyWindow(m_hudWindow);
235    m_hudWindow = 0;
236}
237
238bool FullscreenVideoController::canPlay() const
239{
240    return m_mediaElement && m_mediaElement->canPlay();
241}
242
243void FullscreenVideoController::play()
244{
245    if (m_mediaElement)
246        m_mediaElement->play(m_mediaElement->processingUserGesture());
247}
248
249void FullscreenVideoController::pause()
250{
251    if (m_mediaElement)
252        m_mediaElement->pause(m_mediaElement->processingUserGesture());
253}
254
255float FullscreenVideoController::volume() const
256{
257    return m_mediaElement ? m_mediaElement->volume() : 0;
258}
259
260void FullscreenVideoController::setVolume(float volume)
261{
262    if (m_mediaElement) {
263        ExceptionCode ec;
264        m_mediaElement->setVolume(volume, ec);
265    }
266}
267
268float FullscreenVideoController::currentTime() const
269{
270    return m_mediaElement ? m_mediaElement->currentTime() : 0;
271}
272
273void FullscreenVideoController::setCurrentTime(float value)
274{
275    if (m_mediaElement) {
276        ExceptionCode ec;
277        m_mediaElement->setCurrentTime(value, ec);
278    }
279}
280
281float FullscreenVideoController::duration() const
282{
283    return m_mediaElement ? m_mediaElement->duration() : 0;
284}
285
286void FullscreenVideoController::beginScrubbing()
287{
288    if (m_mediaElement)
289        m_mediaElement->beginScrubbing();
290}
291
292void FullscreenVideoController::endScrubbing()
293{
294    if (m_mediaElement)
295        m_mediaElement->endScrubbing();
296}
297
298LRESULT FullscreenVideoController::fullscreenClientWndProc(HWND wnd, UINT message, WPARAM wParam, LPARAM lParam)
299{
300    switch (message) {
301    case WM_CHAR:
302        onChar(wParam);
303        break;
304    case WM_LBUTTONDOWN:
305        onMouseDown(IntPoint(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)));
306        break;
307    case WM_MOUSEMOVE:
308        onMouseMove(IntPoint(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)));
309        break;
310    case WM_LBUTTONUP:
311        onMouseUp(IntPoint(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)));
312        break;
313    }
314
315    return DefWindowProc(wnd, message, wParam, lParam);
316}
317
318static const LPCWSTR fullscreenVideeoHUDWindowClassName = L"fullscreenVideeoHUDWindowClass";
319
320void FullscreenVideoController::registerHUDWindowClass()
321{
322    static bool haveRegisteredHUDWindowClass;
323    if (haveRegisteredHUDWindowClass)
324        return;
325
326    haveRegisteredHUDWindowClass = true;
327
328    WNDCLASSEX wcex;
329
330    wcex.cbSize = sizeof(WNDCLASSEX);
331
332    wcex.style = CS_HREDRAW | CS_VREDRAW;
333    wcex.lpfnWndProc = hudWndProc;
334    wcex.cbClsExtra = 0;
335    wcex.cbWndExtra = 4;
336    wcex.hInstance = gInstance;
337    wcex.hIcon = 0;
338    wcex.hCursor = LoadCursor(0, IDC_ARROW);
339    wcex.hbrBackground = 0;
340    wcex.lpszMenuName = 0;
341    wcex.lpszClassName = fullscreenVideeoHUDWindowClassName;
342    wcex.hIconSm = 0;
343
344    RegisterClassEx(&wcex);
345}
346
347void FullscreenVideoController::createHUDWindow()
348{
349    m_hudPosition.setX((m_fullscreenSize.width() - windowWidth) / 2);
350    m_hudPosition.setY(m_fullscreenSize.height() * initialHUDPositionY - windowHeight / 2);
351
352    // Local variable that will hold the returned pixels. No need to cleanup this value. It
353    // will get cleaned up when m_bitmap is destroyed in the dtor
354    void* pixels;
355    BitmapInfo bitmapInfo = BitmapInfo::createBottomUp(IntSize(windowWidth, windowHeight));
356    m_bitmap.set(::CreateDIBSection(0, &bitmapInfo, DIB_RGB_COLORS, &pixels, 0, 0));
357
358    // Dirty the window so the HUD draws
359    RECT clearRect = { m_hudPosition.x(), m_hudPosition.y(), m_hudPosition.x() + windowWidth, m_hudPosition.y() + windowHeight };
360    InvalidateRect(m_videoWindow, &clearRect, true);
361
362    m_playPauseButton.setShowAltButton(!canPlay());
363    m_volumeSlider.setValue(volume());
364    m_timeSlider.setValue(currentTime() / duration());
365
366    if (!canPlay())
367        m_timer.startRepeating(timerInterval);
368
369    registerHUDWindowClass();
370
371    m_hudWindow = CreateWindowEx(WS_EX_LAYERED | WS_EX_TRANSPARENT | WS_EX_TOPMOST | WS_EX_TOOLWINDOW,
372        fullscreenVideeoHUDWindowClassName, 0, WS_POPUP | WS_VISIBLE,
373        m_hudPosition.x(), m_hudPosition.y(), 0, 0, 0, 0, gInstance, 0);
374    ASSERT(::IsWindow(m_hudWindow));
375    SetWindowLongPtr(m_hudWindow, 0, reinterpret_cast<LONG_PTR>(this));
376
377    draw();
378}
379
380static String timeToString(float time)
381{
382    if (!isfinite(time))
383        time = 0;
384    int seconds = fabsf(time);
385    int hours = seconds / (60 * 60);
386    int minutes = (seconds / 60) % 60;
387    seconds %= 60;
388
389    if (hours) {
390        if (hours > 9)
391            return String::format("%s%02d:%02d:%02d", (time < 0 ? "-" : ""), hours, minutes, seconds);
392        return String::format("%s%01d:%02d:%02d", (time < 0 ? "-" : ""), hours, minutes, seconds);
393    }
394
395    return String::format("%s%02d:%02d", (time < 0 ? "-" : ""), minutes, seconds);
396}
397
398void FullscreenVideoController::draw()
399{
400    HDC windowDC = GetDC(m_hudWindow);
401    HDC bitmapDC = CreateCompatibleDC(windowDC);
402    ::ReleaseDC(m_hudWindow, windowDC);
403    SelectObject(bitmapDC, m_bitmap.get());
404
405    GraphicsContext context(bitmapDC, true);
406
407    context.save();
408
409    // Draw the background
410    IntSize outerRadius(borderRadius, borderRadius);
411    IntRect outerRect(0, 0, windowWidth, windowHeight);
412    IntSize innerRadius(borderRadius - borderThickness, borderRadius - borderThickness);
413    IntRect innerRect(borderThickness, borderThickness, windowWidth - borderThickness * 2, windowHeight - borderThickness * 2);
414
415    context.fillRoundedRect(outerRect, outerRadius, outerRadius, outerRadius, outerRadius, Color(borderColor), DeviceColorSpace);
416    context.setCompositeOperation(CompositeCopy);
417    context.fillRoundedRect(innerRect, innerRadius, innerRadius, innerRadius, innerRadius, Color(backgroundColor), DeviceColorSpace);
418
419    // Draw the widgets
420    m_playPauseButton.draw(context);
421    m_volumeUpButton.draw(context);
422    m_volumeSliderButton.draw(context);
423    m_volumeDownButton.draw(context);
424    m_timeSliderButton.draw(context);
425    m_exitFullscreenButton.draw(context);
426    m_volumeSlider.draw(context);
427    m_timeSlider.draw(context);
428
429    // Draw the text strings
430    FontDescription desc;
431
432    NONCLIENTMETRICS metrics;
433    metrics.cbSize = sizeof(metrics);
434    SystemParametersInfo(SPI_GETNONCLIENTMETRICS, metrics.cbSize, &metrics, 0);
435    FontFamily family;
436    family.setFamily(metrics.lfSmCaptionFont.lfFaceName);
437    desc.setFamily(family);
438
439    desc.setComputedSize(textSize);
440    Font font = Font(desc, 0, 0);
441    font.update(0);
442
443    String s;
444
445    // The y positioning of these two text strings is tricky because they are so small. They
446    // are currently positioned relative to the center of the slider and then down the font
447    // height / 4 (which is actually half of font height /2), which positions the center of
448    // the text at the center of the slider.
449    // Left string
450    s = timeToString(currentTime());
451    TextRun leftText(s);
452    context.setFillColor(Color(textColor), DeviceColorSpace);
453    context.drawText(font, leftText, IntPoint(windowWidth / 2 - timeSliderWidth / 2 - margin - font.width(leftText), windowHeight - margin - sliderHeight / 2 + font.height() / 4));
454
455    // Right string
456    s = timeToString(currentTime() - duration());
457    TextRun rightText(s);
458    context.setFillColor(Color(textColor), DeviceColorSpace);
459    context.drawText(font, rightText, IntPoint(windowWidth / 2 + timeSliderWidth / 2 + margin, windowHeight - margin - sliderHeight / 2 + font.height() / 4));
460
461    // Copy to the window
462    BLENDFUNCTION blendFunction = {AC_SRC_OVER, 0, 255, AC_SRC_ALPHA};
463    SIZE size = { windowWidth, windowHeight };
464    POINT sourcePoint = {0, 0};
465    POINT destPoint = { m_hudPosition.x(), m_hudPosition.y() };
466    BOOL result = UpdateLayeredWindow(m_hudWindow, 0, &destPoint, &size, bitmapDC, &sourcePoint, 0, &blendFunction, ULW_ALPHA);
467
468    context.restore();
469
470    ::DeleteDC(bitmapDC);
471}
472
473LRESULT FullscreenVideoController::hudWndProc(HWND wnd, UINT message, WPARAM wParam, LPARAM lParam)
474{
475    LONG_PTR longPtr = GetWindowLongPtr(wnd, 0);
476    FullscreenVideoController* controller = reinterpret_cast<FullscreenVideoController*>(longPtr);
477    if (!controller)
478        return DefWindowProc(wnd, message, wParam, lParam);
479
480    switch (message) {
481    case WM_CHAR:
482        controller->onChar(wParam);
483        break;
484    case WM_LBUTTONDOWN:
485        controller->onMouseDown(IntPoint(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)));
486        break;
487    case WM_MOUSEMOVE:
488        controller->onMouseMove(IntPoint(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)));
489        break;
490    case WM_LBUTTONUP:
491        controller->onMouseUp(IntPoint(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)));
492        break;
493    }
494
495    return DefWindowProc(wnd, message, wParam, lParam);
496}
497
498void FullscreenVideoController::onChar(int c)
499{
500    if (c == VK_ESCAPE) {
501        if (m_mediaElement)
502            m_mediaElement->exitFullscreen();
503    } else if (c == VK_SPACE)
504        togglePlay();
505}
506
507void FullscreenVideoController::timerFired(Timer<FullscreenVideoController>*)
508{
509    // Update the time slider
510    m_timeSlider.setValue(currentTime() / duration());
511    draw();
512}
513
514void FullscreenVideoController::onMouseDown(const IntPoint& point)
515{
516    IntPoint convertedPoint(fullScreenToHUDCoordinates(point));
517
518    // Don't bother hit testing if we're outside the bounds of the window
519    if (convertedPoint.x() < 0 || convertedPoint.x() >= windowWidth || convertedPoint.y() < 0 || convertedPoint.y() >= windowHeight)
520        return;
521
522    m_hitWidget = 0;
523    m_movingWindow = false;
524
525    if (m_playPauseButton.hitTest(convertedPoint))
526        m_hitWidget = &m_playPauseButton;
527    else if (m_exitFullscreenButton.hitTest(convertedPoint))
528        m_hitWidget = &m_exitFullscreenButton;
529    else if (m_volumeUpButton.hitTest(convertedPoint))
530        m_hitWidget = &m_volumeUpButton;
531    else if (m_volumeDownButton.hitTest(convertedPoint))
532        m_hitWidget = &m_volumeDownButton;
533    else if (m_volumeSlider.hitTest(convertedPoint)) {
534        m_hitWidget = &m_volumeSlider;
535        m_volumeSlider.drag(convertedPoint, true);
536        setVolume(m_volumeSlider.value());
537    } else if (m_timeSlider.hitTest(convertedPoint)) {
538        m_hitWidget = &m_timeSlider;
539        m_timeSlider.drag(convertedPoint, true);
540        beginScrubbing();
541        setCurrentTime(m_timeSlider.value() * duration());
542    }
543
544    // If we did not pick any of our widgets we are starting a window move
545    if (!m_hitWidget) {
546        m_moveOffset = convertedPoint;
547        m_movingWindow = true;
548    }
549
550    draw();
551}
552
553void FullscreenVideoController::onMouseMove(const IntPoint& point)
554{
555    IntPoint convertedPoint(fullScreenToHUDCoordinates(point));
556
557    if (m_hitWidget) {
558        m_hitWidget->drag(convertedPoint, false);
559        if (m_hitWidget == &m_volumeSlider)
560            setVolume(m_volumeSlider.value());
561        else if (m_hitWidget == &m_timeSlider)
562            setCurrentTime(m_timeSlider.value() * duration());
563        draw();
564    } else if (m_movingWindow)
565        m_hudPosition.move(convertedPoint.x() - m_moveOffset.x(), convertedPoint.y() - m_moveOffset.y());
566}
567
568void FullscreenVideoController::onMouseUp(const IntPoint& point)
569{
570    IntPoint convertedPoint(fullScreenToHUDCoordinates(point));
571    m_movingWindow = false;
572
573    if (m_hitWidget) {
574        if (m_hitWidget == &m_playPauseButton && m_playPauseButton.hitTest(convertedPoint))
575            togglePlay();
576        else if (m_hitWidget == &m_volumeUpButton && m_volumeUpButton.hitTest(convertedPoint)) {
577            setVolume(1);
578            m_volumeSlider.setValue(1);
579        } else if (m_hitWidget == &m_volumeDownButton && m_volumeDownButton.hitTest(convertedPoint)) {
580            setVolume(0);
581            m_volumeSlider.setValue(0);
582        } else if (m_hitWidget == &m_timeSlider)
583            endScrubbing();
584        else if (m_hitWidget == &m_exitFullscreenButton && m_exitFullscreenButton.hitTest(convertedPoint)) {
585            m_hitWidget = 0;
586            if (m_mediaElement)
587                m_mediaElement->exitFullscreen();
588            return;
589        }
590    }
591
592    m_hitWidget = 0;
593    draw();
594}
595
596void FullscreenVideoController::togglePlay()
597{
598    if (canPlay())
599        play();
600    else
601        pause();
602
603    m_playPauseButton.setShowAltButton(!canPlay());
604
605    // Run a timer while the video is playing so we can keep the time
606    // slider and time values up to date.
607    if (!canPlay())
608        m_timer.startRepeating(timerInterval);
609    else
610        m_timer.stop();
611
612    draw();
613}
614
615#endif
616