• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2011, 2012 Apple Inc. All rights reserved.
3  * Copyright (C) 2011, 2012 Google Inc. All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
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.
13  *
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.
25  */
26 
27 #include "config.h"
28 #include "core/html/shadow/MediaControls.h"
29 
30 #include "bindings/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"
36 
37 namespace WebCore {
38 
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;
42 
fullscreenIsSupported(const Document & document)43 static bool fullscreenIsSupported(const Document& document)
44 {
45     return !document.settings() || document.settings()->fullscreenSupported();
46 }
47 
deviceSupportsMouse(const Document & document)48 static bool deviceSupportsMouse(const Document& document)
49 {
50     return !document.settings() || document.settings()->deviceSupportsMouse();
51 }
52 
MediaControls(HTMLMediaElement & mediaElement)53 MediaControls::MediaControls(HTMLMediaElement& mediaElement)
54     : HTMLDivElement(mediaElement.document())
55     , m_mediaElement(&mediaElement)
56     , m_panel(nullptr)
57     , m_textDisplayContainer(nullptr)
58     , m_overlayPlayButton(nullptr)
59     , m_overlayEnclosure(nullptr)
60     , m_playButton(nullptr)
61     , m_currentTimeDisplay(nullptr)
62     , m_timeline(nullptr)
63     , m_muteButton(nullptr)
64     , m_volumeSlider(nullptr)
65     , m_toggleClosedCaptionsButton(nullptr)
66     , m_fullScreenButton(nullptr)
67     , m_durationDisplay(nullptr)
68     , m_enclosure(nullptr)
69     , m_hideMediaControlsTimer(this, &MediaControls::hideMediaControlsTimerFired)
70     , m_isMouseOverControls(false)
71     , m_isPausedForScrubbing(false)
72 {
73 }
74 
create(HTMLMediaElement & mediaElement)75 PassRefPtrWillBeRawPtr<MediaControls> MediaControls::create(HTMLMediaElement& mediaElement)
76 {
77     RefPtrWillBeRawPtr<MediaControls> controls = adoptRefWillBeNoop(new MediaControls(mediaElement));
78 
79     if (controls->initializeControls())
80         return controls.release();
81 
82     return nullptr;
83 }
84 
initializeControls()85 bool MediaControls::initializeControls()
86 {
87     TrackExceptionState exceptionState;
88 
89     if (document().settings() && document().settings()->mediaControlsOverlayPlayButtonEnabled()) {
90         RefPtrWillBeRawPtr<MediaControlOverlayEnclosureElement> overlayEnclosure = MediaControlOverlayEnclosureElement::create(*this);
91         RefPtrWillBeRawPtr<MediaControlOverlayPlayButtonElement> overlayPlayButton = MediaControlOverlayPlayButtonElement::create(*this);
92         m_overlayPlayButton = overlayPlayButton.get();
93         overlayEnclosure->appendChild(overlayPlayButton.release(), exceptionState);
94         if (exceptionState.hadException())
95             return false;
96 
97         m_overlayEnclosure = overlayEnclosure.get();
98         appendChild(overlayEnclosure.release(), exceptionState);
99         if (exceptionState.hadException())
100             return false;
101     }
102 
103     // Create an enclosing element for the panel so we can visually offset the controls correctly.
104     RefPtrWillBeRawPtr<MediaControlPanelEnclosureElement> enclosure = MediaControlPanelEnclosureElement::create(*this);
105 
106     RefPtrWillBeRawPtr<MediaControlPanelElement> panel = MediaControlPanelElement::create(*this);
107 
108     RefPtrWillBeRawPtr<MediaControlPlayButtonElement> playButton = MediaControlPlayButtonElement::create(*this);
109     m_playButton = playButton.get();
110     panel->appendChild(playButton.release(), exceptionState);
111     if (exceptionState.hadException())
112         return false;
113 
114     RefPtrWillBeRawPtr<MediaControlTimelineElement> timeline = MediaControlTimelineElement::create(*this);
115     m_timeline = timeline.get();
116     panel->appendChild(timeline.release(), exceptionState);
117     if (exceptionState.hadException())
118         return false;
119 
120     RefPtrWillBeRawPtr<MediaControlCurrentTimeDisplayElement> currentTimeDisplay = MediaControlCurrentTimeDisplayElement::create(*this);
121     m_currentTimeDisplay = currentTimeDisplay.get();
122     m_currentTimeDisplay->hide();
123     panel->appendChild(currentTimeDisplay.release(), exceptionState);
124     if (exceptionState.hadException())
125         return false;
126 
127     RefPtrWillBeRawPtr<MediaControlTimeRemainingDisplayElement> durationDisplay = MediaControlTimeRemainingDisplayElement::create(*this);
128     m_durationDisplay = durationDisplay.get();
129     panel->appendChild(durationDisplay.release(), exceptionState);
130     if (exceptionState.hadException())
131         return false;
132 
133     RefPtrWillBeRawPtr<MediaControlMuteButtonElement> muteButton = MediaControlMuteButtonElement::create(*this);
134     m_muteButton = muteButton.get();
135     panel->appendChild(muteButton.release(), exceptionState);
136     if (exceptionState.hadException())
137         return false;
138 
139     RefPtrWillBeRawPtr<MediaControlVolumeSliderElement> slider = MediaControlVolumeSliderElement::create(*this);
140     m_volumeSlider = slider.get();
141     panel->appendChild(slider.release(), exceptionState);
142     if (exceptionState.hadException())
143         return false;
144 
145     RefPtrWillBeRawPtr<MediaControlToggleClosedCaptionsButtonElement> toggleClosedCaptionsButton = MediaControlToggleClosedCaptionsButtonElement::create(*this);
146     m_toggleClosedCaptionsButton = toggleClosedCaptionsButton.get();
147     panel->appendChild(toggleClosedCaptionsButton.release(), exceptionState);
148     if (exceptionState.hadException())
149         return false;
150 
151     RefPtrWillBeRawPtr<MediaControlFullscreenButtonElement> fullscreenButton = MediaControlFullscreenButtonElement::create(*this);
152     m_fullScreenButton = fullscreenButton.get();
153     panel->appendChild(fullscreenButton.release(), exceptionState);
154     if (exceptionState.hadException())
155         return false;
156 
157     m_panel = panel.get();
158     enclosure->appendChild(panel.release(), exceptionState);
159     if (exceptionState.hadException())
160         return false;
161 
162     m_enclosure = enclosure.get();
163     appendChild(enclosure.release(), exceptionState);
164     if (exceptionState.hadException())
165         return false;
166 
167     return true;
168 }
169 
reset()170 void MediaControls::reset()
171 {
172     double duration = mediaElement().duration();
173     m_durationDisplay->setInnerText(RenderTheme::theme().formatMediaControlsTime(duration), ASSERT_NO_EXCEPTION);
174     m_durationDisplay->setCurrentValue(duration);
175 
176     updatePlayState();
177 
178     updateCurrentTimeDisplay();
179 
180     m_timeline->setDuration(duration);
181     m_timeline->setPosition(mediaElement().currentTime());
182 
183     if (!mediaElement().hasAudio())
184         m_volumeSlider->hide();
185     else
186         m_volumeSlider->show();
187     updateVolume();
188 
189     refreshClosedCaptionsButtonVisibility();
190 
191     if (mediaElement().hasVideo() && fullscreenIsSupported(document()))
192         m_fullScreenButton->show();
193     else
194         m_fullScreenButton->hide();
195 
196     makeOpaque();
197 }
198 
show()199 void MediaControls::show()
200 {
201     makeOpaque();
202     m_panel->setIsDisplayed(true);
203     m_panel->show();
204 }
205 
mediaElementFocused()206 void MediaControls::mediaElementFocused()
207 {
208     show();
209     stopHideMediaControlsTimer();
210 }
211 
hide()212 void MediaControls::hide()
213 {
214     m_panel->setIsDisplayed(false);
215     m_panel->hide();
216 }
217 
makeOpaque()218 void MediaControls::makeOpaque()
219 {
220     m_panel->makeOpaque();
221 }
222 
makeTransparent()223 void MediaControls::makeTransparent()
224 {
225     m_panel->makeTransparent();
226 }
227 
shouldHideMediaControls(unsigned behaviorFlags) const228 bool MediaControls::shouldHideMediaControls(unsigned behaviorFlags) const
229 {
230     // Never hide for a media element without visual representation.
231     if (!mediaElement().hasVideo())
232         return false;
233     // Don't hide if the mouse is over the controls.
234     const bool ignoreControlsHover = behaviorFlags & IgnoreControlsHover;
235     if (!ignoreControlsHover && m_panel->hovered())
236         return false;
237     // Don't hide if the mouse is over the video area.
238     const bool ignoreVideoHover = behaviorFlags & IgnoreVideoHover;
239     if (!ignoreVideoHover && m_isMouseOverControls)
240         return false;
241     // Don't hide if focus is on the HTMLMediaElement or within the
242     // controls/shadow tree. (Perform the checks separately to avoid going
243     // through all the potential ancestor hosts for the focused element.)
244     const bool ignoreFocus = behaviorFlags & IgnoreFocus;
245     if (!ignoreFocus && (mediaElement().focused() || contains(document().focusedElement())))
246         return false;
247     return true;
248 }
249 
playbackStarted()250 void MediaControls::playbackStarted()
251 {
252     m_currentTimeDisplay->show();
253     m_durationDisplay->hide();
254 
255     updatePlayState();
256     m_timeline->setPosition(mediaElement().currentTime());
257     updateCurrentTimeDisplay();
258 
259     startHideMediaControlsTimer();
260 }
261 
playbackProgressed()262 void MediaControls::playbackProgressed()
263 {
264     m_timeline->setPosition(mediaElement().currentTime());
265     updateCurrentTimeDisplay();
266 
267     if (shouldHideMediaControls())
268         makeTransparent();
269 }
270 
playbackStopped()271 void MediaControls::playbackStopped()
272 {
273     updatePlayState();
274     m_timeline->setPosition(mediaElement().currentTime());
275     updateCurrentTimeDisplay();
276     makeOpaque();
277 
278     stopHideMediaControlsTimer();
279 }
280 
updatePlayState()281 void MediaControls::updatePlayState()
282 {
283     if (m_isPausedForScrubbing)
284         return;
285 
286     if (m_overlayPlayButton)
287         m_overlayPlayButton->updateDisplayType();
288     m_playButton->updateDisplayType();
289 }
290 
beginScrubbing()291 void MediaControls::beginScrubbing()
292 {
293     if (!mediaElement().togglePlayStateWillPlay()) {
294         m_isPausedForScrubbing = true;
295         mediaElement().togglePlayState();
296     }
297 }
298 
endScrubbing()299 void MediaControls::endScrubbing()
300 {
301     if (m_isPausedForScrubbing) {
302         m_isPausedForScrubbing = false;
303         if (mediaElement().togglePlayStateWillPlay())
304             mediaElement().togglePlayState();
305     }
306 }
307 
updateCurrentTimeDisplay()308 void MediaControls::updateCurrentTimeDisplay()
309 {
310     double now = mediaElement().currentTime();
311     double duration = mediaElement().duration();
312 
313     // After seek, hide duration display and show current time.
314     if (now > 0) {
315         m_currentTimeDisplay->show();
316         m_durationDisplay->hide();
317     }
318 
319     // Allow the theme to format the time.
320     m_currentTimeDisplay->setInnerText(RenderTheme::theme().formatMediaControlsCurrentTime(now, duration), IGNORE_EXCEPTION);
321     m_currentTimeDisplay->setCurrentValue(now);
322 }
323 
updateVolume()324 void MediaControls::updateVolume()
325 {
326     m_muteButton->updateDisplayType();
327     if (m_muteButton->renderer())
328         m_muteButton->renderer()->paintInvalidationForWholeRenderer();
329 
330     if (mediaElement().muted())
331         m_volumeSlider->setVolume(0);
332     else
333         m_volumeSlider->setVolume(mediaElement().volume());
334 }
335 
changedClosedCaptionsVisibility()336 void MediaControls::changedClosedCaptionsVisibility()
337 {
338     m_toggleClosedCaptionsButton->updateDisplayType();
339 }
340 
refreshClosedCaptionsButtonVisibility()341 void MediaControls::refreshClosedCaptionsButtonVisibility()
342 {
343     if (mediaElement().hasClosedCaptions())
344         m_toggleClosedCaptionsButton->show();
345     else
346         m_toggleClosedCaptionsButton->hide();
347 }
348 
closedCaptionTracksChanged()349 void MediaControls::closedCaptionTracksChanged()
350 {
351     refreshClosedCaptionsButtonVisibility();
352 }
353 
enteredFullscreen()354 void MediaControls::enteredFullscreen()
355 {
356     m_fullScreenButton->setIsFullscreen(true);
357     stopHideMediaControlsTimer();
358     startHideMediaControlsTimer();
359 }
360 
exitedFullscreen()361 void MediaControls::exitedFullscreen()
362 {
363     m_fullScreenButton->setIsFullscreen(false);
364     stopHideMediaControlsTimer();
365     startHideMediaControlsTimer();
366 }
367 
defaultEventHandler(Event * event)368 void MediaControls::defaultEventHandler(Event* event)
369 {
370     HTMLDivElement::defaultEventHandler(event);
371 
372     if (event->type() == EventTypeNames::mouseover) {
373         if (!containsRelatedTarget(event)) {
374             m_isMouseOverControls = true;
375             if (!mediaElement().togglePlayStateWillPlay()) {
376                 makeOpaque();
377                 if (shouldHideMediaControls())
378                     startHideMediaControlsTimer();
379             }
380         }
381         return;
382     }
383 
384     if (event->type() == EventTypeNames::mouseout) {
385         if (!containsRelatedTarget(event)) {
386             m_isMouseOverControls = false;
387             stopHideMediaControlsTimer();
388         }
389         return;
390     }
391 
392     if (event->type() == EventTypeNames::mousemove) {
393         // When we get a mouse move, show the media controls, and start a timer
394         // that will hide the media controls after a 3 seconds without a mouse move.
395         makeOpaque();
396         if (shouldHideMediaControls(IgnoreVideoHover))
397             startHideMediaControlsTimer();
398         return;
399     }
400 }
401 
hideMediaControlsTimerFired(Timer<MediaControls> *)402 void MediaControls::hideMediaControlsTimerFired(Timer<MediaControls>*)
403 {
404     if (mediaElement().togglePlayStateWillPlay())
405         return;
406 
407     unsigned behaviorFlags = IgnoreFocus | IgnoreVideoHover;
408     // FIXME: improve this check, see http://www.crbug.com/401177.
409     if (!deviceSupportsMouse(document())) {
410         behaviorFlags |= IgnoreControlsHover;
411     }
412     if (!shouldHideMediaControls(behaviorFlags))
413         return;
414 
415     makeTransparent();
416 }
417 
startHideMediaControlsTimer()418 void MediaControls::startHideMediaControlsTimer()
419 {
420     m_hideMediaControlsTimer.startOneShot(timeWithoutMouseMovementBeforeHidingMediaControls, FROM_HERE);
421 }
422 
stopHideMediaControlsTimer()423 void MediaControls::stopHideMediaControlsTimer()
424 {
425     m_hideMediaControlsTimer.stop();
426 }
427 
shadowPseudoId() const428 const AtomicString& MediaControls::shadowPseudoId() const
429 {
430     DEFINE_STATIC_LOCAL(AtomicString, id, ("-webkit-media-controls"));
431     return id;
432 }
433 
containsRelatedTarget(Event * event)434 bool MediaControls::containsRelatedTarget(Event* event)
435 {
436     if (!event->isMouseEvent())
437         return false;
438     EventTarget* relatedTarget = toMouseEvent(event)->relatedTarget();
439     if (!relatedTarget)
440         return false;
441     return contains(relatedTarget->toNode());
442 }
443 
createTextTrackDisplay()444 void MediaControls::createTextTrackDisplay()
445 {
446     if (m_textDisplayContainer)
447         return;
448 
449     RefPtrWillBeRawPtr<MediaControlTextTrackContainerElement> textDisplayContainer = MediaControlTextTrackContainerElement::create(*this);
450     m_textDisplayContainer = textDisplayContainer.get();
451 
452     // Insert it before (behind) all other control elements.
453     if (m_overlayEnclosure && m_overlayPlayButton)
454         m_overlayEnclosure->insertBefore(textDisplayContainer.release(), m_overlayPlayButton);
455     else
456         insertBefore(textDisplayContainer.release(), m_enclosure);
457 }
458 
showTextTrackDisplay()459 void MediaControls::showTextTrackDisplay()
460 {
461     if (!m_textDisplayContainer)
462         createTextTrackDisplay();
463     m_textDisplayContainer->show();
464 }
465 
hideTextTrackDisplay()466 void MediaControls::hideTextTrackDisplay()
467 {
468     if (!m_textDisplayContainer)
469         createTextTrackDisplay();
470     m_textDisplayContainer->hide();
471 }
472 
updateTextTrackDisplay()473 void MediaControls::updateTextTrackDisplay()
474 {
475     if (!m_textDisplayContainer)
476         createTextTrackDisplay();
477 
478     m_textDisplayContainer->updateDisplay();
479 }
480 
trace(Visitor * visitor)481 void MediaControls::trace(Visitor* visitor)
482 {
483     visitor->trace(m_mediaElement);
484     visitor->trace(m_panel);
485     visitor->trace(m_textDisplayContainer);
486     visitor->trace(m_overlayPlayButton);
487     visitor->trace(m_overlayEnclosure);
488     visitor->trace(m_playButton);
489     visitor->trace(m_currentTimeDisplay);
490     visitor->trace(m_timeline);
491     visitor->trace(m_muteButton);
492     visitor->trace(m_volumeSlider);
493     visitor->trace(m_toggleClosedCaptionsButton);
494     visitor->trace(m_fullScreenButton);
495     visitor->trace(m_durationDisplay);
496     visitor->trace(m_enclosure);
497     HTMLDivElement::trace(visitor);
498 }
499 
500 }
501