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/events/MouseEvent.h"
32 #include "core/frame/Settings.h"
33 #include "core/html/HTMLMediaElement.h"
34 #include "core/html/MediaController.h"
35 #include "core/rendering/RenderTheme.h"
39 // If you change this value, then also update the corresponding value in
40 // LayoutTests/media/media-controls.js.
41 static const double timeWithoutMouseMovementBeforeHidingMediaControls = 3;
43 static bool fullscreenIsSupported(const Document& document)
45 return !document.settings() || document.settings()->fullscreenSupported();
48 MediaControls::MediaControls(HTMLMediaElement& mediaElement)
49 : HTMLDivElement(mediaElement.document())
50 , m_mediaElement(&mediaElement)
52 , m_textDisplayContainer(nullptr)
53 , m_overlayPlayButton(nullptr)
54 , m_overlayEnclosure(nullptr)
55 , m_playButton(nullptr)
56 , m_currentTimeDisplay(nullptr)
58 , m_muteButton(nullptr)
59 , m_volumeSlider(nullptr)
60 , m_toggleClosedCaptionsButton(nullptr)
61 , m_fullScreenButton(nullptr)
62 , m_durationDisplay(nullptr)
63 , m_enclosure(nullptr)
64 , m_hideMediaControlsTimer(this, &MediaControls::hideMediaControlsTimerFired)
65 , m_isMouseOverControls(false)
66 , m_isPausedForScrubbing(false)
70 PassRefPtrWillBeRawPtr<MediaControls> MediaControls::create(HTMLMediaElement& mediaElement)
72 RefPtrWillBeRawPtr<MediaControls> controls = adoptRefWillBeNoop(new MediaControls(mediaElement));
74 if (controls->initializeControls())
75 return controls.release();
80 bool MediaControls::initializeControls()
82 TrackExceptionState exceptionState;
84 if (document().settings() && document().settings()->mediaControlsOverlayPlayButtonEnabled()) {
85 RefPtrWillBeRawPtr<MediaControlOverlayEnclosureElement> overlayEnclosure = MediaControlOverlayEnclosureElement::create(*this);
86 RefPtrWillBeRawPtr<MediaControlOverlayPlayButtonElement> overlayPlayButton = MediaControlOverlayPlayButtonElement::create(*this);
87 m_overlayPlayButton = overlayPlayButton.get();
88 overlayEnclosure->appendChild(overlayPlayButton.release(), exceptionState);
89 if (exceptionState.hadException())
92 m_overlayEnclosure = overlayEnclosure.get();
93 appendChild(overlayEnclosure.release(), exceptionState);
94 if (exceptionState.hadException())
98 // Create an enclosing element for the panel so we can visually offset the controls correctly.
99 RefPtrWillBeRawPtr<MediaControlPanelEnclosureElement> enclosure = MediaControlPanelEnclosureElement::create(*this);
101 RefPtrWillBeRawPtr<MediaControlPanelElement> panel = MediaControlPanelElement::create(*this);
103 RefPtrWillBeRawPtr<MediaControlPlayButtonElement> playButton = MediaControlPlayButtonElement::create(*this);
104 m_playButton = playButton.get();
105 panel->appendChild(playButton.release(), exceptionState);
106 if (exceptionState.hadException())
109 RefPtrWillBeRawPtr<MediaControlTimelineElement> timeline = MediaControlTimelineElement::create(*this);
110 m_timeline = timeline.get();
111 panel->appendChild(timeline.release(), exceptionState);
112 if (exceptionState.hadException())
115 RefPtrWillBeRawPtr<MediaControlCurrentTimeDisplayElement> currentTimeDisplay = MediaControlCurrentTimeDisplayElement::create(*this);
116 m_currentTimeDisplay = currentTimeDisplay.get();
117 m_currentTimeDisplay->hide();
118 panel->appendChild(currentTimeDisplay.release(), exceptionState);
119 if (exceptionState.hadException())
122 RefPtrWillBeRawPtr<MediaControlTimeRemainingDisplayElement> durationDisplay = MediaControlTimeRemainingDisplayElement::create(*this);
123 m_durationDisplay = durationDisplay.get();
124 panel->appendChild(durationDisplay.release(), exceptionState);
125 if (exceptionState.hadException())
128 RefPtrWillBeRawPtr<MediaControlMuteButtonElement> muteButton = MediaControlMuteButtonElement::create(*this);
129 m_muteButton = muteButton.get();
130 panel->appendChild(muteButton.release(), exceptionState);
131 if (exceptionState.hadException())
134 RefPtrWillBeRawPtr<MediaControlVolumeSliderElement> slider = MediaControlVolumeSliderElement::create(*this);
135 m_volumeSlider = slider.get();
136 panel->appendChild(slider.release(), exceptionState);
137 if (exceptionState.hadException())
140 RefPtrWillBeRawPtr<MediaControlToggleClosedCaptionsButtonElement> toggleClosedCaptionsButton = MediaControlToggleClosedCaptionsButtonElement::create(*this);
141 m_toggleClosedCaptionsButton = toggleClosedCaptionsButton.get();
142 panel->appendChild(toggleClosedCaptionsButton.release(), exceptionState);
143 if (exceptionState.hadException())
146 RefPtrWillBeRawPtr<MediaControlFullscreenButtonElement> fullscreenButton = MediaControlFullscreenButtonElement::create(*this);
147 m_fullScreenButton = fullscreenButton.get();
148 panel->appendChild(fullscreenButton.release(), exceptionState);
149 if (exceptionState.hadException())
152 m_panel = panel.get();
153 enclosure->appendChild(panel.release(), exceptionState);
154 if (exceptionState.hadException())
157 m_enclosure = enclosure.get();
158 appendChild(enclosure.release(), exceptionState);
159 if (exceptionState.hadException())
165 void MediaControls::reset()
167 double duration = mediaElement().duration();
168 m_durationDisplay->setInnerText(RenderTheme::theme().formatMediaControlsTime(duration), ASSERT_NO_EXCEPTION);
169 m_durationDisplay->setCurrentValue(duration);
173 updateCurrentTimeDisplay();
175 m_timeline->setDuration(duration);
176 m_timeline->setPosition(mediaElement().currentTime());
178 if (!mediaElement().hasAudio())
179 m_volumeSlider->hide();
181 m_volumeSlider->show();
184 refreshClosedCaptionsButtonVisibility();
186 if (mediaElement().hasVideo() && fullscreenIsSupported(document()))
187 m_fullScreenButton->show();
189 m_fullScreenButton->hide();
194 void MediaControls::show()
197 m_panel->setIsDisplayed(true);
199 if (m_overlayPlayButton)
200 m_overlayPlayButton->updateDisplayType();
203 void MediaControls::mediaElementFocused()
206 stopHideMediaControlsTimer();
209 void MediaControls::hide()
211 m_panel->setIsDisplayed(false);
213 if (m_overlayPlayButton)
214 m_overlayPlayButton->hide();
217 void MediaControls::makeOpaque()
219 m_panel->makeOpaque();
222 void MediaControls::makeTransparent()
224 m_panel->makeTransparent();
227 bool MediaControls::shouldHideMediaControls(unsigned behaviorFlags) const
229 // Never hide for a media element without visual representation.
230 if (!mediaElement().hasVideo())
232 // Don't hide if the controls are hovered or the mouse is over the video area.
233 const bool ignoreVideoHover = behaviorFlags & IgnoreVideoHover;
234 if (m_panel->hovered() || (!ignoreVideoHover && m_isMouseOverControls))
236 // Don't hide if focus is on the HTMLMediaElement or within the
237 // controls/shadow tree. (Perform the checks separately to avoid going
238 // through all the potential ancestor hosts for the focused element.)
239 const bool ignoreFocus = behaviorFlags & IgnoreFocus;
240 if (!ignoreFocus && (mediaElement().focused() || contains(document().focusedElement())))
245 void MediaControls::playbackStarted()
247 m_currentTimeDisplay->show();
248 m_durationDisplay->hide();
251 m_timeline->setPosition(mediaElement().currentTime());
252 updateCurrentTimeDisplay();
254 startHideMediaControlsTimer();
257 void MediaControls::playbackProgressed()
259 m_timeline->setPosition(mediaElement().currentTime());
260 updateCurrentTimeDisplay();
262 if (shouldHideMediaControls())
266 void MediaControls::playbackStopped()
269 m_timeline->setPosition(mediaElement().currentTime());
270 updateCurrentTimeDisplay();
273 stopHideMediaControlsTimer();
276 void MediaControls::updatePlayState()
278 if (m_isPausedForScrubbing)
281 if (m_overlayPlayButton)
282 m_overlayPlayButton->updateDisplayType();
283 m_playButton->updateDisplayType();
286 void MediaControls::beginScrubbing()
288 if (!mediaElement().togglePlayStateWillPlay()) {
289 m_isPausedForScrubbing = true;
290 mediaElement().togglePlayState();
294 void MediaControls::endScrubbing()
296 if (m_isPausedForScrubbing) {
297 m_isPausedForScrubbing = false;
298 if (mediaElement().togglePlayStateWillPlay())
299 mediaElement().togglePlayState();
303 void MediaControls::updateCurrentTimeDisplay()
305 double now = mediaElement().currentTime();
306 double duration = mediaElement().duration();
308 // After seek, hide duration display and show current time.
310 m_currentTimeDisplay->show();
311 m_durationDisplay->hide();
314 // Allow the theme to format the time.
315 m_currentTimeDisplay->setInnerText(RenderTheme::theme().formatMediaControlsCurrentTime(now, duration), IGNORE_EXCEPTION);
316 m_currentTimeDisplay->setCurrentValue(now);
319 void MediaControls::updateVolume()
321 m_muteButton->updateDisplayType();
322 if (m_muteButton->renderer())
323 m_muteButton->renderer()->paintInvalidationForWholeRenderer();
325 if (mediaElement().muted())
326 m_volumeSlider->setVolume(0);
328 m_volumeSlider->setVolume(mediaElement().volume());
331 void MediaControls::changedClosedCaptionsVisibility()
333 m_toggleClosedCaptionsButton->updateDisplayType();
336 void MediaControls::refreshClosedCaptionsButtonVisibility()
338 if (mediaElement().hasClosedCaptions())
339 m_toggleClosedCaptionsButton->show();
341 m_toggleClosedCaptionsButton->hide();
344 void MediaControls::closedCaptionTracksChanged()
346 refreshClosedCaptionsButtonVisibility();
349 void MediaControls::enteredFullscreen()
351 m_fullScreenButton->setIsFullscreen(true);
352 stopHideMediaControlsTimer();
353 startHideMediaControlsTimer();
356 void MediaControls::exitedFullscreen()
358 m_fullScreenButton->setIsFullscreen(false);
359 stopHideMediaControlsTimer();
360 startHideMediaControlsTimer();
363 void MediaControls::defaultEventHandler(Event* event)
365 HTMLDivElement::defaultEventHandler(event);
367 if (event->type() == EventTypeNames::mouseover) {
368 if (!containsRelatedTarget(event)) {
369 m_isMouseOverControls = true;
370 if (!mediaElement().togglePlayStateWillPlay()) {
372 if (shouldHideMediaControls())
373 startHideMediaControlsTimer();
379 if (event->type() == EventTypeNames::mouseout) {
380 if (!containsRelatedTarget(event)) {
381 m_isMouseOverControls = false;
382 stopHideMediaControlsTimer();
387 if (event->type() == EventTypeNames::mousemove) {
388 // When we get a mouse move, show the media controls, and start a timer
389 // that will hide the media controls after a 3 seconds without a mouse move.
391 if (shouldHideMediaControls(IgnoreVideoHover))
392 startHideMediaControlsTimer();
397 void MediaControls::hideMediaControlsTimerFired(Timer<MediaControls>*)
399 if (mediaElement().togglePlayStateWillPlay())
402 if (!shouldHideMediaControls(IgnoreFocus | IgnoreVideoHover))
408 void MediaControls::startHideMediaControlsTimer()
410 m_hideMediaControlsTimer.startOneShot(timeWithoutMouseMovementBeforeHidingMediaControls, FROM_HERE);
413 void MediaControls::stopHideMediaControlsTimer()
415 m_hideMediaControlsTimer.stop();
418 const AtomicString& MediaControls::shadowPseudoId() const
420 DEFINE_STATIC_LOCAL(AtomicString, id, ("-webkit-media-controls"));
424 bool MediaControls::containsRelatedTarget(Event* event)
426 if (!event->isMouseEvent())
428 EventTarget* relatedTarget = toMouseEvent(event)->relatedTarget();
431 return contains(relatedTarget->toNode());
434 void MediaControls::createTextTrackDisplay()
436 if (m_textDisplayContainer)
439 RefPtrWillBeRawPtr<MediaControlTextTrackContainerElement> textDisplayContainer = MediaControlTextTrackContainerElement::create(*this);
440 m_textDisplayContainer = textDisplayContainer.get();
442 // Insert it before (behind) all other control elements.
443 if (m_overlayEnclosure && m_overlayPlayButton)
444 m_overlayEnclosure->insertBefore(textDisplayContainer.release(), m_overlayPlayButton);
446 insertBefore(textDisplayContainer.release(), m_enclosure);
449 void MediaControls::showTextTrackDisplay()
451 if (!m_textDisplayContainer)
452 createTextTrackDisplay();
453 m_textDisplayContainer->show();
456 void MediaControls::hideTextTrackDisplay()
458 if (!m_textDisplayContainer)
459 createTextTrackDisplay();
460 m_textDisplayContainer->hide();
463 void MediaControls::updateTextTrackDisplay()
465 if (!m_textDisplayContainer)
466 createTextTrackDisplay();
468 m_textDisplayContainer->updateDisplay();
471 void MediaControls::trace(Visitor* visitor)
473 visitor->trace(m_mediaElement);
474 visitor->trace(m_panel);
475 visitor->trace(m_textDisplayContainer);
476 visitor->trace(m_overlayPlayButton);
477 visitor->trace(m_overlayEnclosure);
478 visitor->trace(m_playButton);
479 visitor->trace(m_currentTimeDisplay);
480 visitor->trace(m_timeline);
481 visitor->trace(m_muteButton);
482 visitor->trace(m_volumeSlider);
483 visitor->trace(m_toggleClosedCaptionsButton);
484 visitor->trace(m_fullScreenButton);
485 visitor->trace(m_durationDisplay);
486 visitor->trace(m_enclosure);
487 HTMLDivElement::trace(visitor);