| OLD | NEW |
| (Empty) |
| 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 "core/html/shadow/MediaControls.h" | |
| 28 | |
| 29 #include "bindings/core/v8/ExceptionState.h" | |
| 30 #include "core/dom/ClientRect.h" | |
| 31 #include "core/dom/Fullscreen.h" | |
| 32 #include "core/dom/ResizeObserver.h" | |
| 33 #include "core/dom/ResizeObserverCallback.h" | |
| 34 #include "core/dom/ResizeObserverEntry.h" | |
| 35 #include "core/dom/TaskRunnerHelper.h" | |
| 36 #include "core/events/MouseEvent.h" | |
| 37 #include "core/frame/Settings.h" | |
| 38 #include "core/frame/UseCounter.h" | |
| 39 #include "core/html/HTMLMediaElement.h" | |
| 40 #include "core/html/HTMLVideoElement.h" | |
| 41 #include "core/html/media/HTMLMediaElementControlsList.h" | |
| 42 #include "core/html/shadow/MediaControlsMediaEventListener.h" | |
| 43 #include "core/html/shadow/MediaControlsOrientationLockDelegate.h" | |
| 44 #include "core/html/shadow/MediaControlsWindowEventListener.h" | |
| 45 #include "core/html/track/TextTrackContainer.h" | |
| 46 #include "core/html/track/TextTrackList.h" | |
| 47 #include "core/layout/LayoutObject.h" | |
| 48 #include "core/layout/LayoutTheme.h" | |
| 49 #include "platform/EventDispatchForbiddenScope.h" | |
| 50 | |
| 51 namespace blink { | |
| 52 | |
| 53 namespace { | |
| 54 | |
| 55 // TODO(steimel): should have better solution than hard-coding pixel values. | |
| 56 // Defined in core/css/mediaControls.css, core/css/mediaControlsAndroid.css, | |
| 57 // and core/paint/MediaControlsPainter.cpp. | |
| 58 constexpr int kOverlayPlayButtonWidth = 48; | |
| 59 constexpr int kOverlayPlayButtonHeight = 48; | |
| 60 constexpr int kOverlayBottomMargin = 10; | |
| 61 constexpr int kAndroidMediaPanelHeight = 48; | |
| 62 | |
| 63 constexpr int kMinWidthForOverlayPlayButton = kOverlayPlayButtonWidth; | |
| 64 constexpr int kMinHeightForOverlayPlayButton = kOverlayPlayButtonHeight + | |
| 65 kAndroidMediaPanelHeight + | |
| 66 (2 * kOverlayBottomMargin); | |
| 67 | |
| 68 } // anonymous namespace | |
| 69 | |
| 70 // If you change this value, then also update the corresponding value in | |
| 71 // LayoutTests/media/media-controls.js. | |
| 72 static const double timeWithoutMouseMovementBeforeHidingMediaControls = 3; | |
| 73 | |
| 74 static bool shouldShowFullscreenButton(const HTMLMediaElement& mediaElement) { | |
| 75 // Unconditionally allow the user to exit fullscreen if we are in it | |
| 76 // now. Especially on android, when we might not yet know if | |
| 77 // fullscreen is supported, we sometimes guess incorrectly and show | |
| 78 // the button earlier, and we don't want to remove it here if the | |
| 79 // user chose to enter fullscreen. crbug.com/500732 . | |
| 80 if (mediaElement.isFullscreen()) | |
| 81 return true; | |
| 82 | |
| 83 if (!mediaElement.isHTMLVideoElement()) | |
| 84 return false; | |
| 85 | |
| 86 if (!mediaElement.hasVideo()) | |
| 87 return false; | |
| 88 | |
| 89 if (!Fullscreen::fullscreenEnabled(mediaElement.document())) | |
| 90 return false; | |
| 91 | |
| 92 if (mediaElement.controlsListInternal()->shouldHideFullscreen()) { | |
| 93 UseCounter::count(mediaElement.document(), | |
| 94 UseCounter::HTMLMediaElementControlsListNoFullscreen); | |
| 95 return false; | |
| 96 } | |
| 97 | |
| 98 return true; | |
| 99 } | |
| 100 | |
| 101 static bool shouldShowCastButton(HTMLMediaElement& mediaElement) { | |
| 102 if (mediaElement.fastHasAttribute(HTMLNames::disableremoteplaybackAttr)) | |
| 103 return false; | |
| 104 | |
| 105 // Explicitly do not show cast button when the mediaControlsEnabled setting is | |
| 106 // false to make sure the overlay does not appear. | |
| 107 Document& document = mediaElement.document(); | |
| 108 if (document.settings() && !document.settings()->getMediaControlsEnabled()) | |
| 109 return false; | |
| 110 | |
| 111 // The page disabled the button via the attribute. | |
| 112 if (mediaElement.controlsListInternal()->shouldHideRemotePlayback()) { | |
| 113 UseCounter::count(mediaElement.document(), | |
| 114 UseCounter::HTMLMediaElementControlsListNoRemotePlayback); | |
| 115 return false; | |
| 116 } | |
| 117 | |
| 118 return mediaElement.hasRemoteRoutes(); | |
| 119 } | |
| 120 | |
| 121 static bool preferHiddenVolumeControls(const Document& document) { | |
| 122 return !document.settings() || | |
| 123 document.settings()->getPreferHiddenVolumeControls(); | |
| 124 } | |
| 125 | |
| 126 class MediaControls::BatchedControlUpdate { | |
| 127 WTF_MAKE_NONCOPYABLE(BatchedControlUpdate); | |
| 128 STACK_ALLOCATED(); | |
| 129 | |
| 130 public: | |
| 131 explicit BatchedControlUpdate(MediaControls* controls) | |
| 132 : m_controls(controls) { | |
| 133 DCHECK(isMainThread()); | |
| 134 DCHECK_GE(s_batchDepth, 0); | |
| 135 ++s_batchDepth; | |
| 136 } | |
| 137 ~BatchedControlUpdate() { | |
| 138 DCHECK(isMainThread()); | |
| 139 DCHECK_GT(s_batchDepth, 0); | |
| 140 if (!(--s_batchDepth)) | |
| 141 m_controls->computeWhichControlsFit(); | |
| 142 } | |
| 143 | |
| 144 private: | |
| 145 Member<MediaControls> m_controls; | |
| 146 static int s_batchDepth; | |
| 147 }; | |
| 148 | |
| 149 // Count of number open batches for controls visibility. | |
| 150 int MediaControls::BatchedControlUpdate::s_batchDepth = 0; | |
| 151 | |
| 152 class MediaControls::MediaControlsResizeObserverCallback final | |
| 153 : public ResizeObserverCallback { | |
| 154 public: | |
| 155 explicit MediaControlsResizeObserverCallback(MediaControls* controls) | |
| 156 : m_controls(controls) { | |
| 157 DCHECK(controls); | |
| 158 } | |
| 159 ~MediaControlsResizeObserverCallback() override = default; | |
| 160 | |
| 161 void handleEvent(const HeapVector<Member<ResizeObserverEntry>>& entries, | |
| 162 ResizeObserver* observer) override { | |
| 163 DCHECK_EQ(1u, entries.size()); | |
| 164 DCHECK_EQ(entries[0]->target(), m_controls->m_mediaElement); | |
| 165 m_controls->notifyElementSizeChanged(entries[0]->contentRect()); | |
| 166 } | |
| 167 | |
| 168 DEFINE_INLINE_TRACE() { | |
| 169 visitor->trace(m_controls); | |
| 170 ResizeObserverCallback::trace(visitor); | |
| 171 } | |
| 172 | |
| 173 private: | |
| 174 Member<MediaControls> m_controls; | |
| 175 }; | |
| 176 | |
| 177 MediaControls::MediaControls(HTMLMediaElement& mediaElement) | |
| 178 : HTMLDivElement(mediaElement.document()), | |
| 179 m_mediaElement(&mediaElement), | |
| 180 m_overlayEnclosure(nullptr), | |
| 181 m_overlayPlayButton(nullptr), | |
| 182 m_overlayCastButton(nullptr), | |
| 183 m_enclosure(nullptr), | |
| 184 m_panel(nullptr), | |
| 185 m_playButton(nullptr), | |
| 186 m_timeline(nullptr), | |
| 187 m_currentTimeDisplay(nullptr), | |
| 188 m_durationDisplay(nullptr), | |
| 189 m_muteButton(nullptr), | |
| 190 m_volumeSlider(nullptr), | |
| 191 m_toggleClosedCaptionsButton(nullptr), | |
| 192 m_textTrackList(nullptr), | |
| 193 m_overflowList(nullptr), | |
| 194 m_castButton(nullptr), | |
| 195 m_fullscreenButton(nullptr), | |
| 196 m_downloadButton(nullptr), | |
| 197 m_mediaEventListener(new MediaControlsMediaEventListener(this)), | |
| 198 m_windowEventListener(MediaControlsWindowEventListener::create( | |
| 199 this, | |
| 200 WTF::bind(&MediaControls::hideAllMenus, wrapWeakPersistent(this)))), | |
| 201 m_orientationLockDelegate(nullptr), | |
| 202 m_hideMediaControlsTimer(TaskRunnerHelper::get(TaskType::UnspecedTimer, | |
| 203 &mediaElement.document()), | |
| 204 this, | |
| 205 &MediaControls::hideMediaControlsTimerFired), | |
| 206 m_hideTimerBehaviorFlags(IgnoreNone), | |
| 207 m_isMouseOverControls(false), | |
| 208 m_isPausedForScrubbing(false), | |
| 209 m_resizeObserver(ResizeObserver::create( | |
| 210 mediaElement.document(), | |
| 211 new MediaControlsResizeObserverCallback(this))), | |
| 212 m_elementSizeChangedTimer(TaskRunnerHelper::get(TaskType::UnspecedTimer, | |
| 213 &mediaElement.document()), | |
| 214 this, | |
| 215 &MediaControls::elementSizeChangedTimerFired), | |
| 216 m_keepShowingUntilTimerFires(false) { | |
| 217 m_resizeObserver->observe(m_mediaElement); | |
| 218 } | |
| 219 | |
| 220 MediaControls* MediaControls::create(HTMLMediaElement& mediaElement, | |
| 221 ShadowRoot& shadowRoot) { | |
| 222 MediaControls* controls = new MediaControls(mediaElement); | |
| 223 controls->setShadowPseudoId(AtomicString("-webkit-media-controls")); | |
| 224 controls->initializeControls(); | |
| 225 controls->reset(); | |
| 226 | |
| 227 // Initialize the orientation lock when going fullscreen feature. | |
| 228 if (RuntimeEnabledFeatures::videoFullscreenOrientationLockEnabled() && | |
| 229 mediaElement.isHTMLVideoElement()) { | |
| 230 controls->m_orientationLockDelegate = | |
| 231 new MediaControlsOrientationLockDelegate( | |
| 232 toHTMLVideoElement(mediaElement)); | |
| 233 } | |
| 234 | |
| 235 shadowRoot.appendChild(controls); | |
| 236 return controls; | |
| 237 } | |
| 238 | |
| 239 // The media controls DOM structure looks like: | |
| 240 // | |
| 241 // MediaControls | |
| 242 // (-webkit-media-controls) | |
| 243 // +-MediaControlOverlayEnclosureElement | |
| 244 // | (-webkit-media-controls-overlay-enclosure) | |
| 245 // | +-MediaControlOverlayPlayButtonElement | |
| 246 // | | (-webkit-media-controls-overlay-play-button) | |
| 247 // | | {if mediaControlsOverlayPlayButtonEnabled} | |
| 248 // | \-MediaControlCastButtonElement | |
| 249 // | (-internal-media-controls-overlay-cast-button) | |
| 250 // \-MediaControlPanelEnclosureElement | |
| 251 // | (-webkit-media-controls-enclosure) | |
| 252 // \-MediaControlPanelElement | |
| 253 // | (-webkit-media-controls-panel) | |
| 254 // +-MediaControlPlayButtonElement | |
| 255 // | (-webkit-media-controls-play-button) | |
| 256 // +-MediaControlCurrentTimeDisplayElement | |
| 257 // | (-webkit-media-controls-current-time-display) | |
| 258 // +-MediaControlTimeRemainingDisplayElement | |
| 259 // | (-webkit-media-controls-time-remaining-display) | |
| 260 // +-MediaControlTimelineElement | |
| 261 // | (-webkit-media-controls-timeline) | |
| 262 // +-MediaControlMuteButtonElement | |
| 263 // | (-webkit-media-controls-mute-button) | |
| 264 // +-MediaControlVolumeSliderElement | |
| 265 // | (-webkit-media-controls-volume-slider) | |
| 266 // +-MediaControlFullscreenButtonElement | |
| 267 // | (-webkit-media-controls-fullscreen-button) | |
| 268 // +-MediaControlDownloadButtonElement | |
| 269 // | (-internal-media-controls-download-button) | |
| 270 // +-MediaControlToggleClosedCaptionsButtonElement | |
| 271 // | (-webkit-media-controls-toggle-closed-captions-button) | |
| 272 // \-MediaControlCastButtonElement | |
| 273 // (-internal-media-controls-cast-button) | |
| 274 // +-MediaControlTextTrackListElement | |
| 275 // | (-internal-media-controls-text-track-list) | |
| 276 // | {for each renderable text track} | |
| 277 // \-MediaControlTextTrackListItem | |
| 278 // | (-internal-media-controls-text-track-list-item) | |
| 279 // +-MediaControlTextTrackListItemInput | |
| 280 // | (-internal-media-controls-text-track-list-item-input) | |
| 281 // +-MediaControlTextTrackListItemCaptions | |
| 282 // | (-internal-media-controls-text-track-list-kind-captions) | |
| 283 // +-MediaControlTextTrackListItemSubtitles | |
| 284 // (-internal-media-controls-text-track-list-kind-subtitles) | |
| 285 void MediaControls::initializeControls() { | |
| 286 MediaControlOverlayEnclosureElement* overlayEnclosure = | |
| 287 MediaControlOverlayEnclosureElement::create(*this); | |
| 288 | |
| 289 if (RuntimeEnabledFeatures::mediaControlsOverlayPlayButtonEnabled()) { | |
| 290 MediaControlOverlayPlayButtonElement* overlayPlayButton = | |
| 291 MediaControlOverlayPlayButtonElement::create(*this); | |
| 292 m_overlayPlayButton = overlayPlayButton; | |
| 293 overlayEnclosure->appendChild(overlayPlayButton); | |
| 294 } | |
| 295 | |
| 296 MediaControlCastButtonElement* overlayCastButton = | |
| 297 MediaControlCastButtonElement::create(*this, true); | |
| 298 m_overlayCastButton = overlayCastButton; | |
| 299 overlayEnclosure->appendChild(overlayCastButton); | |
| 300 | |
| 301 m_overlayEnclosure = overlayEnclosure; | |
| 302 appendChild(overlayEnclosure); | |
| 303 | |
| 304 // Create an enclosing element for the panel so we can visually offset the | |
| 305 // controls correctly. | |
| 306 MediaControlPanelEnclosureElement* enclosure = | |
| 307 MediaControlPanelEnclosureElement::create(*this); | |
| 308 | |
| 309 MediaControlPanelElement* panel = MediaControlPanelElement::create(*this); | |
| 310 | |
| 311 MediaControlPlayButtonElement* playButton = | |
| 312 MediaControlPlayButtonElement::create(*this); | |
| 313 m_playButton = playButton; | |
| 314 panel->appendChild(playButton); | |
| 315 | |
| 316 MediaControlCurrentTimeDisplayElement* currentTimeDisplay = | |
| 317 MediaControlCurrentTimeDisplayElement::create(*this); | |
| 318 m_currentTimeDisplay = currentTimeDisplay; | |
| 319 m_currentTimeDisplay->setIsWanted(true); | |
| 320 panel->appendChild(currentTimeDisplay); | |
| 321 | |
| 322 MediaControlTimeRemainingDisplayElement* durationDisplay = | |
| 323 MediaControlTimeRemainingDisplayElement::create(*this); | |
| 324 m_durationDisplay = durationDisplay; | |
| 325 panel->appendChild(durationDisplay); | |
| 326 | |
| 327 MediaControlTimelineElement* timeline = | |
| 328 MediaControlTimelineElement::create(*this); | |
| 329 m_timeline = timeline; | |
| 330 panel->appendChild(timeline); | |
| 331 | |
| 332 MediaControlMuteButtonElement* muteButton = | |
| 333 MediaControlMuteButtonElement::create(*this); | |
| 334 m_muteButton = muteButton; | |
| 335 panel->appendChild(muteButton); | |
| 336 | |
| 337 MediaControlVolumeSliderElement* slider = | |
| 338 MediaControlVolumeSliderElement::create(*this); | |
| 339 m_volumeSlider = slider; | |
| 340 panel->appendChild(slider); | |
| 341 if (preferHiddenVolumeControls(document())) | |
| 342 m_volumeSlider->setIsWanted(false); | |
| 343 | |
| 344 MediaControlFullscreenButtonElement* fullscreenButton = | |
| 345 MediaControlFullscreenButtonElement::create(*this); | |
| 346 m_fullscreenButton = fullscreenButton; | |
| 347 panel->appendChild(fullscreenButton); | |
| 348 | |
| 349 MediaControlDownloadButtonElement* downloadButton = | |
| 350 MediaControlDownloadButtonElement::create(*this); | |
| 351 m_downloadButton = downloadButton; | |
| 352 panel->appendChild(downloadButton); | |
| 353 | |
| 354 MediaControlCastButtonElement* castButton = | |
| 355 MediaControlCastButtonElement::create(*this, false); | |
| 356 m_castButton = castButton; | |
| 357 panel->appendChild(castButton); | |
| 358 | |
| 359 MediaControlToggleClosedCaptionsButtonElement* toggleClosedCaptionsButton = | |
| 360 MediaControlToggleClosedCaptionsButtonElement::create(*this); | |
| 361 m_toggleClosedCaptionsButton = toggleClosedCaptionsButton; | |
| 362 panel->appendChild(toggleClosedCaptionsButton); | |
| 363 | |
| 364 m_panel = panel; | |
| 365 enclosure->appendChild(panel); | |
| 366 | |
| 367 m_enclosure = enclosure; | |
| 368 appendChild(enclosure); | |
| 369 | |
| 370 MediaControlTextTrackListElement* textTrackList = | |
| 371 MediaControlTextTrackListElement::create(*this); | |
| 372 m_textTrackList = textTrackList; | |
| 373 appendChild(textTrackList); | |
| 374 | |
| 375 MediaControlOverflowMenuButtonElement* overflowMenu = | |
| 376 MediaControlOverflowMenuButtonElement::create(*this); | |
| 377 m_overflowMenu = overflowMenu; | |
| 378 panel->appendChild(overflowMenu); | |
| 379 | |
| 380 MediaControlOverflowMenuListElement* overflowList = | |
| 381 MediaControlOverflowMenuListElement::create(*this); | |
| 382 m_overflowList = overflowList; | |
| 383 appendChild(overflowList); | |
| 384 | |
| 385 // The order in which we append elements to the overflow list is significant | |
| 386 // because it determines how the elements show up in the overflow menu | |
| 387 // relative to each other. The first item appended appears at the top of the | |
| 388 // overflow menu. | |
| 389 m_overflowList->appendChild(m_playButton->createOverflowElement( | |
| 390 *this, MediaControlPlayButtonElement::create(*this))); | |
| 391 m_overflowList->appendChild(m_fullscreenButton->createOverflowElement( | |
| 392 *this, MediaControlFullscreenButtonElement::create(*this))); | |
| 393 m_overflowList->appendChild(m_downloadButton->createOverflowElement( | |
| 394 *this, MediaControlDownloadButtonElement::create(*this))); | |
| 395 m_overflowList->appendChild(m_muteButton->createOverflowElement( | |
| 396 *this, MediaControlMuteButtonElement::create(*this))); | |
| 397 m_overflowList->appendChild(m_castButton->createOverflowElement( | |
| 398 *this, MediaControlCastButtonElement::create(*this, false))); | |
| 399 m_overflowList->appendChild( | |
| 400 m_toggleClosedCaptionsButton->createOverflowElement( | |
| 401 *this, MediaControlToggleClosedCaptionsButtonElement::create(*this))); | |
| 402 } | |
| 403 | |
| 404 Node::InsertionNotificationRequest MediaControls::insertedInto( | |
| 405 ContainerNode* root) { | |
| 406 if (!mediaElement().isConnected()) | |
| 407 return HTMLDivElement::insertedInto(root); | |
| 408 | |
| 409 // TODO(mlamouri): we should show the controls instead of having | |
| 410 // HTMLMediaElement do it. | |
| 411 | |
| 412 // m_windowEventListener doesn't need to be re-attached as it's only needed | |
| 413 // when a menu is visible. | |
| 414 m_mediaEventListener->attach(); | |
| 415 if (m_orientationLockDelegate) | |
| 416 m_orientationLockDelegate->attach(); | |
| 417 | |
| 418 if (!m_resizeObserver) { | |
| 419 m_resizeObserver = | |
| 420 ResizeObserver::create(m_mediaElement->document(), | |
| 421 new MediaControlsResizeObserverCallback(this)); | |
| 422 m_resizeObserver->observe(m_mediaElement); | |
| 423 } | |
| 424 | |
| 425 return HTMLDivElement::insertedInto(root); | |
| 426 } | |
| 427 | |
| 428 void MediaControls::removedFrom(ContainerNode*) { | |
| 429 DCHECK(!mediaElement().isConnected()); | |
| 430 | |
| 431 // TODO(mlamouri): we hide show the controls instead of having | |
| 432 // HTMLMediaElement do it. | |
| 433 | |
| 434 m_windowEventListener->stop(); | |
| 435 m_mediaEventListener->detach(); | |
| 436 if (m_orientationLockDelegate) | |
| 437 m_orientationLockDelegate->detach(); | |
| 438 | |
| 439 m_resizeObserver.clear(); | |
| 440 } | |
| 441 | |
| 442 void MediaControls::reset() { | |
| 443 EventDispatchForbiddenScope::AllowUserAgentEvents allowEventsInShadow; | |
| 444 BatchedControlUpdate batch(this); | |
| 445 | |
| 446 const double duration = mediaElement().duration(); | |
| 447 m_durationDisplay->setTextContent( | |
| 448 LayoutTheme::theme().formatMediaControlsTime(duration)); | |
| 449 m_durationDisplay->setCurrentValue(duration); | |
| 450 | |
| 451 // Show everything that we might hide. | |
| 452 // If we don't have a duration, then mark it to be hidden. For the | |
| 453 // old UI case, want / don't want is the same as show / hide since | |
| 454 // it is never marked as not fitting. | |
| 455 m_durationDisplay->setIsWanted(std::isfinite(duration)); | |
| 456 m_currentTimeDisplay->setIsWanted(true); | |
| 457 m_timeline->setIsWanted(true); | |
| 458 | |
| 459 // If the player has entered an error state, force it into the paused state. | |
| 460 if (mediaElement().error()) | |
| 461 mediaElement().pause(); | |
| 462 | |
| 463 updatePlayState(); | |
| 464 | |
| 465 updateCurrentTimeDisplay(); | |
| 466 | |
| 467 m_timeline->setDuration(duration); | |
| 468 m_timeline->setPosition(mediaElement().currentTime()); | |
| 469 | |
| 470 onVolumeChange(); | |
| 471 onTextTracksAddedOrRemoved(); | |
| 472 | |
| 473 onControlsListUpdated(); | |
| 474 } | |
| 475 | |
| 476 void MediaControls::onControlsListUpdated() { | |
| 477 BatchedControlUpdate batch(this); | |
| 478 | |
| 479 m_fullscreenButton->setIsWanted(shouldShowFullscreenButton(mediaElement())); | |
| 480 | |
| 481 refreshCastButtonVisibilityWithoutUpdate(); | |
| 482 | |
| 483 m_downloadButton->setIsWanted( | |
| 484 m_downloadButton->shouldDisplayDownloadButton()); | |
| 485 } | |
| 486 | |
| 487 LayoutObject* MediaControls::layoutObjectForTextTrackLayout() { | |
| 488 return m_panel->layoutObject(); | |
| 489 } | |
| 490 | |
| 491 void MediaControls::show() { | |
| 492 makeOpaque(); | |
| 493 m_panel->setIsWanted(true); | |
| 494 m_panel->setIsDisplayed(true); | |
| 495 if (m_overlayPlayButton) | |
| 496 m_overlayPlayButton->updateDisplayType(); | |
| 497 } | |
| 498 | |
| 499 void MediaControls::hide() { | |
| 500 m_panel->setIsWanted(false); | |
| 501 m_panel->setIsDisplayed(false); | |
| 502 if (m_overlayPlayButton) | |
| 503 m_overlayPlayButton->setIsWanted(false); | |
| 504 } | |
| 505 | |
| 506 bool MediaControls::isVisible() const { | |
| 507 return m_panel->isOpaque(); | |
| 508 } | |
| 509 | |
| 510 void MediaControls::makeOpaque() { | |
| 511 m_panel->makeOpaque(); | |
| 512 } | |
| 513 | |
| 514 void MediaControls::makeTransparent() { | |
| 515 m_panel->makeTransparent(); | |
| 516 } | |
| 517 | |
| 518 bool MediaControls::shouldHideMediaControls(unsigned behaviorFlags) const { | |
| 519 // Never hide for a media element without visual representation. | |
| 520 if (!mediaElement().isHTMLVideoElement() || !mediaElement().hasVideo() || | |
| 521 mediaElement().isPlayingRemotely()) { | |
| 522 return false; | |
| 523 } | |
| 524 | |
| 525 // Keep the controls visible as long as the timer is running. | |
| 526 const bool ignoreWaitForTimer = behaviorFlags & IgnoreWaitForTimer; | |
| 527 if (!ignoreWaitForTimer && m_keepShowingUntilTimerFires) | |
| 528 return false; | |
| 529 | |
| 530 // Don't hide if the mouse is over the controls. | |
| 531 const bool ignoreControlsHover = behaviorFlags & IgnoreControlsHover; | |
| 532 if (!ignoreControlsHover && m_panel->isHovered()) | |
| 533 return false; | |
| 534 | |
| 535 // Don't hide if the mouse is over the video area. | |
| 536 const bool ignoreVideoHover = behaviorFlags & IgnoreVideoHover; | |
| 537 if (!ignoreVideoHover && m_isMouseOverControls) | |
| 538 return false; | |
| 539 | |
| 540 // Don't hide if focus is on the HTMLMediaElement or within the | |
| 541 // controls/shadow tree. (Perform the checks separately to avoid going | |
| 542 // through all the potential ancestor hosts for the focused element.) | |
| 543 const bool ignoreFocus = behaviorFlags & IgnoreFocus; | |
| 544 if (!ignoreFocus && | |
| 545 (mediaElement().isFocused() || contains(document().focusedElement()))) { | |
| 546 return false; | |
| 547 } | |
| 548 | |
| 549 // Don't hide the media controls when a panel is showing. | |
| 550 if (m_textTrackList->isWanted() || m_overflowList->isWanted()) | |
| 551 return false; | |
| 552 | |
| 553 return true; | |
| 554 } | |
| 555 | |
| 556 void MediaControls::updatePlayState() { | |
| 557 if (m_isPausedForScrubbing) | |
| 558 return; | |
| 559 | |
| 560 if (m_overlayPlayButton) | |
| 561 m_overlayPlayButton->updateDisplayType(); | |
| 562 m_playButton->updateDisplayType(); | |
| 563 } | |
| 564 | |
| 565 void MediaControls::beginScrubbing() { | |
| 566 if (!mediaElement().paused()) { | |
| 567 m_isPausedForScrubbing = true; | |
| 568 mediaElement().pause(); | |
| 569 } | |
| 570 } | |
| 571 | |
| 572 void MediaControls::endScrubbing() { | |
| 573 if (m_isPausedForScrubbing) { | |
| 574 m_isPausedForScrubbing = false; | |
| 575 if (mediaElement().paused()) | |
| 576 mediaElement().play(); | |
| 577 } | |
| 578 } | |
| 579 | |
| 580 void MediaControls::updateCurrentTimeDisplay() { | |
| 581 double now = mediaElement().currentTime(); | |
| 582 double duration = mediaElement().duration(); | |
| 583 | |
| 584 // Allow the theme to format the time. | |
| 585 m_currentTimeDisplay->setInnerText( | |
| 586 LayoutTheme::theme().formatMediaControlsCurrentTime(now, duration), | |
| 587 IGNORE_EXCEPTION_FOR_TESTING); | |
| 588 m_currentTimeDisplay->setCurrentValue(now); | |
| 589 } | |
| 590 | |
| 591 void MediaControls::toggleTextTrackList() { | |
| 592 if (!mediaElement().hasClosedCaptions()) { | |
| 593 m_textTrackList->setVisible(false); | |
| 594 return; | |
| 595 } | |
| 596 | |
| 597 if (!m_textTrackList->isWanted()) | |
| 598 m_windowEventListener->start(); | |
| 599 | |
| 600 m_textTrackList->setVisible(!m_textTrackList->isWanted()); | |
| 601 } | |
| 602 | |
| 603 void MediaControls::showTextTrackAtIndex(unsigned indexToEnable) { | |
| 604 TextTrackList* trackList = mediaElement().textTracks(); | |
| 605 if (indexToEnable >= trackList->length()) | |
| 606 return; | |
| 607 TextTrack* track = trackList->anonymousIndexedGetter(indexToEnable); | |
| 608 if (track && track->canBeRendered()) | |
| 609 track->setMode(TextTrack::showingKeyword()); | |
| 610 } | |
| 611 | |
| 612 void MediaControls::disableShowingTextTracks() { | |
| 613 TextTrackList* trackList = mediaElement().textTracks(); | |
| 614 for (unsigned i = 0; i < trackList->length(); ++i) { | |
| 615 TextTrack* track = trackList->anonymousIndexedGetter(i); | |
| 616 if (track->mode() == TextTrack::showingKeyword()) | |
| 617 track->setMode(TextTrack::disabledKeyword()); | |
| 618 } | |
| 619 } | |
| 620 | |
| 621 void MediaControls::refreshCastButtonVisibility() { | |
| 622 refreshCastButtonVisibilityWithoutUpdate(); | |
| 623 BatchedControlUpdate batch(this); | |
| 624 } | |
| 625 | |
| 626 void MediaControls::refreshCastButtonVisibilityWithoutUpdate() { | |
| 627 if (!shouldShowCastButton(mediaElement())) { | |
| 628 m_castButton->setIsWanted(false); | |
| 629 m_overlayCastButton->setIsWanted(false); | |
| 630 return; | |
| 631 } | |
| 632 | |
| 633 // The reason for the autoplay test is that some pages (e.g. vimeo.com) have | |
| 634 // an autoplay background video, which doesn't autoplay on Chrome for Android | |
| 635 // (we prevent it) so starts paused. In such cases we don't want to | |
| 636 // automatically show the cast button, since it looks strange and is unlikely | |
| 637 // to correspond with anything the user wants to do. If a user does want to | |
| 638 // cast a paused autoplay video then they can still do so by touching or | |
| 639 // clicking on the video, which will cause the cast button to appear. | |
| 640 if (!mediaElement().shouldShowControls() && !mediaElement().autoplay() && | |
| 641 mediaElement().paused()) { | |
| 642 // Note that this is a case where we add the overlay cast button | |
| 643 // without wanting the panel cast button. We depend on the fact | |
| 644 // that computeWhichControlsFit() won't change overlay cast button | |
| 645 // visibility in the case where the cast button isn't wanted. | |
| 646 // We don't call compute...() here, but it will be called as | |
| 647 // non-cast changes (e.g., resize) occur. If the panel button | |
| 648 // is shown, however, compute...() will take control of the | |
| 649 // overlay cast button if it needs to hide it from the panel. | |
| 650 m_overlayCastButton->tryShowOverlay(); | |
| 651 m_castButton->setIsWanted(false); | |
| 652 } else if (mediaElement().shouldShowControls()) { | |
| 653 m_overlayCastButton->setIsWanted(false); | |
| 654 m_castButton->setIsWanted(true); | |
| 655 } | |
| 656 } | |
| 657 | |
| 658 void MediaControls::showOverlayCastButtonIfNeeded() { | |
| 659 if (mediaElement().shouldShowControls() || | |
| 660 !shouldShowCastButton(mediaElement())) | |
| 661 return; | |
| 662 | |
| 663 m_overlayCastButton->tryShowOverlay(); | |
| 664 resetHideMediaControlsTimer(); | |
| 665 } | |
| 666 | |
| 667 void MediaControls::enterFullscreen() { | |
| 668 Fullscreen::requestFullscreen(mediaElement()); | |
| 669 } | |
| 670 | |
| 671 void MediaControls::exitFullscreen() { | |
| 672 Fullscreen::exitFullscreen(document()); | |
| 673 } | |
| 674 | |
| 675 void MediaControls::startedCasting() { | |
| 676 m_castButton->setIsPlayingRemotely(true); | |
| 677 m_overlayCastButton->setIsPlayingRemotely(true); | |
| 678 } | |
| 679 | |
| 680 void MediaControls::stoppedCasting() { | |
| 681 m_castButton->setIsPlayingRemotely(false); | |
| 682 m_overlayCastButton->setIsPlayingRemotely(false); | |
| 683 } | |
| 684 | |
| 685 void MediaControls::defaultEventHandler(Event* event) { | |
| 686 HTMLDivElement::defaultEventHandler(event); | |
| 687 | |
| 688 // Do not handle events to not interfere with the rest of the page if no | |
| 689 // controls should be visible. | |
| 690 if (!mediaElement().shouldShowControls()) | |
| 691 return; | |
| 692 | |
| 693 // Add IgnoreControlsHover to m_hideTimerBehaviorFlags when we see a touch | |
| 694 // event, to allow the hide-timer to do the right thing when it fires. | |
| 695 // FIXME: Preferably we would only do this when we're actually handling the | |
| 696 // event here ourselves. | |
| 697 bool isTouchEvent = | |
| 698 event->isTouchEvent() || event->isGestureEvent() || | |
| 699 (event->isMouseEvent() && toMouseEvent(event)->fromTouch()); | |
| 700 m_hideTimerBehaviorFlags |= isTouchEvent ? IgnoreControlsHover : IgnoreNone; | |
| 701 | |
| 702 // Touch events are treated differently to avoid fake mouse events to trigger | |
| 703 // random behavior. The expect behaviour for touch is that a tap will show the | |
| 704 // controls and they will hide when the timer to hide fires. | |
| 705 if (isTouchEvent) { | |
| 706 if (event->type() != EventTypeNames::gesturetap) | |
| 707 return; | |
| 708 | |
| 709 if (!containsRelatedTarget(event)) { | |
| 710 if (!mediaElement().paused()) { | |
| 711 if (!isVisible()) { | |
| 712 makeOpaque(); | |
| 713 // When the panel switches from invisible to visible, we need to mark | |
| 714 // the event handled to avoid buttons below the tap to be activated. | |
| 715 event->setDefaultHandled(); | |
| 716 } | |
| 717 if (shouldHideMediaControls(IgnoreWaitForTimer)) { | |
| 718 m_keepShowingUntilTimerFires = true; | |
| 719 startHideMediaControlsTimer(); | |
| 720 } | |
| 721 } | |
| 722 } | |
| 723 | |
| 724 return; | |
| 725 } | |
| 726 | |
| 727 if (event->type() == EventTypeNames::mouseover) { | |
| 728 if (!containsRelatedTarget(event)) { | |
| 729 m_isMouseOverControls = true; | |
| 730 if (!mediaElement().paused()) { | |
| 731 makeOpaque(); | |
| 732 if (shouldHideMediaControls()) | |
| 733 startHideMediaControlsTimer(); | |
| 734 } | |
| 735 } | |
| 736 return; | |
| 737 } | |
| 738 | |
| 739 if (event->type() == EventTypeNames::mouseout) { | |
| 740 if (!containsRelatedTarget(event)) { | |
| 741 m_isMouseOverControls = false; | |
| 742 stopHideMediaControlsTimer(); | |
| 743 } | |
| 744 return; | |
| 745 } | |
| 746 | |
| 747 if (event->type() == EventTypeNames::mousemove) { | |
| 748 // When we get a mouse move, show the media controls, and start a timer | |
| 749 // that will hide the media controls after a 3 seconds without a mouse move. | |
| 750 makeOpaque(); | |
| 751 if (shouldHideMediaControls(IgnoreVideoHover)) | |
| 752 startHideMediaControlsTimer(); | |
| 753 return; | |
| 754 } | |
| 755 } | |
| 756 | |
| 757 void MediaControls::hideMediaControlsTimerFired(TimerBase*) { | |
| 758 unsigned behaviorFlags = | |
| 759 m_hideTimerBehaviorFlags | IgnoreFocus | IgnoreVideoHover; | |
| 760 m_hideTimerBehaviorFlags = IgnoreNone; | |
| 761 m_keepShowingUntilTimerFires = false; | |
| 762 | |
| 763 if (mediaElement().paused()) | |
| 764 return; | |
| 765 | |
| 766 if (!shouldHideMediaControls(behaviorFlags)) | |
| 767 return; | |
| 768 | |
| 769 makeTransparent(); | |
| 770 m_overlayCastButton->setIsWanted(false); | |
| 771 } | |
| 772 | |
| 773 void MediaControls::startHideMediaControlsTimer() { | |
| 774 m_hideMediaControlsTimer.startOneShot( | |
| 775 timeWithoutMouseMovementBeforeHidingMediaControls, BLINK_FROM_HERE); | |
| 776 } | |
| 777 | |
| 778 void MediaControls::stopHideMediaControlsTimer() { | |
| 779 m_keepShowingUntilTimerFires = false; | |
| 780 m_hideMediaControlsTimer.stop(); | |
| 781 } | |
| 782 | |
| 783 void MediaControls::resetHideMediaControlsTimer() { | |
| 784 stopHideMediaControlsTimer(); | |
| 785 if (!mediaElement().paused()) | |
| 786 startHideMediaControlsTimer(); | |
| 787 } | |
| 788 | |
| 789 bool MediaControls::containsRelatedTarget(Event* event) { | |
| 790 if (!event->isMouseEvent()) | |
| 791 return false; | |
| 792 EventTarget* relatedTarget = toMouseEvent(event)->relatedTarget(); | |
| 793 if (!relatedTarget) | |
| 794 return false; | |
| 795 return contains(relatedTarget->toNode()); | |
| 796 } | |
| 797 | |
| 798 void MediaControls::onVolumeChange() { | |
| 799 m_muteButton->updateDisplayType(); | |
| 800 m_volumeSlider->setVolume(mediaElement().muted() ? 0 | |
| 801 : mediaElement().volume()); | |
| 802 | |
| 803 // Update visibility of volume controls. | |
| 804 // TODO(mlamouri): it should not be part of the volumechange handling because | |
| 805 // it is using audio availability as input. | |
| 806 BatchedControlUpdate batch(this); | |
| 807 m_volumeSlider->setIsWanted(mediaElement().hasAudio() && | |
| 808 !preferHiddenVolumeControls(document())); | |
| 809 m_muteButton->setIsWanted(mediaElement().hasAudio()); | |
| 810 } | |
| 811 | |
| 812 void MediaControls::onFocusIn() { | |
| 813 if (!mediaElement().shouldShowControls()) | |
| 814 return; | |
| 815 | |
| 816 show(); | |
| 817 resetHideMediaControlsTimer(); | |
| 818 } | |
| 819 | |
| 820 void MediaControls::onTimeUpdate() { | |
| 821 m_timeline->setPosition(mediaElement().currentTime()); | |
| 822 updateCurrentTimeDisplay(); | |
| 823 | |
| 824 // 'timeupdate' might be called in a paused state. The controls should not | |
| 825 // become transparent in that case. | |
| 826 if (mediaElement().paused()) { | |
| 827 makeOpaque(); | |
| 828 return; | |
| 829 } | |
| 830 | |
| 831 if (isVisible() && shouldHideMediaControls()) | |
| 832 makeTransparent(); | |
| 833 } | |
| 834 | |
| 835 void MediaControls::onDurationChange() { | |
| 836 const double duration = mediaElement().duration(); | |
| 837 | |
| 838 // Update the displayed current time/duration. | |
| 839 m_durationDisplay->setTextContent( | |
| 840 LayoutTheme::theme().formatMediaControlsTime(duration)); | |
| 841 m_durationDisplay->setCurrentValue(duration); | |
| 842 updateCurrentTimeDisplay(); | |
| 843 | |
| 844 // Update the timeline (the UI with the seek marker). | |
| 845 m_timeline->setDuration(duration); | |
| 846 } | |
| 847 | |
| 848 void MediaControls::onPlay() { | |
| 849 updatePlayState(); | |
| 850 m_timeline->setPosition(mediaElement().currentTime()); | |
| 851 updateCurrentTimeDisplay(); | |
| 852 | |
| 853 startHideMediaControlsTimer(); | |
| 854 } | |
| 855 | |
| 856 void MediaControls::onPause() { | |
| 857 updatePlayState(); | |
| 858 m_timeline->setPosition(mediaElement().currentTime()); | |
| 859 updateCurrentTimeDisplay(); | |
| 860 makeOpaque(); | |
| 861 | |
| 862 stopHideMediaControlsTimer(); | |
| 863 } | |
| 864 | |
| 865 void MediaControls::onTextTracksAddedOrRemoved() { | |
| 866 m_toggleClosedCaptionsButton->setIsWanted(mediaElement().hasClosedCaptions()); | |
| 867 BatchedControlUpdate batch(this); | |
| 868 } | |
| 869 | |
| 870 void MediaControls::onTextTracksChanged() { | |
| 871 m_toggleClosedCaptionsButton->updateDisplayType(); | |
| 872 } | |
| 873 | |
| 874 void MediaControls::onError() { | |
| 875 // TODO(mlamouri): we should only change the aspects of the control that need | |
| 876 // to be changed. | |
| 877 reset(); | |
| 878 } | |
| 879 | |
| 880 void MediaControls::onLoadedMetadata() { | |
| 881 // TODO(mlamouri): we should only change the aspects of the control that need | |
| 882 // to be changed. | |
| 883 reset(); | |
| 884 } | |
| 885 | |
| 886 void MediaControls::onEnteredFullscreen() { | |
| 887 m_fullscreenButton->setIsFullscreen(true); | |
| 888 stopHideMediaControlsTimer(); | |
| 889 startHideMediaControlsTimer(); | |
| 890 } | |
| 891 | |
| 892 void MediaControls::onExitedFullscreen() { | |
| 893 m_fullscreenButton->setIsFullscreen(false); | |
| 894 stopHideMediaControlsTimer(); | |
| 895 startHideMediaControlsTimer(); | |
| 896 } | |
| 897 | |
| 898 void MediaControls::notifyElementSizeChanged(ClientRect* newSize) { | |
| 899 // Note that this code permits a bad frame on resize, since it is | |
| 900 // run after the relayout / paint happens. It would be great to improve | |
| 901 // this, but it would be even greater to move this code entirely to | |
| 902 // JS and fix it there. | |
| 903 | |
| 904 IntSize oldSize = m_size; | |
| 905 m_size.setWidth(newSize->width()); | |
| 906 m_size.setHeight(newSize->height()); | |
| 907 | |
| 908 // Adjust for effective zoom. | |
| 909 if (m_panel->layoutObject() && m_panel->layoutObject()->style()) { | |
| 910 m_size.setWidth(ceil(m_size.width() / | |
| 911 m_panel->layoutObject()->style()->effectiveZoom())); | |
| 912 m_size.setHeight(ceil(m_size.height() / | |
| 913 m_panel->layoutObject()->style()->effectiveZoom())); | |
| 914 } | |
| 915 | |
| 916 // Don't bother to do any work if this matches the most recent size. | |
| 917 if (oldSize != m_size) | |
| 918 m_elementSizeChangedTimer.startOneShot(0, BLINK_FROM_HERE); | |
| 919 } | |
| 920 | |
| 921 void MediaControls::elementSizeChangedTimerFired(TimerBase*) { | |
| 922 computeWhichControlsFit(); | |
| 923 } | |
| 924 | |
| 925 void MediaControls::computeWhichControlsFit() { | |
| 926 // Hide all controls that don't fit, and show the ones that do. | |
| 927 // This might be better suited for a layout, but since JS media controls | |
| 928 // won't benefit from that anwyay, we just do it here like JS will. | |
| 929 | |
| 930 // Controls that we'll hide / show, in order of decreasing priority. | |
| 931 MediaControlElement* elements[] = { | |
| 932 // Exclude m_overflowMenu; we handle it specially. | |
| 933 m_playButton.get(), | |
| 934 m_fullscreenButton.get(), | |
| 935 m_downloadButton.get(), | |
| 936 m_timeline.get(), | |
| 937 m_muteButton.get(), | |
| 938 m_volumeSlider.get(), | |
| 939 m_toggleClosedCaptionsButton.get(), | |
| 940 m_castButton.get(), | |
| 941 m_currentTimeDisplay.get(), | |
| 942 m_durationDisplay.get(), | |
| 943 }; | |
| 944 | |
| 945 // TODO(mlamouri): we need a more dynamic way to find out the width of an | |
| 946 // element. | |
| 947 const int sliderMargin = 36; // Sliders have 18px margin on each side. | |
| 948 | |
| 949 if (!m_size.width()) { | |
| 950 // No layout yet -- hide everything, then make them show up later. | |
| 951 // This prevents the wrong controls from being shown briefly | |
| 952 // immediately after the first layout and paint, but before we have | |
| 953 // a chance to revise them. | |
| 954 for (MediaControlElement* element : elements) { | |
| 955 if (element) | |
| 956 element->setDoesFit(false); | |
| 957 } | |
| 958 return; | |
| 959 } | |
| 960 | |
| 961 // Assume that all controls require 48px, unless we can get the computed | |
| 962 // style for a button. The minimumWidth is recorded and re-use for future | |
| 963 // MediaControls instances and future calls to this method given that at the | |
| 964 // moment the controls button width is per plataform. | |
| 965 // TODO(mlamouri): improve the mechanism without bandaid. | |
| 966 static int minimumWidth = 48; | |
| 967 if (m_playButton->layoutObject() && m_playButton->layoutObject()->style()) { | |
| 968 const ComputedStyle* style = m_playButton->layoutObject()->style(); | |
| 969 minimumWidth = ceil(style->width().pixels() / style->effectiveZoom()); | |
| 970 } else if (m_overflowMenu->layoutObject() && | |
| 971 m_overflowMenu->layoutObject()->style()) { | |
| 972 const ComputedStyle* style = m_overflowMenu->layoutObject()->style(); | |
| 973 minimumWidth = ceil(style->width().pixels() / style->effectiveZoom()); | |
| 974 } | |
| 975 | |
| 976 // Insert an overflow menu. However, if we see that the overflow menu | |
| 977 // doesn't end up containing at least two elements, we will not display it | |
| 978 // but instead make place for the first element that was dropped. | |
| 979 m_overflowMenu->setDoesFit(true); | |
| 980 m_overflowMenu->setIsWanted(true); | |
| 981 int usedWidth = minimumWidth; | |
| 982 | |
| 983 std::list<MediaControlElement*> overflowElements; | |
| 984 MediaControlElement* firstDisplacedElement = nullptr; | |
| 985 // For each control that fits, enable it in order of decreasing priority. | |
| 986 for (MediaControlElement* element : elements) { | |
| 987 if (!element) | |
| 988 continue; | |
| 989 int width = minimumWidth; | |
| 990 if ((element == m_timeline.get()) || (element == m_volumeSlider.get())) | |
| 991 width += sliderMargin; | |
| 992 element->shouldShowButtonInOverflowMenu(false); | |
| 993 if (element->isWanted()) { | |
| 994 if (usedWidth + width <= m_size.width()) { | |
| 995 element->setDoesFit(true); | |
| 996 usedWidth += width; | |
| 997 } else { | |
| 998 element->setDoesFit(false); | |
| 999 element->shouldShowButtonInOverflowMenu(true); | |
| 1000 if (element->hasOverflowButton()) | |
| 1001 overflowElements.push_front(element); | |
| 1002 // We want a way to access the first media element that was | |
| 1003 // removed. If we don't end up needing an overflow menu, we can | |
| 1004 // use the space the overflow menu would have taken up to | |
| 1005 // instead display that media element. | |
| 1006 if (!element->hasOverflowButton() && !firstDisplacedElement) | |
| 1007 firstDisplacedElement = element; | |
| 1008 } | |
| 1009 } | |
| 1010 } | |
| 1011 | |
| 1012 // If we don't have at least two overflow elements, we will not show the | |
| 1013 // overflow menu. | |
| 1014 if (overflowElements.empty()) { | |
| 1015 m_overflowMenu->setIsWanted(false); | |
| 1016 usedWidth -= minimumWidth; | |
| 1017 if (firstDisplacedElement) { | |
| 1018 int width = minimumWidth; | |
| 1019 if ((firstDisplacedElement == m_timeline.get()) || | |
| 1020 (firstDisplacedElement == m_volumeSlider.get())) | |
| 1021 width += sliderMargin; | |
| 1022 if (usedWidth + width <= m_size.width()) | |
| 1023 firstDisplacedElement->setDoesFit(true); | |
| 1024 } | |
| 1025 } else if (overflowElements.size() == 1) { | |
| 1026 m_overflowMenu->setIsWanted(false); | |
| 1027 overflowElements.front()->setDoesFit(true); | |
| 1028 } | |
| 1029 | |
| 1030 // Decide if the overlay play button fits. | |
| 1031 if (m_overlayPlayButton) { | |
| 1032 bool doesFit = m_size.width() >= kMinWidthForOverlayPlayButton && | |
| 1033 m_size.height() >= kMinHeightForOverlayPlayButton; | |
| 1034 m_overlayPlayButton->setDoesFit(doesFit); | |
| 1035 } | |
| 1036 } | |
| 1037 | |
| 1038 void MediaControls::invalidate(Element* element) { | |
| 1039 if (!element) | |
| 1040 return; | |
| 1041 | |
| 1042 if (LayoutObject* layoutObject = element->layoutObject()) | |
| 1043 layoutObject | |
| 1044 ->setShouldDoFullPaintInvalidationIncludingNonCompositingDescendants(); | |
| 1045 } | |
| 1046 | |
| 1047 void MediaControls::networkStateChanged() { | |
| 1048 invalidate(m_playButton); | |
| 1049 invalidate(m_overlayPlayButton); | |
| 1050 invalidate(m_muteButton); | |
| 1051 invalidate(m_fullscreenButton); | |
| 1052 invalidate(m_downloadButton); | |
| 1053 invalidate(m_timeline); | |
| 1054 invalidate(m_volumeSlider); | |
| 1055 | |
| 1056 // Update the display state of the download button in case we now have a | |
| 1057 // source or no longer have a source. | |
| 1058 m_downloadButton->setIsWanted( | |
| 1059 m_downloadButton->shouldDisplayDownloadButton()); | |
| 1060 } | |
| 1061 | |
| 1062 bool MediaControls::overflowMenuVisible() { | |
| 1063 return m_overflowList ? m_overflowList->isWanted() : false; | |
| 1064 } | |
| 1065 | |
| 1066 void MediaControls::toggleOverflowMenu() { | |
| 1067 DCHECK(m_overflowList); | |
| 1068 | |
| 1069 if (!m_overflowList->isWanted()) | |
| 1070 m_windowEventListener->start(); | |
| 1071 m_overflowList->setIsWanted(!m_overflowList->isWanted()); | |
| 1072 } | |
| 1073 | |
| 1074 void MediaControls::hideAllMenus() { | |
| 1075 m_windowEventListener->stop(); | |
| 1076 | |
| 1077 if (m_overflowList->isWanted()) | |
| 1078 m_overflowList->setIsWanted(false); | |
| 1079 if (m_textTrackList->isWanted()) | |
| 1080 m_textTrackList->setVisible(false); | |
| 1081 } | |
| 1082 | |
| 1083 DEFINE_TRACE(MediaControls) { | |
| 1084 visitor->trace(m_resizeObserver); | |
| 1085 visitor->trace(m_mediaElement); | |
| 1086 visitor->trace(m_panel); | |
| 1087 visitor->trace(m_overlayPlayButton); | |
| 1088 visitor->trace(m_overlayEnclosure); | |
| 1089 visitor->trace(m_playButton); | |
| 1090 visitor->trace(m_currentTimeDisplay); | |
| 1091 visitor->trace(m_timeline); | |
| 1092 visitor->trace(m_muteButton); | |
| 1093 visitor->trace(m_volumeSlider); | |
| 1094 visitor->trace(m_toggleClosedCaptionsButton); | |
| 1095 visitor->trace(m_fullscreenButton); | |
| 1096 visitor->trace(m_downloadButton); | |
| 1097 visitor->trace(m_durationDisplay); | |
| 1098 visitor->trace(m_enclosure); | |
| 1099 visitor->trace(m_textTrackList); | |
| 1100 visitor->trace(m_overflowMenu); | |
| 1101 visitor->trace(m_overflowList); | |
| 1102 visitor->trace(m_castButton); | |
| 1103 visitor->trace(m_overlayCastButton); | |
| 1104 visitor->trace(m_mediaEventListener); | |
| 1105 visitor->trace(m_windowEventListener); | |
| 1106 visitor->trace(m_orientationLockDelegate); | |
| 1107 HTMLDivElement::trace(visitor); | |
| 1108 } | |
| 1109 | |
| 1110 } // namespace blink | |
| OLD | NEW |