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