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