OLD | NEW |
(Empty) | |
| 1 // Copyright 2017 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 #include "modules/media_controls/MediaControlsRotateToFullscreenDelegate.h" |
| 6 |
| 7 #include "core/dom/DocumentUserGestureToken.h" |
| 8 #include "core/dom/ElementVisibilityObserver.h" |
| 9 #include "core/dom/Fullscreen.h" |
| 10 #include "core/events/Event.h" |
| 11 #include "core/frame/LocalDOMWindow.h" |
| 12 #include "core/html/HTMLVideoElement.h" |
| 13 #include "core/page/ChromeClient.h" |
| 14 #include "modules/media_controls/MediaControlsImpl.h" |
| 15 #include "platform/UserGestureIndicator.h" |
| 16 #include "public/platform/WebScreenInfo.h" |
| 17 |
| 18 namespace blink { |
| 19 |
| 20 namespace { |
| 21 |
| 22 // Videos must be at least this big in both dimensions to qualify. |
| 23 constexpr unsigned kMinSize = 200; |
| 24 |
| 25 // At least this fraction of the video must be visible. |
| 26 constexpr float kVisibilityThreshold = 0.75; |
| 27 |
| 28 } // anonymous namespace |
| 29 |
| 30 MediaControlsRotateToFullscreenDelegate:: |
| 31 MediaControlsRotateToFullscreenDelegate(HTMLVideoElement& video) |
| 32 : EventListener(kCPPEventListenerType), video_element_(video) {} |
| 33 |
| 34 void MediaControlsRotateToFullscreenDelegate::Attach() { |
| 35 DCHECK(video_element_->isConnected()); |
| 36 |
| 37 LocalDOMWindow* dom_window = video_element_->GetDocument().domWindow(); |
| 38 if (!dom_window) |
| 39 return; |
| 40 |
| 41 video_element_->addEventListener(EventTypeNames::play, this, true); |
| 42 video_element_->addEventListener(EventTypeNames::pause, this, true); |
| 43 |
| 44 // Listen to two different fullscreen events in order to make sure the new and |
| 45 // old APIs are handled. |
| 46 video_element_->addEventListener(EventTypeNames::webkitfullscreenchange, this, |
| 47 true); |
| 48 video_element_->GetDocument().addEventListener( |
| 49 EventTypeNames::fullscreenchange, this, true); |
| 50 |
| 51 current_screen_orientation_ = ComputeScreenOrientation(); |
| 52 // TODO(johnme): Check this is battery efficient (note that this doesn't need |
| 53 // to receive events for 180 deg rotations). |
| 54 dom_window->addEventListener(EventTypeNames::orientationchange, this, false); |
| 55 } |
| 56 |
| 57 void MediaControlsRotateToFullscreenDelegate::Detach() { |
| 58 DCHECK(!video_element_->isConnected()); |
| 59 |
| 60 if (visibility_observer_) { |
| 61 // TODO(johnme): Should I also call Stop in a prefinalizer? |
| 62 visibility_observer_->Stop(); |
| 63 visibility_observer_ = nullptr; |
| 64 is_visible_ = false; |
| 65 } |
| 66 |
| 67 video_element_->removeEventListener(EventTypeNames::play, this, true); |
| 68 video_element_->removeEventListener(EventTypeNames::pause, this, true); |
| 69 |
| 70 video_element_->removeEventListener(EventTypeNames::webkitfullscreenchange, |
| 71 this, true); |
| 72 video_element_->GetDocument().removeEventListener( |
| 73 EventTypeNames::fullscreenchange, this, true); |
| 74 |
| 75 LocalDOMWindow* dom_window = video_element_->GetDocument().domWindow(); |
| 76 if (!dom_window) |
| 77 return; |
| 78 dom_window->removeEventListener(EventTypeNames::orientationchange, this, |
| 79 false); |
| 80 } |
| 81 |
| 82 bool MediaControlsRotateToFullscreenDelegate::operator==( |
| 83 const EventListener& other) const { |
| 84 return this == &other; |
| 85 } |
| 86 |
| 87 void MediaControlsRotateToFullscreenDelegate::handleEvent( |
| 88 ExecutionContext* execution_context, |
| 89 Event* event) { |
| 90 if (event->type() == EventTypeNames::play || |
| 91 event->type() == EventTypeNames::pause || |
| 92 event->type() == EventTypeNames::fullscreenchange || |
| 93 event->type() == EventTypeNames::webkitfullscreenchange) { |
| 94 OnStateChange(); |
| 95 return; |
| 96 } |
| 97 if (event->type() == EventTypeNames::orientationchange) { |
| 98 OnScreenOrientationChange(); |
| 99 return; |
| 100 } |
| 101 |
| 102 NOTREACHED(); |
| 103 } |
| 104 |
| 105 void MediaControlsRotateToFullscreenDelegate::OnStateChange() { |
| 106 // TODO(johnme): Check this aggressive disabling doesn't lead to race |
| 107 // conditions where we briefly don't know if the video is visible. |
| 108 bool needs_visibility_observer = |
| 109 !video_element_->paused() && !video_element_->IsFullscreen(); |
| 110 DVLOG(3) << __func__ << " " << !!visibility_observer_ << " -> " |
| 111 << needs_visibility_observer; |
| 112 |
| 113 if (needs_visibility_observer && !visibility_observer_) { |
| 114 visibility_observer_ = new ElementVisibilityObserver( |
| 115 video_element_, |
| 116 WTF::Bind(&MediaControlsRotateToFullscreenDelegate::OnVisibilityChange, |
| 117 WrapWeakPersistent(this))); |
| 118 visibility_observer_->Start(kVisibilityThreshold); |
| 119 } else if (!needs_visibility_observer && visibility_observer_) { |
| 120 visibility_observer_->Stop(); |
| 121 visibility_observer_ = nullptr; |
| 122 is_visible_ = false; |
| 123 } |
| 124 } |
| 125 |
| 126 void MediaControlsRotateToFullscreenDelegate::OnVisibilityChange( |
| 127 bool is_visible) { |
| 128 DVLOG(3) << __func__ << " " << is_visible_ << " -> " << is_visible; |
| 129 is_visible_ = is_visible; |
| 130 } |
| 131 |
| 132 void MediaControlsRotateToFullscreenDelegate::OnScreenOrientationChange() { |
| 133 SimpleOrientation previous_screen_orientation = current_screen_orientation_; |
| 134 current_screen_orientation_ = ComputeScreenOrientation(); |
| 135 DVLOG(3) << __func__ << " " << static_cast<int>(previous_screen_orientation) |
| 136 << " -> " << static_cast<int>(current_screen_orientation_); |
| 137 |
| 138 // Only enable if native media controls are used. |
| 139 if (!video_element_->ShouldShowControls()) |
| 140 return; |
| 141 |
| 142 // Don't enter/exit fullscreen if some other element is fullscreen. |
| 143 Element* fullscreen_element = |
| 144 Fullscreen::CurrentFullScreenElementFrom(video_element_->GetDocument()); |
| 145 if (fullscreen_element && fullscreen_element != video_element_) |
| 146 return; |
| 147 |
| 148 // To enter fullscreen, video must be visible and playing. |
| 149 // TODO(johnme): If orientation changes whilst this tab is in the background, |
| 150 // we'll get an orientationchange event when this tab next becomes active. |
| 151 // Check that those events don't trigger rotate-to-fullscreen. |
| 152 if (!video_element_->IsFullscreen() && |
| 153 (!is_visible_ || video_element_->paused())) { |
| 154 return; |
| 155 } |
| 156 |
| 157 // Ignore (unexpected) events where we have incomplete information. |
| 158 if (previous_screen_orientation == SimpleOrientation::kUnknown || |
| 159 current_screen_orientation_ == SimpleOrientation::kUnknown) { |
| 160 return; |
| 161 } |
| 162 |
| 163 // Ignore 180 degree rotations between PortraitPrimary and PortraitSecondary, |
| 164 // or between LandscapePrimary and LandscapeSecondary. |
| 165 if (previous_screen_orientation == current_screen_orientation_) |
| 166 return; |
| 167 |
| 168 SimpleOrientation video_orientation = ComputeVideoOrientation(); |
| 169 |
| 170 // Ignore videos that are square/small/etc. |
| 171 if (video_orientation == SimpleOrientation::kUnknown) |
| 172 return; |
| 173 |
| 174 MediaControlsImpl& media_controls = |
| 175 *static_cast<MediaControlsImpl*>(video_element_->GetMediaControls()); |
| 176 |
| 177 { |
| 178 UserGestureIndicator gesture( |
| 179 DocumentUserGestureToken::Create(&video_element_->GetDocument())); |
| 180 |
| 181 bool should_be_fullscreen = |
| 182 current_screen_orientation_ == video_orientation; |
| 183 if (should_be_fullscreen && !video_element_->IsFullscreen()) |
| 184 media_controls.EnterFullscreen(); |
| 185 else if (!should_be_fullscreen && video_element_->IsFullscreen()) |
| 186 media_controls.ExitFullscreen(); |
| 187 } |
| 188 } |
| 189 |
| 190 MediaControlsRotateToFullscreenDelegate::SimpleOrientation |
| 191 MediaControlsRotateToFullscreenDelegate::ComputeVideoOrientation() const { |
| 192 if (video_element_->getReadyState() == HTMLMediaElement::kHaveNothing) |
| 193 return SimpleOrientation::kUnknown; |
| 194 |
| 195 const unsigned width = video_element_->videoWidth(); |
| 196 const unsigned height = video_element_->videoHeight(); |
| 197 |
| 198 if (width < kMinSize || height < kMinSize) |
| 199 return SimpleOrientation::kUnknown; // Too small, ignore this video. |
| 200 |
| 201 if (width >= height) |
| 202 return SimpleOrientation::kLandscape; // Includes square videos. |
| 203 return SimpleOrientation::kPortrait; |
| 204 } |
| 205 |
| 206 MediaControlsRotateToFullscreenDelegate::SimpleOrientation |
| 207 MediaControlsRotateToFullscreenDelegate::ComputeScreenOrientation() const { |
| 208 Frame* frame = video_element_->GetDocument().GetFrame(); |
| 209 if (!frame) |
| 210 return SimpleOrientation::kUnknown; |
| 211 |
| 212 switch (frame->GetChromeClient().GetScreenInfo().orientation_type) { |
| 213 case kWebScreenOrientationPortraitPrimary: |
| 214 case kWebScreenOrientationPortraitSecondary: |
| 215 return SimpleOrientation::kPortrait; |
| 216 case kWebScreenOrientationLandscapePrimary: |
| 217 case kWebScreenOrientationLandscapeSecondary: |
| 218 return SimpleOrientation::kLandscape; |
| 219 case kWebScreenOrientationUndefined: |
| 220 return SimpleOrientation::kUnknown; |
| 221 } |
| 222 |
| 223 NOTREACHED(); |
| 224 return SimpleOrientation::kUnknown; |
| 225 } |
| 226 |
| 227 DEFINE_TRACE(MediaControlsRotateToFullscreenDelegate) { |
| 228 EventListener::Trace(visitor); |
| 229 visitor->Trace(video_element_); |
| 230 visitor->Trace(visibility_observer_); |
| 231 } |
| 232 |
| 233 } // namespace blink |
OLD | NEW |