Reland "Picture-in-Picture: move key methods into a service."
This reverts commit 9a3eafe448d45259b15e2c8bfa4916da2b39ee75.
Reason for revert: the flake source was found and the test will be disabled until then.
Original change's description:
> Revert "Picture-in-Picture: move key methods into a service."
>
> This reverts commit 45675d0dfab5de54253d40cb2bd52ef034c3a200.
>
> Reason for revert: Speculative revert for crbug.com/930338
>
> Original change's description:
> > Picture-in-Picture: move key methods into a service.
> >
> > This is moving most of the Picture-in-Picture feature into a service.
> > Follow-up CLs will move the remaining methods and some Android auto-pip
> > code. It will also include further cleanups.
> >
> > This CL was meant to be the smallest CL that could move the core feature
> > set without breaking compilation or tests. It's larger than expected.
> >
> > Bug: 919860, 908400
> > Change-Id: I72c5faad9a14de6645a7cd1565b29d6a353e24a3
> > Binary-Size: cause unknown, landing approved by agrieve@
> > Reviewed-on: https://chromium-review.googlesource.com/c/1379049
> > Commit-Queue: Mounir Lamouri <mlamouri@chromium.org>
> > Reviewed-by: Daniel Cheng <dcheng@chromium.org>
> > Reviewed-by: Dale Curtis <dalecurtis@chromium.org>
> > Reviewed-by: Jochen Eisinger <jochen@chromium.org>
> > Cr-Commit-Position: refs/heads/master@{#630361}
>
> TBR=dcheng@chromium.org,dalecurtis@chromium.org,beaufort.francois@gmail.com,mlamouri@chromium.org,agrieve@chromium.org,jochen@chromium.org
>
> Change-Id: I88efa31a23ab1c55b256e5813fa20fd2d8776a21
> No-Presubmit: true
> No-Tree-Checks: true
> No-Try: true
> Bug: 919860, 908400, 930338
> Reviewed-on: https://chromium-review.googlesource.com/c/1461546
> Reviewed-by: Karan Bhatia <karandeepb@chromium.org>
> Commit-Queue: Karan Bhatia <karandeepb@chromium.org>
> Cr-Commit-Position: refs/heads/master@{#630550}
TBR=dcheng@chromium.org,dalecurtis@chromium.org,beaufort.francois@gmail.com,mlamouri@chromium.org,agrieve@chromium.org,karandeepb@chromium.org,jochen@chromium.org
# Not skipping CQ checks because original CL landed > 1 day ago.
Bug: 919860, 908400, 930338
Change-Id: I1bd2ab7ade56569a83951ec942685d9f77dc4af5
Binary-Size: cause unknown, landing approved by agrieve@
No-Presubmit: true
Reviewed-on: https://chromium-review.googlesource.com/c/1465063
Commit-Queue: Mounir Lamouri <mlamouri@chromium.org>
Reviewed-by: Mounir Lamouri <mlamouri@chromium.org>
Reviewed-by: Kentaro Hara <haraken@chromium.org>
Reviewed-by: Daniel Cheng <dcheng@chromium.org>
Cr-Commit-Position: refs/heads/master@{#631030}
diff --git a/chrome/browser/picture_in_picture/picture_in_picture_window_controller_browsertest.cc b/chrome/browser/picture_in_picture/picture_in_picture_window_controller_browsertest.cc
index 1e0834a..dc73d30 100644
--- a/chrome/browser/picture_in_picture/picture_in_picture_window_controller_browsertest.cc
+++ b/chrome/browser/picture_in_picture/picture_in_picture_window_controller_browsertest.cc
@@ -1829,6 +1829,7 @@
content::WebContents* active_web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
+
ASSERT_NE(nullptr, active_web_contents);
ASSERT_TRUE(content::ExecuteScript(active_web_contents, "video.play();"));
@@ -2570,3 +2571,21 @@
content::TitleWatcher(active_web_contents, expected_title)
.WaitAndGetTitle());
}
+
+// Tests that exiting Picture-in-Picture when the video has no source fires the
+// event and resolves the callback.
+IN_PROC_BROWSER_TEST_F(PictureInPictureWindowControllerBrowserTest,
+ ExitFireEventAndCallbackWhenNoSource) {
+ LoadTabAndEnterPictureInPicture(browser());
+
+ content::WebContents* active_web_contents =
+ browser()->tab_strip_model()->GetActiveWebContents();
+ ASSERT_TRUE(content::ExecuteScript(active_web_contents,
+ "video.src=''; exitPictureInPicture();"));
+
+ // 'left' is sent when the first video leaves Picture-in-Picture.
+ base::string16 expected_title = base::ASCIIToUTF16("left");
+ EXPECT_EQ(expected_title,
+ content::TitleWatcher(active_web_contents, expected_title)
+ .WaitAndGetTitle());
+}
diff --git a/content/browser/BUILD.gn b/content/browser/BUILD.gn
index 0bdcde6c..078e20d 100644
--- a/content/browser/BUILD.gn
+++ b/content/browser/BUILD.gn
@@ -1291,6 +1291,8 @@
"permissions/permission_service_impl.h",
"picture_in_picture/overlay_surface_embedder.cc",
"picture_in_picture/overlay_surface_embedder.h",
+ "picture_in_picture/picture_in_picture_service_impl.cc",
+ "picture_in_picture/picture_in_picture_service_impl.h",
"picture_in_picture/picture_in_picture_window_controller_impl.cc",
"picture_in_picture/picture_in_picture_window_controller_impl.h",
"portal/portal.cc",
diff --git a/content/browser/frame_host/render_frame_host_impl.cc b/content/browser/frame_host/render_frame_host_impl.cc
index fb5cebf..66ce76d 100644
--- a/content/browser/frame_host/render_frame_host_impl.cc
+++ b/content/browser/frame_host/render_frame_host_impl.cc
@@ -73,6 +73,7 @@
#include "content/browser/permissions/permission_controller_impl.h"
#include "content/browser/permissions/permission_service_context.h"
#include "content/browser/permissions/permission_service_impl.h"
+#include "content/browser/picture_in_picture/picture_in_picture_service_impl.h"
#include "content/browser/portal/portal.h"
#include "content/browser/presentation/presentation_service_impl.h"
#include "content/browser/quota_dispatcher_host.h"
@@ -4087,6 +4088,9 @@
registry_->AddInterface(base::BindRepeating(&WakeLockServiceImpl::Create,
base::Unretained(this)));
+
+ registry_->AddInterface(base::BindRepeating(
+ &PictureInPictureServiceImpl::Create, base::Unretained(this)));
}
void RenderFrameHostImpl::ResetWaitingState() {
diff --git a/content/browser/media/media_web_contents_observer.cc b/content/browser/media/media_web_contents_observer.cc
index 6e29f3c..740bdc6 100644
--- a/content/browser/media/media_web_contents_observer.cc
+++ b/content/browser/media/media_web_contents_observer.cc
@@ -77,11 +77,6 @@
picture_in_picture_allowed_in_fullscreen_.reset();
fullscreen_player_.reset();
}
-
- // Usually the frame will exit PIP before it is deleted but for OOPIF, it
- // seems that the player never notifies the browser process.
- if (pip_player_ && pip_player_->render_frame_host == render_frame_host)
- ExitPictureInPictureInternal();
}
void MediaWebContentsObserver::MaybeUpdateAudibleState() {
@@ -120,15 +115,6 @@
return fullscreen_player_;
}
-const base::Optional<WebContentsObserver::MediaPlayerId>&
-MediaWebContentsObserver::GetPictureInPictureVideoMediaPlayerId() const {
- return pip_player_;
-}
-
-void MediaWebContentsObserver::ResetPictureInPictureVideoMediaPlayerId() {
- pip_player_.reset();
-}
-
bool MediaWebContentsObserver::OnMessageReceived(
const IPC::Message& msg,
RenderFrameHost* render_frame_host) {
@@ -148,16 +134,8 @@
IPC_MESSAGE_HANDLER(MediaPlayerDelegateHostMsg_OnMediaSizeChanged,
OnMediaSizeChanged)
IPC_MESSAGE_HANDLER(
- MediaPlayerDelegateHostMsg_OnPictureInPictureModeStarted,
- OnPictureInPictureModeStarted)
- IPC_MESSAGE_HANDLER(MediaPlayerDelegateHostMsg_OnPictureInPictureModeEnded,
- OnPictureInPictureModeEnded)
- IPC_MESSAGE_HANDLER(
MediaPlayerDelegateHostMsg_OnSetPictureInPictureCustomControls,
OnSetPictureInPictureCustomControls)
- IPC_MESSAGE_HANDLER(
- MediaPlayerDelegateHostMsg_OnPictureInPictureSurfaceChanged,
- OnPictureInPictureSurfaceChanged)
IPC_MESSAGE_UNHANDLED(handled = false)
IPC_END_MESSAGE_MAP()
return handled;
@@ -187,16 +165,6 @@
return MediaPlayerEntryExists(player_id, active_audio_players_);
}
-void MediaWebContentsObserver::OnPictureInPictureWindowResize(
- const gfx::Size& window_size) {
- DCHECK(pip_player_.has_value());
-
- pip_player_->render_frame_host->Send(
- new MediaPlayerDelegateMsg_OnPictureInPictureWindowResize(
- pip_player_->render_frame_host->GetRoutingID(),
- pip_player_->delegate_id, window_size));
-}
-
void MediaWebContentsObserver::OnMediaDestroyed(
RenderFrameHost* render_frame_host,
int delegate_id) {
@@ -212,16 +180,6 @@
const bool removed_video =
RemoveMediaPlayerEntry(player_id, &active_video_players_);
- if (!web_contents()->IsBeingDestroyed() && pip_player_ == player_id) {
- PictureInPictureWindowControllerImpl* pip_controller =
- PictureInPictureWindowControllerImpl::FromWebContents(
- web_contents_impl());
- if (pip_controller) {
- pip_controller->UpdatePlaybackState(false /* is not playing */,
- reached_end_of_stream);
- }
- }
-
if (removed_audio || removed_video) {
// Notify observers the player has been "paused".
web_contents_impl()->MediaStoppedPlaying(
@@ -263,16 +221,6 @@
return;
}
- if (!web_contents()->IsBeingDestroyed() && pip_player_ == id) {
- PictureInPictureWindowControllerImpl* pip_controller =
- PictureInPictureWindowControllerImpl::FromWebContents(
- web_contents_impl());
- if (pip_controller) {
- pip_controller->UpdatePlaybackState(true /* is not playing */,
- false /* reached_end_of_stream */);
- }
- }
-
// Notify observers of the new player.
web_contents_impl()->MediaStartedPlaying(
WebContentsObserver::MediaPlayerInfo(has_video, has_audio), id);
@@ -317,41 +265,6 @@
web_contents_impl()->MediaResized(size, id);
}
-void MediaWebContentsObserver::OnPictureInPictureModeStarted(
- RenderFrameHost* render_frame_host,
- int delegate_id,
- const viz::SurfaceId& surface_id,
- const gfx::Size& natural_size,
- int request_id,
- bool show_play_pause_button) {
- DCHECK(surface_id.is_valid());
- pip_player_ = MediaPlayerId(render_frame_host, delegate_id);
-
- gfx::Size window_size =
- web_contents_impl()->EnterPictureInPicture(surface_id, natural_size);
-
- if (auto* pip_controller =
- PictureInPictureWindowControllerImpl::FromWebContents(
- web_contents_impl()))
- pip_controller->SetAlwaysHidePlayPauseButton(show_play_pause_button);
-
- render_frame_host->Send(
- new MediaPlayerDelegateMsg_OnPictureInPictureModeStarted_ACK(
- render_frame_host->GetRoutingID(), delegate_id, request_id,
- window_size));
-}
-
-void MediaWebContentsObserver::OnPictureInPictureModeEnded(
- RenderFrameHost* render_frame_host,
- int delegate_id,
- int request_id) {
- ExitPictureInPictureInternal();
-
- render_frame_host->Send(
- new MediaPlayerDelegateMsg_OnPictureInPictureModeEnded_ACK(
- render_frame_host->GetRoutingID(), delegate_id, request_id));
-}
-
void MediaWebContentsObserver::OnSetPictureInPictureCustomControls(
RenderFrameHost* render_frame_host,
int delegate_id,
@@ -363,26 +276,6 @@
pip_controller->SetPictureInPictureCustomControls(controls);
}
-void MediaWebContentsObserver::OnPictureInPictureSurfaceChanged(
- RenderFrameHost* render_frame_host,
- int delegate_id,
- const viz::SurfaceId& surface_id,
- const gfx::Size& natural_size,
- bool show_play_pause_button) {
- DCHECK(surface_id.is_valid());
-
- pip_player_ = MediaPlayerId(render_frame_host, delegate_id);
-
- // The PictureInPictureWindowController instance may not have been created by
- // the embedder.
- if (auto* pip_controller =
- PictureInPictureWindowControllerImpl::FromWebContents(
- web_contents_impl())) {
- pip_controller->EmbedSurface(surface_id, natural_size);
- pip_controller->SetAlwaysHidePlayPauseButton(show_play_pause_button);
- }
-}
-
void MediaWebContentsObserver::ClearWakeLocks(
RenderFrameHost* render_frame_host) {
std::set<MediaPlayerId> video_players;
@@ -482,16 +375,6 @@
player_map->erase(it);
}
-void MediaWebContentsObserver::ExitPictureInPictureInternal() {
- DCHECK(pip_player_);
-
- web_contents_impl()->ExitPictureInPicture();
-
- // Reset must happen after notifying the WebContents because it may interact
- // with it.
- ResetPictureInPictureVideoMediaPlayerId();
-}
-
WebContentsImpl* MediaWebContentsObserver::web_contents_impl() const {
return static_cast<WebContentsImpl*>(web_contents());
}
diff --git a/content/browser/media/media_web_contents_observer.h b/content/browser/media/media_web_contents_observer.h
index d1eb2af..788c018 100644
--- a/content/browser/media/media_web_contents_observer.h
+++ b/content/browser/media/media_web_contents_observer.h
@@ -34,10 +34,6 @@
class Size;
} // namespace size
-namespace viz {
-class SurfaceId;
-} // namespace viz
-
namespace content {
class AudibleMetrics;
@@ -72,14 +68,6 @@
// Gets the MediaPlayerId of the fullscreen video if it exists.
const base::Optional<MediaPlayerId>& GetFullscreenVideoMediaPlayerId() const;
- // Gets the MediaPlayerId of the picture in picture video if it exists.
- const base::Optional<MediaPlayerId>& GetPictureInPictureVideoMediaPlayerId()
- const;
-
- // Reset the MediaPlayerId of the picture in picture video when user closes
- // Picture-in-Picture window manually.
- void ResetPictureInPictureVideoMediaPlayerId();
-
// WebContentsObserver implementation.
void WebContentsDestroyed() override;
void RenderFrameDeleted(RenderFrameHost* render_frame_host) override;
@@ -96,11 +84,6 @@
// Returns whether or not the given player id is active.
bool IsPlayerActive(const MediaPlayerId& player_id) const;
- // Called by the Picture-in-Picture controller when the associated window is
- // resized. |window_size| represents the new size of the window. It MUST be
- // called when there is a player in Picture-in-Picture.
- void OnPictureInPictureWindowResize(const gfx::Size& window_size);
-
bool has_audio_wake_lock_for_testing() const {
return has_audio_wake_lock_for_testing_;
}
@@ -135,24 +118,10 @@
void OnMediaMutedStatusChanged(RenderFrameHost* render_frame_host,
int delegate_id,
bool muted);
- void OnPictureInPictureModeStarted(RenderFrameHost* render_frame_host,
- int delegate_id,
- const viz::SurfaceId&,
- const gfx::Size& natural_size,
- int request_id,
- bool show_play_pause_button);
- void OnPictureInPictureModeEnded(RenderFrameHost* render_frame_host,
- int delegate_id,
- int request_id);
void OnSetPictureInPictureCustomControls(
RenderFrameHost* render_frame_host,
int delegate_id,
const std::vector<blink::PictureInPictureControlInfo>& controls);
- void OnPictureInPictureSurfaceChanged(RenderFrameHost*,
- int delegate_id,
- const viz::SurfaceId&,
- const gfx::Size&,
- bool show_play_pause_button);
// Clear |render_frame_host|'s tracking entry for its WakeLocks.
void ClearWakeLocks(RenderFrameHost* render_frame_host);
@@ -176,10 +145,6 @@
ActiveMediaPlayerMap* player_map,
std::set<MediaPlayerId>* removed_players);
- // Internal method to exit Picture-in-Picture from an event received from the
- // renderer process.
- void ExitPictureInPictureInternal();
-
// Convenience method that casts web_contents() to a WebContentsImpl*.
WebContentsImpl* web_contents_impl() const;
@@ -191,7 +156,6 @@
ActiveMediaPlayerMap active_video_players_;
device::mojom::WakeLockPtr audio_wake_lock_;
base::Optional<MediaPlayerId> fullscreen_player_;
- base::Optional<MediaPlayerId> pip_player_;
base::Optional<bool> picture_in_picture_allowed_in_fullscreen_;
bool has_audio_wake_lock_for_testing_ = false;
diff --git a/content/browser/picture_in_picture/picture_in_picture_service_impl.cc b/content/browser/picture_in_picture/picture_in_picture_service_impl.cc
new file mode 100644
index 0000000..4d3bc41b
--- /dev/null
+++ b/content/browser/picture_in_picture/picture_in_picture_service_impl.cc
@@ -0,0 +1,130 @@
+// Copyright 2019 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 "content/browser/picture_in_picture/picture_in_picture_service_impl.h"
+
+#include <utility>
+
+#include "content/browser/picture_in_picture/picture_in_picture_window_controller_impl.h"
+#include "content/browser/web_contents/web_contents_impl.h"
+
+namespace content {
+
+// static
+void PictureInPictureServiceImpl::Create(
+ RenderFrameHost* render_frame_host,
+ blink::mojom::PictureInPictureServiceRequest request) {
+ DCHECK(render_frame_host);
+ new PictureInPictureServiceImpl(render_frame_host, std::move(request));
+}
+
+// static
+PictureInPictureServiceImpl* PictureInPictureServiceImpl::CreateForTesting(
+ RenderFrameHost* render_frame_host,
+ blink::mojom::PictureInPictureServiceRequest request) {
+ return new PictureInPictureServiceImpl(render_frame_host, std::move(request));
+}
+
+void PictureInPictureServiceImpl::StartSession(
+ uint32_t player_id,
+ const base::Optional<viz::SurfaceId>& surface_id,
+ const gfx::Size& natural_size,
+ bool show_play_pause_button,
+ StartSessionCallback callback) {
+ player_id_ = MediaPlayerId(render_frame_host_, player_id);
+
+ auto* pip_controller = GetController();
+ if (pip_controller)
+ pip_controller->set_service(this);
+
+ gfx::Size window_size = web_contents_impl()->EnterPictureInPicture(
+ surface_id.value(), natural_size);
+
+ if (pip_controller)
+ pip_controller->SetAlwaysHidePlayPauseButton(show_play_pause_button);
+
+ std::move(callback).Run(window_size);
+}
+
+void PictureInPictureServiceImpl::EndSession(EndSessionCallback callback) {
+ DCHECK(player_id_);
+
+ ExitPictureInPictureInternal();
+
+ std::move(callback).Run();
+}
+
+void PictureInPictureServiceImpl::UpdateSession(
+ uint32_t player_id,
+ const base::Optional<viz::SurfaceId>& surface_id,
+ const gfx::Size& natural_size,
+ bool show_play_pause_button) {
+ player_id_ = MediaPlayerId(render_frame_host_, player_id);
+
+ // The PictureInPictureWindowController instance may not have been created by
+ // the embedder.
+ if (auto* pip_controller = GetController()) {
+ pip_controller->EmbedSurface(surface_id.value(), natural_size);
+ pip_controller->SetAlwaysHidePlayPauseButton(show_play_pause_button);
+ pip_controller->set_service(this);
+ }
+}
+
+void PictureInPictureServiceImpl::SetDelegate(
+ blink::mojom::PictureInPictureDelegatePtr delegate) {
+ delegate.set_connection_error_handler(
+ base::BindOnce(&PictureInPictureServiceImpl::OnDelegateDisconnected,
+ // delegate is held by |this|.
+ base::Unretained(this)));
+
+ if (delegate_)
+ mojo::ReportBadMessage("SetDelegate() should only be called once.");
+
+ delegate_ = std::move(delegate);
+}
+
+void PictureInPictureServiceImpl::NotifyWindowResized(const gfx::Size& size) {
+ if (delegate_)
+ delegate_->PictureInPictureWindowSizeChanged(size);
+}
+
+PictureInPictureServiceImpl::PictureInPictureServiceImpl(
+ RenderFrameHost* render_frame_host,
+ blink::mojom::PictureInPictureServiceRequest request)
+ : FrameServiceBase(render_frame_host, std::move(request)),
+ render_frame_host_(render_frame_host) {}
+
+PictureInPictureServiceImpl::~PictureInPictureServiceImpl() {
+ if (player_id_)
+ ExitPictureInPictureInternal();
+ if (GetController())
+ GetController()->set_service(nullptr);
+}
+
+PictureInPictureWindowControllerImpl*
+PictureInPictureServiceImpl::GetController() {
+ return PictureInPictureWindowControllerImpl::GetOrCreateForWebContents(
+ web_contents_impl());
+}
+
+void PictureInPictureServiceImpl::OnDelegateDisconnected() {
+ delegate_ = nullptr;
+}
+
+void PictureInPictureServiceImpl::ExitPictureInPictureInternal() {
+ web_contents_impl()->ExitPictureInPicture();
+
+ // Reset must happen after notifying the WebContents because it may interact
+ // with it.
+ player_id_.reset();
+
+ if (auto* controller = GetController())
+ controller->set_service(nullptr);
+}
+
+WebContentsImpl* PictureInPictureServiceImpl::web_contents_impl() {
+ return static_cast<WebContentsImpl*>(web_contents());
+}
+
+} // namespace content
diff --git a/content/browser/picture_in_picture/picture_in_picture_service_impl.h b/content/browser/picture_in_picture/picture_in_picture_service_impl.h
new file mode 100644
index 0000000..28710e3
--- /dev/null
+++ b/content/browser/picture_in_picture/picture_in_picture_service_impl.h
@@ -0,0 +1,78 @@
+// Copyright 2019 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.
+
+#ifndef CONTENT_BROWSER_PICTURE_IN_PICTURE_PICTURE_IN_PICTURE_SERVICE_IMPL_H_
+#define CONTENT_BROWSER_PICTURE_IN_PICTURE_PICTURE_IN_PICTURE_SERVICE_IMPL_H_
+
+#include "base/containers/unique_ptr_adapters.h"
+#include "content/common/content_export.h"
+#include "content/public/browser/frame_service_base.h"
+#include "third_party/blink/public/mojom/picture_in_picture/picture_in_picture.mojom.h"
+
+namespace content {
+
+class PictureInPictureWindowControllerImpl;
+
+// Receives Picture-in-Picture messages from a given RenderFrame. There is one
+// PictureInPictureServiceImpl per RenderFrameHost. It talks directly to the
+// PictureInPictureWindowControllerImpl. Only one service interacts with the
+// window at a given time.
+class CONTENT_EXPORT PictureInPictureServiceImpl final
+ : public content::FrameServiceBase<blink::mojom::PictureInPictureService> {
+ public:
+ static void Create(RenderFrameHost*,
+ blink::mojom::PictureInPictureServiceRequest);
+ static PictureInPictureServiceImpl* CreateForTesting(
+ RenderFrameHost*,
+ blink::mojom::PictureInPictureServiceRequest);
+
+ // PictureInPictureService implementation.
+ void StartSession(uint32_t player_id,
+ const base::Optional<viz::SurfaceId>& surface_id,
+ const gfx::Size& natural_size,
+ bool show_play_pause_button,
+ StartSessionCallback) final;
+ void EndSession(EndSessionCallback) final;
+ void UpdateSession(uint32_t player_id,
+ const base::Optional<viz::SurfaceId>& surface_id,
+ const gfx::Size& natural_size,
+ bool show_play_pause_button) final;
+ void SetDelegate(blink::mojom::PictureInPictureDelegatePtr) final;
+
+ void NotifyWindowResized(const gfx::Size&);
+
+ // Returns the player that is currently in Picture-in-Picture in the context
+ // of the frame associated with the service. Returns nullopt if there are
+ // none.
+ const base::Optional<MediaPlayerId>& player_id() const { return player_id_; }
+ void ResetPlayerId() { player_id_.reset(); }
+
+ private:
+ PictureInPictureServiceImpl(RenderFrameHost*,
+ blink::mojom::PictureInPictureServiceRequest);
+ ~PictureInPictureServiceImpl() override;
+
+ // Returns the PictureInPictureWindowControllerImpl associated with the
+ // WebContents. Can be null.
+ PictureInPictureWindowControllerImpl* GetController();
+
+ // Callack run when the delegate is disconnected. Only one delegate should be
+ // set at any given time.
+ void OnDelegateDisconnected();
+
+ // Implementation of ExitPictureInPicture without callback handling.
+ void ExitPictureInPictureInternal();
+
+ WebContentsImpl* web_contents_impl();
+
+ blink::mojom::PictureInPictureDelegatePtr delegate_ = nullptr;
+ RenderFrameHost* render_frame_host_ = nullptr;
+ base::Optional<MediaPlayerId> player_id_;
+
+ DISALLOW_COPY_AND_ASSIGN(PictureInPictureServiceImpl);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_PICTURE_IN_PICTURE_PICTURE_IN_PICTURE_SERVICE_IMPL_H_
diff --git a/content/browser/picture_in_picture/picture_in_picture_service_impl_unittest.cc b/content/browser/picture_in_picture/picture_in_picture_service_impl_unittest.cc
new file mode 100644
index 0000000..abefe2a56
--- /dev/null
+++ b/content/browser/picture_in_picture/picture_in_picture_service_impl_unittest.cc
@@ -0,0 +1,145 @@
+// Copyright 2019 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 "content/browser/picture_in_picture/picture_in_picture_service_impl.h"
+
+#include <memory>
+#include <utility>
+#include <vector>
+
+#include "content/common/media/media_player_delegate_messages.h"
+#include "content/public/browser/overlay_window.h"
+#include "content/public/browser/web_contents_delegate.h"
+#include "content/test/test_content_browser_client.h"
+#include "content/test/test_render_frame_host.h"
+#include "content/test/test_render_view_host.h"
+#include "content/test/test_web_contents.h"
+#include "mojo/public/cpp/bindings/interface_ptr.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+namespace content {
+
+class PictureInPictureDelegate : public WebContentsDelegate {
+ public:
+ PictureInPictureDelegate() = default;
+
+ MOCK_METHOD3(EnterPictureInPicture,
+ gfx::Size(WebContents*,
+ const viz::SurfaceId&,
+ const gfx::Size&));
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(PictureInPictureDelegate);
+};
+
+class TestOverlayWindow : public OverlayWindow {
+ public:
+ TestOverlayWindow() = default;
+ ~TestOverlayWindow() override{};
+
+ static std::unique_ptr<OverlayWindow> Create(
+ PictureInPictureWindowController* controller) {
+ return std::unique_ptr<OverlayWindow>(new TestOverlayWindow());
+ }
+
+ bool IsActive() const override { return false; }
+ void Close() override {}
+ void ShowInactive() override {}
+ void Hide() override {}
+ void SetPictureInPictureCustomControls(
+ const std::vector<blink::PictureInPictureControlInfo>& controls)
+ override {}
+ bool IsVisible() const override { return false; }
+ bool IsAlwaysOnTop() const override { return false; }
+ ui::Layer* GetLayer() override { return nullptr; }
+ gfx::Rect GetBounds() const override { return gfx::Rect(); }
+ void UpdateVideoSize(const gfx::Size& natural_size) override {}
+ void SetPlaybackState(PlaybackState playback_state) override {}
+ void SetAlwaysHidePlayPauseButton(bool is_visible) override {}
+ ui::Layer* GetWindowBackgroundLayer() override { return nullptr; }
+ ui::Layer* GetVideoLayer() override { return nullptr; }
+ gfx::Rect GetVideoBounds() override { return gfx::Rect(); }
+ void SetSkipAdButtonVisibility(bool is_visible) override {}
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(TestOverlayWindow);
+};
+
+class PictureInPictureTestBrowserClient : public TestContentBrowserClient {
+ public:
+ PictureInPictureTestBrowserClient() = default;
+ ~PictureInPictureTestBrowserClient() override = default;
+
+ std::unique_ptr<OverlayWindow> CreateWindowForPictureInPicture(
+ PictureInPictureWindowController* controller) override {
+ return TestOverlayWindow::Create(controller);
+ }
+};
+
+class PictureInPictureServiceImplTest : public RenderViewHostImplTestHarness {
+ public:
+ void SetUp() override {
+ RenderViewHostImplTestHarness::SetUp();
+ // WebUIControllerFactory::RegisterFactory(
+ // ContentWebUIControllerFactory::GetInstance());
+
+ SetBrowserClientForTesting(&browser_client_);
+
+ TestRenderFrameHost* render_frame_host = contents()->GetMainFrame();
+ render_frame_host->InitializeRenderFrameIfNeeded();
+
+ contents()->SetDelegate(&delegate_);
+
+ blink::mojom::PictureInPictureServiceRequest request;
+ service_impl_ = PictureInPictureServiceImpl::CreateForTesting(
+ render_frame_host, std::move(request));
+ }
+
+ void TearDown() override {
+ // WebUIControllerFactory::UnregisterFactoryForTesting(
+ // ContentWebUIControllerFactory::GetInstance());
+ RenderViewHostImplTestHarness::TearDown();
+ }
+
+ PictureInPictureServiceImpl& service() { return *service_impl_; }
+
+ PictureInPictureDelegate& delegate() { return delegate_; }
+
+ private:
+ PictureInPictureTestBrowserClient browser_client_;
+ PictureInPictureDelegate delegate_;
+ // Will be deleted when the frame is destroyed.
+ PictureInPictureServiceImpl* service_impl_;
+};
+
+TEST_F(PictureInPictureServiceImplTest, EnterPictureInPicture) {
+ const int kPlayerVideoOnlyId = 30;
+
+ // If Picture-in-Picture was never triggered, the media player id would not be
+ // set.
+ EXPECT_FALSE(service().player_id().has_value());
+
+ viz::SurfaceId surface_id =
+ viz::SurfaceId(viz::FrameSinkId(1, 1),
+ viz::LocalSurfaceId(
+ 11, base::UnguessableToken::Deserialize(0x111111, 0)));
+
+ EXPECT_CALL(delegate(),
+ EnterPictureInPicture(contents(), surface_id, gfx::Size(42, 42)));
+
+ service().StartSession(kPlayerVideoOnlyId, surface_id, gfx::Size(42, 42),
+ true /* show_play_pause_button */, base::DoNothing());
+ EXPECT_TRUE(service().player_id().has_value());
+ EXPECT_EQ(kPlayerVideoOnlyId, service().player_id()->delegate_id);
+
+ // Picture-in-Picture media player id should not be reset when the media is
+ // destroyed (e.g. video stops playing). This allows the Picture-in-Picture
+ // window to continue to control the media.
+ contents()->GetMainFrame()->OnMessageReceived(
+ MediaPlayerDelegateHostMsg_OnMediaDestroyed(
+ contents()->GetMainFrame()->GetRoutingID(), kPlayerVideoOnlyId));
+ EXPECT_TRUE(service().player_id().has_value());
+}
+
+} // namespace content
diff --git a/content/browser/picture_in_picture/picture_in_picture_window_controller_impl.cc b/content/browser/picture_in_picture/picture_in_picture_window_controller_impl.cc
index 44534b1..b26f9d0 100644
--- a/content/browser/picture_in_picture/picture_in_picture_window_controller_impl.cc
+++ b/content/browser/picture_in_picture/picture_in_picture_window_controller_impl.cc
@@ -10,6 +10,7 @@
#include "content/browser/media/media_web_contents_observer.h"
#include "content/browser/media/session/media_session_impl.h"
#include "content/browser/picture_in_picture/overlay_surface_embedder.h"
+#include "content/browser/picture_in_picture/picture_in_picture_service_impl.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/common/media/media_player_delegate_messages.h"
#include "content/public/browser/content_browser_client.h"
@@ -57,7 +58,8 @@
PictureInPictureWindowControllerImpl::PictureInPictureWindowControllerImpl(
WebContents* initiator)
- : initiator_(static_cast<WebContentsImpl* const>(initiator)) {
+ : WebContentsObserver(initiator),
+ initiator_(static_cast<WebContentsImpl* const>(initiator)) {
DCHECK(initiator_);
media_web_contents_observer_ = initiator_->media_web_contents_observer();
@@ -126,9 +128,7 @@
// surface id was updated for the same video, this is a no-op. This could
// be updated for a different video if another media player on the same
// |initiator_| enters Picture-in-Picture mode.
- media_player_id_ =
- media_web_contents_observer_->GetPictureInPictureVideoMediaPlayerId();
- UpdatePlaybackState(IsPlayerActive(), !media_player_id_.has_value());
+ UpdateMediaPlayerId();
window_->UpdateVideoSize(natural_size);
@@ -142,9 +142,9 @@
}
void PictureInPictureWindowControllerImpl::UpdateLayerBounds() {
- if (media_player_id_.has_value() && window_ && window_->IsVisible()) {
- media_web_contents_observer_->OnPictureInPictureWindowResize(
- window_->GetBounds().size());
+ if (media_player_id_.has_value() && service_ && window_ &&
+ window_->IsVisible()) {
+ service_->NotifyWindowResized(window_->GetBounds().size());
}
if (embedder_)
@@ -152,10 +152,12 @@
}
bool PictureInPictureWindowControllerImpl::IsPlayerActive() {
- if (!media_player_id_.has_value()) {
- media_player_id_ =
- media_web_contents_observer_->GetPictureInPictureVideoMediaPlayerId();
- }
+ if (!media_player_id_.has_value())
+ media_player_id_ = service_ ? service_->player_id() : base::nullopt;
+
+ // At creation time, the player id may not be set.
+ if (!media_player_id_.has_value())
+ return false;
return media_player_id_.has_value() &&
media_web_contents_observer_->IsPlayerActive(*media_player_id_);
@@ -219,6 +221,11 @@
media_player_id_->delegate_id, control_id));
}
+void PictureInPictureWindowControllerImpl::UpdateMediaPlayerId() {
+ media_player_id_ = service_ ? service_->player_id() : base::nullopt;
+ UpdatePlaybackState(IsPlayerActive(), !media_player_id_.has_value());
+}
+
void PictureInPictureWindowControllerImpl::SetAlwaysHidePlayPauseButton(
bool is_visible) {
always_hide_play_pause_button_ = is_visible;
@@ -254,6 +261,33 @@
window_->SetSkipAdButtonVisibility(media_session_action_skip_ad_handled_);
}
+void PictureInPictureWindowControllerImpl::MediaStartedPlaying(
+ const MediaPlayerInfo&,
+ const MediaPlayerId& media_player_id) {
+ if (initiator_->IsBeingDestroyed())
+ return;
+
+ if (media_player_id_ != media_player_id)
+ return;
+
+ UpdatePlaybackState(true /* is_playing */, false /* reached_end_of_stream */);
+}
+
+void PictureInPictureWindowControllerImpl::MediaStoppedPlaying(
+ const MediaPlayerInfo&,
+ const MediaPlayerId& media_player_id,
+ WebContentsObserver::MediaStoppedReason reason) {
+ if (initiator_->IsBeingDestroyed())
+ return;
+
+ if (media_player_id_ != media_player_id)
+ return;
+
+ UpdatePlaybackState(
+ false /* is_playing */,
+ reason == WebContentsObserver::MediaStoppedReason::kReachedEndOfStream);
+}
+
void PictureInPictureWindowControllerImpl::OnLeavingPictureInPicture(
bool should_pause_video,
bool should_reset_pip_player) {
@@ -269,8 +303,12 @@
new MediaPlayerDelegateMsg_EndPictureInPictureMode(
media_player_id_->render_frame_host->GetRoutingID(),
media_player_id_->delegate_id));
- if (should_reset_pip_player)
- media_web_contents_observer_->ResetPictureInPictureVideoMediaPlayerId();
+
+ if (should_reset_pip_player) {
+ DCHECK(service_);
+ service_->ResetPlayerId();
+ media_player_id_.reset();
+ }
}
}
diff --git a/content/browser/picture_in_picture/picture_in_picture_window_controller_impl.h b/content/browser/picture_in_picture/picture_in_picture_window_controller_impl.h
index f44a7ca..91701f7 100644
--- a/content/browser/picture_in_picture/picture_in_picture_window_controller_impl.h
+++ b/content/browser/picture_in_picture/picture_in_picture_window_controller_impl.h
@@ -14,10 +14,11 @@
namespace content {
+class MediaWebContentsObserver;
class OverlaySurfaceEmbedder;
+class PictureInPictureServiceImpl;
class WebContents;
class WebContentsImpl;
-class MediaWebContentsObserver;
// TODO(thakis,mlamouri): PictureInPictureWindowControllerImpl isn't
// CONTENT_EXPORT'd because it creates complicated build issues with
@@ -27,7 +28,8 @@
// work with it. https://crbug.com/589840.
class PictureInPictureWindowControllerImpl
: public PictureInPictureWindowController,
- public WebContentsUserData<PictureInPictureWindowControllerImpl> {
+ public WebContentsUserData<PictureInPictureWindowControllerImpl>,
+ public WebContentsObserver {
public:
// Gets a reference to the controller associated with |initiator| and creates
// one if it does not exist. The returned pointer is guaranteed to be
@@ -62,6 +64,22 @@
CONTENT_EXPORT void MediaSessionActionsChanged(
const std::set<media_session::mojom::MediaSessionAction>& actions);
+ // WebContentsObserver:
+ void MediaStartedPlaying(const MediaPlayerInfo&,
+ const MediaPlayerId&) override;
+ void MediaStoppedPlaying(const MediaPlayerInfo&,
+ const MediaPlayerId&,
+ WebContentsObserver::MediaStoppedReason) override;
+
+ // TODO(mlamouri): temporary method used because of the media player id is
+ // stored in a different location from the one that is used to update the
+ // state of this object.
+ void UpdateMediaPlayerId();
+
+ void set_service(PictureInPictureServiceImpl* service) {
+ service_ = service;
+ };
+
private:
friend class WebContentsUserData<PictureInPictureWindowControllerImpl>;
@@ -89,6 +107,7 @@
std::unique_ptr<OverlayWindow> window_;
std::unique_ptr<OverlaySurfaceEmbedder> embedder_;
+ // TODO(929156): remove this as it should be accessible via `web_contents()`.
WebContentsImpl* const initiator_;
// Used to determine the state of the media player and route messages to
@@ -109,6 +128,11 @@
// Session API in UpdatePlayPauseButtonVisibility().
bool always_hide_play_pause_button_ = false;
+ // Service currently associated with the Picture-in-Picture window. The
+ // service makes the bridge with the renderer process by sending enter/exit
+ // requests. It is also holding the Picture-in-Picture MediaPlayerId.
+ PictureInPictureServiceImpl* service_ = nullptr;
+
WEB_CONTENTS_USER_DATA_KEY_DECL();
DISALLOW_COPY_AND_ASSIGN(PictureInPictureWindowControllerImpl);
diff --git a/content/browser/web_contents/web_contents_impl_unittest.cc b/content/browser/web_contents/web_contents_impl_unittest.cc
index 5d505aa..74e086c0 100644
--- a/content/browser/web_contents/web_contents_impl_unittest.cc
+++ b/content/browser/web_contents/web_contents_impl_unittest.cc
@@ -26,7 +26,6 @@
#include "content/browser/webui/web_ui_controller_factory_registry.h"
#include "content/common/frame_messages.h"
#include "content/common/input/synthetic_web_input_event_builders.h"
-#include "content/common/media/media_player_delegate_messages.h"
#include "content/common/view_messages.h"
#include "content/public/browser/content_browser_client.h"
#include "content/public/browser/global_request_id.h"
@@ -35,7 +34,6 @@
#include "content/public/browser/navigation_details.h"
#include "content/public/browser/notification_details.h"
#include "content/public/browser/notification_source.h"
-#include "content/public/browser/overlay_window.h"
#include "content/public/browser/render_widget_host_view.h"
#include "content/public/browser/ssl_host_state_delegate.h"
#include "content/public/browser/storage_partition.h"
@@ -3242,113 +3240,6 @@
EXPECT_EQ(SK_ColorGREEN, observer.last_theme_color());
}
-class PictureInPictureDelegate : public WebContentsDelegate {
- public:
- PictureInPictureDelegate() = default;
-
- MOCK_METHOD3(EnterPictureInPicture,
- gfx::Size(content::WebContents* web_contents,
- const viz::SurfaceId&,
- const gfx::Size&));
-
- private:
- DISALLOW_COPY_AND_ASSIGN(PictureInPictureDelegate);
-};
-
-class TestOverlayWindow : public OverlayWindow {
- public:
- TestOverlayWindow() = default;
- ~TestOverlayWindow() override{};
-
- static std::unique_ptr<OverlayWindow> Create(
- PictureInPictureWindowController* controller) {
- return std::unique_ptr<OverlayWindow>(new TestOverlayWindow());
- }
-
- bool IsActive() const override { return false; }
- void Close() override {}
- void ShowInactive() override {}
- void Hide() override {}
- void SetPictureInPictureCustomControls(
- const std::vector<blink::PictureInPictureControlInfo>& controls)
- override {}
- bool IsVisible() const override { return false; }
- bool IsAlwaysOnTop() const override { return false; }
- ui::Layer* GetLayer() override { return nullptr; }
- gfx::Rect GetBounds() const override { return gfx::Rect(); }
- void UpdateVideoSize(const gfx::Size& natural_size) override {}
- void SetPlaybackState(PlaybackState playback_state) override {}
- void SetAlwaysHidePlayPauseButton(bool is_visible) override {}
- void SetSkipAdButtonVisibility(bool is_visible) override {}
- ui::Layer* GetWindowBackgroundLayer() override { return nullptr; }
- ui::Layer* GetVideoLayer() override { return nullptr; }
- gfx::Rect GetVideoBounds() override { return gfx::Rect(); }
-
- private:
- DISALLOW_COPY_AND_ASSIGN(TestOverlayWindow);
-};
-
-class PictureInPictureTestBrowserClient : public TestContentBrowserClient {
- public:
- PictureInPictureTestBrowserClient()
- : original_browser_client_(SetBrowserClientForTesting(this)) {}
-
- ~PictureInPictureTestBrowserClient() override {
- SetBrowserClientForTesting(original_browser_client_);
- }
-
- std::unique_ptr<OverlayWindow> CreateWindowForPictureInPicture(
- PictureInPictureWindowController* controller) override {
- return TestOverlayWindow::Create(controller);
- }
-
- private:
- ContentBrowserClient* original_browser_client_;
-};
-
-TEST_F(WebContentsImplTest, EnterPictureInPicture) {
- PictureInPictureTestBrowserClient browser_client;
- SetBrowserClientForTesting(&browser_client);
-
- const int kPlayerVideoOnlyId = 30; /* arbitrary and used for tests */
-
- PictureInPictureDelegate delegate;
- contents()->SetDelegate(&delegate);
-
- MediaWebContentsObserver* observer =
- contents()->media_web_contents_observer();
- TestRenderFrameHost* rfh = main_test_rfh();
- rfh->InitializeRenderFrameIfNeeded();
-
- // If Picture-in-Picture was never triggered, the media player id would not be
- // set.
- EXPECT_FALSE(observer->GetPictureInPictureVideoMediaPlayerId().has_value());
-
- viz::SurfaceId surface_id =
- viz::SurfaceId(viz::FrameSinkId(1, 1),
- viz::LocalSurfaceId(
- 11, base::UnguessableToken::Deserialize(0x111111, 0)));
-
- EXPECT_CALL(delegate,
- EnterPictureInPicture(contents(), surface_id, gfx::Size(42, 42)));
-
- rfh->OnMessageReceived(
- MediaPlayerDelegateHostMsg_OnPictureInPictureModeStarted(
- rfh->GetRoutingID(), kPlayerVideoOnlyId, surface_id /* surface_id */,
- gfx::Size(42, 42) /* natural_size */, 1 /* request_id */,
- true /* show_play_pause_button */));
- EXPECT_TRUE(observer->GetPictureInPictureVideoMediaPlayerId().has_value());
- EXPECT_EQ(kPlayerVideoOnlyId,
- observer->GetPictureInPictureVideoMediaPlayerId()->delegate_id);
-
- // Picture-in-Picture media player id should not be reset when the media is
- // destroyed (e.g. video stops playing). This allows the Picture-in-Picture
- // window to continue to control the media.
- rfh->OnMessageReceived(MediaPlayerDelegateHostMsg_OnMediaDestroyed(
- rfh->GetRoutingID(), kPlayerVideoOnlyId));
- EXPECT_TRUE(observer->GetPictureInPictureVideoMediaPlayerId().has_value());
-}
-
TEST_F(WebContentsImplTest, ParseDownloadHeaders) {
download::DownloadUrlParameters::RequestHeadersType request_headers =
WebContentsImpl::ParseDownloadHeaders("A: 1\r\nB: 2\r\nC: 3\r\n\r\n");
diff --git a/content/common/media/media_player_delegate_messages.h b/content/common/media/media_player_delegate_messages.h
index b482679e..4870d5b2 100644
--- a/content/common/media/media_player_delegate_messages.h
+++ b/content/common/media/media_player_delegate_messages.h
@@ -76,23 +76,6 @@
int /* delegate_id, distinguishes instances */,
std::string /* control_id */)
-IPC_MESSAGE_ROUTED2(MediaPlayerDelegateMsg_OnPictureInPictureWindowResize,
- int /* delegate_id, distinguishes instances */,
- gfx::Size /* window_size */)
-
-// ----------------------------------------------------------------------------
-// Messages from the browser to the renderer acknowledging changes happened.
-// ----------------------------------------------------------------------------
-
-IPC_MESSAGE_ROUTED3(MediaPlayerDelegateMsg_OnPictureInPictureModeStarted_ACK,
- int /* delegate id */,
- int /* request_id */,
- gfx::Size /* window_size */)
-
-IPC_MESSAGE_ROUTED2(MediaPlayerDelegateMsg_OnPictureInPictureModeEnded_ACK,
- int /* delegate id */,
- int /* request_id */)
-
// ----------------------------------------------------------------------------
// Messages from the renderer notifying the browser of playback state changes.
// ----------------------------------------------------------------------------
@@ -124,23 +107,6 @@
int /* delegate_id, distinguishes instances */,
gfx::Size /* new size of video */)
-IPC_MESSAGE_ROUTED5(MediaPlayerDelegateHostMsg_OnPictureInPictureModeStarted,
- int /* delegate id */,
- viz::SurfaceId /* surface_id */,
- gfx::Size /* natural_size */,
- int /* request_id */,
- bool /* show_play_pause_button */)
-
-IPC_MESSAGE_ROUTED2(MediaPlayerDelegateHostMsg_OnPictureInPictureModeEnded,
- int /* delegate id */,
- int /* request_id */)
-
-IPC_MESSAGE_ROUTED4(MediaPlayerDelegateHostMsg_OnPictureInPictureSurfaceChanged,
- int /* delegate id */,
- viz::SurfaceId /* surface_id */,
- gfx::Size /* natural_size */,
- bool /* show_play_pause_button */)
-
IPC_MESSAGE_ROUTED2(
MediaPlayerDelegateHostMsg_OnSetPictureInPictureCustomControls,
int /* delegate id */,
diff --git a/content/public/app/content_browser_manifest.cc b/content/public/app/content_browser_manifest.cc
index cf6cf19..54bccd3 100644
--- a/content/public/app/content_browser_manifest.cc
+++ b/content/public/app/content_browser_manifest.cc
@@ -227,6 +227,7 @@
"blink.mojom.MediaSessionService",
"blink.mojom.NotificationService",
"blink.mojom.PermissionService",
+ "blink.mojom.PictureInPictureService",
"blink.mojom.Portal",
"blink.mojom.PrefetchURLLoaderService",
"blink.mojom.PresentationService",
diff --git a/content/renderer/media/renderer_webmediaplayer_delegate.cc b/content/renderer/media/renderer_webmediaplayer_delegate.cc
index f6dbb40..d7d9255 100644
--- a/content/renderer/media/renderer_webmediaplayer_delegate.cc
+++ b/content/renderer/media/renderer_webmediaplayer_delegate.cc
@@ -120,30 +120,6 @@
delegate_id, muted));
}
-void RendererWebMediaPlayerDelegate::DidPictureInPictureModeStart(
- int delegate_id,
- const viz::SurfaceId& surface_id,
- const gfx::Size& natural_size,
- blink::WebMediaPlayer::PipWindowOpenedCallback callback,
- bool show_play_pause_button) {
- int request_id = next_picture_in_picture_callback_id_++;
- enter_picture_in_picture_callback_map_.insert(
- std::make_pair(request_id, std::move(callback)));
- Send(new MediaPlayerDelegateHostMsg_OnPictureInPictureModeStarted(
- routing_id(), delegate_id, surface_id, natural_size, request_id,
- show_play_pause_button));
-}
-
-void RendererWebMediaPlayerDelegate::DidPictureInPictureModeEnd(
- int delegate_id,
- base::OnceClosure callback) {
- int request_id = next_picture_in_picture_callback_id_++;
- exit_picture_in_picture_callback_map_.insert(
- std::make_pair(request_id, std::move(callback)));
- Send(new MediaPlayerDelegateHostMsg_OnPictureInPictureModeEnded(
- routing_id(), delegate_id, request_id));
-}
-
void RendererWebMediaPlayerDelegate::DidSetPictureInPictureCustomControls(
int delegate_id,
const std::vector<blink::PictureInPictureControlInfo>& controls) {
@@ -151,24 +127,6 @@
routing_id(), delegate_id, controls));
}
-void RendererWebMediaPlayerDelegate::DidPictureInPictureSurfaceChange(
- int delegate_id,
- const viz::SurfaceId& surface_id,
- const gfx::Size& natural_size,
- bool show_play_pause_button) {
- Send(new MediaPlayerDelegateHostMsg_OnPictureInPictureSurfaceChanged(
- routing_id(), delegate_id, surface_id, natural_size,
- show_play_pause_button));
-}
-
-void RendererWebMediaPlayerDelegate::
- RegisterPictureInPictureWindowResizeCallback(
- int player_id,
- blink::WebMediaPlayer::PipWindowResizedCallback callback) {
- picture_in_picture_window_resize_observer_ =
- std::make_pair(player_id, std::move(callback));
-}
-
void RendererWebMediaPlayerDelegate::DidPause(int player_id) {
DVLOG(2) << __func__ << "(" << player_id << ")";
DCHECK(id_map_.Lookup(player_id));
@@ -291,13 +249,6 @@
OnPictureInPictureModeEnded)
IPC_MESSAGE_HANDLER(MediaPlayerDelegateMsg_ClickPictureInPictureControl,
OnPictureInPictureControlClicked)
- IPC_MESSAGE_HANDLER(MediaPlayerDelegateMsg_OnPictureInPictureModeEnded_ACK,
- OnPictureInPictureModeEndedAck)
- IPC_MESSAGE_HANDLER(
- MediaPlayerDelegateMsg_OnPictureInPictureModeStarted_ACK,
- OnPictureInPictureModeStartedAck)
- IPC_MESSAGE_HANDLER(MediaPlayerDelegateMsg_OnPictureInPictureWindowResize,
- OnPictureInPictureWindowResize)
IPC_MESSAGE_UNHANDLED(return false)
IPC_END_MESSAGE_MAP()
return true;
@@ -417,39 +368,6 @@
observer->OnPictureInPictureControlClicked(control_id);
}
-void RendererWebMediaPlayerDelegate::OnPictureInPictureModeEndedAck(
- int player_id,
- int request_id) {
- auto iter = exit_picture_in_picture_callback_map_.find(request_id);
- DCHECK(iter != exit_picture_in_picture_callback_map_.end());
-
- std::move(iter->second).Run();
- exit_picture_in_picture_callback_map_.erase(iter);
-}
-
-void RendererWebMediaPlayerDelegate::OnPictureInPictureModeStartedAck(
- int player_id,
- int request_id,
- const gfx::Size& window_size) {
- auto iter = enter_picture_in_picture_callback_map_.find(request_id);
- DCHECK(iter != enter_picture_in_picture_callback_map_.end());
-
- std::move(iter->second).Run(blink::WebSize(window_size));
- enter_picture_in_picture_callback_map_.erase(iter);
-}
-
-void RendererWebMediaPlayerDelegate::OnPictureInPictureWindowResize(
- int player_id,
- const gfx::Size& window_size) {
- if (!picture_in_picture_window_resize_observer_ ||
- picture_in_picture_window_resize_observer_->first != player_id) {
- return;
- }
-
- picture_in_picture_window_resize_observer_->second.Run(
- blink::WebSize(window_size));
-}
-
void RendererWebMediaPlayerDelegate::ScheduleUpdateTask() {
if (!pending_update_task_) {
base::ThreadTaskRunnerHandle::Get()->PostTask(
diff --git a/content/renderer/media/renderer_webmediaplayer_delegate.h b/content/renderer/media/renderer_webmediaplayer_delegate.h
index ad78d316..1f7242c3 100644
--- a/content/renderer/media/renderer_webmediaplayer_delegate.h
+++ b/content/renderer/media/renderer_webmediaplayer_delegate.h
@@ -65,23 +65,9 @@
blink::WebFullscreenVideoStatus fullscreen_video_status) override;
void DidPlayerSizeChange(int delegate_id, const gfx::Size& size) override;
void DidPlayerMutedStatusChange(int delegate_id, bool muted) override;
- void DidPictureInPictureModeStart(
- int delegate_id,
- const viz::SurfaceId&,
- const gfx::Size&,
- blink::WebMediaPlayer::PipWindowOpenedCallback,
- bool show_play_pause_button) override;
- void DidPictureInPictureModeEnd(int delegate_id, base::OnceClosure) override;
void DidSetPictureInPictureCustomControls(
int delegate_id,
const std::vector<blink::PictureInPictureControlInfo>& controls) override;
- void DidPictureInPictureSurfaceChange(int delegate_id,
- const viz::SurfaceId&,
- const gfx::Size&,
- bool show_play_pause_button) override;
- void RegisterPictureInPictureWindowResizeCallback(
- int player_id,
- blink::WebMediaPlayer::PipWindowResizedCallback) override;
// content::RenderFrameObserver overrides.
void WasHidden() override;
@@ -114,11 +100,6 @@
void OnPictureInPictureModeEnded(int player_id);
void OnPictureInPictureControlClicked(int player_id,
const std::string& control_id);
- void OnPictureInPictureModeEndedAck(int player_id, int request_id);
- void OnPictureInPictureModeStartedAck(int player_id,
- int request_id,
- const gfx::Size&);
- void OnPictureInPictureWindowResize(int player_id, const gfx::Size&);
// Schedules UpdateTask() to run soon.
void ScheduleUpdateTask();
@@ -185,35 +166,6 @@
// when the idle cleanup timer should be fired more aggressively.
bool is_jelly_bean_;
- // Map associating a callback with a request sent to the browser process. The
- // index is used as a unique request id that is passed to the browser process
- // and will then ACK with the same id which will be used to run the right
- // callback.
- using ExitPictureInPictureCallbackMap =
- base::flat_map<int, base::OnceClosure>;
- ExitPictureInPictureCallbackMap exit_picture_in_picture_callback_map_;
-
- // Map associating a callback with a request sent to the browser process. The
- // index is used as a unique request id that is passed to the browser process
- // and will then ACK with the same id which will be used to run the right
- // callback.
- using EnterPictureInPictureCallbackMap =
- base::flat_map<int, blink::WebMediaPlayer::PipWindowOpenedCallback>;
- EnterPictureInPictureCallbackMap enter_picture_in_picture_callback_map_;
-
- // Counter that is used to use unique request id associated with
- // picture-in-picture callbacks. It is incremented every time it is used.
- int next_picture_in_picture_callback_id_ = 0;
-
- // Associating a player id and a Picture-in-Picture window resize callback.
- // It holds the callback alive and guarantees that the notification sent from
- // the browser proccess matches the player currently in Picture-in-Picture in
- // the renderer.
- using PictureInPictureWindowResizeObserver =
- std::pair<int, blink::WebMediaPlayer::PipWindowResizedCallback>;
- base::Optional<PictureInPictureWindowResizeObserver>
- picture_in_picture_window_resize_observer_;
-
DISALLOW_COPY_AND_ASSIGN(RendererWebMediaPlayerDelegate);
};
diff --git a/content/renderer/media/stream/webmediaplayer_ms.cc b/content/renderer/media/stream/webmediaplayer_ms.cc
index 5f58e9e..3b11c6d 100644
--- a/content/renderer/media/stream/webmediaplayer_ms.cc
+++ b/content/renderer/media/stream/webmediaplayer_ms.cc
@@ -425,11 +425,8 @@
// disabled.
// The viz::SurfaceId may be updated when the video begins playback or when
// the size of the video changes.
- if (client_ && IsInPictureInPicture() && !client_->IsInAutoPIP()) {
- delegate_->DidPictureInPictureSurfaceChange(
- delegate_id_, surface_id, NaturalSize(),
- false /* show_play_pause_button */);
- }
+ if (client_)
+ client_->OnPictureInPictureStateChange();
}
void WebMediaPlayerMS::TrackAdded(const blink::WebMediaStreamTrack& track) {
@@ -460,6 +457,16 @@
audio_renderer_->Stop();
}
+int WebMediaPlayerMS::GetDelegateId() {
+ return delegate_id_;
+}
+
+base::Optional<viz::SurfaceId> WebMediaPlayerMS::GetSurfaceId() {
+ if (bridge_)
+ return bridge_->GetSurfaceId();
+ return base::nullopt;
+}
+
void WebMediaPlayerMS::Reload() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
if (web_stream_.IsNull())
@@ -640,30 +647,17 @@
delegate_->DidPlayerMutedStatusChange(delegate_id_, volume == 0.0);
}
-void WebMediaPlayerMS::EnterPictureInPicture(
- blink::WebMediaPlayer::PipWindowOpenedCallback callback) {
+void WebMediaPlayerMS::EnterPictureInPicture() {
if (!bridge_)
ActivateSurfaceLayerForVideo();
DCHECK(bridge_);
-
- const viz::SurfaceId& surface_id = bridge_->GetSurfaceId();
- DCHECK(surface_id.is_valid());
-
- // Notifies the browser process that the player should now be in
- // Picture-in-Picture mode.
- delegate_->DidPictureInPictureModeStart(delegate_id_, surface_id,
- NaturalSize(), std::move(callback),
- false /* show_play_pause_button */);
+ DCHECK(bridge_->GetSurfaceId().is_valid());
}
-void WebMediaPlayerMS::ExitPictureInPicture(
- blink::WebMediaPlayer::PipWindowClosedCallback callback) {
- // Notifies the browser process that Picture-in-Picture has ended. It will
- // clear out the states and close the window.
- delegate_->DidPictureInPictureModeEnd(delegate_id_, std::move(callback));
-
+void WebMediaPlayerMS::ExitPictureInPicture() {
// Internal cleanups.
+ // TODO(mlamouri): remove the need for this.
OnPictureInPictureModeEnded();
}
@@ -672,14 +666,6 @@
delegate_->DidSetPictureInPictureCustomControls(delegate_id_, controls);
}
-void WebMediaPlayerMS::RegisterPictureInPictureWindowResizeCallback(
- blink::WebMediaPlayer::PipWindowResizedCallback callback) {
- DCHECK(IsInPictureInPicture() && !client_->IsInAutoPIP());
-
- delegate_->RegisterPictureInPictureWindowResizeCallback(delegate_id_,
- std::move(callback));
-}
-
void WebMediaPlayerMS::SetSinkId(
const blink::WebString& sink_id,
std::unique_ptr<blink::WebSetSinkIdCallbacks> web_callback) {
diff --git a/content/renderer/media/stream/webmediaplayer_ms.h b/content/renderer/media/stream/webmediaplayer_ms.h
index 1dde82e..3fcc581 100644
--- a/content/renderer/media/stream/webmediaplayer_ms.h
+++ b/content/renderer/media/stream/webmediaplayer_ms.h
@@ -116,14 +116,10 @@
void Seek(double seconds) override;
void SetRate(double rate) override;
void SetVolume(double volume) override;
- void EnterPictureInPicture(
- blink::WebMediaPlayer::PipWindowOpenedCallback callback) override;
- void ExitPictureInPicture(
- blink::WebMediaPlayer::PipWindowClosedCallback callback) override;
+ void EnterPictureInPicture() override;
+ void ExitPictureInPicture() override;
void SetPictureInPictureCustomControls(
const std::vector<blink::PictureInPictureControlInfo>&) override;
- void RegisterPictureInPictureWindowResizeCallback(
- blink::WebMediaPlayer::PipWindowResizedCallback) override;
void SetSinkId(
const blink::WebString& sink_id,
std::unique_ptr<blink::WebSetSinkIdCallbacks> web_callback) override;
@@ -240,6 +236,8 @@
void TrackAdded(const blink::WebMediaStreamTrack& track) override;
void TrackRemoved(const blink::WebMediaStreamTrack& track) override;
void ActiveStateChanged(bool is_active) override;
+ int GetDelegateId() override;
+ base::Optional<viz::SurfaceId> GetSurfaceId() override;
void OnDisplayTypeChanged(WebMediaPlayer::DisplayType) override;
diff --git a/content/renderer/media/stream/webmediaplayer_ms_unittest.cc b/content/renderer/media/stream/webmediaplayer_ms_unittest.cc
index ccf92dd..c649211 100644
--- a/content/renderer/media/stream/webmediaplayer_ms_unittest.cc
+++ b/content/renderer/media/stream/webmediaplayer_ms_unittest.cc
@@ -113,21 +113,9 @@
EXPECT_EQ(delegate_id_, delegate_id);
}
- MOCK_METHOD5(DidPictureInPictureModeStart,
- void(int,
- const viz::SurfaceId&,
- const gfx::Size&,
- blink::WebMediaPlayer::PipWindowOpenedCallback,
- bool));
- MOCK_METHOD2(DidPictureInPictureModeEnd,
- void(int, blink::WebMediaPlayer::PipWindowClosedCallback));
MOCK_METHOD2(DidSetPictureInPictureCustomControls,
void(int,
const std::vector<blink::PictureInPictureControlInfo>&));
- MOCK_METHOD4(DidPictureInPictureSurfaceChange,
- void(int, const viz::SurfaceId&, const gfx::Size&, bool));
- MOCK_METHOD2(RegisterPictureInPictureWindowResizeCallback,
- void(int, blink::WebMediaPlayer::PipWindowResizedCallback));
void DidPause(int delegate_id) override {
EXPECT_EQ(delegate_id_, delegate_id);
@@ -618,6 +606,7 @@
void StopRendering() override;
void DidReceiveFrame() override;
bool IsDrivingFrameUpdates() const override { return true; }
+ void OnPictureInPictureStateChange() override {}
// For test use
void SetBackgroundRendering(bool background_rendering) {
@@ -1407,68 +1396,9 @@
}
#endif
-// Tests delegate methods are called when Picture-in-Picture is triggered.
-TEST_P(WebMediaPlayerMSTest, PictureInPictureTriggerCallback) {
- InitializeWebMediaPlayerMS();
-
- // It works only a surface layer is used instead of a video layer.
- if (!enable_surface_layer_for_video_) {
- EXPECT_CALL(*this, DoSetCcLayer(false));
- return;
- }
-
- MockMediaStreamVideoRenderer* provider = LoadAndGetFrameProvider(true);
-
- int tokens[] = {0, 33, 66, 100, 133, 166, 200, 233, 266, 300,
- 333, 366, 400, 433, 466, 500, 533, 566, 600};
- std::vector<int> timestamps(tokens, tokens + sizeof(tokens) / sizeof(int));
- provider->QueueFrames(timestamps);
-
- EXPECT_CALL(*submitter_ptr_, StartRendering());
- EXPECT_CALL(*this, DisplayType()).Times(2);
- EXPECT_CALL(*this, DoReadyStateChanged(
- blink::WebMediaPlayer::kReadyStateHaveMetadata));
- EXPECT_CALL(*this, DoReadyStateChanged(
- blink::WebMediaPlayer::kReadyStateHaveEnoughData));
- EXPECT_CALL(*this,
- CheckSizeChanged(gfx::Size(kStandardWidth, kStandardHeight)));
- message_loop_controller_.RunAndWaitForStatus(
- media::PipelineStatus::PIPELINE_OK);
- testing::Mock::VerifyAndClearExpectations(this);
-
- EXPECT_CALL(*this, DisplayType())
- .WillRepeatedly(
- Return(blink::WebMediaPlayer::DisplayType::kPictureInPicture));
-
- const gfx::Size natural_size = player_->NaturalSize();
- EXPECT_CALL(delegate_, DidPictureInPictureSurfaceChange(
- delegate_.delegate_id(),
- surface_layer_bridge_ptr_->GetSurfaceId(),
- natural_size, false))
- .Times(2);
-
- player_->OnSurfaceIdUpdated(surface_layer_bridge_ptr_->GetSurfaceId());
-
- EXPECT_CALL(delegate_, DidPictureInPictureModeStart(
- delegate_.delegate_id(),
- surface_layer_bridge_ptr_->GetSurfaceId(),
- natural_size, _, false));
-
- player_->EnterPictureInPicture(base::DoNothing());
- player_->OnSurfaceIdUpdated(surface_layer_bridge_ptr_->GetSurfaceId());
-
- // Updating SurfaceId should NOT exit Picture-in-Picture.
- EXPECT_CALL(delegate_, DidPictureInPictureModeEnd(delegate_.delegate_id(), _))
- .Times(0);
-
- testing::Mock::VerifyAndClearExpectations(this);
- EXPECT_CALL(*this, DoSetCcLayer(false));
- EXPECT_CALL(*submitter_ptr_, StopUsingProvider());
-}
-
-INSTANTIATE_TEST_SUITE_P(,
- WebMediaPlayerMSTest,
- ::testing::Combine(::testing::Bool(),
- ::testing::Bool(),
- ::testing::Bool()));
+INSTANTIATE_TEST_CASE_P(,
+ WebMediaPlayerMSTest,
+ ::testing::Combine(::testing::Bool(),
+ ::testing::Bool(),
+ ::testing::Bool()));
} // namespace content
diff --git a/content/renderer/media_capture_from_element/html_video_element_capturer_source_unittest.cc b/content/renderer/media_capture_from_element/html_video_element_capturer_source_unittest.cc
index 18325df..510dff25 100644
--- a/content/renderer/media_capture_from_element/html_video_element_capturer_source_unittest.cc
+++ b/content/renderer/media_capture_from_element/html_video_element_capturer_source_unittest.cc
@@ -50,12 +50,10 @@
void Seek(double seconds) override {}
void SetRate(double) override {}
void SetVolume(double) override {}
- void EnterPictureInPicture(PipWindowOpenedCallback) override {}
- void ExitPictureInPicture(PipWindowClosedCallback) override {}
+ void EnterPictureInPicture() override {}
+ void ExitPictureInPicture() override {}
void SetPictureInPictureCustomControls(
const std::vector<blink::PictureInPictureControlInfo>&) override {}
- void RegisterPictureInPictureWindowResizeCallback(
- PipWindowResizedCallback) override {}
blink::WebTimeRanges Buffered() const override {
return blink::WebTimeRanges();
}
diff --git a/content/test/BUILD.gn b/content/test/BUILD.gn
index 8b6db3d0..245c3e9 100644
--- a/content/test/BUILD.gn
+++ b/content/test/BUILD.gn
@@ -1542,6 +1542,7 @@
"../browser/payments/payment_app_content_unittest_base.h",
"../browser/payments/payment_app_provider_impl_unittest.cc",
"../browser/payments/payment_manager_unittest.cc",
+ "../browser/picture_in_picture/picture_in_picture_service_impl_unittest.cc",
"../browser/plugin_list_unittest.cc",
"../browser/presentation/presentation_service_impl_unittest.cc",
"../browser/renderer_host/clipboard_host_impl_unittest.cc",
diff --git a/media/blink/webmediaplayer_delegate.h b/media/blink/webmediaplayer_delegate.h
index af33ac9..c3f9c0d7 100644
--- a/media/blink/webmediaplayer_delegate.h
+++ b/media/blink/webmediaplayer_delegate.h
@@ -16,10 +16,6 @@
class Size;
} // namespace gfx
-namespace viz {
-class SurfaceId;
-} // namespace viz
-
namespace media {
enum class MediaContentType;
@@ -118,38 +114,12 @@
// Notify that the muted status of the media player has changed.
virtual void DidPlayerMutedStatusChange(int delegate_id, bool muted) = 0;
- // Notify that the source media player has entered Picture-in-Picture mode.
- virtual void DidPictureInPictureModeStart(
- int delegate_id,
- const viz::SurfaceId&,
- const gfx::Size&,
- blink::WebMediaPlayer::PipWindowOpenedCallback,
- bool show_play_pause_button) = 0;
-
- // Notify that the source media player has exited Picture-in-Picture mode.
- virtual void DidPictureInPictureModeEnd(int delegate_id,
- base::OnceClosure) = 0;
-
// Notify that custom controls have been sent to be assigned to the
// Picture-in-Picture window.
virtual void DidSetPictureInPictureCustomControls(
int delegate_id,
const std::vector<blink::PictureInPictureControlInfo>&) = 0;
- // Notify that the media player in Picture-in-Picture had a change of surface.
- virtual void DidPictureInPictureSurfaceChange(
- int delegate_id,
- const viz::SurfaceId&,
- const gfx::Size&,
- bool show_play_pause_button) = 0;
-
- // Registers a callback associated with a player that will be called when
- // receiving a notification from the browser process that the
- // Picture-in-Picture associated to this player has been resized.
- virtual void RegisterPictureInPictureWindowResizeCallback(
- int player_id,
- blink::WebMediaPlayer::PipWindowResizedCallback) = 0;
-
// Notify that playback is stopped. This will drop wake locks and remove any
// external controls.
//
diff --git a/media/blink/webmediaplayer_impl.cc b/media/blink/webmediaplayer_impl.cc
index 6e9d472..7dcdd0b 100644
--- a/media/blink/webmediaplayer_impl.cc
+++ b/media/blink/webmediaplayer_impl.cc
@@ -517,11 +517,8 @@
// disabled.
// The viz::SurfaceId may be updated when the video begins playback or when
// the size of the video changes.
- if (client_ && IsInPictureInPicture() && !client_->IsInAutoPIP()) {
- delegate_->DidPictureInPictureSurfaceChange(
- delegate_id_, surface_id, pipeline_metadata_.natural_size,
- ShouldShowPlayPauseButtonInPictureInPictureWindow());
- }
+ if (client_)
+ client_->OnPictureInPictureStateChange();
}
bool WebMediaPlayerImpl::SupportsOverlayFullscreenVideo() {
@@ -905,30 +902,17 @@
UpdatePlayState();
}
-void WebMediaPlayerImpl::EnterPictureInPicture(
- blink::WebMediaPlayer::PipWindowOpenedCallback callback) {
+void WebMediaPlayerImpl::EnterPictureInPicture() {
if (!surface_layer_for_video_enabled_)
ActivateSurfaceLayerForVideo();
DCHECK(bridge_);
-
- const viz::SurfaceId& surface_id = bridge_->GetSurfaceId();
- DCHECK(surface_id.is_valid());
-
- // Notifies the browser process that the player should now be in
- // Picture-in-Picture mode.
- delegate_->DidPictureInPictureModeStart(
- delegate_id_, surface_id, pipeline_metadata_.natural_size,
- std::move(callback), ShouldShowPlayPauseButtonInPictureInPictureWindow());
+ DCHECK(bridge_->GetSurfaceId().is_valid());
}
-void WebMediaPlayerImpl::ExitPictureInPicture(
- blink::WebMediaPlayer::PipWindowClosedCallback callback) {
- // Notifies the browser process that Picture-in-Picture has ended. It will
- // clear out the states and close the window.
- delegate_->DidPictureInPictureModeEnd(delegate_id_, std::move(callback));
-
+void WebMediaPlayerImpl::ExitPictureInPicture() {
// Internal cleanups.
+ // TODO(mlamouri): remove the need for this.
OnPictureInPictureModeEnded();
}
@@ -937,14 +921,6 @@
delegate_->DidSetPictureInPictureCustomControls(delegate_id_, controls);
}
-void WebMediaPlayerImpl::RegisterPictureInPictureWindowResizeCallback(
- blink::WebMediaPlayer::PipWindowResizedCallback callback) {
- DCHECK(IsInPictureInPicture() && !client_->IsInAutoPIP());
-
- delegate_->RegisterPictureInPictureWindowResizeCallback(delegate_id_,
- std::move(callback));
-}
-
void WebMediaPlayerImpl::SetSinkId(
const blink::WebString& sink_id,
std::unique_ptr<blink::WebSetSinkIdCallbacks> web_callback) {
@@ -3145,6 +3121,16 @@
return opaque_;
}
+int WebMediaPlayerImpl::GetDelegateId() {
+ return delegate_id_;
+}
+
+base::Optional<viz::SurfaceId> WebMediaPlayerImpl::GetSurfaceId() {
+ if (!surface_layer_for_video_enabled_)
+ return base::nullopt;
+ return bridge_->GetSurfaceId();
+}
+
bool WebMediaPlayerImpl::ShouldPauseVideoWhenHidden() const {
if (!is_background_video_playback_enabled_)
return true;
@@ -3449,11 +3435,6 @@
WebMediaPlayer::DisplayType::kPictureInPicture;
}
-bool WebMediaPlayerImpl::ShouldShowPlayPauseButtonInPictureInPictureWindow()
- const {
- return Duration() != std::numeric_limits<double>::infinity();
-}
-
void WebMediaPlayerImpl::MaybeSetContainerName() {
// MSE nor MediaPlayerRenderer provide container information.
if (chunk_demuxer_ || using_media_player_renderer_)
diff --git a/media/blink/webmediaplayer_impl.h b/media/blink/webmediaplayer_impl.h
index 2030aee..7183daa 100644
--- a/media/blink/webmediaplayer_impl.h
+++ b/media/blink/webmediaplayer_impl.h
@@ -120,14 +120,10 @@
void Seek(double seconds) override;
void SetRate(double rate) override;
void SetVolume(double volume) override;
- void EnterPictureInPicture(
- blink::WebMediaPlayer::PipWindowOpenedCallback callback) override;
- void ExitPictureInPicture(
- blink::WebMediaPlayer::PipWindowClosedCallback callback) override;
+ void EnterPictureInPicture() override;
+ void ExitPictureInPicture() override;
void SetPictureInPictureCustomControls(
const std::vector<blink::PictureInPictureControlInfo>&) override;
- void RegisterPictureInPictureWindowResizeCallback(
- blink::WebMediaPlayer::PipWindowResizedCallback callback) override;
void SetSinkId(
const blink::WebString& sink_id,
std::unique_ptr<blink::WebSetSinkIdCallbacks> web_callback) override;
@@ -264,10 +260,11 @@
// paused; see UpdatePlayState_ComputePlayState() for the exact details.
void ForceStaleStateForTesting(ReadyState target_state) override;
bool IsSuspendedForTesting() override;
-
bool DidLazyLoad() const override;
void OnBecameVisible() override;
bool IsOpaque() const override;
+ int GetDelegateId() override;
+ base::Optional<viz::SurfaceId> GetSurfaceId() override;
bool IsBackgroundMediaSuspendEnabled() const {
return is_background_suspend_enabled_;
@@ -578,11 +575,6 @@
void SendBytesReceivedUpdate();
- // Returns whether the Picture-in-Picture window should contain a play/pause
- // button. It will return false if video is "live", in other words if duration
- // is equals to Infinity.
- bool ShouldShowPlayPauseButtonInPictureInPictureWindow() const;
-
blink::WebLocalFrame* const frame_;
// The playback state last reported to |delegate_|, to avoid setting duplicate
diff --git a/media/blink/webmediaplayer_impl_unittest.cc b/media/blink/webmediaplayer_impl_unittest.cc
index ddc23ba..33f932eb 100644
--- a/media/blink/webmediaplayer_impl_unittest.cc
+++ b/media/blink/webmediaplayer_impl_unittest.cc
@@ -148,9 +148,9 @@
MOCK_METHOD1(ActivateViewportIntersectionMonitoring, void(bool));
MOCK_METHOD1(MediaRemotingStarted, void(const blink::WebString&));
MOCK_METHOD1(MediaRemotingStopped, void(blink::WebLocalizedString::Name));
- MOCK_METHOD0(PictureInPictureStarted, void());
MOCK_METHOD0(PictureInPictureStopped, void());
MOCK_METHOD1(PictureInPictureControlClicked, void(const blink::WebString&));
+ MOCK_METHOD0(OnPictureInPictureStateChange, void());
MOCK_CONST_METHOD0(CouldPlayIfEnoughData, bool());
MOCK_METHOD0(RequestPlay, void());
MOCK_METHOD0(RequestPause, void());
@@ -215,21 +215,9 @@
DCHECK_EQ(player_id_, delegate_id);
}
- MOCK_METHOD5(DidPictureInPictureModeStart,
- void(int,
- const viz::SurfaceId&,
- const gfx::Size&,
- blink::WebMediaPlayer::PipWindowOpenedCallback,
- bool));
- MOCK_METHOD2(DidPictureInPictureModeEnd,
- void(int, blink::WebMediaPlayer::PipWindowClosedCallback));
MOCK_METHOD2(DidSetPictureInPictureCustomControls,
void(int,
const std::vector<blink::PictureInPictureControlInfo>&));
- MOCK_METHOD4(DidPictureInPictureSurfaceChange,
- void(int, const viz::SurfaceId&, const gfx::Size&, bool));
- MOCK_METHOD2(RegisterPictureInPictureWindowResizeCallback,
- void(int, blink::WebMediaPlayer::PipWindowResizedCallback));
void ClearStaleFlag(int player_id) override {
DCHECK_EQ(player_id_, player_id);
@@ -1566,8 +1554,8 @@
}
}
-// Tests delegate methods are called when Picture-in-Picture is triggered.
-TEST_F(WebMediaPlayerImplTest, PictureInPictureTriggerCallback) {
+// Tests that updating the surface id calls OnPictureInPictureStateChange.
+TEST_F(WebMediaPlayerImplTest, PictureInPictureStateChange) {
base::test::ScopedFeatureList feature_list;
feature_list.InitFromCommandLine(kUseSurfaceLayerForVideo.name, "");
@@ -1588,68 +1576,10 @@
EXPECT_CALL(client_, DisplayType())
.WillRepeatedly(
Return(blink::WebMediaPlayer::DisplayType::kPictureInPicture));
- EXPECT_CALL(delegate_,
- DidPictureInPictureSurfaceChange(
- delegate_.player_id(), surface_id_, GetNaturalSize(), true))
- .Times(2);
+ EXPECT_CALL(client_, OnPictureInPictureStateChange()).Times(1);
wmpi_->OnSurfaceIdUpdated(surface_id_);
- EXPECT_CALL(delegate_,
- DidPictureInPictureModeStart(delegate_.player_id(), surface_id_,
- GetNaturalSize(), _, true));
-
- wmpi_->EnterPictureInPicture(base::DoNothing());
- wmpi_->OnSurfaceIdUpdated(surface_id_);
-
- // Updating SurfaceId should NOT exit Picture-in-Picture.
- EXPECT_CALL(delegate_, DidPictureInPictureModeEnd(delegate_.player_id(), _))
- .Times(0);
- EXPECT_CALL(*surface_layer_bridge_ptr_, ClearObserver());
-}
-
-// Tests delegate methods are called with the appropriate play/pause button
-// state when Picture-in-Picture is triggered and video duration is infinity.
-TEST_F(WebMediaPlayerImplTest,
- PictureInPictureTriggerWithInfiniteDurationCallback) {
- base::test::ScopedFeatureList feature_list;
- feature_list.InitFromCommandLine(kUseSurfaceLayerForVideo.name, "");
-
- InitializeWebMediaPlayerImpl();
- SetDuration(kInfiniteDuration);
-
- EXPECT_CALL(*surface_layer_bridge_ptr_, CreateSurfaceLayer());
- EXPECT_CALL(*surface_layer_bridge_ptr_, GetSurfaceId())
- .WillRepeatedly(ReturnRef(surface_id_));
- EXPECT_CALL(*surface_layer_bridge_ptr_, GetLocalSurfaceIdAllocationTime())
- .WillRepeatedly(Return(base::TimeTicks()));
- EXPECT_CALL(*compositor_, EnableSubmission(_, _, _, _));
- EXPECT_CALL(*surface_layer_bridge_ptr_, SetContentsOpaque(false));
-
- PipelineMetadata metadata;
- metadata.has_video = true;
- OnMetadata(metadata);
-
- EXPECT_CALL(client_, DisplayType())
- .WillRepeatedly(
- Return(blink::WebMediaPlayer::DisplayType::kPictureInPicture));
- EXPECT_CALL(delegate_,
- DidPictureInPictureSurfaceChange(
- delegate_.player_id(), surface_id_, GetNaturalSize(), false))
- .Times(2);
-
- wmpi_->OnSurfaceIdUpdated(surface_id_);
-
- EXPECT_CALL(delegate_,
- DidPictureInPictureModeStart(delegate_.player_id(), surface_id_,
- GetNaturalSize(), _, false));
-
- wmpi_->EnterPictureInPicture(base::DoNothing());
- wmpi_->OnSurfaceIdUpdated(surface_id_);
-
- // Updating SurfaceId should NOT exit Picture-in-Picture.
- EXPECT_CALL(delegate_, DidPictureInPictureModeEnd(delegate_.player_id(), _))
- .Times(0);
EXPECT_CALL(*surface_layer_bridge_ptr_, ClearObserver());
}
diff --git a/third_party/blink/public/mojom/BUILD.gn b/third_party/blink/public/mojom/BUILD.gn
index d588cf8..52434a2 100644
--- a/third_party/blink/public/mojom/BUILD.gn
+++ b/third_party/blink/public/mojom/BUILD.gn
@@ -59,6 +59,7 @@
"notifications/notification.mojom",
"page/display_cutout.mojom",
"payments/payment_app.mojom",
+ "picture_in_picture/picture_in_picture.mojom",
"plugins/plugin_registry.mojom",
"presentation/presentation.mojom",
"quota/quota_dispatcher_host.mojom",
@@ -110,6 +111,7 @@
"//services/device/public/mojom",
"//services/network/public/mojom",
"//services/service_manager/public/mojom",
+ "//services/viz/public/interfaces",
"//skia/public/interfaces",
"//third_party/blink/public:web_feature_mojo_bindings",
"//third_party/blink/public/mojom/usb",
diff --git a/third_party/blink/public/mojom/picture_in_picture/OWNERS b/third_party/blink/public/mojom/picture_in_picture/OWNERS
new file mode 100644
index 0000000..08850f4
--- /dev/null
+++ b/third_party/blink/public/mojom/picture_in_picture/OWNERS
@@ -0,0 +1,2 @@
+per-file *.mojom=set noparent
+per-file *.mojom=file://ipc/SECURITY_OWNERS
diff --git a/third_party/blink/public/mojom/picture_in_picture/picture_in_picture.mojom b/third_party/blink/public/mojom/picture_in_picture/picture_in_picture.mojom
new file mode 100644
index 0000000..9146179e
--- /dev/null
+++ b/third_party/blink/public/mojom/picture_in_picture/picture_in_picture.mojom
@@ -0,0 +1,58 @@
+// Copyright 2019 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.
+
+module blink.mojom;
+
+import "services/viz/public/interfaces/compositing/surface_id.mojom";
+import "ui/gfx/geometry/mojo/geometry.mojom";
+
+// PictureInPictureDelegate is associated to a PictureInPictureService. It will
+// be notified by running its callbacks when a change happened to the running
+// Picture-in-Picture session.
+interface PictureInPictureDelegate {
+ PictureInPictureWindowSizeChanged(gfx.mojom.Size size);
+};
+
+// PictureInPictureService is a Document-associated interface that lives in
+// the browser process. It is responsible for implementing the browser-side
+// support for https://wicg.github.io/picture-in-picture and manages all
+// picture-in-picture interactions for Document. Picture-in-picture allows
+// a Document to display a video in an always-on-top floating window with
+// basic playback functionality (e.g. play and pause).
+interface PictureInPictureService {
+ // Notify the service that it should start a Picture-in-Picture session.
+ // player_id: WebMediaPlayer id used to control playback via the
+ // Picture-in-Picture window.
+ // surface_id: SurfaceId to be shown in the Picture-in-Picture window.
+ // natural_size: Size of the video frames.
+ // show_play_pause_button: Whether a play/pause control should be offered in
+ // the Picture-in-Picture window.
+ // NOTE: this method shouldn't be called twice in a raw. An interleaved call
+ // to EndSession() would be expected. In case of some parameters have changed,
+ // UpdateSession() should be called.
+ StartSession(
+ uint32 player_id,
+ viz.mojom.SurfaceId? surface_id,
+ gfx.mojom.Size natural_size,
+ bool show_play_pause_button) => (gfx.mojom.Size size);
+
+ // Notify the service to end the Picture-in-Picture session. It will close
+ // the Picture-in-Picture window.
+ EndSession() => ();
+
+ // Notify the service that some information about the client have change. All
+ // information associated with the Picture-in-Picture session are sent again.
+ // A Picture-in-Picture session must already be active from a previous call to
+ // StartSession().
+ UpdateSession(
+ uint32 player_id,
+ viz.mojom.SurfaceId? surface_id,
+ gfx.mojom.Size natural_size,
+ bool show_play_pause_button);
+
+ // Associate a PictureInPictureDelegate with the service. All changes in
+ // Picture-in-Picture states will be sent to the delegate. Only one delegate
+ // can be set at a time.
+ SetDelegate(PictureInPictureDelegate delegate);
+};
diff --git a/third_party/blink/public/platform/web_media_player.h b/third_party/blink/public/platform/web_media_player.h
index edfea4c..55115e8 100644
--- a/third_party/blink/public/platform/web_media_player.h
+++ b/third_party/blink/public/platform/web_media_player.h
@@ -31,7 +31,9 @@
#ifndef THIRD_PARTY_BLINK_PUBLIC_PLATFORM_WEB_MEDIA_PLAYER_H_
#define THIRD_PARTY_BLINK_PUBLIC_PLATFORM_WEB_MEDIA_PLAYER_H_
+#include "base/optional.h"
#include "base/time/time.h"
+#include "components/viz/common/surfaces/surface_id.h"
#include "third_party/blink/public/platform/web_callbacks.h"
#include "third_party/blink/public/platform/web_content_decryption_module.h"
#include "third_party/blink/public/platform/web_media_source.h"
@@ -137,14 +139,6 @@
kAlways,
};
- // Callback to get notified when the Picture-in-Picture window is opened.
- using PipWindowOpenedCallback = base::OnceCallback<void(const WebSize&)>;
- // Callback to get notified when Picture-in-Picture window is closed.
- using PipWindowClosedCallback = base::OnceClosure;
- // Callback to get notified when the Picture-in-Picture window is resized.
- using PipWindowResizedCallback =
- base::RepeatingCallback<void(const WebSize&)>;
-
virtual ~WebMediaPlayer() = default;
virtual LoadTiming Load(LoadType, const WebMediaPlayerSource&, CorsMode) = 0;
@@ -158,16 +152,13 @@
// Enter Picture-in-Picture and notifies Blink with window size
// when video successfully enters Picture-in-Picture.
- virtual void EnterPictureInPicture(PipWindowOpenedCallback) = 0;
+ // TODO(mlamouri): rename to "OnRequestPictureInPicture".
+ virtual void EnterPictureInPicture() = 0;
// Exit Picture-in-Picture and notifies Blink when it's done.
- virtual void ExitPictureInPicture(PipWindowClosedCallback) = 0;
+ virtual void ExitPictureInPicture() = 0;
// Assign custom controls to the Picture-in-Picture window.
virtual void SetPictureInPictureCustomControls(
const std::vector<PictureInPictureControlInfo>&) = 0;
- // Register a callback that will be run when the Picture-in-Picture window
- // is resized.
- virtual void RegisterPictureInPictureWindowResizeCallback(
- PipWindowResizedCallback) = 0;
virtual void RequestRemotePlayback() {}
virtual void RequestRemotePlaybackControl() {}
@@ -411,6 +402,18 @@
virtual void OnBecameVisible() {}
virtual bool IsOpaque() const { return false; }
+
+ // Returns the id given by the WebMediaPlayerDelegate. This is used by the
+ // Blink code to pass a player id to mojo services.
+ // TODO(mlamouri): remove this and move the id handling to Blink.
+ virtual int GetDelegateId() { return -1; };
+
+ // Returns the SurfaceId the video element is currently using.
+ // Returns base::nullopt if the element isn't a video or doesn't have a
+ // SurfaceId associated to it.
+ virtual base::Optional<viz::SurfaceId> GetSurfaceId() {
+ return base::nullopt;
+ }
};
} // namespace blink
diff --git a/third_party/blink/public/platform/web_media_player_client.h b/third_party/blink/public/platform/web_media_player_client.h
index 49594467..e4cab565 100644
--- a/third_party/blink/public/platform/web_media_player_client.h
+++ b/third_party/blink/public/platform/web_media_player_client.h
@@ -179,6 +179,15 @@
// Request the player to pause playback.
virtual void RequestPause() = 0;
+ // Notify the client that one of the state used by Picture-in-Picture has
+ // changed. The client will then have to poll the states from the associated
+ // WebMediaPlayer.
+ // The states are:
+ // - Delegate ID;
+ // - Surface ID;
+ // - Natural Size.
+ virtual void OnPictureInPictureStateChange() = 0;
+
protected:
~WebMediaPlayerClient() = default;
};
diff --git a/third_party/blink/renderer/core/frame/picture_in_picture_controller.h b/third_party/blink/renderer/core/frame/picture_in_picture_controller.h
index 98b35b8..87d82d73 100644
--- a/third_party/blink/renderer/core/frame/picture_in_picture_controller.h
+++ b/third_party/blink/renderer/core/frame/picture_in_picture_controller.h
@@ -83,6 +83,8 @@
HTMLVideoElement*,
const std::vector<PictureInPictureControlInfo>&) = 0;
+ // Notifies that one of the states used by Picture-in-Picture has changed.
+ virtual void OnPictureInPictureStateChange() = 0;
void Trace(blink::Visitor*) override;
diff --git a/third_party/blink/renderer/core/html/media/html_audio_element.h b/third_party/blink/renderer/core/html/media/html_audio_element.h
index 2463517..b6076b7 100644
--- a/third_party/blink/renderer/core/html/media/html_audio_element.h
+++ b/third_party/blink/renderer/core/html/media/html_audio_element.h
@@ -55,6 +55,7 @@
void PictureInPictureControlClicked(const WebString& control_id) override {
NOTREACHED();
}
+ void OnPictureInPictureStateChange() final { NOTREACHED(); }
};
} // namespace blink
diff --git a/third_party/blink/renderer/core/html/media/html_media_element.h b/third_party/blink/renderer/core/html/media/html_media_element.h
index 674f448..0d0e45c 100644
--- a/third_party/blink/renderer/core/html/media/html_media_element.h
+++ b/third_party/blink/renderer/core/html/media/html_media_element.h
@@ -333,6 +333,8 @@
// becomes visible again.
bool PausedWhenVisible() const;
+ void SetCcLayerForTesting(cc::Layer* layer) { SetCcLayer(layer); }
+
protected:
HTMLMediaElement(const QualifiedName&, Document&);
~HTMLMediaElement() override;
diff --git a/third_party/blink/renderer/core/html/media/html_video_element.cc b/third_party/blink/renderer/core/html/media/html_video_element.cc
index dad6245..43565f1 100644
--- a/third_party/blink/renderer/core/html/media/html_video_element.cc
+++ b/third_party/blink/renderer/core/html/media/html_video_element.cc
@@ -502,8 +502,10 @@
void HTMLVideoElement::DidEnterFullscreen() {
UpdateControlsVisibility();
- if (DisplayType() == WebMediaPlayer::DisplayType::kPictureInPicture)
- exitPictureInPicture(base::DoNothing());
+ if (DisplayType() == WebMediaPlayer::DisplayType::kPictureInPicture) {
+ PictureInPictureController::From(GetDocument())
+ .ExitPictureInPicture(this, nullptr);
+ }
if (GetWebMediaPlayer()) {
// FIXME: There is no embedder-side handling in web test mode.
@@ -661,19 +663,12 @@
PictureInPictureController::Status::kEnabled;
}
-void HTMLVideoElement::enterPictureInPicture(
- WebMediaPlayer::PipWindowOpenedCallback callback) {
+void HTMLVideoElement::enterPictureInPicture() {
if (DisplayType() == WebMediaPlayer::DisplayType::kFullscreen)
Fullscreen::ExitFullscreen(GetDocument());
if (GetWebMediaPlayer())
- GetWebMediaPlayer()->EnterPictureInPicture(std::move(callback));
-}
-
-void HTMLVideoElement::exitPictureInPicture(
- WebMediaPlayer::PipWindowClosedCallback callback) {
- if (GetWebMediaPlayer())
- GetWebMediaPlayer()->ExitPictureInPicture(std::move(callback));
+ GetWebMediaPlayer()->EnterPictureInPicture();
}
void HTMLVideoElement::SendCustomControlsToPipWindow() {
@@ -712,6 +707,16 @@
return is_auto_picture_in_picture_;
}
+void HTMLVideoElement::OnPictureInPictureStateChange() {
+ if (DisplayType() != WebMediaPlayer::DisplayType::kPictureInPicture ||
+ IsInAutoPIP()) {
+ return;
+ }
+
+ PictureInPictureController::From(GetDocument())
+ .OnPictureInPictureStateChange();
+}
+
void HTMLVideoElement::OnEnteredPictureInPicture() {
if (!picture_in_picture_interstitial_) {
picture_in_picture_interstitial_ =
diff --git a/third_party/blink/renderer/core/html/media/html_video_element.h b/third_party/blink/renderer/core/html/media/html_video_element.h
index 61759f9b..b8313c26 100644
--- a/third_party/blink/renderer/core/html/media/html_video_element.h
+++ b/third_party/blink/renderer/core/html/media/html_video_element.h
@@ -175,14 +175,14 @@
void MediaRemotingStarted(const WebString& remote_device_friendly_name) final;
bool SupportsPictureInPicture() const final;
- void enterPictureInPicture(WebMediaPlayer::PipWindowOpenedCallback callback);
- void exitPictureInPicture(WebMediaPlayer::PipWindowClosedCallback callback);
+ void enterPictureInPicture();
void SendCustomControlsToPipWindow();
void PictureInPictureStopped() final;
void PictureInPictureControlClicked(const WebString& control_id) final;
void MediaRemotingStopped(WebLocalizedString::Name error_msg) final;
WebMediaPlayer::DisplayType DisplayType() const final;
bool IsInAutoPIP() const final;
+ void OnPictureInPictureStateChange() final;
// Used by the PictureInPictureController as callback when the video element
// enters or exits Picture-in-Picture state.
diff --git a/third_party/blink/renderer/core/html/media/video_wake_lock_test.cc b/third_party/blink/renderer/core/html/media/video_wake_lock_test.cc
index 8fd93c8e..bc01a5ee 100644
--- a/third_party/blink/renderer/core/html/media/video_wake_lock_test.cc
+++ b/third_party/blink/renderer/core/html/media/video_wake_lock_test.cc
@@ -5,6 +5,7 @@
#include "third_party/blink/renderer/core/html/media/video_wake_lock.h"
#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/public/mojom/picture_in_picture/picture_in_picture.mojom-blink.h"
#include "third_party/blink/renderer/core/dom/events/event.h"
#include "third_party/blink/renderer/core/frame/picture_in_picture_controller.h"
#include "third_party/blink/renderer/core/html/media/html_media_test_helper.h"
@@ -15,23 +16,107 @@
namespace blink {
-class VideoWakeLockMediaPlayer : public EmptyWebMediaPlayer {
+// The VideoWakeLockPictureInPictureService implements the PictureInPicture
+// service in the same process as the test and guarantees that the callbacks are
+// called in order for the events to be fired. set_run_loop() MUST be called
+// before each attempt to enter/leave Picture-in-Picture.
+class VideoWakeLockPictureInPictureService
+ : public mojom::blink::PictureInPictureService {
public:
- void EnterPictureInPicture(PipWindowOpenedCallback callback) final {
+ VideoWakeLockPictureInPictureService() : binding_(this) {}
+ ~VideoWakeLockPictureInPictureService() override = default;
+
+ void Bind(mojo::ScopedMessagePipeHandle handle) {
+ binding_.Bind(
+ mojom::blink::PictureInPictureServiceRequest(std::move(handle)));
+ }
+
+ void StartSession(uint32_t,
+ const base::Optional<viz::SurfaceId>&,
+ const blink::WebSize&,
+ bool,
+ StartSessionCallback callback) final {
std::move(callback).Run(WebSize());
}
- void ExitPictureInPicture(PipWindowClosedCallback callback) final {
+ void EndSession(EndSessionCallback callback) final {
std::move(callback).Run();
}
+
+ void UpdateSession(uint32_t,
+ const base::Optional<viz::SurfaceId>&,
+ const blink::WebSize&,
+ bool) final {}
+ void SetDelegate(mojom::blink::PictureInPictureDelegatePtr) final {}
+
+ private:
+ mojo::Binding<mojom::blink::PictureInPictureService> binding_;
+};
+
+class VideoWakeLockFrameClient : public test::MediaStubLocalFrameClient {
+ public:
+ static VideoWakeLockFrameClient* Create(
+ std::unique_ptr<WebMediaPlayer> player) {
+ return MakeGarbageCollected<VideoWakeLockFrameClient>(std::move(player));
+ }
+
+ explicit VideoWakeLockFrameClient(std::unique_ptr<WebMediaPlayer> player)
+ : test::MediaStubLocalFrameClient(std::move(player)),
+ interface_provider_(new service_manager::InterfaceProvider()) {}
+
+ service_manager::InterfaceProvider* GetInterfaceProvider() override {
+ return interface_provider_.get();
+ }
+
+ private:
+ std::unique_ptr<service_manager::InterfaceProvider> interface_provider_;
+
+ DISALLOW_COPY_AND_ASSIGN(VideoWakeLockFrameClient);
};
class VideoWakeLockTest : public PageTestBase {
public:
+ // Helper class that will block running the test until the given event is
+ // fired on the given element.
+ class WaitForEvent : public NativeEventListener {
+ public:
+ static WaitForEvent* Create(Element* element, const AtomicString& name) {
+ return MakeGarbageCollected<WaitForEvent>(element, name);
+ }
+
+ WaitForEvent(Element* element, const AtomicString& name)
+ : element_(element), name_(name) {
+ element_->addEventListener(name_, this);
+ run_loop_.Run();
+ }
+
+ void Invoke(ExecutionContext*, Event*) final {
+ run_loop_.Quit();
+ element_->removeEventListener(name_, this);
+ }
+
+ void Trace(Visitor* visitor) final {
+ NativeEventListener::Trace(visitor);
+ visitor->Trace(element_);
+ }
+
+ private:
+ base::RunLoop run_loop_;
+ Member<Element> element_;
+ AtomicString name_;
+ };
+
void SetUp() override {
PageTestBase::SetupPageWithClients(
- nullptr, test::MediaStubLocalFrameClient::Create(
- std::make_unique<VideoWakeLockMediaPlayer>()));
+ nullptr, VideoWakeLockFrameClient::Create(
+ std::make_unique<EmptyWebMediaPlayer>()));
+
+ service_manager::InterfaceProvider::TestApi test_api(
+ GetFrame().Client()->GetInterfaceProvider());
+ test_api.SetBinderForName(
+ mojom::blink::PictureInPictureService::Name_,
+ WTF::BindRepeating(&VideoWakeLockPictureInPictureService::Bind,
+ WTF::Unretained(&pip_service_)));
video_ = HTMLVideoElement::Create(GetDocument());
video_wake_lock_ = MakeGarbageCollected<VideoWakeLock>(*video_.Get());
@@ -57,16 +142,24 @@
void SimulateEnterPictureInPicture() {
PictureInPictureController::From(GetDocument())
.EnterPictureInPicture(Video(), nullptr);
+
+ WaitForEvent::Create(video_.Get(),
+ event_type_names::kEnterpictureinpicture);
}
void SimulateLeavePictureInPicture() {
PictureInPictureController::From(GetDocument())
.ExitPictureInPicture(Video(), nullptr);
+
+ WaitForEvent::Create(video_.Get(),
+ event_type_names::kLeavepictureinpicture);
}
private:
Persistent<HTMLVideoElement> video_;
Persistent<VideoWakeLock> video_wake_lock_;
+
+ VideoWakeLockPictureInPictureService pip_service_;
};
TEST_F(VideoWakeLockTest, NoLockByDefault) {
diff --git a/third_party/blink/renderer/modules/BUILD.gn b/third_party/blink/renderer/modules/BUILD.gn
index db4b2a3..da0819b 100644
--- a/third_party/blink/renderer/modules/BUILD.gn
+++ b/third_party/blink/renderer/modules/BUILD.gn
@@ -321,6 +321,7 @@
"peerconnection/rtc_quic_transport_test.cc",
"peerconnection/rtc_quic_transport_test.h",
"picture_in_picture/html_video_element_picture_in_picture_test.cc",
+ "picture_in_picture/picture_in_picture_controller_test.cc",
"presentation/mock_presentation_service.h",
"presentation/presentation_availability_state_test.cc",
"presentation/presentation_availability_test.cc",
diff --git a/third_party/blink/renderer/modules/picture_in_picture/DEPS b/third_party/blink/renderer/modules/picture_in_picture/DEPS
index 03fe8e3..c3389f6 100644
--- a/third_party/blink/renderer/modules/picture_in_picture/DEPS
+++ b/third_party/blink/renderer/modules/picture_in_picture/DEPS
@@ -2,5 +2,6 @@
"-third_party/blink/renderer/modules",
"+third_party/blink/renderer/modules/event_modules.h",
"+third_party/blink/renderer/modules/event_target_modules.h",
+ "+third_party/blink/renderer/modules/modules_export.h",
"+third_party/blink/renderer/modules/picture_in_picture",
]
\ No newline at end of file
diff --git a/third_party/blink/renderer/modules/picture_in_picture/picture_in_picture_controller_impl.cc b/third_party/blink/renderer/modules/picture_in_picture/picture_in_picture_controller_impl.cc
index 3039b1c..3b96446 100644
--- a/third_party/blink/renderer/modules/picture_in_picture/picture_in_picture_controller_impl.cc
+++ b/third_party/blink/renderer/modules/picture_in_picture/picture_in_picture_controller_impl.cc
@@ -4,7 +4,11 @@
#include "third_party/blink/renderer/modules/picture_in_picture/picture_in_picture_controller_impl.h"
+#include <limits>
+#include <utility>
+
#include "base/bind_helpers.h"
+#include "services/service_manager/public/cpp/interface_provider.h"
#include "third_party/blink/public/common/manifest/web_display_mode.h"
#include "third_party/blink/public/mojom/feature_policy/feature_policy.mojom-blink.h"
#include "third_party/blink/renderer/core/dom/document.h"
@@ -20,7 +24,14 @@
namespace blink {
-PictureInPictureControllerImpl::~PictureInPictureControllerImpl() = default;
+namespace {
+
+bool ShouldShowPlayPauseButton(const HTMLVideoElement& element) {
+ return element.GetLoadType() != WebMediaPlayer::kLoadTypeMediaStream &&
+ element.duration() != std::numeric_limits<double>::infinity();
+}
+
+} // namespace
// static
PictureInPictureControllerImpl* PictureInPictureControllerImpl::Create(
@@ -88,19 +99,32 @@
void PictureInPictureControllerImpl::EnterPictureInPicture(
HTMLVideoElement* element,
ScriptPromiseResolver* resolver) {
- if (picture_in_picture_element_ != element) {
- element->enterPictureInPicture(
- WTF::Bind(&PictureInPictureControllerImpl::OnEnteredPictureInPicture,
- WrapPersistent(this), WrapPersistent(element),
- WrapPersistent(resolver)));
- // If the media element has already been given custom controls, this will
- // ensure that they get set. Otherwise, this will do nothing.
- element->SendCustomControlsToPipWindow();
+ if (picture_in_picture_element_ == element) {
+ if (resolver)
+ resolver->Resolve(picture_in_picture_window_);
+
return;
}
- if (resolver)
- resolver->Resolve(picture_in_picture_window_);
+ element->enterPictureInPicture();
+
+ DCHECK(element->GetWebMediaPlayer());
+
+ if (!EnsureService())
+ return;
+
+ picture_in_picture_service_->StartSession(
+ element->GetWebMediaPlayer()->GetDelegateId(),
+ element->GetWebMediaPlayer()->GetSurfaceId(),
+ element->GetWebMediaPlayer()->NaturalSize(),
+ ShouldShowPlayPauseButton(*element),
+ WTF::Bind(&PictureInPictureControllerImpl::OnEnteredPictureInPicture,
+ WrapPersistent(this), WrapPersistent(element),
+ WrapPersistent(resolver)));
+
+ // If the media element has already been given custom controls, this will
+ // ensure that they get set. Otherwise, this will do nothing.
+ element->SendCustomControlsToPipWindow();
}
void PictureInPictureControllerImpl::OnEnteredPictureInPicture(
@@ -112,7 +136,7 @@
resolver->Reject(
DOMException::Create(DOMExceptionCode::kInvalidStateError, ""));
}
- element->exitPictureInPicture(base::DoNothing());
+ ExitPictureInPicture(element, nullptr);
return;
}
@@ -132,11 +156,15 @@
event_type_names::kEnterpictureinpicture,
WrapPersistent(picture_in_picture_window_.Get())));
- if (element->GetWebMediaPlayer()) {
- element->GetWebMediaPlayer()->RegisterPictureInPictureWindowResizeCallback(
- WTF::BindRepeating(&PictureInPictureWindow::OnResize,
- WrapPersistent(picture_in_picture_window_.Get())));
- }
+ if (!EnsureService())
+ return;
+
+ if (delegate_binding_.is_bound())
+ delegate_binding_.Close();
+
+ mojom::blink::PictureInPictureDelegatePtr delegate;
+ delegate_binding_.Bind(mojo::MakeRequest(&delegate));
+ picture_in_picture_service_->SetDelegate(std::move(delegate));
if (resolver)
resolver->Resolve(picture_in_picture_window_);
@@ -145,9 +173,16 @@
void PictureInPictureControllerImpl::ExitPictureInPicture(
HTMLVideoElement* element,
ScriptPromiseResolver* resolver) {
- element->exitPictureInPicture(
+ if (element->GetWebMediaPlayer())
+ element->GetWebMediaPlayer()->ExitPictureInPicture();
+
+ if (!EnsureService())
+ return;
+
+ picture_in_picture_service_->EndSession(
WTF::Bind(&PictureInPictureControllerImpl::OnExitedPictureInPicture,
WrapPersistent(this), WrapPersistent(resolver)));
+ delegate_binding_.Close();
}
void PictureInPictureControllerImpl::SetPictureInPictureCustomControls(
@@ -243,7 +278,6 @@
// Auto Picture-in-Picture is allowed only in a PWA window.
if (!GetSupplementable()->GetFrame() ||
- !GetSupplementable()->GetFrame()->View() ||
GetSupplementable()->GetFrame()->View()->DisplayMode() ==
WebDisplayMode::kWebDisplayModeBrowser) {
return;
@@ -274,17 +308,53 @@
}
}
+void PictureInPictureControllerImpl::ContextDestroyed(Document*) {
+ picture_in_picture_service_.reset();
+ delegate_binding_.Close();
+}
+
+void PictureInPictureControllerImpl::OnPictureInPictureStateChange() {
+ DCHECK(picture_in_picture_element_);
+ DCHECK(picture_in_picture_element_->GetWebMediaPlayer());
+
+ picture_in_picture_service_->UpdateSession(
+ picture_in_picture_element_->GetWebMediaPlayer()->GetDelegateId(),
+ picture_in_picture_element_->GetWebMediaPlayer()->GetSurfaceId(),
+ picture_in_picture_element_->GetWebMediaPlayer()->NaturalSize(),
+ ShouldShowPlayPauseButton(*picture_in_picture_element_));
+}
+
+void PictureInPictureControllerImpl::PictureInPictureWindowSizeChanged(
+ const blink::WebSize& size) {
+ if (picture_in_picture_window_)
+ picture_in_picture_window_->OnResize(size);
+}
+
void PictureInPictureControllerImpl::Trace(blink::Visitor* visitor) {
visitor->Trace(picture_in_picture_element_);
visitor->Trace(auto_picture_in_picture_elements_);
visitor->Trace(picture_in_picture_window_);
PictureInPictureController::Trace(visitor);
PageVisibilityObserver::Trace(visitor);
+ DocumentShutdownObserver::Trace(visitor);
}
PictureInPictureControllerImpl::PictureInPictureControllerImpl(
Document& document)
: PictureInPictureController(document),
- PageVisibilityObserver(document.GetPage()) {}
+ PageVisibilityObserver(document.GetPage()),
+ delegate_binding_(this) {}
+
+bool PictureInPictureControllerImpl::EnsureService() {
+ if (picture_in_picture_service_)
+ return true;
+
+ if (!GetSupplementable()->GetFrame())
+ return false;
+
+ GetSupplementable()->GetFrame()->GetInterfaceProvider().GetInterface(
+ mojo::MakeRequest(&picture_in_picture_service_));
+ return true;
+}
} // namespace blink
diff --git a/third_party/blink/renderer/modules/picture_in_picture/picture_in_picture_controller_impl.h b/third_party/blink/renderer/modules/picture_in_picture/picture_in_picture_controller_impl.h
index af8a3f9..f0e16f1f 100644
--- a/third_party/blink/renderer/modules/picture_in_picture/picture_in_picture_controller_impl.h
+++ b/third_party/blink/renderer/modules/picture_in_picture/picture_in_picture_controller_impl.h
@@ -5,8 +5,11 @@
#ifndef THIRD_PARTY_BLINK_RENDERER_MODULES_PICTURE_IN_PICTURE_PICTURE_IN_PICTURE_CONTROLLER_IMPL_H_
#define THIRD_PARTY_BLINK_RENDERER_MODULES_PICTURE_IN_PICTURE_PICTURE_IN_PICTURE_CONTROLLER_IMPL_H_
+#include "third_party/blink/public/mojom/picture_in_picture/picture_in_picture.mojom-blink.h"
+#include "third_party/blink/renderer/core/dom/document_shutdown_observer.h"
#include "third_party/blink/renderer/core/frame/picture_in_picture_controller.h"
#include "third_party/blink/renderer/core/page/page_visibility_observer.h"
+#include "third_party/blink/renderer/modules/modules_export.h"
namespace blink {
@@ -23,14 +26,17 @@
// PictureInPictureControllerImpl instance is associated to a Document. It is
// supplement and therefore can be lazy-initiated. Callers should consider
// whether they want to instantiate an object when they make a call.
-class PictureInPictureControllerImpl : public PictureInPictureController,
- public PageVisibilityObserver {
+class MODULES_EXPORT PictureInPictureControllerImpl
+ : public PictureInPictureController,
+ public PageVisibilityObserver,
+ public DocumentShutdownObserver,
+ public blink::mojom::blink::PictureInPictureDelegate {
USING_GARBAGE_COLLECTED_MIXIN(PictureInPictureControllerImpl);
WTF_MAKE_NONCOPYABLE(PictureInPictureControllerImpl);
public:
explicit PictureInPictureControllerImpl(Document&);
- ~PictureInPictureControllerImpl() override;
+ ~PictureInPictureControllerImpl() override = default;
// Meant to be called internally by PictureInPictureController::From()
// through ModulesInitializer.
@@ -67,12 +73,24 @@
const std::vector<PictureInPictureControlInfo>&) override;
Status IsElementAllowed(const HTMLVideoElement&) const override;
bool IsPictureInPictureElement(const Element*) const override;
+ void OnPictureInPictureStateChange() override;
- // PageVisibilityObserver implementation.
+ // Implementation of PictureInPictureDelegate.
+ void PictureInPictureWindowSizeChanged(const blink::WebSize&) override;
+
+ // Implementation of PageVisibilityObserver.
void PageVisibilityChanged() override;
+ // Implementation of DocumentShutdownObserver.
+ void ContextDestroyed(Document*) override;
+
void Trace(blink::Visitor*) override;
+ mojo::Binding<mojom::blink::PictureInPictureDelegate>&
+ GetDelegateBindingForTesting() {
+ return delegate_binding_;
+ }
+
private:
void OnEnteredPictureInPicture(HTMLVideoElement*,
ScriptPromiseResolver*,
@@ -80,6 +98,10 @@
void OnExitedPictureInPicture(ScriptPromiseResolver*) override;
void OnPictureInPictureControlClicked(const WebString& control_id) override;
+ // Makes sure the `picture_in_picture_service_` is set. Returns whether it was
+ // initialized successfully.
+ bool EnsureService();
+
// The Picture-in-Picture element for the associated document.
Member<HTMLVideoElement> picture_in_picture_element_;
@@ -89,6 +111,12 @@
// The Picture-in-Picture window for the associated document.
Member<PictureInPictureWindow> picture_in_picture_window_;
+
+ // Mojo bindings for the delegate interface implemented by |this|.
+ mojo::Binding<mojom::blink::PictureInPictureDelegate> delegate_binding_;
+
+ // Picture-in-Picture service living in the browser process.
+ mojom::blink::PictureInPictureServicePtr picture_in_picture_service_;
};
} // namespace blink
diff --git a/third_party/blink/renderer/modules/picture_in_picture/picture_in_picture_controller_test.cc b/third_party/blink/renderer/modules/picture_in_picture/picture_in_picture_controller_test.cc
new file mode 100644
index 0000000..05239c5
--- /dev/null
+++ b/third_party/blink/renderer/modules/picture_in_picture/picture_in_picture_controller_test.cc
@@ -0,0 +1,321 @@
+// Copyright 2019 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/modules/picture_in_picture/picture_in_picture_controller_impl.h"
+
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/public/mojom/picture_in_picture/picture_in_picture.mojom-blink.h"
+#include "third_party/blink/public/platform/web_media_stream.h"
+#include "third_party/blink/public/platform/web_media_stream_track.h"
+#include "third_party/blink/renderer/core/dom/events/event.h"
+#include "third_party/blink/renderer/core/dom/events/native_event_listener.h"
+#include "third_party/blink/renderer/core/html/media/html_media_test_helper.h"
+#include "third_party/blink/renderer/core/html/media/html_video_element.h"
+#include "third_party/blink/renderer/core/testing/page_test_base.h"
+#include "third_party/blink/renderer/platform/testing/empty_web_media_player.h"
+#include "third_party/blink/renderer/platform/testing/unit_test_helpers.h"
+
+using ::testing::_;
+
+namespace blink {
+
+// The MockPictureInPictureService implements the PictureInPicture service in
+// the same process as the test and guarantees that the callbacks are called in
+// order for the events to be fired.
+class MockPictureInPictureService
+ : public mojom::blink::PictureInPictureService {
+ public:
+ MockPictureInPictureService() : binding_(this) {
+ // Setup default implementations.
+ ON_CALL(*this, StartSession(_, _, _, _, _))
+ .WillByDefault([](uint32_t, const base::Optional<viz::SurfaceId>&,
+ const blink::WebSize&, bool,
+ StartSessionCallback callback) {
+ std::move(callback).Run(WebSize());
+ });
+ ON_CALL(*this, EndSession(_))
+ .WillByDefault(
+ [](EndSessionCallback callback) { std::move(callback).Run(); });
+ }
+ ~MockPictureInPictureService() override = default;
+
+ void Bind(mojo::ScopedMessagePipeHandle handle) {
+ binding_.Bind(
+ mojom::blink::PictureInPictureServiceRequest(std::move(handle)));
+ }
+
+ MOCK_METHOD5(StartSession,
+ void(uint32_t,
+ const base::Optional<viz::SurfaceId>&,
+ const blink::WebSize&,
+ bool,
+ StartSessionCallback));
+ MOCK_METHOD1(EndSession, void(EndSessionCallback));
+ MOCK_METHOD4(UpdateSession,
+ void(uint32_t,
+ const base::Optional<viz::SurfaceId>&,
+ const blink::WebSize&,
+ bool));
+ MOCK_METHOD1(SetDelegate, void(mojom::blink::PictureInPictureDelegatePtr));
+
+ private:
+ mojo::Binding<mojom::blink::PictureInPictureService> binding_;
+
+ DISALLOW_COPY_AND_ASSIGN(MockPictureInPictureService);
+};
+
+// Helper class that will block running the test until the given event is fired
+// on the given element.
+class WaitForEvent : public NativeEventListener {
+ public:
+ static WaitForEvent* Create(Element* element, const AtomicString& name) {
+ return MakeGarbageCollected<WaitForEvent>(element, name);
+ }
+
+ WaitForEvent(Element* element, const AtomicString& name)
+ : element_(element), name_(name) {
+ element_->addEventListener(name_, this);
+ run_loop_.Run();
+ }
+
+ void Invoke(ExecutionContext*, Event*) final {
+ run_loop_.Quit();
+ element_->removeEventListener(name_, this);
+ }
+
+ void Trace(Visitor* visitor) final {
+ NativeEventListener::Trace(visitor);
+ visitor->Trace(element_);
+ }
+
+ private:
+ base::RunLoop run_loop_;
+ Member<Element> element_;
+ AtomicString name_;
+};
+
+class PictureInPictureControllerFrameClient
+ : public test::MediaStubLocalFrameClient {
+ public:
+ static PictureInPictureControllerFrameClient* Create(
+ std::unique_ptr<WebMediaPlayer> player) {
+ return MakeGarbageCollected<PictureInPictureControllerFrameClient>(
+ std::move(player));
+ }
+
+ explicit PictureInPictureControllerFrameClient(
+ std::unique_ptr<WebMediaPlayer> player)
+ : test::MediaStubLocalFrameClient(std::move(player)),
+ interface_provider_(new service_manager::InterfaceProvider()) {}
+
+ service_manager::InterfaceProvider* GetInterfaceProvider() override {
+ return interface_provider_.get();
+ }
+
+ private:
+ std::unique_ptr<service_manager::InterfaceProvider> interface_provider_;
+
+ DISALLOW_COPY_AND_ASSIGN(PictureInPictureControllerFrameClient);
+};
+
+// TODO: can probably be removed.
+class PictureInPictureControllerPlayer : public EmptyWebMediaPlayer {
+ public:
+ PictureInPictureControllerPlayer() = default;
+ ~PictureInPictureControllerPlayer() final = default;
+
+ double Duration() const final {
+ if (infinity_duration_)
+ return std::numeric_limits<double>::infinity();
+ return EmptyWebMediaPlayer::Duration();
+ }
+
+ void set_infinity_duration(bool value) { infinity_duration_ = value; }
+
+ private:
+ bool infinity_duration_ = false;
+
+ DISALLOW_COPY_AND_ASSIGN(PictureInPictureControllerPlayer);
+};
+
+class PictureInPictureControllerTest : public PageTestBase {
+ public:
+ void SetUp() override {
+ PageTestBase::SetupPageWithClients(
+ nullptr, PictureInPictureControllerFrameClient::Create(
+ std::make_unique<PictureInPictureControllerPlayer>()));
+
+ service_manager::InterfaceProvider::TestApi test_api(
+ GetFrame().Client()->GetInterfaceProvider());
+ test_api.SetBinderForName(
+ mojom::blink::PictureInPictureService::Name_,
+ WTF::BindRepeating(&MockPictureInPictureService::Bind,
+ WTF::Unretained(&mock_service_)));
+
+ video_ = HTMLVideoElement::Create(GetDocument());
+ layer_ = cc::Layer::Create();
+ video_->SetCcLayerForTesting(layer_.get());
+
+ std::string test_name =
+ testing::UnitTest::GetInstance()->current_test_info()->name();
+ if (test_name.find("MediaSource") != std::string::npos) {
+ blink::WebMediaStream web_media_stream;
+ blink::WebVector<blink::WebMediaStreamTrack> dummy_tracks;
+ web_media_stream.Initialize(dummy_tracks, dummy_tracks);
+ Video()->SetSrcObject(web_media_stream);
+ } else {
+ video_->SetSrc("http://example.com/foo.mp4");
+ }
+
+ test::RunPendingTasks();
+ }
+
+ HTMLVideoElement* Video() const { return video_.Get(); }
+ MockPictureInPictureService& Service() { return mock_service_; }
+
+ private:
+ Persistent<HTMLVideoElement> video_;
+ MockPictureInPictureService mock_service_;
+ scoped_refptr<cc::Layer> layer_;
+};
+
+TEST_F(PictureInPictureControllerTest, EnterPictureInPictureFiresEvent) {
+ EXPECT_EQ(nullptr, PictureInPictureControllerImpl::From(GetDocument())
+ .PictureInPictureElement());
+
+ WebMediaPlayer* player = Video()->GetWebMediaPlayer();
+ EXPECT_CALL(Service(),
+ StartSession(player->GetDelegateId(), player->GetSurfaceId(),
+ player->NaturalSize(), true, _));
+ EXPECT_CALL(Service(), SetDelegate(_));
+
+ PictureInPictureControllerImpl::From(GetDocument())
+ .EnterPictureInPicture(Video(), nullptr);
+
+ WaitForEvent::Create(Video(), event_type_names::kEnterpictureinpicture);
+
+ EXPECT_NE(nullptr, PictureInPictureControllerImpl::From(GetDocument())
+ .PictureInPictureElement());
+
+ // `SetDelegate()` may or may not have been called yet. Waiting a bit for it.
+ test::RunPendingTasks();
+}
+
+TEST_F(PictureInPictureControllerTest, ExitPictureInPictureFiresEvent) {
+ EXPECT_EQ(nullptr, PictureInPictureControllerImpl::From(GetDocument())
+ .PictureInPictureElement());
+
+ WebMediaPlayer* player = Video()->GetWebMediaPlayer();
+ EXPECT_CALL(Service(),
+ StartSession(player->GetDelegateId(), player->GetSurfaceId(),
+ player->NaturalSize(), true, _));
+ EXPECT_CALL(Service(), EndSession(_));
+ EXPECT_CALL(Service(), SetDelegate(_));
+
+ PictureInPictureControllerImpl::From(GetDocument())
+ .EnterPictureInPicture(Video(), nullptr);
+ WaitForEvent::Create(Video(), event_type_names::kEnterpictureinpicture);
+
+ PictureInPictureControllerImpl::From(GetDocument())
+ .ExitPictureInPicture(Video(), nullptr);
+ WaitForEvent::Create(Video(), event_type_names::kLeavepictureinpicture);
+
+ EXPECT_EQ(nullptr, PictureInPictureControllerImpl::From(GetDocument())
+ .PictureInPictureElement());
+}
+
+TEST_F(PictureInPictureControllerTest, StartObserving) {
+ EXPECT_FALSE(PictureInPictureControllerImpl::From(GetDocument())
+ .GetDelegateBindingForTesting()
+ .is_bound());
+
+ WebMediaPlayer* player = Video()->GetWebMediaPlayer();
+ EXPECT_CALL(Service(),
+ StartSession(player->GetDelegateId(), player->GetSurfaceId(),
+ player->NaturalSize(), true, _));
+ EXPECT_CALL(Service(), SetDelegate(_));
+
+ PictureInPictureControllerImpl::From(GetDocument())
+ .EnterPictureInPicture(Video(), nullptr);
+
+ WaitForEvent::Create(Video(), event_type_names::kEnterpictureinpicture);
+
+ EXPECT_TRUE(PictureInPictureControllerImpl::From(GetDocument())
+ .GetDelegateBindingForTesting()
+ .is_bound());
+
+ // `SetDelegate()` may or may not have been called yet. Waiting a bit for it.
+ test::RunPendingTasks();
+}
+
+TEST_F(PictureInPictureControllerTest, StopObserving) {
+ EXPECT_FALSE(PictureInPictureControllerImpl::From(GetDocument())
+ .GetDelegateBindingForTesting()
+ .is_bound());
+
+ WebMediaPlayer* player = Video()->GetWebMediaPlayer();
+ EXPECT_CALL(Service(),
+ StartSession(player->GetDelegateId(), player->GetSurfaceId(),
+ player->NaturalSize(), true, _));
+ EXPECT_CALL(Service(), EndSession(_));
+ EXPECT_CALL(Service(), SetDelegate(_));
+
+ PictureInPictureControllerImpl::From(GetDocument())
+ .EnterPictureInPicture(Video(), nullptr);
+ WaitForEvent::Create(Video(), event_type_names::kEnterpictureinpicture);
+
+ PictureInPictureControllerImpl::From(GetDocument())
+ .ExitPictureInPicture(Video(), nullptr);
+ WaitForEvent::Create(Video(), event_type_names::kLeavepictureinpicture);
+
+ EXPECT_FALSE(PictureInPictureControllerImpl::From(GetDocument())
+ .GetDelegateBindingForTesting()
+ .is_bound());
+}
+
+TEST_F(PictureInPictureControllerTest, PlayPauseButton_InfiniteDuration) {
+ EXPECT_EQ(nullptr, PictureInPictureControllerImpl::From(GetDocument())
+ .PictureInPictureElement());
+
+ Video()->DurationChanged(std::numeric_limits<double>::infinity(), false);
+
+ WebMediaPlayer* player = Video()->GetWebMediaPlayer();
+ EXPECT_CALL(Service(),
+ StartSession(player->GetDelegateId(), player->GetSurfaceId(),
+ player->NaturalSize(), false, _));
+ EXPECT_CALL(Service(), SetDelegate(_));
+
+ PictureInPictureControllerImpl::From(GetDocument())
+ .EnterPictureInPicture(Video(), nullptr);
+
+ WaitForEvent::Create(Video(), event_type_names::kEnterpictureinpicture);
+
+ // `SetDelegate()` may or may not have been called yet. Waiting a bit for it.
+ test::RunPendingTasks();
+}
+
+TEST_F(PictureInPictureControllerTest, PlayPauseButton_MediaSource) {
+ EXPECT_EQ(nullptr, PictureInPictureControllerImpl::From(GetDocument())
+ .PictureInPictureElement());
+
+ // The test automatically setup the WebMediaPlayer with a MediaSource based on
+ // the test name.
+
+ WebMediaPlayer* player = Video()->GetWebMediaPlayer();
+ EXPECT_CALL(Service(),
+ StartSession(player->GetDelegateId(), player->GetSurfaceId(),
+ player->NaturalSize(), false, _));
+ EXPECT_CALL(Service(), SetDelegate(_));
+
+ PictureInPictureControllerImpl::From(GetDocument())
+ .EnterPictureInPicture(Video(), nullptr);
+
+ WaitForEvent::Create(Video(), event_type_names::kEnterpictureinpicture);
+
+ // `SetDelegate()` may or may not have been called yet. Waiting a bit for it.
+ test::RunPendingTasks();
+}
+
+} // namespace blink
diff --git a/third_party/blink/renderer/platform/testing/empty_web_media_player.h b/third_party/blink/renderer/platform/testing/empty_web_media_player.h
index 205fde9..fea2a40 100644
--- a/third_party/blink/renderer/platform/testing/empty_web_media_player.h
+++ b/third_party/blink/renderer/platform/testing/empty_web_media_player.h
@@ -28,12 +28,10 @@
void Seek(double seconds) override {}
void SetRate(double) override {}
void SetVolume(double) override {}
- void EnterPictureInPicture(PipWindowOpenedCallback) override {}
- void ExitPictureInPicture(PipWindowClosedCallback) override {}
+ void EnterPictureInPicture() override {}
+ void ExitPictureInPicture() override {}
void SetPictureInPictureCustomControls(
const std::vector<PictureInPictureControlInfo>&) override {}
- void RegisterPictureInPictureWindowResizeCallback(
- PipWindowResizedCallback) override {}
SurfaceLayerMode GetVideoSurfaceLayerMode() const override {
return SurfaceLayerMode::kNever;
}