2 * Copyright (C) 2011 Apple Inc. All rights reserved.
3 * Copyright (C) 2011 Google Inc. All rights reserved.
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
14 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
15 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
18 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
19 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
20 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
21 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
22 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 #include "MediaControls.h"
33 #include "HTMLDivElement.h"
34 #include "HTMLMediaElement.h"
35 #include "HTMLNames.h"
37 #include "MediaControlElements.h"
38 #include "MouseEvent.h"
40 #include "RenderLayer.h"
41 #include "RenderTheme.h"
42 #include <wtf/CurrentTime.h>
43 #include <wtf/MathExtras.h>
46 #include "TouchEvent.h"
53 >>>>>>> WebKit.org at r84325
57 MediaControls::MediaControls(HTMLMediaElement* mediaElement)
59 : m_mediaElement(mediaElement)
60 , m_opacityAnimationTimer(this, &MediaControls::opacityAnimationTimerFired)
61 , m_opacityAnimationStartTime(0)
62 , m_opacityAnimationDuration(0)
63 , m_opacityAnimationFrom(0)
64 , m_opacityAnimationTo(1.0f)
72 // FIXME: This will turn into the standard element factory method once shadow DOM conversion is complete.
73 // (see https://bugs.webkit.org/show_bug.cgi?id=53020)
74 PassRefPtr<MediaControlShadowRootElement> MediaControls::create(HTMLMediaElement* mediaElement)
77 ASSERT(!m_muteButton);
78 ASSERT(!m_playButton);
79 ASSERT(!m_returnToRealtimeButton);
80 ASSERT(!m_statusDisplay);
81 ASSERT(!m_timelineContainer);
82 ASSERT(!m_currentTimeDisplay);
84 ASSERT(!m_timeRemainingDisplay);
85 ASSERT(!m_seekBackButton);
86 ASSERT(!m_seekForwardButton);
87 ASSERT(!m_toggleClosedCaptionsButton);
88 ASSERT(!m_fullscreenButton);
89 ASSERT(!m_muteButton);
90 ASSERT(!m_volumeSliderContainer);
91 ASSERT(!m_volumeSlider);
92 ASSERT(!m_volumeSliderMuteButton);
93 ASSERT(!m_fullScreenMinVolumeButton);
94 ASSERT(!m_fullScreenMaxVolumeButton);
95 ASSERT(!m_fullScreenVolumeSlider);
97 RefPtr<MediaControlShadowRootElement> controls = MediaControlShadowRootElement::create(mediaElement);
99 m_panel = MediaControlPanelElement::create(mediaElement);
101 m_rewindButton = MediaControlRewindButtonElement::create(mediaElement);
102 m_rewindButton->attachToParent(m_panel.get());
104 m_playButton = MediaControlPlayButtonElement::create(mediaElement);
105 m_playButton->attachToParent(m_panel.get());
107 m_returnToRealtimeButton = MediaControlReturnToRealtimeButtonElement::create(mediaElement);
108 m_returnToRealtimeButton->attachToParent(m_panel.get());
110 m_statusDisplay = MediaControlStatusDisplayElement::create(mediaElement);
111 m_statusDisplay->attachToParent(m_panel.get());
113 m_timelineContainer = MediaControlTimelineContainerElement::create(mediaElement);
115 m_currentTimeDisplay = MediaControlCurrentTimeDisplayElement::create(mediaElement);
116 m_currentTimeDisplay->attachToParent(m_timelineContainer.get());
118 m_timeline = MediaControlTimelineElement::create(mediaElement);
119 m_timeline->attachToParent(m_timelineContainer.get());
121 m_timeRemainingDisplay = MediaControlTimeRemainingDisplayElement::create(mediaElement);
122 m_timeRemainingDisplay->attachToParent(m_timelineContainer.get());
124 m_timelineContainer->attachToParent(m_panel.get());
126 m_seekBackButton = MediaControlSeekBackButtonElement::create(mediaElement);
127 m_seekBackButton->attachToParent(m_panel.get());
129 m_seekForwardButton = MediaControlSeekForwardButtonElement::create(mediaElement);
130 m_seekForwardButton->attachToParent(m_panel.get());
132 m_toggleClosedCaptionsButton = MediaControlToggleClosedCaptionsButtonElement::create(mediaElement);
133 m_toggleClosedCaptionsButton->attachToParent(m_panel.get());
135 m_fullscreenButton = MediaControlFullscreenButtonElement::create(mediaElement);
136 m_fullscreenButton->attachToParent(m_panel.get());
138 m_muteButton = MediaControlPanelMuteButtonElement::create(mediaElement);
139 m_muteButton->attachToParent(m_panel.get());
141 m_volumeSliderContainer = MediaControlVolumeSliderContainerElement::create(mediaElement);
143 m_volumeSlider = MediaControlVolumeSliderElement::create(mediaElement);
144 m_volumeSlider->attachToParent(m_volumeSliderContainer.get());
146 m_volumeSliderMuteButton = MediaControlVolumeSliderMuteButtonElement::create(mediaElement);
147 m_volumeSliderMuteButton->attachToParent(m_volumeSliderContainer.get());
149 m_volumeSliderContainer->attachToParent(m_panel.get());
151 // FIXME: These controls, and others, should be created dynamically when needed, instead of
152 // always created. <http://webkit.org/b/57163>
153 m_fullScreenMinVolumeButton = MediaControlFullscreenVolumeMinButtonElement::create(mediaElement);
154 m_fullScreenMinVolumeButton->attachToParent(m_panel.get());
156 m_fullScreenVolumeSlider = MediaControlFullscreenVolumeSliderElement::create(mediaElement);
157 m_fullScreenVolumeSlider->attachToParent(m_panel.get());
159 m_fullScreenMaxVolumeButton = MediaControlFullscreenVolumeMaxButtonElement::create(mediaElement);
160 m_fullScreenMaxVolumeButton->attachToParent(m_panel.get());
162 m_panel->attachToParent(controls.get());
163 return controls.release();
166 void MediaControls::reset()
171 void MediaControls::playbackProgressed()
174 m_timeline->update(false);
178 void MediaControls::playbackStarted()
180 playbackProgressed();
183 void MediaControls::playbackStopped()
185 playbackProgressed();
188 void MediaControls::changedMute()
193 void MediaControls::changedVolume()
198 void MediaControls::changedClosedCaptionsVisibility()
203 void MediaControls::updateStyle()
205 if (!m_controlsShadowRoot)
209 m_panel->updateStyle();
211 m_muteButton->updateStyle();
213 m_playButton->updateStyle();
214 if (m_seekBackButton)
215 m_seekBackButton->updateStyle();
216 if (m_seekForwardButton)
217 m_seekForwardButton->updateStyle();
219 m_rewindButton->updateStyle();
220 if (m_returnToRealtimeButton)
221 m_returnToRealtimeButton->updateStyle();
222 if (m_toggleClosedCaptionsButton)
223 m_toggleClosedCaptionsButton->updateStyle();
225 m_statusDisplay->updateStyle();
226 if (m_timelineContainer)
227 m_timelineContainer->updateStyle();
229 m_timeline->updateStyle();
230 if (m_fullscreenButton)
231 m_fullscreenButton->updateStyle();
232 if (m_currentTimeDisplay)
233 m_currentTimeDisplay->updateStyle();
234 if (m_timeRemainingDisplay)
235 m_timeRemainingDisplay->updateStyle();
236 if (m_volumeSliderContainer)
237 m_volumeSliderContainer->updateStyle();
238 if (m_volumeSliderMuteButton)
239 m_volumeSliderMuteButton->updateStyle();
241 m_volumeSlider->updateStyle();
242 if (m_fullScreenMinVolumeButton)
243 m_fullScreenMinVolumeButton->updateStyle();
244 if (m_fullScreenVolumeSlider)
245 m_fullScreenVolumeSlider->updateStyle();
246 if (m_fullScreenMaxVolumeButton)
247 m_fullScreenMaxVolumeButton->updateStyle();
250 void MediaControls::destroy()
252 ASSERT(m_mediaElement->renderer());
254 if (m_controlsShadowRoot && m_controlsShadowRoot->renderer()) {
256 // detach the panel before removing the shadow renderer to prevent a crash in m_controlsShadowRoot->detach()
257 // when display: style changes
260 m_mediaElement->renderer()->removeChild(m_controlsShadowRoot->renderer());
261 m_controlsShadowRoot->detach();
262 m_controlsShadowRoot = 0;
266 void MediaControls::update()
268 HTMLMediaElement* media = m_mediaElement;
269 if (!media->controls() || !media->inActiveDocument()) {
270 if (m_controlsShadowRoot) {
271 m_controlsShadowRoot->detach();
276 m_timelineContainer = 0;
278 m_seekBackButton = 0;
279 m_seekForwardButton = 0;
281 m_returnToRealtimeButton = 0;
282 m_currentTimeDisplay = 0;
283 m_timeRemainingDisplay = 0;
284 m_fullscreenButton = 0;
285 m_volumeSliderContainer = 0;
287 m_volumeSliderMuteButton = 0;
288 m_controlsShadowRoot = 0;
289 m_toggleClosedCaptionsButton = 0;
290 m_fullScreenMinVolumeButton = 0;
291 m_fullScreenVolumeSlider = 0;
292 m_fullScreenMaxVolumeButton = 0;
294 m_opacityAnimationTo = 1.0f;
295 m_opacityAnimationTimer.stop();
299 if (!m_controlsShadowRoot) {
300 m_controlsShadowRoot = create(m_mediaElement);
301 m_mediaElement->renderer()->addChild(m_controlsShadowRoot->renderer());
306 // update() might alter the opacity of the element, especially if we are in the middle
307 // of an animation. This is the only element concerned as we animate only this element.
308 float opacityBeforeChangingStyle = m_panel->renderer() ? m_panel->renderer()->style()->opacity() : 0;
310 changeOpacity(m_panel.get(), opacityBeforeChangingStyle);
313 m_muteButton->update();
315 m_playButton->update();
316 if (m_timelineContainer)
317 m_timelineContainer->update();
318 if (m_volumeSliderContainer)
319 m_volumeSliderContainer->update();
321 m_timeline->update();
322 if (m_currentTimeDisplay)
323 m_currentTimeDisplay->update();
324 if (m_timeRemainingDisplay)
325 m_timeRemainingDisplay->update();
326 if (m_seekBackButton)
327 m_seekBackButton->update();
328 if (m_seekForwardButton)
329 m_seekForwardButton->update();
331 m_rewindButton->update();
332 if (m_returnToRealtimeButton)
333 m_returnToRealtimeButton->update();
334 if (m_toggleClosedCaptionsButton)
335 m_toggleClosedCaptionsButton->update();
337 m_statusDisplay->update();
338 if (m_fullscreenButton)
339 m_fullscreenButton->update();
341 m_volumeSlider->update();
342 if (m_volumeSliderMuteButton)
343 m_volumeSliderMuteButton->update();
344 if (m_fullScreenMinVolumeButton)
345 m_fullScreenMinVolumeButton->update();
346 if (m_fullScreenVolumeSlider)
347 m_fullScreenVolumeSlider->update();
348 if (m_fullScreenMaxVolumeButton)
349 m_fullScreenMaxVolumeButton->update();
351 updateControlVisibility();
354 void MediaControls::updateTimeDisplay()
356 ASSERT(m_mediaElement->renderer());
358 if (!m_currentTimeDisplay || !m_currentTimeDisplay->renderer() || m_currentTimeDisplay->renderer()->style()->display() == NONE || m_mediaElement->renderer()->style()->visibility() != VISIBLE)
361 float now = m_mediaElement->currentTime();
362 float duration = m_mediaElement->duration();
364 // Allow the theme to format the time
366 m_currentTimeDisplay->setInnerText(m_mediaElement->renderer()->theme()->formatMediaControlsCurrentTime(now, duration), ec);
367 m_currentTimeDisplay->setCurrentValue(now);
368 m_timeRemainingDisplay->setInnerText(m_mediaElement->renderer()->theme()->formatMediaControlsRemainingTime(now, duration), ec);
369 m_timeRemainingDisplay->setCurrentValue(now - duration);
372 RenderBox* MediaControls::renderBox()
374 return m_controlsShadowRoot ? m_controlsShadowRoot->renderBox() : 0;
377 void MediaControls::updateControlVisibility()
379 if (!m_panel || !m_panel->renderer())
382 // Don't fade for audio controls.
383 HTMLMediaElement* media = m_mediaElement;
384 if (!media->hasVideo())
387 ASSERT(media->renderer());
389 // Don't fade if the media element is not visible
390 if (media->renderer()->style()->visibility() != VISIBLE)
393 #if PLATFORM(ANDROID)
394 if (WTF::currentTime() - m_lastTouch > TOUCH_DELAY)
400 bool shouldHideController = !m_mouseOver && !media->canPlay();
402 // Do fading manually, css animations don't work with shadow trees
404 float animateFrom = m_panel->renderer()->style()->opacity();
405 float animateTo = shouldHideController ? 0.0f : 1.0f;
407 if (animateFrom == animateTo)
410 if (m_opacityAnimationTimer.isActive()) {
411 if (m_opacityAnimationTo == animateTo)
413 m_opacityAnimationTimer.stop();
416 if (animateFrom < animateTo)
417 m_opacityAnimationDuration = m_panel->renderer()->theme()->mediaControlsFadeInDuration();
419 m_opacityAnimationDuration = m_panel->renderer()->theme()->mediaControlsFadeOutDuration();
421 m_opacityAnimationFrom = animateFrom;
422 m_opacityAnimationTo = animateTo;
424 m_opacityAnimationStartTime = currentTime();
425 m_opacityAnimationTimer.startRepeating(cOpacityAnimationRepeatDelay);
428 void MediaControls::changeOpacity(HTMLElement* e, float opacity)
430 if (!e || !e->renderer() || !e->renderer()->style())
432 RefPtr<RenderStyle> s = RenderStyle::clone(e->renderer()->style());
433 s->setOpacity(opacity);
434 // z-index can't be auto if opacity is used
436 e->renderer()->setStyle(s.release());
439 void MediaControls::opacityAnimationTimerFired(Timer<MediaControls>*)
441 double time = currentTime() - m_opacityAnimationStartTime;
442 if (time >= m_opacityAnimationDuration) {
443 time = m_opacityAnimationDuration;
444 m_opacityAnimationTimer.stop();
446 float opacity = narrowPrecisionToFloat(m_opacityAnimationFrom + (m_opacityAnimationTo - m_opacityAnimationFrom) * time / m_opacityAnimationDuration);
447 changeOpacity(m_panel.get(), opacity);
450 void MediaControls::updateVolumeSliderContainer(bool visible)
452 if (!m_mediaElement->hasAudio() || !m_volumeSliderContainer || !m_volumeSlider)
455 if (visible && !m_volumeSliderContainer->isVisible()) {
456 if (!m_muteButton || !m_muteButton->renderer() || !m_muteButton->renderBox())
459 RefPtr<RenderStyle> s = m_volumeSliderContainer->styleForElement();
460 m_volumeSliderContainer->setVisible(true);
461 m_volumeSliderContainer->update();
462 m_volumeSlider->update();
463 } else if (!visible && m_volumeSliderContainer->isVisible()) {
464 m_volumeSliderContainer->setVisible(false);
465 m_volumeSliderContainer->updateStyle();
469 void MediaControls::forwardEvent(Event* event)
471 #if PLATFORM(ANDROID)
472 if (event->isMouseEvent())
474 #if ENABLE(TOUCH_EVENTS)
475 if (event->isTouchEvent())
480 ASSERT(m_mediaElement->renderer());
482 if (event->isMouseEvent() && m_controlsShadowRoot) {
483 MouseEvent* mouseEvent = static_cast<MouseEvent*>(event);
484 IntPoint point(mouseEvent->absoluteLocation());
486 bool defaultHandled = false;
487 if (m_volumeSliderMuteButton && m_volumeSliderMuteButton->hitTest(point)) {
488 m_volumeSliderMuteButton->defaultEventHandler(event);
489 defaultHandled = event->defaultHandled();
492 bool showVolumeSlider = false;
493 if (!defaultHandled && m_muteButton && m_muteButton->hitTest(point)) {
494 m_muteButton->defaultEventHandler(event);
495 if (event->type() != eventNames().mouseoutEvent)
496 showVolumeSlider = true;
499 if (m_volumeSliderContainer && m_volumeSliderContainer->hitTest(point))
500 showVolumeSlider = true;
502 if (m_volumeSlider && m_volumeSlider->hitTest(point)) {
503 m_volumeSlider->defaultEventHandler(event);
504 showVolumeSlider = true;
507 updateVolumeSliderContainer(showVolumeSlider);
509 if (m_playButton && m_playButton->hitTest(point))
510 m_playButton->defaultEventHandler(event);
512 if (m_seekBackButton && m_seekBackButton->hitTest(point))
513 m_seekBackButton->defaultEventHandler(event);
515 if (m_seekForwardButton && m_seekForwardButton->hitTest(point))
516 m_seekForwardButton->defaultEventHandler(event);
518 if (m_rewindButton && m_rewindButton->hitTest(point))
519 m_rewindButton->defaultEventHandler(event);
521 if (m_returnToRealtimeButton && m_returnToRealtimeButton->hitTest(point))
522 m_returnToRealtimeButton->defaultEventHandler(event);
524 if (m_toggleClosedCaptionsButton && m_toggleClosedCaptionsButton->hitTest(point))
525 m_toggleClosedCaptionsButton->defaultEventHandler(event);
527 if (m_timeline && m_timeline->hitTest(point))
528 m_timeline->defaultEventHandler(event);
530 if (m_fullscreenButton && m_fullscreenButton->hitTest(point))
531 m_fullscreenButton->defaultEventHandler(event);
533 if (event->type() == eventNames().mouseoverEvent) {
535 updateControlVisibility();
537 if (event->type() == eventNames().mouseoutEvent) {
538 // When the scrollbar thumb captures mouse events, we should treat the mouse as still being over our renderer if the new target is a descendant
539 Node* mouseOverNode = mouseEvent->relatedTarget() ? mouseEvent->relatedTarget()->toNode() : 0;
540 RenderObject* mouseOverRenderer = mouseOverNode ? mouseOverNode->renderer() : 0;
541 m_mouseOver = mouseOverRenderer && mouseOverRenderer->isDescendantOf(m_mediaElement->renderer());
542 updateControlVisibility();
545 #if PLATFORM(ANDROID) && ENABLE(TOUCH_EVENTS)
546 // We want to process touch events landing on the timeline so that the user
547 // can drag the scrollbar thumb with their finger.
548 else if (event->isTouchEvent() && m_controlsShadowRoot) {
549 TouchEvent* touchEvent = static_cast<TouchEvent*>(event);
550 if (touchEvent->touches() && touchEvent->touches()->item(0)) {
552 point.setX(touchEvent->touches()->item(0)->pageX());
553 point.setY(touchEvent->touches()->item(0)->pageY());
554 if (m_timeline && m_timeline->hitTest(point))
555 m_timeline->defaultEventHandler(event);
560 : HTMLDivElement(HTMLNames::divTag, mediaElement->document())
562 >>>>>>> WebKit.org at r84325
565 #if PLATFORM(ANDROID)
566 void MediaControls::updateLastTouch()
568 m_lastTouch = WTF::currentTime();