| // Copyright (c) 2012 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 "media/base/pipeline_impl.h" |
| |
| #include <stddef.h> |
| #include <memory> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/bind.h" |
| #include "base/location.h" |
| #include "base/macros.h" |
| #include "base/run_loop.h" |
| #include "base/single_thread_task_runner.h" |
| #include "base/test/gmock_callback_support.h" |
| #include "base/test/simple_test_tick_clock.h" |
| #include "base/test/task_environment.h" |
| #include "base/threading/simple_thread.h" |
| #include "base/threading/thread_task_runner_handle.h" |
| #include "base/time/clock.h" |
| #include "media/base/fake_text_track_stream.h" |
| #include "media/base/media_util.h" |
| #include "media/base/mock_filters.h" |
| #include "media/base/test_helpers.h" |
| #include "media/base/text_renderer.h" |
| #include "media/base/text_track_config.h" |
| #include "media/base/time_delta_interpolator.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "ui/gfx/geometry/size.h" |
| |
| using ::base::test::RunClosure; |
| using ::base::test::RunOnceCallback; |
| using ::base::test::RunOnceClosure; |
| using ::testing::_; |
| using ::testing::AnyNumber; |
| using ::testing::DeleteArg; |
| using ::testing::DoAll; |
| using ::testing::Invoke; |
| using ::testing::InvokeWithoutArgs; |
| using ::testing::Mock; |
| using ::testing::NiceMock; |
| using ::testing::NotNull; |
| using ::testing::Return; |
| using ::testing::SaveArg; |
| using ::testing::StrictMock; |
| using ::testing::WithArg; |
| |
| namespace media { |
| |
| ACTION_P(SetDemuxerProperties, duration) { |
| arg0->SetDuration(duration); |
| } |
| |
| ACTION_P(Stop, pipeline) { |
| pipeline->Stop(); |
| } |
| |
| ACTION_P(PostStop, pipeline) { |
| base::ThreadTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, base::BindOnce(&Pipeline::Stop, base::Unretained(pipeline))); |
| } |
| |
| ACTION_P2(SetError, renderer_client, status) { |
| (*renderer_client)->OnError(status); |
| } |
| |
| ACTION_P3(SetBufferingState, renderer_client, buffering_state, reason) { |
| (*renderer_client)->OnBufferingStateChange(buffering_state, reason); |
| } |
| |
| ACTION_TEMPLATE(PostCallback, |
| HAS_1_TEMPLATE_PARAMS(int, k), |
| AND_1_VALUE_PARAMS(p0)) { |
| base::ThreadTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, base::BindOnce(std::move(std::get<k>(args)), p0)); |
| } |
| |
| // TODO(scherkus): even though some filters are initialized on separate |
| // threads these test aren't flaky... why? It's because filters' Initialize() |
| // is executed on |message_loop_| and the mock filters instantly call |
| // InitializationComplete(), which keeps the pipeline humming along. If |
| // either filters don't call InitializationComplete() immediately or filter |
| // initialization is moved to a separate thread this test will become flaky. |
| class PipelineImplTest : public ::testing::Test { |
| public: |
| // Used for setting expectations on pipeline callbacks. Using a StrictMock |
| // also lets us test for missing callbacks. |
| class CallbackHelper : public MockPipelineClient { |
| public: |
| CallbackHelper() = default; |
| |
| CallbackHelper(const CallbackHelper&) = delete; |
| CallbackHelper& operator=(const CallbackHelper&) = delete; |
| |
| virtual ~CallbackHelper() = default; |
| |
| MOCK_METHOD1(OnStart, void(PipelineStatus)); |
| MOCK_METHOD1(OnSeek, void(PipelineStatus)); |
| MOCK_METHOD1(OnSuspend, void(PipelineStatus)); |
| MOCK_METHOD1(OnResume, void(PipelineStatus)); |
| MOCK_METHOD1(OnCdmAttached, void(bool)); |
| }; |
| |
| PipelineImplTest() |
| : demuxer_(new StrictMock<MockDemuxer>()), |
| demuxer_host_(nullptr), |
| scoped_renderer_(new StrictMock<MockRenderer>()), |
| renderer_(scoped_renderer_.get()), |
| renderer_client_(nullptr) { |
| pipeline_ = std::make_unique<PipelineImpl>( |
| task_environment_.GetMainThreadTaskRunner(), |
| task_environment_.GetMainThreadTaskRunner(), |
| base::BindRepeating(&PipelineImplTest::CreateRenderer, |
| base::Unretained(this)), |
| &media_log_); |
| |
| // SetDemuxerExpectations() adds overriding expectations for expected |
| // non-NULL streams. |
| std::vector<DemuxerStream*> empty; |
| EXPECT_CALL(*demuxer_, GetAllStreams()).WillRepeatedly(Return(empty)); |
| |
| EXPECT_CALL(*demuxer_, GetTimelineOffset()) |
| .WillRepeatedly(Return(base::Time())); |
| |
| EXPECT_CALL(*renderer_, GetMediaTime()) |
| .WillRepeatedly(Return(base::TimeDelta())); |
| |
| EXPECT_CALL(*demuxer_, GetStartTime()).WillRepeatedly(Return(start_time_)); |
| |
| EXPECT_CALL(*renderer_, SetPreservesPitch(true)).Times(AnyNumber()); |
| } |
| |
| PipelineImplTest(const PipelineImplTest&) = delete; |
| PipelineImplTest& operator=(const PipelineImplTest&) = delete; |
| |
| ~PipelineImplTest() override { |
| if (pipeline_->IsRunning()) { |
| ExpectDemuxerStop(); |
| |
| pipeline_->Stop(); |
| } |
| |
| pipeline_.reset(); |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| void OnDemuxerError() { demuxer_host_->OnDemuxerError(PIPELINE_ERROR_ABORT); } |
| |
| protected: |
| // Sets up expectations to allow the demuxer to initialize. |
| void SetDemuxerExpectations(base::TimeDelta duration) { |
| EXPECT_CALL(callbacks_, OnDurationChange()); |
| EXPECT_CALL(*demuxer_, OnInitialize(_, _)) |
| .WillOnce(DoAll(SaveArg<0>(&demuxer_host_), |
| SetDemuxerProperties(duration), |
| PostCallback<1>(PIPELINE_OK))); |
| EXPECT_CALL(*demuxer_, GetAllStreams()).WillRepeatedly(Return(streams_)); |
| } |
| |
| void SetDemuxerExpectations() { |
| // Initialize with a default non-zero duration. |
| SetDemuxerExpectations(base::Seconds(10)); |
| } |
| |
| std::unique_ptr<StrictMock<MockDemuxerStream>> CreateStream( |
| DemuxerStream::Type type) { |
| std::unique_ptr<StrictMock<MockDemuxerStream>> stream( |
| new StrictMock<MockDemuxerStream>(type)); |
| return stream; |
| } |
| |
| // Sets up expectations to allow the renderer to initialize. |
| void ExpectRendererInitialization() { |
| EXPECT_CALL(*renderer_, OnInitialize(_, _, _)) |
| .WillOnce( |
| DoAll(SaveArg<1>(&renderer_client_), PostCallback<2>(PIPELINE_OK))); |
| } |
| |
| void StartPipeline( |
| Pipeline::StartType start_type = Pipeline::StartType::kNormal) { |
| EXPECT_CALL(callbacks_, OnWaiting(_)).Times(0); |
| pipeline_->Start(start_type, demuxer_.get(), &callbacks_, |
| base::BindOnce(&CallbackHelper::OnStart, |
| base::Unretained(&callbacks_))); |
| } |
| |
| void SetRendererPostStartExpectations() { |
| EXPECT_CALL(*renderer_, SetPlaybackRate(0.0)); |
| EXPECT_CALL(*renderer_, SetVolume(1.0f)); |
| EXPECT_CALL(*renderer_, StartPlayingFrom(start_time_)) |
| .WillOnce(SetBufferingState(&renderer_client_, BUFFERING_HAVE_ENOUGH, |
| BUFFERING_CHANGE_REASON_UNKNOWN)); |
| EXPECT_CALL(callbacks_, |
| OnBufferingStateChange(BUFFERING_HAVE_ENOUGH, |
| BUFFERING_CHANGE_REASON_UNKNOWN)); |
| } |
| |
| // Suspension status of the pipeline post Start(). |
| enum class PostStartStatus { |
| kNormal, |
| kSuspended, |
| }; |
| |
| // Sets up expectations on the callback and initializes the pipeline. Called |
| // after tests have set expectations any filters they wish to use. |
| void StartPipelineAndExpect( |
| PipelineStatus start_status, |
| Pipeline::StartType start_type = Pipeline::StartType::kNormal, |
| PostStartStatus post_start_status = PostStartStatus::kNormal) { |
| EXPECT_CALL(callbacks_, OnStart(start_status)); |
| |
| if (start_status == PIPELINE_OK) { |
| EXPECT_CALL(callbacks_, OnMetadata(_)).WillOnce(SaveArg<0>(&metadata_)); |
| |
| if (start_type == Pipeline::StartType::kNormal) |
| ExpectRendererInitialization(); |
| |
| if (post_start_status == PostStartStatus::kNormal) |
| SetRendererPostStartExpectations(); |
| } |
| |
| StartPipeline(start_type); |
| base::RunLoop().RunUntilIdle(); |
| |
| if (start_status == PIPELINE_OK) |
| EXPECT_TRUE(pipeline_->IsRunning()); |
| |
| if (post_start_status != PostStartStatus::kNormal) |
| EXPECT_TRUE(pipeline_->IsSuspended()); |
| } |
| |
| void CreateAudioStream() { |
| audio_stream_ = CreateStream(DemuxerStream::AUDIO); |
| streams_.push_back(audio_stream_.get()); |
| } |
| |
| void CreateVideoStream(bool is_encrypted = false) { |
| video_stream_ = CreateStream(DemuxerStream::VIDEO); |
| video_stream_->set_video_decoder_config( |
| is_encrypted ? TestVideoConfig::NormalEncrypted() |
| : TestVideoConfig::Normal()); |
| streams_.push_back(video_stream_.get()); |
| } |
| |
| void CreateAudioAndVideoStream() { |
| CreateAudioStream(); |
| CreateVideoStream(); |
| } |
| |
| void CreateEncryptedVideoStream() { CreateVideoStream(true); } |
| |
| void SetCdmAndExpect(bool expected_result) { |
| EXPECT_CALL(*renderer_, OnSetCdm(_, _)).WillOnce(RunOnceCallback<1>(true)); |
| EXPECT_CALL(callbacks_, OnCdmAttached(expected_result)); |
| pipeline_->SetCdm(&cdm_context_, |
| base::BindOnce(&CallbackHelper::OnCdmAttached, |
| base::Unretained(&callbacks_))); |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| void ExpectSeek(const base::TimeDelta& seek_time, bool underflowed) { |
| EXPECT_CALL(*demuxer_, AbortPendingReads()); |
| EXPECT_CALL(*demuxer_, OnSeek(seek_time, _)) |
| .WillOnce(RunOnceCallback<1>(PIPELINE_OK)); |
| |
| EXPECT_CALL(*renderer_, OnFlush(_)).WillOnce(RunOnceClosure<0>()); |
| EXPECT_CALL(*renderer_, SetPlaybackRate(_)); |
| EXPECT_CALL(*renderer_, StartPlayingFrom(seek_time)) |
| .WillOnce(SetBufferingState(&renderer_client_, BUFFERING_HAVE_ENOUGH, |
| BUFFERING_CHANGE_REASON_UNKNOWN)); |
| |
| // We expect a successful seek callback followed by a buffering update. |
| EXPECT_CALL(callbacks_, OnSeek(PIPELINE_OK)); |
| EXPECT_CALL(callbacks_, |
| OnBufferingStateChange(BUFFERING_HAVE_ENOUGH, |
| BUFFERING_CHANGE_REASON_UNKNOWN)); |
| } |
| |
| void DoSeek(const base::TimeDelta& seek_time) { |
| pipeline_->Seek(seek_time, base::BindOnce(&CallbackHelper::OnSeek, |
| base::Unretained(&callbacks_))); |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| void ExpectSuspend() { |
| EXPECT_CALL(*demuxer_, AbortPendingReads()); |
| EXPECT_CALL(*renderer_, SetPlaybackRate(0)); |
| EXPECT_CALL(callbacks_, OnSuspend(PIPELINE_OK)); |
| } |
| |
| void DoSuspend() { |
| pipeline_->Suspend(base::BindOnce(&CallbackHelper::OnSuspend, |
| base::Unretained(&callbacks_))); |
| base::RunLoop().RunUntilIdle(); |
| ResetRenderer(); |
| } |
| |
| std::unique_ptr<Renderer> CreateRenderer( |
| absl::optional<RendererType> /* renderer_type */) { |
| return std::move(scoped_renderer_); |
| } |
| |
| void ResetRenderer() { |
| // |renderer_| has been deleted, replace it. |
| scoped_renderer_ = std::make_unique<StrictMock<MockRenderer>>(); |
| renderer_ = scoped_renderer_.get(); |
| EXPECT_CALL(*renderer_, SetPreservesPitch(_)).Times(AnyNumber()); |
| } |
| |
| void ExpectResume(const base::TimeDelta& seek_time) { |
| ExpectRendererInitialization(); |
| EXPECT_CALL(*demuxer_, OnSeek(seek_time, _)) |
| .WillOnce(RunOnceCallback<1>(PIPELINE_OK)); |
| EXPECT_CALL(*renderer_, SetPlaybackRate(_)); |
| EXPECT_CALL(*renderer_, SetVolume(_)); |
| EXPECT_CALL(*renderer_, StartPlayingFrom(seek_time)) |
| .WillOnce(SetBufferingState(&renderer_client_, BUFFERING_HAVE_ENOUGH, |
| BUFFERING_CHANGE_REASON_UNKNOWN)); |
| EXPECT_CALL(callbacks_, |
| OnBufferingStateChange(BUFFERING_HAVE_ENOUGH, |
| BUFFERING_CHANGE_REASON_UNKNOWN)); |
| EXPECT_CALL(callbacks_, OnResume(PIPELINE_OK)); |
| } |
| |
| void DoResume(const base::TimeDelta& seek_time) { |
| pipeline_->Resume(seek_time, base::BindOnce(&CallbackHelper::OnResume, |
| base::Unretained(&callbacks_))); |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| void ExpectDemuxerStop() { |
| if (demuxer_) |
| EXPECT_CALL(*demuxer_, Stop()); |
| } |
| |
| void RunBufferedTimeRangesTest(const base::TimeDelta duration) { |
| EXPECT_EQ(0u, pipeline_->GetBufferedTimeRanges().size()); |
| EXPECT_FALSE(pipeline_->DidLoadingProgress()); |
| |
| Ranges<base::TimeDelta> ranges; |
| ranges.Add(base::TimeDelta(), duration); |
| demuxer_host_->OnBufferedTimeRangesChanged(ranges); |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_TRUE(pipeline_->DidLoadingProgress()); |
| EXPECT_FALSE(pipeline_->DidLoadingProgress()); |
| EXPECT_EQ(1u, pipeline_->GetBufferedTimeRanges().size()); |
| EXPECT_EQ(base::TimeDelta(), pipeline_->GetBufferedTimeRanges().start(0)); |
| EXPECT_EQ(duration, pipeline_->GetBufferedTimeRanges().end(0)); |
| } |
| |
| // Fixture members. |
| StrictMock<CallbackHelper> callbacks_; |
| base::SimpleTestTickClock test_tick_clock_; |
| base::test::SingleThreadTaskEnvironment task_environment_; |
| NullMediaLog media_log_; |
| std::unique_ptr<PipelineImpl> pipeline_; |
| NiceMock<MockCdmContext> cdm_context_; |
| |
| std::unique_ptr<StrictMock<MockDemuxer>> demuxer_; |
| DemuxerHost* demuxer_host_; |
| std::unique_ptr<StrictMock<MockRenderer>> scoped_renderer_; |
| StrictMock<MockRenderer>* renderer_; |
| std::unique_ptr<StrictMock<MockDemuxerStream>> audio_stream_; |
| std::unique_ptr<StrictMock<MockDemuxerStream>> video_stream_; |
| std::vector<DemuxerStream*> streams_; |
| RendererClient* renderer_client_; |
| VideoDecoderConfig video_decoder_config_; |
| PipelineMetadata metadata_; |
| base::TimeDelta start_time_; |
| }; |
| |
| // Test that playback controls methods can be set even before the pipeline is |
| // started. |
| TEST_F(PipelineImplTest, ControlMethods) { |
| const base::TimeDelta kZero; |
| |
| EXPECT_FALSE(pipeline_->IsRunning()); |
| |
| // Initial value. |
| EXPECT_EQ(0.0f, pipeline_->GetPlaybackRate()); |
| // Invalid values cannot be set. |
| pipeline_->SetPlaybackRate(-1.0); |
| EXPECT_EQ(0.0f, pipeline_->GetPlaybackRate()); |
| // Valid settings should work. |
| pipeline_->SetPlaybackRate(1.0); |
| EXPECT_EQ(1.0f, pipeline_->GetPlaybackRate()); |
| |
| // Initial value. |
| EXPECT_EQ(1.0f, pipeline_->GetVolume()); |
| // Invalid values cannot be set. |
| pipeline_->SetVolume(-1.0f); |
| EXPECT_EQ(1.0f, pipeline_->GetVolume()); |
| // Valid settings should work. |
| pipeline_->SetVolume(0.0f); |
| EXPECT_EQ(0.0f, pipeline_->GetVolume()); |
| |
| EXPECT_TRUE(kZero == pipeline_->GetMediaTime()); |
| EXPECT_EQ(0u, pipeline_->GetBufferedTimeRanges().size()); |
| EXPECT_TRUE(kZero == pipeline_->GetMediaDuration()); |
| } |
| |
| TEST_F(PipelineImplTest, NeverInitializes) { |
| // Don't execute the callback passed into Initialize(). |
| EXPECT_CALL(*demuxer_, OnInitialize(_, _)); |
| |
| // This test hangs during initialization by never calling |
| // InitializationComplete(). StrictMock<> will ensure that the callback is |
| // never executed. |
| StartPipeline(); |
| base::RunLoop().RunUntilIdle(); |
| |
| // Because our callback will get executed when the test tears down, we'll |
| // verify that nothing has been called, then set our expectation for the call |
| // made during tear down. |
| Mock::VerifyAndClear(&callbacks_); |
| } |
| |
| TEST_F(PipelineImplTest, StopWithoutStart) { |
| pipeline_->Stop(); |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| TEST_F(PipelineImplTest, StartThenStopImmediately) { |
| EXPECT_CALL(*demuxer_, OnInitialize(_, _)) |
| .WillOnce(PostCallback<1>(PIPELINE_OK)); |
| EXPECT_CALL(*demuxer_, Stop()); |
| EXPECT_CALL(callbacks_, OnMetadata(_)); |
| |
| EXPECT_CALL(callbacks_, OnStart(_)); |
| StartPipeline(); |
| base::RunLoop().RunUntilIdle(); |
| |
| pipeline_->Stop(); |
| } |
| |
| TEST_F(PipelineImplTest, StartSuspendedAndResumeAudioOnly) { |
| CreateAudioStream(); |
| SetDemuxerExpectations(base::Seconds(3000)); |
| |
| StartPipelineAndExpect(PIPELINE_OK, |
| Pipeline::StartType::kSuspendAfterMetadataForAudioOnly, |
| PostStartStatus::kSuspended); |
| ASSERT_TRUE(pipeline_->IsSuspended()); |
| |
| ResetRenderer(); |
| base::TimeDelta expected = base::Seconds(2000); |
| ExpectResume(expected); |
| DoResume(expected); |
| } |
| |
| TEST_F(PipelineImplTest, StartSuspendedAndResumeAudioVideo) { |
| CreateAudioAndVideoStream(); |
| SetDemuxerExpectations(base::Seconds(3000)); |
| |
| StartPipelineAndExpect(PIPELINE_OK, |
| Pipeline::StartType::kSuspendAfterMetadata, |
| PostStartStatus::kSuspended); |
| ASSERT_TRUE(pipeline_->IsSuspended()); |
| |
| ResetRenderer(); |
| base::TimeDelta expected = base::Seconds(2000); |
| ExpectResume(expected); |
| DoResume(expected); |
| } |
| |
| TEST_F(PipelineImplTest, StartSuspendedFailsOnVideoWithAudioOnlyExpectation) { |
| CreateAudioAndVideoStream(); |
| SetDemuxerExpectations(base::Seconds(3000)); |
| |
| // StartType kSuspendAfterMetadataForAudioOnly only applies to AudioOnly. |
| // Since this playback has video, renderer will be initialized and the |
| // pipeline is not suspended. |
| ExpectRendererInitialization(); |
| StartPipelineAndExpect(PIPELINE_OK, |
| Pipeline::StartType::kSuspendAfterMetadataForAudioOnly, |
| PostStartStatus::kNormal); |
| ASSERT_FALSE(pipeline_->IsSuspended()); |
| } |
| |
| TEST_F(PipelineImplTest, DemuxerErrorDuringStop) { |
| CreateAudioStream(); |
| SetDemuxerExpectations(); |
| |
| StartPipelineAndExpect(PIPELINE_OK); |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_CALL(*demuxer_, Stop()) |
| .WillOnce(InvokeWithoutArgs(this, &PipelineImplTest::OnDemuxerError)); |
| pipeline_->Stop(); |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| TEST_F(PipelineImplTest, NoStreams) { |
| EXPECT_CALL(*demuxer_, OnInitialize(_, _)) |
| .WillOnce(PostCallback<1>(PIPELINE_OK)); |
| EXPECT_CALL(callbacks_, OnMetadata(_)); |
| |
| StartPipelineAndExpect(PIPELINE_ERROR_COULD_NOT_RENDER); |
| } |
| |
| TEST_F(PipelineImplTest, AudioStream) { |
| CreateAudioStream(); |
| SetDemuxerExpectations(); |
| |
| StartPipelineAndExpect(PIPELINE_OK); |
| EXPECT_TRUE(metadata_.has_audio); |
| EXPECT_FALSE(metadata_.has_video); |
| } |
| |
| TEST_F(PipelineImplTest, VideoStream) { |
| CreateVideoStream(); |
| SetDemuxerExpectations(); |
| |
| StartPipelineAndExpect(PIPELINE_OK); |
| EXPECT_FALSE(metadata_.has_audio); |
| EXPECT_TRUE(metadata_.has_video); |
| } |
| |
| TEST_F(PipelineImplTest, AudioVideoStream) { |
| CreateAudioAndVideoStream(); |
| SetDemuxerExpectations(); |
| |
| StartPipelineAndExpect(PIPELINE_OK); |
| EXPECT_TRUE(metadata_.has_audio); |
| EXPECT_TRUE(metadata_.has_video); |
| } |
| |
| TEST_F(PipelineImplTest, EncryptedStream_SetCdmBeforeStart) { |
| CreateEncryptedVideoStream(); |
| SetDemuxerExpectations(); |
| |
| SetCdmAndExpect(true); |
| StartPipelineAndExpect(PIPELINE_OK); |
| } |
| |
| TEST_F(PipelineImplTest, EncryptedStream_SetCdmAfterStart) { |
| CreateEncryptedVideoStream(); |
| SetDemuxerExpectations(); |
| |
| // Demuxer initialization and metadata reporting don't wait for CDM. |
| EXPECT_CALL(callbacks_, OnMetadata(_)).WillOnce(SaveArg<0>(&metadata_)); |
| |
| base::RunLoop run_loop; |
| EXPECT_CALL(callbacks_, OnWaiting(_)) |
| .WillOnce(RunClosure(run_loop.QuitClosure())); |
| pipeline_->Start( |
| Pipeline::StartType::kNormal, demuxer_.get(), &callbacks_, |
| base::BindOnce(&CallbackHelper::OnStart, base::Unretained(&callbacks_))); |
| run_loop.Run(); |
| |
| ExpectRendererInitialization(); |
| EXPECT_CALL(callbacks_, OnStart(PIPELINE_OK)); |
| SetRendererPostStartExpectations(); |
| SetCdmAndExpect(true); |
| } |
| |
| TEST_F(PipelineImplTest, Seek) { |
| CreateAudioAndVideoStream(); |
| SetDemuxerExpectations(base::Seconds(3000)); |
| |
| // Initialize then seek! |
| StartPipelineAndExpect(PIPELINE_OK); |
| |
| // Every filter should receive a call to Seek(). |
| base::TimeDelta expected = base::Seconds(2000); |
| ExpectSeek(expected, false); |
| DoSeek(expected); |
| } |
| |
| TEST_F(PipelineImplTest, SeekAfterError) { |
| CreateAudioStream(); |
| SetDemuxerExpectations(base::Seconds(3000)); |
| |
| // Initialize then seek! |
| StartPipelineAndExpect(PIPELINE_OK); |
| |
| // Pipeline::Client is supposed to call Pipeline::Stop() after errors. |
| EXPECT_CALL(callbacks_, OnError(_)).WillOnce(Stop(pipeline_.get())); |
| EXPECT_CALL(*demuxer_, Stop()); |
| OnDemuxerError(); |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_CALL(callbacks_, OnSeek(PIPELINE_ERROR_INVALID_STATE)); |
| pipeline_->Seek( |
| base::Milliseconds(100), |
| base::BindOnce(&CallbackHelper::OnSeek, base::Unretained(&callbacks_))); |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| TEST_F(PipelineImplTest, SuspendResume) { |
| CreateAudioAndVideoStream(); |
| SetDemuxerExpectations(base::Seconds(3000)); |
| |
| StartPipelineAndExpect(PIPELINE_OK); |
| |
| // Inject some fake memory usage to verify its cleared after suspend. |
| PipelineStatistics stats; |
| stats.audio_memory_usage = 12345; |
| stats.video_memory_usage = 67890; |
| renderer_client_->OnStatisticsUpdate(stats); |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_EQ(stats.audio_memory_usage, |
| pipeline_->GetStatistics().audio_memory_usage); |
| EXPECT_EQ(stats.video_memory_usage, |
| pipeline_->GetStatistics().video_memory_usage); |
| |
| // Make sure the preserves pitch flag is preserved between after resuming. |
| EXPECT_CALL(*renderer_, SetPreservesPitch(false)).Times(1); |
| pipeline_->SetPreservesPitch(false); |
| |
| ExpectSuspend(); |
| DoSuspend(); |
| |
| EXPECT_EQ(0, pipeline_->GetStatistics().audio_memory_usage); |
| EXPECT_EQ(0, pipeline_->GetStatistics().video_memory_usage); |
| |
| base::TimeDelta expected = base::Seconds(2000); |
| ExpectResume(expected); |
| EXPECT_CALL(*renderer_, SetPreservesPitch(false)).Times(1); |
| |
| DoResume(expected); |
| } |
| |
| TEST_F(PipelineImplTest, SetVolume) { |
| CreateAudioStream(); |
| SetDemuxerExpectations(); |
| |
| // The audio renderer should receive a call to SetVolume(). |
| float expected = 0.5f; |
| EXPECT_CALL(*renderer_, SetVolume(expected)); |
| |
| // Initialize then set volume! |
| StartPipelineAndExpect(PIPELINE_OK); |
| pipeline_->SetVolume(expected); |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| TEST_F(PipelineImplTest, SetVolumeDuringStartup) { |
| CreateAudioStream(); |
| SetDemuxerExpectations(); |
| |
| // The audio renderer should receive two calls to SetVolume(). |
| float expected = 0.5f; |
| EXPECT_CALL(*renderer_, SetVolume(expected)).Times(2); |
| EXPECT_CALL(callbacks_, OnStart(PIPELINE_OK)); |
| EXPECT_CALL(callbacks_, OnMetadata(_)) |
| .WillOnce(RunOnceClosure(base::BindOnce(&PipelineImpl::SetVolume, |
| base::Unretained(pipeline_.get()), |
| expected))); |
| ExpectRendererInitialization(); |
| EXPECT_CALL(*renderer_, SetPlaybackRate(0.0)); |
| EXPECT_CALL(*renderer_, StartPlayingFrom(start_time_)) |
| .WillOnce(SetBufferingState(&renderer_client_, BUFFERING_HAVE_ENOUGH, |
| BUFFERING_CHANGE_REASON_UNKNOWN)); |
| EXPECT_CALL(callbacks_, |
| OnBufferingStateChange(BUFFERING_HAVE_ENOUGH, |
| BUFFERING_CHANGE_REASON_UNKNOWN)); |
| StartPipeline(); |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| TEST_F(PipelineImplTest, SetPreservesPitch) { |
| CreateAudioStream(); |
| SetDemuxerExpectations(); |
| |
| // The audio renderer preserve pitch by default. |
| EXPECT_CALL(*renderer_, SetPreservesPitch(true)); |
| StartPipelineAndExpect(PIPELINE_OK); |
| base::RunLoop().RunUntilIdle(); |
| |
| // Changes to the preservesPitch flag should be propagated. |
| EXPECT_CALL(*renderer_, SetPreservesPitch(false)); |
| pipeline_->SetPreservesPitch(false); |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| TEST_F(PipelineImplTest, Properties) { |
| CreateVideoStream(); |
| const auto kDuration = base::Seconds(100); |
| SetDemuxerExpectations(kDuration); |
| |
| StartPipelineAndExpect(PIPELINE_OK); |
| EXPECT_EQ(kDuration.ToInternalValue(), |
| pipeline_->GetMediaDuration().ToInternalValue()); |
| EXPECT_FALSE(pipeline_->DidLoadingProgress()); |
| } |
| |
| TEST_F(PipelineImplTest, GetBufferedTimeRanges) { |
| CreateVideoStream(); |
| const auto kDuration = base::Seconds(100); |
| SetDemuxerExpectations(kDuration); |
| |
| StartPipelineAndExpect(PIPELINE_OK); |
| RunBufferedTimeRangesTest(kDuration / 8); |
| |
| base::TimeDelta kSeekTime = kDuration / 2; |
| ExpectSeek(kSeekTime, false); |
| DoSeek(kSeekTime); |
| |
| EXPECT_FALSE(pipeline_->DidLoadingProgress()); |
| } |
| |
| TEST_F(PipelineImplTest, BufferedTimeRangesCanChangeAfterStop) { |
| EXPECT_CALL(*demuxer_, OnInitialize(_, _)) |
| .WillOnce( |
| DoAll(SaveArg<0>(&demuxer_host_), PostCallback<1>(PIPELINE_OK))); |
| EXPECT_CALL(*demuxer_, Stop()); |
| EXPECT_CALL(callbacks_, OnMetadata(_)); |
| EXPECT_CALL(callbacks_, OnStart(_)); |
| StartPipeline(); |
| base::RunLoop().RunUntilIdle(); |
| |
| pipeline_->Stop(); |
| RunBufferedTimeRangesTest(base::Seconds(5)); |
| } |
| |
| TEST_F(PipelineImplTest, OnStatisticsUpdate) { |
| CreateAudioAndVideoStream(); |
| SetDemuxerExpectations(); |
| StartPipelineAndExpect(PIPELINE_OK); |
| |
| PipelineStatistics stats; |
| stats.audio_pipeline_info.decoder_type = AudioDecoderType::kMojo; |
| stats.audio_pipeline_info.is_platform_decoder = false; |
| EXPECT_CALL(callbacks_, OnAudioPipelineInfoChange(_)); |
| renderer_client_->OnStatisticsUpdate(stats); |
| base::RunLoop().RunUntilIdle(); |
| |
| // VideoPipelineInfo changed and we expect OnVideoPipelineInfoChange() to be |
| // called. |
| stats.video_pipeline_info.decoder_type = VideoDecoderType::kMojo; |
| stats.video_pipeline_info.is_platform_decoder = true; |
| EXPECT_CALL(callbacks_, OnVideoPipelineInfoChange(_)); |
| renderer_client_->OnStatisticsUpdate(stats); |
| base::RunLoop().RunUntilIdle(); |
| |
| // OnStatisticsUpdate() with the same |stats| should not cause new |
| // PipelineClient calls. |
| renderer_client_->OnStatisticsUpdate(stats); |
| base::RunLoop().RunUntilIdle(); |
| |
| // AudioPipelineInfo changed and we expect OnAudioPipelineInfoChange() to be |
| // called. |
| stats.audio_pipeline_info.is_platform_decoder = true; |
| EXPECT_CALL(callbacks_, OnAudioPipelineInfoChange(_)); |
| renderer_client_->OnStatisticsUpdate(stats); |
| base::RunLoop().RunUntilIdle(); |
| |
| // Both info changed. |
| stats.audio_pipeline_info.decoder_type = AudioDecoderType::kFFmpeg; |
| stats.video_pipeline_info.has_decrypting_demuxer_stream = true; |
| EXPECT_CALL(callbacks_, OnAudioPipelineInfoChange(_)); |
| EXPECT_CALL(callbacks_, OnVideoPipelineInfoChange(_)); |
| renderer_client_->OnStatisticsUpdate(stats); |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| TEST_F(PipelineImplTest, EndedCallback) { |
| CreateAudioAndVideoStream(); |
| SetDemuxerExpectations(); |
| StartPipelineAndExpect(PIPELINE_OK); |
| |
| // The ended callback shouldn't run until all renderers have ended. |
| EXPECT_CALL(callbacks_, OnEnded()); |
| renderer_client_->OnEnded(); |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| TEST_F(PipelineImplTest, DemuxerErrorDuringSeek) { |
| CreateAudioStream(); |
| SetDemuxerExpectations(); |
| StartPipelineAndExpect(PIPELINE_OK); |
| |
| double playback_rate = 1.0; |
| EXPECT_CALL(*renderer_, SetPlaybackRate(playback_rate)); |
| pipeline_->SetPlaybackRate(playback_rate); |
| base::RunLoop().RunUntilIdle(); |
| |
| base::TimeDelta seek_time = base::Seconds(5); |
| |
| EXPECT_CALL(*renderer_, OnFlush(_)).WillOnce(RunOnceClosure<0>()); |
| |
| EXPECT_CALL(*demuxer_, AbortPendingReads()); |
| EXPECT_CALL(*demuxer_, OnSeek(seek_time, _)) |
| .WillOnce(RunOnceCallback<1>(PIPELINE_ERROR_READ)); |
| EXPECT_CALL(*demuxer_, Stop()); |
| |
| pipeline_->Seek(seek_time, base::BindOnce(&CallbackHelper::OnSeek, |
| base::Unretained(&callbacks_))); |
| EXPECT_CALL(callbacks_, OnSeek(PIPELINE_ERROR_READ)) |
| .WillOnce(Stop(pipeline_.get())); |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| TEST_F(PipelineImplTest, PipelineErrorDuringSeek) { |
| CreateAudioStream(); |
| SetDemuxerExpectations(); |
| StartPipelineAndExpect(PIPELINE_OK); |
| |
| base::TimeDelta seek_time = base::Seconds(5); |
| |
| // Set expectations for seek. |
| EXPECT_CALL(*renderer_, OnFlush(_)).WillOnce(RunOnceClosure<0>()); |
| EXPECT_CALL(*renderer_, SetPlaybackRate(_)); |
| EXPECT_CALL(*renderer_, StartPlayingFrom(seek_time)); |
| EXPECT_CALL(*demuxer_, AbortPendingReads()); |
| EXPECT_CALL(*demuxer_, OnSeek(seek_time, _)) |
| .WillOnce(RunOnceCallback<1>(PIPELINE_OK)); |
| EXPECT_CALL(callbacks_, OnSeek(PIPELINE_ERROR_DECODE)); |
| |
| // Triggers pipeline error during pending seek. |
| pipeline_->Seek(seek_time, base::BindOnce(&CallbackHelper::OnSeek, |
| base::Unretained(&callbacks_))); |
| renderer_client_->OnError(PIPELINE_ERROR_DECODE); |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| TEST_F(PipelineImplTest, PipelineErrorDuringSuspend) { |
| CreateAudioAndVideoStream(); |
| SetDemuxerExpectations(base::Seconds(3000)); |
| StartPipelineAndExpect(PIPELINE_OK); |
| |
| // Set expectations for suspend. |
| EXPECT_CALL(*demuxer_, AbortPendingReads()); |
| EXPECT_CALL(*renderer_, SetPlaybackRate(0)); |
| EXPECT_CALL(callbacks_, OnSuspend(PIPELINE_ERROR_DECODE)); |
| |
| // Triggers pipeline error during pending suspend. The order matters for |
| // reproducing crbug.com/1250636. Otherwise OnError() is ignored if already in |
| // kSuspending state. |
| renderer_client_->OnError(PIPELINE_ERROR_DECODE); |
| pipeline_->Suspend(base::BindOnce(&CallbackHelper::OnSuspend, |
| base::Unretained(&callbacks_))); |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| TEST_F(PipelineImplTest, DestroyAfterStop) { |
| CreateAudioStream(); |
| SetDemuxerExpectations(); |
| StartPipelineAndExpect(PIPELINE_OK); |
| |
| ExpectDemuxerStop(); |
| pipeline_->Stop(); |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| TEST_F(PipelineImplTest, Underflow) { |
| CreateAudioAndVideoStream(); |
| SetDemuxerExpectations(); |
| StartPipelineAndExpect(PIPELINE_OK); |
| |
| // Simulate underflow. |
| EXPECT_CALL(callbacks_, |
| OnBufferingStateChange(BUFFERING_HAVE_NOTHING, |
| BUFFERING_CHANGE_REASON_UNKNOWN)); |
| renderer_client_->OnBufferingStateChange(BUFFERING_HAVE_NOTHING, |
| BUFFERING_CHANGE_REASON_UNKNOWN); |
| base::RunLoop().RunUntilIdle(); |
| |
| // Seek while underflowed. |
| base::TimeDelta expected = base::Seconds(5); |
| ExpectSeek(expected, true); |
| DoSeek(expected); |
| } |
| |
| TEST_F(PipelineImplTest, PositiveStartTime) { |
| start_time_ = base::Seconds(1); |
| EXPECT_CALL(*demuxer_, GetStartTime()).WillRepeatedly(Return(start_time_)); |
| CreateAudioStream(); |
| SetDemuxerExpectations(); |
| StartPipelineAndExpect(PIPELINE_OK); |
| ExpectDemuxerStop(); |
| pipeline_->Stop(); |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| TEST_F(PipelineImplTest, GetMediaTime) { |
| CreateAudioStream(); |
| SetDemuxerExpectations(); |
| StartPipelineAndExpect(PIPELINE_OK); |
| |
| // Pipeline should report the same media time returned by the renderer. |
| base::TimeDelta kMediaTime = base::Seconds(2); |
| EXPECT_CALL(*renderer_, GetMediaTime()).WillRepeatedly(Return(kMediaTime)); |
| EXPECT_EQ(kMediaTime, pipeline_->GetMediaTime()); |
| |
| // Media time should not go backwards even if the renderer returns an |
| // errorneous value. PipelineImpl should clamp it to last reported value. |
| EXPECT_CALL(*renderer_, GetMediaTime()) |
| .WillRepeatedly(Return(base::Seconds(1))); |
| EXPECT_EQ(kMediaTime, pipeline_->GetMediaTime()); |
| } |
| |
| // Seeking posts a task from main thread to media thread to seek the renderer, |
| // resetting its internal clock. Calling GetMediaTime() should be safe even |
| // when the renderer has not performed the seek (simulated by its continuing |
| // to return the pre-seek time). Verifies fix for http://crbug.com/675556 |
| TEST_F(PipelineImplTest, GetMediaTimeAfterSeek) { |
| CreateAudioStream(); |
| SetDemuxerExpectations(); |
| StartPipelineAndExpect(PIPELINE_OK); |
| |
| // Pipeline should report the same media time returned by the renderer. |
| base::TimeDelta kMediaTime = base::Seconds(2); |
| EXPECT_CALL(*renderer_, GetMediaTime()).WillRepeatedly(Return(kMediaTime)); |
| EXPECT_EQ(kMediaTime, pipeline_->GetMediaTime()); |
| |
| // Seek backward 1 second. Do not run RunLoop to ensure renderer is not yet |
| // notified of the seek (via media thread). |
| base::TimeDelta kSeekTime = kMediaTime - base::Seconds(1); |
| ExpectSeek(kSeekTime, false); |
| pipeline_->Seek(kSeekTime, base::BindOnce(&CallbackHelper::OnSeek, |
| base::Unretained(&callbacks_))); |
| |
| // Verify pipeline returns the seek time in spite of renderer returning the |
| // stale media time. |
| EXPECT_EQ(kSeekTime, pipeline_->GetMediaTime()); |
| EXPECT_EQ(kMediaTime, renderer_->GetMediaTime()); |
| |
| // Allow seek task to post to the renderer. |
| base::RunLoop().RunUntilIdle(); |
| |
| // With seek completed, pipeline should again return the renderer's media time |
| // (as long as media time is moving forward). |
| EXPECT_EQ(kMediaTime, pipeline_->GetMediaTime()); |
| } |
| |
| // This test makes sure that, after receiving an error, stopping and starting |
| // the pipeline clears all internal error state, and allows errors to be |
| // propagated again. |
| TEST_F(PipelineImplTest, RendererErrorsReset) { |
| // Basic setup |
| CreateAudioStream(); |
| SetDemuxerExpectations(); |
| StartPipelineAndExpect(PIPELINE_OK); |
| |
| // Trigger two errors. The second error will be ignored. |
| EXPECT_CALL(callbacks_, OnError(PIPELINE_ERROR_READ)).Times(1); |
| renderer_client_->OnError(PIPELINE_ERROR_READ); |
| renderer_client_->OnError(PIPELINE_ERROR_READ); |
| |
| base::RunLoop().RunUntilIdle(); |
| |
| // Stopping the demuxer should clear internal state. |
| EXPECT_CALL(*demuxer_, Stop()); |
| pipeline_->Stop(); |
| |
| base::RunLoop().RunUntilIdle(); |
| |
| ResetRenderer(); |
| SetDemuxerExpectations(); |
| StartPipelineAndExpect(PIPELINE_OK); |
| |
| // New errors should propagate and not be ignored. |
| EXPECT_CALL(callbacks_, OnError(PIPELINE_ERROR_READ)).Times(1); |
| renderer_client_->OnError(PIPELINE_ERROR_READ); |
| |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| class PipelineTeardownTest : public PipelineImplTest { |
| public: |
| enum TeardownState { |
| kInitDemuxer, |
| kInitRenderer, |
| kFlushing, |
| kSeeking, |
| kPlaying, |
| kSuspending, |
| kSuspended, |
| kResuming, |
| }; |
| |
| enum StopOrError { |
| kStop, |
| kError, |
| kErrorAndStop, |
| }; |
| |
| PipelineTeardownTest() = default; |
| |
| PipelineTeardownTest(const PipelineTeardownTest&) = delete; |
| PipelineTeardownTest& operator=(const PipelineTeardownTest&) = delete; |
| |
| ~PipelineTeardownTest() override = default; |
| |
| void RunTest(TeardownState state, StopOrError stop_or_error) { |
| switch (state) { |
| case kInitDemuxer: |
| case kInitRenderer: |
| DoInitialize(state, stop_or_error); |
| break; |
| |
| case kFlushing: |
| case kSeeking: |
| DoInitialize(state, stop_or_error); |
| DoSeek(state, stop_or_error); |
| break; |
| |
| case kPlaying: |
| DoInitialize(state, stop_or_error); |
| DoStopOrError(stop_or_error, true); |
| break; |
| |
| case kSuspending: |
| case kSuspended: |
| case kResuming: |
| DoInitialize(state, stop_or_error); |
| DoSuspend(state, stop_or_error); |
| break; |
| } |
| } |
| |
| private: |
| // TODO(scherkus): We do radically different things whether teardown is |
| // invoked via stop vs error. The teardown path should be the same, |
| // see http://crbug.com/110228 |
| void DoInitialize(TeardownState state, StopOrError stop_or_error) { |
| SetInitializeExpectations(state, stop_or_error); |
| StartPipeline(); |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| void SetInitializeExpectations(TeardownState state, |
| StopOrError stop_or_error) { |
| if (state == kInitDemuxer) { |
| if (stop_or_error == kStop) { |
| EXPECT_CALL(*demuxer_, OnInitialize(_, _)) |
| .WillOnce( |
| DoAll(PostStop(pipeline_.get()), PostCallback<1>(PIPELINE_OK))); |
| // Note: OnStart callback is not called after pipeline is stopped. |
| } else { |
| EXPECT_CALL(*demuxer_, OnInitialize(_, _)) |
| .WillOnce(PostCallback<1>(DEMUXER_ERROR_COULD_NOT_OPEN)); |
| EXPECT_CALL(callbacks_, OnStart(DEMUXER_ERROR_COULD_NOT_OPEN)) |
| .WillOnce(Stop(pipeline_.get())); |
| } |
| |
| EXPECT_CALL(*demuxer_, Stop()); |
| return; |
| } |
| |
| CreateAudioStream(); |
| CreateVideoStream(); |
| SetDemuxerExpectations(base::Seconds(3000)); |
| EXPECT_CALL(*renderer_, SetVolume(1.0f)); |
| |
| if (state == kInitRenderer) { |
| if (stop_or_error == kStop) { |
| EXPECT_CALL(*renderer_, OnInitialize(_, _, _)) |
| .WillOnce( |
| DoAll(PostStop(pipeline_.get()), PostCallback<2>(PIPELINE_OK))); |
| // Note: OnStart is not callback after pipeline is stopped. |
| } else { |
| EXPECT_CALL(*renderer_, OnInitialize(_, _, _)) |
| .WillOnce(PostCallback<2>(PIPELINE_ERROR_INITIALIZATION_FAILED)); |
| EXPECT_CALL(callbacks_, OnStart(PIPELINE_ERROR_INITIALIZATION_FAILED)) |
| .WillOnce(Stop(pipeline_.get())); |
| } |
| |
| EXPECT_CALL(callbacks_, OnMetadata(_)); |
| EXPECT_CALL(*demuxer_, Stop()); |
| return; |
| } |
| |
| EXPECT_CALL(*renderer_, OnInitialize(_, _, _)) |
| .WillOnce( |
| DoAll(SaveArg<1>(&renderer_client_), PostCallback<2>(PIPELINE_OK))); |
| |
| // If we get here it's a successful initialization. |
| EXPECT_CALL(callbacks_, OnStart(PIPELINE_OK)); |
| EXPECT_CALL(callbacks_, OnMetadata(_)); |
| |
| EXPECT_CALL(*renderer_, SetPlaybackRate(0.0)); |
| EXPECT_CALL(*renderer_, StartPlayingFrom(base::TimeDelta())) |
| .WillOnce(SetBufferingState(&renderer_client_, BUFFERING_HAVE_ENOUGH, |
| BUFFERING_CHANGE_REASON_UNKNOWN)); |
| EXPECT_CALL(callbacks_, |
| OnBufferingStateChange(BUFFERING_HAVE_ENOUGH, |
| BUFFERING_CHANGE_REASON_UNKNOWN)); |
| } |
| |
| void DoSeek(TeardownState state, StopOrError stop_or_error) { |
| SetSeekExpectations(state, stop_or_error); |
| |
| EXPECT_CALL(*demuxer_, AbortPendingReads()); |
| EXPECT_CALL(*demuxer_, Stop()); |
| |
| pipeline_->Seek( |
| base::Seconds(10), |
| base::BindOnce(&CallbackHelper::OnSeek, base::Unretained(&callbacks_))); |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| void SetSeekExpectations(TeardownState state, StopOrError stop_or_error) { |
| if (state == kFlushing) { |
| EXPECT_CALL(*demuxer_, OnSeek(_, _)); |
| if (stop_or_error == kStop) { |
| EXPECT_CALL(*renderer_, OnFlush(_)) |
| .WillOnce(DoAll(Stop(pipeline_.get()), RunOnceClosure<0>())); |
| // Note: OnSeek callbacks are not called |
| // after pipeline is stopped. |
| } else { |
| EXPECT_CALL(*renderer_, OnFlush(_)) |
| .WillOnce(DoAll(SetError(&renderer_client_, PIPELINE_ERROR_READ), |
| RunOnceClosure<0>())); |
| EXPECT_CALL(callbacks_, OnSeek(PIPELINE_ERROR_READ)) |
| .WillOnce(Stop(pipeline_.get())); |
| } |
| return; |
| } |
| |
| EXPECT_CALL(*renderer_, OnFlush(_)).WillOnce(RunOnceClosure<0>()); |
| |
| if (state == kSeeking) { |
| if (stop_or_error == kStop) { |
| EXPECT_CALL(*demuxer_, OnSeek(_, _)) |
| .WillOnce(DoAll(PostStop(pipeline_.get()), |
| RunOnceCallback<1>(PIPELINE_OK))); |
| // Note: OnSeek callback is not called after pipeline is stopped. |
| } else { |
| EXPECT_CALL(*demuxer_, OnSeek(_, _)) |
| .WillOnce(RunOnceCallback<1>(PIPELINE_ERROR_READ)); |
| EXPECT_CALL(callbacks_, OnSeek(PIPELINE_ERROR_READ)) |
| .WillOnce(Stop(pipeline_.get())); |
| } |
| return; |
| } |
| |
| NOTREACHED() << "State not supported: " << state; |
| } |
| |
| void DoSuspend(TeardownState state, StopOrError stop_or_error) { |
| SetSuspendExpectations(state, stop_or_error); |
| |
| if (state == kResuming) { |
| EXPECT_CALL(*demuxer_, Stop()); |
| } |
| |
| PipelineImplTest::DoSuspend(); |
| |
| if (state == kResuming) { |
| PipelineImplTest::DoResume(base::TimeDelta()); |
| return; |
| } |
| |
| // kSuspended, kSuspending never throw errors, since Resume() is always able |
| // to restore the pipeline to a pristine state. |
| DoStopOrError(stop_or_error, false); |
| } |
| |
| void SetSuspendExpectations(TeardownState state, StopOrError stop_or_error) { |
| EXPECT_CALL(*renderer_, SetPlaybackRate(0)); |
| EXPECT_CALL(*demuxer_, AbortPendingReads()); |
| EXPECT_CALL(callbacks_, OnSuspend(PIPELINE_OK)); |
| if (state == kResuming) { |
| if (stop_or_error == kStop) { |
| EXPECT_CALL(*demuxer_, OnSeek(_, _)) |
| .WillOnce(DoAll(PostStop(pipeline_.get()), |
| RunOnceCallback<1>(PIPELINE_OK))); |
| // Note: OnResume callback is not called after pipeline is stopped. |
| } else { |
| EXPECT_CALL(*demuxer_, OnSeek(_, _)) |
| .WillOnce(RunOnceCallback<1>(PIPELINE_ERROR_READ)); |
| EXPECT_CALL(callbacks_, OnResume(PIPELINE_ERROR_READ)) |
| .WillOnce(Stop(pipeline_.get())); |
| } |
| } else if (state != kSuspended && state != kSuspending) { |
| NOTREACHED() << "State not supported: " << state; |
| } |
| } |
| |
| void DoStopOrError(StopOrError stop_or_error, bool expect_errors) { |
| switch (stop_or_error) { |
| case kStop: |
| EXPECT_CALL(*demuxer_, Stop()); |
| pipeline_->Stop(); |
| break; |
| |
| case kError: |
| if (expect_errors) { |
| EXPECT_CALL(*demuxer_, Stop()); |
| EXPECT_CALL(callbacks_, OnError(PIPELINE_ERROR_READ)) |
| .WillOnce(Stop(pipeline_.get())); |
| } |
| renderer_client_->OnError(PIPELINE_ERROR_READ); |
| break; |
| |
| case kErrorAndStop: |
| EXPECT_CALL(*demuxer_, Stop()); |
| if (expect_errors) |
| EXPECT_CALL(callbacks_, OnError(PIPELINE_ERROR_READ)); |
| renderer_client_->OnError(PIPELINE_ERROR_READ); |
| base::RunLoop().RunUntilIdle(); |
| pipeline_->Stop(); |
| break; |
| } |
| |
| base::RunLoop().RunUntilIdle(); |
| } |
| }; |
| |
| #define INSTANTIATE_TEARDOWN_TEST(stop_or_error, state) \ |
| TEST_F(PipelineTeardownTest, stop_or_error##_##state) { \ |
| RunTest(k##state, k##stop_or_error); \ |
| } |
| |
| INSTANTIATE_TEARDOWN_TEST(Stop, InitDemuxer) |
| INSTANTIATE_TEARDOWN_TEST(Stop, InitRenderer) |
| INSTANTIATE_TEARDOWN_TEST(Stop, Flushing) |
| INSTANTIATE_TEARDOWN_TEST(Stop, Seeking) |
| INSTANTIATE_TEARDOWN_TEST(Stop, Playing) |
| INSTANTIATE_TEARDOWN_TEST(Stop, Suspending) |
| INSTANTIATE_TEARDOWN_TEST(Stop, Suspended) |
| INSTANTIATE_TEARDOWN_TEST(Stop, Resuming) |
| |
| INSTANTIATE_TEARDOWN_TEST(Error, InitDemuxer) |
| INSTANTIATE_TEARDOWN_TEST(Error, InitRenderer) |
| INSTANTIATE_TEARDOWN_TEST(Error, Flushing) |
| INSTANTIATE_TEARDOWN_TEST(Error, Seeking) |
| INSTANTIATE_TEARDOWN_TEST(Error, Playing) |
| INSTANTIATE_TEARDOWN_TEST(Error, Suspending) |
| INSTANTIATE_TEARDOWN_TEST(Error, Suspended) |
| INSTANTIATE_TEARDOWN_TEST(Error, Resuming) |
| |
| INSTANTIATE_TEARDOWN_TEST(ErrorAndStop, Playing) |
| INSTANTIATE_TEARDOWN_TEST(ErrorAndStop, Suspended) |
| |
| } // namespace media |