| // Copyright 2018 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 "third_party/blink/renderer/core/html/media/video_wake_lock.h" |
| |
| #include "third_party/blink/public/common/browser_interface_broker_proxy.h" |
| #include "third_party/blink/public/mojom/wake_lock/wake_lock.mojom-blink.h" |
| #include "third_party/blink/renderer/core/dom/document.h" |
| #include "third_party/blink/renderer/core/dom/events/event.h" |
| #include "third_party/blink/renderer/core/frame/local_frame.h" |
| #include "third_party/blink/renderer/core/frame/picture_in_picture_controller.h" |
| #include "third_party/blink/renderer/core/html/media/html_video_element.h" |
| #include "third_party/blink/renderer/core/html/media/remote_playback_controller.h" |
| #include "third_party/blink/renderer/core/intersection_observer/intersection_observer_entry.h" |
| #include "third_party/blink/renderer/core/page/page.h" |
| |
| namespace blink { |
| |
| VideoWakeLock::VideoWakeLock(HTMLVideoElement& video) |
| : PageVisibilityObserver(video.GetDocument().GetPage()), |
| ExecutionContextLifecycleStateObserver(video.GetExecutionContext()), |
| video_element_(video) { |
| VideoElement().addEventListener(event_type_names::kPlaying, this, true); |
| VideoElement().addEventListener(event_type_names::kPause, this, true); |
| VideoElement().addEventListener(event_type_names::kEmptied, this, true); |
| VideoElement().addEventListener(event_type_names::kEnterpictureinpicture, |
| this, true); |
| VideoElement().addEventListener(event_type_names::kLeavepictureinpicture, |
| this, true); |
| VideoElement().addEventListener(event_type_names::kVolumechange, this, true); |
| |
| if (RuntimeEnabledFeatures::VideoWakeLockOptimisationHiddenMutedEnabled()) |
| StartIntersectionObserver(); |
| else |
| is_visible_ = true; |
| |
| RemotePlaybackController* remote_playback_controller = |
| RemotePlaybackController::From(VideoElement()); |
| if (remote_playback_controller) |
| remote_playback_controller->AddObserver(this); |
| |
| UpdateStateIfNeeded(); |
| } |
| |
| void VideoWakeLock::ElementDidMoveToNewDocument() { |
| SetExecutionContext(VideoElement().GetExecutionContext()); |
| |
| if (RuntimeEnabledFeatures::VideoWakeLockOptimisationHiddenMutedEnabled()) { |
| intersection_observer_->disconnect(); |
| StartIntersectionObserver(); |
| } |
| } |
| |
| void VideoWakeLock::PageVisibilityChanged() { |
| Update(); |
| } |
| |
| void VideoWakeLock::OnVisibilityChanged( |
| const HeapVector<Member<IntersectionObserverEntry>>& entries) { |
| DCHECK(RuntimeEnabledFeatures::VideoWakeLockOptimisationHiddenMutedEnabled()); |
| |
| is_visible_ = (entries.back()->intersectionRatio() > 0); |
| Update(); |
| } |
| |
| void VideoWakeLock::Trace(Visitor* visitor) const { |
| NativeEventListener::Trace(visitor); |
| PageVisibilityObserver::Trace(visitor); |
| ExecutionContextLifecycleStateObserver::Trace(visitor); |
| visitor->Trace(video_element_); |
| visitor->Trace(intersection_observer_); |
| } |
| |
| void VideoWakeLock::Invoke(ExecutionContext*, Event* event) { |
| if (event->type() == event_type_names::kPlaying) { |
| playing_ = true; |
| } else if (event->type() == event_type_names::kPause || |
| event->type() == event_type_names::kEmptied) { |
| // In 4.8.12.5 steps 6.6.1, the media element is paused when a new load |
| // happens without actually firing a pause event. Because of this, we need |
| // to listen to the emptied event. |
| playing_ = false; |
| } else { |
| DCHECK(event->type() == event_type_names::kEnterpictureinpicture || |
| event->type() == event_type_names::kLeavepictureinpicture || |
| event->type() == event_type_names::kVolumechange); |
| } |
| |
| Update(); |
| } |
| |
| void VideoWakeLock::OnRemotePlaybackStateChanged( |
| mojom::blink::PresentationConnectionState state) { |
| remote_playback_state_ = state; |
| Update(); |
| } |
| |
| void VideoWakeLock::ContextLifecycleStateChanged(mojom::FrameLifecycleState) { |
| Update(); |
| } |
| |
| void VideoWakeLock::ContextDestroyed() { |
| Update(); |
| } |
| |
| void VideoWakeLock::Update() { |
| bool should_be_active = ShouldBeActive(); |
| if (should_be_active == active_) |
| return; |
| |
| active_ = should_be_active; |
| UpdateWakeLockService(); |
| } |
| |
| bool VideoWakeLock::ShouldBeActive() const { |
| bool page_visible = GetPage() && GetPage()->IsPageVisible(); |
| bool in_picture_in_picture = |
| PictureInPictureController::IsElementInPictureInPicture(&VideoElement()); |
| bool context_is_running = |
| VideoElement().GetExecutionContext() && |
| !VideoElement().GetExecutionContext()->IsContextPaused(); |
| |
| // The visibility requirements are met if one of the following is true: |
| // - it's in Picture-in-Picture; |
| // - it's audibly playing on a visible page; |
| // - it's visible to the user. |
| bool visibility_requirements_met = |
| (in_picture_in_picture || |
| (page_visible && |
| (is_visible_ || VideoElement().EffectiveMediaVolume()))); |
| |
| // The video wake lock should be active iif: |
| // - it's playing; |
| // - the visibility requirements are met (see above); |
| // - it's *not* playing in Remote Playback; |
| // - the document is not paused nor destroyed. |
| return playing_ && visibility_requirements_met && |
| remote_playback_state_ != |
| mojom::blink::PresentationConnectionState::CONNECTED && |
| context_is_running; |
| } |
| |
| void VideoWakeLock::EnsureWakeLockService() { |
| if (wake_lock_service_) |
| return; |
| |
| LocalFrame* frame = VideoElement().GetDocument().GetFrame(); |
| if (!frame) |
| return; |
| |
| scoped_refptr<base::SingleThreadTaskRunner> task_runner = |
| frame->GetTaskRunner(TaskType::kMediaElementEvent); |
| |
| mojo::Remote<blink::mojom::blink::WakeLockService> service; |
| frame->GetBrowserInterfaceBroker().GetInterface( |
| service.BindNewPipeAndPassReceiver(task_runner)); |
| service->GetWakeLock(device::mojom::WakeLockType::kPreventDisplaySleep, |
| device::mojom::blink::WakeLockReason::kVideoPlayback, |
| "Video Wake Lock", |
| wake_lock_service_.BindNewPipeAndPassReceiver()); |
| wake_lock_service_.set_disconnect_handler( |
| WTF::Bind(&VideoWakeLock::OnConnectionError, WrapWeakPersistent(this))); |
| } |
| |
| void VideoWakeLock::OnConnectionError() { |
| wake_lock_service_.reset(); |
| } |
| |
| void VideoWakeLock::UpdateWakeLockService() { |
| EnsureWakeLockService(); |
| |
| if (!wake_lock_service_) |
| return; |
| |
| if (active_) |
| wake_lock_service_->RequestWakeLock(); |
| else |
| wake_lock_service_->CancelWakeLock(); |
| } |
| |
| void VideoWakeLock::StartIntersectionObserver() { |
| intersection_observer_ = IntersectionObserver::Create( |
| {}, {IntersectionObserver::kMinimumThreshold}, |
| &VideoElement().GetDocument(), |
| WTF::BindRepeating(&VideoWakeLock::OnVisibilityChanged, |
| WrapWeakPersistent(this)), |
| LocalFrameUkmAggregator::kMediaIntersectionObserver); |
| intersection_observer_->observe(&VideoElement()); |
| } |
| |
| } // namespace blink |