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