|
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/TextRun.h> |
|
42 #include <WebCore/WKCACFLayer.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, DeviceColorSpace, 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), DeviceColorSpace); |
|
136 |
|
137 // Draw button |
|
138 context.setStrokeColor(Color(sliderButtonColor), DeviceColorSpace); |
|
139 context.setFillColor(Color(sliderButtonColor), DeviceColorSpace); |
|
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::LayoutClient : public WKCACFLayerLayoutClient { |
|
178 public: |
|
179 LayoutClient(FullscreenVideoController* parent); |
|
180 void layoutSublayersOfLayer(WKCACFLayer* layer); |
|
181 |
|
182 FullscreenVideoController* m_parent; |
|
183 }; |
|
184 |
|
185 FullscreenVideoController::LayoutClient::LayoutClient(FullscreenVideoController* parent) |
|
186 : m_parent(parent) |
|
187 { |
|
188 } |
|
189 |
|
190 void FullscreenVideoController::LayoutClient::layoutSublayersOfLayer(WKCACFLayer* layer) |
|
191 { |
|
192 ASSERT_ARG(layer, layer == m_parent->m_rootChild); |
|
193 |
|
194 HTMLMediaElement* mediaElement = m_parent->m_mediaElement.get(); |
|
195 if (!mediaElement) |
|
196 return; |
|
197 |
|
198 WKCACFLayer* videoLayer = mediaElement->platformLayer(); |
|
199 if (!videoLayer || videoLayer->superlayer() != layer) |
|
200 return; |
|
201 |
|
202 FloatRect layerBounds = layer->bounds(); |
|
203 |
|
204 FloatSize videoSize = mediaElement->player()->naturalSize(); |
|
205 float scaleFactor; |
|
206 if (videoSize.aspectRatio() > layerBounds.size().aspectRatio()) |
|
207 scaleFactor = layerBounds.width() / videoSize.width(); |
|
208 else |
|
209 scaleFactor = layerBounds.height() / videoSize.height(); |
|
210 videoSize.scale(scaleFactor); |
|
211 |
|
212 // Calculate the centered position based on the videoBounds and layerBounds: |
|
213 FloatPoint videoPosition; |
|
214 FloatPoint videoOrigin; |
|
215 videoOrigin.setX((layerBounds.width() - videoSize.width()) * 0.5); |
|
216 videoOrigin.setY((layerBounds.height() - videoSize.height()) * 0.5); |
|
217 videoLayer->setFrame(FloatRect(videoOrigin, videoSize)); |
|
218 } |
|
219 #endif |
|
220 |
|
221 FullscreenVideoController::FullscreenVideoController() |
|
222 : m_hudWindow(0) |
|
223 , m_playPauseButton(HUDButton::PlayPauseButton, IntPoint((windowWidth - buttonSize) / 2, marginTop)) |
|
224 , m_timeSliderButton(HUDButton::TimeSliderButton, IntPoint(0, 0)) |
|
225 , m_volumeUpButton(HUDButton::VolumeUpButton, IntPoint(margin + buttonMiniSize + volumeSliderWidth + buttonMiniSize / 2, marginTop + (buttonSize - buttonMiniSize) / 2)) |
|
226 , m_volumeSliderButton(HUDButton::VolumeSliderButton, IntPoint(0, 0)) |
|
227 , m_volumeDownButton(HUDButton::VolumeDownButton, IntPoint(margin, marginTop + (buttonSize - buttonMiniSize) / 2)) |
|
228 , m_exitFullscreenButton(HUDButton::ExitFullscreenButton, IntPoint(windowWidth - 2 * margin - buttonMiniSize, marginTop + (buttonSize - buttonMiniSize) / 2)) |
|
229 , m_volumeSlider(HUDSlider::RoundButton, volumeSliderButtonSize, IntRect(IntPoint(margin + buttonMiniSize, marginTop + (buttonSize - buttonMiniSize) / 2 + buttonMiniSize / 2 - sliderHeight / 2), IntSize(volumeSliderWidth, sliderHeight))) |
|
230 , m_timeSlider(HUDSlider::DiamondButton, timeSliderButtonSize, IntRect(IntPoint(windowWidth / 2 - timeSliderWidth / 2, windowHeight - margin - sliderHeight), IntSize(timeSliderWidth, sliderHeight))) |
|
231 , m_hitWidget(0) |
|
232 , m_movingWindow(false) |
|
233 , m_timer(this, &FullscreenVideoController::timerFired) |
|
234 #if USE(ACCELERATED_COMPOSITING) |
|
235 , m_rootChild(WKCACFLayer::create(WKCACFLayer::Layer)) |
|
236 , m_layoutClient(new LayoutClient(this)) |
|
237 #endif |
|
238 , m_fullscreenWindow(new MediaPlayerPrivateFullscreenWindow(this)) |
|
239 { |
|
240 #if USE(ACCELERATED_COMPOSITING) |
|
241 m_rootChild->setLayoutClient(m_layoutClient.get()); |
|
242 #endif |
|
243 } |
|
244 |
|
245 FullscreenVideoController::~FullscreenVideoController() |
|
246 { |
|
247 #if USE(ACCELERATED_COMPOSITING) |
|
248 m_rootChild->setLayoutClient(0); |
|
249 #endif |
|
250 } |
|
251 |
|
252 void FullscreenVideoController::setMediaElement(HTMLMediaElement* mediaElement) |
|
253 { |
|
254 if (mediaElement == m_mediaElement) |
|
255 return; |
|
256 |
|
257 m_mediaElement = mediaElement; |
|
258 if (!m_mediaElement) { |
|
259 // Can't do full-screen, just get out |
|
260 exitFullscreen(); |
|
261 } |
|
262 } |
|
263 |
|
264 void FullscreenVideoController::enterFullscreen() |
|
265 { |
|
266 if (!m_mediaElement) |
|
267 return; |
|
268 |
|
269 WebView* webView = kit(m_mediaElement->document()->page()); |
|
270 HWND parentHwnd = webView ? webView->viewWindow() : 0; |
|
271 |
|
272 m_fullscreenWindow->createWindow(parentHwnd); |
|
273 #if USE(ACCELERATED_COMPOSITING) |
|
274 m_fullscreenWindow->setRootChildLayer(m_rootChild); |
|
275 |
|
276 WKCACFLayer* videoLayer = m_mediaElement->player()->platformLayer(); |
|
277 m_rootChild->addSublayer(videoLayer); |
|
278 m_rootChild->setNeedsLayout(); |
|
279 #endif |
|
280 |
|
281 RECT windowRect; |
|
282 GetClientRect(m_fullscreenWindow->hwnd(), &windowRect); |
|
283 m_fullscreenSize.setWidth(windowRect.right - windowRect.left); |
|
284 m_fullscreenSize.setHeight(windowRect.bottom - windowRect.top); |
|
285 |
|
286 createHUDWindow(); |
|
287 } |
|
288 |
|
289 void FullscreenVideoController::exitFullscreen() |
|
290 { |
|
291 SetWindowLongPtr(m_hudWindow, 0, 0); |
|
292 |
|
293 if (m_fullscreenWindow) |
|
294 m_fullscreenWindow = 0; |
|
295 |
|
296 ASSERT(!IsWindow(m_hudWindow)); |
|
297 m_hudWindow = 0; |
|
298 |
|
299 // We previously ripped the mediaElement's platform layer out |
|
300 // of its orginial layer tree to display it in our fullscreen |
|
301 // window. Now, we need to get the layer back in its original |
|
302 // tree. |
|
303 // |
|
304 // As a side effect of setting the player to invisible/visible, |
|
305 // the player's layer will be recreated, and will be picked up |
|
306 // the next time the layer tree is synched. |
|
307 m_mediaElement->player()->setVisible(0); |
|
308 m_mediaElement->player()->setVisible(1); |
|
309 } |
|
310 |
|
311 bool FullscreenVideoController::canPlay() const |
|
312 { |
|
313 return m_mediaElement && m_mediaElement->canPlay(); |
|
314 } |
|
315 |
|
316 void FullscreenVideoController::play() |
|
317 { |
|
318 if (m_mediaElement) |
|
319 m_mediaElement->play(m_mediaElement->processingUserGesture()); |
|
320 } |
|
321 |
|
322 void FullscreenVideoController::pause() |
|
323 { |
|
324 if (m_mediaElement) |
|
325 m_mediaElement->pause(m_mediaElement->processingUserGesture()); |
|
326 } |
|
327 |
|
328 float FullscreenVideoController::volume() const |
|
329 { |
|
330 return m_mediaElement ? m_mediaElement->volume() : 0; |
|
331 } |
|
332 |
|
333 void FullscreenVideoController::setVolume(float volume) |
|
334 { |
|
335 if (m_mediaElement) { |
|
336 ExceptionCode ec; |
|
337 m_mediaElement->setVolume(volume, ec); |
|
338 } |
|
339 } |
|
340 |
|
341 float FullscreenVideoController::currentTime() const |
|
342 { |
|
343 return m_mediaElement ? m_mediaElement->currentTime() : 0; |
|
344 } |
|
345 |
|
346 void FullscreenVideoController::setCurrentTime(float value) |
|
347 { |
|
348 if (m_mediaElement) { |
|
349 ExceptionCode ec; |
|
350 m_mediaElement->setCurrentTime(value, ec); |
|
351 } |
|
352 } |
|
353 |
|
354 float FullscreenVideoController::duration() const |
|
355 { |
|
356 return m_mediaElement ? m_mediaElement->duration() : 0; |
|
357 } |
|
358 |
|
359 void FullscreenVideoController::beginScrubbing() |
|
360 { |
|
361 if (m_mediaElement) |
|
362 m_mediaElement->beginScrubbing(); |
|
363 } |
|
364 |
|
365 void FullscreenVideoController::endScrubbing() |
|
366 { |
|
367 if (m_mediaElement) |
|
368 m_mediaElement->endScrubbing(); |
|
369 } |
|
370 |
|
371 LRESULT FullscreenVideoController::fullscreenClientWndProc(HWND wnd, UINT message, WPARAM wParam, LPARAM lParam) |
|
372 { |
|
373 switch (message) { |
|
374 case WM_CHAR: |
|
375 onChar(wParam); |
|
376 break; |
|
377 case WM_KEYDOWN: |
|
378 onKeyDown(wParam); |
|
379 break; |
|
380 case WM_LBUTTONDOWN: |
|
381 onMouseDown(IntPoint(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam))); |
|
382 break; |
|
383 case WM_MOUSEMOVE: |
|
384 onMouseMove(IntPoint(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam))); |
|
385 break; |
|
386 case WM_LBUTTONUP: |
|
387 onMouseUp(IntPoint(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam))); |
|
388 break; |
|
389 } |
|
390 |
|
391 return DefWindowProc(wnd, message, wParam, lParam); |
|
392 } |
|
393 |
|
394 static const LPCWSTR fullscreenVideeoHUDWindowClassName = L"fullscreenVideeoHUDWindowClass"; |
|
395 |
|
396 void FullscreenVideoController::registerHUDWindowClass() |
|
397 { |
|
398 static bool haveRegisteredHUDWindowClass; |
|
399 if (haveRegisteredHUDWindowClass) |
|
400 return; |
|
401 |
|
402 haveRegisteredHUDWindowClass = true; |
|
403 |
|
404 WNDCLASSEX wcex; |
|
405 |
|
406 wcex.cbSize = sizeof(WNDCLASSEX); |
|
407 |
|
408 wcex.style = CS_HREDRAW | CS_VREDRAW; |
|
409 wcex.lpfnWndProc = hudWndProc; |
|
410 wcex.cbClsExtra = 0; |
|
411 wcex.cbWndExtra = 4; |
|
412 wcex.hInstance = gInstance; |
|
413 wcex.hIcon = 0; |
|
414 wcex.hCursor = LoadCursor(0, IDC_ARROW); |
|
415 wcex.hbrBackground = 0; |
|
416 wcex.lpszMenuName = 0; |
|
417 wcex.lpszClassName = fullscreenVideeoHUDWindowClassName; |
|
418 wcex.hIconSm = 0; |
|
419 |
|
420 RegisterClassEx(&wcex); |
|
421 } |
|
422 |
|
423 void FullscreenVideoController::createHUDWindow() |
|
424 { |
|
425 m_hudPosition.setX((m_fullscreenSize.width() - windowWidth) / 2); |
|
426 m_hudPosition.setY(m_fullscreenSize.height() * initialHUDPositionY - windowHeight / 2); |
|
427 |
|
428 // Local variable that will hold the returned pixels. No need to cleanup this value. It |
|
429 // will get cleaned up when m_bitmap is destroyed in the dtor |
|
430 void* pixels; |
|
431 BitmapInfo bitmapInfo = BitmapInfo::createBottomUp(IntSize(windowWidth, windowHeight)); |
|
432 m_bitmap.set(::CreateDIBSection(0, &bitmapInfo, DIB_RGB_COLORS, &pixels, 0, 0)); |
|
433 |
|
434 // Dirty the window so the HUD draws |
|
435 RECT clearRect = { m_hudPosition.x(), m_hudPosition.y(), m_hudPosition.x() + windowWidth, m_hudPosition.y() + windowHeight }; |
|
436 InvalidateRect(m_fullscreenWindow->hwnd(), &clearRect, true); |
|
437 |
|
438 m_playPauseButton.setShowAltButton(!canPlay()); |
|
439 m_volumeSlider.setValue(volume()); |
|
440 m_timeSlider.setValue(currentTime() / duration()); |
|
441 |
|
442 if (!canPlay()) |
|
443 m_timer.startRepeating(timerInterval); |
|
444 |
|
445 registerHUDWindowClass(); |
|
446 |
|
447 m_hudWindow = CreateWindowEx(WS_EX_LAYERED | WS_EX_TRANSPARENT | WS_EX_TOOLWINDOW, |
|
448 fullscreenVideeoHUDWindowClassName, 0, WS_POPUP | WS_VISIBLE, |
|
449 m_hudPosition.x(), m_hudPosition.y(), 0, 0, m_fullscreenWindow->hwnd(), 0, gInstance, 0); |
|
450 ASSERT(::IsWindow(m_hudWindow)); |
|
451 SetWindowLongPtr(m_hudWindow, 0, reinterpret_cast<LONG_PTR>(this)); |
|
452 |
|
453 draw(); |
|
454 } |
|
455 |
|
456 static String timeToString(float time) |
|
457 { |
|
458 if (!isfinite(time)) |
|
459 time = 0; |
|
460 int seconds = fabsf(time); |
|
461 int hours = seconds / (60 * 60); |
|
462 int minutes = (seconds / 60) % 60; |
|
463 seconds %= 60; |
|
464 |
|
465 if (hours) { |
|
466 if (hours > 9) |
|
467 return String::format("%s%02d:%02d:%02d", (time < 0 ? "-" : ""), hours, minutes, seconds); |
|
468 return String::format("%s%01d:%02d:%02d", (time < 0 ? "-" : ""), hours, minutes, seconds); |
|
469 } |
|
470 |
|
471 return String::format("%s%02d:%02d", (time < 0 ? "-" : ""), minutes, seconds); |
|
472 } |
|
473 |
|
474 void FullscreenVideoController::draw() |
|
475 { |
|
476 HDC windowDC = GetDC(m_hudWindow); |
|
477 HDC bitmapDC = CreateCompatibleDC(windowDC); |
|
478 ::ReleaseDC(m_hudWindow, windowDC); |
|
479 SelectObject(bitmapDC, m_bitmap.get()); |
|
480 |
|
481 GraphicsContext context(bitmapDC, true); |
|
482 |
|
483 context.save(); |
|
484 |
|
485 // Draw the background |
|
486 IntSize outerRadius(borderRadius, borderRadius); |
|
487 IntRect outerRect(0, 0, windowWidth, windowHeight); |
|
488 IntSize innerRadius(borderRadius - borderThickness, borderRadius - borderThickness); |
|
489 IntRect innerRect(borderThickness, borderThickness, windowWidth - borderThickness * 2, windowHeight - borderThickness * 2); |
|
490 |
|
491 context.fillRoundedRect(outerRect, outerRadius, outerRadius, outerRadius, outerRadius, Color(borderColor), DeviceColorSpace); |
|
492 context.setCompositeOperation(CompositeCopy); |
|
493 context.fillRoundedRect(innerRect, innerRadius, innerRadius, innerRadius, innerRadius, Color(backgroundColor), DeviceColorSpace); |
|
494 |
|
495 // Draw the widgets |
|
496 m_playPauseButton.draw(context); |
|
497 m_volumeUpButton.draw(context); |
|
498 m_volumeSliderButton.draw(context); |
|
499 m_volumeDownButton.draw(context); |
|
500 m_timeSliderButton.draw(context); |
|
501 m_exitFullscreenButton.draw(context); |
|
502 m_volumeSlider.draw(context); |
|
503 m_timeSlider.draw(context); |
|
504 |
|
505 // Draw the text strings |
|
506 FontDescription desc; |
|
507 |
|
508 NONCLIENTMETRICS metrics; |
|
509 metrics.cbSize = sizeof(metrics); |
|
510 SystemParametersInfo(SPI_GETNONCLIENTMETRICS, metrics.cbSize, &metrics, 0); |
|
511 FontFamily family; |
|
512 family.setFamily(metrics.lfSmCaptionFont.lfFaceName); |
|
513 desc.setFamily(family); |
|
514 |
|
515 desc.setComputedSize(textSize); |
|
516 Font font = Font(desc, 0, 0); |
|
517 font.update(0); |
|
518 |
|
519 String s; |
|
520 |
|
521 // The y positioning of these two text strings is tricky because they are so small. They |
|
522 // are currently positioned relative to the center of the slider and then down the font |
|
523 // height / 4 (which is actually half of font height /2), which positions the center of |
|
524 // the text at the center of the slider. |
|
525 // Left string |
|
526 s = timeToString(currentTime()); |
|
527 TextRun leftText(s); |
|
528 context.setFillColor(Color(textColor), DeviceColorSpace); |
|
529 context.drawText(font, leftText, IntPoint(windowWidth / 2 - timeSliderWidth / 2 - margin - font.width(leftText), windowHeight - margin - sliderHeight / 2 + font.height() / 4)); |
|
530 |
|
531 // Right string |
|
532 s = timeToString(currentTime() - duration()); |
|
533 TextRun rightText(s); |
|
534 context.setFillColor(Color(textColor), DeviceColorSpace); |
|
535 context.drawText(font, rightText, IntPoint(windowWidth / 2 + timeSliderWidth / 2 + margin, windowHeight - margin - sliderHeight / 2 + font.height() / 4)); |
|
536 |
|
537 // Copy to the window |
|
538 BLENDFUNCTION blendFunction = {AC_SRC_OVER, 0, 255, AC_SRC_ALPHA}; |
|
539 SIZE size = { windowWidth, windowHeight }; |
|
540 POINT sourcePoint = {0, 0}; |
|
541 POINT destPoint = { m_hudPosition.x(), m_hudPosition.y() }; |
|
542 BOOL result = UpdateLayeredWindow(m_hudWindow, 0, &destPoint, &size, bitmapDC, &sourcePoint, 0, &blendFunction, ULW_ALPHA); |
|
543 |
|
544 context.restore(); |
|
545 |
|
546 ::DeleteDC(bitmapDC); |
|
547 } |
|
548 |
|
549 LRESULT FullscreenVideoController::hudWndProc(HWND wnd, UINT message, WPARAM wParam, LPARAM lParam) |
|
550 { |
|
551 LONG_PTR longPtr = GetWindowLongPtr(wnd, 0); |
|
552 FullscreenVideoController* controller = reinterpret_cast<FullscreenVideoController*>(longPtr); |
|
553 if (!controller) |
|
554 return DefWindowProc(wnd, message, wParam, lParam); |
|
555 |
|
556 switch (message) { |
|
557 case WM_CHAR: |
|
558 controller->onChar(wParam); |
|
559 break; |
|
560 case WM_KEYDOWN: |
|
561 controller->onKeyDown(wParam); |
|
562 break; |
|
563 case WM_LBUTTONDOWN: |
|
564 controller->onMouseDown(IntPoint(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam))); |
|
565 break; |
|
566 case WM_MOUSEMOVE: |
|
567 controller->onMouseMove(IntPoint(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam))); |
|
568 break; |
|
569 case WM_LBUTTONUP: |
|
570 controller->onMouseUp(IntPoint(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam))); |
|
571 break; |
|
572 } |
|
573 |
|
574 return DefWindowProc(wnd, message, wParam, lParam); |
|
575 } |
|
576 |
|
577 void FullscreenVideoController::onChar(int c) |
|
578 { |
|
579 if (c == VK_ESCAPE) { |
|
580 if (m_mediaElement) |
|
581 m_mediaElement->exitFullscreen(); |
|
582 } else if (c == VK_SPACE) |
|
583 togglePlay(); |
|
584 } |
|
585 |
|
586 void FullscreenVideoController::onKeyDown(int virtualKey) |
|
587 { |
|
588 if (virtualKey == VK_ESCAPE) { |
|
589 if (m_mediaElement) |
|
590 m_mediaElement->exitFullscreen(); |
|
591 } |
|
592 } |
|
593 |
|
594 void FullscreenVideoController::timerFired(Timer<FullscreenVideoController>*) |
|
595 { |
|
596 // Update the time slider |
|
597 m_timeSlider.setValue(currentTime() / duration()); |
|
598 draw(); |
|
599 } |
|
600 |
|
601 void FullscreenVideoController::onMouseDown(const IntPoint& point) |
|
602 { |
|
603 IntPoint convertedPoint(fullscreenToHUDCoordinates(point)); |
|
604 |
|
605 // Don't bother hit testing if we're outside the bounds of the window |
|
606 if (convertedPoint.x() < 0 || convertedPoint.x() >= windowWidth || convertedPoint.y() < 0 || convertedPoint.y() >= windowHeight) |
|
607 return; |
|
608 |
|
609 m_hitWidget = 0; |
|
610 m_movingWindow = false; |
|
611 |
|
612 if (m_playPauseButton.hitTest(convertedPoint)) |
|
613 m_hitWidget = &m_playPauseButton; |
|
614 else if (m_exitFullscreenButton.hitTest(convertedPoint)) |
|
615 m_hitWidget = &m_exitFullscreenButton; |
|
616 else if (m_volumeUpButton.hitTest(convertedPoint)) |
|
617 m_hitWidget = &m_volumeUpButton; |
|
618 else if (m_volumeDownButton.hitTest(convertedPoint)) |
|
619 m_hitWidget = &m_volumeDownButton; |
|
620 else if (m_volumeSlider.hitTest(convertedPoint)) { |
|
621 m_hitWidget = &m_volumeSlider; |
|
622 m_volumeSlider.drag(convertedPoint, true); |
|
623 setVolume(m_volumeSlider.value()); |
|
624 } else if (m_timeSlider.hitTest(convertedPoint)) { |
|
625 m_hitWidget = &m_timeSlider; |
|
626 m_timeSlider.drag(convertedPoint, true); |
|
627 beginScrubbing(); |
|
628 setCurrentTime(m_timeSlider.value() * duration()); |
|
629 } |
|
630 |
|
631 // If we did not pick any of our widgets we are starting a window move |
|
632 if (!m_hitWidget) { |
|
633 m_moveOffset = convertedPoint; |
|
634 m_movingWindow = true; |
|
635 } |
|
636 |
|
637 draw(); |
|
638 } |
|
639 |
|
640 void FullscreenVideoController::onMouseMove(const IntPoint& point) |
|
641 { |
|
642 IntPoint convertedPoint(fullscreenToHUDCoordinates(point)); |
|
643 |
|
644 if (m_hitWidget) { |
|
645 m_hitWidget->drag(convertedPoint, false); |
|
646 if (m_hitWidget == &m_volumeSlider) |
|
647 setVolume(m_volumeSlider.value()); |
|
648 else if (m_hitWidget == &m_timeSlider) |
|
649 setCurrentTime(m_timeSlider.value() * duration()); |
|
650 draw(); |
|
651 } else if (m_movingWindow) |
|
652 m_hudPosition.move(convertedPoint.x() - m_moveOffset.x(), convertedPoint.y() - m_moveOffset.y()); |
|
653 } |
|
654 |
|
655 void FullscreenVideoController::onMouseUp(const IntPoint& point) |
|
656 { |
|
657 IntPoint convertedPoint(fullscreenToHUDCoordinates(point)); |
|
658 m_movingWindow = false; |
|
659 |
|
660 if (m_hitWidget) { |
|
661 if (m_hitWidget == &m_playPauseButton && m_playPauseButton.hitTest(convertedPoint)) |
|
662 togglePlay(); |
|
663 else if (m_hitWidget == &m_volumeUpButton && m_volumeUpButton.hitTest(convertedPoint)) { |
|
664 setVolume(1); |
|
665 m_volumeSlider.setValue(1); |
|
666 } else if (m_hitWidget == &m_volumeDownButton && m_volumeDownButton.hitTest(convertedPoint)) { |
|
667 setVolume(0); |
|
668 m_volumeSlider.setValue(0); |
|
669 } else if (m_hitWidget == &m_timeSlider) |
|
670 endScrubbing(); |
|
671 else if (m_hitWidget == &m_exitFullscreenButton && m_exitFullscreenButton.hitTest(convertedPoint)) { |
|
672 m_hitWidget = 0; |
|
673 if (m_mediaElement) |
|
674 m_mediaElement->exitFullscreen(); |
|
675 return; |
|
676 } |
|
677 } |
|
678 |
|
679 m_hitWidget = 0; |
|
680 draw(); |
|
681 } |
|
682 |
|
683 void FullscreenVideoController::togglePlay() |
|
684 { |
|
685 if (canPlay()) |
|
686 play(); |
|
687 else |
|
688 pause(); |
|
689 |
|
690 m_playPauseButton.setShowAltButton(!canPlay()); |
|
691 |
|
692 // Run a timer while the video is playing so we can keep the time |
|
693 // slider and time values up to date. |
|
694 if (!canPlay()) |
|
695 m_timer.startRepeating(timerInterval); |
|
696 else |
|
697 m_timer.stop(); |
|
698 |
|
699 draw(); |
|
700 } |
|
701 |
|
702 #endif |