blob: 5eedbbc11d98026e103b30189a6c00dcdee68346 [file] [log] [blame]
// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// The StarboardRenderer class does not perform much active logic. Most of its
// functionality is separated into other classes, which are stored as data
// members of StarboardRenderer. As such, these tests are more like integration
// tests, which verify that the sub-components of StarboardRenderer
// (GeometryChangeHandler, StarboardPlayerManager, etc.) all work together and
// implement the functionality expected of a ::media::Renderer.
//
// One important detail here is that StarboardRenderer does not actually do any
// rendering or decoding directly. That is done by the starboard library itself.
// Here, MockStarboardApiWrapper is used to verify that the correct data is
// being passed to starboard.
#include "chromecast/starboard/media/renderer/starboard_renderer.h"
#include <array>
#include <memory>
#include <utility>
#include "base/functional/callback.h"
#include "base/task/sequenced_task_runner.h"
#include "base/test/bind.h"
#include "base/test/gmock_callback_support.h"
#include "base/test/task_environment.h"
#include "base/time/time.h"
#include "base/unguessable_token.h"
#include "chromecast/base/metrics/mock_cast_metrics_helper.h"
#include "chromecast/media/service/mojom/video_geometry_setter.mojom.h"
#include "chromecast/media/service/video_geometry_setter_service.h"
#include "chromecast/starboard/media/cdm/starboard_drm_key_tracker.h"
#include "chromecast/starboard/media/cdm/starboard_drm_wrapper.h"
#include "chromecast/starboard/media/media/mock_starboard_api_wrapper.h"
#include "chromecast/starboard/media/media/starboard_api_wrapper.h"
#include "chromecast/starboard/media/media/test_matchers.h"
#include "media/base/audio_codecs.h"
#include "media/base/buffering_state.h"
#include "media/base/decoder_buffer.h"
#include "media/base/demuxer_stream.h"
#include "media/base/mock_filters.h"
#include "media/base/pipeline_status.h"
#include "media/base/test_helpers.h"
#include "media/base/video_codecs.h"
#include "mojo/core/embedder/embedder.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/display/test/test_screen.h"
#include "ui/gfx/geometry/rect_f.h"
#include "ui/gfx/overlay_transform.h"
namespace chromecast {
namespace media {
namespace {
using ::base::test::RunOnceCallback;
using ::media::DemuxerStream;
using ::testing::_;
using ::testing::AnyNumber;
using ::testing::AtLeast;
using ::testing::DoAll;
using ::testing::DoubleEq;
using ::testing::InSequence;
using ::testing::MockFunction;
using ::testing::NiceMock;
using ::testing::NotNull;
using ::testing::Pointee;
using ::testing::Return;
using ::testing::SaveArg;
using ::testing::WithArg;
// Runs any pending tasks that have been posted to the current sequence.
void RunPendingTasks() {
base::RunLoop run_loop;
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, run_loop.QuitClosure());
run_loop.Run();
}
// Returns a valid audio config with values arbitrarily set. The values will
// match the values of GetStarboardAudioConfig.
::media::AudioDecoderConfig GetChromiumAudioConfig() {
return ::media::AudioDecoderConfig(
::media::AudioCodec::kAAC, ::media::SampleFormat::kSampleFormatS32,
::media::ChannelLayout::CHANNEL_LAYOUT_STEREO, 48000, /*extra_data=*/{},
::media::EncryptionScheme::kUnencrypted);
}
// Returns a valid video config with values arbitrarily set. The values will
// match the values of GetStarboardVideoConfig.
::media::VideoDecoderConfig GetChromiumVideoConfig() {
::media::VideoDecoderConfig video_config(
::media::VideoCodec::kH264, ::media::VideoCodecProfile::H264PROFILE_HIGH,
::media::VideoDecoderConfig::AlphaMode::kIsOpaque,
::media::VideoColorSpace(1, 1, 1, gfx::ColorSpace::RangeID::LIMITED),
::media::VideoTransformation(), gfx::Size(3840, 2160),
gfx::Rect(0, 0, 3838, 2121), gfx::Size(1920, 1080), /*extra_data=*/{},
::media::EncryptionScheme::kUnencrypted);
video_config.set_level(42);
return video_config;
}
// Returns a valid starboard audio config with values arbitrarily set. The
// values will match the values of GetChromiumAudioConfig.
StarboardAudioSampleInfo GetStarboardAudioConfig() {
return StarboardAudioSampleInfo{
.codec = kStarboardAudioCodecAac,
.mime = R"-(audio/mp4; codecs="mp4a.40.5")-",
.format_tag = 0,
.number_of_channels = 2,
.samples_per_second = 48000,
.average_bytes_per_second = (32 / 8) * 48000 * 2,
.block_alignment = 4,
.bits_per_sample = 32,
.audio_specific_config_size = 0,
.audio_specific_config = nullptr,
};
}
// Returns a valid starboard video config with values arbitrarily set. The
// values will match the values of GetChromiumVideoConfig.
StarboardVideoSampleInfo GetStarboardVideoConfig() {
return StarboardVideoSampleInfo{
.codec = kStarboardVideoCodecH264,
.mime = R"-(video/mp4; codecs="avc1.64002A")-",
.max_video_capabilities = "",
.is_key_frame = false,
.frame_width = 3840,
.frame_height = 2160,
.color_metadata =
StarboardColorMetadata{
// These 0 fields signify "unknown" to starboard.
.bits_per_channel = 0,
.chroma_subsampling_horizontal = 0,
.chroma_subsampling_vertical = 0,
.cb_subsampling_horizontal = 0,
.cb_subsampling_vertical = 0,
.chroma_siting_horizontal = 0,
.chroma_siting_vertical = 0,
// No HDR metadata, so everything is 0.
.mastering_metadata = StarboardMediaMasteringMetadata{},
.max_cll = 0,
.max_fall = 0,
.primaries = 1, // BT.709
.transfer = 1, // BT.709
.matrix = 1, // BT.709
.range = 1, // broadcast range
.custom_primary_matrix = {0},
},
};
}
// Some default configs.
const StarboardAudioSampleInfo kSbAudioConfig = GetStarboardAudioConfig();
const StarboardVideoSampleInfo kSbVideoConfig = GetStarboardVideoConfig();
// Some default audio/video data.
constexpr auto kAudioData = std::to_array<uint8_t>({1, 1, 1, 2, 2, 2});
constexpr auto kVideoData = std::to_array<uint8_t>({1, 2, 3, 4, 5, 6, 7});
// A test fixture is used to abstract away boilerplate (e.g. setting up the task
// environment, creating mocks).
class StarboardRendererTest : public ::testing::Test {
protected:
StarboardRendererTest() {
mojo::core::Init();
audio_stream_.set_audio_decoder_config(GetChromiumAudioConfig());
video_stream_.set_video_decoder_config(GetChromiumVideoConfig());
ON_CALL(starboard_for_drm_, CreateDrmSystem)
.WillByDefault(Return(&drm_system_));
StarboardDrmWrapper::SetSingletonForTesting(&starboard_for_drm_);
ON_CALL(media_resource_, GetAllStreams)
.WillByDefault(Return(
std::vector<DemuxerStream*>({&audio_stream_, &video_stream_})));
ON_CALL(media_resource_, GetFirstStream(DemuxerStream::Type::AUDIO))
.WillByDefault(Return(&audio_stream_));
ON_CALL(media_resource_, GetFirstStream(DemuxerStream::Type::VIDEO))
.WillByDefault(Return(&video_stream_));
ON_CALL(*starboard_,
CreatePlayer(
Pointee(MatchesPlayerCreationParam(StarboardPlayerCreationParam{
.drm_system = nullptr,
.audio_sample_info = kSbAudioConfig,
.video_sample_info = kSbVideoConfig,
.output_mode = StarboardPlayerOutputMode::
kStarboardPlayerOutputModePunchOut})),
_))
.WillByDefault(
DoAll(SaveArg<1>(&sb_player_callbacks_), Return(&sb_player_)));
ON_CALL(*starboard_, SeekTo(&sb_player_, _, _))
.WillByDefault(SaveArg<2>(&current_seek_ticket_));
// Since matchers compare data ptrs by address, it does not matter that all
// of these buffers use the same audio data (the data is copied, so each
// DecoderBuffer's data ptr will be different).
scoped_refptr<::media::DecoderBuffer> audio_buffer_1 =
::media::DecoderBuffer::CopyFrom(kAudioData);
audio_buffer_1->set_timestamp(base::Seconds(0));
scoped_refptr<::media::DecoderBuffer> audio_buffer_2 =
::media::DecoderBuffer::CopyFrom(kAudioData);
audio_buffer_2->set_timestamp(base::Seconds(1));
scoped_refptr<::media::DecoderBuffer> audio_buffer_3 =
::media::DecoderBuffer::CopyFrom(kAudioData);
audio_buffer_3->set_timestamp(base::Seconds(5));
audio_buffers_ = {
std::move(audio_buffer_1),
std::move(audio_buffer_2),
std::move(audio_buffer_3),
};
scoped_refptr<::media::DecoderBuffer> video_buffer_1 =
::media::DecoderBuffer::CopyFrom(kVideoData);
video_buffer_1->set_timestamp(base::Seconds(0));
scoped_refptr<::media::DecoderBuffer> video_buffer_2 =
::media::DecoderBuffer::CopyFrom(kVideoData);
video_buffer_2->set_timestamp(base::Seconds(1));
scoped_refptr<::media::DecoderBuffer> video_buffer_3 =
::media::DecoderBuffer::CopyFrom(kVideoData);
video_buffer_3->set_timestamp(base::Seconds(5));
scoped_refptr<::media::DecoderBuffer> video_buffer_4 =
::media::DecoderBuffer::CopyFrom(kVideoData);
video_buffer_4->set_timestamp(base::Seconds(20));
video_buffers_ = {
std::move(video_buffer_1),
std::move(video_buffer_2),
std::move(video_buffer_3),
std::move(video_buffer_4),
};
CHECK_EQ(audio_buffers_.size(), sb_audio_buffers_.size());
for (size_t i = 0; i < audio_buffers_.size(); ++i) {
const auto& audio_buffer = audio_buffers_[i];
sb_audio_buffers_[i] = StarboardSampleInfo{
.type = 0,
.buffer = base::span(*audio_buffer).data(),
.buffer_size = static_cast<int>(audio_buffer->size()),
.timestamp = audio_buffer->timestamp().InMicroseconds(),
.side_data = base::span<const StarboardSampleSideData>(),
.audio_sample_info = kSbAudioConfig,
.drm_info = nullptr,
};
}
CHECK_EQ(video_buffers_.size(), sb_video_buffers_.size());
for (size_t i = 0; i < video_buffers_.size(); ++i) {
const auto& video_buffer = video_buffers_[i];
sb_video_buffers_[i] = StarboardSampleInfo{
.type = 1,
.buffer = base::span(*video_buffer).data(),
.buffer_size = static_cast<int>(video_buffer->size()),
.timestamp = video_buffer->timestamp().InMicroseconds(),
.side_data = base::span<const StarboardSampleSideData>(),
.video_sample_info = kSbVideoConfig,
.drm_info = nullptr,
};
}
}
~StarboardRendererTest() override = default;
// This should be destructed last.
base::test::TaskEnvironment task_environment_;
scoped_refptr<base::SequencedTaskRunner> task_runner_ =
base::SequencedTaskRunner::GetCurrentDefault();
NiceMock<::media::MockMediaResource> media_resource_;
NiceMock<::media::MockDemuxerStream> audio_stream_ =
NiceMock<::media::MockDemuxerStream>(DemuxerStream::Type::AUDIO);
NiceMock<::media::MockDemuxerStream> video_stream_ =
NiceMock<::media::MockDemuxerStream>(DemuxerStream::Type::VIDEO);
NiceMock<::media::MockRendererClient> client_;
NiceMock<chromecast::metrics::MockCastMetricsHelper> cast_metrics_helper_;
MockFunction<void(::media::PipelineStatus)> pipeline_status_fn_;
std::unique_ptr<NiceMock<MockStarboardApiWrapper>> starboard_ =
std::make_unique<NiceMock<MockStarboardApiWrapper>>();
// This will be set to the callbacks received by the mock Starboard. These
// callbacks would normally be used by starboard, e.g. to request new buffers.
// We can call these callbacks to simulate that behavior.
const StarboardPlayerCallbackHandler* sb_player_callbacks_ = nullptr;
// It is undefined behavior to set expectations on a mock after its mock
// functions have been called. Thus, to be safe we use a separate mock
// starboard for the StarboardDrmWrapper. All expectations are set before it
// is passed to the StarboardDrmWrapper (in this fixture's ctor).
NiceMock<MockStarboardApiWrapper> starboard_for_drm_;
// Since SbPlayer is used as an opaque void* by cast, we can use any type
// here. All that matters is the address.
int sb_player_ = 1;
// Same for SbDrmSystem.
int drm_system_ = 2;
// Captured by starboard when a seek is performed.
int current_seek_ticket_ = -1;
display::test::TestScreen test_screen_ =
display::test::TestScreen(/*create_display=*/true,
/*register_screen=*/true);
VideoGeometrySetterService geometry_setter_service_;
// We use std::array here so we can have compile-time bounds checking (via
// std::get()) and avoid for-loops when using these arrays in tests (to keep
// EXPECT/ASSERT in top-level TEST bodies).
//
// Chromium buffers.
std::array<scoped_refptr<::media::DecoderBuffer>, 3> audio_buffers_;
std::array<scoped_refptr<::media::DecoderBuffer>, 4> video_buffers_;
// Starboard buffers corresponding to the chromium buffers.
std::array<StarboardSampleInfo, 3> sb_audio_buffers_;
std::array<StarboardSampleInfo, 4> sb_video_buffers_;
const base::UnguessableToken plane_id_ = base::UnguessableToken::Create();
};
TEST_F(StarboardRendererTest,
ReadsFromDemuxerStreamsAndPushesBuffersToStarboard) {
constexpr base::TimeDelta kSeekTime = base::Seconds(0);
// Since we do not simulate geometry_setter_service_ receiving any geometry
// changes, the player's bounds should end up getting set to the screen
// resolution (full screen).
EXPECT_CALL(
*starboard_,
SetPlayerBounds(
&sb_player_, 0,
static_cast<int>(display::test::TestScreen::kDefaultScreenBounds.x()),
static_cast<int>(display::test::TestScreen::kDefaultScreenBounds.y()),
static_cast<int>(
display::test::TestScreen::kDefaultScreenBounds.width()),
static_cast<int>(
display::test::TestScreen::kDefaultScreenBounds.height())))
.Times(1);
EXPECT_CALL(client_, OnBufferingStateChange(
::media::BufferingState::BUFFERING_HAVE_ENOUGH, _))
.Times(AtLeast(1));
EXPECT_CALL(*starboard_, SeekTo(&sb_player_, kSeekTime.InMicroseconds(), _))
.WillOnce(SaveArg<2>(&current_seek_ticket_));
// Audio buffer expectations.
EXPECT_CALL(
*starboard_,
WriteSample(&sb_player_, StarboardMediaType::kStarboardMediaTypeAudio,
ElementsAre(MatchesStarboardSampleInfo(
std::get<0>(sb_audio_buffers_)))))
.Times(1);
EXPECT_CALL(
*starboard_,
WriteSample(&sb_player_, StarboardMediaType::kStarboardMediaTypeAudio,
ElementsAre(MatchesStarboardSampleInfo(
std::get<1>(sb_audio_buffers_)))))
.Times(1);
EXPECT_CALL(
*starboard_,
WriteSample(&sb_player_, StarboardMediaType::kStarboardMediaTypeAudio,
ElementsAre(MatchesStarboardSampleInfo(
std::get<2>(sb_audio_buffers_)))))
.Times(1);
EXPECT_CALL(*starboard_,
WriteEndOfStream(&sb_player_,
StarboardMediaType::kStarboardMediaTypeAudio))
.Times(1);
// Video buffer expectations.
EXPECT_CALL(
*starboard_,
WriteSample(&sb_player_, StarboardMediaType::kStarboardMediaTypeVideo,
ElementsAre(MatchesStarboardSampleInfo(
std::get<0>(sb_video_buffers_)))))
.Times(1);
EXPECT_CALL(
*starboard_,
WriteSample(&sb_player_, StarboardMediaType::kStarboardMediaTypeVideo,
ElementsAre(MatchesStarboardSampleInfo(
std::get<1>(sb_video_buffers_)))))
.Times(1);
EXPECT_CALL(
*starboard_,
WriteSample(&sb_player_, StarboardMediaType::kStarboardMediaTypeVideo,
ElementsAre(MatchesStarboardSampleInfo(
std::get<2>(sb_video_buffers_)))))
.Times(1);
EXPECT_CALL(
*starboard_,
WriteSample(&sb_player_, StarboardMediaType::kStarboardMediaTypeVideo,
ElementsAre(MatchesStarboardSampleInfo(
std::get<3>(sb_video_buffers_)))))
.Times(1);
EXPECT_CALL(*starboard_,
WriteEndOfStream(&sb_player_,
StarboardMediaType::kStarboardMediaTypeVideo))
.Times(1);
// Provide the audio buffers from the audio DemuxerStream, ending with EOS.
EXPECT_CALL(audio_stream_, OnRead)
.WillOnce(
RunOnceCallback<0>(DemuxerStream::Status::kOk,
std::vector<scoped_refptr<::media::DecoderBuffer>>(
{std::get<0>(audio_buffers_)})))
.WillOnce(
RunOnceCallback<0>(DemuxerStream::Status::kOk,
std::vector<scoped_refptr<::media::DecoderBuffer>>(
{std::get<1>(audio_buffers_)})))
.WillOnce(
RunOnceCallback<0>(DemuxerStream::Status::kOk,
std::vector<scoped_refptr<::media::DecoderBuffer>>(
{std::get<2>(audio_buffers_)})))
.WillOnce(
RunOnceCallback<0>(DemuxerStream::Status::kOk,
std::vector<scoped_refptr<::media::DecoderBuffer>>(
{::media::DecoderBuffer::CreateEOSBuffer()})));
// Provide the video buffers from the video DemuxerStream, ending with EOS.
EXPECT_CALL(video_stream_, OnRead)
.WillOnce(
RunOnceCallback<0>(DemuxerStream::Status::kOk,
std::vector<scoped_refptr<::media::DecoderBuffer>>(
{std::get<0>(video_buffers_)})))
.WillOnce(
RunOnceCallback<0>(DemuxerStream::Status::kOk,
std::vector<scoped_refptr<::media::DecoderBuffer>>(
{std::get<1>(video_buffers_)})))
.WillOnce(
RunOnceCallback<0>(DemuxerStream::Status::kOk,
std::vector<scoped_refptr<::media::DecoderBuffer>>(
{std::get<2>(video_buffers_)})))
.WillOnce(
RunOnceCallback<0>(DemuxerStream::Status::kOk,
std::vector<scoped_refptr<::media::DecoderBuffer>>(
{std::get<3>(video_buffers_)})))
.WillOnce(
RunOnceCallback<0>(DemuxerStream::Status::kOk,
std::vector<scoped_refptr<::media::DecoderBuffer>>(
{::media::DecoderBuffer::CreateEOSBuffer()})));
StarboardRenderer renderer(std::move(starboard_), task_runner_, plane_id_,
/*enable_buffering=*/true,
&geometry_setter_service_, &cast_metrics_helper_);
EXPECT_CALL(pipeline_status_fn_,
Call(HasStatusCode(::media::PipelineStatusCodes::PIPELINE_OK)))
.Times(1);
renderer.Initialize(
&media_resource_, &client_,
base::BindLambdaForTesting(pipeline_status_fn_.AsStdFunction()));
RunPendingTasks();
renderer.StartPlayingFrom(kSeekTime);
ASSERT_THAT(sb_player_callbacks_, NotNull());
ASSERT_THAT(sb_player_callbacks_->context, NotNull());
ASSERT_THAT(sb_player_callbacks_->decoder_status_fn, NotNull());
ASSERT_THAT(sb_player_callbacks_->player_status_fn, NotNull());
// Simulate starboard requesting audio buffers. The +1 is to represent the
// expected EOS after all buffers have been returned.
for (size_t i = 0; i < audio_buffers_.size() + 1; ++i) {
sb_player_callbacks_->decoder_status_fn(
&sb_player_, sb_player_callbacks_->context,
StarboardMediaType::kStarboardMediaTypeAudio,
StarboardDecoderState::kStarboardDecoderStateNeedsData,
current_seek_ticket_);
RunPendingTasks();
}
// Simulate starboard requesting video buffers. The +1 is to represent the
// expected EOS after all buffers have been returned.
for (size_t i = 0; i < video_buffers_.size() + 1; ++i) {
sb_player_callbacks_->decoder_status_fn(
&sb_player_, sb_player_callbacks_->context,
StarboardMediaType::kStarboardMediaTypeVideo,
StarboardDecoderState::kStarboardDecoderStateNeedsData,
current_seek_ticket_);
RunPendingTasks();
}
// This should inform the RendererClient that we have enough data buffered.
sb_player_callbacks_->player_status_fn(
&sb_player_, sb_player_callbacks_->context,
StarboardPlayerState::kStarboardPlayerStatePresenting,
current_seek_ticket_);
}
TEST_F(StarboardRendererTest,
FlushSeeksToCurrentMediaTimeAndSetsPlaybackRateToZero) {
constexpr base::TimeDelta kSeekTime = base::Seconds(0);
constexpr base::TimeDelta kMediaTime = base::Seconds(1);
constexpr double kPlaybackRate = 1.1;
EXPECT_CALL(*starboard_, SeekTo(&sb_player_, kSeekTime.InMicroseconds(), _))
.WillOnce(SaveArg<2>(&current_seek_ticket_));
EXPECT_CALL(*starboard_, SeekTo(&sb_player_, kMediaTime.InMicroseconds(), _))
.WillOnce(SaveArg<2>(&current_seek_ticket_));
// The playback rate should be set to zero twice:
// * Once when we start playback
// * Once when we flush
EXPECT_CALL(*starboard_, SetPlaybackRate(&sb_player_, DoubleEq(0))).Times(2);
// The playback rate should be set to 1.1 when we explicitly set the playback
// rate.
EXPECT_CALL(*starboard_,
SetPlaybackRate(&sb_player_, DoubleEq(kPlaybackRate)))
.Times(1);
EXPECT_CALL(*starboard_, GetPlayerInfo(&sb_player_, NotNull()))
.Times(AtLeast(1))
.WillRepeatedly(
WithArg<1>([kMediaTime](StarboardPlayerInfo* player_info) {
*player_info = {};
player_info->current_media_timestamp_micros =
kMediaTime.InMicroseconds();
}));
// Ignore unrelated metrics calls.
EXPECT_CALL(cast_metrics_helper_, RecordApplicationEvent(_))
.Times(AnyNumber());
// Should be called at flush, but not at destruction.
EXPECT_CALL(cast_metrics_helper_,
RecordApplicationEvent("Cast.Platform.Ended"))
.Times(1);
// Should be called when setting the playback rate to a nonzero value.
EXPECT_CALL(cast_metrics_helper_,
RecordApplicationEvent("Cast.Platform.Playing"))
.Times(AtLeast(1));
StarboardRenderer renderer(std::move(starboard_), task_runner_, plane_id_,
/*enable_buffering=*/true,
&geometry_setter_service_, &cast_metrics_helper_);
EXPECT_CALL(pipeline_status_fn_,
Call(HasStatusCode(::media::PipelineStatusCodes::PIPELINE_OK)))
.Times(1);
renderer.Initialize(
&media_resource_, &client_,
base::BindLambdaForTesting(pipeline_status_fn_.AsStdFunction()));
RunPendingTasks();
renderer.StartPlayingFrom(kSeekTime);
renderer.SetPlaybackRate(kPlaybackRate);
MockFunction<void()> flush_cb;
EXPECT_CALL(flush_cb, Call).Times(1);
renderer.Flush(base::BindLambdaForTesting(flush_cb.AsStdFunction()));
RunPendingTasks();
}
TEST_F(StarboardRendererTest, SetVolumeForwardsVolumeChangeToStarboard) {
constexpr base::TimeDelta kSeekTime = base::Seconds(0);
constexpr float kVolume = 0.77f;
EXPECT_CALL(*starboard_, SetVolume(&sb_player_, DoubleEq(kVolume))).Times(1);
StarboardRenderer renderer(std::move(starboard_), task_runner_, plane_id_,
/*enable_buffering=*/true,
&geometry_setter_service_, &cast_metrics_helper_);
EXPECT_CALL(pipeline_status_fn_,
Call(HasStatusCode(::media::PipelineStatusCodes::PIPELINE_OK)))
.Times(1);
renderer.Initialize(
&media_resource_, &client_,
base::BindLambdaForTesting(pipeline_status_fn_.AsStdFunction()));
RunPendingTasks();
renderer.StartPlayingFrom(kSeekTime);
renderer.SetVolume(kVolume);
}
TEST_F(StarboardRendererTest, ForwardsGeometryChangesToStarboard) {
const gfx::RectF geometry_1(0, 0, 1920, 1080);
const gfx::RectF geometry_2(0, 0, 720, 1280);
const gfx::OverlayTransform transform =
gfx::OverlayTransform::OVERLAY_TRANSFORM_NONE;
{
InSequence s;
EXPECT_CALL(
*starboard_,
SetPlayerBounds(&sb_player_, 0, static_cast<int>(geometry_1.x()),
static_cast<int>(geometry_1.y()),
static_cast<int>(geometry_1.width()),
static_cast<int>(geometry_1.height())))
.Times(1);
EXPECT_CALL(
*starboard_,
SetPlayerBounds(&sb_player_, 0, static_cast<int>(geometry_2.x()),
static_cast<int>(geometry_2.y()),
static_cast<int>(geometry_2.width()),
static_cast<int>(geometry_2.height())))
.Times(1);
}
StarboardRenderer renderer(std::move(starboard_), task_runner_, plane_id_,
/*enable_buffering=*/true,
&geometry_setter_service_, &cast_metrics_helper_);
RunPendingTasks();
static_cast<mojom::VideoGeometrySetter*>(&geometry_setter_service_)
->SetVideoGeometry(geometry_1, transform, plane_id_);
RunPendingTasks();
EXPECT_CALL(pipeline_status_fn_,
Call(HasStatusCode(::media::PipelineStatusCodes::PIPELINE_OK)))
.Times(1);
renderer.Initialize(
&media_resource_, &client_,
base::BindLambdaForTesting(pipeline_status_fn_.AsStdFunction()));
RunPendingTasks();
static_cast<mojom::VideoGeometrySetter*>(&geometry_setter_service_)
->SetVideoGeometry(geometry_2, transform, plane_id_);
RunPendingTasks();
}
TEST_F(StarboardRendererTest, GetMediaTimeReadsCurrentMediaTimeFromStarboard) {
constexpr base::TimeDelta kMediaTime = base::Seconds(1);
EXPECT_CALL(*starboard_, GetPlayerInfo(&sb_player_, NotNull()))
.Times(AtLeast(1))
.WillRepeatedly(
WithArg<1>([kMediaTime](StarboardPlayerInfo* player_info) {
*player_info = {};
player_info->current_media_timestamp_micros =
kMediaTime.InMicroseconds();
}));
StarboardRenderer renderer(std::move(starboard_), task_runner_, plane_id_,
/*enable_buffering=*/true,
&geometry_setter_service_, &cast_metrics_helper_);
EXPECT_CALL(pipeline_status_fn_,
Call(HasStatusCode(::media::PipelineStatusCodes::PIPELINE_OK)))
.Times(1);
renderer.Initialize(
&media_resource_, &client_,
base::BindLambdaForTesting(pipeline_status_fn_.AsStdFunction()));
RunPendingTasks();
EXPECT_EQ(renderer.GetMediaTime(), kMediaTime);
}
TEST_F(StarboardRendererTest, SetPlaybackRateReportsMetric) {
// Ignore unrelated metrics calls.
EXPECT_CALL(cast_metrics_helper_, RecordApplicationEvent(_))
.Times(AnyNumber());
// Should be called at destruction.
EXPECT_CALL(cast_metrics_helper_,
RecordApplicationEvent("Cast.Platform.Ended"))
.Times(1);
// Should be called when setting the playback rate to a nonzero value.
EXPECT_CALL(cast_metrics_helper_,
RecordApplicationEvent("Cast.Platform.Playing"))
.Times(1);
// Should be called when setting the playback rate to 0.
EXPECT_CALL(cast_metrics_helper_,
RecordApplicationEvent("Cast.Platform.Pause"))
.Times(1);
StarboardRenderer renderer(std::move(starboard_), task_runner_, plane_id_,
/*enable_buffering=*/true,
&geometry_setter_service_, &cast_metrics_helper_);
renderer.Initialize(
&media_resource_, &client_,
base::BindLambdaForTesting(pipeline_status_fn_.AsStdFunction()));
RunPendingTasks();
renderer.SetPlaybackRate(1.0);
renderer.SetPlaybackRate(0.0);
}
} // namespace
} // namespace media
} // namespace chromecast