/* * Copyright (C) 2007, 2008, 2009 Apple 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 "RenderMedia.h" #include "EventNames.h" #include "FloatConversion.h" #include "HTMLNames.h" #include "MediaControlElements.h" #include "MouseEvent.h" #include "RenderTheme.h" #include #include using namespace std; namespace WebCore { using namespace HTMLNames; static const double cTimeUpdateRepeatDelay = 0.2; static const double cOpacityAnimationRepeatDelay = 0.05; // FIXME get this from style static const double cOpacityAnimationDurationFadeIn = 0.1; static const double cOpacityAnimationDurationFadeOut = 0.3; RenderMedia::RenderMedia(HTMLMediaElement* video) : RenderReplaced(video) , m_timeUpdateTimer(this, &RenderMedia::timeUpdateTimerFired) , m_opacityAnimationTimer(this, &RenderMedia::opacityAnimationTimerFired) , m_mouseOver(false) , m_opacityAnimationStartTime(0) , m_opacityAnimationDuration(cOpacityAnimationDurationFadeIn) , m_opacityAnimationFrom(0) , m_opacityAnimationTo(1.0f) { } RenderMedia::RenderMedia(HTMLMediaElement* video, const IntSize& intrinsicSize) : RenderReplaced(video, intrinsicSize) , m_timeUpdateTimer(this, &RenderMedia::timeUpdateTimerFired) , m_opacityAnimationTimer(this, &RenderMedia::opacityAnimationTimerFired) , m_mouseOver(false) , m_opacityAnimationStartTime(0) , m_opacityAnimationDuration(cOpacityAnimationDurationFadeIn) , m_opacityAnimationFrom(0) , m_opacityAnimationTo(1.0f) { } RenderMedia::~RenderMedia() { } void RenderMedia::destroy() { 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(); removeChild(m_controlsShadowRoot->renderer()); m_controlsShadowRoot->detach(); m_controlsShadowRoot = 0; } RenderReplaced::destroy(); } HTMLMediaElement* RenderMedia::mediaElement() const { return static_cast(node()); } MediaPlayer* RenderMedia::player() const { return mediaElement()->player(); } void RenderMedia::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle) { RenderReplaced::styleDidChange(diff, oldStyle); if (m_controlsShadowRoot) { 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_volumeSlider) m_volumeSlider->updateStyle(); } } void RenderMedia::layout() { IntSize oldSize = contentBoxRect().size(); RenderReplaced::layout(); RenderBox* controlsRenderer = m_controlsShadowRoot ? m_controlsShadowRoot->renderBox() : 0; if (!controlsRenderer) return; IntSize newSize = contentBoxRect().size(); if (newSize != oldSize || controlsRenderer->needsLayout()) { if (m_currentTimeDisplay && m_timeRemainingDisplay) { bool shouldShowTimeDisplays = shouldShowTimeDisplayControls(); m_currentTimeDisplay->setVisible(shouldShowTimeDisplays); m_timeRemainingDisplay->setVisible(shouldShowTimeDisplays); } controlsRenderer->setLocation(borderLeft() + paddingLeft(), borderTop() + paddingTop()); controlsRenderer->style()->setHeight(Length(newSize.height(), Fixed)); controlsRenderer->style()->setWidth(Length(newSize.width(), Fixed)); controlsRenderer->setNeedsLayout(true, false); controlsRenderer->layout(); setChildNeedsLayout(false); } } void RenderMedia::createControlsShadowRoot() { ASSERT(!m_controlsShadowRoot); m_controlsShadowRoot = new MediaControlShadowRootElement(document(), mediaElement()); addChild(m_controlsShadowRoot->renderer()); } void RenderMedia::createPanel() { ASSERT(!m_panel); m_panel = new MediaControlElement(document(), MEDIA_CONTROLS_PANEL, mediaElement()); m_panel->attachToParent(m_controlsShadowRoot.get()); } void RenderMedia::createMuteButton() { ASSERT(!m_muteButton); m_muteButton = new MediaControlMuteButtonElement(document(), mediaElement()); m_muteButton->attachToParent(m_panel.get()); } void RenderMedia::createPlayButton() { ASSERT(!m_playButton); m_playButton = new MediaControlPlayButtonElement(document(), mediaElement()); m_playButton->attachToParent(m_panel.get()); } void RenderMedia::createSeekBackButton() { ASSERT(!m_seekBackButton); m_seekBackButton = new MediaControlSeekButtonElement(document(), mediaElement(), false); m_seekBackButton->attachToParent(m_panel.get()); } void RenderMedia::createSeekForwardButton() { ASSERT(!m_seekForwardButton); m_seekForwardButton = new MediaControlSeekButtonElement(document(), mediaElement(), true); m_seekForwardButton->attachToParent(m_panel.get()); } void RenderMedia::createRewindButton() { ASSERT(!m_rewindButton); m_rewindButton = new MediaControlRewindButtonElement(document(), mediaElement()); m_rewindButton->attachToParent(m_panel.get()); } void RenderMedia::createReturnToRealtimeButton() { ASSERT(!m_returnToRealtimeButton); m_returnToRealtimeButton = new MediaControlReturnToRealtimeButtonElement(document(), mediaElement()); m_returnToRealtimeButton->attachToParent(m_panel.get()); } void RenderMedia::createToggleClosedCaptionsButton() { ASSERT(!m_toggleClosedCaptionsButton); m_toggleClosedCaptionsButton = new MediaControlToggleClosedCaptionsButtonElement(document(), mediaElement()); m_toggleClosedCaptionsButton->attachToParent(m_panel.get()); } void RenderMedia::createStatusDisplay() { ASSERT(!m_statusDisplay); m_statusDisplay = new MediaControlStatusDisplayElement(document(), mediaElement()); m_statusDisplay->attachToParent(m_panel.get()); } void RenderMedia::createTimelineContainer() { ASSERT(!m_timelineContainer); m_timelineContainer = new MediaControlTimelineContainerElement(document(), mediaElement()); m_timelineContainer->attachToParent(m_panel.get()); } void RenderMedia::createTimeline() { ASSERT(!m_timeline); m_timeline = new MediaControlTimelineElement(document(), mediaElement()); m_timeline->setAttribute(precisionAttr, "float"); m_timeline->attachToParent(m_timelineContainer.get()); } void RenderMedia::createVolumeSliderContainer() { ASSERT(!m_volumeSliderContainer); m_volumeSliderContainer = new MediaControlVolumeSliderContainerElement(document(), mediaElement()); m_volumeSliderContainer->attachToParent(m_panel.get()); } void RenderMedia::createVolumeSlider() { ASSERT(!m_volumeSlider); m_volumeSlider = new MediaControlVolumeSliderElement(document(), mediaElement()); m_volumeSlider->setAttribute(precisionAttr, "float"); m_volumeSlider->setAttribute(maxAttr, "1"); m_volumeSlider->setAttribute(valueAttr, String::number(mediaElement()->volume())); m_volumeSlider->attachToParent(m_volumeSliderContainer.get()); } void RenderMedia::createCurrentTimeDisplay() { ASSERT(!m_currentTimeDisplay); m_currentTimeDisplay = new MediaControlTimeDisplayElement(document(), MEDIA_CONTROLS_CURRENT_TIME_DISPLAY, mediaElement()); m_currentTimeDisplay->attachToParent(m_timelineContainer.get()); } void RenderMedia::createTimeRemainingDisplay() { ASSERT(!m_timeRemainingDisplay); m_timeRemainingDisplay = new MediaControlTimeDisplayElement(document(), MEDIA_CONTROLS_TIME_REMAINING_DISPLAY, mediaElement()); m_timeRemainingDisplay->attachToParent(m_timelineContainer.get()); } void RenderMedia::createFullscreenButton() { ASSERT(!m_fullscreenButton); m_fullscreenButton = new MediaControlFullscreenButtonElement(document(), mediaElement()); m_fullscreenButton->attachToParent(m_panel.get()); } void RenderMedia::updateFromElement() { updateControls(); } void RenderMedia::updateControls() { HTMLMediaElement* media = 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_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(); m_panel->attach(); } } if (media->canPlay()) { if (m_timeUpdateTimer.isActive()) m_timeUpdateTimer.stop(); } else if (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(); updateTimeDisplay(); updateControlVisibility(); } void RenderMedia::timeUpdateTimerFired(Timer*) { if (m_timeline) m_timeline->update(false); updateTimeDisplay(); } void RenderMedia::updateTimeDisplay() { if (!m_currentTimeDisplay || !m_currentTimeDisplay->renderer() || m_currentTimeDisplay->renderer()->style()->display() == NONE || style()->visibility() != VISIBLE) return; float now = mediaElement()->currentTime(); float duration = mediaElement()->duration(); m_currentTimeDisplay->setCurrentValue(now); m_timeRemainingDisplay->setCurrentValue(now - duration); } void RenderMedia::updateControlVisibility() { if (!m_panel || !m_panel->renderer()) return; // Don't fade for audio controls. HTMLMediaElement* media = mediaElement(); if (!media->hasVideo()) return; // Don't fade if the media element is not visible if (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 = cOpacityAnimationDurationFadeIn; else m_opacityAnimationDuration = cOpacityAnimationDurationFadeOut; m_opacityAnimationFrom = animateFrom; m_opacityAnimationTo = animateTo; m_opacityAnimationStartTime = currentTime(); m_opacityAnimationTimer.startRepeating(cOpacityAnimationRepeatDelay); } void RenderMedia::changeOpacity(HTMLElement* e, float opacity) { if (!e || !e->renderer() || !e->renderer()->style()) return; RefPtr 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 RenderMedia::opacityAnimationTimerFired(Timer*) { 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 RenderMedia::updateVolumeSliderContainer(bool visible) { if (!mediaElement()->hasAudio() || !m_volumeSliderContainer || !m_volumeSlider) return; if (visible && !m_volumeSliderContainer->isVisible()) { if (!m_muteButton || !m_muteButton->renderer() || !m_muteButton->renderBox()) return; RefPtr s = m_volumeSliderContainer->styleForElement(); int height = s->height().isPercent() ? 0 : s->height().value(); int x = m_muteButton->renderBox()->offsetLeft(); int y = m_muteButton->renderBox()->offsetTop() - height; FloatPoint absPoint = m_muteButton->renderer()->localToAbsolute(FloatPoint(x, y), true, true); if (absPoint.y() < 0) y = m_muteButton->renderBox()->offsetTop() + m_muteButton->renderBox()->height(); m_volumeSliderContainer->setVisible(true); m_volumeSliderContainer->setPosition(x, y); m_volumeSliderContainer->update(); m_volumeSlider->update(); } else if (!visible && m_volumeSliderContainer->isVisible()) { m_volumeSliderContainer->setVisible(false); m_volumeSliderContainer->updateStyle(); } } void RenderMedia::forwardEvent(Event* event) { if (event->isMouseEvent() && m_controlsShadowRoot) { MouseEvent* mouseEvent = static_cast(event); IntPoint point(mouseEvent->absoluteLocation()); bool showVolumeSlider = false; if (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(this); updateControlVisibility(); } } } int RenderMedia::lowestPosition(bool includeOverflowInterior, bool includeSelf) const { int bottom = RenderReplaced::lowestPosition(includeOverflowInterior, includeSelf); if (!m_controlsShadowRoot || !m_controlsShadowRoot->renderer()) return bottom; return max(bottom, m_controlsShadowRoot->renderBox()->y() + m_controlsShadowRoot->renderBox()->lowestPosition(includeOverflowInterior, includeSelf)); } int RenderMedia::rightmostPosition(bool includeOverflowInterior, bool includeSelf) const { int right = RenderReplaced::rightmostPosition(includeOverflowInterior, includeSelf); if (!m_controlsShadowRoot || !m_controlsShadowRoot->renderer()) return right; return max(right, m_controlsShadowRoot->renderBox()->x() + m_controlsShadowRoot->renderBox()->rightmostPosition(includeOverflowInterior, includeSelf)); } int RenderMedia::leftmostPosition(bool includeOverflowInterior, bool includeSelf) const { int left = RenderReplaced::leftmostPosition(includeOverflowInterior, includeSelf); if (!m_controlsShadowRoot || !m_controlsShadowRoot->renderer()) return left; return min(left, m_controlsShadowRoot->renderBox()->x() + m_controlsShadowRoot->renderBox()->leftmostPosition(includeOverflowInterior, includeSelf)); } // We want the timeline slider to be at least 100 pixels wide. static const int minWidthToDisplayTimeDisplays = 16 + 16 + 45 + 100 + 45 + 16 + 1; bool RenderMedia::shouldShowTimeDisplayControls() const { if (!m_currentTimeDisplay && !m_timeRemainingDisplay) return false; int width = mediaElement()->renderBox()->width(); return width >= minWidthToDisplayTimeDisplays * style()->effectiveZoom(); } } // namespace WebCore #endif