blob: 2db7e2e1da9bf7790aa8ef6b8a64f3294a900a2c [file] [log] [blame]
// Copyright 2016 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_controller.h"
#include <memory>
#include <tuple>
#include "base/memory/raw_ptr.h"
#include "base/time/time.h"
#include "content/browser/media/media_web_contents_observer.h"
#include "content/browser/media/session/audio_focus_delegate.h"
#include "content/browser/media/session/media_session_impl.h"
#include "content/public/browser/media_device_id.h"
#include "content/test/mock_agent_scheduling_group_host.h"
#include "content/test/test_render_view_host.h"
#include "content/test/test_web_contents.h"
#include "media/audio/audio_device_description.h"
#include "media/base/picture_in_picture_events_info.h"
#include "media/mojo/mojom/media_player.mojom.h"
#include "mojo/public/cpp/bindings/associated_receiver.h"
#include "mojo/public/cpp/bindings/associated_remote.h"
#include "mojo/public/cpp/bindings/pending_associated_remote.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace content {
class FakeAudioFocusDelegate : public content::AudioFocusDelegate {
public:
void set_audio_focus_result(AudioFocusResult result) {
audio_focus_result_ = result;
}
// content::AudioFocusDelegate:
AudioFocusResult RequestAudioFocus(
media_session::mojom::AudioFocusType audio_focus_type) override {
audio_focus_type_ = audio_focus_type;
return audio_focus_result_;
}
void AbandonAudioFocus() override { audio_focus_type_.reset(); }
std::optional<media_session::mojom::AudioFocusType> GetCurrentFocusType()
const override {
return audio_focus_type_;
}
void MediaSessionInfoChanged(
const media_session::mojom::MediaSessionInfoPtr&) override {}
const base::UnguessableToken& request_id() const override {
return base::UnguessableToken::Null();
}
void ReleaseRequestId() override {}
private:
std::optional<media_session::mojom::AudioFocusType> audio_focus_type_;
AudioFocusResult audio_focus_result_ = AudioFocusResult::kSuccess;
};
// Helper class that provides an implementation of the media::mojom::MediaPlayer
// mojo interface to allow checking that messages sent over mojo are received
// with the right values in the other end.
//
// Note this relies on MediaSessionController::BindMediaPlayer() to provide the
// MediaSessionController instance owned by the test with a valid mojo remote,
// that will be bound to the mojo receiver provided by this class instead of the
// real one used in production which would be owned by HTMLMediaElement instead.
class TestMediaPlayer : public media::mojom::MediaPlayer {
public:
enum class PauseRequestType {
kNone,
kTriggeredByUser,
kNotTriggeredByUser,
};
TestMediaPlayer(MediaWebContentsObserver* media_web_contents_observer,
const MediaPlayerId& player_id) {
// Bind the remote to the receiver, so that we can intercept incoming
// MediaPlayer messages sent via the different methods that use the remote.
// MediaPlayerObserver messages will not be used.
mojo::AssociatedRemote<media::mojom::MediaPlayerHost> player_host;
media_web_contents_observer->BindMediaPlayerHost(
player_id.frame_routing_id,
player_host.BindNewEndpointAndPassDedicatedReceiver());
mojo::PendingAssociatedRemote<media::mojom::MediaPlayer> player;
receiver_.Bind(player.InitWithNewEndpointAndPassReceiver());
mojo::PendingAssociatedRemote<media::mojom::MediaPlayerObserver>
dummy_player_observer;
player_host->OnMediaPlayerAdded(
std::move(player),
dummy_player_observer.InitWithNewEndpointAndPassReceiver(),
player_id.player_id);
player_host.FlushForTesting();
}
// Needs to be called from tests after invoking a method related playback
// from the MediaPlayer mojo interface, so that we have enough time to
// process the message.
void WaitUntilReceivedMessage() {
run_loop_ = std::make_unique<base::RunLoop>();
run_loop_->Run();
run_loop_.reset();
}
// Needs to be called from tests after invoking SetVolumeMultiplier method
// from the MediaPlayer mojo interface, so that we have enough time to
// process the message.
void WaitUntilVolumeChanged() {
run_loop_for_volume_ = std::make_unique<base::RunLoop>();
run_loop_for_volume_->Run();
run_loop_for_volume_.reset();
}
// media::mojom::MediaPlayer implementation.
void RequestPlay() override {
received_play_ = true;
run_loop_->Quit();
}
void RequestPause(bool triggered_by_user) override {
received_pause_type_ = triggered_by_user
? PauseRequestType::kTriggeredByUser
: PauseRequestType::kNotTriggeredByUser;
run_loop_->Quit();
}
void RequestSeekForward(base::TimeDelta seek_time) override {
received_seek_forward_time_ = seek_time;
run_loop_->Quit();
}
void RequestSeekBackward(base::TimeDelta seek_time) override {
received_seek_backward_time_ = seek_time;
run_loop_->Quit();
}
void RequestSeekTo(base::TimeDelta seek_time) override {
received_seek_to_time_ = seek_time;
run_loop_->Quit();
}
void RequestEnterPictureInPicture() override {}
void RequestMute(bool mute) override {}
void SetVolumeMultiplier(double multiplier) override {
received_volume_multiplier_ = multiplier;
if (run_loop_for_volume_)
run_loop_for_volume_->Quit();
}
void SetPersistentState(bool persistent) override {}
void SetPowerExperimentState(bool enabled) override {}
void SetAudioSinkId(const std::string& sink_id) override {
received_set_audio_sink_id_ = sink_id;
run_loop_->Quit();
}
void SuspendForFrameClosed() override {}
void RequestMediaRemoting() override {}
void RequestVisibility(
RequestVisibilityCallback request_visibility_callback) override {
std::move(request_visibility_callback).Run(expected_visibility_);
if (run_loop_) {
run_loop_->Quit();
}
}
// Helper method to set expected video visibility, which is later used as an
// argument for the `RequestVisibility` method callback
// (`RequestVisibilityCallback`).
void SetExpectedVisibility(bool expected_visibility) {
expected_visibility_ = expected_visibility;
}
void RecordAutoPictureInPictureInfo(
const media::PictureInPictureEventsInfo::AutoPipInfo&
auto_picture_in_picture_info) override {
auto_picture_in_picture_info_ = auto_picture_in_picture_info;
run_loop_->Quit();
}
// Getters used from MediaSessionControllerTest.
bool received_play() const { return received_play_; }
PauseRequestType received_pause() const { return received_pause_type_; }
const base::TimeDelta& received_seek_forward_time() const {
return received_seek_forward_time_;
}
const base::TimeDelta& received_seek_backward_time() const {
return received_seek_backward_time_;
}
const base::TimeDelta& received_seek_to_time() const {
return received_seek_to_time_;
}
double received_volume_multiplier() const {
return received_volume_multiplier_;
}
const std::string& received_set_audio_sink_id() const {
return received_set_audio_sink_id_;
}
const media::PictureInPictureEventsInfo::AutoPipInfo&
received_auto_picture_in_picture_info() const {
return auto_picture_in_picture_info_;
}
private:
std::unique_ptr<base::RunLoop> run_loop_;
std::unique_ptr<base::RunLoop> run_loop_for_volume_;
mojo::AssociatedReceiver<media::mojom::MediaPlayer> receiver_{this};
bool received_play_{false};
double received_volume_multiplier_{0};
PauseRequestType received_pause_type_{PauseRequestType::kNone};
base::TimeDelta received_seek_forward_time_;
base::TimeDelta received_seek_backward_time_;
base::TimeDelta received_seek_to_time_;
std::string received_set_audio_sink_id_;
bool expected_visibility_ = false;
media::PictureInPictureEventsInfo::AutoPipInfo auto_picture_in_picture_info_;
};
// Helper class to mock `RequestVisibility` callbacks.
class RequestVisibilityWaiter {
public:
RequestVisibilityWaiter() = default;
RequestVisibilityWaiter(const RequestVisibilityWaiter&) = delete;
RequestVisibilityWaiter(RequestVisibilityWaiter&&) = delete;
RequestVisibilityWaiter& operator=(const RequestVisibilityWaiter&) = delete;
TestMediaPlayer::RequestVisibilityCallback VisibilityCallback() {
meets_visibility_ = std::nullopt;
// base::Unretained() is safe since no further tasks can run after
// RunLoop::Run() returns.
return base::BindOnce(&RequestVisibilityWaiter::RequestVisibility,
base::Unretained(this));
}
void WaitUntilDone() {
if (meets_visibility_) {
return;
}
run_loop_ = std::make_unique<base::RunLoop>();
run_loop_->Run();
}
bool MeetsVisibility() {
DCHECK(meets_visibility_);
return meets_visibility_.value();
}
private:
void RequestVisibility(bool meets_visibility) {
meets_visibility_ = meets_visibility;
if (run_loop_) {
run_loop_->Quit();
}
}
std::unique_ptr<base::RunLoop> run_loop_;
std::optional<bool> meets_visibility_;
};
class MediaSessionControllerTest : public RenderViewHostImplTestHarness {
public:
void SetUp() override {
RenderViewHostImplTestHarness::SetUp();
id_ = MediaPlayerId(contents()->GetPrimaryMainFrame()->GetGlobalId(), 0);
controller_ = CreateController();
media_player_ = CreateMediaPlayer(controller_.get());
auto delegate = std::make_unique<FakeAudioFocusDelegate>();
audio_focus_delegate_ = delegate.get();
media_session()->SetDelegateForTests(std::move(delegate));
}
void TearDown() override {
audio_focus_delegate_ = nullptr;
// Destruct the controller prior to any other teardown to avoid out of order
// destruction relative to the MediaSession instance.
controller_.reset();
media_player_.reset();
RenderViewHostImplTestHarness::TearDown();
}
protected:
std::unique_ptr<MediaSessionController> CreateController() {
return std::make_unique<MediaSessionController>(id_, contents());
}
std::unique_ptr<TestMediaPlayer> CreateMediaPlayer(
MediaSessionController* controller) {
MediaWebContentsObserver* media_web_contents_observer =
contents()->media_web_contents_observer();
DCHECK(media_web_contents_observer);
return std::make_unique<TestMediaPlayer>(media_web_contents_observer, id_);
}
MediaSessionImpl* media_session() {
return MediaSessionImpl::Get(contents());
}
void Suspend() {
controller_->OnSuspend(controller_->get_player_id_for_testing());
media_player_->WaitUntilReceivedMessage();
}
void Resume() {
controller_->OnResume(controller_->get_player_id_for_testing());
media_player_->WaitUntilReceivedMessage();
}
void SeekForward(base::TimeDelta seek_time) {
controller_->OnSeekForward(controller_->get_player_id_for_testing(),
seek_time);
media_player_->WaitUntilReceivedMessage();
}
void SeekBackward(base::TimeDelta seek_time) {
controller_->OnSeekBackward(controller_->get_player_id_for_testing(),
seek_time);
media_player_->WaitUntilReceivedMessage();
}
void SeekTo(base::TimeDelta seek_time) {
controller_->OnSeekTo(controller_->get_player_id_for_testing(), seek_time);
media_player_->WaitUntilReceivedMessage();
}
void SetVolumeMultiplier(double multiplier) {
controller_->OnSetVolumeMultiplier(controller_->get_player_id_for_testing(),
multiplier);
media_player_->WaitUntilVolumeChanged();
}
void RequestVisibility(
TestMediaPlayer::RequestVisibilityCallback request_visibility_callback) {
controller_->OnRequestVisibility(controller_->get_player_id_for_testing(),
std::move(request_visibility_callback));
media_player_->WaitUntilReceivedMessage();
}
// Helpers to check the results of using the basic controls.
bool ReceivedMessagePlay() { return media_player_->received_play(); }
bool ReceivedMessagePause(bool triggered_by_user) {
TestMediaPlayer::PauseRequestType expected_pause_request =
triggered_by_user
? TestMediaPlayer::PauseRequestType::kTriggeredByUser
: TestMediaPlayer::PauseRequestType::kNotTriggeredByUser;
return media_player_->received_pause() == expected_pause_request;
}
bool ReceivedMessageSeekForward(base::TimeDelta expected_seek_time) {
return expected_seek_time == media_player_->received_seek_forward_time();
}
bool ReceivedMessageSeekBackward(base::TimeDelta expected_seek_time) {
return expected_seek_time == media_player_->received_seek_backward_time();
}
bool ReceivedMessageSeekTo(base::TimeDelta expected_seek_time) {
return expected_seek_time == media_player_->received_seek_to_time();
}
bool ReceivedMessageVolume(double expected_volume_multiplier) {
return expected_volume_multiplier ==
media_player_->received_volume_multiplier();
}
// Helper method to set the expected visibility that will be used by the
// `RequestVisibilityCallback`.
void SetExpectedVisibility(bool expected_visibility) {
media_player_->SetExpectedVisibility(expected_visibility);
}
MediaPlayerId id_ = MediaPlayerId::CreateMediaPlayerIdForTests();
std::unique_ptr<MediaSessionController> controller_;
std::unique_ptr<TestMediaPlayer> media_player_;
raw_ptr<FakeAudioFocusDelegate> audio_focus_delegate_ = nullptr;
};
TEST_F(MediaSessionControllerTest, NoAudioNoSession) {
controller_->SetMetadata(false, true, media::MediaContentType::kPersistent);
ASSERT_TRUE(controller_->OnPlaybackStarted());
EXPECT_FALSE(media_session()->IsActive());
EXPECT_FALSE(media_session()->IsControllable());
}
TEST_F(MediaSessionControllerTest, TransientNoControllableSession) {
controller_->SetMetadata(true, false, media::MediaContentType::kTransient);
ASSERT_TRUE(controller_->OnPlaybackStarted());
EXPECT_TRUE(media_session()->IsActive());
EXPECT_FALSE(media_session()->IsControllable());
}
TEST_F(MediaSessionControllerTest, BasicControls) {
controller_->SetMetadata(true, false, media::MediaContentType::kPersistent);
ASSERT_TRUE(controller_->OnPlaybackStarted());
EXPECT_TRUE(media_session()->IsActive());
EXPECT_TRUE(media_session()->IsControllable());
// Verify suspend notifies the renderer and maintains its session.
Suspend();
EXPECT_TRUE(ReceivedMessagePause(/*triggered_by_user=*/true));
// Likewise verify the resume behavior.
Resume();
EXPECT_TRUE(ReceivedMessagePlay());
// ...as well as the seek behavior.
const base::TimeDelta kTestSeekForwardTime = base::Seconds(1);
SeekForward(kTestSeekForwardTime);
EXPECT_TRUE(ReceivedMessageSeekForward(kTestSeekForwardTime));
const base::TimeDelta kTestSeekBackwardTime = base::Seconds(2);
SeekBackward(kTestSeekBackwardTime);
EXPECT_TRUE(ReceivedMessageSeekBackward(kTestSeekBackwardTime));
const base::TimeDelta kTestSeekToTime = base::Seconds(3);
SeekTo(kTestSeekToTime);
EXPECT_TRUE(ReceivedMessageSeekTo(kTestSeekToTime));
// Verify destruction of the controller removes its session.
controller_.reset();
EXPECT_FALSE(media_session()->IsActive());
EXPECT_FALSE(media_session()->IsControllable());
}
TEST_F(MediaSessionControllerTest, VolumeMultiplier) {
controller_->SetMetadata(true, false, media::MediaContentType::kPersistent);
ASSERT_TRUE(controller_->OnPlaybackStarted());
EXPECT_TRUE(media_session()->IsActive());
EXPECT_TRUE(media_session()->IsControllable());
// Upon creation of the MediaSession the default multiplier will be sent.
media_player_->WaitUntilVolumeChanged();
EXPECT_TRUE(ReceivedMessageVolume(1.0));
// Verify a different volume multiplier is sent.
const double kTestMultiplier = 0.5;
SetVolumeMultiplier(kTestMultiplier);
EXPECT_TRUE(ReceivedMessageVolume(kTestMultiplier));
}
TEST_F(MediaSessionControllerTest, ControllerSidePause) {
controller_->SetMetadata(true, false, media::MediaContentType::kPersistent);
ASSERT_TRUE(controller_->OnPlaybackStarted());
EXPECT_TRUE(media_session()->IsActive());
EXPECT_TRUE(media_session()->IsControllable());
// Verify pause behavior.
controller_->OnPlaybackPaused(false);
EXPECT_FALSE(media_session()->IsActive());
EXPECT_TRUE(media_session()->IsControllable());
// Verify the next OnPlaybackStarted() call restores the session.
ASSERT_TRUE(controller_->OnPlaybackStarted());
EXPECT_TRUE(media_session()->IsActive());
EXPECT_TRUE(media_session()->IsControllable());
}
TEST_F(MediaSessionControllerTest, Reinitialize) {
controller_->SetMetadata(false, true, media::MediaContentType::kPersistent);
ASSERT_TRUE(controller_->OnPlaybackStarted());
EXPECT_FALSE(media_session()->IsActive());
EXPECT_FALSE(media_session()->IsControllable());
// Create a transient type session.
controller_->SetMetadata(true, false, media::MediaContentType::kTransient);
EXPECT_TRUE(media_session()->IsActive());
EXPECT_FALSE(media_session()->IsControllable());
const int current_player_id = controller_->get_player_id_for_testing();
// Reinitialize the session as a content type.
controller_->SetMetadata(true, false, media::MediaContentType::kPersistent);
EXPECT_TRUE(media_session()->IsActive());
EXPECT_TRUE(media_session()->IsControllable());
// Player id should not change when there's an active session.
EXPECT_EQ(current_player_id, controller_->get_player_id_for_testing());
// Verify suspend notifies the renderer and maintains its session.
Suspend();
EXPECT_TRUE(ReceivedMessagePause(/*triggered_by_user=*/true));
// Likewise verify the resume behavior.
Resume();
EXPECT_TRUE(ReceivedMessagePlay());
}
TEST_F(MediaSessionControllerTest, PositionState) {
media_session::MediaPosition expected_position(
/*playback_rate=*/0.0, /*duration=*/base::Seconds(10),
/*position=*/base::TimeDelta(), /*end_of_media=*/false);
controller_->OnMediaPositionStateChanged(expected_position);
EXPECT_EQ(expected_position,
controller_->GetPosition(controller_->get_player_id_for_testing()));
}
TEST_F(MediaSessionControllerTest, RemovePlayerIfSessionReset) {
controller_->SetMetadata(true, false, media::MediaContentType::kPersistent);
ASSERT_TRUE(controller_->OnPlaybackStarted());
EXPECT_TRUE(media_session()->IsActive());
controller_.reset();
EXPECT_FALSE(media_session()->IsActive());
}
TEST_F(MediaSessionControllerTest, PictureInPictureAvailability) {
EXPECT_FALSE(controller_->IsPictureInPictureAvailable(
controller_->get_player_id_for_testing()));
controller_->OnPictureInPictureAvailabilityChanged(true);
EXPECT_TRUE(controller_->IsPictureInPictureAvailable(
controller_->get_player_id_for_testing()));
}
TEST_F(MediaSessionControllerTest, SufficientlyVisibleVideo) {
EXPECT_FALSE(controller_->HasSufficientlyVisibleVideo(
controller_->get_player_id_for_testing()));
controller_->OnVideoVisibilityChanged(true);
EXPECT_TRUE(controller_->HasSufficientlyVisibleVideo(
controller_->get_player_id_for_testing()));
}
TEST_F(MediaSessionControllerTest, RequestVisibility) {
media_player_->SetExpectedVisibility(true);
RequestVisibilityWaiter request_visibility_waiter;
RequestVisibility(request_visibility_waiter.VisibilityCallback());
request_visibility_waiter.WaitUntilDone();
EXPECT_TRUE(request_visibility_waiter.MeetsVisibility());
media_player_->SetExpectedVisibility(false);
RequestVisibility(request_visibility_waiter.VisibilityCallback());
request_visibility_waiter.WaitUntilDone();
EXPECT_FALSE(request_visibility_waiter.MeetsVisibility());
}
TEST_F(MediaSessionControllerTest, AudioOutputSinkIdChange) {
EXPECT_EQ(controller_->GetAudioOutputSinkId(
controller_->get_player_id_for_testing()),
media::AudioDeviceDescription::kDefaultDeviceId);
controller_->OnAudioOutputSinkChanged("1");
EXPECT_EQ(controller_->GetAudioOutputSinkId(
controller_->get_player_id_for_testing()),
"1");
}
TEST_F(MediaSessionControllerTest, AddPlayerWhenUnmuted) {
contents()->SetAudioMuted(true);
controller_->SetMetadata(
/* has_audio = */ true, /* has_video = */ false,
media::MediaContentType::kPersistent);
ASSERT_TRUE(controller_->OnPlaybackStarted());
ASSERT_FALSE(media_session()->IsActive());
contents()->SetAudioMuted(false);
controller_->WebContentsMutedStateChanged(false);
EXPECT_TRUE(media_session()->IsActive());
}
TEST_F(MediaSessionControllerTest, RemovePlayerWhenMuted) {
controller_->SetMetadata(
/* has_audio = */ true, /* has_video = */ false,
media::MediaContentType::kPersistent);
ASSERT_TRUE(controller_->OnPlaybackStarted());
ASSERT_TRUE(media_session()->IsActive());
contents()->SetAudioMuted(true);
controller_->WebContentsMutedStateChanged(true);
EXPECT_FALSE(media_session()->IsActive());
}
TEST_F(MediaSessionControllerTest, EnterLeavePictureInPictureMuted) {
contents()->SetAudioMuted(true);
controller_->SetMetadata(
/* has_audio = */ true, /* has_video = */ false,
media::MediaContentType::kPersistent);
ASSERT_TRUE(controller_->OnPlaybackStarted());
ASSERT_FALSE(media_session()->IsActive());
// Entering PictureInPicture means the user expects to control the media, so
// this should activate the session.
contents()->SetHasPictureInPictureVideo(true);
controller_->PictureInPictureStateChanged(true);
EXPECT_TRUE(media_session()->IsActive());
contents()->SetHasPictureInPictureVideo(false);
controller_->PictureInPictureStateChanged(false);
EXPECT_FALSE(media_session()->IsActive());
}
TEST_F(MediaSessionControllerTest, MuteWithPictureInPicture) {
controller_->SetMetadata(
/* has_audio = */ true, /* has_video = */ false,
media::MediaContentType::kPersistent);
ASSERT_TRUE(controller_->OnPlaybackStarted());
contents()->SetHasPictureInPictureVideo(true);
controller_->PictureInPictureStateChanged(true);
ASSERT_TRUE(media_session()->IsActive());
contents()->SetAudioMuted(true);
controller_->WebContentsMutedStateChanged(true);
EXPECT_TRUE(media_session()->IsActive());
}
TEST_F(MediaSessionControllerTest, LeavePictureInPictureUnmuted) {
contents()->SetAudioMuted(true);
controller_->SetMetadata(
/* has_audio = */ true, /* has_video = */ false,
media::MediaContentType::kPersistent);
ASSERT_TRUE(controller_->OnPlaybackStarted());
ASSERT_FALSE(media_session()->IsActive());
contents()->SetAudioMuted(false);
controller_->WebContentsMutedStateChanged(false);
contents()->SetHasPictureInPictureVideo(true);
controller_->PictureInPictureStateChanged(true);
// Media was unmuted, so we now have audio focus, which should keep the
// session active.
contents()->SetHasPictureInPictureVideo(false);
controller_->PictureInPictureStateChanged(false);
EXPECT_TRUE(media_session()->IsActive());
}
TEST_F(MediaSessionControllerTest, AddPlayerWhenAddingAudio) {
controller_->SetMetadata(
/* has_audio = */ false, /* has_video = */ true,
media::MediaContentType::kPersistent);
ASSERT_TRUE(controller_->OnPlaybackStarted());
ASSERT_FALSE(media_session()->IsActive());
controller_->SetMetadata(
/* has_audio = */ true, /* has_video = */ false,
media::MediaContentType::kPersistent);
EXPECT_TRUE(media_session()->IsActive());
}
TEST_F(MediaSessionControllerTest,
AddPlayerWhenEnteringPictureInPictureWithNoAudio) {
controller_->SetMetadata(
/* has_audio = */ false, /* has_video = */ true,
media::MediaContentType::kPersistent);
ASSERT_TRUE(controller_->OnPlaybackStarted());
ASSERT_FALSE(media_session()->IsActive());
contents()->SetHasPictureInPictureVideo(true);
controller_->PictureInPictureStateChanged(true);
EXPECT_TRUE(media_session()->IsActive());
}
TEST_F(MediaSessionControllerTest,
AddPlayerWhenEnteringPictureInPicturePaused) {
controller_->SetMetadata(
/*has_audio=*/false, /*has_video=*/true,
media::MediaContentType::kPersistent);
ASSERT_TRUE(controller_->OnPlaybackStarted());
controller_->OnPlaybackPaused(/*reached_end_of_stream=*/false);
ASSERT_FALSE(media_session()->IsActive());
contents()->SetHasPictureInPictureVideo(true);
controller_->PictureInPictureStateChanged(true);
EXPECT_FALSE(media_session()->IsActive());
}
TEST_F(MediaSessionControllerTest,
AddPlayerInitiallyPictureInPictureWithNoAudio) {
contents()->SetHasPictureInPictureVideo(true);
controller_->SetMetadata(
/* has_audio = */ false, /* has_video = */ true,
media::MediaContentType::kPersistent);
ASSERT_TRUE(controller_->OnPlaybackStarted());
EXPECT_TRUE(media_session()->IsActive());
contents()->SetHasPictureInPictureVideo(false);
controller_->PictureInPictureStateChanged(false);
EXPECT_FALSE(media_session()->IsActive());
}
TEST_F(MediaSessionControllerTest,
AddPlayerWhenStartingRemotePlaybackWithNoAudio) {
controller_->SetMetadata(
/* has_audio */ false, /* has_video */ true,
media::MediaContentType::kPersistent);
ASSERT_TRUE(controller_->OnPlaybackStarted());
ASSERT_FALSE(media_session()->IsActive());
controller_->OnRemotePlaybackMetadataChanged(
media_session::mojom::RemotePlaybackMetadata::New(
"video_codec", "audio_codec",
/* is_remote_playback_disabled */ false,
/* is_remote_rendering */ true, "device_friendly_name",
/* is_encrypted_media */ false));
EXPECT_TRUE(media_session()->IsActive());
}
TEST_F(MediaSessionControllerTest, EndOfPlaybackWithInPictureInPicture) {
contents()->SetHasPictureInPictureVideo(true);
controller_->PictureInPictureStateChanged(true);
controller_->SetMetadata(
/*has_audio=*/true, /*has_video=*/false,
media::MediaContentType::kPersistent);
ASSERT_TRUE(controller_->OnPlaybackStarted());
EXPECT_TRUE(media_session()->IsActive());
EXPECT_TRUE(media_session()->IsControllable());
// Keeping the PiP window open should keep the session controllable.
controller_->OnPlaybackPaused(/*reached_end_of_stream=*/true);
EXPECT_FALSE(media_session()->IsActive());
EXPECT_TRUE(media_session()->IsControllable());
contents()->SetHasPictureInPictureVideo(false);
controller_->PictureInPictureStateChanged(false);
EXPECT_FALSE(media_session()->IsActive());
EXPECT_FALSE(media_session()->IsControllable());
// Re-opening the PiP window makes the session controllable again.
contents()->SetHasPictureInPictureVideo(true);
controller_->PictureInPictureStateChanged(true);
EXPECT_FALSE(media_session()->IsActive());
EXPECT_TRUE(media_session()->IsControllable());
}
TEST_F(MediaSessionControllerTest, HasVideo_True) {
controller_->SetMetadata(
/* has_audio = */ true, /* has_video = */ true,
media::MediaContentType::kPersistent);
EXPECT_TRUE(controller_->HasVideo(controller_->get_player_id_for_testing()));
}
TEST_F(MediaSessionControllerTest, HasVideo_False) {
controller_->SetMetadata(
/* has_audio = */ true, /* has_video = */ false,
media::MediaContentType::kPersistent);
EXPECT_FALSE(controller_->HasVideo(controller_->get_player_id_for_testing()));
}
TEST_F(MediaSessionControllerTest, AudioFocusRequestFailure) {
// Start playback with the audio track only.
controller_->SetMetadata(
/* has_audio = */ true, /* has_video = */ false,
media::MediaContentType::kPersistent);
ASSERT_TRUE(controller_->OnPlaybackStarted());
ASSERT_TRUE(media_session()->IsActive());
// Add a video track while audio focus cannot be obtained.
audio_focus_delegate_->set_audio_focus_result(
AudioFocusDelegate::AudioFocusResult::kFailed);
media_session()->Suspend(MediaSession::SuspendType::kSystem);
controller_->SetMetadata(
/* has_audio = */ true, /* has_video = */ true,
media::MediaContentType::kPersistent);
EXPECT_FALSE(media_session()->IsActive());
// Have a one-shot player re-activate the session, then discard it.
audio_focus_delegate_->set_audio_focus_result(
AudioFocusDelegate::AudioFocusResult::kSuccess);
auto transient_controller = CreateController();
transient_controller->SetMetadata(
/* has_audio = */ true, /* has_video = */ true,
media::MediaContentType::kOneShot);
ASSERT_TRUE(transient_controller->OnPlaybackStarted());
EXPECT_TRUE(media_session()->IsActive());
transient_controller->OnPlaybackPaused(false);
// Activate the first player.
controller_->SetMetadata(
/* has_audio = */ true, /* has_video = */ true,
media::MediaContentType::kPersistent);
EXPECT_TRUE(media_session()->IsActive());
// Remove the controller's session and make sure position updates are simply
// ignored (no active player).
controller_.reset();
EXPECT_FALSE(media_session()->IsActive());
media_session()->RebuildAndNotifyMediaPositionChanged();
}
TEST_F(MediaSessionControllerTest, SetAudioSinkId) {
// No sink ID has been set.
EXPECT_TRUE(media_player_->received_set_audio_sink_id().empty());
// Set a non-default device ID.
const std::string new_sink_id = "new sink id";
controller_->OnSetAudioSinkId(controller_->get_player_id_for_testing(),
new_sink_id);
media_player_->WaitUntilReceivedMessage();
// The media player receives a hashed version of `new_sink_id`, which must
// follow a specific format.
EXPECT_FALSE(media_player_->received_set_audio_sink_id().empty());
EXPECT_TRUE(IsValidDeviceId(media_player_->received_set_audio_sink_id()));
EXPECT_NE(media_player_->received_set_audio_sink_id(),
media::AudioDeviceDescription::kDefaultDeviceId);
// Set the default device ID.
controller_->OnSetAudioSinkId(
controller_->get_player_id_for_testing(),
media::AudioDeviceDescription::kDefaultDeviceId);
media_player_->WaitUntilReceivedMessage();
// The hashed version of the default device ID equals the unhashed version.
EXPECT_EQ(media_player_->received_set_audio_sink_id(),
media::AudioDeviceDescription::kDefaultDeviceId);
}
TEST_F(MediaSessionControllerTest, AutoPictureInPictureInfoChanged) {
auto received_info = media_player_->received_auto_picture_in_picture_info();
EXPECT_EQ(received_info.auto_pip_reason,
media::PictureInPictureEventsInfo::AutoPipReason::kUnknown);
EXPECT_FALSE(received_info.has_audio_focus);
EXPECT_FALSE(received_info.is_playing);
EXPECT_FALSE(received_info.was_recently_audible);
EXPECT_FALSE(received_info.has_safe_url);
EXPECT_FALSE(received_info.meets_media_engagement_conditions);
EXPECT_FALSE(received_info.blocked_due_to_content_setting);
const media::PictureInPictureEventsInfo::AutoPipInfo
auto_picture_in_picture_info{
.auto_pip_reason =
media::PictureInPictureEventsInfo::AutoPipReason::kMediaPlayback,
.has_audio_focus = true,
.is_playing = true,
.was_recently_audible = true,
.has_safe_url = true,
.meets_media_engagement_conditions = true,
.blocked_due_to_content_setting = true,
};
controller_->OnAutoPictureInPictureInfoChanged(
controller_->get_player_id_for_testing(), auto_picture_in_picture_info);
media_player_->WaitUntilReceivedMessage();
received_info = media_player_->received_auto_picture_in_picture_info();
EXPECT_EQ(received_info.auto_pip_reason,
auto_picture_in_picture_info.auto_pip_reason);
EXPECT_EQ(received_info.has_audio_focus,
auto_picture_in_picture_info.has_audio_focus);
EXPECT_EQ(received_info.is_playing, auto_picture_in_picture_info.is_playing);
EXPECT_EQ(received_info.was_recently_audible,
auto_picture_in_picture_info.was_recently_audible);
EXPECT_EQ(received_info.has_safe_url,
auto_picture_in_picture_info.has_safe_url);
EXPECT_EQ(received_info.meets_media_engagement_conditions,
auto_picture_in_picture_info.meets_media_engagement_conditions);
EXPECT_EQ(received_info.blocked_due_to_content_setting,
auto_picture_in_picture_info.blocked_due_to_content_setting);
}
} // namespace content