blob: 82cdc83a39e5393b4fe4b3bd774f0ac63c8ebfba [file] [log] [blame]
// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/browser/media/session/media_session_impl.h"
#include <memory>
#include "base/command_line.h"
#include "base/memory/ptr_util.h"
#include "base/test/scoped_feature_list.h"
#include "build/build_config.h"
#include "content/browser/media/session/media_session_player_observer.h"
#include "content/browser/media/session/mock_media_session_player_observer.h"
#include "content/browser/media/session/mock_media_session_service_impl.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/media_session_service.h"
#include "content/public/browser/web_contents_delegate.h"
#include "content/public/test/test_browser_context.h"
#include "content/public/test/test_media_session_client.h"
#include "content/public/test/test_renderer_host.h"
#include "content/test/test_web_contents.h"
#include "media/base/media_content_type.h"
#include "media/base/media_switches.h"
#include "services/media_session/public/cpp/features.h"
#include "services/media_session/public/cpp/test/audio_focus_test_util.h"
#include "services/media_session/public/cpp/test/mock_media_session.h"
#include "services/media_session/public/mojom/audio_focus.mojom.h"
#include "services/media_session/public/mojom/media_session.mojom.h"
#include "third_party/blink/public/common/features.h"
using ::testing::_;
namespace content {
using media_session::mojom::AudioFocusType;
using media_session::mojom::MediaPlaybackState;
using media_session::mojom::MediaSessionAction;
using media_session::mojom::MediaSessionInfo;
using media_session::mojom::MediaSessionInfoPtr;
using media_session::test::MockMediaSessionMojoObserver;
using media_session::test::TestAudioFocusObserver;
namespace {
class MockAudioFocusDelegate : public AudioFocusDelegate {
public:
MockAudioFocusDelegate() = default;
MockAudioFocusDelegate(const MockAudioFocusDelegate&) = delete;
MockAudioFocusDelegate& operator=(const MockAudioFocusDelegate&) = delete;
~MockAudioFocusDelegate() override = default;
void AbandonAudioFocus() override {}
AudioFocusResult RequestAudioFocus(AudioFocusType type) override {
request_audio_focus_count_++;
last_requested_focus_type_ = type;
return AudioFocusResult::kSuccess;
}
std::optional<AudioFocusType> GetCurrentFocusType() const override {
return AudioFocusType::kGain;
}
void MediaSessionInfoChanged(
const MediaSessionInfoPtr& session_info) override {
session_info_ = session_info.Clone();
}
MOCK_CONST_METHOD0(request_id, const base::UnguessableToken&());
MOCK_METHOD(void, ReleaseRequestId, (), (override));
MediaSessionInfo::SessionState GetState() const {
DCHECK(!session_info_.is_null());
return session_info_->state;
}
int request_audio_focus_count() const { return request_audio_focus_count_; }
const std::optional<AudioFocusType>& GetLastRequestedFocusType() {
return last_requested_focus_type_;
}
private:
int request_audio_focus_count_ = 0;
std::optional<AudioFocusType> last_requested_focus_type_;
MediaSessionInfoPtr session_info_;
};
// A mock WebContentsDelegate which listens to |ActivateContents()| calls.
class MockWebContentsDelegate : public content::WebContentsDelegate {
public:
// content::WebContentsDelegate:
MOCK_METHOD(void, ActivateContents, (content::WebContents*), (override));
};
} // anonymous namespace
class MediaSessionImplTest : public RenderViewHostTestHarness {
public:
MediaSessionImplTest()
: RenderViewHostTestHarness(
base::test::TaskEnvironment::TimeSource::MOCK_TIME) {
default_actions_.insert(MediaSessionAction::kPlay);
default_actions_.insert(MediaSessionAction::kPause);
default_actions_.insert(MediaSessionAction::kStop);
default_actions_.insert(MediaSessionAction::kSeekTo);
default_actions_.insert(MediaSessionAction::kScrubTo);
default_actions_.insert(MediaSessionAction::kSeekForward);
default_actions_.insert(MediaSessionAction::kSeekBackward);
}
MediaSessionImplTest(const MediaSessionImplTest&) = delete;
MediaSessionImplTest& operator=(const MediaSessionImplTest&) = delete;
void SetUp() override {
scoped_feature_list_.InitWithFeatures(
{media_session::features::kMediaSessionService,
media_session::features::kAudioFocusEnforcement,
media::kGlobalMediaControlsPictureInPicture,
blink::features::kMediaSessionEnterPictureInPicture,
blink::features::kBrowserInitiatedAutomaticPictureInPicture},
{});
RenderViewHostTestHarness::SetUp();
player_observer_ = std::make_unique<MockMediaSessionPlayerObserver>(
main_rfh(), media::MediaContentType::kPersistent);
mock_media_session_service_ =
std::make_unique<testing::NiceMock<MockMediaSessionServiceImpl>>(
main_rfh());
// Connect to the Media Session service and bind |audio_focus_remote_| to
// it.
GetMediaSessionService().BindAudioFocusManager(
audio_focus_remote_.BindNewPipeAndPassReceiver());
audio_focus_remote_->SetEnforcementMode(
media_session::mojom::EnforcementMode::kDefault);
}
void TearDown() override {
mock_media_session_service_.reset();
scoped_feature_list_.Reset();
audio_focus_remote_->SetEnforcementMode(
media_session::mojom::EnforcementMode::kDefault);
RenderViewHostTestHarness::TearDown();
}
void RequestAudioFocus(MediaSessionImpl* session,
AudioFocusType audio_focus_type) {
session->RequestSystemAudioFocus(audio_focus_type);
}
void AbandonAudioFocus(MediaSessionImpl* session) {
session->AbandonSystemAudioFocusIfNeeded();
}
bool GetForceDuck(MediaSessionImpl* session) {
return media_session::test::GetMediaSessionInfoSync(session)->force_duck;
}
MediaSessionInfo::SessionState GetState(MediaSessionImpl* session) {
return media_session::test::GetMediaSessionInfoSync(session)->state;
}
void ClearObservers(MediaSessionImpl* session) {
session->observers_.Clear();
}
bool HasObservers(MediaSessionImpl* session) {
return !session->observers_.empty();
}
void FlushForTesting(MediaSessionImpl* session) {
session->FlushForTesting();
}
std::unique_ptr<TestAudioFocusObserver> CreateAudioFocusObserver() {
std::unique_ptr<TestAudioFocusObserver> observer =
std::make_unique<TestAudioFocusObserver>();
audio_focus_remote_->AddObserver(observer->BindNewPipeAndPassRemote());
audio_focus_remote_.FlushForTesting();
return observer;
}
std::unique_ptr<MockMediaSessionPlayerObserver> player_observer_;
void SetDelegateForTests(MediaSessionImpl* session,
std::unique_ptr<AudioFocusDelegate> delegate) {
session->SetDelegateForTests(std::move(delegate));
}
MockMediaSessionServiceImpl& mock_media_session_service() const {
return *mock_media_session_service_.get();
}
MediaSessionImpl* GetMediaSession() {
return MediaSessionImpl::Get(web_contents());
}
// Returns the player ID.
int StartNewPlayer() {
int player_id;
GetMediaSession()->AddPlayer(
player_observer_.get(), player_id = player_observer_->StartNewPlayer());
return player_id;
}
MockMediaSessionPlayerObserver* player_observer() {
return player_observer_.get();
}
const std::set<MediaSessionAction>& default_actions() const {
return default_actions_;
}
void OnVideoVisibilityChanged() {
GetMediaSession()->OnVideoVisibilityChanged();
}
bool HasSufficientlyVisibleVideo() {
return GetMediaSession()->HasSufficientlyVisibleVideo();
}
private:
std::set<MediaSessionAction> default_actions_;
base::test::ScopedFeatureList scoped_feature_list_;
std::unique_ptr<MockMediaSessionServiceImpl> mock_media_session_service_;
mojo::Remote<media_session::mojom::AudioFocusManager> audio_focus_remote_;
};
TEST_F(MediaSessionImplTest, SessionInfoState) {
EXPECT_EQ(MediaSessionInfo::SessionState::kInactive,
GetState(GetMediaSession()));
{
MockMediaSessionMojoObserver observer(*GetMediaSession());
RequestAudioFocus(GetMediaSession(), AudioFocusType::kGain);
observer.WaitForState(MediaSessionInfo::SessionState::kActive);
EXPECT_TRUE(observer.session_info().Equals(
media_session::test::GetMediaSessionInfoSync(GetMediaSession())));
}
{
MockMediaSessionMojoObserver observer(*GetMediaSession());
GetMediaSession()->StartDucking();
observer.WaitForState(MediaSessionInfo::SessionState::kDucking);
}
{
MockMediaSessionMojoObserver observer(*GetMediaSession());
GetMediaSession()->StopDucking();
observer.WaitForState(MediaSessionInfo::SessionState::kActive);
EXPECT_TRUE(observer.session_info().Equals(
media_session::test::GetMediaSessionInfoSync(GetMediaSession())));
}
{
MockMediaSessionMojoObserver observer(*GetMediaSession());
GetMediaSession()->Suspend(MediaSession::SuspendType::kSystem);
observer.WaitForState(MediaSessionInfo::SessionState::kSuspended);
EXPECT_TRUE(observer.session_info().Equals(
media_session::test::GetMediaSessionInfoSync(GetMediaSession())));
}
{
MockMediaSessionMojoObserver observer(*GetMediaSession());
GetMediaSession()->Resume(MediaSession::SuspendType::kSystem);
observer.WaitForState(MediaSessionInfo::SessionState::kActive);
EXPECT_TRUE(observer.session_info().Equals(
media_session::test::GetMediaSessionInfoSync(GetMediaSession())));
}
{
MockMediaSessionMojoObserver observer(*GetMediaSession());
AbandonAudioFocus(GetMediaSession());
observer.WaitForState(MediaSessionInfo::SessionState::kInactive);
EXPECT_TRUE(observer.session_info().Equals(
media_session::test::GetMediaSessionInfoSync(GetMediaSession())));
}
}
TEST_F(MediaSessionImplTest, NotifyDelegateOnStateChange) {
auto delegate_unique = std::make_unique<MockAudioFocusDelegate>();
MockAudioFocusDelegate* delegate = delegate_unique.get();
SetDelegateForTests(GetMediaSession(), std::move(delegate_unique));
RequestAudioFocus(GetMediaSession(), AudioFocusType::kGain);
base::RunLoop().RunUntilIdle();
EXPECT_EQ(MediaSessionInfo::SessionState::kActive, delegate->GetState());
GetMediaSession()->StartDucking();
base::RunLoop().RunUntilIdle();
EXPECT_EQ(MediaSessionInfo::SessionState::kDucking, delegate->GetState());
GetMediaSession()->StopDucking();
base::RunLoop().RunUntilIdle();
EXPECT_EQ(MediaSessionInfo::SessionState::kActive, delegate->GetState());
GetMediaSession()->Suspend(MediaSession::SuspendType::kSystem);
base::RunLoop().RunUntilIdle();
EXPECT_EQ(MediaSessionInfo::SessionState::kSuspended, delegate->GetState());
GetMediaSession()->Resume(MediaSession::SuspendType::kSystem);
base::RunLoop().RunUntilIdle();
EXPECT_EQ(MediaSessionInfo::SessionState::kActive, delegate->GetState());
AbandonAudioFocus(GetMediaSession());
base::RunLoop().RunUntilIdle();
EXPECT_EQ(MediaSessionInfo::SessionState::kInactive, delegate->GetState());
}
TEST_F(MediaSessionImplTest, RegisterObserver) {
// There is no way to get the number of mojo observers so we should just
// remove them all and check if the mojo observers interface ptr set is
// empty or not.
ClearObservers(GetMediaSession());
EXPECT_FALSE(HasObservers(GetMediaSession()));
MockMediaSessionMojoObserver observer(*GetMediaSession());
FlushForTesting(GetMediaSession());
EXPECT_TRUE(HasObservers(GetMediaSession()));
}
TEST_F(MediaSessionImplTest, SessionInfo_PlaybackState) {
EXPECT_EQ(MediaPlaybackState::kPaused,
media_session::test::GetMediaSessionInfoSync(GetMediaSession())
->playback_state);
int player_id = player_observer_->StartNewPlayer();
{
MockMediaSessionMojoObserver observer(*GetMediaSession());
GetMediaSession()->AddPlayer(player_observer_.get(), player_id);
observer.WaitForPlaybackState(MediaPlaybackState::kPlaying);
}
{
MockMediaSessionMojoObserver observer(*GetMediaSession());
GetMediaSession()->OnPlayerPaused(player_observer_.get(), player_id);
observer.WaitForPlaybackState(MediaPlaybackState::kPaused);
}
}
TEST_F(MediaSessionImplTest, SuspendUI) {
EXPECT_CALL(mock_media_session_service().mock_client(),
DidReceiveAction(MediaSessionAction::kPause, _))
.Times(0);
StartNewPlayer();
GetMediaSession()->Suspend(MediaSession::SuspendType::kUI);
mock_media_session_service().FlushForTesting();
media_session::test::MockMediaSessionMojoObserver observer(
*GetMediaSession());
observer.WaitForExpectedActions(default_actions());
}
TEST_F(MediaSessionImplTest, SuspendContent_WithAction) {
EXPECT_CALL(mock_media_session_service().mock_client(),
DidReceiveAction(MediaSessionAction::kPause, _))
.Times(0);
StartNewPlayer();
mock_media_session_service().EnableAction(MediaSessionAction::kPause);
GetMediaSession()->Suspend(MediaSession::SuspendType::kContent);
mock_media_session_service().FlushForTesting();
media_session::test::MockMediaSessionMojoObserver observer(
*GetMediaSession());
observer.WaitForExpectedActions(default_actions());
}
TEST_F(MediaSessionImplTest, SuspendSystem_WithAction) {
EXPECT_CALL(mock_media_session_service().mock_client(),
DidReceiveAction(MediaSessionAction::kPause, _))
.Times(0);
StartNewPlayer();
mock_media_session_service().EnableAction(MediaSessionAction::kPause);
GetMediaSession()->Suspend(MediaSession::SuspendType::kSystem);
mock_media_session_service().FlushForTesting();
media_session::test::MockMediaSessionMojoObserver observer(
*GetMediaSession());
observer.WaitForExpectedActions(default_actions());
}
TEST_F(MediaSessionImplTest, SuspendUI_WithAction) {
EXPECT_CALL(mock_media_session_service().mock_client(),
DidReceiveAction(MediaSessionAction::kPause, _));
StartNewPlayer();
mock_media_session_service().EnableAction(MediaSessionAction::kPause);
GetMediaSession()->Suspend(MediaSession::SuspendType::kUI);
mock_media_session_service().FlushForTesting();
media_session::test::MockMediaSessionMojoObserver observer(
*GetMediaSession());
observer.WaitForExpectedActions(default_actions());
}
TEST_F(MediaSessionImplTest, ResumeUI) {
EXPECT_CALL(mock_media_session_service().mock_client(),
DidReceiveAction(MediaSessionAction::kPlay, _))
.Times(0);
StartNewPlayer();
GetMediaSession()->Suspend(MediaSession::SuspendType::kSystem);
GetMediaSession()->Resume(MediaSession::SuspendType::kUI);
mock_media_session_service().FlushForTesting();
media_session::test::MockMediaSessionMojoObserver observer(
*GetMediaSession());
observer.WaitForExpectedActions(default_actions());
}
TEST_F(MediaSessionImplTest, ResumeContent_WithAction) {
EXPECT_CALL(mock_media_session_service().mock_client(),
DidReceiveAction(MediaSessionAction::kPlay, _))
.Times(0);
StartNewPlayer();
mock_media_session_service().EnableAction(MediaSessionAction::kPlay);
GetMediaSession()->Suspend(MediaSession::SuspendType::kSystem);
GetMediaSession()->Resume(MediaSession::SuspendType::kContent);
mock_media_session_service().FlushForTesting();
media_session::test::MockMediaSessionMojoObserver observer(
*GetMediaSession());
observer.WaitForExpectedActions(default_actions());
}
TEST_F(MediaSessionImplTest, ResumeSystem_WithAction) {
EXPECT_CALL(mock_media_session_service().mock_client(),
DidReceiveAction(MediaSessionAction::kPlay, _))
.Times(0);
StartNewPlayer();
mock_media_session_service().EnableAction(MediaSessionAction::kPlay);
GetMediaSession()->Suspend(MediaSession::SuspendType::kSystem);
GetMediaSession()->Resume(MediaSession::SuspendType::kSystem);
mock_media_session_service().FlushForTesting();
media_session::test::MockMediaSessionMojoObserver observer(
*GetMediaSession());
observer.WaitForExpectedActions(default_actions());
}
TEST_F(MediaSessionImplTest, ResumeUI_WithAction) {
EXPECT_CALL(mock_media_session_service().mock_client(),
DidReceiveAction(MediaSessionAction::kPlay, _));
StartNewPlayer();
mock_media_session_service().EnableAction(MediaSessionAction::kPlay);
GetMediaSession()->Suspend(MediaSession::SuspendType::kSystem);
GetMediaSession()->Resume(MediaSession::SuspendType::kUI);
mock_media_session_service().FlushForTesting();
media_session::test::MockMediaSessionMojoObserver observer(
*GetMediaSession());
observer.WaitForExpectedActions(default_actions());
}
#if !BUILDFLAG(IS_ANDROID)
TEST_F(MediaSessionImplTest, WebContentsDestroyed_ReleasesFocus) {
std::unique_ptr<WebContents> web_contents(CreateTestWebContents());
MediaSessionImpl* media_session = MediaSessionImpl::Get(web_contents.get());
{
std::unique_ptr<TestAudioFocusObserver> observer =
CreateAudioFocusObserver();
RequestAudioFocus(media_session, AudioFocusType::kGain);
observer->WaitForGainedEvent();
}
{
MockMediaSessionMojoObserver observer(*media_session);
observer.WaitForState(MediaSessionInfo::SessionState::kActive);
}
{
std::unique_ptr<TestAudioFocusObserver> observer =
CreateAudioFocusObserver();
web_contents.reset();
observer->WaitForLostEvent();
}
}
TEST_F(MediaSessionImplTest, WebContentsDestroyed_ReleasesTransients) {
std::unique_ptr<WebContents> web_contents(CreateTestWebContents());
MediaSessionImpl* media_session = MediaSessionImpl::Get(web_contents.get());
{
std::unique_ptr<TestAudioFocusObserver> observer =
CreateAudioFocusObserver();
RequestAudioFocus(media_session, AudioFocusType::kGainTransientMayDuck);
observer->WaitForGainedEvent();
}
{
MockMediaSessionMojoObserver observer(*media_session);
observer.WaitForState(MediaSessionInfo::SessionState::kActive);
}
{
std::unique_ptr<TestAudioFocusObserver> observer =
CreateAudioFocusObserver();
web_contents.reset();
observer->WaitForLostEvent();
}
}
TEST_F(MediaSessionImplTest, WebContentsDestroyed_StopsDucking) {
std::unique_ptr<WebContents> web_contents_1(CreateTestWebContents());
MediaSessionImpl* media_session_1 =
MediaSessionImpl::Get(web_contents_1.get());
std::unique_ptr<WebContents> web_contents_2(CreateTestWebContents());
MediaSessionImpl* media_session_2 =
MediaSessionImpl::Get(web_contents_2.get());
{
std::unique_ptr<TestAudioFocusObserver> observer =
CreateAudioFocusObserver();
RequestAudioFocus(media_session_1, AudioFocusType::kGain);
observer->WaitForGainedEvent();
}
{
MockMediaSessionMojoObserver observer(*media_session_1);
observer.WaitForState(MediaSessionInfo::SessionState::kActive);
}
{
std::unique_ptr<TestAudioFocusObserver> observer =
CreateAudioFocusObserver();
RequestAudioFocus(media_session_2, AudioFocusType::kGainTransientMayDuck);
observer->WaitForGainedEvent();
}
{
MockMediaSessionMojoObserver observer(*media_session_1);
observer.WaitForState(MediaSessionInfo::SessionState::kDucking);
}
{
std::unique_ptr<TestAudioFocusObserver> observer =
CreateAudioFocusObserver();
web_contents_2.reset();
observer->WaitForLostEvent();
}
{
MockMediaSessionMojoObserver observer(*media_session_1);
observer.WaitForState(MediaSessionInfo::SessionState::kActive);
}
}
#if BUILDFLAG(IS_MAC)
TEST_F(MediaSessionImplTest, TabFocusDoesNotCauseAudioFocus) {
auto delegate_unique = std::make_unique<MockAudioFocusDelegate>();
MockAudioFocusDelegate* delegate = delegate_unique.get();
SetDelegateForTests(GetMediaSession(), std::move(delegate_unique));
{
MockMediaSessionMojoObserver observer(*GetMediaSession());
RequestAudioFocus(GetMediaSession(), AudioFocusType::kGain);
FlushForTesting(GetMediaSession());
observer.WaitForState(MediaSessionInfo::SessionState::kActive);
}
EXPECT_EQ(1, delegate->request_audio_focus_count());
GetMediaSession()->OnWebContentsFocused(nullptr);
EXPECT_EQ(1, delegate->request_audio_focus_count());
}
#else // BUILDFLAG(IS_MAC)
TEST_F(MediaSessionImplTest, RequestAudioFocus_OnFocus_Active) {
auto delegate_unique = std::make_unique<MockAudioFocusDelegate>();
MockAudioFocusDelegate* delegate = delegate_unique.get();
SetDelegateForTests(GetMediaSession(), std::move(delegate_unique));
{
MockMediaSessionMojoObserver observer(*GetMediaSession());
RequestAudioFocus(GetMediaSession(), AudioFocusType::kGain);
FlushForTesting(GetMediaSession());
observer.WaitForState(MediaSessionInfo::SessionState::kActive);
}
EXPECT_EQ(1, delegate->request_audio_focus_count());
GetMediaSession()->OnWebContentsFocused(nullptr);
EXPECT_EQ(2, delegate->request_audio_focus_count());
}
TEST_F(MediaSessionImplTest, RequestAudioFocus_OnFocus_Inactive) {
auto delegate_unique = std::make_unique<MockAudioFocusDelegate>();
MockAudioFocusDelegate* delegate = delegate_unique.get();
SetDelegateForTests(GetMediaSession(), std::move(delegate_unique));
EXPECT_EQ(MediaSessionInfo::SessionState::kInactive,
GetState(GetMediaSession()));
EXPECT_EQ(0, delegate->request_audio_focus_count());
GetMediaSession()->OnWebContentsFocused(nullptr);
EXPECT_EQ(0, delegate->request_audio_focus_count());
}
TEST_F(MediaSessionImplTest, RequestAudioFocus_OnFocus_Suspended) {
auto delegate_unique = std::make_unique<MockAudioFocusDelegate>();
MockAudioFocusDelegate* delegate = delegate_unique.get();
SetDelegateForTests(GetMediaSession(), std::move(delegate_unique));
{
MockMediaSessionMojoObserver observer(*GetMediaSession());
RequestAudioFocus(GetMediaSession(), AudioFocusType::kGain);
FlushForTesting(GetMediaSession());
observer.WaitForState(MediaSessionInfo::SessionState::kActive);
}
{
MockMediaSessionMojoObserver observer(*GetMediaSession());
GetMediaSession()->Suspend(MediaSession::SuspendType::kSystem);
observer.WaitForState(MediaSessionInfo::SessionState::kSuspended);
}
EXPECT_EQ(1, delegate->request_audio_focus_count());
GetMediaSession()->OnWebContentsFocused(nullptr);
EXPECT_EQ(1, delegate->request_audio_focus_count());
}
#endif // BUILDFLAG(IS_MAC)
#endif // !BUILDFLAG(IS_ANDROID)
TEST_F(MediaSessionImplTest, SourceId_SameBrowserContext) {
auto other_contents = TestWebContents::Create(browser_context(), nullptr);
MediaSessionImpl* other_session = MediaSessionImpl::Get(other_contents.get());
EXPECT_EQ(GetMediaSession()->GetSourceId(), other_session->GetSourceId());
}
TEST_F(MediaSessionImplTest, SourceId_DifferentBrowserContext) {
auto other_context = CreateBrowserContext();
auto other_contents = TestWebContents::Create(other_context.get(), nullptr);
MediaSessionImpl* other_session = MediaSessionImpl::Get(other_contents.get());
EXPECT_NE(GetMediaSession()->GetSourceId(), other_session->GetSourceId());
}
TEST_F(MediaSessionImplTest, SessionInfoPictureInPicture) {
WebContentsImpl* web_contents_impl =
static_cast<WebContentsImpl*>(web_contents());
EXPECT_EQ(
media_session::test::GetMediaSessionInfoSync(GetMediaSession())
->picture_in_picture_state,
media_session::mojom::MediaPictureInPictureState::kNotInPictureInPicture);
web_contents_impl->SetHasPictureInPictureVideo(true);
EXPECT_EQ(
media_session::test::GetMediaSessionInfoSync(GetMediaSession())
->picture_in_picture_state,
media_session::mojom::MediaPictureInPictureState::kInPictureInPicture);
web_contents_impl->SetHasPictureInPictureVideo(false);
EXPECT_EQ(
media_session::test::GetMediaSessionInfoSync(GetMediaSession())
->picture_in_picture_state,
media_session::mojom::MediaPictureInPictureState::kNotInPictureInPicture);
}
TEST_F(MediaSessionImplTest, SessionInfoAudioSink) {
// When the session is created it should be using the default audio device.
// When the default audio device is in use, the |audio_sink_id| attribute
// should be unset.
EXPECT_FALSE(media_session::test::GetMediaSessionInfoSync(GetMediaSession())
->audio_sink_id.has_value());
int player1 = player_observer_->StartNewPlayer();
int player2 = player_observer_->StartNewPlayer();
GetMediaSession()->AddPlayer(player_observer_.get(), player1);
GetMediaSession()->AddPlayer(player_observer_.get(), player2);
player_observer_->SetAudioSinkId(player1, "1");
player_observer_->SetAudioSinkId(player2, "1");
auto info = media_session::test::GetMediaSessionInfoSync(GetMediaSession());
ASSERT_TRUE(info->audio_sink_id.has_value());
EXPECT_EQ(info->audio_sink_id.value(), "1");
// If multiple audio devices are being used the audio sink id attribute should
// be unset.
player_observer_->SetAudioSinkId(player2, "2");
info = media_session::test::GetMediaSessionInfoSync(GetMediaSession());
EXPECT_FALSE(info->audio_sink_id.has_value());
}
TEST_F(MediaSessionImplTest, SessionInfoPresentation) {
EXPECT_FALSE(media_session::test::GetMediaSessionInfoSync(GetMediaSession())
->has_presentation);
GetMediaSession()->OnPresentationsChanged(true);
EXPECT_TRUE(media_session::test::GetMediaSessionInfoSync(GetMediaSession())
->has_presentation);
GetMediaSession()->OnPresentationsChanged(false);
EXPECT_FALSE(media_session::test::GetMediaSessionInfoSync(GetMediaSession())
->has_presentation);
}
TEST_F(MediaSessionImplTest, SessionInfoRemotePlaybackMetadata) {
EXPECT_FALSE(media_session::test::GetMediaSessionInfoSync(GetMediaSession())
->remote_playback_metadata);
int player1 = player_observer_->StartNewPlayer();
GetMediaSession()->AddPlayer(player_observer_.get(), player1);
GetMediaSession()->SetRemotePlaybackMetadata(
media_session::mojom::RemotePlaybackMetadata::New(
"video_codec", "audio_codec", false, true, "device_friendly_name",
false));
EXPECT_TRUE(media_session::test::GetMediaSessionInfoSync(GetMediaSession())
->remote_playback_metadata);
int player2 = player_observer_->StartNewPlayer();
GetMediaSession()->AddPlayer(player_observer_.get(), player2);
EXPECT_TRUE(media_session::test::GetMediaSessionInfoSync(GetMediaSession())
->remote_playback_metadata->remote_playback_disabled);
}
TEST_F(MediaSessionImplTest, RaiseActivatesWebContents) {
MockWebContentsDelegate delegate;
web_contents()->SetDelegate(&delegate);
// When the WebContents has a delegate, |Raise()| should activate the
// WebContents.
EXPECT_CALL(delegate, ActivateContents(web_contents()));
GetMediaSession()->Raise();
testing::Mock::VerifyAndClearExpectations(&delegate);
// When the WebContents does not have a delegate, |Raise()| should not crash.
web_contents()->SetDelegate(nullptr);
GetMediaSession()->Raise();
}
TEST_F(MediaSessionImplTest,
RegisteredEnterPictureInPictureExposesAutoPictureInPicture) {
// When the website has registered for 'enterpictureinpicture',
// MediaSessionImpl should expose the kEnterPictureInPicture,
// kEnterAutoPictureInPicture, and kExitPictureInPicture to the internal
// MediaSession service.
StartNewPlayer();
media_session::test::MockMediaSessionMojoObserver observer(
*GetMediaSession());
mock_media_session_service().EnableAction(
MediaSessionAction::kEnterPictureInPicture);
mock_media_session_service().FlushForTesting();
EXPECT_TRUE(base::Contains(observer.actions(),
MediaSessionAction::kEnterPictureInPicture));
EXPECT_TRUE(base::Contains(observer.actions(),
MediaSessionAction::kEnterAutoPictureInPicture));
EXPECT_TRUE(base::Contains(observer.actions(),
MediaSessionAction::kExitPictureInPicture));
}
TEST_F(MediaSessionImplTest, WebContentsHasPictureInPictureVideo) {
WebContentsImpl* web_contents_impl =
static_cast<WebContentsImpl*>(web_contents());
web_contents_impl->SetHasPictureInPictureVideo(true);
StartNewPlayer();
media_session::test::MockMediaSessionMojoObserver observer(
*GetMediaSession());
mock_media_session_service().EnableAction(MediaSessionAction::kPause);
mock_media_session_service().FlushForTesting();
EXPECT_FALSE(base::Contains(observer.actions(),
MediaSessionAction::kEnterPictureInPicture));
EXPECT_TRUE(base::Contains(observer.actions(),
MediaSessionAction::kExitPictureInPicture));
}
TEST_F(MediaSessionImplTest, WebContentsHasPictureInPictureDocument) {
WebContentsImpl* web_contents_impl =
static_cast<WebContentsImpl*>(web_contents());
web_contents_impl->SetHasPictureInPictureDocument(true);
StartNewPlayer();
media_session::test::MockMediaSessionMojoObserver observer(
*GetMediaSession());
mock_media_session_service().EnableAction(MediaSessionAction::kPause);
mock_media_session_service().FlushForTesting();
EXPECT_FALSE(base::Contains(observer.actions(),
MediaSessionAction::kEnterPictureInPicture));
EXPECT_TRUE(base::Contains(observer.actions(),
MediaSessionAction::kExitPictureInPicture));
}
TEST_F(MediaSessionImplTest, SufficientlyVisibleVideo_NoPlayer) {
OnVideoVisibilityChanged();
EXPECT_FALSE(HasSufficientlyVisibleVideo());
}
TEST_F(MediaSessionImplTest, SufficientlyVisibleVideo_MultiplePlayers) {
// Start with a single player with a video reporting as sufficiently visible.
int player1 = player_observer_->StartNewPlayer();
player_observer_->SetHasSufficientlyVisibleVideo(player1, true);
GetMediaSession()->AddPlayer(player_observer_.get(), player1);
OnVideoVisibilityChanged();
EXPECT_TRUE(HasSufficientlyVisibleVideo());
// Add a second player with with a video reporting as sufficiently visible,
// and make player1 report that its video is not sufficiently visible.
int player2 = player_observer_->StartNewPlayer();
player_observer_->SetHasSufficientlyVisibleVideo(player2, true);
GetMediaSession()->AddPlayer(player_observer_.get(), player2);
player_observer_->SetHasSufficientlyVisibleVideo(player1, false);
OnVideoVisibilityChanged();
EXPECT_TRUE(HasSufficientlyVisibleVideo());
// Make player2 report that its video is not sufficiently visible.
player_observer_->SetHasSufficientlyVisibleVideo(player2, false);
OnVideoVisibilityChanged();
EXPECT_FALSE(HasSufficientlyVisibleVideo());
}
TEST_F(MediaSessionImplTest, SessionInfoDontHideMetadataByDefault) {
EXPECT_FALSE(media_session::test::GetMediaSessionInfoSync(GetMediaSession())
->hide_metadata);
}
TEST_F(MediaSessionImplTest, PausedPlayersDoNotRequestFocus) {
// If a player is paused when it's added, it should be controllable but should
// not request audio focus.
auto delegate_unique = std::make_unique<MockAudioFocusDelegate>();
MockAudioFocusDelegate* delegate = delegate_unique.get();
SetDelegateForTests(GetMediaSession(), std::move(delegate_unique));
int player_id = StartNewPlayer();
EXPECT_TRUE(GetMediaSession()->IsActive());
EXPECT_TRUE(GetMediaSession()->IsControllable());
EXPECT_EQ(delegate->request_audio_focus_count(), 1);
player_observer()->SetPlaying(player_id, false);
// Remember that the player still has the audio focus. Re-adding the paused
// player should neither lose the focus, nor re-request it.
GetMediaSession()->AddPlayer(player_observer(), player_id);
EXPECT_EQ(delegate->request_audio_focus_count(), 1);
EXPECT_TRUE(GetMediaSession()->IsActive());
// Give up audio focus.
GetMediaSession()->RemovePlayer(player_observer(), player_id);
EXPECT_FALSE(GetMediaSession()->IsActive());
EXPECT_FALSE(GetMediaSession()->IsControllable());
// Adding should now result in an inactive session that is controllable,
// without requesting focus again.
GetMediaSession()->AddPlayer(player_observer(), player_id);
EXPECT_EQ(delegate->request_audio_focus_count(), 1);
EXPECT_FALSE(GetMediaSession()->IsActive());
EXPECT_TRUE(GetMediaSession()->IsControllable());
}
TEST_F(MediaSessionImplTest, SeekingAndScrubbingNotAllowedWithMaxDuration) {
MockMediaSessionMojoObserver observer(*GetMediaSession());
int player_id = player_observer_->StartNewPlayer();
GetMediaSession()->AddPlayer(player_observer_.get(), player_id);
media_session::MediaPosition pos;
pos = media_session::MediaPosition(
/*playback_rate=*/1.0,
/*duration=*/base::TimeDelta::Max(),
/*position=*/base::TimeDelta(), /*end_of_media=*/false);
player_observer_->SetPosition(player_id, pos);
GetMediaSession()->RebuildAndNotifyMediaPositionChanged();
FlushForTesting(GetMediaSession());
// With a max duration, we should be considered live media and should not
// allow seeking and scrubbing actions by default.
EXPECT_FALSE(base::Contains(observer.actions(), MediaSessionAction::kSeekTo));
EXPECT_FALSE(
base::Contains(observer.actions(), MediaSessionAction::kScrubTo));
EXPECT_FALSE(
base::Contains(observer.actions(), MediaSessionAction::kSeekForward));
EXPECT_FALSE(
base::Contains(observer.actions(), MediaSessionAction::kSeekBackward));
// However, if the website explicitly supports the action, then we will still
// route it.
mock_media_session_service().EnableAction(MediaSessionAction::kSeekTo);
FlushForTesting(GetMediaSession());
EXPECT_TRUE(base::Contains(observer.actions(), MediaSessionAction::kSeekTo));
EXPECT_FALSE(
base::Contains(observer.actions(), MediaSessionAction::kScrubTo));
EXPECT_FALSE(
base::Contains(observer.actions(), MediaSessionAction::kSeekForward));
EXPECT_FALSE(
base::Contains(observer.actions(), MediaSessionAction::kSeekBackward));
}
TEST_F(MediaSessionImplTest, AmbientPlayerFocusRequest) {
auto delegate_unique = std::make_unique<MockAudioFocusDelegate>();
MockAudioFocusDelegate* delegate = delegate_unique.get();
SetDelegateForTests(GetMediaSession(), std::move(delegate_unique));
int player_id = player_observer_->StartNewPlayer();
player_observer_->SetMediaContentType(media::MediaContentType::kAmbient);
MockMediaSessionMojoObserver observer(*GetMediaSession());
GetMediaSession()->AddPlayer(player_observer_.get(), player_id);
#if BUILDFLAG(IS_ANDROID)
// On Android, ambient players should not request audio focus.
observer.WaitForState(MediaSessionInfo::SessionState::kInactive);
EXPECT_FALSE(delegate->GetLastRequestedFocusType().has_value());
#else
// On other platforms, an ambient player should request ambient focus.
observer.WaitForState(MediaSessionInfo::SessionState::kActive);
EXPECT_TRUE(delegate->GetLastRequestedFocusType().has_value());
EXPECT_EQ(AudioFocusType::kAmbient, *delegate->GetLastRequestedFocusType());
// Ambient players should also receive volume multiplier updates.
constexpr float kDuckingMultiplier = 0.1;
EXPECT_EQ(player_observer_->GetVolumeMultiplier(player_id), 1.0);
GetMediaSession()->SetDuckingVolumeMultiplier(kDuckingMultiplier);
GetMediaSession()->StartDucking();
EXPECT_EQ(player_observer_->GetVolumeMultiplier(player_id),
kDuckingMultiplier);
GetMediaSession()->StopDucking();
EXPECT_EQ(player_observer_->GetVolumeMultiplier(player_id), 1.0);
#endif // BUILDFLAG(IS_ANDROID)
}
TEST_F(MediaSessionImplTest, AmbientPlayerDoesNotRequestFocusWhenSuspended) {
auto delegate_unique = std::make_unique<MockAudioFocusDelegate>();
SetDelegateForTests(GetMediaSession(), std::move(delegate_unique));
int persistent_player_id = player_observer_->StartNewPlayer();
MockMediaSessionPlayerObserver ambient_player_observer(
main_rfh(), media::MediaContentType::kAmbient);
int ambient_player_id = ambient_player_observer.StartNewPlayer();
// Add a persistent player to get audio focus.
MockMediaSessionMojoObserver observer(*GetMediaSession());
GetMediaSession()->AddPlayer(player_observer_.get(), persistent_player_id);
observer.WaitForState(MediaSessionInfo::SessionState::kActive);
// Suspend the persistent player.
GetMediaSession()->Suspend(MediaSession::SuspendType::kSystem);
observer.WaitForState(MediaSessionInfo::SessionState::kSuspended);
// Adding an ambient player should not change the audio focus state.
GetMediaSession()->AddPlayer(&ambient_player_observer, ambient_player_id);
observer.WaitForState(MediaSessionInfo::SessionState::kSuspended);
}
TEST_F(MediaSessionImplTest, AutoPictureInPictureInfoChanged) {
EXPECT_EQ(
0,
player_observer_->received_auto_picture_in_picture_info_changed_calls());
mock_media_session_service().EnableAction(
MediaSessionAction::kEnterPictureInPicture);
mock_media_session_service().FlushForTesting();
int player = player_observer_->StartNewPlayer();
GetMediaSession()->AddPlayer(player_observer_.get(), player);
GetMediaSession()->EnterAutoPictureInPicture();
EXPECT_EQ(
1,
player_observer_->received_auto_picture_in_picture_info_changed_calls());
}
TEST_F(MediaSessionImplTest,
DoesNotEntersBrowserInitiatedAutoPip_MoreThanOnePlayer) {
MockMediaSessionMojoObserver observer(*GetMediaSession());
FlushForTesting(GetMediaSession());
EXPECT_FALSE(base::Contains(observer.actions(),
MediaSessionAction::kEnterAutoPictureInPicture));
EXPECT_EQ(0, player_observer_->received_enter_picture_in_picture_calls());
int player1 = player_observer_->StartNewPlayer();
player_observer_->SetIsPictureInPictureAvailable(player1, true);
GetMediaSession()->AddPlayer(player_observer_.get(), player1);
observer.WaitForState(MediaSessionInfo::SessionState::kActive);
FlushForTesting(GetMediaSession());
EXPECT_TRUE(base::Contains(observer.actions(),
MediaSessionAction::kEnterAutoPictureInPicture));
int player2 = player_observer_->StartNewPlayer();
player_observer_->SetIsPictureInPictureAvailable(player2, true);
GetMediaSession()->AddPlayer(player_observer_.get(), player2);
observer.WaitForState(MediaSessionInfo::SessionState::kActive);
FlushForTesting(GetMediaSession());
EXPECT_FALSE(GetMediaSession()->ShouldRouteAction(
MediaSessionAction::kEnterPictureInPicture));
EXPECT_FALSE(base::Contains(observer.actions(),
MediaSessionAction::kEnterAutoPictureInPicture));
GetMediaSession()->EnterAutoPictureInPicture();
EXPECT_EQ(0, player_observer_->received_enter_picture_in_picture_calls());
}
TEST_F(MediaSessionImplTest,
DoesNotEntersBrowserInitiatedAutoPip_SinglePlayerNotPlaying) {
MockMediaSessionMojoObserver observer(*GetMediaSession());
FlushForTesting(GetMediaSession());
EXPECT_FALSE(base::Contains(observer.actions(),
MediaSessionAction::kEnterAutoPictureInPicture));
EXPECT_EQ(0, player_observer_->received_enter_picture_in_picture_calls());
int player = player_observer_->StartNewPlayer(/*is_playing=*/false);
player_observer_->SetIsPictureInPictureAvailable(player, true);
GetMediaSession()->AddPlayer(player_observer_.get(), player);
FlushForTesting(GetMediaSession());
EXPECT_FALSE(GetMediaSession()->ShouldRouteAction(
MediaSessionAction::kEnterPictureInPicture));
EXPECT_FALSE(base::Contains(observer.actions(),
MediaSessionAction::kEnterAutoPictureInPicture));
GetMediaSession()->EnterAutoPictureInPicture();
EXPECT_EQ(0, player_observer_->received_enter_picture_in_picture_calls());
}
TEST_F(MediaSessionImplTest,
DoesNotEnterBrowserInitiatedAutoPip_PlayerInSubframe) {
// Create a subframe.
auto* parent_host_tester = content::RenderFrameHostTester::For(main_rfh());
parent_host_tester->InitializeRenderFrameIfNeeded();
content::RenderFrameHost* sub_frame =
parent_host_tester->AppendChild("child");
ASSERT_TRUE(sub_frame);
// Create a player observer associated with the subframe.
player_observer_ = std::make_unique<MockMediaSessionPlayerObserver>(
sub_frame, media::MediaContentType::kPersistent);
// Start a playing player with picture-in-picture available.
int player_id = player_observer_->StartNewPlayer(/*is_playing=*/true);
player_observer_->SetIsPictureInPictureAvailable(player_id, true);
GetMediaSession()->AddPlayer(player_observer_.get(), player_id);
// Verify that kEnterAutoPictureInPicture action is not available.
MockMediaSessionMojoObserver observer(*GetMediaSession());
FlushForTesting(GetMediaSession());
EXPECT_FALSE(GetMediaSession()->ShouldRouteAction(
MediaSessionAction::kEnterPictureInPicture));
EXPECT_FALSE(base::Contains(observer.actions(),
MediaSessionAction::kEnterAutoPictureInPicture));
// Attempt to enter automatic picture-in-picture and verify that
// OnEnterPictureInPicture was not called.
GetMediaSession()->EnterAutoPictureInPicture();
EXPECT_EQ(0, player_observer_->received_enter_picture_in_picture_calls());
}
TEST_F(MediaSessionImplTest,
EntersBrowserInitiatedAutoPip_SinglePlayerPlaying) {
MockMediaSessionMojoObserver observer(*GetMediaSession());
FlushForTesting(GetMediaSession());
EXPECT_FALSE(base::Contains(observer.actions(),
MediaSessionAction::kEnterAutoPictureInPicture));
EXPECT_EQ(0, player_observer_->received_enter_picture_in_picture_calls());
int player = player_observer_->StartNewPlayer();
player_observer_->SetIsPictureInPictureAvailable(player, true);
GetMediaSession()->AddPlayer(player_observer_.get(), player);
FlushForTesting(GetMediaSession());
EXPECT_FALSE(GetMediaSession()->ShouldRouteAction(
MediaSessionAction::kEnterPictureInPicture));
EXPECT_TRUE(base::Contains(observer.actions(),
MediaSessionAction::kEnterAutoPictureInPicture));
GetMediaSession()->EnterAutoPictureInPicture();
EXPECT_EQ(1, player_observer_->received_enter_picture_in_picture_calls());
}
TEST_F(MediaSessionImplTest,
DoesNotEnterBrowserInitiatedAutoPip_WhenUsingCamera) {
media_session::test::MockMediaSessionMojoObserver observer(
*GetMediaSession());
FlushForTesting(GetMediaSession());
EXPECT_FALSE(base::Contains(observer.actions(),
MediaSessionAction::kEnterAutoPictureInPicture));
EXPECT_EQ(0, player_observer_->received_enter_picture_in_picture_calls());
int player = player_observer_->StartNewPlayer();
player_observer_->SetIsPictureInPictureAvailable(player, true);
GetMediaSession()->AddPlayer(player_observer_.get(), player);
FlushForTesting(GetMediaSession());
// With no camera/microphone usage, auto-pip should be possible.
EXPECT_TRUE(base::Contains(observer.actions(),
MediaSessionAction::kEnterAutoPictureInPicture));
// Set camera state to `kTurnedOn`.
mock_media_session_service().SetCameraState(
media_session::mojom::CameraState::kTurnedOn);
FlushForTesting(GetMediaSession());
// Auto-pip should not be possible.
EXPECT_FALSE(base::Contains(observer.actions(),
MediaSessionAction::kEnterAutoPictureInPicture));
GetMediaSession()->EnterAutoPictureInPicture();
EXPECT_EQ(0, player_observer_->received_enter_picture_in_picture_calls());
// Set camera state back to Unknown.
mock_media_session_service().SetCameraState(
media_session::mojom::CameraState::kUnknown);
FlushForTesting(GetMediaSession());
// Auto-pip should be possible.
EXPECT_TRUE(base::Contains(observer.actions(),
MediaSessionAction::kEnterAutoPictureInPicture));
GetMediaSession()->EnterAutoPictureInPicture();
EXPECT_EQ(1, player_observer_->received_enter_picture_in_picture_calls());
}
TEST_F(MediaSessionImplTest,
DoesNotEnterBrowserInitiatedAutoPip_WhenUsingMicrophone) {
media_session::test::MockMediaSessionMojoObserver observer(
*GetMediaSession());
FlushForTesting(GetMediaSession());
EXPECT_FALSE(base::Contains(observer.actions(),
MediaSessionAction::kEnterAutoPictureInPicture));
EXPECT_EQ(0, player_observer_->received_enter_picture_in_picture_calls());
int player = player_observer_->StartNewPlayer();
player_observer_->SetIsPictureInPictureAvailable(player, true);
GetMediaSession()->AddPlayer(player_observer_.get(), player);
FlushForTesting(GetMediaSession());
// With no camera/microphone usage, auto-pip should be possible.
EXPECT_TRUE(base::Contains(observer.actions(),
MediaSessionAction::kEnterAutoPictureInPicture));
// Set microphone state to `kUnmuted`.
mock_media_session_service().SetMicrophoneState(
media_session::mojom::MicrophoneState::kUnmuted);
FlushForTesting(GetMediaSession());
// Auto-pip should not be possible.
EXPECT_FALSE(base::Contains(observer.actions(),
MediaSessionAction::kEnterAutoPictureInPicture));
GetMediaSession()->EnterAutoPictureInPicture();
EXPECT_EQ(0, player_observer_->received_enter_picture_in_picture_calls());
// Set microphone state back to `kUnknown`.
mock_media_session_service().SetMicrophoneState(
media_session::mojom::MicrophoneState::kUnknown);
FlushForTesting(GetMediaSession());
// Auto-pip should be possible.
EXPECT_TRUE(base::Contains(observer.actions(),
MediaSessionAction::kEnterAutoPictureInPicture));
GetMediaSession()->EnterAutoPictureInPicture();
EXPECT_EQ(1, player_observer_->received_enter_picture_in_picture_calls());
}
TEST_F(MediaSessionImplTest,
DoesNotEnterBrowserInitiatedAutoPip_WhenUsingCameraAndMicrophone) {
media_session::test::MockMediaSessionMojoObserver observer(
*GetMediaSession());
FlushForTesting(GetMediaSession());
EXPECT_FALSE(base::Contains(observer.actions(),
MediaSessionAction::kEnterAutoPictureInPicture));
EXPECT_EQ(0, player_observer_->received_enter_picture_in_picture_calls());
int player = player_observer_->StartNewPlayer();
player_observer_->SetIsPictureInPictureAvailable(player, true);
GetMediaSession()->AddPlayer(player_observer_.get(), player);
FlushForTesting(GetMediaSession());
// With no camera/microphone usage, auto-pip should be possible.
EXPECT_TRUE(base::Contains(observer.actions(),
MediaSessionAction::kEnterAutoPictureInPicture));
// Set camera and microphone state to `kTurnedOn`/`kUnmuted`.
mock_media_session_service().SetCameraState(
media_session::mojom::CameraState::kTurnedOn);
mock_media_session_service().SetMicrophoneState(
media_session::mojom::MicrophoneState::kUnmuted);
FlushForTesting(GetMediaSession());
// Auto-pip should not be possible.
EXPECT_FALSE(base::Contains(observer.actions(),
MediaSessionAction::kEnterAutoPictureInPicture));
GetMediaSession()->EnterAutoPictureInPicture();
EXPECT_EQ(0, player_observer_->received_enter_picture_in_picture_calls());
// Set camera and microphone state back to `kUnknown`.
mock_media_session_service().SetCameraState(
media_session::mojom::CameraState::kUnknown);
mock_media_session_service().SetMicrophoneState(
media_session::mojom::MicrophoneState::kUnknown);
FlushForTesting(GetMediaSession());
// Auto-pip should be possible.
EXPECT_TRUE(base::Contains(observer.actions(),
MediaSessionAction::kEnterAutoPictureInPicture));
GetMediaSession()->EnterAutoPictureInPicture();
EXPECT_EQ(1, player_observer_->received_enter_picture_in_picture_calls());
}
class MediaSessionImplWithMediaSessionClientTest : public MediaSessionImplTest {
protected:
TestMediaSessionClient client_;
};
TEST_F(MediaSessionImplWithMediaSessionClientTest,
SessionInfoDontHideMetadata) {
client_.SetShouldHideMetadata(false);
EXPECT_FALSE(media_session::test::GetMediaSessionInfoSync(GetMediaSession())
->hide_metadata);
}
TEST_F(MediaSessionImplWithMediaSessionClientTest, SessionInfoHideMetadata) {
client_.SetShouldHideMetadata(true);
EXPECT_TRUE(media_session::test::GetMediaSessionInfoSync(GetMediaSession())
->hide_metadata);
}
// Tests for throttling duration updates.
// TODO (jazzhsu): Remove these tests once media session supports livestream.
class MediaSessionImplDurationThrottleTest : public MediaSessionImplTest {
public:
void SetUp() override {
MediaSessionImplTest::SetUp();
GetMediaSession()->SetShouldThrottleDurationUpdateForTest(true);
}
void FastForwardBy(base::TimeDelta delay) {
task_environment()->FastForwardBy(delay);
}
int GetDurationUpdateMaxAllowance() {
return MediaSessionImpl::kDurationUpdateMaxAllowance;
}
base::TimeDelta GetDurationUpdateAllowanceIncreaseInterval() {
return MediaSessionImpl::kDurationUpdateAllowanceIncreaseInterval;
}
};
TEST_F(MediaSessionImplDurationThrottleTest, ThrottleDurationUpdate) {
MockMediaSessionMojoObserver observer(*GetMediaSession());
int player_id = player_observer_->StartNewPlayer();
GetMediaSession()->AddPlayer(player_observer_.get(), player_id);
media_session::MediaPosition pos;
for (int duration = 0; duration <= GetDurationUpdateMaxAllowance();
++duration) {
pos = media_session::MediaPosition(
/*playback_rate=*/0.0,
/*duration=*/base::Seconds(duration),
/*position=*/base::TimeDelta(), /*end_of_media=*/false);
player_observer_->SetPosition(player_id, pos);
GetMediaSession()->RebuildAndNotifyMediaPositionChanged();
FlushForTesting(GetMediaSession());
if (duration == GetDurationUpdateMaxAllowance()) {
// Last update should be throttle and marked as +INF duration.
EXPECT_EQ(**observer.session_position(),
media_session::MediaPosition(
/*playback_rate=*/0.0,
/*duration=*/base::TimeDelta::Max(),
/*position=*/base::TimeDelta(), /*end_of_media=*/false));
// Since we're now considered live, the seeking and scrubbing actions
// should no longer be available.
EXPECT_FALSE(
base::Contains(observer.actions(), MediaSessionAction::kSeekTo));
EXPECT_FALSE(
base::Contains(observer.actions(), MediaSessionAction::kScrubTo));
EXPECT_FALSE(
base::Contains(observer.actions(), MediaSessionAction::kSeekForward));
EXPECT_FALSE(base::Contains(observer.actions(),
MediaSessionAction::kSeekBackward));
} else {
EXPECT_EQ(**observer.session_position(), pos);
// If we're not considered live, then the seeking and scrubbing actions
// should still be available.
EXPECT_TRUE(
base::Contains(observer.actions(), MediaSessionAction::kSeekTo));
EXPECT_TRUE(
base::Contains(observer.actions(), MediaSessionAction::kScrubTo));
EXPECT_TRUE(
base::Contains(observer.actions(), MediaSessionAction::kSeekForward));
EXPECT_TRUE(base::Contains(observer.actions(),
MediaSessionAction::kSeekBackward));
}
}
// Media session should send last throttled duration update after certain
// delay.
FastForwardBy(GetDurationUpdateAllowanceIncreaseInterval());
FlushForTesting(GetMediaSession());
EXPECT_EQ(**observer.session_position(), pos);
}
TEST_F(MediaSessionImplDurationThrottleTest, ThrottleResetOnPlayerChange) {
MockMediaSessionMojoObserver observer(*GetMediaSession());
// Duration updates caused by player switch should not be throttled.
media_session::MediaPosition pos;
for (int duration = 0; duration <= GetDurationUpdateMaxAllowance();
++duration) {
int player_id = player_observer_->StartNewPlayer();
GetMediaSession()->AddPlayer(player_observer_.get(), player_id);
pos = media_session::MediaPosition(
/*playback_rate=*/0.0,
/*duration=*/base::Seconds(duration),
/*position=*/base::TimeDelta(), /*end_of_media=*/false);
player_observer_->SetPosition(player_id, pos);
GetMediaSession()->RebuildAndNotifyMediaPositionChanged();
FlushForTesting(GetMediaSession());
EXPECT_EQ(**observer.session_position(), pos);
GetMediaSession()->RemovePlayer(player_observer_.get(), player_id);
}
}
} // namespace content