| // 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 "media/filters/pipeline_controller.h" |
| |
| #include <memory> |
| |
| #include "base/bind.h" |
| #include "base/callback_helpers.h" |
| #include "base/macros.h" |
| #include "base/memory/ref_counted.h" |
| #include "base/notreached.h" |
| #include "base/run_loop.h" |
| #include "base/test/gmock_callback_support.h" |
| #include "base/test/gmock_move_support.h" |
| #include "base/test/task_environment.h" |
| #include "base/time/time.h" |
| #include "media/base/mock_filters.h" |
| #include "media/base/pipeline.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| using ::base::test::RunOnceClosure; |
| using ::testing::_; |
| using ::testing::AnyNumber; |
| using ::testing::DoAll; |
| using ::testing::Mock; |
| using ::testing::NiceMock; |
| using ::testing::Return; |
| using ::testing::SaveArg; |
| using ::testing::StrictMock; |
| |
| namespace media { |
| |
| class PipelineControllerTest : public ::testing::Test, public Pipeline::Client { |
| public: |
| PipelineControllerTest() |
| : pipeline_(new StrictMock<MockPipeline>()), |
| pipeline_controller_( |
| std::unique_ptr<Pipeline>(pipeline_), |
| base::BindRepeating(&PipelineControllerTest::OnSeeked, |
| base::Unretained(this)), |
| base::BindRepeating(&PipelineControllerTest::OnSuspended, |
| base::Unretained(this)), |
| base::BindRepeating(&PipelineControllerTest::OnBeforeResume, |
| base::Unretained(this)), |
| base::BindRepeating(&PipelineControllerTest::OnResumed, |
| base::Unretained(this)), |
| base::BindRepeating(&PipelineControllerTest::OnError, |
| base::Unretained(this))) {} |
| |
| PipelineControllerTest(const PipelineControllerTest&) = delete; |
| PipelineControllerTest& operator=(const PipelineControllerTest&) = delete; |
| |
| ~PipelineControllerTest() override = default; |
| |
| PipelineStatusCallback StartPipeline(bool is_streaming, bool is_static) { |
| EXPECT_FALSE(pipeline_controller_.IsStable()); |
| PipelineStatusCallback start_cb; |
| EXPECT_CALL(*pipeline_, OnStart(_, _, _, _)) |
| .WillOnce(MoveArg<3>(&start_cb)); |
| pipeline_controller_.Start(Pipeline::StartType::kNormal, &demuxer_, this, |
| is_streaming, is_static); |
| Mock::VerifyAndClear(pipeline_); |
| EXPECT_CALL(*pipeline_, IsSuspended()) |
| .Times(AnyNumber()) |
| .WillOnce(Return(false)); |
| EXPECT_FALSE(pipeline_controller_.IsStable()); |
| return start_cb; |
| } |
| |
| PipelineStatusCallback StartPipeline() { return StartPipeline(false, true); } |
| |
| PipelineStatusCallback StartPipeline_WithDynamicData() { |
| return StartPipeline(false, false); |
| } |
| |
| PipelineStatusCallback StartPipeline_WithStreamingData() { |
| return StartPipeline(true, false); |
| } |
| |
| PipelineStatusCallback SeekPipeline(base::TimeDelta time) { |
| EXPECT_TRUE(pipeline_controller_.IsStable()); |
| PipelineStatusCallback seek_cb; |
| EXPECT_CALL(*pipeline_, OnSeek(time, _)).WillOnce(MoveArg<1>(&seek_cb)); |
| pipeline_controller_.Seek(time, true); |
| Mock::VerifyAndClear(pipeline_); |
| EXPECT_FALSE(pipeline_controller_.IsStable()); |
| return seek_cb; |
| } |
| |
| PipelineStatusCallback SuspendPipeline() { |
| EXPECT_TRUE(pipeline_controller_.IsStable()); |
| PipelineStatusCallback suspend_cb; |
| EXPECT_CALL(*pipeline_, OnSuspend(_)).WillOnce(MoveArg<0>(&suspend_cb)); |
| pipeline_controller_.Suspend(); |
| Mock::VerifyAndClear(pipeline_); |
| EXPECT_CALL(*pipeline_, IsSuspended()) |
| .Times(AnyNumber()) |
| .WillOnce(Return(true)); |
| EXPECT_TRUE(pipeline_controller_.IsSuspended()); |
| EXPECT_FALSE(pipeline_controller_.IsStable()); |
| EXPECT_FALSE(pipeline_controller_.IsPipelineSuspended()); |
| return suspend_cb; |
| } |
| |
| PipelineStatusCallback ResumePipeline() { |
| EXPECT_TRUE(pipeline_controller_.IsPipelineSuspended()); |
| PipelineStatusCallback resume_cb; |
| EXPECT_CALL(*pipeline_, OnResume(_, _)) |
| .WillOnce( |
| DoAll(SaveArg<0>(&last_resume_time_), MoveArg<1>(&resume_cb))); |
| EXPECT_CALL(*pipeline_, GetMediaTime()) |
| .WillRepeatedly(Return(base::TimeDelta())); |
| pipeline_controller_.Resume(); |
| Mock::VerifyAndClear(pipeline_); |
| EXPECT_CALL(*pipeline_, IsSuspended()) |
| .Times(AnyNumber()) |
| .WillOnce(Return(false)); |
| EXPECT_FALSE(pipeline_controller_.IsSuspended()); |
| EXPECT_FALSE(pipeline_controller_.IsStable()); |
| EXPECT_FALSE(pipeline_controller_.IsPipelineSuspended()); |
| return resume_cb; |
| } |
| |
| void Complete(PipelineStatusCallback cb) { |
| std::move(cb).Run(PIPELINE_OK); |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| protected: |
| void OnSeeked(bool time_updated) { |
| was_seeked_ = true; |
| last_seeked_time_updated_ = time_updated; |
| } |
| |
| void OnSuspended() { was_suspended_ = true; } |
| void OnBeforeResume() { was_resuming_ = true; } |
| void OnResumed() { was_resumed_ = true; } |
| |
| // Pipeline::Client overrides |
| void OnError(PipelineStatus status) override { NOTREACHED(); } |
| void OnEnded() override {} |
| void OnMetadata(const PipelineMetadata& metadata) override {} |
| void OnBufferingStateChange(BufferingState state, |
| BufferingStateChangeReason reason) override {} |
| void OnDurationChange() override {} |
| void OnAddTextTrack(const TextTrackConfig& config, |
| AddTextTrackDoneCB done_cb) override {} |
| void OnWaiting(WaitingReason reason) override {} |
| void OnVideoNaturalSizeChange(const gfx::Size& size) override {} |
| void OnAudioConfigChange(const AudioDecoderConfig& config) override {} |
| void OnVideoConfigChange(const VideoDecoderConfig& config) override {} |
| void OnVideoOpacityChange(bool opaque) override {} |
| void OnVideoFrameRateChange(absl::optional<int>) override {} |
| void OnVideoAverageKeyframeDistanceUpdate() override {} |
| void OnAudioPipelineInfoChange(const AudioPipelineInfo& info) override {} |
| void OnVideoPipelineInfoChange(const VideoPipelineInfo& info) override {} |
| |
| base::test::SingleThreadTaskEnvironment task_environment_; |
| |
| NiceMock<MockDemuxer> demuxer_; |
| StrictMock<MockPipeline>* pipeline_; |
| PipelineController pipeline_controller_; |
| |
| bool was_seeked_ = false; |
| bool last_seeked_time_updated_ = false; |
| bool was_suspended_ = false; |
| bool was_resuming_ = false; |
| bool was_resumed_ = false; |
| base::TimeDelta last_resume_time_; |
| }; |
| |
| TEST_F(PipelineControllerTest, Startup) { |
| PipelineStatusCallback start_cb = StartPipeline(); |
| EXPECT_FALSE(was_seeked_); |
| |
| Complete(std::move(start_cb)); |
| EXPECT_TRUE(was_seeked_); |
| EXPECT_FALSE(last_seeked_time_updated_); |
| EXPECT_FALSE(was_suspended_); |
| EXPECT_TRUE(pipeline_controller_.IsStable()); |
| } |
| |
| TEST_F(PipelineControllerTest, StartSuspendedSeekAndResume) { |
| EXPECT_FALSE(pipeline_controller_.IsStable()); |
| PipelineStatusCallback start_cb; |
| EXPECT_CALL(*pipeline_, OnStart(_, _, _, _)).WillOnce(MoveArg<3>(&start_cb)); |
| pipeline_controller_.Start(Pipeline::StartType::kSuspendAfterMetadata, |
| &demuxer_, this, false, true); |
| Mock::VerifyAndClear(pipeline_); |
| |
| // Initiate a seek before the pipeline completes suspended startup. |
| base::TimeDelta seek_time = base::Seconds(5); |
| EXPECT_CALL(demuxer_, StartWaitingForSeek(seek_time)); |
| pipeline_controller_.Seek(seek_time, true); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_FALSE(was_seeked_); |
| |
| PipelineStatusCallback resume_cb; |
| EXPECT_CALL(*pipeline_, OnResume(_, _)) |
| .WillOnce(DoAll(MoveArg<1>(&resume_cb))); |
| EXPECT_CALL(*pipeline_, GetMediaTime()) |
| .WillRepeatedly(Return(base::TimeDelta())); |
| |
| EXPECT_CALL(*pipeline_, IsSuspended()).WillRepeatedly(Return(true)); |
| EXPECT_FALSE(pipeline_controller_.IsStable()); |
| Complete(std::move(start_cb)); |
| |
| EXPECT_FALSE(pipeline_controller_.IsStable()); |
| EXPECT_FALSE(pipeline_controller_.IsPipelineSuspended()); |
| EXPECT_FALSE(pipeline_controller_.IsSuspended()); |
| Mock::VerifyAndClear(pipeline_); |
| |
| EXPECT_CALL(*pipeline_, IsSuspended()).WillRepeatedly(Return(false)); |
| Complete(std::move(resume_cb)); |
| EXPECT_TRUE(was_seeked_); |
| was_seeked_ = false; |
| |
| EXPECT_TRUE(pipeline_controller_.IsStable()); |
| EXPECT_FALSE(pipeline_controller_.IsPipelineSuspended()); |
| EXPECT_FALSE(pipeline_controller_.IsSuspended()); |
| Mock::VerifyAndClear(pipeline_); |
| } |
| |
| TEST_F(PipelineControllerTest, StartSuspendedAndResume) { |
| EXPECT_FALSE(pipeline_controller_.IsStable()); |
| PipelineStatusCallback start_cb; |
| EXPECT_CALL(*pipeline_, OnStart(_, _, _, _)).WillOnce(MoveArg<3>(&start_cb)); |
| pipeline_controller_.Start(Pipeline::StartType::kSuspendAfterMetadata, |
| &demuxer_, this, false, true); |
| Mock::VerifyAndClear(pipeline_); |
| EXPECT_CALL(*pipeline_, IsSuspended()).WillRepeatedly(Return(true)); |
| EXPECT_FALSE(pipeline_controller_.IsStable()); |
| Complete(std::move(start_cb)); |
| EXPECT_TRUE(was_seeked_); |
| was_seeked_ = false; |
| |
| EXPECT_FALSE(pipeline_controller_.IsStable()); |
| EXPECT_TRUE(pipeline_controller_.IsPipelineSuspended()); |
| EXPECT_TRUE(pipeline_controller_.IsSuspended()); |
| Mock::VerifyAndClear(pipeline_); |
| |
| EXPECT_CALL(*pipeline_, IsSuspended()).WillRepeatedly(Return(false)); |
| PipelineStatusCallback resume_cb = ResumePipeline(); |
| EXPECT_TRUE(was_resuming_); |
| EXPECT_FALSE(was_resumed_); |
| |
| Complete(std::move(resume_cb)); |
| EXPECT_TRUE(was_resumed_); |
| EXPECT_TRUE(pipeline_controller_.IsStable()); |
| |
| // |was_seeked_| should not be affected by Suspend()/Resume() at all. |
| EXPECT_FALSE(was_seeked_); |
| } |
| |
| TEST_F(PipelineControllerTest, SuspendResume) { |
| Complete(StartPipeline()); |
| EXPECT_TRUE(was_seeked_); |
| was_seeked_ = false; |
| |
| Complete(SuspendPipeline()); |
| EXPECT_TRUE(was_suspended_); |
| EXPECT_FALSE(pipeline_controller_.IsStable()); |
| |
| PipelineStatusCallback resume_cb = ResumePipeline(); |
| EXPECT_TRUE(was_resuming_); |
| EXPECT_FALSE(was_resumed_); |
| |
| Complete(std::move(resume_cb)); |
| EXPECT_TRUE(was_resumed_); |
| 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::Seconds(5); |
| EXPECT_CALL(demuxer_, StartWaitingForSeek(seek_time)); |
| PipelineStatusCallback seek_cb = SeekPipeline(seek_time); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_FALSE(was_seeked_); |
| |
| Complete(std::move(seek_cb)); |
| EXPECT_TRUE(was_seeked_); |
| EXPECT_TRUE(pipeline_controller_.IsStable()); |
| } |
| |
| // Makes sure OnDecoderStateLost() triggers a seek to the current media time. |
| TEST_F(PipelineControllerTest, DecoderStateLost) { |
| Complete(StartPipeline()); |
| |
| constexpr auto kCurrentMediaTime = base::Seconds(7); |
| EXPECT_CALL(*pipeline_, GetMediaTime()) |
| .WillRepeatedly(Return(kCurrentMediaTime)); |
| |
| EXPECT_CALL(demuxer_, StartWaitingForSeek(kCurrentMediaTime)); |
| EXPECT_CALL(*pipeline_, OnSeek(kCurrentMediaTime, _)); |
| |
| pipeline_controller_.OnDecoderStateLost(); |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| // Makes sure OnDecoderStateLost() does not trigger a seek during pending seek. |
| TEST_F(PipelineControllerTest, DecoderStateLost_DuringPendingSeek) { |
| Complete(StartPipeline()); |
| |
| // Create a pending seek. |
| base::TimeDelta kSeekTime = base::Seconds(5); |
| EXPECT_CALL(demuxer_, StartWaitingForSeek(kSeekTime)); |
| PipelineStatusCallback seek_cb = SeekPipeline(kSeekTime); |
| base::RunLoop().RunUntilIdle(); |
| Mock::VerifyAndClear(&demuxer_); |
| |
| // OnDecoderStateLost() should not trigger another seek. |
| EXPECT_CALL(*pipeline_, GetMediaTime()).Times(0); |
| pipeline_controller_.OnDecoderStateLost(); |
| base::RunLoop().RunUntilIdle(); |
| |
| Complete(std::move(seek_cb)); |
| } |
| |
| TEST_F(PipelineControllerTest, SuspendResumeTime) { |
| Complete(StartPipeline()); |
| Complete(SuspendPipeline()); |
| |
| base::TimeDelta seek_time = base::Seconds(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::Seconds(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::Seconds(5); |
| EXPECT_CALL(demuxer_, StartWaitingForSeek(seek_time_1)); |
| PipelineStatusCallback 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::Seconds(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_, OnSeek(seek_time_2, _)); |
| Complete(std::move(seek_cb_1)); |
| } |
| |
| TEST_F(PipelineControllerTest, PendingSuspend) { |
| Complete(StartPipeline()); |
| |
| base::TimeDelta seek_time = base::Seconds(5); |
| PipelineStatusCallback 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_, OnSuspend(_)); |
| Complete(std::move(seek_cb)); |
| } |
| |
| TEST_F(PipelineControllerTest, SeekMergesWithResume) { |
| Complete(StartPipeline()); |
| Complete(SuspendPipeline()); |
| |
| // Pipeline startup always completes with a seek. |
| EXPECT_TRUE(was_seeked_); |
| was_seeked_ = false; |
| |
| // Request a seek while suspended. |
| // It will be a mock failure if pipeline_.Seek() is called. |
| base::TimeDelta seek_time = base::Seconds(5); |
| pipeline_controller_.Seek(seek_time, true); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_FALSE(was_seeked_); |
| |
| // 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::Seconds(5); |
| PipelineStatusCallback seek_cb_1 = SeekPipeline(seek_time_1); |
| base::RunLoop().RunUntilIdle(); |
| |
| // Request another seek while the first is ongoing. |
| base::TimeDelta seek_time_2 = base::Seconds(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::Seconds(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_, OnSeek(seek_time_3, _)); |
| Complete(std::move(seek_cb_1)); |
| } |
| |
| TEST_F(PipelineControllerTest, SeekToSeekTimeElided) { |
| Complete(StartPipeline()); |
| |
| base::TimeDelta seek_time = base::Seconds(5); |
| PipelineStatusCallback 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(std::move(seek_cb_1)); |
| EXPECT_TRUE(pipeline_controller_.IsStable()); |
| } |
| |
| TEST_F(PipelineControllerTest, SeekToSeekTimeNotElided) { |
| Complete(StartPipeline_WithDynamicData()); |
| |
| base::TimeDelta seek_time = base::Seconds(5); |
| PipelineStatusCallback 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_, OnSeek(seek_time, _)); |
| Complete(std::move(seek_cb_1)); |
| } |
| |
| TEST_F(PipelineControllerTest, VideoTrackChangeWhileSuspending) { |
| Complete(StartPipeline()); |
| EXPECT_CALL(*pipeline_, OnSuspend(_)); |
| EXPECT_CALL(*pipeline_, OnSelectedVideoTrackChanged(_, _)).Times(0); |
| pipeline_controller_.Suspend(); |
| pipeline_controller_.OnSelectedVideoTrackChanged({}); |
| } |
| |
| TEST_F(PipelineControllerTest, AudioTrackChangeWhileSuspending) { |
| Complete(StartPipeline()); |
| EXPECT_CALL(*pipeline_, OnSuspend(_)); |
| EXPECT_CALL(*pipeline_, OnEnabledAudioTracksChanged(_, _)).Times(0); |
| pipeline_controller_.Suspend(); |
| pipeline_controller_.OnEnabledAudioTracksChanged({}); |
| } |
| |
| TEST_F(PipelineControllerTest, AudioTrackChangeDuringVideoTrackChange) { |
| Complete(StartPipeline()); |
| |
| EXPECT_CALL(*pipeline_, OnSelectedVideoTrackChanged(_, _)); |
| pipeline_controller_.OnSelectedVideoTrackChanged({}); |
| pipeline_controller_.OnEnabledAudioTracksChanged({}); |
| EXPECT_CALL(*pipeline_, OnEnabledAudioTracksChanged(_, _)); |
| |
| pipeline_controller_.FireOnTrackChangeCompleteForTesting( |
| PipelineController::State::PLAYING); |
| |
| pipeline_controller_.FireOnTrackChangeCompleteForTesting( |
| PipelineController::State::PLAYING); |
| } |
| |
| TEST_F(PipelineControllerTest, SuspendDuringVideoTrackChange) { |
| Complete(StartPipeline()); |
| EXPECT_CALL(*pipeline_, OnSelectedVideoTrackChanged(_, _)); |
| was_resumed_ = false; |
| pipeline_controller_.OnSelectedVideoTrackChanged({}); |
| pipeline_controller_.Suspend(); |
| |
| base::RunLoop loop; |
| EXPECT_CALL(*pipeline_, OnSuspend(_)) |
| .WillOnce(RunOnceClosure(loop.QuitClosure())); |
| |
| pipeline_controller_.FireOnTrackChangeCompleteForTesting( |
| PipelineController::State::PLAYING); |
| |
| loop.Run(); |
| EXPECT_FALSE(was_resumed_); |
| } |
| |
| TEST_F(PipelineControllerTest, SuspendDuringAudioTrackChange) { |
| Complete(StartPipeline()); |
| EXPECT_CALL(*pipeline_, OnEnabledAudioTracksChanged(_, _)); |
| was_resumed_ = false; |
| |
| pipeline_controller_.OnEnabledAudioTracksChanged({}); |
| pipeline_controller_.Suspend(); |
| |
| base::RunLoop loop; |
| EXPECT_CALL(*pipeline_, OnSuspend(_)) |
| .WillOnce(RunOnceClosure(loop.QuitClosure())); |
| |
| pipeline_controller_.FireOnTrackChangeCompleteForTesting( |
| PipelineController::State::PLAYING); |
| |
| loop.Run(); |
| EXPECT_FALSE(was_resumed_); |
| } |
| |
| TEST_F(PipelineControllerTest, ResumePlaybackDuringSwitchingTracksState) { |
| Complete(StartPipeline()); |
| Complete(SuspendPipeline()); |
| EXPECT_CALL(*pipeline_, OnSelectedVideoTrackChanged(_, _)).Times(1); |
| EXPECT_CALL(*pipeline_, GetMediaTime()).Times(1); |
| EXPECT_CALL(*pipeline_, OnResume(_, _)).Times(1); |
| |
| pipeline_controller_.OnSelectedVideoTrackChanged({}); |
| pipeline_controller_.Resume(); |
| pipeline_controller_.FireOnTrackChangeCompleteForTesting( |
| PipelineController::State::SUSPENDED); |
| } |
| |
| TEST_F(PipelineControllerTest, PreservesPitch) { |
| Complete(StartPipeline()); |
| EXPECT_CALL(*pipeline_, SetPreservesPitch(false)); |
| pipeline_controller_.SetPreservesPitch(false); |
| |
| EXPECT_CALL(*pipeline_, SetPreservesPitch(true)); |
| pipeline_controller_.SetPreservesPitch(true); |
| } |
| |
| } // namespace media |