2 * Copyright (C) 2011, 2012 Apple Inc. All rights reserved.
3 * Copyright (C) 2011, 2012 Google Inc. All rights reserved.
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
14 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
15 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
18 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
19 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
20 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
21 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
22 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 #include "core/html/shadow/MediaControls.h"
30 #include "bindings/core/v8/ExceptionStatePlaceholder.h"
31 #include "core/dom/ClientRect.h"
32 #include "core/dom/Fullscreen.h"
33 #include "core/events/MouseEvent.h"
34 #include "core/frame/Settings.h"
35 #include "core/html/HTMLMediaElement.h"
36 #include "core/html/MediaController.h"
37 #include "core/html/track/TextTrackContainer.h"
38 #include "core/layout/LayoutTheme.h"
42 // If you change this value, then also update the corresponding value in
43 // LayoutTests/media/media-controls.js.
44 static const double timeWithoutMouseMovementBeforeHidingMediaControls
= 3;
46 static bool shouldShowFullscreenButton(const HTMLMediaElement
& mediaElement
)
48 // Unconditionally allow the user to exit fullscreen if we are in it
49 // now. Especially on android, when we might not yet know if
50 // fullscreen is supported, we sometimes guess incorrectly and show
51 // the button earlier, and we don't want to remove it here if the
52 // user chose to enter fullscreen. crbug.com/500732 .
53 if (mediaElement
.isFullscreen())
56 if (!mediaElement
.hasVideo())
59 if (!Fullscreen::fullscreenEnabled(mediaElement
.document()))
65 static bool preferHiddenVolumeControls(const Document
& document
)
67 return !document
.settings() || document
.settings()->preferHiddenVolumeControls();
70 class MediaControls::BatchedControlUpdate
{
71 WTF_MAKE_NONCOPYABLE(BatchedControlUpdate
);
74 explicit BatchedControlUpdate(MediaControls
* controls
)
75 : m_controls(controls
)
77 ASSERT(isMainThread());
78 ASSERT(s_batchDepth
>= 0);
81 ~BatchedControlUpdate()
83 ASSERT(isMainThread());
84 ASSERT(s_batchDepth
> 0);
85 if (!(--s_batchDepth
))
86 m_controls
->computeWhichControlsFit();
90 RawPtrWillBeMember
<MediaControls
> m_controls
;
91 static int s_batchDepth
;
94 // Count of number open batches for controls visibility.
95 int MediaControls::BatchedControlUpdate::s_batchDepth
= 0;
97 MediaControls::MediaControls(HTMLMediaElement
& mediaElement
)
98 : HTMLDivElement(mediaElement
.document())
99 , m_mediaElement(&mediaElement
)
100 , m_overlayEnclosure(nullptr)
101 , m_overlayPlayButton(nullptr)
102 , m_overlayCastButton(nullptr)
103 , m_enclosure(nullptr)
105 , m_playButton(nullptr)
106 , m_timeline(nullptr)
107 , m_currentTimeDisplay(nullptr)
108 , m_durationDisplay(nullptr)
109 , m_muteButton(nullptr)
110 , m_volumeSlider(nullptr)
111 , m_toggleClosedCaptionsButton(nullptr)
112 , m_castButton(nullptr)
113 , m_fullScreenButton(nullptr)
114 , m_hideMediaControlsTimer(this, &MediaControls::hideMediaControlsTimerFired
)
115 , m_hideTimerBehaviorFlags(IgnoreNone
)
116 , m_isMouseOverControls(false)
117 , m_isPausedForScrubbing(false)
118 , m_panelWidthChangedTimer(this, &MediaControls::panelWidthChangedTimerFired
)
120 , m_allowHiddenVolumeControls(RuntimeEnabledFeatures::newMediaPlaybackUiEnabled())
124 PassRefPtrWillBeRawPtr
<MediaControls
> MediaControls::create(HTMLMediaElement
& mediaElement
)
126 RefPtrWillBeRawPtr
<MediaControls
> controls
= adoptRefWillBeNoop(new MediaControls(mediaElement
));
127 controls
->setShadowPseudoId(AtomicString("-webkit-media-controls", AtomicString::ConstructFromLiteral
));
128 controls
->initializeControls();
129 return controls
.release();
132 // The media controls DOM structure looks like:
134 // MediaControls (-webkit-media-controls)
135 // +-MediaControlOverlayEnclosureElement (-webkit-media-controls-overlay-enclosure)
136 // | +-MediaControlOverlayPlayButtonElement (-webkit-media-controls-overlay-play-button)
137 // | | {if mediaControlsOverlayPlayButtonEnabled}
138 // | \-MediaControlCastButtonElement (-internal-media-controls-overlay-cast-button)
139 // \-MediaControlPanelEnclosureElement (-webkit-media-controls-enclosure)
140 // \-MediaControlPanelElement (-webkit-media-controls-panel)
141 // +-MediaControlPlayButtonElement (-webkit-media-controls-play-button)
142 // | {if !RTE::newMediaPlaybackUi()}
143 // +-MediaControlTimelineElement (-webkit-media-controls-timeline)
144 // +-MediaControlCurrentTimeDisplayElement (-webkit-media-controls-current-time-display)
145 // +-MediaControlTimeRemainingDisplayElement (-webkit-media-controls-time-remaining-display)
146 // | {if RTE::newMediaPlaybackUi()}
147 // +-MediaControlTimelineElement (-webkit-media-controls-timeline)
148 // +-MediaControlMuteButtonElement (-webkit-media-controls-mute-button)
149 // +-MediaControlVolumeSliderElement (-webkit-media-controls-volume-slider)
150 // +-MediaControlToggleClosedCaptionsButtonElement (-webkit-media-controls-toggle-closed-captions-button)
151 // +-MediaControlCastButtonElement (-internal-media-controls-cast-button)
152 // \-MediaControlFullscreenButtonElement (-webkit-media-controls-fullscreen-button)
153 void MediaControls::initializeControls()
155 const bool useNewUi
= RuntimeEnabledFeatures::newMediaPlaybackUiEnabled();
156 RefPtrWillBeRawPtr
<MediaControlOverlayEnclosureElement
> overlayEnclosure
= MediaControlOverlayEnclosureElement::create(*this);
158 if (document().settings() && document().settings()->mediaControlsOverlayPlayButtonEnabled()) {
159 RefPtrWillBeRawPtr
<MediaControlOverlayPlayButtonElement
> overlayPlayButton
= MediaControlOverlayPlayButtonElement::create(*this);
160 m_overlayPlayButton
= overlayPlayButton
.get();
161 overlayEnclosure
->appendChild(overlayPlayButton
.release());
164 RefPtrWillBeRawPtr
<MediaControlCastButtonElement
> overlayCastButton
= MediaControlCastButtonElement::create(*this, true);
165 m_overlayCastButton
= overlayCastButton
.get();
166 overlayEnclosure
->appendChild(overlayCastButton
.release());
168 m_overlayEnclosure
= overlayEnclosure
.get();
169 appendChild(overlayEnclosure
.release());
171 // Create an enclosing element for the panel so we can visually offset the controls correctly.
172 RefPtrWillBeRawPtr
<MediaControlPanelEnclosureElement
> enclosure
= MediaControlPanelEnclosureElement::create(*this);
174 RefPtrWillBeRawPtr
<MediaControlPanelElement
> panel
= MediaControlPanelElement::create(*this);
176 RefPtrWillBeRawPtr
<MediaControlPlayButtonElement
> playButton
= MediaControlPlayButtonElement::create(*this);
177 m_playButton
= playButton
.get();
178 panel
->appendChild(playButton
.release());
180 RefPtrWillBeRawPtr
<MediaControlTimelineElement
> timeline
= MediaControlTimelineElement::create(*this);
181 m_timeline
= timeline
.get();
182 // In old UX, timeline is before the time / duration text.
184 panel
->appendChild(timeline
.release());
185 // else we will attach it later.
187 RefPtrWillBeRawPtr
<MediaControlCurrentTimeDisplayElement
> currentTimeDisplay
= MediaControlCurrentTimeDisplayElement::create(*this);
188 m_currentTimeDisplay
= currentTimeDisplay
.get();
189 m_currentTimeDisplay
->setIsWanted(useNewUi
);
190 panel
->appendChild(currentTimeDisplay
.release());
192 RefPtrWillBeRawPtr
<MediaControlTimeRemainingDisplayElement
> durationDisplay
= MediaControlTimeRemainingDisplayElement::create(*this);
193 m_durationDisplay
= durationDisplay
.get();
194 panel
->appendChild(durationDisplay
.release());
196 // Timeline is after the time / duration text if newMediaPlaybackUiEnabled.
198 panel
->appendChild(timeline
.release());
200 RefPtrWillBeRawPtr
<MediaControlMuteButtonElement
> muteButton
= MediaControlMuteButtonElement::create(*this);
201 m_muteButton
= muteButton
.get();
202 panel
->appendChild(muteButton
.release());
204 RefPtrWillBeRawPtr
<MediaControlVolumeSliderElement
> slider
= MediaControlVolumeSliderElement::create(*this);
205 m_volumeSlider
= slider
.get();
206 panel
->appendChild(slider
.release());
207 if (m_allowHiddenVolumeControls
&& preferHiddenVolumeControls(document()))
208 m_volumeSlider
->setIsWanted(false);
210 RefPtrWillBeRawPtr
<MediaControlToggleClosedCaptionsButtonElement
> toggleClosedCaptionsButton
= MediaControlToggleClosedCaptionsButtonElement::create(*this);
211 m_toggleClosedCaptionsButton
= toggleClosedCaptionsButton
.get();
212 panel
->appendChild(toggleClosedCaptionsButton
.release());
214 RefPtrWillBeRawPtr
<MediaControlCastButtonElement
> castButton
= MediaControlCastButtonElement::create(*this, false);
215 m_castButton
= castButton
.get();
216 panel
->appendChild(castButton
.release());
218 RefPtrWillBeRawPtr
<MediaControlFullscreenButtonElement
> fullscreenButton
= MediaControlFullscreenButtonElement::create(*this);
219 m_fullScreenButton
= fullscreenButton
.get();
220 panel
->appendChild(fullscreenButton
.release());
222 m_panel
= panel
.get();
223 enclosure
->appendChild(panel
.release());
225 m_enclosure
= enclosure
.get();
226 appendChild(enclosure
.release());
229 void MediaControls::reset()
231 const bool useNewUi
= RuntimeEnabledFeatures::newMediaPlaybackUiEnabled();
232 BatchedControlUpdate
batch(this);
234 m_allowHiddenVolumeControls
= useNewUi
;
236 const double duration
= mediaElement().duration();
237 m_durationDisplay
->setInnerText(LayoutTheme::theme().formatMediaControlsTime(duration
), ASSERT_NO_EXCEPTION
);
238 m_durationDisplay
->setCurrentValue(duration
);
241 // Show everything that we might hide.
242 // If we don't have a duration, then mark it to be hidden. For the
243 // old UI case, want / don't want is the same as show / hide since
244 // it is never marked as not fitting.
245 m_durationDisplay
->setIsWanted(std::isfinite(duration
));
246 m_currentTimeDisplay
->setIsWanted(true);
247 m_timeline
->setIsWanted(true);
252 updateCurrentTimeDisplay();
254 m_timeline
->setDuration(duration
);
255 m_timeline
->setPosition(mediaElement().currentTime());
259 refreshClosedCaptionsButtonVisibility();
261 m_fullScreenButton
->setIsWanted(shouldShowFullscreenButton(mediaElement()));
263 refreshCastButtonVisibilityWithoutUpdate();
265 // Set the panel width here, and force a layout, before the controls update.
266 // This would be harmless for the !useNewUi case too, but it causes
267 // compositing/geometry/video-fixed-scrolling.html to fail with two extra
268 // 0 height nodes in the render tree.
270 m_panelWidth
= m_panel
->clientWidth();
273 LayoutObject
* MediaControls::layoutObjectForTextTrackLayout()
275 return m_panel
->layoutObject();
278 void MediaControls::show()
281 m_panel
->setIsWanted(true);
282 m_panel
->setIsDisplayed(true);
283 if (m_overlayPlayButton
)
284 m_overlayPlayButton
->updateDisplayType();
287 void MediaControls::mediaElementFocused()
289 if (mediaElement().shouldShowControls()) {
291 resetHideMediaControlsTimer();
295 void MediaControls::hide()
297 m_panel
->setIsWanted(false);
298 m_panel
->setIsDisplayed(false);
299 if (m_overlayPlayButton
)
300 m_overlayPlayButton
->setIsWanted(false);
303 void MediaControls::makeOpaque()
305 m_panel
->makeOpaque();
308 void MediaControls::makeTransparent()
310 m_panel
->makeTransparent();
313 bool MediaControls::shouldHideMediaControls(unsigned behaviorFlags
) const
315 // Never hide for a media element without visual representation.
316 if (!mediaElement().hasVideo() || mediaElement().isPlayingRemotely())
318 // Don't hide if the mouse is over the controls.
319 const bool ignoreControlsHover
= behaviorFlags
& IgnoreControlsHover
;
320 if (!ignoreControlsHover
&& m_panel
->hovered())
322 // Don't hide if the mouse is over the video area.
323 const bool ignoreVideoHover
= behaviorFlags
& IgnoreVideoHover
;
324 if (!ignoreVideoHover
&& m_isMouseOverControls
)
326 // Don't hide if focus is on the HTMLMediaElement or within the
327 // controls/shadow tree. (Perform the checks separately to avoid going
328 // through all the potential ancestor hosts for the focused element.)
329 const bool ignoreFocus
= behaviorFlags
& IgnoreFocus
;
330 if (!ignoreFocus
&& (mediaElement().focused() || contains(document().focusedElement())))
335 void MediaControls::playbackStarted()
337 BatchedControlUpdate
batch(this);
339 if (!RuntimeEnabledFeatures::newMediaPlaybackUiEnabled()) {
340 m_currentTimeDisplay
->setIsWanted(true);
341 m_durationDisplay
->setIsWanted(false);
345 m_timeline
->setPosition(mediaElement().currentTime());
346 updateCurrentTimeDisplay();
348 startHideMediaControlsTimer();
351 void MediaControls::playbackProgressed()
353 m_timeline
->setPosition(mediaElement().currentTime());
354 updateCurrentTimeDisplay();
356 if (shouldHideMediaControls())
360 void MediaControls::playbackStopped()
363 m_timeline
->setPosition(mediaElement().currentTime());
364 updateCurrentTimeDisplay();
367 stopHideMediaControlsTimer();
370 void MediaControls::updatePlayState()
372 if (m_isPausedForScrubbing
)
375 if (m_overlayPlayButton
)
376 m_overlayPlayButton
->updateDisplayType();
377 m_playButton
->updateDisplayType();
380 void MediaControls::beginScrubbing()
382 if (!mediaElement().togglePlayStateWillPlay()) {
383 m_isPausedForScrubbing
= true;
384 mediaElement().togglePlayState();
388 void MediaControls::endScrubbing()
390 if (m_isPausedForScrubbing
) {
391 m_isPausedForScrubbing
= false;
392 if (mediaElement().togglePlayStateWillPlay())
393 mediaElement().togglePlayState();
397 void MediaControls::updateCurrentTimeDisplay()
399 double now
= mediaElement().currentTime();
400 double duration
= mediaElement().duration();
402 // After seek, hide duration display and show current time.
403 if (!RuntimeEnabledFeatures::newMediaPlaybackUiEnabled() && now
> 0) {
404 BatchedControlUpdate
batch(this);
405 m_currentTimeDisplay
->setIsWanted(true);
406 m_durationDisplay
->setIsWanted(false);
409 // Allow the theme to format the time.
410 m_currentTimeDisplay
->setInnerText(LayoutTheme::theme().formatMediaControlsCurrentTime(now
, duration
), IGNORE_EXCEPTION
);
411 m_currentTimeDisplay
->setCurrentValue(now
);
414 void MediaControls::updateVolume()
416 m_muteButton
->updateDisplayType();
417 // Invalidate the mute button because it paints differently according to volume.
418 if (LayoutObject
* layoutObject
= m_muteButton
->layoutObject())
419 layoutObject
->setShouldDoFullPaintInvalidation();
421 if (mediaElement().muted())
422 m_volumeSlider
->setVolume(0);
424 m_volumeSlider
->setVolume(mediaElement().volume());
426 // Update the visibility of our audio elements.
427 // We never want the volume slider if there's no audio.
428 // If there is audio, then we want it unless hiding audio is enabled and
429 // we prefer to hide it.
430 BatchedControlUpdate
batch(this);
431 m_volumeSlider
->setIsWanted(mediaElement().hasAudio()
432 && !(m_allowHiddenVolumeControls
&& preferHiddenVolumeControls(document())));
434 // The mute button is a little more complicated. If enableNewMediaPlaybackUi
435 // is true, then we choose to hide or show the mute button to save space.
436 // If enableNew* is not set, then we never touch the mute button, and
437 // instead leave it to the CSS.
438 // Note that this is why m_allowHiddenVolumeControls isn't rolled into prefer...().
439 if (m_allowHiddenVolumeControls
) {
440 // If there is no audio track, then hide the mute button.
441 m_muteButton
->setIsWanted(mediaElement().hasAudio());
444 // Invalidate the volume slider because it paints differently according to volume.
445 if (LayoutObject
* layoutObject
= m_volumeSlider
->layoutObject())
446 layoutObject
->setShouldDoFullPaintInvalidation();
449 void MediaControls::changedClosedCaptionsVisibility()
451 m_toggleClosedCaptionsButton
->updateDisplayType();
454 void MediaControls::refreshClosedCaptionsButtonVisibility()
456 m_toggleClosedCaptionsButton
->setIsWanted(mediaElement().hasClosedCaptions());
457 BatchedControlUpdate
batch(this);
460 static Element
* elementFromCenter(Element
& element
)
462 ClientRect
* clientRect
= element
.getBoundingClientRect();
463 int centerX
= static_cast<int>((clientRect
->left() + clientRect
->right()) / 2);
464 int centerY
= static_cast<int>((clientRect
->top() + clientRect
->bottom()) / 2);
466 return element
.document().elementFromPoint(centerX
, centerY
);
469 void MediaControls::tryShowOverlayCastButton()
471 // The element needs to be shown to have its dimensions and position.
472 m_overlayCastButton
->setIsWanted(true);
473 if (elementFromCenter(*m_overlayCastButton
) != &mediaElement())
474 m_overlayCastButton
->setIsWanted(false);
477 void MediaControls::refreshCastButtonVisibility()
479 refreshCastButtonVisibilityWithoutUpdate();
480 BatchedControlUpdate
batch(this);
483 void MediaControls::refreshCastButtonVisibilityWithoutUpdate()
485 if (mediaElement().hasRemoteRoutes()) {
486 // The reason for the autoplay test is that some pages (e.g. vimeo.com) have an autoplay background video, which
487 // doesn't autoplay on Chrome for Android (we prevent it) so starts paused. In such cases we don't want to automatically
488 // show the cast button, since it looks strange and is unlikely to correspond with anything the user wants to do.
489 // If a user does want to cast a paused autoplay video then they can still do so by touching or clicking on the
490 // video, which will cause the cast button to appear.
491 if (!mediaElement().shouldShowControls() && !mediaElement().autoplay() && mediaElement().paused()) {
492 // Note that this is a case where we add the overlay cast button
493 // without wanting the panel cast button. We depend on the fact
494 // that computeWhichControlsFit() won't change overlay cast button
495 // visibility in the case where the cast button isn't wanted.
496 // We don't call compute...() here, but it will be called as
497 // non-cast changes (e.g., resize) occur. If the panel button
498 // is shown, however, compute...() will take control of the
499 // overlay cast button if it needs to hide it from the panel.
500 tryShowOverlayCastButton();
501 m_castButton
->setIsWanted(false);
502 } else if (mediaElement().shouldShowControls()) {
503 m_overlayCastButton
->setIsWanted(false);
504 m_castButton
->setIsWanted(true);
505 // Check that the cast button actually fits on the bar. For the
506 // newMediaPlaybackUiEnabled case, we let computeWhichControlsFit()
508 if ( !RuntimeEnabledFeatures::newMediaPlaybackUiEnabled()
509 && m_fullScreenButton
->getBoundingClientRect()->right() > m_panel
->getBoundingClientRect()->right()) {
510 m_castButton
->setIsWanted(false);
511 tryShowOverlayCastButton();
515 m_castButton
->setIsWanted(false);
516 m_overlayCastButton
->setIsWanted(false);
520 void MediaControls::showOverlayCastButton()
522 tryShowOverlayCastButton();
523 resetHideMediaControlsTimer();
526 void MediaControls::enteredFullscreen()
528 m_fullScreenButton
->setIsFullscreen(true);
529 stopHideMediaControlsTimer();
530 startHideMediaControlsTimer();
533 void MediaControls::exitedFullscreen()
535 m_fullScreenButton
->setIsFullscreen(false);
536 stopHideMediaControlsTimer();
537 startHideMediaControlsTimer();
540 void MediaControls::startedCasting()
542 m_castButton
->setIsPlayingRemotely(true);
543 m_overlayCastButton
->setIsPlayingRemotely(true);
546 void MediaControls::stoppedCasting()
548 m_castButton
->setIsPlayingRemotely(false);
549 m_overlayCastButton
->setIsPlayingRemotely(false);
552 void MediaControls::defaultEventHandler(Event
* event
)
554 HTMLDivElement::defaultEventHandler(event
);
556 // Add IgnoreControlsHover to m_hideTimerBehaviorFlags when we see a touch event,
557 // to allow the hide-timer to do the right thing when it fires.
558 // FIXME: Preferably we would only do this when we're actually handling the event
560 bool wasLastEventTouch
= event
->isTouchEvent() || event
->isGestureEvent()
561 || (event
->isMouseEvent() && toMouseEvent(event
)->fromTouch());
562 m_hideTimerBehaviorFlags
|= wasLastEventTouch
? IgnoreControlsHover
: IgnoreNone
;
564 if (event
->type() == EventTypeNames::mouseover
) {
565 if (!containsRelatedTarget(event
)) {
566 m_isMouseOverControls
= true;
567 if (!mediaElement().togglePlayStateWillPlay()) {
569 if (shouldHideMediaControls())
570 startHideMediaControlsTimer();
576 if (event
->type() == EventTypeNames::mouseout
) {
577 if (!containsRelatedTarget(event
)) {
578 m_isMouseOverControls
= false;
579 stopHideMediaControlsTimer();
584 if (event
->type() == EventTypeNames::mousemove
) {
585 // When we get a mouse move, show the media controls, and start a timer
586 // that will hide the media controls after a 3 seconds without a mouse move.
588 refreshCastButtonVisibility();
589 if (shouldHideMediaControls(IgnoreVideoHover
))
590 startHideMediaControlsTimer();
595 void MediaControls::hideMediaControlsTimerFired(Timer
<MediaControls
>*)
597 unsigned behaviorFlags
= m_hideTimerBehaviorFlags
| IgnoreFocus
| IgnoreVideoHover
;
598 m_hideTimerBehaviorFlags
= IgnoreNone
;
600 if (mediaElement().togglePlayStateWillPlay())
603 if (!shouldHideMediaControls(behaviorFlags
))
607 m_overlayCastButton
->setIsWanted(false);
610 void MediaControls::startHideMediaControlsTimer()
612 m_hideMediaControlsTimer
.startOneShot(timeWithoutMouseMovementBeforeHidingMediaControls
, FROM_HERE
);
615 void MediaControls::stopHideMediaControlsTimer()
617 m_hideMediaControlsTimer
.stop();
620 void MediaControls::resetHideMediaControlsTimer()
622 stopHideMediaControlsTimer();
623 if (!mediaElement().paused())
624 startHideMediaControlsTimer();
628 bool MediaControls::containsRelatedTarget(Event
* event
)
630 if (!event
->isMouseEvent())
632 EventTarget
* relatedTarget
= toMouseEvent(event
)->relatedTarget();
635 return contains(relatedTarget
->toNode());
638 void MediaControls::notifyPanelWidthChanged(const LayoutUnit
& newWidth
)
640 // Don't bother to do any work if this matches the most recent panel
641 // width, since we're called after layout.
642 // Note that this code permits a bad frame on resize, since it is
643 // run after the relayout / paint happens. It would be great to improve
644 // this, but it would be even greater to move this code entirely to
645 // JS and fix it there.
646 const int panelWidth
= newWidth
.toInt();
648 if (!RuntimeEnabledFeatures::newMediaPlaybackUiEnabled())
651 m_panelWidth
= panelWidth
;
653 // Adjust for effective zoom.
654 if (!m_panel
->layoutObject() || !m_panel
->layoutObject()->style())
656 m_panelWidth
= ceil(m_panelWidth
/ m_panel
->layoutObject()->style()->effectiveZoom());
658 m_panelWidthChangedTimer
.startOneShot(0, FROM_HERE
);
661 void MediaControls::panelWidthChangedTimerFired(Timer
<MediaControls
>*)
663 computeWhichControlsFit();
666 void MediaControls::computeWhichControlsFit()
668 // Hide all controls that don't fit, and show the ones that do.
669 // This might be better suited for a layout, but since JS media controls
670 // won't benefit from that anwyay, we just do it here like JS will.
672 if (!RuntimeEnabledFeatures::newMediaPlaybackUiEnabled())
678 // Controls that we'll hide / show, in order of decreasing priority.
679 MediaControlElement
* elements
[] = {
681 m_toggleClosedCaptionsButton
.get(),
682 m_fullScreenButton
.get(),
684 m_currentTimeDisplay
.get(),
685 m_volumeSlider
.get(),
688 m_durationDisplay
.get(),
692 bool droppedCastButton
= false;
693 // Assume that all controls require 48px. Ideally, we could get this
694 // the computed style, but that requires the controls to be shown.
695 const int minimumWidth
= 48;
696 for (MediaControlElement
* element
: elements
) {
700 if (element
->isWanted()) {
701 if (usedWidth
+ minimumWidth
<= m_panelWidth
) {
702 element
->setDoesFit(true);
703 usedWidth
+= minimumWidth
;
705 element
->setDoesFit(false);
706 if (element
== m_castButton
.get())
707 droppedCastButton
= true;
712 // Special case for cast: if we want a cast button but dropped it, then
713 // show the overlay cast button instead.
714 if (m_castButton
->isWanted())
715 m_overlayCastButton
->setIsWanted(droppedCastButton
);
718 void MediaControls::setAllowHiddenVolumeControls(bool allow
)
720 m_allowHiddenVolumeControls
= allow
;
721 // Update the controls visibility.
725 DEFINE_TRACE(MediaControls
)
727 visitor
->trace(m_mediaElement
);
728 visitor
->trace(m_panel
);
729 visitor
->trace(m_overlayPlayButton
);
730 visitor
->trace(m_overlayEnclosure
);
731 visitor
->trace(m_playButton
);
732 visitor
->trace(m_currentTimeDisplay
);
733 visitor
->trace(m_timeline
);
734 visitor
->trace(m_muteButton
);
735 visitor
->trace(m_volumeSlider
);
736 visitor
->trace(m_toggleClosedCaptionsButton
);
737 visitor
->trace(m_fullScreenButton
);
738 visitor
->trace(m_durationDisplay
);
739 visitor
->trace(m_enclosure
);
740 visitor
->trace(m_castButton
);
741 visitor
->trace(m_overlayCastButton
);
742 HTMLDivElement::trace(visitor
);