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