Index: third_party/WebKit/Source/modules/media_controls/MediaControlsRotateToFullscreenDelegate.cpp |
diff --git a/third_party/WebKit/Source/modules/media_controls/MediaControlsRotateToFullscreenDelegate.cpp b/third_party/WebKit/Source/modules/media_controls/MediaControlsRotateToFullscreenDelegate.cpp |
new file mode 100644 |
index 0000000000000000000000000000000000000000..adee413a406a4faab29c31d0bf50bea83e861ec8 |
--- /dev/null |
+++ b/third_party/WebKit/Source/modules/media_controls/MediaControlsRotateToFullscreenDelegate.cpp |
@@ -0,0 +1,233 @@ |
+// Copyright 2017 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+#include "modules/media_controls/MediaControlsRotateToFullscreenDelegate.h" |
+ |
+#include "core/dom/DocumentUserGestureToken.h" |
+#include "core/dom/ElementVisibilityObserver.h" |
+#include "core/dom/Fullscreen.h" |
+#include "core/events/Event.h" |
+#include "core/frame/LocalDOMWindow.h" |
+#include "core/html/HTMLVideoElement.h" |
+#include "core/page/ChromeClient.h" |
+#include "modules/media_controls/MediaControlsImpl.h" |
+#include "platform/UserGestureIndicator.h" |
+#include "public/platform/WebScreenInfo.h" |
+ |
+namespace blink { |
+ |
+namespace { |
+ |
+// Videos must be at least this big in both dimensions to qualify. |
+constexpr unsigned kMinSize = 200; |
+ |
+// At least this fraction of the video must be visible. |
+constexpr float kVisibilityThreshold = 0.75; |
+ |
+} // anonymous namespace |
+ |
+MediaControlsRotateToFullscreenDelegate:: |
+ MediaControlsRotateToFullscreenDelegate(HTMLVideoElement& video) |
+ : EventListener(kCPPEventListenerType), video_element_(video) {} |
+ |
+void MediaControlsRotateToFullscreenDelegate::Attach() { |
+ DCHECK(video_element_->isConnected()); |
+ |
+ LocalDOMWindow* dom_window = video_element_->GetDocument().domWindow(); |
+ if (!dom_window) |
+ return; |
+ |
+ video_element_->addEventListener(EventTypeNames::play, this, true); |
+ video_element_->addEventListener(EventTypeNames::pause, this, true); |
+ |
+ // Listen to two different fullscreen events in order to make sure the new and |
+ // old APIs are handled. |
+ video_element_->addEventListener(EventTypeNames::webkitfullscreenchange, this, |
+ true); |
+ video_element_->GetDocument().addEventListener( |
+ EventTypeNames::fullscreenchange, this, true); |
+ |
+ current_screen_orientation_ = ComputeScreenOrientation(); |
+ // TODO(johnme): Check this is battery efficient (note that this doesn't need |
+ // to receive events for 180 deg rotations). |
+ dom_window->addEventListener(EventTypeNames::orientationchange, this, false); |
+} |
+ |
+void MediaControlsRotateToFullscreenDelegate::Detach() { |
+ DCHECK(!video_element_->isConnected()); |
+ |
+ if (visibility_observer_) { |
+ // TODO(johnme): Should I also call Stop in a prefinalizer? |
+ visibility_observer_->Stop(); |
+ visibility_observer_ = nullptr; |
+ is_visible_ = false; |
+ } |
+ |
+ video_element_->removeEventListener(EventTypeNames::play, this, true); |
+ video_element_->removeEventListener(EventTypeNames::pause, this, true); |
+ |
+ video_element_->removeEventListener(EventTypeNames::webkitfullscreenchange, |
+ this, true); |
+ video_element_->GetDocument().removeEventListener( |
+ EventTypeNames::fullscreenchange, this, true); |
+ |
+ LocalDOMWindow* dom_window = video_element_->GetDocument().domWindow(); |
+ if (!dom_window) |
+ return; |
+ dom_window->removeEventListener(EventTypeNames::orientationchange, this, |
+ false); |
+} |
+ |
+bool MediaControlsRotateToFullscreenDelegate::operator==( |
+ const EventListener& other) const { |
+ return this == &other; |
+} |
+ |
+void MediaControlsRotateToFullscreenDelegate::handleEvent( |
+ ExecutionContext* execution_context, |
+ Event* event) { |
+ if (event->type() == EventTypeNames::play || |
+ event->type() == EventTypeNames::pause || |
+ event->type() == EventTypeNames::fullscreenchange || |
+ event->type() == EventTypeNames::webkitfullscreenchange) { |
+ OnStateChange(); |
+ return; |
+ } |
+ if (event->type() == EventTypeNames::orientationchange) { |
+ OnScreenOrientationChange(); |
+ return; |
+ } |
+ |
+ NOTREACHED(); |
+} |
+ |
+void MediaControlsRotateToFullscreenDelegate::OnStateChange() { |
+ // TODO(johnme): Check this aggressive disabling doesn't lead to race |
+ // conditions where we briefly don't know if the video is visible. |
+ bool needs_visibility_observer = |
+ !video_element_->paused() && !video_element_->IsFullscreen(); |
+ DVLOG(3) << __func__ << " " << !!visibility_observer_ << " -> " |
+ << needs_visibility_observer; |
+ |
+ if (needs_visibility_observer && !visibility_observer_) { |
+ visibility_observer_ = new ElementVisibilityObserver( |
+ video_element_, |
+ WTF::Bind(&MediaControlsRotateToFullscreenDelegate::OnVisibilityChange, |
+ WrapWeakPersistent(this))); |
+ visibility_observer_->Start(kVisibilityThreshold); |
+ } else if (!needs_visibility_observer && visibility_observer_) { |
+ visibility_observer_->Stop(); |
+ visibility_observer_ = nullptr; |
+ is_visible_ = false; |
+ } |
+} |
+ |
+void MediaControlsRotateToFullscreenDelegate::OnVisibilityChange( |
+ bool is_visible) { |
+ DVLOG(3) << __func__ << " " << is_visible_ << " -> " << is_visible; |
+ is_visible_ = is_visible; |
+} |
+ |
+void MediaControlsRotateToFullscreenDelegate::OnScreenOrientationChange() { |
+ SimpleOrientation previous_screen_orientation = current_screen_orientation_; |
+ current_screen_orientation_ = ComputeScreenOrientation(); |
+ DVLOG(3) << __func__ << " " << static_cast<int>(previous_screen_orientation) |
+ << " -> " << static_cast<int>(current_screen_orientation_); |
+ |
+ // Only enable if native media controls are used. |
+ if (!video_element_->ShouldShowControls()) |
+ return; |
+ |
+ // Don't enter/exit fullscreen if some other element is fullscreen. |
+ Element* fullscreen_element = |
+ Fullscreen::CurrentFullScreenElementFrom(video_element_->GetDocument()); |
+ if (fullscreen_element && fullscreen_element != video_element_) |
+ return; |
+ |
+ // To enter fullscreen, video must be visible and playing. |
+ // TODO(johnme): If orientation changes whilst this tab is in the background, |
+ // we'll get an orientationchange event when this tab next becomes active. |
+ // Check that those events don't trigger rotate-to-fullscreen. |
+ if (!video_element_->IsFullscreen() && |
+ (!is_visible_ || video_element_->paused())) { |
+ return; |
+ } |
+ |
+ // Ignore (unexpected) events where we have incomplete information. |
+ if (previous_screen_orientation == SimpleOrientation::kUnknown || |
+ current_screen_orientation_ == SimpleOrientation::kUnknown) { |
+ return; |
+ } |
+ |
+ // Ignore 180 degree rotations between PortraitPrimary and PortraitSecondary, |
+ // or between LandscapePrimary and LandscapeSecondary. |
+ if (previous_screen_orientation == current_screen_orientation_) |
+ return; |
+ |
+ SimpleOrientation video_orientation = ComputeVideoOrientation(); |
+ |
+ // Ignore videos that are square/small/etc. |
+ if (video_orientation == SimpleOrientation::kUnknown) |
+ return; |
+ |
+ MediaControlsImpl& media_controls = |
+ *static_cast<MediaControlsImpl*>(video_element_->GetMediaControls()); |
+ |
+ { |
+ UserGestureIndicator gesture( |
+ DocumentUserGestureToken::Create(&video_element_->GetDocument())); |
+ |
+ bool should_be_fullscreen = |
+ current_screen_orientation_ == video_orientation; |
+ if (should_be_fullscreen && !video_element_->IsFullscreen()) |
+ media_controls.EnterFullscreen(); |
+ else if (!should_be_fullscreen && video_element_->IsFullscreen()) |
+ media_controls.ExitFullscreen(); |
+ } |
+} |
+ |
+MediaControlsRotateToFullscreenDelegate::SimpleOrientation |
+MediaControlsRotateToFullscreenDelegate::ComputeVideoOrientation() const { |
+ if (video_element_->getReadyState() == HTMLMediaElement::kHaveNothing) |
+ return SimpleOrientation::kUnknown; |
+ |
+ const unsigned width = video_element_->videoWidth(); |
+ const unsigned height = video_element_->videoHeight(); |
+ |
+ if (width < kMinSize || height < kMinSize) |
+ return SimpleOrientation::kUnknown; // Too small, ignore this video. |
+ |
+ if (width >= height) |
+ return SimpleOrientation::kLandscape; // Includes square videos. |
+ return SimpleOrientation::kPortrait; |
+} |
+ |
+MediaControlsRotateToFullscreenDelegate::SimpleOrientation |
+MediaControlsRotateToFullscreenDelegate::ComputeScreenOrientation() const { |
+ Frame* frame = video_element_->GetDocument().GetFrame(); |
+ if (!frame) |
+ return SimpleOrientation::kUnknown; |
+ |
+ switch (frame->GetChromeClient().GetScreenInfo().orientation_type) { |
+ case kWebScreenOrientationPortraitPrimary: |
+ case kWebScreenOrientationPortraitSecondary: |
+ return SimpleOrientation::kPortrait; |
+ case kWebScreenOrientationLandscapePrimary: |
+ case kWebScreenOrientationLandscapeSecondary: |
+ return SimpleOrientation::kLandscape; |
+ case kWebScreenOrientationUndefined: |
+ return SimpleOrientation::kUnknown; |
+ } |
+ |
+ NOTREACHED(); |
+ return SimpleOrientation::kUnknown; |
+} |
+ |
+DEFINE_TRACE(MediaControlsRotateToFullscreenDelegate) { |
+ EventListener::Trace(visitor); |
+ visitor->Trace(video_element_); |
+ visitor->Trace(visibility_observer_); |
+} |
+ |
+} // namespace blink |