Index: Source/core/html/shadow/MediaControls.cpp |
diff --git a/Source/core/html/shadow/MediaControls.cpp b/Source/core/html/shadow/MediaControls.cpp |
index 17d1096113fffeb9acbde1c6aaa10e83a405b48e..0e262e60995af9f2f54258e11e48abf70f513cfd 100644 |
--- a/Source/core/html/shadow/MediaControls.cpp |
+++ b/Source/core/html/shadow/MediaControls.cpp |
@@ -47,6 +47,11 @@ static bool fullscreenIsSupported(const Document& document) |
return !document.settings() || document.settings()->fullscreenSupported(); |
} |
+static bool preferHiddenAudioElements(const Document& document) |
+{ |
+ return !document.settings() || document.settings()->preferHiddenAudioElements(); |
+} |
+ |
MediaControls::MediaControls(HTMLMediaElement& mediaElement) |
: HTMLDivElement(mediaElement.document()) |
, m_mediaElement(&mediaElement) |
@@ -68,6 +73,10 @@ MediaControls::MediaControls(HTMLMediaElement& mediaElement) |
, m_hideTimerBehaviorFlags(IgnoreNone) |
, m_isMouseOverControls(false) |
, m_isPausedForScrubbing(false) |
+ , m_panelWidthChangedTimer(this, &MediaControls::panelWidthChangedTimerFired) |
+ , m_panelWidth(0) |
+ , m_allowHiddenAudioElements(RuntimeEnabledFeatures::newMediaPlaybackUiEnabled()) |
+ , m_keepMuteButton(false) |
{ |
} |
@@ -89,9 +98,12 @@ PassRefPtrWillBeRawPtr<MediaControls> MediaControls::create(HTMLMediaElement& me |
// \-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) |
@@ -99,6 +111,7 @@ PassRefPtrWillBeRawPtr<MediaControls> MediaControls::create(HTMLMediaElement& me |
// \-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()) { |
@@ -125,24 +138,36 @@ void MediaControls::initializeControls() |
RefPtrWillBeRawPtr<MediaControlTimelineElement> timeline = MediaControlTimelineElement::create(*this); |
m_timeline = timeline.get(); |
- panel->appendChild(timeline.release()); |
+ // 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->hide(); |
+ 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()); |
+ if (m_allowHiddenAudioElements && preferHiddenAudioElements(document())) |
+ m_muteButton->setIsWanted(false); |
RefPtrWillBeRawPtr<MediaControlVolumeSliderElement> slider = MediaControlVolumeSliderElement::create(*this); |
m_volumeSlider = slider.get(); |
panel->appendChild(slider.release()); |
+ if (m_allowHiddenAudioElements && preferHiddenAudioElements(document())) |
+ m_volumeSlider->setIsWanted(false); |
RefPtrWillBeRawPtr<MediaControlToggleClosedCaptionsButtonElement> toggleClosedCaptionsButton = MediaControlToggleClosedCaptionsButtonElement::create(*this); |
m_toggleClosedCaptionsButton = toggleClosedCaptionsButton.get(); |
@@ -165,10 +190,24 @@ void MediaControls::initializeControls() |
void MediaControls::reset() |
{ |
- double duration = mediaElement().duration(); |
+ const bool useNewUi = RuntimeEnabledFeatures::newMediaPlaybackUiEnabled(); |
+ 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. |
+ if (std::isfinite(duration)) |
+ m_durationDisplay->setIsWanted(true); |
+ else |
+ m_durationDisplay->setIsWanted(false); |
+ m_currentTimeDisplay->setIsWanted(true); |
+ m_timeline->setIsWanted(true); |
+ } |
+ |
updatePlayState(); |
updateCurrentTimeDisplay(); |
@@ -176,10 +215,6 @@ void MediaControls::reset() |
m_timeline->setDuration(duration); |
m_timeline->setPosition(mediaElement().currentTime()); |
- if (!mediaElement().hasAudio()) |
- m_volumeSlider->hide(); |
- else |
- m_volumeSlider->show(); |
updateVolume(); |
refreshClosedCaptionsButtonVisibility(); |
@@ -189,14 +224,13 @@ void MediaControls::reset() |
// 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().hasVideo() && fullscreenIsSupported(document())) |
- || mediaElement().isFullscreen()) |
- m_fullScreenButton->show(); |
- else |
- m_fullScreenButton->hide(); |
+ m_fullScreenButton->setIsWanted((mediaElement().hasVideo() |
+ && fullscreenIsSupported(document())) |
+ || mediaElement().isFullscreen()); |
- refreshCastButtonVisibility(); |
+ refreshCastButtonVisibilityWithoutUpdate(); |
makeOpaque(); |
+ changedControlSelections(); |
} |
LayoutObject* MediaControls::layoutObjectForTextTrackLayout() |
@@ -207,7 +241,7 @@ LayoutObject* MediaControls::layoutObjectForTextTrackLayout() |
void MediaControls::show() |
{ |
makeOpaque(); |
- m_panel->show(); |
+ m_panel->setIsWanted(true); |
m_panel->setIsDisplayed(true); |
if (m_overlayPlayButton) |
m_overlayPlayButton->updateDisplayType(); |
@@ -223,10 +257,10 @@ void MediaControls::mediaElementFocused() |
void MediaControls::hide() |
{ |
- m_panel->hide(); |
+ m_panel->setIsWanted(false); |
m_panel->setIsDisplayed(false); |
if (m_overlayPlayButton) |
- m_overlayPlayButton->hide(); |
+ m_overlayPlayButton->setIsWanted(false); |
} |
void MediaControls::makeOpaque() |
@@ -237,6 +271,7 @@ void MediaControls::makeOpaque() |
void MediaControls::makeTransparent() |
{ |
m_panel->makeTransparent(); |
+ m_overlayCastButton->setIsWanted(false); |
} |
bool MediaControls::shouldHideMediaControls(unsigned behaviorFlags) const |
@@ -263,8 +298,10 @@ bool MediaControls::shouldHideMediaControls(unsigned behaviorFlags) const |
void MediaControls::playbackStarted() |
{ |
- m_currentTimeDisplay->show(); |
- m_durationDisplay->hide(); |
+ if (!RuntimeEnabledFeatures::newMediaPlaybackUiEnabled()) { |
+ m_currentTimeDisplay->setIsWanted(true); |
+ m_durationDisplay->setIsWanted(false); |
+ } |
updatePlayState(); |
m_timeline->setPosition(mediaElement().currentTime()); |
@@ -325,9 +362,9 @@ void MediaControls::updateCurrentTimeDisplay() |
double duration = mediaElement().duration(); |
// After seek, hide duration display and show current time. |
- if (now > 0) { |
- m_currentTimeDisplay->show(); |
- m_durationDisplay->hide(); |
+ if (!RuntimeEnabledFeatures::newMediaPlaybackUiEnabled() && now > 0) { |
+ m_currentTimeDisplay->setIsWanted(true); |
+ m_durationDisplay->setIsWanted(false); |
} |
// Allow the theme to format the time. |
@@ -342,10 +379,36 @@ void MediaControls::updateVolume() |
if (LayoutObject* layoutObject = m_muteButton->layoutObject()) |
layoutObject->setShouldDoFullPaintInvalidation(); |
- if (mediaElement().muted()) |
+ if (mediaElement().muted()) { |
m_volumeSlider->setVolume(0); |
- else |
+ } 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. |
+ m_volumeSlider->setIsWanted(mediaElement().hasAudio() |
+ && !(m_allowHiddenAudioElements && preferHiddenAudioElements(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 let it up to the CSS. |
+ // Note that this is why m_allowHiddenAudioElements isn't rolled into prefer...(). |
+ if (m_allowHiddenAudioElements) { |
+ // If there is no audio track, then hide the mute button. If there |
+ // is an audio track, then we always show the mute button unless |
+ // we prefer to hide it and the media isn't muted. If it's muted, |
+ // then we show it to let the user unmute it. In this case, we don't |
+ // want to re-hide the mute button later. |
+ m_keepMuteButton |= mediaElement().muted(); |
+ m_muteButton->setIsWanted(mediaElement().hasAudio() |
+ && (!preferHiddenAudioElements(document()) || m_keepMuteButton)); |
+ } |
+ |
+ |
// Invalidate the volume slider because it paints differently according to volume. |
if (LayoutObject* layoutObject = m_volumeSlider->layoutObject()) |
layoutObject->setShouldDoFullPaintInvalidation(); |
@@ -358,10 +421,7 @@ void MediaControls::changedClosedCaptionsVisibility() |
void MediaControls::refreshClosedCaptionsButtonVisibility() |
{ |
- if (mediaElement().hasClosedCaptions()) |
- m_toggleClosedCaptionsButton->show(); |
- else |
- m_toggleClosedCaptionsButton->hide(); |
+ m_toggleClosedCaptionsButton->setIsWanted(mediaElement().hasClosedCaptions()); |
} |
static Element* elementFromCenter(Element& element) |
@@ -376,14 +436,19 @@ static Element* elementFromCenter(Element& element) |
void MediaControls::tryShowOverlayCastButton() |
{ |
// The element needs to be shown to have its dimensions and position. |
- m_overlayCastButton->show(); |
- |
+ m_overlayCastButton->setIsWanted(true); |
if (elementFromCenter(*m_overlayCastButton) != &mediaElement()) |
- m_overlayCastButton->hide(); |
+ m_overlayCastButton->setIsWanted(false); |
} |
void MediaControls::refreshCastButtonVisibility() |
{ |
+ refreshCastButtonVisibilityWithoutUpdate(); |
+ changedControlSelections(); |
+} |
+ |
+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 |
@@ -391,19 +456,31 @@ void MediaControls::refreshCastButtonVisibility() |
// 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()) { |
- showOverlayCastButton(); |
+ // 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->hide(); |
- m_castButton->show(); |
- // Check that the cast button actually fits on the bar. |
- if (m_fullScreenButton->getBoundingClientRect()->right() > m_panel->getBoundingClientRect()->right()) { |
- m_castButton->hide(); |
+ m_overlayCastButton->setIsWanted(false); |
+ m_castButton->setIsWanted(true); |
+ // Check that the cast button actually fits on the bar. For the |
+ // newMediaPlaybackUiEnabled case, we let changedControlSelections() |
+ // handle this. |
+ if ( !RuntimeEnabledFeatures::newMediaPlaybackUiEnabled() |
+ && m_fullScreenButton->getBoundingClientRect()->right() > m_panel->getBoundingClientRect()->right()) { |
+ m_castButton->setIsWanted(false); |
tryShowOverlayCastButton(); |
} |
} |
} else { |
- m_castButton->hide(); |
- m_overlayCastButton->hide(); |
+ m_castButton->setIsWanted(false); |
+ m_overlayCastButton->setIsWanted(false); |
} |
} |
@@ -494,7 +571,6 @@ void MediaControls::hideMediaControlsTimerFired(Timer<MediaControls>*) |
return; |
makeTransparent(); |
- m_overlayCastButton->hide(); |
} |
void MediaControls::startHideMediaControlsTimer() |
@@ -525,6 +601,97 @@ bool MediaControls::containsRelatedTarget(Event* event) |
return contains(relatedTarget->toNode()); |
} |
+void MediaControls::notifyPanelWidthChanged(int panelWidth) |
+{ |
+ // Don't bother to do any work if this matches the most recent panel |
+ // width, since we're called after layout. |
+ if (panelWidth == 0) |
+ return; |
+ |
+ if (!RuntimeEnabledFeatures::newMediaPlaybackUiEnabled()) |
+ return; |
+ |
+ m_panelWidth = panelWidth; |
+ m_panelWidthChangedTimer.startOneShot(0, FROM_HERE); |
+} |
+ |
+void MediaControls::changedControlSelections() |
+{ |
+ int panelWidth = m_panel->clientWidth(); |
+ // Don't short-circuit update if the panel hasn't changed. The fit state |
+ // is a function of the controls we want, so recompute if we can. |
+ if (panelWidth == 0) |
+ return; |
+ |
+ m_panelWidth = panelWidth; |
+ computeWhichControlsFit(); |
+} |
+ |
+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. |
+ // The order, in order of decreasing droppiness: |
+ // Volume, time, seek bar, cast. |
+ |
+ 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; |
+ for (MediaControlElement* element : elements) { |
+ if (!element) |
+ continue; |
+ |
+ if (element->isWanted()) { |
+ if (usedWidth + element->minimumWidth() <= m_panelWidth) { |
+ element->setDoesFit(true); |
+ usedWidth += element->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::setAllowHiddenAudioElements(bool allow) |
+{ |
+ m_allowHiddenAudioElements = allow; |
+ // Clear the 'keep muted flag', for tests. |
+ m_keepMuteButton = false; |
+ // Update the controls visibility. |
+ updateVolume(); |
+} |
+ |
DEFINE_TRACE(MediaControls) |
{ |
visitor->trace(m_mediaElement); |