#include <stdint.h>
#include <memory>
#include <tuple>
#include <utility>
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/run_loop.h"
#include "base/task/single_thread_task_runner.h"
#include "base/test/task_environment.h"
#include "build/build_config.h"
#include "media/base/cdm_config.h"
#include "media/base/mock_filters.h"
#include "media/base/test_helpers.h"
#include "media/cdm/clear_key_cdm_common.h"
#include "media/media_buildflags.h"
#include "media/mojo/buildflags.h"
#include "media/mojo/clients/mojo_decryptor.h"
#include "media/mojo/clients/mojo_demuxer_stream_impl.h"
#include "media/mojo/common/media_type_converters.h"
#include "media/mojo/mojom/content_decryption_module.mojom.h"
#include "media/mojo/mojom/decryptor.mojom.h"
#include "media/mojo/mojom/interface_factory.mojom.h"
#include "media/mojo/mojom/media_service.mojom.h"
#include "media/mojo/mojom/renderer.mojom.h"
#include "media/mojo/services/media_service_factory.h"
#include "mojo/public/cpp/bindings/associated_receiver.h"
#include "mojo/public/cpp/bindings/pending_associated_receiver.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"
namespace media {
namespace {
using testing::_;
using testing::DoAll;
using testing::Invoke;
using testing::InvokeWithoutArgs;
using testing::NiceMock;
using testing::SaveArg;
using testing::StrictMock;
using testing::WithArg;
MATCHER_P(MatchesResult, success, "") {
return arg->success == success;
}
#if BUILDFLAG(ENABLE_MOJO_CDM) && !BUILDFLAG(IS_ANDROID)
const char kInvalidKeySystem[] = "invalid.key.system";
#endif
class MockRendererClient : public mojom::RendererClient {
public:
MockRendererClient() = default;
MockRendererClient(const MockRendererClient&) = delete;
MockRendererClient& operator=(const MockRendererClient&) = delete;
~MockRendererClient() override = default;
MOCK_METHOD3(OnTimeUpdate,
void(base::TimeDelta time,
base::TimeDelta max_time,
base::TimeTicks capture_time));
MOCK_METHOD2(OnBufferingStateChange,
void(BufferingState state, BufferingStateChangeReason reason));
MOCK_METHOD0(OnEnded, void());
MOCK_METHOD1(OnError, void(const PipelineStatus& status));
MOCK_METHOD1(OnVideoOpacityChange, void(bool opaque));
MOCK_METHOD1(OnAudioConfigChange, void(const AudioDecoderConfig&));
MOCK_METHOD1(OnVideoConfigChange, void(const VideoDecoderConfig&));
MOCK_METHOD1(OnVideoNaturalSizeChange, void(const gfx::Size& size));
MOCK_METHOD1(OnStatisticsUpdate, void(const PipelineStatistics& stats));
MOCK_METHOD1(OnWaiting, void(WaitingReason));
MOCK_METHOD1(OnDurationChange, void(base::TimeDelta duration));
MOCK_METHOD1(OnRemotePlayStateChange, void(MediaStatus::State state));
};
ACTION_P(QuitLoop, run_loop) {
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, run_loop->QuitClosure());
}
class MediaServiceTest : public testing::Test {
public:
MediaServiceTest()
: renderer_client_receiver_(&renderer_client_),
video_stream_(DemuxerStream::VIDEO) {}
MediaServiceTest(const MediaServiceTest&) = delete;
MediaServiceTest& operator=(const MediaServiceTest&) = delete;
~MediaServiceTest() override = default;
void SetUp() override {
mojo::PendingRemote<mojom::FrameInterfaceFactory> frame_interfaces;
std::ignore = frame_interfaces.InitWithNewPipeAndPassReceiver();
media_service_impl_ = CreateMediaServiceForTesting(
media_service_.BindNewPipeAndPassReceiver());
media_service_.set_idle_handler(
base::TimeDelta(),
base::BindRepeating(&MediaServiceTest::OnMediaServiceIdle,
base::Unretained(this)));
media_service_->CreateInterfaceFactory(
interface_factory_.BindNewPipeAndPassReceiver(),
std::move(frame_interfaces));
}
MOCK_METHOD0(OnCdmConnectionError, void());
void InitializeCdm(const std::string& key_system, bool expected_result) {
interface_factory_->CreateCdm(
{key_system, false, false, false},
base::BindOnce(&MediaServiceTest::OnCdmCreated, base::Unretained(this),
expected_result));
task_environment_.RunUntilIdle();
}
MOCK_METHOD1(OnRendererInitialized, void(bool));
void InitializeRenderer(const VideoDecoderConfig& video_config,
bool expected_result) {
base::RunLoop run_loop;
interface_factory_->CreateDefaultRenderer(
std::string(), renderer_.BindNewPipeAndPassReceiver());
video_stream_.set_video_decoder_config(video_config);
mojo::PendingRemote<mojom::DemuxerStream> video_stream_proxy;
mojo_video_stream_ = std::make_unique<MojoDemuxerStreamImpl>(
&video_stream_, video_stream_proxy.InitWithNewPipeAndPassReceiver());
mojo::PendingAssociatedRemote<mojom::RendererClient> client_remote;
renderer_client_receiver_.Bind(
client_remote.InitWithNewEndpointAndPassReceiver());
std::vector<mojo::PendingRemote<mojom::DemuxerStream>> streams;
streams.push_back(std::move(video_stream_proxy));
EXPECT_CALL(*this, OnRendererInitialized(expected_result))
.WillOnce(QuitLoop(&run_loop));
renderer_->Initialize(
std::move(client_remote), std::move(streams),
base::BindOnce(&MediaServiceTest::OnRendererInitialized,
base::Unretained(this)));
run_loop.Run();
}
MOCK_METHOD0(OnMediaServiceIdle, void());
protected:
void OnCdmCreated(bool expected_result,
mojo::PendingRemote<mojom::ContentDecryptionModule> remote,
mojom::CdmContextPtr cdm_context,
CreateCdmStatus status) {
if (!expected_result) {
EXPECT_FALSE(remote);
EXPECT_FALSE(cdm_context);
EXPECT_NE(status, CreateCdmStatus::kSuccess);
return;
}
EXPECT_TRUE(remote);
EXPECT_EQ(status, CreateCdmStatus::kSuccess);
cdm_.Bind(std::move(remote));
cdm_.set_disconnect_handler(base::BindOnce(
&MediaServiceTest::OnCdmConnectionError, base::Unretained(this)));
}
base::test::TaskEnvironment task_environment_;
mojo::Remote<mojom::MediaService> media_service_;
mojo::Remote<mojom::InterfaceFactory> interface_factory_;
mojo::Remote<mojom::ContentDecryptionModule> cdm_;
mojo::Remote<mojom::Renderer> renderer_;
std::unique_ptr<MediaService> media_service_impl_;
NiceMock<MockRendererClient> renderer_client_;
mojo::AssociatedReceiver<mojom::RendererClient> renderer_client_receiver_;
StrictMock<MockDemuxerStream> video_stream_;
std::unique_ptr<MojoDemuxerStreamImpl> mojo_video_stream_;
};
}
#if BUILDFLAG(ENABLE_MOJO_CDM) && !BUILDFLAG(IS_ANDROID)
TEST_F(MediaServiceTest, InitializeCdm_Success) {
InitializeCdm(kClearKeyKeySystem, true);
}
TEST_F(MediaServiceTest, InitializeCdm_InvalidKeySystem) {
InitializeCdm(kInvalidKeySystem, false);
}
#endif
#if BUILDFLAG(ENABLE_MOJO_RENDERER)
TEST_F(MediaServiceTest, InitializeRenderer) {
InitializeRenderer(TestVideoConfig::Normal(), true);
}
#endif
TEST_F(MediaServiceTest, InterfaceFactoryPreventsIdling) {
interface_factory_.FlushForTesting();
base::RunLoop run_loop;
EXPECT_CALL(*this, OnMediaServiceIdle()).WillOnce(QuitLoop(&run_loop));
interface_factory_.reset();
run_loop.Run();
}
#if (BUILDFLAG(ENABLE_MOJO_CDM) && !BUILDFLAG(IS_ANDROID)) || \
BUILDFLAG(ENABLE_MOJO_RENDERER)
TEST_F(MediaServiceTest, Idling) {
#if BUILDFLAG(ENABLE_MOJO_CDM) && !BUILDFLAG(IS_ANDROID)
InitializeCdm(kClearKeyKeySystem, true);
#endif
#if BUILDFLAG(ENABLE_MOJO_RENDERER)
InitializeRenderer(TestVideoConfig::Normal(), true);
#endif
cdm_.reset();
renderer_.reset();
interface_factory_.FlushForTesting();
base::RunLoop run_loop;
EXPECT_CALL(*this, OnMediaServiceIdle()).WillOnce(QuitLoop(&run_loop));
interface_factory_.reset();
run_loop.Run();
}
TEST_F(MediaServiceTest, MoreIdling) {
#if BUILDFLAG(ENABLE_MOJO_CDM) && !BUILDFLAG(IS_ANDROID)
InitializeCdm(kClearKeyKeySystem, true);
#endif
#if BUILDFLAG(ENABLE_MOJO_RENDERER)
InitializeRenderer(TestVideoConfig::Normal(), true);
#endif
ASSERT_TRUE(cdm_ || renderer_);
interface_factory_.reset();
if (cdm_)
cdm_.FlushForTesting();
else if (renderer_)
renderer_.FlushForTesting();
else
NOTREACHED();
base::RunLoop run_loop;
EXPECT_CALL(*this, OnMediaServiceIdle()).WillOnce(QuitLoop(&run_loop));
cdm_.reset();
renderer_.reset();
run_loop.Run();
}
#endif
}