blob: 546ce5490b9aead37f67e659af833d519de4bcae [file] [log] [blame]
// Copyright 2016 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/media/session/media_session_impl.h"
#include <map>
#include <memory>
#include "base/run_loop.h"
#include "base/strings/utf_string_conversions.h"
#include "base/time/time.h"
#include "content/browser/media/session/media_session_player_observer.h"
#include "content/browser/media/session/media_session_service_impl.h"
#include "content/browser/media/session/mock_media_session_observer.h"
#include "content/test/test_render_view_host.h"
#include "content/test/test_web_contents.h"
#include "media/base/media_content_type.h"
#include "services/media_session/public/cpp/media_metadata.h"
#include "services/media_session/public/cpp/test/mock_media_session.h"
#include "services/media_session/public/mojom/constants.mojom.h"
#include "third_party/blink/public/platform/modules/mediasession/media_session.mojom.h"
using ::testing::_;
using ::testing::AnyNumber;
using ::testing::Eq;
using ::testing::InvokeWithoutArgs;
using ::testing::NiceMock;
namespace content {
namespace {
constexpr base::TimeDelta kDefaultSeekTime =
base::TimeDelta::FromSeconds(media_session::mojom::kDefaultSeekTimeSeconds);
static const int kPlayerId = 0;
class MockMediaSessionServiceImpl : public MediaSessionServiceImpl {
public:
explicit MockMediaSessionServiceImpl(RenderFrameHost* rfh)
: MediaSessionServiceImpl(rfh) {}
~MockMediaSessionServiceImpl() override = default;
};
class MockMediaSessionClient : public blink::mojom::MediaSessionClient {
public:
MockMediaSessionClient() : binding_(this) {}
blink::mojom::MediaSessionClientPtr CreateInterfacePtrAndBind() {
blink::mojom::MediaSessionClientPtr client;
binding_.Bind(mojo::MakeRequest(&client));
return client;
}
MOCK_METHOD1(DidReceiveAction,
void(media_session::mojom::MediaSessionAction action));
private:
mojo::Binding<blink::mojom::MediaSessionClient> binding_;
DISALLOW_COPY_AND_ASSIGN(MockMediaSessionClient);
};
class MockMediaSessionPlayerObserver : public MediaSessionPlayerObserver {
public:
explicit MockMediaSessionPlayerObserver(RenderFrameHost* rfh)
: render_frame_host_(rfh) {}
~MockMediaSessionPlayerObserver() override = default;
MOCK_METHOD1(OnSuspend, void(int player_id));
MOCK_METHOD1(OnResume, void(int player_id));
MOCK_METHOD2(OnSeekForward, void(int player_id, base::TimeDelta seek_time));
MOCK_METHOD2(OnSeekBackward, void(int player_id, base::TimeDelta seek_time));
MOCK_METHOD2(OnSetVolumeMultiplier,
void(int player_id, double volume_multiplier));
RenderFrameHost* render_frame_host() const override {
return render_frame_host_;
}
private:
RenderFrameHost* render_frame_host_;
};
} // anonymous namespace
class MediaSessionImplServiceRoutingTest
: public RenderViewHostImplTestHarness {
public:
MediaSessionImplServiceRoutingTest() = default;
~MediaSessionImplServiceRoutingTest() override = default;
void SetUp() override {
RenderViewHostImplTestHarness::SetUp();
contents()->GetMainFrame()->InitializeRenderFrameIfNeeded();
mock_media_session_observer_.reset(new NiceMock<MockMediaSessionObserver>(
MediaSessionImpl::Get(contents())));
main_frame_ = contents()->GetMainFrame();
sub_frame_ = main_frame_->AppendChild("sub_frame");
}
void TearDown() override {
mock_media_session_observer_.reset();
services_.clear();
clients_.clear();
RenderViewHostImplTestHarness::TearDown();
}
protected:
MockMediaSessionObserver* mock_media_session_observer() {
return mock_media_session_observer_.get();
}
void CreateServiceForFrame(TestRenderFrameHost* frame) {
services_[frame] =
std::make_unique<NiceMock<MockMediaSessionServiceImpl>>(frame);
clients_[frame] = std::make_unique<NiceMock<MockMediaSessionClient>>();
services_[frame]->SetClient(clients_[frame]->CreateInterfacePtrAndBind());
}
void DestroyServiceForFrame(TestRenderFrameHost* frame) {
services_.erase(frame);
clients_.erase(frame);
}
MockMediaSessionClient* GetClientForFrame(TestRenderFrameHost* frame) {
auto iter = clients_.find(frame);
return (iter != clients_.end()) ? iter->second.get() : nullptr;
}
void StartPlayerForFrame(TestRenderFrameHost* frame) {
players_[frame] =
std::make_unique<NiceMock<MockMediaSessionPlayerObserver>>(frame);
MediaSessionImpl::Get(contents())
->AddPlayer(players_[frame].get(), kPlayerId,
media::MediaContentType::Persistent);
}
void ClearPlayersForFrame(TestRenderFrameHost* frame) {
if (!players_.count(frame))
return;
MediaSessionImpl::Get(contents())
->RemovePlayer(players_[frame].get(), kPlayerId);
}
MockMediaSessionPlayerObserver* GetPlayerForFrame(
TestRenderFrameHost* frame) {
auto iter = players_.find(frame);
return (iter != players_.end()) ? iter->second.get() : nullptr;
}
MediaSessionServiceImpl* ComputeServiceForRouting() {
return MediaSessionImpl::Get(contents())->ComputeServiceForRouting();
}
MediaSessionImpl* GetMediaSession() {
return MediaSessionImpl::Get(contents());
}
TestRenderFrameHost* main_frame_;
TestRenderFrameHost* sub_frame_;
std::unique_ptr<MockMediaSessionObserver> mock_media_session_observer_;
using ServiceMap = std::map<TestRenderFrameHost*,
std::unique_ptr<MockMediaSessionServiceImpl>>;
ServiceMap services_;
using ClientMap =
std::map<TestRenderFrameHost*, std::unique_ptr<MockMediaSessionClient>>;
ClientMap clients_;
using PlayerMap = std::map<TestRenderFrameHost*,
std::unique_ptr<MockMediaSessionPlayerObserver>>;
PlayerMap players_;
};
TEST_F(MediaSessionImplServiceRoutingTest, NoFrameProducesAudio) {
CreateServiceForFrame(main_frame_);
CreateServiceForFrame(sub_frame_);
ASSERT_EQ(nullptr, ComputeServiceForRouting());
}
TEST_F(MediaSessionImplServiceRoutingTest,
OnlyMainFrameProducesAudioButHasNoService) {
StartPlayerForFrame(main_frame_);
ASSERT_EQ(nullptr, ComputeServiceForRouting());
}
TEST_F(MediaSessionImplServiceRoutingTest,
OnlySubFrameProducesAudioButHasNoService) {
StartPlayerForFrame(sub_frame_);
ASSERT_EQ(nullptr, ComputeServiceForRouting());
}
TEST_F(MediaSessionImplServiceRoutingTest,
OnlyMainFrameProducesAudioButHasDestroyedService) {
CreateServiceForFrame(main_frame_);
StartPlayerForFrame(main_frame_);
DestroyServiceForFrame(main_frame_);
ASSERT_EQ(nullptr, ComputeServiceForRouting());
}
TEST_F(MediaSessionImplServiceRoutingTest,
OnlySubFrameProducesAudioButHasDestroyedService) {
CreateServiceForFrame(sub_frame_);
StartPlayerForFrame(sub_frame_);
DestroyServiceForFrame(sub_frame_);
ASSERT_EQ(nullptr, ComputeServiceForRouting());
}
TEST_F(MediaSessionImplServiceRoutingTest,
OnlyMainFrameProducesAudioAndServiceIsCreatedAfterwards) {
StartPlayerForFrame(main_frame_);
CreateServiceForFrame(main_frame_);
ASSERT_EQ(services_[main_frame_].get(), ComputeServiceForRouting());
}
TEST_F(MediaSessionImplServiceRoutingTest,
OnlySubFrameProducesAudioAndServiceIsCreatedAfterwards) {
StartPlayerForFrame(sub_frame_);
CreateServiceForFrame(sub_frame_);
ASSERT_EQ(services_[sub_frame_].get(), ComputeServiceForRouting());
}
TEST_F(MediaSessionImplServiceRoutingTest,
BothFrameProducesAudioButOnlySubFrameHasService) {
StartPlayerForFrame(main_frame_);
StartPlayerForFrame(sub_frame_);
CreateServiceForFrame(sub_frame_);
ASSERT_EQ(services_[sub_frame_].get(), ComputeServiceForRouting());
}
TEST_F(MediaSessionImplServiceRoutingTest, PreferTopMostFrame) {
StartPlayerForFrame(main_frame_);
StartPlayerForFrame(sub_frame_);
CreateServiceForFrame(main_frame_);
CreateServiceForFrame(sub_frame_);
ASSERT_EQ(services_[main_frame_].get(), ComputeServiceForRouting());
}
TEST_F(MediaSessionImplServiceRoutingTest,
RoutedServiceUpdatedAfterRemovingPlayer) {
StartPlayerForFrame(main_frame_);
StartPlayerForFrame(sub_frame_);
CreateServiceForFrame(main_frame_);
CreateServiceForFrame(sub_frame_);
ClearPlayersForFrame(main_frame_);
ASSERT_EQ(services_[sub_frame_].get(), ComputeServiceForRouting());
}
TEST_F(MediaSessionImplServiceRoutingTest,
DontNotifyMetadataAndActionsChangeWhenUncontrollable) {
EXPECT_CALL(*mock_media_session_observer(), MediaSessionMetadataChanged(_))
.Times(0);
EXPECT_CALL(*mock_media_session_observer(), MediaSessionActionsChanged(_))
.Times(0);
CreateServiceForFrame(main_frame_);
services_[main_frame_]->SetMetadata(media_session::MediaMetadata());
services_[main_frame_]->EnableAction(
media_session::mojom::MediaSessionAction::kPlay);
}
TEST_F(MediaSessionImplServiceRoutingTest,
NotifyMetadataAndActionsChangeWhenControllable) {
media_session::MediaMetadata expected_metadata;
expected_metadata.title = base::ASCIIToUTF16("title");
expected_metadata.artist = base::ASCIIToUTF16("artist");
expected_metadata.album = base::ASCIIToUTF16("album");
std::set<media_session::mojom::MediaSessionAction> empty_actions;
std::set<media_session::mojom::MediaSessionAction> expected_actions;
expected_actions.insert(media_session::mojom::MediaSessionAction::kPlay);
EXPECT_CALL(*mock_media_session_observer(),
MediaSessionMetadataChanged(Eq(base::nullopt)))
.Times(AnyNumber());
EXPECT_CALL(*mock_media_session_observer(),
MediaSessionActionsChanged(Eq(empty_actions)))
.Times(AnyNumber());
EXPECT_CALL(*mock_media_session_observer(),
MediaSessionMetadataChanged(Eq(expected_metadata)))
.Times(1);
EXPECT_CALL(*mock_media_session_observer(),
MediaSessionActionsChanged(Eq(expected_actions)))
.Times(1);
CreateServiceForFrame(main_frame_);
StartPlayerForFrame(main_frame_);
services_[main_frame_]->SetMetadata(expected_metadata);
services_[main_frame_]->EnableAction(
media_session::mojom::MediaSessionAction::kPlay);
}
TEST_F(MediaSessionImplServiceRoutingTest,
NotifyMetadataAndActionsChangeWhenTurningControllable) {
media_session::MediaMetadata expected_metadata;
expected_metadata.title = base::ASCIIToUTF16("title");
expected_metadata.artist = base::ASCIIToUTF16("artist");
expected_metadata.album = base::ASCIIToUTF16("album");
std::set<media_session::mojom::MediaSessionAction> expected_actions;
expected_actions.insert(media_session::mojom::MediaSessionAction::kPlay);
EXPECT_CALL(*mock_media_session_observer(),
MediaSessionMetadataChanged(Eq(expected_metadata)))
.Times(1);
EXPECT_CALL(*mock_media_session_observer(),
MediaSessionActionsChanged(Eq(expected_actions)))
.Times(1);
CreateServiceForFrame(main_frame_);
services_[main_frame_]->SetMetadata(expected_metadata);
services_[main_frame_]->EnableAction(
media_session::mojom::MediaSessionAction::kPlay);
StartPlayerForFrame(main_frame_);
}
TEST_F(MediaSessionImplServiceRoutingTest,
DontNotifyMetadataAndActionsChangeWhenTurningUncontrollable) {
media_session::MediaMetadata expected_metadata;
expected_metadata.title = base::ASCIIToUTF16("title");
expected_metadata.artist = base::ASCIIToUTF16("artist");
expected_metadata.album = base::ASCIIToUTF16("album");
std::set<media_session::mojom::MediaSessionAction> empty_actions;
std::set<media_session::mojom::MediaSessionAction> expected_actions;
expected_actions.insert(media_session::mojom::MediaSessionAction::kPlay);
EXPECT_CALL(*mock_media_session_observer(), MediaSessionMetadataChanged(_))
.Times(AnyNumber());
EXPECT_CALL(*mock_media_session_observer(), MediaSessionActionsChanged(_))
.Times(AnyNumber());
EXPECT_CALL(*mock_media_session_observer(),
MediaSessionMetadataChanged(Eq(base::nullopt)))
.Times(0);
EXPECT_CALL(*mock_media_session_observer(),
MediaSessionActionsChanged(Eq(empty_actions)))
.Times(0);
CreateServiceForFrame(main_frame_);
services_[main_frame_]->SetMetadata(expected_metadata);
services_[main_frame_]->EnableAction(
media_session::mojom::MediaSessionAction::kPlay);
StartPlayerForFrame(main_frame_);
ClearPlayersForFrame(main_frame_);
}
TEST_F(MediaSessionImplServiceRoutingTest,
TestPauseBehaviorWhenMainFrameIsRouted) {
base::RunLoop run_loop;
StartPlayerForFrame(main_frame_);
StartPlayerForFrame(sub_frame_);
CreateServiceForFrame(main_frame_);
EXPECT_CALL(*GetPlayerForFrame(sub_frame_), OnSuspend(_));
EXPECT_CALL(
*GetClientForFrame(main_frame_),
DidReceiveAction(media_session::mojom::MediaSessionAction::kPause))
.WillOnce(InvokeWithoutArgs(&run_loop, &base::RunLoop::Quit));
services_[main_frame_]->EnableAction(
media_session::mojom::MediaSessionAction::kPause);
MediaSessionImpl::Get(contents())
->DidReceiveAction(media_session::mojom::MediaSessionAction::kPause);
run_loop.Run();
}
TEST_F(MediaSessionImplServiceRoutingTest,
TestPauseBehaviorWhenSubFrameIsRouted) {
base::RunLoop run_loop;
StartPlayerForFrame(main_frame_);
StartPlayerForFrame(sub_frame_);
CreateServiceForFrame(sub_frame_);
EXPECT_CALL(*GetPlayerForFrame(main_frame_), OnSuspend(_));
EXPECT_CALL(
*GetClientForFrame(sub_frame_),
DidReceiveAction(media_session::mojom::MediaSessionAction::kPause))
.WillOnce(InvokeWithoutArgs(&run_loop, &base::RunLoop::Quit));
services_[sub_frame_]->EnableAction(
media_session::mojom::MediaSessionAction::kPause);
MediaSessionImpl::Get(contents())
->DidReceiveAction(media_session::mojom::MediaSessionAction::kPause);
run_loop.Run();
}
TEST_F(MediaSessionImplServiceRoutingTest,
TestReceivingPauseActionWhenNoServiceRouted) {
CreateServiceForFrame(main_frame_);
CreateServiceForFrame(sub_frame_);
EXPECT_EQ(nullptr, ComputeServiceForRouting());
// This should not crash.
MediaSessionImpl::Get(contents())
->DidReceiveAction(media_session::mojom::MediaSessionAction::kPause);
}
TEST_F(MediaSessionImplServiceRoutingTest,
TestPreviousTrackBehaviorWhenMainFrameIsRouted) {
base::RunLoop run_loop;
StartPlayerForFrame(main_frame_);
StartPlayerForFrame(sub_frame_);
CreateServiceForFrame(main_frame_);
EXPECT_CALL(*GetClientForFrame(main_frame_),
DidReceiveAction(
media_session::mojom::MediaSessionAction::kPreviousTrack))
.WillOnce(InvokeWithoutArgs(&run_loop, &base::RunLoop::Quit));
services_[main_frame_]->EnableAction(
media_session::mojom::MediaSessionAction::kPreviousTrack);
MediaSessionImpl::Get(contents())->PreviousTrack();
run_loop.Run();
}
TEST_F(MediaSessionImplServiceRoutingTest,
TestNextTrackBehaviorWhenMainFrameIsRouted) {
base::RunLoop run_loop;
StartPlayerForFrame(main_frame_);
StartPlayerForFrame(sub_frame_);
CreateServiceForFrame(main_frame_);
EXPECT_CALL(
*GetClientForFrame(main_frame_),
DidReceiveAction(media_session::mojom::MediaSessionAction::kNextTrack))
.WillOnce(InvokeWithoutArgs(&run_loop, &base::RunLoop::Quit));
services_[main_frame_]->EnableAction(
media_session::mojom::MediaSessionAction::kNextTrack);
MediaSessionImpl::Get(contents())->NextTrack();
run_loop.Run();
}
TEST_F(MediaSessionImplServiceRoutingTest, TestSeekBackwardBehaviourDefault) {
base::RunLoop run_loop;
StartPlayerForFrame(main_frame_);
CreateServiceForFrame(main_frame_);
EXPECT_CALL(*GetPlayerForFrame(main_frame_),
OnSeekBackward(_, kDefaultSeekTime))
.WillOnce(InvokeWithoutArgs(&run_loop, &base::RunLoop::Quit));
EXPECT_CALL(
*GetClientForFrame(main_frame_),
DidReceiveAction(media_session::mojom::MediaSessionAction::kSeekBackward))
.Times(0);
MediaSessionImpl::Get(contents())->Seek(kDefaultSeekTime * -1);
run_loop.Run();
}
TEST_F(MediaSessionImplServiceRoutingTest,
TestSeekBackwardBehaviourWhenActionEnabled) {
base::RunLoop run_loop;
StartPlayerForFrame(main_frame_);
CreateServiceForFrame(main_frame_);
EXPECT_CALL(*GetPlayerForFrame(main_frame_), OnSeekBackward(_, _)).Times(0);
EXPECT_CALL(
*GetClientForFrame(main_frame_),
DidReceiveAction(media_session::mojom::MediaSessionAction::kSeekBackward))
.WillOnce(InvokeWithoutArgs(&run_loop, &base::RunLoop::Quit));
services_[main_frame_]->EnableAction(
media_session::mojom::MediaSessionAction::kSeekBackward);
MediaSessionImpl::Get(contents())->Seek(kDefaultSeekTime * -1);
run_loop.Run();
}
TEST_F(MediaSessionImplServiceRoutingTest, TestSeekForwardBehaviourDefault) {
base::RunLoop run_loop;
StartPlayerForFrame(main_frame_);
CreateServiceForFrame(main_frame_);
EXPECT_CALL(*GetPlayerForFrame(main_frame_),
OnSeekForward(_, kDefaultSeekTime))
.WillOnce(InvokeWithoutArgs(&run_loop, &base::RunLoop::Quit));
EXPECT_CALL(
*GetClientForFrame(main_frame_),
DidReceiveAction(media_session::mojom::MediaSessionAction::kSeekForward))
.Times(0);
MediaSessionImpl::Get(contents())->Seek(kDefaultSeekTime);
run_loop.Run();
}
TEST_F(MediaSessionImplServiceRoutingTest,
TestSeekForwardBehaviourWhenActionEnabled) {
base::RunLoop run_loop;
StartPlayerForFrame(main_frame_);
CreateServiceForFrame(main_frame_);
EXPECT_CALL(*GetPlayerForFrame(main_frame_), OnSeekForward(_, _)).Times(0);
EXPECT_CALL(
*GetClientForFrame(main_frame_),
DidReceiveAction(media_session::mojom::MediaSessionAction::kSeekForward))
.WillOnce(InvokeWithoutArgs(&run_loop, &base::RunLoop::Quit));
services_[main_frame_]->EnableAction(
media_session::mojom::MediaSessionAction::kSeekForward);
MediaSessionImpl::Get(contents())->Seek(kDefaultSeekTime);
run_loop.Run();
}
TEST_F(MediaSessionImplServiceRoutingTest,
NotifyMojoObserverMetadataWhenControllable) {
media_session::MediaMetadata expected_metadata;
expected_metadata.title = base::ASCIIToUTF16("title");
expected_metadata.artist = base::ASCIIToUTF16("artist");
expected_metadata.album = base::ASCIIToUTF16("album");
CreateServiceForFrame(main_frame_);
StartPlayerForFrame(main_frame_);
{
media_session::test::MockMediaSessionMojoObserver observer(
*GetMediaSession());
services_[main_frame_]->SetMetadata(expected_metadata);
EXPECT_EQ(expected_metadata, observer.WaitForNonEmptyMetadata());
}
}
TEST_F(MediaSessionImplServiceRoutingTest,
NotifyMojoObserverMetadataEmptyWhenControllable) {
CreateServiceForFrame(main_frame_);
StartPlayerForFrame(main_frame_);
{
media_session::test::MockMediaSessionMojoObserver observer(
*GetMediaSession());
services_[main_frame_]->SetMetadata(base::nullopt);
// When the session becomes controllable we should receive empty metadata
// because we have not set any. The |is_controllable| boolean will also
// become true.
EXPECT_FALSE(observer.WaitForMetadata());
EXPECT_TRUE(observer.session_info()->is_controllable);
}
}
TEST_F(MediaSessionImplServiceRoutingTest,
NotifyMojoObserverWhenTurningUncontrollable) {
CreateServiceForFrame(main_frame_);
StartPlayerForFrame(main_frame_);
{
media_session::test::MockMediaSessionMojoObserver observer(
*GetMediaSession());
ClearPlayersForFrame(main_frame_);
// When the session becomes inactive it will also become uncontrollable so
// we should check the |is_controllable| boolean.
observer.WaitForState(
media_session::mojom::MediaSessionInfo::SessionState::kInactive);
EXPECT_FALSE(observer.session_info()->is_controllable);
}
}
TEST_F(MediaSessionImplServiceRoutingTest,
NotifyMojoObserverWhenActionsChange) {
CreateServiceForFrame(main_frame_);
StartPlayerForFrame(main_frame_);
services_[main_frame_]->EnableAction(
media_session::mojom::MediaSessionAction::kPlay);
media_session::test::MockMediaSessionMojoObserver observer(
*GetMediaSession());
observer.WaitForActions();
EXPECT_EQ(1u, observer.actions().size());
EXPECT_EQ(media_session::mojom::MediaSessionAction::kPlay,
observer.actions()[0]);
services_[main_frame_]->DisableAction(
media_session::mojom::MediaSessionAction::kPlay);
observer.WaitForActions();
EXPECT_TRUE(observer.actions().empty());
}
} // namespace content