OLD | NEW |
1 // Copyright 2016 The Chromium Authors. All rights reserved. | 1 // Copyright 2016 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "modules/media_controls/MediaControlsOrientationLockDelegate.h" | 5 #include "modules/media_controls/MediaControlsOrientationLockDelegate.h" |
6 | 6 |
7 #include "core/events/Event.h" | 7 #include "core/events/Event.h" |
| 8 #include "core/frame/LocalDOMWindow.h" |
| 9 #include "core/frame/Screen.h" |
8 #include "core/frame/ScreenOrientationController.h" | 10 #include "core/frame/ScreenOrientationController.h" |
9 #include "core/html/HTMLVideoElement.h" | 11 #include "core/html/HTMLVideoElement.h" |
10 #include "core/page/ChromeClient.h" | 12 #include "core/page/ChromeClient.h" |
| 13 #include "modules/device_orientation/DeviceOrientationData.h" |
| 14 #include "modules/device_orientation/DeviceOrientationEvent.h" |
| 15 #include "modules/screen_orientation/ScreenOrientation.h" |
| 16 #include "modules/screen_orientation/ScreenScreenOrientation.h" |
11 #include "platform/Histogram.h" | 17 #include "platform/Histogram.h" |
| 18 #include "platform/RuntimeEnabledFeatures.h" |
| 19 #include "platform/wtf/Functional.h" |
| 20 #include "platform/wtf/MathExtras.h" |
12 #include "public/platform/WebScreenInfo.h" | 21 #include "public/platform/WebScreenInfo.h" |
13 #include "public/platform/modules/screen_orientation/WebLockOrientationCallback.
h" | 22 #include "public/platform/modules/screen_orientation/WebLockOrientationCallback.
h" |
14 | 23 |
| 24 #if OS(ANDROID) |
| 25 #include "platform/mojo/MojoHelper.h" |
| 26 #include "public/platform/Platform.h" |
| 27 #include "services/device/public/interfaces/constants.mojom-blink.h" |
| 28 #include "services/service_manager/public/cpp/connector.h" |
| 29 #endif // OS(ANDROID) |
| 30 |
| 31 #undef atan2 // to use std::atan2 instead of wtf_atan2 |
| 32 #undef fmod // to use std::fmod instead of wtf_fmod |
| 33 #include <cmath> |
| 34 |
15 namespace blink { | 35 namespace blink { |
16 | 36 |
17 namespace { | 37 namespace { |
18 | 38 |
19 // These values are used for histograms. Do not reorder. | 39 // These values are used for histograms. Do not reorder. |
20 enum class MetadataAvailabilityMetrics { | 40 enum class MetadataAvailabilityMetrics { |
21 kAvailable = 0, // Available when lock was attempted. | 41 kAvailable = 0, // Available when lock was attempted. |
22 kMissing = 1, // Missing when lock was attempted. | 42 kMissing = 1, // Missing when lock was attempted. |
23 kReceived = 2, // Received after being missing in order to lock. | 43 kReceived = 2, // Received after being missing in order to lock. |
24 | 44 |
(...skipping 87 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
112 if (!GetDocument().GetFrame()) | 132 if (!GetDocument().GetFrame()) |
113 return; | 133 return; |
114 | 134 |
115 auto controller = | 135 auto controller = |
116 ScreenOrientationController::From(*GetDocument().GetFrame()); | 136 ScreenOrientationController::From(*GetDocument().GetFrame()); |
117 if (controller->MaybeHasActiveLock()) { | 137 if (controller->MaybeHasActiveLock()) { |
118 RecordLockResult(LockResultMetrics::kAlreadyLocked); | 138 RecordLockResult(LockResultMetrics::kAlreadyLocked); |
119 return; | 139 return; |
120 } | 140 } |
121 | 141 |
122 WebScreenOrientationLockType orientation_lock = ComputeOrientationLock(); | 142 locked_orientation_ = ComputeOrientationLock(); |
123 controller->lock(orientation_lock, | 143 DCHECK_NE(locked_orientation_, kWebScreenOrientationLockDefault); |
| 144 controller->lock(locked_orientation_, |
124 WTF::WrapUnique(new DummyScreenOrientationCallback)); | 145 WTF::WrapUnique(new DummyScreenOrientationCallback)); |
125 should_unlock_orientation_ = true; | |
126 | 146 |
127 if (orientation_lock == kWebScreenOrientationLockLandscape) | 147 if (locked_orientation_ == kWebScreenOrientationLockLandscape) |
128 RecordLockResult(LockResultMetrics::kLandscape); | 148 RecordLockResult(LockResultMetrics::kLandscape); |
129 else | 149 else |
130 RecordLockResult(LockResultMetrics::kPortrait); | 150 RecordLockResult(LockResultMetrics::kPortrait); |
| 151 |
| 152 MaybeListenToDeviceOrientation(); |
131 } | 153 } |
132 | 154 |
133 void MediaControlsOrientationLockDelegate::MaybeUnlockOrientation() { | 155 void MediaControlsOrientationLockDelegate::MaybeUnlockOrientation() { |
134 DCHECK(state_ != State::kPendingFullscreen); | 156 DCHECK(state_ != State::kPendingFullscreen); |
135 | 157 |
136 state_ = State::kPendingFullscreen; | 158 state_ = State::kPendingFullscreen; |
137 | 159 |
138 if (!should_unlock_orientation_) | 160 if (locked_orientation_ == kWebScreenOrientationLockDefault /* unlocked */) |
139 return; | 161 return; |
140 | 162 |
| 163 monitor_.reset(); // Cancel any GotIsAutoRotateEnabledByUser Mojo callback. |
| 164 if (LocalDOMWindow* dom_window = GetDocument().domWindow()) { |
| 165 dom_window->removeEventListener(EventTypeNames::deviceorientation, this, |
| 166 false); |
| 167 } |
| 168 |
141 ScreenOrientationController::From(*GetDocument().GetFrame())->unlock(); | 169 ScreenOrientationController::From(*GetDocument().GetFrame())->unlock(); |
142 should_unlock_orientation_ = false; | 170 locked_orientation_ = kWebScreenOrientationLockDefault /* unlocked */; |
| 171 } |
| 172 |
| 173 void MediaControlsOrientationLockDelegate::MaybeListenToDeviceOrientation() { |
| 174 DCHECK_EQ(state_, State::kMaybeLockedFullscreen); |
| 175 DCHECK_NE(locked_orientation_, kWebScreenOrientationLockDefault); |
| 176 |
| 177 // If the rotate-to-fullscreen feature is also enabled, then start listening |
| 178 // to deviceorientation events so the orientation can be unlocked once the |
| 179 // user rotates the device to match the video's orientation (allowing the user |
| 180 // to then exit fullscreen by rotating their device back to the opposite |
| 181 // orientation). Otherwise, don't listen for deviceorientation events and just |
| 182 // hold the orientation lock until the user exits fullscreen (which prevents |
| 183 // the user rotating to the wrong fullscreen orientation). |
| 184 if (!RuntimeEnabledFeatures::videoRotateToFullscreenEnabled()) |
| 185 return; |
| 186 |
| 187 // Check whether the user locked screen orientation at the OS level. |
| 188 #if OS(ANDROID) |
| 189 DCHECK(!monitor_.is_bound()); |
| 190 Platform::Current()->GetConnector()->BindInterface( |
| 191 device::mojom::blink::kServiceName, mojo::MakeRequest(&monitor_)); |
| 192 monitor_->IsAutoRotateEnabledByUser(ConvertToBaseCallback(WTF::Bind( |
| 193 &MediaControlsOrientationLockDelegate::GotIsAutoRotateEnabledByUser, |
| 194 WrapPersistent(this)))); |
| 195 #else |
| 196 GotIsAutoRotateEnabledByUser(true); // Assume always enabled on other OSes. |
| 197 #endif // OS(ANDROID) |
| 198 } |
| 199 |
| 200 void MediaControlsOrientationLockDelegate::GotIsAutoRotateEnabledByUser( |
| 201 bool enabled) { |
| 202 monitor_.reset(); |
| 203 |
| 204 if (!enabled) { |
| 205 // Since the user has locked their screen orientation, prevent |
| 206 // MediaControlsRotateToFullscreenDelegate from exiting fullscreen by not |
| 207 // listening for deviceorientation events and instead continuing to hold the |
| 208 // orientation lock until the user exits fullscreen. This enables users to |
| 209 // watch videos in bed with their head facing sideways (which requires a |
| 210 // landscape screen orientation when the device is portrait and vice versa). |
| 211 // TODO(johnme): Ideally we would start listening for deviceorientation |
| 212 // events and allow rotating to exit if a user enables screen auto rotation |
| 213 // after we have locked to landscape. That would require listening for |
| 214 // changes to the auto rotate setting, rather than only checking it once. |
| 215 return; |
| 216 } |
| 217 |
| 218 if (LocalDOMWindow* dom_window = GetDocument().domWindow()) { |
| 219 dom_window->addEventListener(EventTypeNames::deviceorientation, this, |
| 220 false); |
| 221 } |
143 } | 222 } |
144 | 223 |
145 HTMLVideoElement& MediaControlsOrientationLockDelegate::VideoElement() const { | 224 HTMLVideoElement& MediaControlsOrientationLockDelegate::VideoElement() const { |
146 return *video_element_; | 225 return *video_element_; |
147 } | 226 } |
148 | 227 |
149 Document& MediaControlsOrientationLockDelegate::GetDocument() const { | 228 Document& MediaControlsOrientationLockDelegate::GetDocument() const { |
150 return VideoElement().GetDocument(); | 229 return VideoElement().GetDocument(); |
151 } | 230 } |
152 | 231 |
(...skipping 13 matching lines...) Expand all Loading... |
166 return; | 245 return; |
167 } | 246 } |
168 | 247 |
169 if (event->type() == EventTypeNames::loadedmetadata) { | 248 if (event->type() == EventTypeNames::loadedmetadata) { |
170 if (state_ == State::kPendingMetadata) | 249 if (state_ == State::kPendingMetadata) |
171 MaybeLockOrientation(); | 250 MaybeLockOrientation(); |
172 | 251 |
173 return; | 252 return; |
174 } | 253 } |
175 | 254 |
| 255 if (event->type() == EventTypeNames::deviceorientation) { |
| 256 MaybeUnlockIfDeviceOrientationMatchesVideo(ToDeviceOrientationEvent(event)); |
| 257 |
| 258 return; |
| 259 } |
| 260 |
176 NOTREACHED(); | 261 NOTREACHED(); |
177 } | 262 } |
178 | 263 |
179 WebScreenOrientationLockType | 264 WebScreenOrientationLockType |
180 MediaControlsOrientationLockDelegate::ComputeOrientationLock() const { | 265 MediaControlsOrientationLockDelegate::ComputeOrientationLock() const { |
181 DCHECK(VideoElement().getReadyState() != HTMLMediaElement::kHaveNothing); | 266 DCHECK(VideoElement().getReadyState() != HTMLMediaElement::kHaveNothing); |
182 | 267 |
183 const unsigned width = VideoElement().videoWidth(); | 268 const unsigned width = VideoElement().videoWidth(); |
184 const unsigned height = VideoElement().videoHeight(); | 269 const unsigned height = VideoElement().videoHeight(); |
185 | 270 |
(...skipping 19 matching lines...) Expand all Loading... |
205 case kWebScreenOrientationLandscapeSecondary: | 290 case kWebScreenOrientationLandscapeSecondary: |
206 return kWebScreenOrientationLockLandscape; | 291 return kWebScreenOrientationLockLandscape; |
207 case kWebScreenOrientationUndefined: | 292 case kWebScreenOrientationUndefined: |
208 return kWebScreenOrientationLockLandscape; | 293 return kWebScreenOrientationLockLandscape; |
209 } | 294 } |
210 | 295 |
211 NOTREACHED(); | 296 NOTREACHED(); |
212 return kWebScreenOrientationLockLandscape; | 297 return kWebScreenOrientationLockLandscape; |
213 } | 298 } |
214 | 299 |
| 300 void MediaControlsOrientationLockDelegate:: |
| 301 MaybeUnlockIfDeviceOrientationMatchesVideo(DeviceOrientationEvent* event) { |
| 302 DCHECK_EQ(state_, State::kMaybeLockedFullscreen); |
| 303 DCHECK_NE(locked_orientation_, kWebScreenOrientationLockDefault); |
| 304 |
| 305 LocalDOMWindow* dom_window = GetDocument().domWindow(); |
| 306 if (!dom_window) |
| 307 return; |
| 308 |
| 309 if (!event->Orientation()->CanProvideBeta() || |
| 310 !event->Orientation()->CanProvideGamma()) { |
| 311 return; |
| 312 } |
| 313 double beta = event->Orientation()->Beta(); |
| 314 double gamma = event->Orientation()->Gamma(); |
| 315 |
| 316 // Calculate the projection of the up vector (normal to the earth's surface) |
| 317 // onto the device's screen in its natural orientation. (x,y) will lie within |
| 318 // the unit circle centered on (0,0), e.g. if the top of the device is |
| 319 // pointing upwards (x,y) will be (0,-1). |
| 320 double x = -std::sin(deg2rad(gamma)) * std::cos(deg2rad(beta)); |
| 321 double y = -std::sin(deg2rad(beta)); |
| 322 |
| 323 // Convert (x,y) to polar coordinates: 0 <= device_orientation_angle < 360 and |
| 324 // 0 <= r <= 1, such that device_orientation_angle is the clockwise angle in |
| 325 // degrees between the current physical orientation of the device and the |
| 326 // natural physical orientation of the device (ignoring the screen |
| 327 // orientation). Thus snapping device_orientation_angle to the nearest |
| 328 // multiple of 90 gives the value screen.orientation.angle would have if the |
| 329 // screen orientation was allowed to rotate freely to match the device |
| 330 // orientation. Note that we want device_orientation_angle==0 when the top of |
| 331 // the device is pointing upwards, but atan2's zero angle points to the right, |
| 332 // so we pass y=x and x=-y to atan2 to rotate by 90 degrees. |
| 333 double r = std::sqrt(x * x + y * y); |
| 334 double device_orientation_angle = |
| 335 std::fmod(rad2deg(std::atan2(/* y= */ x, /* x= */ -y)) + 360, 360); |
| 336 |
| 337 constexpr double kMinElevationAngle = 24; // degrees from horizontal plane |
| 338 if (r < std::sin(deg2rad(kMinElevationAngle))) |
| 339 return; // Device is too flat to reliably determine orientation. |
| 340 |
| 341 // device_orientation_angle snapped to nearest multiple of 90. |
| 342 int device_orientation_angle90 = |
| 343 std::lround(device_orientation_angle / 90) * 90; |
| 344 |
| 345 if (std::abs(device_orientation_angle - device_orientation_angle90) > 23) { |
| 346 // Device is diagonal (within 44 degree hysteresis zone). |
| 347 return; |
| 348 } |
| 349 |
| 350 // screen.orientation.angle is the standardized replacement for |
| 351 // window.orientation. They are equal, except -90 was replaced by 270. |
| 352 int screen_orientation_angle = |
| 353 ScreenScreenOrientation::orientation(nullptr /* ScriptState */, |
| 354 *dom_window->screen()) |
| 355 ->angle(); |
| 356 |
| 357 // This is equivalent to screen.orientation.type.startsWith('landscape'). |
| 358 bool screen_orientation_is_landscape = |
| 359 dom_window->screen()->width() > dom_window->screen()->height(); |
| 360 |
| 361 // The natural orientation of the device could either be portrait (almost |
| 362 // all phones, and some tablets like Nexus 7) or landscape (other tablets |
| 363 // like Pixel C). Detect this by comparing angle to orientation. |
| 364 // TODO(johnme): This might get confused on square screens. |
| 365 bool screen_orientation_is_natural_or_flipped_natural = |
| 366 screen_orientation_angle % 180 == 0; |
| 367 bool natural_orientation_is_landscape = |
| 368 screen_orientation_is_landscape == |
| 369 screen_orientation_is_natural_or_flipped_natural; |
| 370 |
| 371 bool natural_orientation_matches_video = |
| 372 natural_orientation_is_landscape == |
| 373 (locked_orientation_ == kWebScreenOrientationLockLandscape); |
| 374 |
| 375 // If natural_orientation_matches_video, then 0 and 180 match video, otherwise |
| 376 // 90 and 270 match video. |
| 377 bool device_orientation_matches_video = |
| 378 (device_orientation_angle90 % 180) == |
| 379 (natural_orientation_matches_video ? 0 : 90); |
| 380 |
| 381 if (!device_orientation_matches_video) |
| 382 return; |
| 383 |
| 384 // Job done: the user rotated their device to match the orientation of the |
| 385 // video that we locked to, so now we can unlock (and stop listening). |
| 386 MaybeUnlockOrientation(); |
| 387 } |
| 388 |
215 DEFINE_TRACE(MediaControlsOrientationLockDelegate) { | 389 DEFINE_TRACE(MediaControlsOrientationLockDelegate) { |
216 EventListener::Trace(visitor); | 390 EventListener::Trace(visitor); |
217 visitor->Trace(video_element_); | 391 visitor->Trace(video_element_); |
218 } | 392 } |
219 | 393 |
220 } // namespace blink | 394 } // namespace blink |
OLD | NEW |