Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(521)

Side by Side Diff: third_party/WebKit/Source/core/html/shadow/MediaControls.cpp

Issue 2795783004: Move core MediaControls implementation to modules/media_controls/. (Closed)
Patch Set: rebase Created 3 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(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
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698