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
HUDButton(HUDButtonType type,const IntPoint & position)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
draw(GraphicsContext & context)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
HUDSlider(HUDSliderButtonShape shape,int buttonSize,const IntRect & rect)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
draw(GraphicsContext & context)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
drag(const IntPoint & point,bool start)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:
LayerClient(FullscreenVideoController * parent)179 LayerClient(FullscreenVideoController* parent) : m_parent(parent) { }
180
181 private:
182 virtual void platformCALayerLayoutSublayersOfLayer(PlatformCALayer*);
platformCALayerRespondsToLayoutChanges() const183 virtual bool platformCALayerRespondsToLayoutChanges() const { return true; }
184
platformCALayerAnimationStarted(CFTimeInterval beginTime)185 virtual void platformCALayerAnimationStarted(CFTimeInterval beginTime) { }
platformCALayerContentsOrientation() const186 virtual GraphicsLayer::CompositingCoordinatesOrientation platformCALayerContentsOrientation() const { return GraphicsLayer::CompositingCoordinatesBottomUp; }
platformCALayerPaintContents(GraphicsContext &,const IntRect & inClip)187 virtual void platformCALayerPaintContents(GraphicsContext&, const IntRect& inClip) { }
platformCALayerShowDebugBorders() const188 virtual bool platformCALayerShowDebugBorders() const { return false; }
platformCALayerShowRepaintCounter() const189 virtual bool platformCALayerShowRepaintCounter() const { return false; }
platformCALayerIncrementRepaintCount()190 virtual int platformCALayerIncrementRepaintCount() { return 0; }
191
platformCALayerContentsOpaque() const192 virtual bool platformCALayerContentsOpaque() const { return false; }
platformCALayerDrawsContent() const193 virtual bool platformCALayerDrawsContent() const { return false; }
platformCALayerLayerDidDisplay(PlatformLayer *)194 virtual void platformCALayerLayerDidDisplay(PlatformLayer*) { }
195
196 FullscreenVideoController* m_parent;
197 };
198
platformCALayerLayoutSublayersOfLayer(PlatformCALayer * layer)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
FullscreenVideoController()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
~FullscreenVideoController()252 FullscreenVideoController::~FullscreenVideoController()
253 {
254 #if USE(ACCELERATED_COMPOSITING)
255 m_rootChild->setOwner(0);
256 #endif
257 }
258
setMediaElement(HTMLMediaElement * mediaElement)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
enterFullscreen()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
exitFullscreen()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
canPlay() const319 bool FullscreenVideoController::canPlay() const
320 {
321 return m_mediaElement && m_mediaElement->canPlay();
322 }
323
play()324 void FullscreenVideoController::play()
325 {
326 if (m_mediaElement)
327 m_mediaElement->play(m_mediaElement->processingUserGesture());
328 }
329
pause()330 void FullscreenVideoController::pause()
331 {
332 if (m_mediaElement)
333 m_mediaElement->pause(m_mediaElement->processingUserGesture());
334 }
335
volume() const336 float FullscreenVideoController::volume() const
337 {
338 return m_mediaElement ? m_mediaElement->volume() : 0;
339 }
340
setVolume(float volume)341 void FullscreenVideoController::setVolume(float volume)
342 {
343 if (m_mediaElement) {
344 ExceptionCode ec;
345 m_mediaElement->setVolume(volume, ec);
346 }
347 }
348
currentTime() const349 float FullscreenVideoController::currentTime() const
350 {
351 return m_mediaElement ? m_mediaElement->currentTime() : 0;
352 }
353
setCurrentTime(float value)354 void FullscreenVideoController::setCurrentTime(float value)
355 {
356 if (m_mediaElement) {
357 ExceptionCode ec;
358 m_mediaElement->setCurrentTime(value, ec);
359 }
360 }
361
duration() const362 float FullscreenVideoController::duration() const
363 {
364 return m_mediaElement ? m_mediaElement->duration() : 0;
365 }
366
beginScrubbing()367 void FullscreenVideoController::beginScrubbing()
368 {
369 if (m_mediaElement)
370 m_mediaElement->beginScrubbing();
371 }
372
endScrubbing()373 void FullscreenVideoController::endScrubbing()
374 {
375 if (m_mediaElement)
376 m_mediaElement->endScrubbing();
377 }
378
fullscreenClientWndProc(HWND wnd,UINT message,WPARAM wParam,LPARAM lParam)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
registerHUDWindowClass()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
createHUDWindow()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
timeToString(float time)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
draw()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
hudWndProc(HWND wnd,UINT message,WPARAM wParam,LPARAM lParam)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
onChar(int c)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
onKeyDown(int virtualKey)596 void FullscreenVideoController::onKeyDown(int virtualKey)
597 {
598 if (virtualKey == VK_ESCAPE) {
599 if (m_mediaElement)
600 m_mediaElement->exitFullscreen();
601 }
602 }
603
timerFired(Timer<FullscreenVideoController> *)604 void FullscreenVideoController::timerFired(Timer<FullscreenVideoController>*)
605 {
606 // Update the time slider
607 m_timeSlider.setValue(currentTime() / duration());
608 draw();
609 }
610
onMouseDown(const IntPoint & point)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
onMouseMove(const IntPoint & point)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
onMouseUp(const IntPoint & point)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
togglePlay()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