Commit a0743f97 authored by Mounir Lamouri's avatar Mounir Lamouri Committed by Commit Bot

Video Wake Lock: do not take a wake lock for hidden muted videos.

These videos are, as far as the user is concerned, non-existent. This is the
first part into strengthening the conditions in which the video element takes a
screen lock.

Bug: 1086776
Change-Id: Ife302518d45986a75fbb27a91fc5dc77d5f07d03
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2213933Reviewed-by: default avatarDale Curtis <dalecurtis@chromium.org>
Commit-Queue: Mounir Lamouri <mlamouri@chromium.org>
Cr-Commit-Position: refs/heads/master@{#772337}
parent 8a7e2c57
......@@ -12,6 +12,7 @@
#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 {
......@@ -27,6 +28,9 @@ VideoWakeLock::VideoWakeLock(HTMLVideoElement& video)
this, true);
VideoElement().addEventListener(event_type_names::kLeavepictureinpicture,
this, true);
VideoElement().addEventListener(event_type_names::kVolumechange, this, true);
StartIntersectionObserver();
RemotePlaybackController* remote_playback_controller =
RemotePlaybackController::From(VideoElement());
......@@ -38,17 +42,27 @@ VideoWakeLock::VideoWakeLock(HTMLVideoElement& video)
void VideoWakeLock::ElementDidMoveToNewDocument() {
SetExecutionContext(VideoElement().GetExecutionContext());
intersection_observer_->disconnect();
StartIntersectionObserver();
}
void VideoWakeLock::PageVisibilityChanged() {
Update();
}
void VideoWakeLock::OnVisibilityChanged(
const HeapVector<Member<IntersectionObserverEntry>>& entries) {
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) {
......@@ -62,7 +76,8 @@ void VideoWakeLock::Invoke(ExecutionContext*, Event* event) {
playing_ = false;
} else {
DCHECK(event->type() == event_type_names::kEnterpictureinpicture ||
event->type() == event_type_names::kLeavepictureinpicture);
event->type() == event_type_names::kLeavepictureinpicture ||
event->type() == event_type_names::kVolumechange);
}
Update();
......@@ -96,7 +111,21 @@ bool VideoWakeLock::ShouldBeActive() const {
bool in_picture_in_picture =
PictureInPictureController::IsElementInPictureInPicture(&VideoElement());
return playing_ && (page_visible || in_picture_in_picture) &&
// 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 &&
!(VideoElement().GetDocument().IsContextPaused() ||
......@@ -141,4 +170,13 @@ void VideoWakeLock::UpdateWakeLockService() {
wake_lock_service_->CancelWakeLock();
}
void VideoWakeLock::StartIntersectionObserver() {
intersection_observer_ = IntersectionObserver::Create(
{}, {IntersectionObserver::kMinimumThreshold},
&VideoElement().GetDocument(),
WTF::BindRepeating(&VideoWakeLock::OnVisibilityChanged,
WrapWeakPersistent(this)));
intersection_observer_->observe(&VideoElement());
}
} // namespace blink
......@@ -11,6 +11,7 @@
#include "third_party/blink/renderer/core/dom/events/native_event_listener.h"
#include "third_party/blink/renderer/core/execution_context/execution_context_lifecycle_state_observer.h"
#include "third_party/blink/renderer/core/html/media/remote_playback_observer.h"
#include "third_party/blink/renderer/core/intersection_observer/intersection_observer.h"
#include "third_party/blink/renderer/core/page/page_visibility_observer.h"
namespace blink {
......@@ -55,9 +56,16 @@ class CORE_EXPORT VideoWakeLock final
bool active_for_tests() const { return active_; }
private:
friend class VideoWakeLockTest;
// PageVisibilityObserver implementation.
void PageVisibilityChanged() final;
// Called by the IntersectionObserver instance when the visibility state of
// the video element has changed.
void OnVisibilityChanged(
const HeapVector<Member<IntersectionObserverEntry>>&);
// Called when any state is changed. Will update active state and notify the
// service if needed.
void Update();
......@@ -74,6 +82,9 @@ class CORE_EXPORT VideoWakeLock final
// Notify the wake lock service of the current wake lock state.
void UpdateWakeLockService();
// Create a new |intersection_observer_| instance and start observing.
void StartIntersectionObserver();
HTMLVideoElement& VideoElement() { return *video_element_; }
const HTMLVideoElement& VideoElement() const { return *video_element_; }
......@@ -86,6 +97,8 @@ class CORE_EXPORT VideoWakeLock final
bool active_ = false;
mojom::blink::PresentationConnectionState remote_playback_state_ =
mojom::blink::PresentationConnectionState::CLOSED;
Member<IntersectionObserver> intersection_observer_;
bool is_visible_ = false;
};
} // namespace blink
......
......@@ -11,6 +11,7 @@
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/browser_interface_broker_proxy.h"
#include "third_party/blink/public/mojom/picture_in_picture/picture_in_picture.mojom-blink.h"
#include "third_party/blink/renderer/core/css_value_keywords.h"
#include "third_party/blink/renderer/core/dom/events/event.h"
#include "third_party/blink/renderer/core/frame/local_dom_window.h"
#include "third_party/blink/renderer/core/frame/picture_in_picture_controller.h"
......@@ -106,7 +107,8 @@ class VideoWakeLockTest : public PageTestBase {
WTF::BindRepeating(&VideoWakeLockPictureInPictureService::Bind,
WTF::Unretained(&pip_service_)));
video_ = MakeGarbageCollected<HTMLVideoElement>(GetDocument());
GetDocument().body()->setInnerHTML("<body><video></video></body>");
video_ = To<HTMLVideoElement>(GetDocument().QuerySelector("video"));
video_->SetReadyState(HTMLMediaElement::ReadyState::kHaveMetadata);
video_wake_lock_ = MakeGarbageCollected<VideoWakeLock>(*video_.Get());
......@@ -117,6 +119,8 @@ class VideoWakeLockTest : public PageTestBase {
void TearDown() override {
GetDocument().GetBrowserInterfaceBroker().SetBinderForTesting(
mojom::blink::PictureInPictureService::Name_, {});
PageTestBase::TearDown();
}
HTMLVideoElement* Video() const { return video_.Get(); }
......@@ -169,6 +173,19 @@ class VideoWakeLockTest : public PageTestBase {
video_->SetNetworkState(network_state);
}
void UpdateVisibilityObserver() {
UpdateAllLifecyclePhasesForTest();
test::RunPendingTasks();
}
void HideVideo() {
video_->SetInlineStyleProperty(CSSPropertyID::kDisplay, CSSValueID::kNone);
}
void ShowVideo() {
video_->SetInlineStyleProperty(CSSPropertyID::kDisplay, CSSValueID::kBlock);
}
private:
Persistent<HTMLVideoElement> video_;
Persistent<VideoWakeLock> video_wake_lock_;
......@@ -375,4 +392,93 @@ TEST_F(VideoWakeLockTest, LoadingCancelsLock) {
EXPECT_FALSE(GetVideoWakeLock()->active_for_tests());
}
TEST_F(VideoWakeLockTest, MutedHiddenVideoDoesNotTakeLock) {
Video()->setMuted(true);
HideVideo();
UpdateVisibilityObserver();
SimulatePlaying();
EXPECT_FALSE(GetVideoWakeLock()->active_for_tests());
}
TEST_F(VideoWakeLockTest, AudibleHiddenVideoTakesLock) {
Video()->setMuted(false);
HideVideo();
UpdateVisibilityObserver();
SimulatePlaying();
EXPECT_TRUE(GetVideoWakeLock()->active_for_tests());
}
TEST_F(VideoWakeLockTest, UnmutingHiddenVideoTakesLock) {
Video()->setMuted(true);
HideVideo();
UpdateVisibilityObserver();
SimulatePlaying();
EXPECT_FALSE(GetVideoWakeLock()->active_for_tests());
Video()->setMuted(false);
test::RunPendingTasks();
EXPECT_TRUE(GetVideoWakeLock()->active_for_tests());
}
TEST_F(VideoWakeLockTest, MutingHiddenVideoReleasesLock) {
Video()->setMuted(false);
HideVideo();
UpdateVisibilityObserver();
SimulatePlaying();
EXPECT_TRUE(GetVideoWakeLock()->active_for_tests());
Video()->setMuted(true);
test::RunPendingTasks();
EXPECT_FALSE(GetVideoWakeLock()->active_for_tests());
}
TEST_F(VideoWakeLockTest, HidingAudibleVideoDoesNotReleaseLock) {
Video()->setMuted(false);
ShowVideo();
UpdateVisibilityObserver();
SimulatePlaying();
EXPECT_TRUE(GetVideoWakeLock()->active_for_tests());
HideVideo();
UpdateVisibilityObserver();
EXPECT_TRUE(GetVideoWakeLock()->active_for_tests());
}
TEST_F(VideoWakeLockTest, HidingMutedVideoReleasesLock) {
Video()->setMuted(true);
ShowVideo();
UpdateVisibilityObserver();
SimulatePlaying();
EXPECT_TRUE(GetVideoWakeLock()->active_for_tests());
HideVideo();
UpdateVisibilityObserver();
EXPECT_FALSE(GetVideoWakeLock()->active_for_tests());
}
TEST_F(VideoWakeLockTest, HiddenMutedVideoAlwaysVisibleInPictureInPicture) {
// This initialeses the video element in order to not crash when the
// interstitial tries to show itself and so that the WebMediaPlayer is set up.
scoped_refptr<cc::Layer> layer = cc::Layer::Create();
SetFakeCcLayer(layer.get());
Video()->SetSrc("http://example.com/foo.mp4");
Video()->setMuted(true);
HideVideo();
UpdateVisibilityObserver();
SimulatePlaying();
EXPECT_FALSE(GetVideoWakeLock()->active_for_tests());
SimulateEnterPictureInPicture();
EXPECT_TRUE(GetVideoWakeLock()->active_for_tests());
SimulateLeavePictureInPicture();
EXPECT_FALSE(GetVideoWakeLock()->active_for_tests());
}
} // namespace blink
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment