| // 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 "cobalt/media/filters/pipeline_controller.h" |
| |
| #include <memory> |
| |
| #include "base/basictypes.h" |
| #include "base/bind.h" |
| #include "base/bind_helpers.h" |
| #include "base/logging.h" |
| #include "base/memory/ref_counted.h" |
| #include "base/message_loop.h" |
| #include "base/run_loop.h" |
| #include "base/time.h" |
| #include "cobalt/media/base/mock_filters.h" |
| #include "cobalt/media/base/pipeline.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| using ::testing::_; |
| using ::testing::DoAll; |
| using ::testing::Mock; |
| using ::testing::NiceMock; |
| using ::testing::Return; |
| using ::testing::SaveArg; |
| using ::testing::StrictMock; |
| |
| namespace cobalt { |
| namespace media { |
| |
| class PipelineControllerTest : public ::testing::Test, public Pipeline::Client { |
| public: |
| PipelineControllerTest() |
| : pipeline_controller_(&pipeline_, |
| base::Bind(&PipelineControllerTest::CreateRenderer, |
| base::Unretained(this)), |
| base::Bind(&PipelineControllerTest::OnSeeked, |
| base::Unretained(this)), |
| base::Bind(&PipelineControllerTest::OnSuspended, |
| base::Unretained(this)), |
| base::Bind(&PipelineControllerTest::OnError, |
| base::Unretained(this))) {} |
| |
| ~PipelineControllerTest() override {} |
| |
| PipelineStatusCB StartPipeline(bool is_streaming, bool is_static) { |
| EXPECT_FALSE(pipeline_controller_.IsStable()); |
| PipelineStatusCB start_cb; |
| EXPECT_CALL(pipeline_, Start(_, _, _, _)).WillOnce(SaveArg<3>(&start_cb)); |
| pipeline_controller_.Start(&demuxer_, this, is_streaming, is_static); |
| Mock::VerifyAndClear(&pipeline_); |
| EXPECT_FALSE(pipeline_controller_.IsStable()); |
| return start_cb; |
| } |
| |
| PipelineStatusCB StartPipeline() { return StartPipeline(false, true); } |
| |
| PipelineStatusCB StartPipeline_WithDynamicData() { |
| return StartPipeline(false, false); |
| } |
| |
| PipelineStatusCB StartPipeline_WithStreamingData() { |
| return StartPipeline(true, false); |
| } |
| |
| PipelineStatusCB SeekPipeline(base::TimeDelta time) { |
| EXPECT_TRUE(pipeline_controller_.IsStable()); |
| PipelineStatusCB seek_cb; |
| EXPECT_CALL(pipeline_, Seek(time, _)).WillOnce(SaveArg<1>(&seek_cb)); |
| pipeline_controller_.Seek(time, true); |
| Mock::VerifyAndClear(&pipeline_); |
| EXPECT_FALSE(pipeline_controller_.IsStable()); |
| return seek_cb; |
| } |
| |
| PipelineStatusCB SuspendPipeline() { |
| EXPECT_TRUE(pipeline_controller_.IsStable()); |
| PipelineStatusCB suspend_cb; |
| EXPECT_CALL(pipeline_, Suspend(_)).WillOnce(SaveArg<0>(&suspend_cb)); |
| pipeline_controller_.Suspend(); |
| Mock::VerifyAndClear(&pipeline_); |
| EXPECT_TRUE(pipeline_controller_.IsSuspended()); |
| EXPECT_FALSE(pipeline_controller_.IsStable()); |
| EXPECT_FALSE(pipeline_controller_.IsPipelineSuspended()); |
| return suspend_cb; |
| } |
| |
| PipelineStatusCB ResumePipeline() { |
| EXPECT_TRUE(pipeline_controller_.IsPipelineSuspended()); |
| PipelineStatusCB resume_cb; |
| EXPECT_CALL(pipeline_, Resume(_, _, _)) |
| .WillOnce( |
| DoAll(SaveArg<1>(&last_resume_time_), SaveArg<2>(&resume_cb))); |
| EXPECT_CALL(pipeline_, GetMediaTime()) |
| .WillRepeatedly(Return(base::TimeDelta())); |
| pipeline_controller_.Resume(); |
| Mock::VerifyAndClear(&pipeline_); |
| EXPECT_FALSE(pipeline_controller_.IsSuspended()); |
| EXPECT_FALSE(pipeline_controller_.IsStable()); |
| EXPECT_FALSE(pipeline_controller_.IsPipelineSuspended()); |
| return resume_cb; |
| } |
| |
| void Complete(const PipelineStatusCB& cb) { |
| cb.Run(PIPELINE_OK); |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| protected: |
| std::unique_ptr<Renderer> CreateRenderer() { |
| return std::unique_ptr<Renderer>(); |
| } |
| |
| void OnSeeked(bool time_updated) { |
| was_seeked_ = true; |
| last_seeked_time_updated_ = time_updated; |
| } |
| |
| void OnSuspended() { was_suspended_ = true; } |
| |
| // Pipeline::Client overrides |
| void OnError(PipelineStatus status) override { NOTREACHED(); } |
| void OnEnded() override {} |
| void OnMetadata(PipelineMetadata metadata) override {} |
| void OnBufferingStateChange(BufferingState state) override {} |
| void OnDurationChange() override {} |
| void OnAddTextTrack(const TextTrackConfig& config, |
| const AddTextTrackDoneCB& done_cb) override {} |
| void OnWaitingForDecryptionKey() override {} |
| void OnVideoNaturalSizeChange(const gfx::Size& size) override {} |
| void OnVideoOpacityChange(bool opaque) override {} |
| |
| base::MessageLoop message_loop_; |
| |
| NiceMock<MockDemuxer> demuxer_; |
| StrictMock<MockPipeline> pipeline_; |
| PipelineController pipeline_controller_; |
| |
| bool was_seeked_ = false; |
| bool last_seeked_time_updated_ = false; |
| bool was_suspended_ = false; |
| base::TimeDelta last_resume_time_; |
| |
| DISALLOW_COPY_AND_ASSIGN(PipelineControllerTest); |
| }; |
| |
| TEST_F(PipelineControllerTest, Startup) { |
| PipelineStatusCB start_cb = StartPipeline(); |
| EXPECT_FALSE(was_seeked_); |
| |
| Complete(start_cb); |
| EXPECT_TRUE(was_seeked_); |
| EXPECT_FALSE(last_seeked_time_updated_); |
| EXPECT_FALSE(was_suspended_); |
| EXPECT_TRUE(pipeline_controller_.IsStable()); |
| } |
| |
| TEST_F(PipelineControllerTest, SuspendResume) { |
| Complete(StartPipeline()); |
| EXPECT_TRUE(was_seeked_); |
| was_seeked_ = false; |
| |
| Complete(SuspendPipeline()); |
| EXPECT_TRUE(was_suspended_); |
| EXPECT_FALSE(pipeline_controller_.IsStable()); |
| |
| Complete(ResumePipeline()); |
| EXPECT_TRUE(pipeline_controller_.IsStable()); |
| |
| // |was_seeked_| should not be affected by Suspend()/Resume() at all. |
| EXPECT_FALSE(was_seeked_); |
| } |
| |
| TEST_F(PipelineControllerTest, Seek) { |
| // Normal seeking should not result in a cancel. |
| EXPECT_CALL(demuxer_, CancelPendingSeek(_)).Times(0); |
| |
| Complete(StartPipeline()); |
| was_seeked_ = false; |
| |
| base::TimeDelta seek_time = base::TimeDelta::FromSeconds(5); |
| EXPECT_CALL(demuxer_, StartWaitingForSeek(seek_time)); |
| PipelineStatusCB seek_cb = SeekPipeline(seek_time); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_FALSE(was_seeked_); |
| |
| Complete(seek_cb); |
| EXPECT_TRUE(was_seeked_); |
| EXPECT_TRUE(pipeline_controller_.IsStable()); |
| } |
| |
| TEST_F(PipelineControllerTest, SuspendResumeTime) { |
| Complete(StartPipeline()); |
| Complete(SuspendPipeline()); |
| |
| base::TimeDelta seek_time = base::TimeDelta::FromSeconds(5); |
| pipeline_controller_.Seek(seek_time, true); |
| base::RunLoop().RunUntilIdle(); |
| |
| Complete(ResumePipeline()); |
| EXPECT_EQ(seek_time, last_resume_time_); |
| } |
| |
| TEST_F(PipelineControllerTest, SuspendResumeTime_WithStreamingData) { |
| Complete(StartPipeline_WithStreamingData()); |
| Complete(SuspendPipeline()); |
| |
| base::TimeDelta seek_time = base::TimeDelta::FromSeconds(5); |
| pipeline_controller_.Seek(seek_time, true); |
| base::RunLoop().RunUntilIdle(); |
| |
| Complete(ResumePipeline()); |
| EXPECT_EQ(base::TimeDelta(), last_resume_time_); |
| } |
| |
| TEST_F(PipelineControllerTest, SeekAborted) { |
| Complete(StartPipeline()); |
| |
| // Create a first pending seek. |
| base::TimeDelta seek_time_1 = base::TimeDelta::FromSeconds(5); |
| EXPECT_CALL(demuxer_, StartWaitingForSeek(seek_time_1)); |
| PipelineStatusCB seek_cb_1 = SeekPipeline(seek_time_1); |
| base::RunLoop().RunUntilIdle(); |
| Mock::VerifyAndClear(&demuxer_); |
| |
| // Create a second seek; the first should be aborted. |
| base::TimeDelta seek_time_2 = base::TimeDelta::FromSeconds(10); |
| EXPECT_CALL(demuxer_, CancelPendingSeek(seek_time_2)); |
| pipeline_controller_.Seek(seek_time_2, true); |
| base::RunLoop().RunUntilIdle(); |
| Mock::VerifyAndClear(&demuxer_); |
| |
| // When the first seek is completed (or aborted) the second should be issued. |
| EXPECT_CALL(demuxer_, StartWaitingForSeek(seek_time_2)); |
| EXPECT_CALL(pipeline_, Seek(seek_time_2, _)); |
| Complete(seek_cb_1); |
| } |
| |
| TEST_F(PipelineControllerTest, PendingSuspend) { |
| Complete(StartPipeline()); |
| |
| base::TimeDelta seek_time = base::TimeDelta::FromSeconds(5); |
| PipelineStatusCB seek_cb = SeekPipeline(seek_time); |
| base::RunLoop().RunUntilIdle(); |
| |
| // While the seek is ongoing, request a suspend. |
| // It will be a mock failure if pipeline_.Suspend() is called. |
| pipeline_controller_.Suspend(); |
| base::RunLoop().RunUntilIdle(); |
| |
| // Expect the suspend to trigger when the seek is completed. |
| EXPECT_CALL(pipeline_, Suspend(_)); |
| Complete(seek_cb); |
| } |
| |
| TEST_F(PipelineControllerTest, SeekMergesWithResume) { |
| Complete(StartPipeline()); |
| Complete(SuspendPipeline()); |
| |
| // Request a seek while suspended. |
| // It will be a mock failure if pipeline_.Seek() is called. |
| base::TimeDelta seek_time = base::TimeDelta::FromSeconds(5); |
| pipeline_controller_.Seek(seek_time, true); |
| base::RunLoop().RunUntilIdle(); |
| |
| // Resume and verify the resume time includes the seek. |
| Complete(ResumePipeline()); |
| EXPECT_EQ(seek_time, last_resume_time_); |
| EXPECT_TRUE(last_seeked_time_updated_); |
| } |
| |
| TEST_F(PipelineControllerTest, SeekMergesWithSeek) { |
| Complete(StartPipeline()); |
| |
| base::TimeDelta seek_time_1 = base::TimeDelta::FromSeconds(5); |
| PipelineStatusCB seek_cb_1 = SeekPipeline(seek_time_1); |
| base::RunLoop().RunUntilIdle(); |
| |
| // Request another seek while the first is ongoing. |
| base::TimeDelta seek_time_2 = base::TimeDelta::FromSeconds(10); |
| pipeline_controller_.Seek(seek_time_2, true); |
| base::RunLoop().RunUntilIdle(); |
| |
| // Request a third seek. (It should replace the second.) |
| base::TimeDelta seek_time_3 = base::TimeDelta::FromSeconds(15); |
| pipeline_controller_.Seek(seek_time_3, true); |
| base::RunLoop().RunUntilIdle(); |
| |
| // Expect the third seek to trigger when the first seek completes. |
| EXPECT_CALL(pipeline_, Seek(seek_time_3, _)); |
| Complete(seek_cb_1); |
| } |
| |
| TEST_F(PipelineControllerTest, SeekToSeekTimeElided) { |
| Complete(StartPipeline()); |
| |
| base::TimeDelta seek_time = base::TimeDelta::FromSeconds(5); |
| PipelineStatusCB seek_cb_1 = SeekPipeline(seek_time); |
| base::RunLoop().RunUntilIdle(); |
| |
| // Request a seek to the same time again. |
| pipeline_controller_.Seek(seek_time, true); |
| base::RunLoop().RunUntilIdle(); |
| |
| // Complete the first seek. |
| // It would be a mock error if the second seek was dispatched here. |
| Complete(seek_cb_1); |
| EXPECT_TRUE(pipeline_controller_.IsStable()); |
| } |
| |
| TEST_F(PipelineControllerTest, SeekToSeekTimeNotElided) { |
| Complete(StartPipeline_WithDynamicData()); |
| |
| base::TimeDelta seek_time = base::TimeDelta::FromSeconds(5); |
| PipelineStatusCB seek_cb_1 = SeekPipeline(seek_time); |
| base::RunLoop().RunUntilIdle(); |
| |
| // Request a seek to the same time again. |
| pipeline_controller_.Seek(seek_time, true); |
| base::RunLoop().RunUntilIdle(); |
| |
| // Expect the second seek to trigger when the first seek completes. |
| EXPECT_CALL(pipeline_, Seek(seek_time, _)); |
| Complete(seek_cb_1); |
| } |
| |
| } // namespace media |
| } // namespace cobalt |