• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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