#include "config.h"
#include "core/html/shadow/MediaControls.h"
-#include "bindings/v8/ExceptionStatePlaceholder.h"
-#if OS(ANDROID)
-#include "core/html/shadow/MediaControlsAndroid.h"
-#endif
-
-namespace WebCore {
-
-static const double timeWithoutMouseMovementBeforeHidingFullscreenControls = 3;
-
-MediaControls::MediaControls(Document& document)
- : HTMLDivElement(document)
- , m_mediaController(0)
- , m_panel(0)
- , m_textDisplayContainer(0)
- , m_playButton(0)
- , m_currentTimeDisplay(0)
- , m_timeline(0)
- , m_panelMuteButton(0)
- , m_volumeSlider(0)
- , m_toggleClosedCaptionsButton(0)
- , m_fullScreenButton(0)
- , m_durationDisplay(0)
- , m_enclosure(0)
- , m_hideFullscreenControlsTimer(this, &MediaControls::hideFullscreenControlsTimerFired)
- , m_isFullscreen(false)
+#include "bindings/core/v8/ExceptionStatePlaceholder.h"
+#include "core/events/MouseEvent.h"
+#include "core/frame/Settings.h"
+#include "core/html/HTMLMediaElement.h"
+#include "core/html/MediaController.h"
+#include "core/rendering/RenderTheme.h"
+
+namespace blink {
+
+// If you change this value, then also update the corresponding value in
+// LayoutTests/media/media-controls.js.
+static const double timeWithoutMouseMovementBeforeHidingMediaControls = 3;
+
+static bool fullscreenIsSupported(const Document& document)
+{
+ return !document.settings() || document.settings()->fullscreenSupported();
+}
+
+static bool deviceSupportsMouse(const Document& document)
+{
+ return !document.settings() || document.settings()->deviceSupportsMouse();
+}
+
+MediaControls::MediaControls(HTMLMediaElement& mediaElement)
+ : HTMLDivElement(mediaElement.document())
+ , m_mediaElement(&mediaElement)
+ , m_panel(nullptr)
+ , m_textDisplayContainer(nullptr)
+ , m_overlayPlayButton(nullptr)
+ , m_overlayEnclosure(nullptr)
+ , m_playButton(nullptr)
+ , m_currentTimeDisplay(nullptr)
+ , m_timeline(nullptr)
+ , m_muteButton(nullptr)
+ , m_volumeSlider(nullptr)
+ , m_toggleClosedCaptionsButton(nullptr)
+ , m_fullScreenButton(nullptr)
+ , m_durationDisplay(nullptr)
+ , m_enclosure(nullptr)
+ , m_hideMediaControlsTimer(this, &MediaControls::hideMediaControlsTimerFired)
, m_isMouseOverControls(false)
+ , m_isPausedForScrubbing(false)
{
}
-PassRefPtr<MediaControls> MediaControls::create(Document& document)
+PassRefPtrWillBeRawPtr<MediaControls> MediaControls::create(HTMLMediaElement& mediaElement)
{
- if (!document.page())
- return 0;
+ RefPtrWillBeRawPtr<MediaControls> controls = adoptRefWillBeNoop(new MediaControls(mediaElement));
- RefPtr<MediaControls> controls;
-#if OS(ANDROID)
- controls = adoptRef(new MediaControlsAndroid(document));
-#else
- controls = adoptRef(new MediaControls(document));
-#endif
-
- if (controls->initializeControls(document))
+ if (controls->initializeControls())
return controls.release();
- return 0;
+ return nullptr;
}
-bool MediaControls::initializeControls(Document& document)
+bool MediaControls::initializeControls()
{
- // Create an enclosing element for the panel so we can visually offset the controls correctly.
- RefPtr<MediaControlPanelEnclosureElement> enclosure = MediaControlPanelEnclosureElement::create(document);
+ TrackExceptionState exceptionState;
- RefPtr<MediaControlPanelElement> panel = MediaControlPanelElement::create(document);
+ if (document().settings() && document().settings()->mediaControlsOverlayPlayButtonEnabled()) {
+ RefPtrWillBeRawPtr<MediaControlOverlayEnclosureElement> overlayEnclosure = MediaControlOverlayEnclosureElement::create(*this);
+ RefPtrWillBeRawPtr<MediaControlOverlayPlayButtonElement> overlayPlayButton = MediaControlOverlayPlayButtonElement::create(*this);
+ m_overlayPlayButton = overlayPlayButton.get();
+ overlayEnclosure->appendChild(overlayPlayButton.release(), exceptionState);
+ if (exceptionState.hadException())
+ return false;
+
+ m_overlayEnclosure = overlayEnclosure.get();
+ appendChild(overlayEnclosure.release(), exceptionState);
+ if (exceptionState.hadException())
+ return false;
+ }
- TrackExceptionState exceptionState;
+ // Create an enclosing element for the panel so we can visually offset the controls correctly.
+ RefPtrWillBeRawPtr<MediaControlPanelEnclosureElement> enclosure = MediaControlPanelEnclosureElement::create(*this);
+
+ RefPtrWillBeRawPtr<MediaControlPanelElement> panel = MediaControlPanelElement::create(*this);
- RefPtr<MediaControlPlayButtonElement> playButton = MediaControlPlayButtonElement::create(document);
+ RefPtrWillBeRawPtr<MediaControlPlayButtonElement> playButton = MediaControlPlayButtonElement::create(*this);
m_playButton = playButton.get();
panel->appendChild(playButton.release(), exceptionState);
if (exceptionState.hadException())
return false;
- RefPtr<MediaControlTimelineElement> timeline = MediaControlTimelineElement::create(document, this);
+ RefPtrWillBeRawPtr<MediaControlTimelineElement> timeline = MediaControlTimelineElement::create(*this);
m_timeline = timeline.get();
panel->appendChild(timeline.release(), exceptionState);
if (exceptionState.hadException())
return false;
- RefPtr<MediaControlCurrentTimeDisplayElement> currentTimeDisplay = MediaControlCurrentTimeDisplayElement::create(document);
+ RefPtrWillBeRawPtr<MediaControlCurrentTimeDisplayElement> currentTimeDisplay = MediaControlCurrentTimeDisplayElement::create(*this);
m_currentTimeDisplay = currentTimeDisplay.get();
m_currentTimeDisplay->hide();
panel->appendChild(currentTimeDisplay.release(), exceptionState);
if (exceptionState.hadException())
return false;
- RefPtr<MediaControlTimeRemainingDisplayElement> durationDisplay = MediaControlTimeRemainingDisplayElement::create(document);
+ RefPtrWillBeRawPtr<MediaControlTimeRemainingDisplayElement> durationDisplay = MediaControlTimeRemainingDisplayElement::create(*this);
m_durationDisplay = durationDisplay.get();
panel->appendChild(durationDisplay.release(), exceptionState);
if (exceptionState.hadException())
return false;
- RefPtr<MediaControlPanelMuteButtonElement> panelMuteButton = MediaControlPanelMuteButtonElement::create(document, this);
- m_panelMuteButton = panelMuteButton.get();
- panel->appendChild(panelMuteButton.release(), exceptionState);
+ RefPtrWillBeRawPtr<MediaControlMuteButtonElement> muteButton = MediaControlMuteButtonElement::create(*this);
+ m_muteButton = muteButton.get();
+ panel->appendChild(muteButton.release(), exceptionState);
if (exceptionState.hadException())
return false;
- RefPtr<MediaControlPanelVolumeSliderElement> slider = MediaControlPanelVolumeSliderElement::create(document);
+ RefPtrWillBeRawPtr<MediaControlVolumeSliderElement> slider = MediaControlVolumeSliderElement::create(*this);
m_volumeSlider = slider.get();
- m_volumeSlider->setClearMutedOnUserInteraction(true);
panel->appendChild(slider.release(), exceptionState);
if (exceptionState.hadException())
return false;
- RefPtr<MediaControlToggleClosedCaptionsButtonElement> toggleClosedCaptionsButton = MediaControlToggleClosedCaptionsButtonElement::create(document, this);
+ RefPtrWillBeRawPtr<MediaControlToggleClosedCaptionsButtonElement> toggleClosedCaptionsButton = MediaControlToggleClosedCaptionsButtonElement::create(*this);
m_toggleClosedCaptionsButton = toggleClosedCaptionsButton.get();
panel->appendChild(toggleClosedCaptionsButton.release(), exceptionState);
if (exceptionState.hadException())
return false;
- RefPtr<MediaControlFullscreenButtonElement> fullscreenButton = MediaControlFullscreenButtonElement::create(document);
+ RefPtrWillBeRawPtr<MediaControlFullscreenButtonElement> fullscreenButton = MediaControlFullscreenButtonElement::create(*this);
m_fullScreenButton = fullscreenButton.get();
panel->appendChild(fullscreenButton.release(), exceptionState);
if (exceptionState.hadException())
return true;
}
-void MediaControls::setMediaController(MediaControllerInterface* controller)
-{
- if (m_mediaController == controller)
- return;
- m_mediaController = controller;
-
- if (m_panel)
- m_panel->setMediaController(controller);
- if (m_textDisplayContainer)
- m_textDisplayContainer->setMediaController(controller);
- if (m_playButton)
- m_playButton->setMediaController(controller);
- if (m_currentTimeDisplay)
- m_currentTimeDisplay->setMediaController(controller);
- if (m_timeline)
- m_timeline->setMediaController(controller);
- if (m_panelMuteButton)
- m_panelMuteButton->setMediaController(controller);
- if (m_volumeSlider)
- m_volumeSlider->setMediaController(controller);
- if (m_toggleClosedCaptionsButton)
- m_toggleClosedCaptionsButton->setMediaController(controller);
- if (m_fullScreenButton)
- m_fullScreenButton->setMediaController(controller);
- if (m_durationDisplay)
- m_durationDisplay->setMediaController(controller);
- if (m_enclosure)
- m_enclosure->setMediaController(controller);
-}
-
void MediaControls::reset()
{
- Page* page = document().page();
- if (!page)
- return;
-
- double duration = m_mediaController->duration();
+ double duration = mediaElement().duration();
m_durationDisplay->setInnerText(RenderTheme::theme().formatMediaControlsTime(duration), ASSERT_NO_EXCEPTION);
m_durationDisplay->setCurrentValue(duration);
- m_playButton->updateDisplayType();
+ updatePlayState();
updateCurrentTimeDisplay();
- m_timeline->setDuration(m_mediaController->duration());
- m_timeline->setPosition(m_mediaController->currentTime());
+ m_timeline->setDuration(duration);
+ m_timeline->setPosition(mediaElement().currentTime());
- m_panelMuteButton->show();
-
- if (m_volumeSlider) {
- if (!m_mediaController->hasAudio())
- m_volumeSlider->hide();
- else {
- m_volumeSlider->show();
- m_volumeSlider->setVolume(m_mediaController->volume());
- }
- }
+ if (!mediaElement().hasAudio())
+ m_volumeSlider->hide();
+ else
+ m_volumeSlider->show();
+ updateVolume();
refreshClosedCaptionsButtonVisibility();
- if (m_fullScreenButton) {
- if (m_mediaController->hasVideo())
- m_fullScreenButton->show();
- else
- m_fullScreenButton->hide();
- }
+ if (mediaElement().hasVideo() && fullscreenIsSupported(document()))
+ m_fullScreenButton->show();
+ else
+ m_fullScreenButton->hide();
makeOpaque();
}
makeOpaque();
m_panel->setIsDisplayed(true);
m_panel->show();
+ if (m_overlayPlayButton)
+ m_overlayPlayButton->updateDisplayType();
+}
+
+void MediaControls::mediaElementFocused()
+{
+ show();
+ stopHideMediaControlsTimer();
}
void MediaControls::hide()
{
m_panel->setIsDisplayed(false);
m_panel->hide();
+ if (m_overlayPlayButton)
+ m_overlayPlayButton->hide();
}
void MediaControls::makeOpaque()
m_panel->makeTransparent();
}
-bool MediaControls::shouldHideControls()
-{
- return !m_panel->hovered();
-}
-
-void MediaControls::bufferingProgressed()
+bool MediaControls::shouldHideMediaControls(unsigned behaviorFlags) const
{
- // We only need to update buffering progress when paused, during normal
- // playback playbackProgressed() will take care of it.
- if (m_mediaController->paused())
- m_timeline->setPosition(m_mediaController->currentTime());
+ // Never hide for a media element without visual representation.
+ if (!mediaElement().hasVideo())
+ return false;
+ // Don't hide if the mouse is over the controls.
+ const bool ignoreControlsHover = behaviorFlags & IgnoreControlsHover;
+ if (!ignoreControlsHover && m_panel->hovered())
+ return false;
+ // Don't hide if the mouse is over the video area.
+ const bool ignoreVideoHover = behaviorFlags & IgnoreVideoHover;
+ if (!ignoreVideoHover && m_isMouseOverControls)
+ return false;
+ // Don't hide if focus is on the HTMLMediaElement or within the
+ // controls/shadow tree. (Perform the checks separately to avoid going
+ // through all the potential ancestor hosts for the focused element.)
+ const bool ignoreFocus = behaviorFlags & IgnoreFocus;
+ if (!ignoreFocus && (mediaElement().focused() || contains(document().focusedElement())))
+ return false;
+ return true;
}
void MediaControls::playbackStarted()
m_currentTimeDisplay->show();
m_durationDisplay->hide();
- m_playButton->updateDisplayType();
- m_timeline->setPosition(m_mediaController->currentTime());
+ updatePlayState();
+ m_timeline->setPosition(mediaElement().currentTime());
updateCurrentTimeDisplay();
- if (m_isFullscreen)
- startHideFullscreenControlsTimer();
+ startHideMediaControlsTimer();
}
void MediaControls::playbackProgressed()
{
- m_timeline->setPosition(m_mediaController->currentTime());
+ m_timeline->setPosition(mediaElement().currentTime());
updateCurrentTimeDisplay();
- if (!m_isMouseOverControls && m_mediaController->hasVideo())
+ if (shouldHideMediaControls())
makeTransparent();
}
void MediaControls::playbackStopped()
{
- m_playButton->updateDisplayType();
- m_timeline->setPosition(m_mediaController->currentTime());
+ updatePlayState();
+ m_timeline->setPosition(mediaElement().currentTime());
updateCurrentTimeDisplay();
makeOpaque();
- stopHideFullscreenControlsTimer();
+ stopHideMediaControlsTimer();
}
-void MediaControls::updateCurrentTimeDisplay()
+void MediaControls::updatePlayState()
{
- double now = m_mediaController->currentTime();
- double duration = m_mediaController->duration();
-
- Page* page = document().page();
- if (!page)
+ if (m_isPausedForScrubbing)
return;
+ if (m_overlayPlayButton)
+ m_overlayPlayButton->updateDisplayType();
+ m_playButton->updateDisplayType();
+}
+
+void MediaControls::beginScrubbing()
+{
+ if (!mediaElement().togglePlayStateWillPlay()) {
+ m_isPausedForScrubbing = true;
+ mediaElement().togglePlayState();
+ }
+}
+
+void MediaControls::endScrubbing()
+{
+ if (m_isPausedForScrubbing) {
+ m_isPausedForScrubbing = false;
+ if (mediaElement().togglePlayStateWillPlay())
+ mediaElement().togglePlayState();
+ }
+}
+
+void MediaControls::updateCurrentTimeDisplay()
+{
+ double now = mediaElement().currentTime();
+ double duration = mediaElement().duration();
+
// After seek, hide duration display and show current time.
if (now > 0) {
m_currentTimeDisplay->show();
m_currentTimeDisplay->setCurrentValue(now);
}
-void MediaControls::showVolumeSlider()
-{
- if (!m_mediaController->hasAudio())
- return;
-
- m_volumeSlider->show();
-}
-
-void MediaControls::changedMute()
+void MediaControls::updateVolume()
{
- m_panelMuteButton->changedMute();
+ m_muteButton->updateDisplayType();
+ if (m_muteButton->renderer())
+ m_muteButton->renderer()->paintInvalidationForWholeRenderer();
- if (m_mediaController->muted())
+ if (mediaElement().muted())
m_volumeSlider->setVolume(0);
else
- m_volumeSlider->setVolume(m_mediaController->volume());
-}
-
-void MediaControls::changedVolume()
-{
- if (m_volumeSlider)
- m_volumeSlider->setVolume(m_mediaController->volume());
- if (m_panelMuteButton && m_panelMuteButton->renderer())
- m_panelMuteButton->renderer()->repaint();
+ m_volumeSlider->setVolume(mediaElement().volume());
}
void MediaControls::changedClosedCaptionsVisibility()
{
- if (m_toggleClosedCaptionsButton)
- m_toggleClosedCaptionsButton->updateDisplayType();
+ m_toggleClosedCaptionsButton->updateDisplayType();
}
void MediaControls::refreshClosedCaptionsButtonVisibility()
{
- if (!m_toggleClosedCaptionsButton)
- return;
-
- if (m_mediaController->hasClosedCaptions())
+ if (mediaElement().hasClosedCaptions())
m_toggleClosedCaptionsButton->show();
else
m_toggleClosedCaptionsButton->hide();
void MediaControls::enteredFullscreen()
{
- m_isFullscreen = true;
m_fullScreenButton->setIsFullscreen(true);
- startHideFullscreenControlsTimer();
+ stopHideMediaControlsTimer();
+ startHideMediaControlsTimer();
}
void MediaControls::exitedFullscreen()
{
- m_isFullscreen = false;
m_fullScreenButton->setIsFullscreen(false);
- stopHideFullscreenControlsTimer();
+ stopHideMediaControlsTimer();
+ startHideMediaControlsTimer();
}
void MediaControls::defaultEventHandler(Event* event)
if (event->type() == EventTypeNames::mouseover) {
if (!containsRelatedTarget(event)) {
m_isMouseOverControls = true;
- if (!m_mediaController->canPlay()) {
+ if (!mediaElement().togglePlayStateWillPlay()) {
makeOpaque();
- if (shouldHideControls())
- startHideFullscreenControlsTimer();
+ if (shouldHideMediaControls())
+ startHideMediaControlsTimer();
}
}
return;
if (event->type() == EventTypeNames::mouseout) {
if (!containsRelatedTarget(event)) {
m_isMouseOverControls = false;
- stopHideFullscreenControlsTimer();
+ stopHideMediaControlsTimer();
}
return;
}
if (event->type() == EventTypeNames::mousemove) {
- if (m_isFullscreen) {
- // When we get a mouse move in fullscreen mode, show the media controls, and start a timer
- // that will hide the media controls after a 3 seconds without a mouse move.
- makeOpaque();
- if (shouldHideControls())
- startHideFullscreenControlsTimer();
- }
+ // When we get a mouse move, show the media controls, and start a timer
+ // that will hide the media controls after a 3 seconds without a mouse move.
+ makeOpaque();
+ if (shouldHideMediaControls(IgnoreVideoHover))
+ startHideMediaControlsTimer();
return;
}
}
-void MediaControls::hideFullscreenControlsTimerFired(Timer<MediaControls>*)
+void MediaControls::hideMediaControlsTimerFired(Timer<MediaControls>*)
{
- if (m_mediaController->paused())
- return;
-
- if (!m_isFullscreen)
+ if (mediaElement().togglePlayStateWillPlay())
return;
- if (!shouldHideControls())
+ unsigned behaviorFlags = IgnoreFocus | IgnoreVideoHover;
+ // FIXME: improve this check, see http://www.crbug.com/401177.
+ if (!deviceSupportsMouse(document())) {
+ behaviorFlags |= IgnoreControlsHover;
+ }
+ if (!shouldHideMediaControls(behaviorFlags))
return;
makeTransparent();
}
-void MediaControls::startHideFullscreenControlsTimer()
+void MediaControls::startHideMediaControlsTimer()
{
- if (!m_isFullscreen)
- return;
-
- Page* page = document().page();
- if (!page)
- return;
-
- m_hideFullscreenControlsTimer.startOneShot(timeWithoutMouseMovementBeforeHidingFullscreenControls);
+ m_hideMediaControlsTimer.startOneShot(timeWithoutMouseMovementBeforeHidingMediaControls, FROM_HERE);
}
-void MediaControls::stopHideFullscreenControlsTimer()
+void MediaControls::stopHideMediaControlsTimer()
{
- m_hideFullscreenControlsTimer.stop();
+ m_hideMediaControlsTimer.stop();
}
const AtomicString& MediaControls::shadowPseudoId() const
if (m_textDisplayContainer)
return;
- RefPtr<MediaControlTextTrackContainerElement> textDisplayContainer = MediaControlTextTrackContainerElement::create(document());
+ RefPtrWillBeRawPtr<MediaControlTextTrackContainerElement> textDisplayContainer = MediaControlTextTrackContainerElement::create(*this);
m_textDisplayContainer = textDisplayContainer.get();
- if (m_mediaController)
- m_textDisplayContainer->setMediaController(m_mediaController);
-
- insertTextTrackContainer(textDisplayContainer.release());
+ // Insert it before (behind) all other control elements.
+ if (m_overlayEnclosure && m_overlayPlayButton)
+ m_overlayEnclosure->insertBefore(textDisplayContainer.release(), m_overlayPlayButton);
+ else
+ insertBefore(textDisplayContainer.release(), m_enclosure);
}
void MediaControls::showTextTrackDisplay()
m_textDisplayContainer->updateDisplay();
}
-void MediaControls::insertTextTrackContainer(PassRefPtr<MediaControlTextTrackContainerElement> textTrackContainer)
-{
- // Insert it before the first controller element so it always displays behind the controls.
- insertBefore(textTrackContainer, m_enclosure);
+void MediaControls::trace(Visitor* visitor)
+{
+ visitor->trace(m_mediaElement);
+ visitor->trace(m_panel);
+ visitor->trace(m_textDisplayContainer);
+ visitor->trace(m_overlayPlayButton);
+ visitor->trace(m_overlayEnclosure);
+ visitor->trace(m_playButton);
+ visitor->trace(m_currentTimeDisplay);
+ visitor->trace(m_timeline);
+ visitor->trace(m_muteButton);
+ visitor->trace(m_volumeSlider);
+ visitor->trace(m_toggleClosedCaptionsButton);
+ visitor->trace(m_fullScreenButton);
+ visitor->trace(m_durationDisplay);
+ visitor->trace(m_enclosure);
+ HTMLDivElement::trace(visitor);
}
}