blob: 5c6780b58c0879ead0b2c7f1b7cb4b62ca04ca7c [file] [log] [blame]
/*
* Copyright (C) 2011, 2012 Apple Inc. All rights reserved.
* Copyright (C) 2011, 2012 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"
#include "core/html/shadow/MediaControls.h"
#include "bindings/core/v8/ExceptionStatePlaceholder.h"
#include "core/dom/ClientRect.h"
#include "core/dom/Fullscreen.h"
#include "core/events/MouseEvent.h"
#include "core/frame/Settings.h"
#include "core/html/HTMLMediaElement.h"
#include "core/html/MediaController.h"
#include "core/html/track/TextTrackContainer.h"
#include "core/layout/LayoutTheme.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 shouldShowFullscreenButton(const HTMLMediaElement& mediaElement)
{
// Unconditionally allow the user to exit fullscreen if we are in it
// now. Especially on android, when we might not yet know if
// fullscreen is supported, we sometimes guess incorrectly and show
// the button earlier, and we don't want to remove it here if the
// user chose to enter fullscreen. crbug.com/500732 .
if (mediaElement.isFullscreen())
return true;
if (!mediaElement.hasVideo())
return false;
if (!Fullscreen::fullscreenEnabled(mediaElement.document()))
return false;
return true;
}
static bool preferHiddenVolumeControls(const Document& document)
{
return !document.settings() || document.settings()->preferHiddenVolumeControls();
}
class MediaControls::BatchedControlUpdate {
WTF_MAKE_NONCOPYABLE(BatchedControlUpdate);
STACK_ALLOCATED();
public:
explicit BatchedControlUpdate(MediaControls* controls)
: m_controls(controls)
{
ASSERT(isMainThread());
ASSERT(s_batchDepth >= 0);
++s_batchDepth;
}
~BatchedControlUpdate()
{
ASSERT(isMainThread());
ASSERT(s_batchDepth > 0);
if (!(--s_batchDepth))
m_controls->computeWhichControlsFit();
}
private:
RawPtrWillBeMember<MediaControls> m_controls;
static int s_batchDepth;
};
// Count of number open batches for controls visibility.
int MediaControls::BatchedControlUpdate::s_batchDepth = 0;
MediaControls::MediaControls(HTMLMediaElement& mediaElement)
: HTMLDivElement(mediaElement.document())
, m_mediaElement(&mediaElement)
, m_overlayEnclosure(nullptr)
, m_overlayPlayButton(nullptr)
, m_overlayCastButton(nullptr)
, m_enclosure(nullptr)
, m_panel(nullptr)
, m_playButton(nullptr)
, m_timeline(nullptr)
, m_currentTimeDisplay(nullptr)
, m_durationDisplay(nullptr)
, m_muteButton(nullptr)
, m_volumeSlider(nullptr)
, m_toggleClosedCaptionsButton(nullptr)
, m_castButton(nullptr)
, m_fullScreenButton(nullptr)
, m_hideMediaControlsTimer(this, &MediaControls::hideMediaControlsTimerFired)
, m_hideTimerBehaviorFlags(IgnoreNone)
, m_isMouseOverControls(false)
, m_isPausedForScrubbing(false)
, m_panelWidthChangedTimer(this, &MediaControls::panelWidthChangedTimerFired)
, m_panelWidth(0)
, m_allowHiddenVolumeControls(RuntimeEnabledFeatures::newMediaPlaybackUiEnabled())
{
}
PassRefPtrWillBeRawPtr<MediaControls> MediaControls::create(HTMLMediaElement& mediaElement)
{
RefPtrWillBeRawPtr<MediaControls> controls = adoptRefWillBeNoop(new MediaControls(mediaElement));
controls->setShadowPseudoId(AtomicString("-webkit-media-controls", AtomicString::ConstructFromLiteral));
controls->initializeControls();
return controls.release();
}
// The media controls DOM structure looks like:
//
// MediaControls (-webkit-media-controls)
// +-MediaControlOverlayEnclosureElement (-webkit-media-controls-overlay-enclosure)
// | +-MediaControlOverlayPlayButtonElement (-webkit-media-controls-overlay-play-button)
// | | {if mediaControlsOverlayPlayButtonEnabled}
// | \-MediaControlCastButtonElement (-internal-media-controls-overlay-cast-button)
// \-MediaControlPanelEnclosureElement (-webkit-media-controls-enclosure)
// \-MediaControlPanelElement (-webkit-media-controls-panel)
// +-MediaControlPlayButtonElement (-webkit-media-controls-play-button)
// | {if !RTE::newMediaPlaybackUi()}
// +-MediaControlTimelineElement (-webkit-media-controls-timeline)
// +-MediaControlCurrentTimeDisplayElement (-webkit-media-controls-current-time-display)
// +-MediaControlTimeRemainingDisplayElement (-webkit-media-controls-time-remaining-display)
// | {if RTE::newMediaPlaybackUi()}
// +-MediaControlTimelineElement (-webkit-media-controls-timeline)
// +-MediaControlMuteButtonElement (-webkit-media-controls-mute-button)
// +-MediaControlVolumeSliderElement (-webkit-media-controls-volume-slider)
// +-MediaControlToggleClosedCaptionsButtonElement (-webkit-media-controls-toggle-closed-captions-button)
// +-MediaControlCastButtonElement (-internal-media-controls-cast-button)
// \-MediaControlFullscreenButtonElement (-webkit-media-controls-fullscreen-button)
void MediaControls::initializeControls()
{
const bool useNewUi = RuntimeEnabledFeatures::newMediaPlaybackUiEnabled();
RefPtrWillBeRawPtr<MediaControlOverlayEnclosureElement> overlayEnclosure = MediaControlOverlayEnclosureElement::create(*this);
if (document().settings() && document().settings()->mediaControlsOverlayPlayButtonEnabled()) {
RefPtrWillBeRawPtr<MediaControlOverlayPlayButtonElement> overlayPlayButton = MediaControlOverlayPlayButtonElement::create(*this);
m_overlayPlayButton = overlayPlayButton.get();
overlayEnclosure->appendChild(overlayPlayButton.release());
}
RefPtrWillBeRawPtr<MediaControlCastButtonElement> overlayCastButton = MediaControlCastButtonElement::create(*this, true);
m_overlayCastButton = overlayCastButton.get();
overlayEnclosure->appendChild(overlayCastButton.release());
m_overlayEnclosure = overlayEnclosure.get();
appendChild(overlayEnclosure.release());
// 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);
RefPtrWillBeRawPtr<MediaControlPlayButtonElement> playButton = MediaControlPlayButtonElement::create(*this);
m_playButton = playButton.get();
panel->appendChild(playButton.release());
RefPtrWillBeRawPtr<MediaControlTimelineElement> timeline = MediaControlTimelineElement::create(*this);
m_timeline = timeline.get();
// In old UX, timeline is before the time / duration text.
if (!useNewUi)
panel->appendChild(timeline.release());
// else we will attach it later.
RefPtrWillBeRawPtr<MediaControlCurrentTimeDisplayElement> currentTimeDisplay = MediaControlCurrentTimeDisplayElement::create(*this);
m_currentTimeDisplay = currentTimeDisplay.get();
m_currentTimeDisplay->setIsWanted(useNewUi);
panel->appendChild(currentTimeDisplay.release());
RefPtrWillBeRawPtr<MediaControlTimeRemainingDisplayElement> durationDisplay = MediaControlTimeRemainingDisplayElement::create(*this);
m_durationDisplay = durationDisplay.get();
panel->appendChild(durationDisplay.release());
// Timeline is after the time / duration text if newMediaPlaybackUiEnabled.
if (useNewUi)
panel->appendChild(timeline.release());
RefPtrWillBeRawPtr<MediaControlMuteButtonElement> muteButton = MediaControlMuteButtonElement::create(*this);
m_muteButton = muteButton.get();
panel->appendChild(muteButton.release());
RefPtrWillBeRawPtr<MediaControlVolumeSliderElement> slider = MediaControlVolumeSliderElement::create(*this);
m_volumeSlider = slider.get();
panel->appendChild(slider.release());
if (m_allowHiddenVolumeControls && preferHiddenVolumeControls(document()))
m_volumeSlider->setIsWanted(false);
RefPtrWillBeRawPtr<MediaControlToggleClosedCaptionsButtonElement> toggleClosedCaptionsButton = MediaControlToggleClosedCaptionsButtonElement::create(*this);
m_toggleClosedCaptionsButton = toggleClosedCaptionsButton.get();
panel->appendChild(toggleClosedCaptionsButton.release());
RefPtrWillBeRawPtr<MediaControlCastButtonElement> castButton = MediaControlCastButtonElement::create(*this, false);
m_castButton = castButton.get();
panel->appendChild(castButton.release());
RefPtrWillBeRawPtr<MediaControlFullscreenButtonElement> fullscreenButton = MediaControlFullscreenButtonElement::create(*this);
m_fullScreenButton = fullscreenButton.get();
panel->appendChild(fullscreenButton.release());
m_panel = panel.get();
enclosure->appendChild(panel.release());
m_enclosure = enclosure.get();
appendChild(enclosure.release());
}
void MediaControls::reset()
{
const bool useNewUi = RuntimeEnabledFeatures::newMediaPlaybackUiEnabled();
BatchedControlUpdate batch(this);
m_allowHiddenVolumeControls = useNewUi;
const double duration = mediaElement().duration();
m_durationDisplay->setInnerText(LayoutTheme::theme().formatMediaControlsTime(duration), ASSERT_NO_EXCEPTION);
m_durationDisplay->setCurrentValue(duration);
if (useNewUi) {
// Show everything that we might hide.
// If we don't have a duration, then mark it to be hidden. For the
// old UI case, want / don't want is the same as show / hide since
// it is never marked as not fitting.
m_durationDisplay->setIsWanted(std::isfinite(duration));
m_currentTimeDisplay->setIsWanted(true);
m_timeline->setIsWanted(true);
}
updatePlayState();
updateCurrentTimeDisplay();
m_timeline->setDuration(duration);
m_timeline->setPosition(mediaElement().currentTime());
updateVolume();
refreshClosedCaptionsButtonVisibility();
m_fullScreenButton->setIsWanted(shouldShowFullscreenButton(mediaElement()));
refreshCastButtonVisibilityWithoutUpdate();
// Set the panel width here, and force a layout, before the controls update.
// This would be harmless for the !useNewUi case too, but it causes
// compositing/geometry/video-fixed-scrolling.html to fail with two extra
// 0 height nodes in the render tree.
if (useNewUi)
m_panelWidth = m_panel->clientWidth();
}
LayoutObject* MediaControls::layoutObjectForTextTrackLayout()
{
return m_panel->layoutObject();
}
void MediaControls::show()
{
makeOpaque();
m_panel->setIsWanted(true);
m_panel->setIsDisplayed(true);
if (m_overlayPlayButton)
m_overlayPlayButton->updateDisplayType();
}
void MediaControls::mediaElementFocused()
{
if (mediaElement().shouldShowControls()) {
show();
resetHideMediaControlsTimer();
}
}
void MediaControls::hide()
{
m_panel->setIsWanted(false);
m_panel->setIsDisplayed(false);
if (m_overlayPlayButton)
m_overlayPlayButton->setIsWanted(false);
}
void MediaControls::makeOpaque()
{
m_panel->makeOpaque();
}
void MediaControls::makeTransparent()
{
m_panel->makeTransparent();
m_overlayCastButton->setIsWanted(false);
}
bool MediaControls::shouldHideMediaControls(unsigned behaviorFlags) const
{
// Never hide for a media element without visual representation.
if (!mediaElement().hasVideo() || mediaElement().isPlayingRemotely())
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()
{
BatchedControlUpdate batch(this);
if (!RuntimeEnabledFeatures::newMediaPlaybackUiEnabled()) {
m_currentTimeDisplay->setIsWanted(true);
m_durationDisplay->setIsWanted(false);
}
updatePlayState();
m_timeline->setPosition(mediaElement().currentTime());
updateCurrentTimeDisplay();
startHideMediaControlsTimer();
}
void MediaControls::playbackProgressed()
{
m_timeline->setPosition(mediaElement().currentTime());
updateCurrentTimeDisplay();
if (shouldHideMediaControls())
makeTransparent();
}
void MediaControls::playbackStopped()
{
updatePlayState();
m_timeline->setPosition(mediaElement().currentTime());
updateCurrentTimeDisplay();
makeOpaque();
stopHideMediaControlsTimer();
}
void MediaControls::updatePlayState()
{
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 (!RuntimeEnabledFeatures::newMediaPlaybackUiEnabled() && now > 0) {
BatchedControlUpdate batch(this);
m_currentTimeDisplay->setIsWanted(true);
m_durationDisplay->setIsWanted(false);
}
// Allow the theme to format the time.
m_currentTimeDisplay->setInnerText(LayoutTheme::theme().formatMediaControlsCurrentTime(now, duration), IGNORE_EXCEPTION);
m_currentTimeDisplay->setCurrentValue(now);
}
void MediaControls::updateVolume()
{
m_muteButton->updateDisplayType();
// Invalidate the mute button because it paints differently according to volume.
if (LayoutObject* layoutObject = m_muteButton->layoutObject())
layoutObject->setShouldDoFullPaintInvalidation();
if (mediaElement().muted())
m_volumeSlider->setVolume(0);
else
m_volumeSlider->setVolume(mediaElement().volume());
// Update the visibility of our audio elements.
// We never want the volume slider if there's no audio.
// If there is audio, then we want it unless hiding audio is enabled and
// we prefer to hide it.
BatchedControlUpdate batch(this);
m_volumeSlider->setIsWanted(mediaElement().hasAudio()
&& !(m_allowHiddenVolumeControls && preferHiddenVolumeControls(document())));
// The mute button is a little more complicated. If enableNewMediaPlaybackUi
// is true, then we choose to hide or show the mute button to save space.
// If enableNew* is not set, then we never touch the mute button, and
// instead leave it to the CSS.
// Note that this is why m_allowHiddenVolumeControls isn't rolled into prefer...().
if (m_allowHiddenVolumeControls) {
// If there is no audio track, then hide the mute button.
m_muteButton->setIsWanted(mediaElement().hasAudio());
}
// Invalidate the volume slider because it paints differently according to volume.
if (LayoutObject* layoutObject = m_volumeSlider->layoutObject())
layoutObject->setShouldDoFullPaintInvalidation();
}
void MediaControls::changedClosedCaptionsVisibility()
{
m_toggleClosedCaptionsButton->updateDisplayType();
}
void MediaControls::refreshClosedCaptionsButtonVisibility()
{
m_toggleClosedCaptionsButton->setIsWanted(mediaElement().hasClosedCaptions());
BatchedControlUpdate batch(this);
}
static Element* elementFromCenter(Element& element)
{
ClientRect* clientRect = element.getBoundingClientRect();
int centerX = static_cast<int>((clientRect->left() + clientRect->right()) / 2);
int centerY = static_cast<int>((clientRect->top() + clientRect->bottom()) / 2);
return element.document().elementFromPoint(centerX , centerY);
}
void MediaControls::tryShowOverlayCastButton()
{
// The element needs to be shown to have its dimensions and position.
m_overlayCastButton->setIsWanted(true);
if (elementFromCenter(*m_overlayCastButton) != &mediaElement())
m_overlayCastButton->setIsWanted(false);
}
void MediaControls::refreshCastButtonVisibility()
{
refreshCastButtonVisibilityWithoutUpdate();
BatchedControlUpdate batch(this);
}
void MediaControls::refreshCastButtonVisibilityWithoutUpdate()
{
if (mediaElement().hasRemoteRoutes()) {
// The reason for the autoplay test is that some pages (e.g. vimeo.com) have an autoplay background video, which
// doesn't autoplay on Chrome for Android (we prevent it) so starts paused. In such cases we don't want to automatically
// show the cast button, since it looks strange and is unlikely to correspond with anything the user wants to do.
// If a user does want to cast a paused autoplay video then they can still do so by touching or clicking on the
// video, which will cause the cast button to appear.
if (!mediaElement().shouldShowControls() && !mediaElement().autoplay() && mediaElement().paused()) {
// Note that this is a case where we add the overlay cast button
// without wanting the panel cast button. We depend on the fact
// that computeWhichControlsFit() won't change overlay cast button
// visibility in the case where the cast button isn't wanted.
// We don't call compute...() here, but it will be called as
// non-cast changes (e.g., resize) occur. If the panel button
// is shown, however, compute...() will take control of the
// overlay cast button if it needs to hide it from the panel.
tryShowOverlayCastButton();
m_castButton->setIsWanted(false);
} else if (mediaElement().shouldShowControls()) {
m_overlayCastButton->setIsWanted(false);
m_castButton->setIsWanted(true);
// Check that the cast button actually fits on the bar. For the
// newMediaPlaybackUiEnabled case, we let computeWhichControlsFit()
// handle this.
if ( !RuntimeEnabledFeatures::newMediaPlaybackUiEnabled()
&& m_fullScreenButton->getBoundingClientRect()->right() > m_panel->getBoundingClientRect()->right()) {
m_castButton->setIsWanted(false);
tryShowOverlayCastButton();
}
}
} else {
m_castButton->setIsWanted(false);
m_overlayCastButton->setIsWanted(false);
}
}
void MediaControls::showOverlayCastButton()
{
tryShowOverlayCastButton();
resetHideMediaControlsTimer();
}
void MediaControls::enteredFullscreen()
{
m_fullScreenButton->setIsFullscreen(true);
stopHideMediaControlsTimer();
startHideMediaControlsTimer();
}
void MediaControls::exitedFullscreen()
{
m_fullScreenButton->setIsFullscreen(false);
stopHideMediaControlsTimer();
startHideMediaControlsTimer();
}
void MediaControls::startedCasting()
{
m_castButton->setIsPlayingRemotely(true);
m_overlayCastButton->setIsPlayingRemotely(true);
}
void MediaControls::stoppedCasting()
{
m_castButton->setIsPlayingRemotely(false);
m_overlayCastButton->setIsPlayingRemotely(false);
}
void MediaControls::defaultEventHandler(Event* event)
{
HTMLDivElement::defaultEventHandler(event);
// Add IgnoreControlsHover to m_hideTimerBehaviorFlags when we see a touch event,
// to allow the hide-timer to do the right thing when it fires.
// FIXME: Preferably we would only do this when we're actually handling the event
// here ourselves.
bool wasLastEventTouch = event->isTouchEvent() || event->isGestureEvent()
|| (event->isMouseEvent() && toMouseEvent(event)->fromTouch());
m_hideTimerBehaviorFlags |= wasLastEventTouch ? IgnoreControlsHover : IgnoreNone;
if (event->type() == EventTypeNames::mouseover) {
if (!containsRelatedTarget(event)) {
m_isMouseOverControls = true;
if (!mediaElement().togglePlayStateWillPlay()) {
makeOpaque();
if (shouldHideMediaControls())
startHideMediaControlsTimer();
}
}
return;
}
if (event->type() == EventTypeNames::mouseout) {
if (!containsRelatedTarget(event)) {
m_isMouseOverControls = false;
stopHideMediaControlsTimer();
}
return;
}
if (event->type() == EventTypeNames::mousemove) {
// 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();
refreshCastButtonVisibility();
if (shouldHideMediaControls(IgnoreVideoHover))
startHideMediaControlsTimer();
return;
}
}
void MediaControls::hideMediaControlsTimerFired(Timer<MediaControls>*)
{
unsigned behaviorFlags = m_hideTimerBehaviorFlags | IgnoreFocus | IgnoreVideoHover;
m_hideTimerBehaviorFlags = IgnoreNone;
if (mediaElement().togglePlayStateWillPlay())
return;
if (!shouldHideMediaControls(behaviorFlags))
return;
makeTransparent();
}
void MediaControls::startHideMediaControlsTimer()
{
m_hideMediaControlsTimer.startOneShot(timeWithoutMouseMovementBeforeHidingMediaControls, FROM_HERE);
}
void MediaControls::stopHideMediaControlsTimer()
{
m_hideMediaControlsTimer.stop();
}
void MediaControls::resetHideMediaControlsTimer()
{
stopHideMediaControlsTimer();
if (!mediaElement().paused())
startHideMediaControlsTimer();
}
bool MediaControls::containsRelatedTarget(Event* event)
{
if (!event->isMouseEvent())
return false;
EventTarget* relatedTarget = toMouseEvent(event)->relatedTarget();
if (!relatedTarget)
return false;
return contains(relatedTarget->toNode());
}
void MediaControls::notifyPanelWidthChanged(const LayoutUnit& newWidth)
{
// Don't bother to do any work if this matches the most recent panel
// width, since we're called after layout.
// Note that this code permits a bad frame on resize, since it is
// run after the relayout / paint happens. It would be great to improve
// this, but it would be even greater to move this code entirely to
// JS and fix it there.
const int panelWidth = newWidth.toInt();
if (!RuntimeEnabledFeatures::newMediaPlaybackUiEnabled())
return;
m_panelWidth = panelWidth;
// Adjust for effective zoom.
if (!m_panel->layoutObject() || !m_panel->layoutObject()->style())
return;
m_panelWidth = ceil(m_panelWidth / m_panel->layoutObject()->style()->effectiveZoom());
m_panelWidthChangedTimer.startOneShot(0, FROM_HERE);
}
void MediaControls::panelWidthChangedTimerFired(Timer<MediaControls>*)
{
computeWhichControlsFit();
}
void MediaControls::computeWhichControlsFit()
{
// Hide all controls that don't fit, and show the ones that do.
// This might be better suited for a layout, but since JS media controls
// won't benefit from that anwyay, we just do it here like JS will.
if (!RuntimeEnabledFeatures::newMediaPlaybackUiEnabled())
return;
if (!m_panelWidth)
return;
// Controls that we'll hide / show, in order of decreasing priority.
MediaControlElement* elements[] = {
m_playButton.get(),
m_toggleClosedCaptionsButton.get(),
m_fullScreenButton.get(),
m_timeline.get(),
m_currentTimeDisplay.get(),
m_volumeSlider.get(),
m_castButton.get(),
m_muteButton.get(),
m_durationDisplay.get(),
};
int usedWidth = 0;
bool droppedCastButton = false;
// Assume that all controls require 48px. Ideally, we could get this
// the computed style, but that requires the controls to be shown.
const int minimumWidth = 48;
for (MediaControlElement* element : elements) {
if (!element)
continue;
if (element->isWanted()) {
if (usedWidth + minimumWidth <= m_panelWidth) {
element->setDoesFit(true);
usedWidth += minimumWidth;
} else {
element->setDoesFit(false);
if (element == m_castButton.get())
droppedCastButton = true;
}
}
}
// Special case for cast: if we want a cast button but dropped it, then
// show the overlay cast button instead.
if (m_castButton->isWanted())
m_overlayCastButton->setIsWanted(droppedCastButton);
}
void MediaControls::setAllowHiddenVolumeControls(bool allow)
{
m_allowHiddenVolumeControls = allow;
// Update the controls visibility.
updateVolume();
}
DEFINE_TRACE(MediaControls)
{
visitor->trace(m_mediaElement);
visitor->trace(m_panel);
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);
visitor->trace(m_castButton);
visitor->trace(m_overlayCastButton);
HTMLDivElement::trace(visitor);
}
}