1/*
2 * Copyright (C) 2010, 2013 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. ``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 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) && !USE(GSTREAMER) && !USE(MEDIA_FOUNDATION)
29
30#include "FullscreenVideoController.h"
31
32#include "WebKitDLL.h"
33#include "WebView.h"
34#include <ApplicationServices/ApplicationServices.h>
35#include <WebCore/BitmapInfo.h>
36#include <WebCore/Chrome.h>
37#include <WebCore/FloatRoundedRect.h>
38#include <WebCore/Font.h>
39#include <WebCore/FontSelector.h>
40#include <WebCore/GraphicsContext.h>
41#include <WebCore/HWndDC.h>
42#include <WebCore/Page.h>
43#include <WebCore/PlatformCALayerWin.h>
44#include <WebCore/TextRun.h>
45#include <WebKitSystemInterface/WebKitSystemInterface.h>
46#include <windowsx.h>
47#include <wtf/OwnPtr.h>
48#include <wtf/PassOwnPtr.h>
49#include <wtf/StdLibExtras.h>
50
51using namespace std;
52using namespace WebCore;
53
54static const float timerInterval = 0.033;
55
56// HUD Size
57static const int windowHeight = 59;
58static const int windowWidth = 438;
59
60// Margins and button sizes
61static const int margin = 9;
62static const int marginTop = 9;
63static const int buttonSize = 25;
64static const int buttonMiniSize = 16;
65static const int volumeSliderWidth = 50;
66static const int timeSliderWidth = 310;
67static const int sliderHeight = 8;
68static const int volumeSliderButtonSize = 10;
69static const int timeSliderButtonSize = 8;
70static const int textSize = 11;
71static const float initialHUDPositionY = 0.9; // Initial Y position of HUD in percentage from top of screen
72
73// Background values
74static const int borderRadius = 12;
75static const int borderThickness = 2;
76
77// Colors
78static const unsigned int backgroundColor = 0xA0202020;
79static const unsigned int borderColor = 0xFFA0A0A0;
80static const unsigned int sliderGutterColor = 0xFF141414;
81static const unsigned int sliderButtonColor = 0xFF808080;
82static const unsigned int textColor = 0xFFFFFFFF;
83
84HUDButton::HUDButton(HUDButtonType type, const IntPoint& position)
85    : HUDWidget(IntRect(position, IntSize()))
86    , m_type(type)
87    , m_showAltButton(false)
88{
89    const char* buttonResource = 0;
90    const char* buttonResourceAlt = 0;
91    switch (m_type) {
92    case PlayPauseButton:
93        buttonResource = "fsVideoPlay";
94        buttonResourceAlt = "fsVideoPause";
95        break;
96    case TimeSliderButton:
97        break;
98    case VolumeUpButton:
99        buttonResource = "fsVideoAudioVolumeHigh";
100        break;
101    case VolumeSliderButton:
102        break;
103    case VolumeDownButton:
104        buttonResource = "fsVideoAudioVolumeLow";
105        break;
106    case ExitFullscreenButton:
107        buttonResource = "fsVideoExitFullscreen";
108        break;
109    }
110
111    if (buttonResource) {
112        m_buttonImage = Image::loadPlatformResource(buttonResource);
113        m_rect.setWidth(m_buttonImage->width());
114        m_rect.setHeight(m_buttonImage->height());
115    }
116    if (buttonResourceAlt)
117        m_buttonImageAlt = Image::loadPlatformResource(buttonResourceAlt);
118}
119
120void HUDButton::draw(GraphicsContext& context)
121{
122    Image* image = (m_showAltButton && m_buttonImageAlt) ? m_buttonImageAlt.get() : m_buttonImage.get();
123    context.drawImage(image, ColorSpaceDeviceRGB, m_rect.location());
124}
125
126HUDSlider::HUDSlider(HUDSliderButtonShape shape, int buttonSize, const IntRect& rect)
127    : HUDWidget(rect)
128    , m_buttonShape(shape)
129    , m_buttonSize(buttonSize)
130    , m_buttonPosition(0)
131    , m_dragStartOffset(0)
132{
133}
134
135void HUDSlider::draw(GraphicsContext& context)
136{
137    // Draw gutter
138    IntSize radius(m_rect.height() / 2, m_rect.height() / 2);
139    context.fillRoundedRect(FloatRoundedRect(m_rect, radius, radius, radius, radius), Color(sliderGutterColor), ColorSpaceDeviceRGB);
140
141    // Draw button
142    context.setStrokeColor(Color(sliderButtonColor), ColorSpaceDeviceRGB);
143    context.setFillColor(Color(sliderButtonColor), ColorSpaceDeviceRGB);
144
145    if (m_buttonShape == RoundButton) {
146        context.drawEllipse(IntRect(m_rect.location().x() + m_buttonPosition, m_rect.location().y() - (m_buttonSize - m_rect.height()) / 2, m_buttonSize, m_buttonSize));
147        return;
148    }
149
150    // Draw a diamond
151    FloatPoint points[4];
152    float half = static_cast<float>(m_buttonSize) / 2;
153    points[0].setX(m_rect.location().x() + m_buttonPosition + half);
154    points[0].setY(m_rect.location().y());
155    points[1].setX(m_rect.location().x() + m_buttonPosition + m_buttonSize);
156    points[1].setY(m_rect.location().y() + half);
157    points[2].setX(m_rect.location().x() + m_buttonPosition + half);
158    points[2].setY(m_rect.location().y() + m_buttonSize);
159    points[3].setX(m_rect.location().x() + m_buttonPosition);
160    points[3].setY(m_rect.location().y() + half);
161    context.drawConvexPolygon(4, points, true);
162}
163
164void HUDSlider::drag(const IntPoint& point, bool start)
165{
166    if (start) {
167        // When we start, we need to snap the slider position to the x position if we clicked the gutter.
168        // But if we click the button, we need to drag relative to where we clicked down. We only need
169        // to check X because we would not even get here unless Y were already inside.
170        int relativeX = point.x() - m_rect.location().x();
171        if (relativeX >= m_buttonPosition && relativeX <= m_buttonPosition + m_buttonSize)
172            m_dragStartOffset = point.x() - m_buttonPosition;
173        else
174            m_dragStartOffset = m_rect.location().x() + m_buttonSize / 2;
175    }
176
177    m_buttonPosition = max(0, min(m_rect.width() - m_buttonSize, point.x() - m_dragStartOffset));
178}
179
180class FullscreenVideoController::LayerClient : public WebCore::PlatformCALayerClient {
181public:
182    LayerClient(FullscreenVideoController* parent) : m_parent(parent) { }
183
184private:
185    virtual void platformCALayerLayoutSublayersOfLayer(PlatformCALayer*);
186    virtual bool platformCALayerRespondsToLayoutChanges() const { return true; }
187
188    virtual void platformCALayerAnimationStarted(CFTimeInterval beginTime) { }
189    virtual GraphicsLayer::CompositingCoordinatesOrientation platformCALayerContentsOrientation() const { return GraphicsLayer::CompositingCoordinatesBottomUp; }
190    virtual void platformCALayerPaintContents(PlatformCALayer*, GraphicsContext&, const FloatRect&) { }
191    virtual bool platformCALayerShowDebugBorders() const { return false; }
192    virtual bool platformCALayerShowRepaintCounter(PlatformCALayer*) const { return false; }
193    virtual int platformCALayerIncrementRepaintCount(PlatformCALayer*) { return 0; }
194
195    virtual bool platformCALayerContentsOpaque() const { return false; }
196    virtual bool platformCALayerDrawsContent() const { return false; }
197    virtual void platformCALayerLayerDidDisplay(PlatformLayer*) { }
198    virtual void platformCALayerDidCreateTiles(const Vector<FloatRect>&) { }
199    virtual float platformCALayerDeviceScaleFactor() const override { return 1; }
200
201    FullscreenVideoController* m_parent;
202};
203
204void FullscreenVideoController::LayerClient::platformCALayerLayoutSublayersOfLayer(PlatformCALayer* layer)
205{
206    ASSERT_ARG(layer, layer == m_parent->m_rootChild);
207
208    HTMLMediaElement* mediaElement = m_parent->m_mediaElement.get();
209    if (!mediaElement)
210        return;
211
212
213    PlatformCALayer* videoLayer = PlatformCALayer::platformCALayer(mediaElement->platformLayer());
214    if (!videoLayer || videoLayer->superlayer() != layer)
215        return;
216
217    FloatRect layerBounds = layer->bounds();
218
219    FloatSize videoSize = mediaElement->player()->naturalSize();
220    float scaleFactor;
221    if (videoSize.aspectRatio() > layerBounds.size().aspectRatio())
222        scaleFactor = layerBounds.width() / videoSize.width();
223    else
224        scaleFactor = layerBounds.height() / videoSize.height();
225    videoSize.scale(scaleFactor);
226
227    // Calculate the centered position based on the videoBounds and layerBounds:
228    FloatPoint videoPosition;
229    FloatPoint videoOrigin;
230    videoOrigin.setX((layerBounds.width() - videoSize.width()) * 0.5);
231    videoOrigin.setY((layerBounds.height() - videoSize.height()) * 0.5);
232    videoLayer->setPosition(videoOrigin);
233    videoLayer->setBounds(FloatRect(FloatPoint(), videoSize));
234}
235
236FullscreenVideoController::FullscreenVideoController()
237    : m_hudWindow(0)
238    , m_playPauseButton(HUDButton::PlayPauseButton, IntPoint((windowWidth - buttonSize) / 2, marginTop))
239    , m_timeSliderButton(HUDButton::TimeSliderButton, IntPoint(0, 0))
240    , m_volumeUpButton(HUDButton::VolumeUpButton, IntPoint(margin + buttonMiniSize + volumeSliderWidth + buttonMiniSize / 2, marginTop + (buttonSize - buttonMiniSize) / 2))
241    , m_volumeSliderButton(HUDButton::VolumeSliderButton, IntPoint(0, 0))
242    , m_volumeDownButton(HUDButton::VolumeDownButton, IntPoint(margin, marginTop + (buttonSize - buttonMiniSize) / 2))
243    , m_exitFullscreenButton(HUDButton::ExitFullscreenButton, IntPoint(windowWidth - 2 * margin - buttonMiniSize, marginTop + (buttonSize - buttonMiniSize) / 2))
244    , m_volumeSlider(HUDSlider::RoundButton, volumeSliderButtonSize, IntRect(IntPoint(margin + buttonMiniSize, marginTop + (buttonSize - buttonMiniSize) / 2 + buttonMiniSize / 2 - sliderHeight / 2), IntSize(volumeSliderWidth, sliderHeight)))
245    , m_timeSlider(HUDSlider::DiamondButton, timeSliderButtonSize, IntRect(IntPoint(windowWidth / 2 - timeSliderWidth / 2, windowHeight - margin - sliderHeight), IntSize(timeSliderWidth, sliderHeight)))
246    , m_hitWidget(0)
247    , m_movingWindow(false)
248    , m_timer(this, &FullscreenVideoController::timerFired)
249    , m_layerClient(adoptPtr(new LayerClient(this)))
250    , m_rootChild(PlatformCALayerWin::create(PlatformCALayer::LayerTypeLayer, m_layerClient.get()))
251    , m_fullscreenWindow(adoptPtr(new MediaPlayerPrivateFullscreenWindow(this)))
252{
253}
254
255FullscreenVideoController::~FullscreenVideoController()
256{
257    m_rootChild->setOwner(0);
258}
259
260void FullscreenVideoController::setMediaElement(HTMLMediaElement* mediaElement)
261{
262    if (mediaElement == m_mediaElement)
263        return;
264
265    m_mediaElement = mediaElement;
266    if (!m_mediaElement) {
267        // Can't do full-screen, just get out
268        exitFullscreen();
269    }
270}
271
272void FullscreenVideoController::enterFullscreen()
273{
274    if (!m_mediaElement)
275        return;
276
277    WebView* webView = kit(m_mediaElement->document().page());
278    HWND parentHwnd = webView ? webView->viewWindow() : 0;
279
280    m_fullscreenWindow->createWindow(parentHwnd);
281    ::ShowWindow(m_fullscreenWindow->hwnd(), SW_SHOW);
282    m_fullscreenWindow->setRootChildLayer(m_rootChild);
283
284    PlatformCALayer* videoLayer = PlatformCALayer::platformCALayer(m_mediaElement->platformLayer());
285    m_rootChild->appendSublayer(videoLayer);
286    m_rootChild->setNeedsLayout();
287    m_rootChild->setGeometryFlipped(1);
288
289    RECT windowRect;
290    GetClientRect(m_fullscreenWindow->hwnd(), &windowRect);
291    m_fullscreenSize.setWidth(windowRect.right - windowRect.left);
292    m_fullscreenSize.setHeight(windowRect.bottom - windowRect.top);
293
294    createHUDWindow();
295}
296
297void FullscreenVideoController::exitFullscreen()
298{
299    SetWindowLongPtr(m_hudWindow, 0, 0);
300
301    if (m_fullscreenWindow)
302        m_fullscreenWindow = nullptr;
303
304    ASSERT(!IsWindow(m_hudWindow));
305    m_hudWindow = 0;
306
307    // We previously ripped the mediaElement's platform layer out
308    // of its orginial layer tree to display it in our fullscreen
309    // window.  Now, we need to get the layer back in its original
310    // tree.
311    //
312    // As a side effect of setting the player to invisible/visible,
313    // the player's layer will be recreated, and will be picked up
314    // the next time the layer tree is synched.
315    m_mediaElement->player()->setVisible(0);
316    m_mediaElement->player()->setVisible(1);
317}
318
319bool FullscreenVideoController::canPlay() const
320{
321    return m_mediaElement && m_mediaElement->canPlay();
322}
323
324void FullscreenVideoController::play()
325{
326    if (m_mediaElement)
327        m_mediaElement->play();
328}
329
330void FullscreenVideoController::pause()
331{
332    if (m_mediaElement)
333        m_mediaElement->pause();
334}
335
336float FullscreenVideoController::volume() const
337{
338    return m_mediaElement ? m_mediaElement->volume() : 0;
339}
340
341void FullscreenVideoController::setVolume(float volume)
342{
343    if (m_mediaElement) {
344        ExceptionCode ec;
345        m_mediaElement->setVolume(volume, ec);
346    }
347}
348
349float FullscreenVideoController::currentTime() const
350{
351    return m_mediaElement ? m_mediaElement->currentTime() : 0;
352}
353
354void FullscreenVideoController::setCurrentTime(float value)
355{
356    if (m_mediaElement)
357        m_mediaElement->setCurrentTime(value);
358}
359
360float FullscreenVideoController::duration() const
361{
362    return m_mediaElement ? m_mediaElement->duration() : 0;
363}
364
365void FullscreenVideoController::beginScrubbing()
366{
367    if (m_mediaElement)
368        m_mediaElement->beginScrubbing();
369}
370
371void FullscreenVideoController::endScrubbing()
372{
373    if (m_mediaElement)
374        m_mediaElement->endScrubbing();
375}
376
377LRESULT FullscreenVideoController::fullscreenClientWndProc(HWND wnd, UINT message, WPARAM wParam, LPARAM lParam)
378{
379    switch (message) {
380    case WM_CHAR:
381        onChar(wParam);
382        break;
383    case WM_KEYDOWN:
384        onKeyDown(wParam);
385        break;
386    case WM_LBUTTONDOWN:
387        onMouseDown(IntPoint(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)));
388        break;
389    case WM_MOUSEMOVE:
390        onMouseMove(IntPoint(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)));
391        break;
392    case WM_LBUTTONUP:
393        onMouseUp(IntPoint(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)));
394        break;
395    }
396
397    return DefWindowProc(wnd, message, wParam, lParam);
398}
399
400static const LPCWSTR fullscreenVideeoHUDWindowClassName = L"fullscreenVideeoHUDWindowClass";
401
402void FullscreenVideoController::registerHUDWindowClass()
403{
404    static bool haveRegisteredHUDWindowClass;
405    if (haveRegisteredHUDWindowClass)
406        return;
407
408    haveRegisteredHUDWindowClass = true;
409
410    WNDCLASSEX wcex;
411
412    wcex.cbSize = sizeof(WNDCLASSEX);
413
414    wcex.style = CS_HREDRAW | CS_VREDRAW;
415    wcex.lpfnWndProc = hudWndProc;
416    wcex.cbClsExtra = 0;
417    wcex.cbWndExtra = sizeof(FullscreenVideoController*);
418    wcex.hInstance = gInstance;
419    wcex.hIcon = 0;
420    wcex.hCursor = LoadCursor(0, IDC_ARROW);
421    wcex.hbrBackground = 0;
422    wcex.lpszMenuName = 0;
423    wcex.lpszClassName = fullscreenVideeoHUDWindowClassName;
424    wcex.hIconSm = 0;
425
426    RegisterClassEx(&wcex);
427}
428
429void FullscreenVideoController::createHUDWindow()
430{
431    m_hudPosition.setX((m_fullscreenSize.width() - windowWidth) / 2);
432    m_hudPosition.setY(m_fullscreenSize.height() * initialHUDPositionY - windowHeight / 2);
433
434    // Local variable that will hold the returned pixels. No need to cleanup this value. It
435    // will get cleaned up when m_bitmap is destroyed in the dtor
436    void* pixels;
437    BitmapInfo bitmapInfo = BitmapInfo::createBottomUp(IntSize(windowWidth, windowHeight));
438    m_bitmap = adoptGDIObject(::CreateDIBSection(0, &bitmapInfo, DIB_RGB_COLORS, &pixels, 0, 0));
439
440    // Dirty the window so the HUD draws
441    RECT clearRect = { m_hudPosition.x(), m_hudPosition.y(), m_hudPosition.x() + windowWidth, m_hudPosition.y() + windowHeight };
442    InvalidateRect(m_fullscreenWindow->hwnd(), &clearRect, true);
443
444    m_playPauseButton.setShowAltButton(!canPlay());
445    m_volumeSlider.setValue(volume());
446    m_timeSlider.setValue(currentTime() / duration());
447
448    if (!canPlay())
449        m_timer.startRepeating(timerInterval);
450
451    registerHUDWindowClass();
452
453    m_hudWindow = CreateWindowEx(WS_EX_LAYERED | WS_EX_TRANSPARENT | WS_EX_TOOLWINDOW,
454        fullscreenVideeoHUDWindowClassName, 0, WS_POPUP | WS_VISIBLE,
455        m_hudPosition.x(), m_hudPosition.y(), 0, 0, m_fullscreenWindow->hwnd(), 0, gInstance, 0);
456    ASSERT(::IsWindow(m_hudWindow));
457    SetWindowLongPtr(m_hudWindow, 0, reinterpret_cast<LONG_PTR>(this));
458
459    draw();
460}
461
462static String timeToString(float time)
463{
464    if (!std::isfinite(time))
465        time = 0;
466    int seconds = fabsf(time);
467    int hours = seconds / (60 * 60);
468    int minutes = (seconds / 60) % 60;
469    seconds %= 60;
470
471    if (hours) {
472        if (hours > 9)
473            return String::format("%s%02d:%02d:%02d", (time < 0 ? "-" : ""), hours, minutes, seconds);
474        return String::format("%s%01d:%02d:%02d", (time < 0 ? "-" : ""), hours, minutes, seconds);
475    }
476
477    return String::format("%s%02d:%02d", (time < 0 ? "-" : ""), minutes, seconds);
478}
479
480void FullscreenVideoController::draw()
481{
482    auto bitmapDC = adoptGDIObject(::CreateCompatibleDC(HWndDC(m_hudWindow)));
483    HGDIOBJ oldBitmap = SelectObject(bitmapDC.get(), m_bitmap.get());
484
485    GraphicsContext context(bitmapDC.get(), true);
486
487    context.save();
488
489    // Draw the background
490    IntSize outerRadius(borderRadius, borderRadius);
491    IntRect outerRect(0, 0, windowWidth, windowHeight);
492    IntSize innerRadius(borderRadius - borderThickness, borderRadius - borderThickness);
493    IntRect innerRect(borderThickness, borderThickness, windowWidth - borderThickness * 2, windowHeight - borderThickness * 2);
494
495    context.fillRoundedRect(FloatRoundedRect(outerRect, outerRadius, outerRadius, outerRadius, outerRadius), Color(borderColor), ColorSpaceDeviceRGB);
496    context.setCompositeOperation(CompositeCopy);
497    context.fillRoundedRect(FloatRoundedRect(innerRect, innerRadius, innerRadius, innerRadius, innerRadius), Color(backgroundColor), ColorSpaceDeviceRGB);
498
499    // Draw the widgets
500    m_playPauseButton.draw(context);
501    m_volumeUpButton.draw(context);
502    m_volumeSliderButton.draw(context);
503    m_volumeDownButton.draw(context);
504    m_timeSliderButton.draw(context);
505    m_exitFullscreenButton.draw(context);
506    m_volumeSlider.draw(context);
507    m_timeSlider.draw(context);
508
509    // Draw the text strings
510    FontDescription desc;
511
512    NONCLIENTMETRICS metrics;
513    metrics.cbSize = sizeof(metrics);
514    SystemParametersInfo(SPI_GETNONCLIENTMETRICS, metrics.cbSize, &metrics, 0);
515    desc.setOneFamily(metrics.lfSmCaptionFont.lfFaceName);
516
517    desc.setComputedSize(textSize);
518    Font font = Font(desc, 0, 0);
519    font.update(0);
520
521    String s;
522
523    // The y positioning of these two text strings is tricky because they are so small. They
524    // are currently positioned relative to the center of the slider and then down the font
525    // height / 4 (which is actually half of font height /2), which positions the center of
526    // the text at the center of the slider.
527    // Left string
528    s = timeToString(currentTime());
529    int fontHeight = font.fontMetrics().height();
530    TextRun leftText(s);
531    context.setFillColor(Color(textColor), ColorSpaceDeviceRGB);
532    context.drawText(font, leftText, IntPoint(windowWidth / 2 - timeSliderWidth / 2 - margin - font.width(leftText), windowHeight - margin - sliderHeight / 2 + fontHeight / 4));
533
534    // Right string
535    s = timeToString(currentTime() - duration());
536    TextRun rightText(s);
537    context.setFillColor(Color(textColor), ColorSpaceDeviceRGB);
538    context.drawText(font, rightText, IntPoint(windowWidth / 2 + timeSliderWidth / 2 + margin, windowHeight - margin - sliderHeight / 2 + fontHeight / 4));
539
540    // Copy to the window
541    BLENDFUNCTION blendFunction = {AC_SRC_OVER, 0, 255, AC_SRC_ALPHA};
542    SIZE size = { windowWidth, windowHeight };
543    POINT sourcePoint = {0, 0};
544    POINT destPoint = { m_hudPosition.x(), m_hudPosition.y() };
545    BOOL result = UpdateLayeredWindow(m_hudWindow, 0, &destPoint, &size, bitmapDC.get(), &sourcePoint, 0, &blendFunction, ULW_ALPHA);
546
547    context.restore();
548
549    ::SelectObject(bitmapDC.get(), oldBitmap);
550}
551
552LRESULT FullscreenVideoController::hudWndProc(HWND wnd, UINT message, WPARAM wParam, LPARAM lParam)
553{
554    LONG_PTR longPtr = GetWindowLongPtr(wnd, 0);
555    FullscreenVideoController* controller = reinterpret_cast<FullscreenVideoController*>(longPtr);
556    if (!controller)
557        return DefWindowProc(wnd, message, wParam, lParam);
558
559    switch (message) {
560    case WM_CHAR:
561        controller->onChar(wParam);
562        break;
563    case WM_KEYDOWN:
564        controller->onKeyDown(wParam);
565        break;
566    case WM_LBUTTONDOWN:
567        controller->onMouseDown(IntPoint(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)));
568        break;
569    case WM_MOUSEMOVE:
570        controller->onMouseMove(IntPoint(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)));
571        break;
572    case WM_LBUTTONUP:
573        controller->onMouseUp(IntPoint(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)));
574        break;
575    }
576
577    return DefWindowProc(wnd, message, wParam, lParam);
578}
579
580void FullscreenVideoController::onChar(int c)
581{
582    if (c == VK_ESCAPE) {
583        if (m_mediaElement)
584            m_mediaElement->exitFullscreen();
585    } else if (c == VK_SPACE)
586        togglePlay();
587}
588
589void FullscreenVideoController::onKeyDown(int virtualKey)
590{
591    if (virtualKey == VK_ESCAPE) {
592        if (m_mediaElement)
593            m_mediaElement->exitFullscreen();
594    }
595}
596
597void FullscreenVideoController::timerFired(Timer<FullscreenVideoController>*)
598{
599    // Update the time slider
600    m_timeSlider.setValue(currentTime() / duration());
601    draw();
602}
603
604void FullscreenVideoController::onMouseDown(const IntPoint& point)
605{
606    IntPoint convertedPoint(fullscreenToHUDCoordinates(point));
607
608    // Don't bother hit testing if we're outside the bounds of the window
609    if (convertedPoint.x() < 0 || convertedPoint.x() >= windowWidth || convertedPoint.y() < 0 || convertedPoint.y() >= windowHeight)
610        return;
611
612    m_hitWidget = 0;
613    m_movingWindow = false;
614
615    if (m_playPauseButton.hitTest(convertedPoint))
616        m_hitWidget = &m_playPauseButton;
617    else if (m_exitFullscreenButton.hitTest(convertedPoint))
618        m_hitWidget = &m_exitFullscreenButton;
619    else if (m_volumeUpButton.hitTest(convertedPoint))
620        m_hitWidget = &m_volumeUpButton;
621    else if (m_volumeDownButton.hitTest(convertedPoint))
622        m_hitWidget = &m_volumeDownButton;
623    else if (m_volumeSlider.hitTest(convertedPoint)) {
624        m_hitWidget = &m_volumeSlider;
625        m_volumeSlider.drag(convertedPoint, true);
626        setVolume(m_volumeSlider.value());
627    } else if (m_timeSlider.hitTest(convertedPoint)) {
628        m_hitWidget = &m_timeSlider;
629        m_timeSlider.drag(convertedPoint, true);
630        beginScrubbing();
631        setCurrentTime(m_timeSlider.value() * duration());
632    }
633
634    // If we did not pick any of our widgets we are starting a window move
635    if (!m_hitWidget) {
636        m_moveOffset = convertedPoint;
637        m_movingWindow = true;
638    }
639
640    draw();
641}
642
643void FullscreenVideoController::onMouseMove(const IntPoint& point)
644{
645    IntPoint convertedPoint(fullscreenToHUDCoordinates(point));
646
647    if (m_hitWidget) {
648        m_hitWidget->drag(convertedPoint, false);
649        if (m_hitWidget == &m_volumeSlider)
650            setVolume(m_volumeSlider.value());
651        else if (m_hitWidget == &m_timeSlider)
652            setCurrentTime(m_timeSlider.value() * duration());
653        draw();
654    } else if (m_movingWindow)
655        m_hudPosition.move(convertedPoint.x() - m_moveOffset.x(), convertedPoint.y() - m_moveOffset.y());
656}
657
658void FullscreenVideoController::onMouseUp(const IntPoint& point)
659{
660    IntPoint convertedPoint(fullscreenToHUDCoordinates(point));
661    m_movingWindow = false;
662
663    if (m_hitWidget) {
664        if (m_hitWidget == &m_playPauseButton && m_playPauseButton.hitTest(convertedPoint))
665            togglePlay();
666        else if (m_hitWidget == &m_volumeUpButton && m_volumeUpButton.hitTest(convertedPoint)) {
667            setVolume(1);
668            m_volumeSlider.setValue(1);
669        } else if (m_hitWidget == &m_volumeDownButton && m_volumeDownButton.hitTest(convertedPoint)) {
670            setVolume(0);
671            m_volumeSlider.setValue(0);
672        } else if (m_hitWidget == &m_timeSlider)
673            endScrubbing();
674        else if (m_hitWidget == &m_exitFullscreenButton && m_exitFullscreenButton.hitTest(convertedPoint)) {
675            m_hitWidget = 0;
676            if (m_mediaElement)
677                m_mediaElement->exitFullscreen();
678            return;
679        }
680    }
681
682    m_hitWidget = 0;
683    draw();
684}
685
686void FullscreenVideoController::togglePlay()
687{
688    if (canPlay())
689        play();
690    else
691        pause();
692
693    m_playPauseButton.setShowAltButton(!canPlay());
694
695    // Run a timer while the video is playing so we can keep the time
696    // slider and time values up to date.
697    if (!canPlay())
698        m_timer.startRepeating(timerInterval);
699    else
700        m_timer.stop();
701
702    draw();
703}
704
705#endif
706