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