• 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/core/v8/ExceptionStatePlaceholder.h"
31 #include "core/dom/ClientRect.h"
32 #include "core/events/MouseEvent.h"
33 #include "core/frame/Settings.h"
34 #include "core/html/HTMLMediaElement.h"
35 #include "core/html/MediaController.h"
36 #include "core/rendering/RenderTheme.h"
37 
38 namespace blink {
39 
40 // If you change this value, then also update the corresponding value in
41 // LayoutTests/media/media-controls.js.
42 static const double timeWithoutMouseMovementBeforeHidingMediaControls = 3;
43 
fullscreenIsSupported(const Document & document)44 static bool fullscreenIsSupported(const Document& document)
45 {
46     return !document.settings() || document.settings()->fullscreenSupported();
47 }
48 
MediaControls(HTMLMediaElement & mediaElement)49 MediaControls::MediaControls(HTMLMediaElement& mediaElement)
50     : HTMLDivElement(mediaElement.document())
51     , m_mediaElement(&mediaElement)
52     , m_panel(nullptr)
53     , m_textDisplayContainer(nullptr)
54     , m_overlayPlayButton(nullptr)
55     , m_overlayEnclosure(nullptr)
56     , m_playButton(nullptr)
57     , m_currentTimeDisplay(nullptr)
58     , m_timeline(nullptr)
59     , m_muteButton(nullptr)
60     , m_volumeSlider(nullptr)
61     , m_toggleClosedCaptionsButton(nullptr)
62     , m_fullScreenButton(nullptr)
63     , m_castButton(nullptr)
64     , m_overlayCastButton(nullptr)
65     , m_durationDisplay(nullptr)
66     , m_enclosure(nullptr)
67     , m_hideMediaControlsTimer(this, &MediaControls::hideMediaControlsTimerFired)
68     , m_isMouseOverControls(false)
69     , m_isPausedForScrubbing(false)
70     , m_wasLastEventTouch(false)
71 {
72 }
73 
create(HTMLMediaElement & mediaElement)74 PassRefPtrWillBeRawPtr<MediaControls> MediaControls::create(HTMLMediaElement& mediaElement)
75 {
76     RefPtrWillBeRawPtr<MediaControls> controls = adoptRefWillBeNoop(new MediaControls(mediaElement));
77 
78     if (controls->initializeControls())
79         return controls.release();
80 
81     return nullptr;
82 }
83 
initializeControls()84 bool MediaControls::initializeControls()
85 {
86     TrackExceptionState exceptionState;
87 
88     RefPtrWillBeRawPtr<MediaControlOverlayEnclosureElement> overlayEnclosure = MediaControlOverlayEnclosureElement::create(*this);
89 
90     if (document().settings() && document().settings()->mediaControlsOverlayPlayButtonEnabled()) {
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 
98     RefPtrWillBeRawPtr<MediaControlCastButtonElement> overlayCastButton = MediaControlCastButtonElement::create(*this, true);
99     m_overlayCastButton = overlayCastButton.get();
100     overlayEnclosure->appendChild(overlayCastButton.release(), exceptionState);
101     if (exceptionState.hadException())
102         return false;
103 
104     m_overlayEnclosure = overlayEnclosure.get();
105     appendChild(overlayEnclosure.release(), exceptionState);
106     if (exceptionState.hadException())
107         return false;
108 
109     // Create an enclosing element for the panel so we can visually offset the controls correctly.
110     RefPtrWillBeRawPtr<MediaControlPanelEnclosureElement> enclosure = MediaControlPanelEnclosureElement::create(*this);
111 
112     RefPtrWillBeRawPtr<MediaControlPanelElement> panel = MediaControlPanelElement::create(*this);
113 
114     RefPtrWillBeRawPtr<MediaControlPlayButtonElement> playButton = MediaControlPlayButtonElement::create(*this);
115     m_playButton = playButton.get();
116     panel->appendChild(playButton.release(), exceptionState);
117     if (exceptionState.hadException())
118         return false;
119 
120     RefPtrWillBeRawPtr<MediaControlTimelineElement> timeline = MediaControlTimelineElement::create(*this);
121     m_timeline = timeline.get();
122     panel->appendChild(timeline.release(), exceptionState);
123     if (exceptionState.hadException())
124         return false;
125 
126     RefPtrWillBeRawPtr<MediaControlCurrentTimeDisplayElement> currentTimeDisplay = MediaControlCurrentTimeDisplayElement::create(*this);
127     m_currentTimeDisplay = currentTimeDisplay.get();
128     m_currentTimeDisplay->hide();
129     panel->appendChild(currentTimeDisplay.release(), exceptionState);
130     if (exceptionState.hadException())
131         return false;
132 
133     RefPtrWillBeRawPtr<MediaControlTimeRemainingDisplayElement> durationDisplay = MediaControlTimeRemainingDisplayElement::create(*this);
134     m_durationDisplay = durationDisplay.get();
135     panel->appendChild(durationDisplay.release(), exceptionState);
136     if (exceptionState.hadException())
137         return false;
138 
139     RefPtrWillBeRawPtr<MediaControlMuteButtonElement> muteButton = MediaControlMuteButtonElement::create(*this);
140     m_muteButton = muteButton.get();
141     panel->appendChild(muteButton.release(), exceptionState);
142     if (exceptionState.hadException())
143         return false;
144 
145     RefPtrWillBeRawPtr<MediaControlVolumeSliderElement> slider = MediaControlVolumeSliderElement::create(*this);
146     m_volumeSlider = slider.get();
147     panel->appendChild(slider.release(), exceptionState);
148     if (exceptionState.hadException())
149         return false;
150 
151     RefPtrWillBeRawPtr<MediaControlToggleClosedCaptionsButtonElement> toggleClosedCaptionsButton = MediaControlToggleClosedCaptionsButtonElement::create(*this);
152     m_toggleClosedCaptionsButton = toggleClosedCaptionsButton.get();
153     panel->appendChild(toggleClosedCaptionsButton.release(), exceptionState);
154     if (exceptionState.hadException())
155         return false;
156 
157     RefPtrWillBeRawPtr<MediaControlCastButtonElement> castButton = MediaControlCastButtonElement::create(*this, false);
158     m_castButton = castButton.get();
159     panel->appendChild(castButton.release(), exceptionState);
160     if (exceptionState.hadException())
161         return false;
162 
163     RefPtrWillBeRawPtr<MediaControlFullscreenButtonElement> fullscreenButton = MediaControlFullscreenButtonElement::create(*this);
164     m_fullScreenButton = fullscreenButton.get();
165     panel->appendChild(fullscreenButton.release(), exceptionState);
166     if (exceptionState.hadException())
167         return false;
168 
169     m_panel = panel.get();
170     enclosure->appendChild(panel.release(), exceptionState);
171     if (exceptionState.hadException())
172         return false;
173 
174     m_enclosure = enclosure.get();
175     appendChild(enclosure.release(), exceptionState);
176     if (exceptionState.hadException())
177         return false;
178 
179     return true;
180 }
181 
reset()182 void MediaControls::reset()
183 {
184     double duration = mediaElement().duration();
185     m_durationDisplay->setInnerText(RenderTheme::theme().formatMediaControlsTime(duration), ASSERT_NO_EXCEPTION);
186     m_durationDisplay->setCurrentValue(duration);
187 
188     updatePlayState();
189 
190     updateCurrentTimeDisplay();
191 
192     m_timeline->setDuration(duration);
193     m_timeline->setPosition(mediaElement().currentTime());
194 
195     if (!mediaElement().hasAudio())
196         m_volumeSlider->hide();
197     else
198         m_volumeSlider->show();
199     updateVolume();
200 
201     refreshClosedCaptionsButtonVisibility();
202 
203     if (mediaElement().hasVideo() && fullscreenIsSupported(document()))
204         m_fullScreenButton->show();
205     else
206         m_fullScreenButton->hide();
207 
208     refreshCastButtonVisibility();
209     makeOpaque();
210 }
211 
show()212 void MediaControls::show()
213 {
214     makeOpaque();
215     m_panel->setIsDisplayed(true);
216     m_panel->show();
217     if (m_overlayPlayButton)
218         m_overlayPlayButton->updateDisplayType();
219 }
220 
mediaElementFocused()221 void MediaControls::mediaElementFocused()
222 {
223     show();
224     resetHideMediaControlsTimer();
225 }
226 
hide()227 void MediaControls::hide()
228 {
229     m_panel->setIsDisplayed(false);
230     m_panel->hide();
231     if (m_overlayPlayButton)
232         m_overlayPlayButton->hide();
233 }
234 
makeOpaque()235 void MediaControls::makeOpaque()
236 {
237     m_panel->makeOpaque();
238 }
239 
makeTransparent()240 void MediaControls::makeTransparent()
241 {
242     m_panel->makeTransparent();
243 }
244 
shouldHideMediaControls(unsigned behaviorFlags) const245 bool MediaControls::shouldHideMediaControls(unsigned behaviorFlags) const
246 {
247     // Never hide for a media element without visual representation.
248     if (!mediaElement().hasVideo() || mediaElement().isPlayingRemotely())
249         return false;
250     // Don't hide if the mouse is over the controls.
251     const bool ignoreControlsHover = behaviorFlags & IgnoreControlsHover;
252     if (!ignoreControlsHover && m_panel->hovered())
253         return false;
254     // Don't hide if the mouse is over the video area.
255     const bool ignoreVideoHover = behaviorFlags & IgnoreVideoHover;
256     if (!ignoreVideoHover && m_isMouseOverControls)
257         return false;
258     // Don't hide if focus is on the HTMLMediaElement or within the
259     // controls/shadow tree. (Perform the checks separately to avoid going
260     // through all the potential ancestor hosts for the focused element.)
261     const bool ignoreFocus = behaviorFlags & IgnoreFocus;
262     if (!ignoreFocus && (mediaElement().focused() || contains(document().focusedElement())))
263         return false;
264     return true;
265 }
266 
playbackStarted()267 void MediaControls::playbackStarted()
268 {
269     m_currentTimeDisplay->show();
270     m_durationDisplay->hide();
271 
272     updatePlayState();
273     m_timeline->setPosition(mediaElement().currentTime());
274     updateCurrentTimeDisplay();
275 
276     startHideMediaControlsTimer();
277 }
278 
playbackProgressed()279 void MediaControls::playbackProgressed()
280 {
281     m_timeline->setPosition(mediaElement().currentTime());
282     updateCurrentTimeDisplay();
283 
284     if (shouldHideMediaControls())
285         makeTransparent();
286 }
287 
playbackStopped()288 void MediaControls::playbackStopped()
289 {
290     updatePlayState();
291     m_timeline->setPosition(mediaElement().currentTime());
292     updateCurrentTimeDisplay();
293     makeOpaque();
294 
295     stopHideMediaControlsTimer();
296 }
297 
updatePlayState()298 void MediaControls::updatePlayState()
299 {
300     if (m_isPausedForScrubbing)
301         return;
302 
303     if (m_overlayPlayButton)
304         m_overlayPlayButton->updateDisplayType();
305     m_playButton->updateDisplayType();
306 }
307 
beginScrubbing()308 void MediaControls::beginScrubbing()
309 {
310     if (!mediaElement().togglePlayStateWillPlay()) {
311         m_isPausedForScrubbing = true;
312         mediaElement().togglePlayState();
313     }
314 }
315 
endScrubbing()316 void MediaControls::endScrubbing()
317 {
318     if (m_isPausedForScrubbing) {
319         m_isPausedForScrubbing = false;
320         if (mediaElement().togglePlayStateWillPlay())
321             mediaElement().togglePlayState();
322     }
323 }
324 
updateCurrentTimeDisplay()325 void MediaControls::updateCurrentTimeDisplay()
326 {
327     double now = mediaElement().currentTime();
328     double duration = mediaElement().duration();
329 
330     // After seek, hide duration display and show current time.
331     if (now > 0) {
332         m_currentTimeDisplay->show();
333         m_durationDisplay->hide();
334     }
335 
336     // Allow the theme to format the time.
337     m_currentTimeDisplay->setInnerText(RenderTheme::theme().formatMediaControlsCurrentTime(now, duration), IGNORE_EXCEPTION);
338     m_currentTimeDisplay->setCurrentValue(now);
339 }
340 
updateVolume()341 void MediaControls::updateVolume()
342 {
343     m_muteButton->updateDisplayType();
344     if (m_muteButton->renderer())
345         m_muteButton->renderer()->setShouldDoFullPaintInvalidation(true);
346 
347     if (mediaElement().muted())
348         m_volumeSlider->setVolume(0);
349     else
350         m_volumeSlider->setVolume(mediaElement().volume());
351 }
352 
changedClosedCaptionsVisibility()353 void MediaControls::changedClosedCaptionsVisibility()
354 {
355     m_toggleClosedCaptionsButton->updateDisplayType();
356 }
357 
refreshClosedCaptionsButtonVisibility()358 void MediaControls::refreshClosedCaptionsButtonVisibility()
359 {
360     if (mediaElement().hasClosedCaptions())
361         m_toggleClosedCaptionsButton->show();
362     else
363         m_toggleClosedCaptionsButton->hide();
364 }
365 
textTracksChanged()366 void MediaControls::textTracksChanged()
367 {
368     refreshClosedCaptionsButtonVisibility();
369 }
370 
refreshCastButtonVisibility()371 void MediaControls::refreshCastButtonVisibility()
372 {
373     if (mediaElement().hasRemoteRoutes()) {
374         // The reason for the autoplay test is that some pages (e.g. vimeo.com) have an autoplay background video, which
375         // doesn't autoplay on Chrome for Android (we prevent it) so starts paused. In such cases we don't want to automatically
376         // show the cast button, since it looks strange and is unlikely to correspond with anything the user wants to do.
377         // If a user does want to cast a paused autoplay video then they can still do so by touching or clicking on the
378         // video, which will cause the cast button to appear.
379         if (!mediaElement().shouldShowControls() && !mediaElement().autoplay() && mediaElement().paused()) {
380             showOverlayCastButton();
381         } else if (mediaElement().shouldShowControls()) {
382             m_overlayCastButton->hide();
383             m_castButton->show();
384             // Check that the cast button actually fits on the bar.
385             if (m_fullScreenButton->getBoundingClientRect()->right() > m_panel->getBoundingClientRect()->right()) {
386                 m_castButton->hide();
387                 m_overlayCastButton->show();
388             }
389         }
390     } else {
391         m_castButton->hide();
392         m_overlayCastButton->hide();
393     }
394 }
395 
showOverlayCastButton()396 void MediaControls::showOverlayCastButton()
397 {
398     m_overlayCastButton->show();
399     resetHideMediaControlsTimer();
400 }
401 
enteredFullscreen()402 void MediaControls::enteredFullscreen()
403 {
404     m_fullScreenButton->setIsFullscreen(true);
405     stopHideMediaControlsTimer();
406     startHideMediaControlsTimer();
407 }
408 
exitedFullscreen()409 void MediaControls::exitedFullscreen()
410 {
411     m_fullScreenButton->setIsFullscreen(false);
412     stopHideMediaControlsTimer();
413     startHideMediaControlsTimer();
414 }
415 
startedCasting()416 void MediaControls::startedCasting()
417 {
418     m_castButton->setIsPlayingRemotely(true);
419     m_overlayCastButton->setIsPlayingRemotely(true);
420 }
421 
stoppedCasting()422 void MediaControls::stoppedCasting()
423 {
424     m_castButton->setIsPlayingRemotely(false);
425     m_overlayCastButton->setIsPlayingRemotely(false);
426 }
427 
defaultEventHandler(Event * event)428 void MediaControls::defaultEventHandler(Event* event)
429 {
430     HTMLDivElement::defaultEventHandler(event);
431     m_wasLastEventTouch = event->isTouchEvent() || event->isGestureEvent()
432         || (event->isMouseEvent() && toMouseEvent(event)->fromTouch());
433 
434     if (event->type() == EventTypeNames::mouseover) {
435         if (!containsRelatedTarget(event)) {
436             m_isMouseOverControls = true;
437             if (!mediaElement().togglePlayStateWillPlay()) {
438                 makeOpaque();
439                 if (shouldHideMediaControls())
440                     startHideMediaControlsTimer();
441             }
442         }
443         return;
444     }
445 
446     if (event->type() == EventTypeNames::mouseout) {
447         if (!containsRelatedTarget(event)) {
448             m_isMouseOverControls = false;
449             stopHideMediaControlsTimer();
450         }
451         return;
452     }
453 
454     if (event->type() == EventTypeNames::mousemove) {
455         // When we get a mouse move, show the media controls, and start a timer
456         // that will hide the media controls after a 3 seconds without a mouse move.
457         makeOpaque();
458         refreshCastButtonVisibility();
459         if (shouldHideMediaControls(IgnoreVideoHover))
460             startHideMediaControlsTimer();
461         return;
462     }
463 }
464 
hideMediaControlsTimerFired(Timer<MediaControls> *)465 void MediaControls::hideMediaControlsTimerFired(Timer<MediaControls>*)
466 {
467     if (mediaElement().togglePlayStateWillPlay())
468         return;
469 
470     unsigned behaviorFlags = IgnoreFocus | IgnoreVideoHover;
471     if (m_wasLastEventTouch) {
472         behaviorFlags |= IgnoreControlsHover;
473     }
474     if (!shouldHideMediaControls(behaviorFlags))
475         return;
476 
477     makeTransparent();
478     m_overlayCastButton->hide();
479 }
480 
startHideMediaControlsTimer()481 void MediaControls::startHideMediaControlsTimer()
482 {
483     m_hideMediaControlsTimer.startOneShot(timeWithoutMouseMovementBeforeHidingMediaControls, FROM_HERE);
484 }
485 
stopHideMediaControlsTimer()486 void MediaControls::stopHideMediaControlsTimer()
487 {
488     m_hideMediaControlsTimer.stop();
489 }
490 
resetHideMediaControlsTimer()491 void MediaControls::resetHideMediaControlsTimer()
492 {
493     stopHideMediaControlsTimer();
494     if (!mediaElement().paused())
495         startHideMediaControlsTimer();
496 }
497 
498 
shadowPseudoId() const499 const AtomicString& MediaControls::shadowPseudoId() const
500 {
501     DEFINE_STATIC_LOCAL(AtomicString, id, ("-webkit-media-controls", AtomicString::ConstructFromLiteral));
502     return id;
503 }
504 
containsRelatedTarget(Event * event)505 bool MediaControls::containsRelatedTarget(Event* event)
506 {
507     if (!event->isMouseEvent())
508         return false;
509     EventTarget* relatedTarget = toMouseEvent(event)->relatedTarget();
510     if (!relatedTarget)
511         return false;
512     return contains(relatedTarget->toNode());
513 }
514 
createTextTrackDisplay()515 void MediaControls::createTextTrackDisplay()
516 {
517     if (m_textDisplayContainer)
518         return;
519 
520     RefPtrWillBeRawPtr<MediaControlTextTrackContainerElement> textDisplayContainer = MediaControlTextTrackContainerElement::create(*this);
521     m_textDisplayContainer = textDisplayContainer.get();
522 
523     // Insert it before (behind) all other control elements.
524     if (m_overlayPlayButton)
525         m_overlayEnclosure->insertBefore(textDisplayContainer.release(), m_overlayPlayButton);
526     else
527         m_overlayEnclosure->insertBefore(textDisplayContainer.release(), m_overlayCastButton);
528 }
529 
showTextTrackDisplay()530 void MediaControls::showTextTrackDisplay()
531 {
532     if (!m_textDisplayContainer)
533         createTextTrackDisplay();
534     m_textDisplayContainer->show();
535 }
536 
hideTextTrackDisplay()537 void MediaControls::hideTextTrackDisplay()
538 {
539     if (!m_textDisplayContainer)
540         createTextTrackDisplay();
541     m_textDisplayContainer->hide();
542 }
543 
updateTextTrackDisplay()544 void MediaControls::updateTextTrackDisplay()
545 {
546     if (!m_textDisplayContainer)
547         createTextTrackDisplay();
548 
549     m_textDisplayContainer->updateDisplay();
550 }
551 
trace(Visitor * visitor)552 void MediaControls::trace(Visitor* visitor)
553 {
554     visitor->trace(m_mediaElement);
555     visitor->trace(m_panel);
556     visitor->trace(m_textDisplayContainer);
557     visitor->trace(m_overlayPlayButton);
558     visitor->trace(m_overlayEnclosure);
559     visitor->trace(m_playButton);
560     visitor->trace(m_currentTimeDisplay);
561     visitor->trace(m_timeline);
562     visitor->trace(m_muteButton);
563     visitor->trace(m_volumeSlider);
564     visitor->trace(m_toggleClosedCaptionsButton);
565     visitor->trace(m_fullScreenButton);
566     visitor->trace(m_durationDisplay);
567     visitor->trace(m_enclosure);
568     visitor->trace(m_castButton);
569     visitor->trace(m_overlayCastButton);
570     HTMLDivElement::trace(visitor);
571 }
572 
573 }
574