Home | History | Annotate | Download | only in shadow
      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 
     43 static bool fullscreenIsSupported(const Document& document)
     44 {
     45     return !document.settings() || document.settings()->fullscreenSupported();
     46 }
     47 
     48 static bool deviceSupportsMouse(const Document& document)
     49 {
     50     return !document.settings() || document.settings()->deviceSupportsMouse();
     51 }
     52 
     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 
     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 
     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 
    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 
    199 void MediaControls::show()
    200 {
    201     makeOpaque();
    202     m_panel->setIsDisplayed(true);
    203     m_panel->show();
    204 }
    205 
    206 void MediaControls::mediaElementFocused()
    207 {
    208     show();
    209     stopHideMediaControlsTimer();
    210 }
    211 
    212 void MediaControls::hide()
    213 {
    214     m_panel->setIsDisplayed(false);
    215     m_panel->hide();
    216 }
    217 
    218 void MediaControls::makeOpaque()
    219 {
    220     m_panel->makeOpaque();
    221 }
    222 
    223 void MediaControls::makeTransparent()
    224 {
    225     m_panel->makeTransparent();
    226 }
    227 
    228 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 
    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 
    262 void MediaControls::playbackProgressed()
    263 {
    264     m_timeline->setPosition(mediaElement().currentTime());
    265     updateCurrentTimeDisplay();
    266 
    267     if (shouldHideMediaControls())
    268         makeTransparent();
    269 }
    270 
    271 void MediaControls::playbackStopped()
    272 {
    273     updatePlayState();
    274     m_timeline->setPosition(mediaElement().currentTime());
    275     updateCurrentTimeDisplay();
    276     makeOpaque();
    277 
    278     stopHideMediaControlsTimer();
    279 }
    280 
    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 
    291 void MediaControls::beginScrubbing()
    292 {
    293     if (!mediaElement().togglePlayStateWillPlay()) {
    294         m_isPausedForScrubbing = true;
    295         mediaElement().togglePlayState();
    296     }
    297 }
    298 
    299 void MediaControls::endScrubbing()
    300 {
    301     if (m_isPausedForScrubbing) {
    302         m_isPausedForScrubbing = false;
    303         if (mediaElement().togglePlayStateWillPlay())
    304             mediaElement().togglePlayState();
    305     }
    306 }
    307 
    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 
    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 
    336 void MediaControls::changedClosedCaptionsVisibility()
    337 {
    338     m_toggleClosedCaptionsButton->updateDisplayType();
    339 }
    340 
    341 void MediaControls::refreshClosedCaptionsButtonVisibility()
    342 {
    343     if (mediaElement().hasClosedCaptions())
    344         m_toggleClosedCaptionsButton->show();
    345     else
    346         m_toggleClosedCaptionsButton->hide();
    347 }
    348 
    349 void MediaControls::closedCaptionTracksChanged()
    350 {
    351     refreshClosedCaptionsButtonVisibility();
    352 }
    353 
    354 void MediaControls::enteredFullscreen()
    355 {
    356     m_fullScreenButton->setIsFullscreen(true);
    357     stopHideMediaControlsTimer();
    358     startHideMediaControlsTimer();
    359 }
    360 
    361 void MediaControls::exitedFullscreen()
    362 {
    363     m_fullScreenButton->setIsFullscreen(false);
    364     stopHideMediaControlsTimer();
    365     startHideMediaControlsTimer();
    366 }
    367 
    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 
    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 
    418 void MediaControls::startHideMediaControlsTimer()
    419 {
    420     m_hideMediaControlsTimer.startOneShot(timeWithoutMouseMovementBeforeHidingMediaControls, FROM_HERE);
    421 }
    422 
    423 void MediaControls::stopHideMediaControlsTimer()
    424 {
    425     m_hideMediaControlsTimer.stop();
    426 }
    427 
    428 const AtomicString& MediaControls::shadowPseudoId() const
    429 {
    430     DEFINE_STATIC_LOCAL(AtomicString, id, ("-webkit-media-controls"));
    431     return id;
    432 }
    433 
    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 
    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 
    459 void MediaControls::showTextTrackDisplay()
    460 {
    461     if (!m_textDisplayContainer)
    462         createTextTrackDisplay();
    463     m_textDisplayContainer->show();
    464 }
    465 
    466 void MediaControls::hideTextTrackDisplay()
    467 {
    468     if (!m_textDisplayContainer)
    469         createTextTrackDisplay();
    470     m_textDisplayContainer->hide();
    471 }
    472 
    473 void MediaControls::updateTextTrackDisplay()
    474 {
    475     if (!m_textDisplayContainer)
    476         createTextTrackDisplay();
    477 
    478     m_textDisplayContainer->updateDisplay();
    479 }
    480 
    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