summaryrefslogtreecommitdiffstats
path: root/Source/WebCore/html/shadow/MediaControls.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'Source/WebCore/html/shadow/MediaControls.cpp')
-rw-r--r--Source/WebCore/html/shadow/MediaControls.cpp572
1 files changed, 572 insertions, 0 deletions
diff --git a/Source/WebCore/html/shadow/MediaControls.cpp b/Source/WebCore/html/shadow/MediaControls.cpp
new file mode 100644
index 0000000..731a934
--- /dev/null
+++ b/Source/WebCore/html/shadow/MediaControls.cpp
@@ -0,0 +1,572 @@
+/*
+ * Copyright (C) 2007, 2008, 2009, 2010 Apple Inc. All rights reserved.
+ * Copyright (C) 2011 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+
+#if ENABLE(VIDEO)
+#include "MediaControls.h"
+
+#include "EventNames.h"
+#include "FloatConversion.h"
+#include "HTMLNames.h"
+#include "MediaControlElements.h"
+#include "MouseEvent.h"
+#include "Page.h"
+#include "RenderLayer.h"
+#include "RenderTheme.h"
+#include <wtf/CurrentTime.h>
+#include <wtf/MathExtras.h>
+
+
+using namespace std;
+
+namespace WebCore {
+
+using namespace HTMLNames;
+
+static const double cTimeUpdateRepeatDelay = 0.2;
+static const double cOpacityAnimationRepeatDelay = 0.05;
+
+MediaControls::MediaControls(HTMLMediaElement* mediaElement)
+ : m_mediaElement(mediaElement)
+ , m_timeUpdateTimer(this, &MediaControls::timeUpdateTimerFired)
+ , m_opacityAnimationTimer(this, &MediaControls::opacityAnimationTimerFired)
+ , m_opacityAnimationStartTime(0)
+ , m_opacityAnimationDuration(0)
+ , m_opacityAnimationFrom(0)
+ , m_opacityAnimationTo(1.0f)
+ , m_mouseOver(false)
+{
+}
+
+void MediaControls::updateStyle()
+{
+ if (!m_controlsShadowRoot)
+ return;
+
+ if (m_panel)
+ m_panel->updateStyle();
+ if (m_muteButton)
+ m_muteButton->updateStyle();
+ if (m_playButton)
+ m_playButton->updateStyle();
+ if (m_seekBackButton)
+ m_seekBackButton->updateStyle();
+ if (m_seekForwardButton)
+ m_seekForwardButton->updateStyle();
+ if (m_rewindButton)
+ m_rewindButton->updateStyle();
+ if (m_returnToRealtimeButton)
+ m_returnToRealtimeButton->updateStyle();
+ if (m_toggleClosedCaptionsButton)
+ m_toggleClosedCaptionsButton->updateStyle();
+ if (m_statusDisplay)
+ m_statusDisplay->updateStyle();
+ if (m_timelineContainer)
+ m_timelineContainer->updateStyle();
+ if (m_timeline)
+ m_timeline->updateStyle();
+ if (m_fullscreenButton)
+ m_fullscreenButton->updateStyle();
+ if (m_currentTimeDisplay)
+ m_currentTimeDisplay->updateStyle();
+ if (m_timeRemainingDisplay)
+ m_timeRemainingDisplay->updateStyle();
+ if (m_volumeSliderContainer)
+ m_volumeSliderContainer->updateStyle();
+ if (m_volumeSliderMuteButton)
+ m_volumeSliderMuteButton->updateStyle();
+ if (m_volumeSlider)
+ m_volumeSlider->updateStyle();
+}
+
+void MediaControls::destroy()
+{
+ ASSERT(m_mediaElement->renderer());
+
+ if (m_controlsShadowRoot && m_controlsShadowRoot->renderer()) {
+
+ // detach the panel before removing the shadow renderer to prevent a crash in m_controlsShadowRoot->detach()
+ // when display: style changes
+ m_panel->detach();
+
+ m_mediaElement->renderer()->removeChild(m_controlsShadowRoot->renderer());
+ m_controlsShadowRoot->detach();
+ m_controlsShadowRoot = 0;
+ }
+}
+
+void MediaControls::update()
+{
+ HTMLMediaElement* media = m_mediaElement;
+ if (!media->controls() || !media->inActiveDocument()) {
+ if (m_controlsShadowRoot) {
+ m_controlsShadowRoot->detach();
+ m_panel = 0;
+ m_muteButton = 0;
+ m_playButton = 0;
+ m_statusDisplay = 0;
+ m_timelineContainer = 0;
+ m_timeline = 0;
+ m_seekBackButton = 0;
+ m_seekForwardButton = 0;
+ m_rewindButton = 0;
+ m_returnToRealtimeButton = 0;
+ m_currentTimeDisplay = 0;
+ m_timeRemainingDisplay = 0;
+ m_fullscreenButton = 0;
+ m_volumeSliderContainer = 0;
+ m_volumeSlider = 0;
+ m_volumeSliderMuteButton = 0;
+ m_controlsShadowRoot = 0;
+ m_toggleClosedCaptionsButton = 0;
+ }
+ m_opacityAnimationTo = 1.0f;
+ m_opacityAnimationTimer.stop();
+ m_timeUpdateTimer.stop();
+ return;
+ }
+
+ if (!m_controlsShadowRoot) {
+ createControlsShadowRoot();
+ createPanel();
+ if (m_panel) {
+ createRewindButton();
+ createPlayButton();
+ createReturnToRealtimeButton();
+ createStatusDisplay();
+ createTimelineContainer();
+ if (m_timelineContainer) {
+ createCurrentTimeDisplay();
+ createTimeline();
+ createTimeRemainingDisplay();
+ }
+ createSeekBackButton();
+ createSeekForwardButton();
+ createToggleClosedCaptionsButton();
+ createFullscreenButton();
+ createMuteButton();
+ createVolumeSliderContainer();
+ if (m_volumeSliderContainer) {
+ createVolumeSlider();
+ createVolumeSliderMuteButton();
+ }
+ m_panel->attach();
+ }
+ }
+
+ if (media->canPlay()) {
+ if (m_timeUpdateTimer.isActive())
+ m_timeUpdateTimer.stop();
+ } else if (media->renderer()->style()->visibility() == VISIBLE && m_timeline && m_timeline->renderer() && m_timeline->renderer()->style()->display() != NONE) {
+ m_timeUpdateTimer.startRepeating(cTimeUpdateRepeatDelay);
+ }
+
+ if (m_panel) {
+ // update() might alter the opacity of the element, especially if we are in the middle
+ // of an animation. This is the only element concerned as we animate only this element.
+ float opacityBeforeChangingStyle = m_panel->renderer() ? m_panel->renderer()->style()->opacity() : 0;
+ m_panel->update();
+ changeOpacity(m_panel.get(), opacityBeforeChangingStyle);
+ }
+ if (m_muteButton)
+ m_muteButton->update();
+ if (m_playButton)
+ m_playButton->update();
+ if (m_timelineContainer)
+ m_timelineContainer->update();
+ if (m_volumeSliderContainer)
+ m_volumeSliderContainer->update();
+ if (m_timeline)
+ m_timeline->update();
+ if (m_currentTimeDisplay)
+ m_currentTimeDisplay->update();
+ if (m_timeRemainingDisplay)
+ m_timeRemainingDisplay->update();
+ if (m_seekBackButton)
+ m_seekBackButton->update();
+ if (m_seekForwardButton)
+ m_seekForwardButton->update();
+ if (m_rewindButton)
+ m_rewindButton->update();
+ if (m_returnToRealtimeButton)
+ m_returnToRealtimeButton->update();
+ if (m_toggleClosedCaptionsButton)
+ m_toggleClosedCaptionsButton->update();
+ if (m_statusDisplay)
+ m_statusDisplay->update();
+ if (m_fullscreenButton)
+ m_fullscreenButton->update();
+ if (m_volumeSlider)
+ m_volumeSlider->update();
+ if (m_volumeSliderMuteButton)
+ m_volumeSliderMuteButton->update();
+
+ updateTimeDisplay();
+ updateControlVisibility();
+}
+
+void MediaControls::createControlsShadowRoot()
+{
+ ASSERT(!m_controlsShadowRoot);
+ m_controlsShadowRoot = MediaControlShadowRootElement::create(m_mediaElement);
+ m_mediaElement->renderer()->addChild(m_controlsShadowRoot->renderer());
+}
+
+void MediaControls::createPanel()
+{
+ ASSERT(!m_panel);
+ m_panel = MediaControlPanelElement::create(m_mediaElement);
+ m_panel->attachToParent(m_controlsShadowRoot.get());
+}
+
+void MediaControls::createMuteButton()
+{
+ ASSERT(!m_muteButton);
+ m_muteButton = MediaControlMuteButtonElement::create(m_mediaElement);
+ m_muteButton->attachToParent(m_panel.get());
+}
+
+void MediaControls::createPlayButton()
+{
+ ASSERT(!m_playButton);
+ m_playButton = MediaControlPlayButtonElement::create(m_mediaElement);
+ m_playButton->attachToParent(m_panel.get());
+}
+
+void MediaControls::createSeekBackButton()
+{
+ ASSERT(!m_seekBackButton);
+ m_seekBackButton = MediaControlSeekBackButtonElement::create(m_mediaElement);
+ m_seekBackButton->attachToParent(m_panel.get());
+}
+
+void MediaControls::createSeekForwardButton()
+{
+ ASSERT(!m_seekForwardButton);
+ m_seekForwardButton = MediaControlSeekForwardButtonElement::create(m_mediaElement);
+ m_seekForwardButton->attachToParent(m_panel.get());
+}
+
+void MediaControls::createRewindButton()
+{
+ ASSERT(!m_rewindButton);
+ m_rewindButton = MediaControlRewindButtonElement::create(m_mediaElement);
+ m_rewindButton->attachToParent(m_panel.get());
+}
+
+void MediaControls::createReturnToRealtimeButton()
+{
+ ASSERT(!m_returnToRealtimeButton);
+ m_returnToRealtimeButton = MediaControlReturnToRealtimeButtonElement::create(m_mediaElement);
+ m_returnToRealtimeButton->attachToParent(m_panel.get());
+}
+
+void MediaControls::createToggleClosedCaptionsButton()
+{
+ ASSERT(!m_toggleClosedCaptionsButton);
+ m_toggleClosedCaptionsButton = MediaControlToggleClosedCaptionsButtonElement::create(m_mediaElement);
+ m_toggleClosedCaptionsButton->attachToParent(m_panel.get());
+}
+
+void MediaControls::createStatusDisplay()
+{
+ ASSERT(!m_statusDisplay);
+ m_statusDisplay = MediaControlStatusDisplayElement::create(m_mediaElement);
+ m_statusDisplay->attachToParent(m_panel.get());
+}
+
+void MediaControls::createTimelineContainer()
+{
+ ASSERT(!m_timelineContainer);
+ m_timelineContainer = MediaControlTimelineContainerElement::create(m_mediaElement);
+ m_timelineContainer->attachToParent(m_panel.get());
+}
+
+void MediaControls::createTimeline()
+{
+ ASSERT(!m_timeline);
+ m_timeline = MediaControlTimelineElement::create(m_mediaElement);
+ m_timeline->setAttribute(precisionAttr, "float");
+ m_timeline->attachToParent(m_timelineContainer.get());
+}
+
+void MediaControls::createVolumeSliderContainer()
+{
+ ASSERT(!m_volumeSliderContainer);
+ m_volumeSliderContainer = MediaControlVolumeSliderContainerElement::create(m_mediaElement);
+ m_volumeSliderContainer->attachToParent(m_panel.get());
+}
+
+void MediaControls::createVolumeSlider()
+{
+ ASSERT(!m_volumeSlider);
+ m_volumeSlider = MediaControlVolumeSliderElement::create(m_mediaElement);
+ m_volumeSlider->setAttribute(precisionAttr, "float");
+ m_volumeSlider->setAttribute(maxAttr, "1");
+ m_volumeSlider->setAttribute(valueAttr, String::number(m_mediaElement->volume()));
+ m_volumeSlider->attachToParent(m_volumeSliderContainer.get());
+}
+
+void MediaControls::createVolumeSliderMuteButton()
+{
+ ASSERT(!m_volumeSliderMuteButton);
+ m_volumeSliderMuteButton = MediaControlVolumeSliderMuteButtonElement::create(m_mediaElement);
+ m_volumeSliderMuteButton->attachToParent(m_volumeSliderContainer.get());
+}
+
+void MediaControls::createCurrentTimeDisplay()
+{
+ ASSERT(!m_currentTimeDisplay);
+ m_currentTimeDisplay = MediaControlCurrentTimeDisplayElement::create(m_mediaElement);
+ m_currentTimeDisplay->attachToParent(m_timelineContainer.get());
+}
+
+void MediaControls::createTimeRemainingDisplay()
+{
+ ASSERT(!m_timeRemainingDisplay);
+ m_timeRemainingDisplay = MediaControlTimeRemainingDisplayElement::create(m_mediaElement);
+ m_timeRemainingDisplay->attachToParent(m_timelineContainer.get());
+}
+
+void MediaControls::createFullscreenButton()
+{
+ ASSERT(!m_fullscreenButton);
+ m_fullscreenButton = MediaControlFullscreenButtonElement::create(m_mediaElement);
+ m_fullscreenButton->attachToParent(m_panel.get());
+}
+
+void MediaControls::timeUpdateTimerFired(Timer<MediaControls>*)
+{
+ if (m_timeline)
+ m_timeline->update(false);
+ updateTimeDisplay();
+}
+
+void MediaControls::updateTimeDisplay()
+{
+ ASSERT(m_mediaElement->renderer());
+
+ if (!m_currentTimeDisplay || !m_currentTimeDisplay->renderer() || m_currentTimeDisplay->renderer()->style()->display() == NONE || m_mediaElement->renderer()->style()->visibility() != VISIBLE)
+ return;
+
+ float now = m_mediaElement->currentTime();
+ float duration = m_mediaElement->duration();
+
+ // Allow the theme to format the time
+ ExceptionCode ec;
+ m_currentTimeDisplay->setInnerText(m_mediaElement->renderer()->theme()->formatMediaControlsCurrentTime(now, duration), ec);
+ m_currentTimeDisplay->setCurrentValue(now);
+ m_timeRemainingDisplay->setInnerText(m_mediaElement->renderer()->theme()->formatMediaControlsRemainingTime(now, duration), ec);
+ m_timeRemainingDisplay->setCurrentValue(now - duration);
+}
+
+RenderBox* MediaControls::renderBox()
+{
+ return m_controlsShadowRoot ? m_controlsShadowRoot->renderBox() : 0;
+}
+
+void MediaControls::updateControlVisibility()
+{
+ if (!m_panel || !m_panel->renderer())
+ return;
+
+ // Don't fade for audio controls.
+ HTMLMediaElement* media = m_mediaElement;
+ if (!media->hasVideo())
+ return;
+
+ ASSERT(media->renderer());
+
+ // Don't fade if the media element is not visible
+ if (media->renderer()->style()->visibility() != VISIBLE)
+ return;
+
+ bool shouldHideController = !m_mouseOver && !media->canPlay();
+
+ // Do fading manually, css animations don't work with shadow trees
+
+ float animateFrom = m_panel->renderer()->style()->opacity();
+ float animateTo = shouldHideController ? 0.0f : 1.0f;
+
+ if (animateFrom == animateTo)
+ return;
+
+ if (m_opacityAnimationTimer.isActive()) {
+ if (m_opacityAnimationTo == animateTo)
+ return;
+ m_opacityAnimationTimer.stop();
+ }
+
+ if (animateFrom < animateTo)
+ m_opacityAnimationDuration = m_panel->renderer()->theme()->mediaControlsFadeInDuration();
+ else
+ m_opacityAnimationDuration = m_panel->renderer()->theme()->mediaControlsFadeOutDuration();
+
+ m_opacityAnimationFrom = animateFrom;
+ m_opacityAnimationTo = animateTo;
+
+ m_opacityAnimationStartTime = currentTime();
+ m_opacityAnimationTimer.startRepeating(cOpacityAnimationRepeatDelay);
+}
+
+void MediaControls::changeOpacity(HTMLElement* e, float opacity)
+{
+ if (!e || !e->renderer() || !e->renderer()->style())
+ return;
+ RefPtr<RenderStyle> s = RenderStyle::clone(e->renderer()->style());
+ s->setOpacity(opacity);
+ // z-index can't be auto if opacity is used
+ s->setZIndex(0);
+ e->renderer()->setStyle(s.release());
+}
+
+void MediaControls::opacityAnimationTimerFired(Timer<MediaControls>*)
+{
+ double time = currentTime() - m_opacityAnimationStartTime;
+ if (time >= m_opacityAnimationDuration) {
+ time = m_opacityAnimationDuration;
+ m_opacityAnimationTimer.stop();
+ }
+ float opacity = narrowPrecisionToFloat(m_opacityAnimationFrom + (m_opacityAnimationTo - m_opacityAnimationFrom) * time / m_opacityAnimationDuration);
+ changeOpacity(m_panel.get(), opacity);
+}
+
+void MediaControls::updateVolumeSliderContainer(bool visible)
+{
+ if (!m_mediaElement->hasAudio() || !m_volumeSliderContainer || !m_volumeSlider)
+ return;
+
+ if (visible && !m_volumeSliderContainer->isVisible()) {
+ if (!m_muteButton || !m_muteButton->renderer() || !m_muteButton->renderBox())
+ return;
+
+ RefPtr<RenderStyle> s = m_volumeSliderContainer->styleForElement();
+ int height = s->height().isPercent() ? 0 : s->height().value();
+ int width = s->width().isPercent() ? 0 : s->width().value();
+ IntPoint offset = m_mediaElement->document()->page()->theme()->volumeSliderOffsetFromMuteButton(m_muteButton->renderer()->node(), IntSize(width, height));
+ int x = offset.x() + m_muteButton->renderBox()->offsetLeft();
+ int y = offset.y() + m_muteButton->renderBox()->offsetTop();
+
+ m_volumeSliderContainer->setPosition(x, y);
+ m_volumeSliderContainer->setVisible(true);
+ m_volumeSliderContainer->update();
+ m_volumeSlider->update();
+ } else if (!visible && m_volumeSliderContainer->isVisible()) {
+ m_volumeSliderContainer->setVisible(false);
+ m_volumeSliderContainer->updateStyle();
+ }
+}
+
+void MediaControls::forwardEvent(Event* event)
+{
+ ASSERT(m_mediaElement->renderer());
+
+ if (event->isMouseEvent() && m_controlsShadowRoot) {
+ MouseEvent* mouseEvent = static_cast<MouseEvent*>(event);
+ IntPoint point(mouseEvent->absoluteLocation());
+
+ bool defaultHandled = false;
+ if (m_volumeSliderMuteButton && m_volumeSliderMuteButton->hitTest(point)) {
+ m_volumeSliderMuteButton->defaultEventHandler(event);
+ defaultHandled = event->defaultHandled();
+ }
+
+ bool showVolumeSlider = false;
+ if (!defaultHandled && m_muteButton && m_muteButton->hitTest(point)) {
+ m_muteButton->defaultEventHandler(event);
+ if (event->type() != eventNames().mouseoutEvent)
+ showVolumeSlider = true;
+ }
+
+ if (m_volumeSliderContainer && m_volumeSliderContainer->hitTest(point))
+ showVolumeSlider = true;
+
+ if (m_volumeSlider && m_volumeSlider->hitTest(point)) {
+ m_volumeSlider->defaultEventHandler(event);
+ showVolumeSlider = true;
+ }
+
+ updateVolumeSliderContainer(showVolumeSlider);
+
+ if (m_playButton && m_playButton->hitTest(point))
+ m_playButton->defaultEventHandler(event);
+
+ if (m_seekBackButton && m_seekBackButton->hitTest(point))
+ m_seekBackButton->defaultEventHandler(event);
+
+ if (m_seekForwardButton && m_seekForwardButton->hitTest(point))
+ m_seekForwardButton->defaultEventHandler(event);
+
+ if (m_rewindButton && m_rewindButton->hitTest(point))
+ m_rewindButton->defaultEventHandler(event);
+
+ if (m_returnToRealtimeButton && m_returnToRealtimeButton->hitTest(point))
+ m_returnToRealtimeButton->defaultEventHandler(event);
+
+ if (m_toggleClosedCaptionsButton && m_toggleClosedCaptionsButton->hitTest(point))
+ m_toggleClosedCaptionsButton->defaultEventHandler(event);
+
+ if (m_timeline && m_timeline->hitTest(point))
+ m_timeline->defaultEventHandler(event);
+
+ if (m_fullscreenButton && m_fullscreenButton->hitTest(point))
+ m_fullscreenButton->defaultEventHandler(event);
+
+ if (event->type() == eventNames().mouseoverEvent) {
+ m_mouseOver = true;
+ updateControlVisibility();
+ }
+ if (event->type() == eventNames().mouseoutEvent) {
+ // 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
+ Node* mouseOverNode = mouseEvent->relatedTarget() ? mouseEvent->relatedTarget()->toNode() : 0;
+ RenderObject* mouseOverRenderer = mouseOverNode ? mouseOverNode->renderer() : 0;
+ m_mouseOver = mouseOverRenderer && mouseOverRenderer->isDescendantOf(m_mediaElement->renderer());
+ updateControlVisibility();
+ }
+ }
+}
+
+// We want the timeline slider to be at least 100 pixels wide.
+static const int minWidthToDisplayTimeDisplays = 16 + 16 + 45 + 100 + 45 + 16 + 1;
+
+void MediaControls::updateTimeDisplayVisibility()
+{
+ ASSERT(m_mediaElement->renderer());
+
+ if (!m_currentTimeDisplay && !m_timeRemainingDisplay)
+ return;
+
+ int width = m_mediaElement->renderBox()->width();
+ bool shouldShowTimeDisplays = width >= minWidthToDisplayTimeDisplays * m_mediaElement->renderer()->style()->effectiveZoom();
+
+ m_currentTimeDisplay->setVisible(shouldShowTimeDisplays);
+ m_timeRemainingDisplay->setVisible(shouldShowTimeDisplays);
+}
+
+}
+
+#endif