| // 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 <vector> |
| |
| #include "base/bind.h" |
| #include "base/message_loop/message_loop.h" |
| #include "base/stl_util.h" |
| #include "base/test/simple_test_tick_clock.h" |
| #include "base/threading/simple_thread.h" |
| #include "base/time/clock.h" |
| #include "media/base/clock.h" |
| #include "media/base/fake_text_track_stream.h" |
| #include "media/base/gmock_callback_support.h" |
| #include "media/base/media_log.h" |
| #include "media/base/mock_filters.h" |
| #include "media/base/pipeline.h" |
| #include "media/base/test_helpers.h" |
| #include "media/base/text_renderer.h" |
| #include "media/base/text_track_config.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "ui/gfx/size.h" |
| |
| using ::testing::_; |
| using ::testing::DeleteArg; |
| using ::testing::DoAll; |
| // TODO(scherkus): Remove InSequence after refactoring Pipeline. |
| using ::testing::InSequence; |
| using ::testing::Invoke; |
| using ::testing::InvokeWithoutArgs; |
| using ::testing::Mock; |
| using ::testing::NotNull; |
| using ::testing::Return; |
| using ::testing::SaveArg; |
| using ::testing::StrictMock; |
| using ::testing::WithArg; |
| |
| namespace media { |
| |
| // Demuxer properties. |
| const int kTotalBytes = 1024; |
| |
| ACTION_P(SetDemuxerProperties, duration) { |
| arg0->SetTotalBytes(kTotalBytes); |
| arg0->SetDuration(duration); |
| } |
| |
| ACTION_P2(Stop, pipeline, stop_cb) { |
| pipeline->Stop(stop_cb); |
| } |
| |
| ACTION_P2(SetError, pipeline, status) { |
| pipeline->SetErrorForTesting(status); |
| } |
| |
| // Used for setting expectations on pipeline callbacks. Using a StrictMock |
| // also lets us test for missing callbacks. |
| class CallbackHelper { |
| public: |
| CallbackHelper() {} |
| virtual ~CallbackHelper() {} |
| |
| MOCK_METHOD1(OnStart, void(PipelineStatus)); |
| MOCK_METHOD1(OnSeek, void(PipelineStatus)); |
| MOCK_METHOD0(OnStop, void()); |
| MOCK_METHOD0(OnEnded, void()); |
| MOCK_METHOD1(OnError, void(PipelineStatus)); |
| MOCK_METHOD1(OnBufferingState, void(Pipeline::BufferingState)); |
| MOCK_METHOD0(OnDurationChange, void()); |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(CallbackHelper); |
| }; |
| |
| // 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 PipelineTest : public ::testing::Test { |
| public: |
| PipelineTest() |
| : pipeline_(new Pipeline(message_loop_.message_loop_proxy(), |
| new MediaLog())), |
| filter_collection_(new FilterCollection()), |
| demuxer_(new MockDemuxer()) { |
| filter_collection_->SetDemuxer(demuxer_.get()); |
| |
| video_renderer_ = new MockVideoRenderer(); |
| scoped_ptr<VideoRenderer> video_renderer(video_renderer_); |
| filter_collection_->SetVideoRenderer(video_renderer.Pass()); |
| |
| audio_renderer_ = new MockAudioRenderer(); |
| scoped_ptr<AudioRenderer> audio_renderer(audio_renderer_); |
| filter_collection_->SetAudioRenderer(audio_renderer.Pass()); |
| |
| text_renderer_ = new TextRenderer( |
| message_loop_.message_loop_proxy(), |
| base::Bind(&PipelineTest::OnAddTextTrack, |
| base::Unretained(this))); |
| scoped_ptr<TextRenderer> text_renderer(text_renderer_); |
| filter_collection_->SetTextRenderer(text_renderer.Pass()); |
| |
| // InitializeDemuxer() adds overriding expectations for expected non-NULL |
| // streams. |
| DemuxerStream* null_pointer = NULL; |
| EXPECT_CALL(*demuxer_, GetStream(_)) |
| .WillRepeatedly(Return(null_pointer)); |
| |
| EXPECT_CALL(*demuxer_, GetStartTime()) |
| .WillRepeatedly(Return(base::TimeDelta())); |
| } |
| |
| virtual ~PipelineTest() { |
| if (!pipeline_ || !pipeline_->IsRunning()) |
| return; |
| |
| ExpectStop(); |
| |
| // The mock demuxer doesn't stop the fake text track stream, |
| // so just stop it manually. |
| if (text_stream_) { |
| text_stream_->Stop(); |
| message_loop_.RunUntilIdle(); |
| } |
| |
| // Expect a stop callback if we were started. |
| EXPECT_CALL(callbacks_, OnStop()); |
| pipeline_->Stop(base::Bind(&CallbackHelper::OnStop, |
| base::Unretained(&callbacks_))); |
| message_loop_.RunUntilIdle(); |
| } |
| |
| protected: |
| // Sets up expectations to allow the demuxer to initialize. |
| typedef std::vector<MockDemuxerStream*> MockDemuxerStreamVector; |
| void InitializeDemuxer(MockDemuxerStreamVector* streams, |
| const base::TimeDelta& duration) { |
| EXPECT_CALL(callbacks_, OnDurationChange()); |
| EXPECT_CALL(*demuxer_, Initialize(_, _, _)) |
| .WillOnce(DoAll(SetDemuxerProperties(duration), |
| RunCallback<1>(PIPELINE_OK))); |
| |
| // Configure the demuxer to return the streams. |
| for (size_t i = 0; i < streams->size(); ++i) { |
| DemuxerStream* stream = (*streams)[i]; |
| EXPECT_CALL(*demuxer_, GetStream(stream->type())) |
| .WillRepeatedly(Return(stream)); |
| } |
| } |
| |
| void InitializeDemuxer(MockDemuxerStreamVector* streams) { |
| // Initialize with a default non-zero duration. |
| InitializeDemuxer(streams, base::TimeDelta::FromSeconds(10)); |
| } |
| |
| scoped_ptr<StrictMock<MockDemuxerStream> > CreateStream( |
| DemuxerStream::Type type) { |
| scoped_ptr<StrictMock<MockDemuxerStream> > stream( |
| new StrictMock<MockDemuxerStream>(type)); |
| return stream.Pass(); |
| } |
| |
| // Sets up expectations to allow the video renderer to initialize. |
| void InitializeVideoRenderer(DemuxerStream* stream) { |
| EXPECT_CALL(*video_renderer_, Initialize(stream, _, _, _, _, _, _, _, _)) |
| .WillOnce(RunCallback<1>(PIPELINE_OK)); |
| EXPECT_CALL(*video_renderer_, SetPlaybackRate(0.0f)); |
| |
| // Startup sequence. |
| EXPECT_CALL(*video_renderer_, Preroll(demuxer_->GetStartTime(), _)) |
| .WillOnce(RunCallback<1>(PIPELINE_OK)); |
| EXPECT_CALL(*video_renderer_, Play(_)) |
| .WillOnce(RunClosure<0>()); |
| } |
| |
| // Sets up expectations to allow the audio renderer to initialize. |
| void InitializeAudioRenderer(DemuxerStream* stream, |
| bool disable_after_init_cb) { |
| if (disable_after_init_cb) { |
| EXPECT_CALL(*audio_renderer_, Initialize(stream, _, _, _, _, _, _, _)) |
| .WillOnce(DoAll(RunCallback<1>(PIPELINE_OK), |
| WithArg<6>(RunClosure<0>()))); // |disabled_cb|. |
| } else { |
| EXPECT_CALL(*audio_renderer_, Initialize(stream, _, _, _, _, _, _, _)) |
| .WillOnce(DoAll(SaveArg<4>(&audio_time_cb_), |
| RunCallback<1>(PIPELINE_OK))); |
| } |
| } |
| |
| void AddTextStream() { |
| EXPECT_CALL(*this, OnAddTextTrack(_,_)) |
| .WillOnce(Invoke(this, &PipelineTest::DoOnAddTextTrack)); |
| static_cast<DemuxerHost*>(pipeline_.get())->AddTextStream(text_stream(), |
| TextTrackConfig(kTextSubtitles, "", "", "")); |
| } |
| |
| // Sets up expectations on the callback and initializes the pipeline. Called |
| // after tests have set expectations any filters they wish to use. |
| void InitializePipeline(PipelineStatus start_status) { |
| EXPECT_CALL(callbacks_, OnStart(start_status)); |
| |
| if (start_status == PIPELINE_OK) { |
| EXPECT_CALL(callbacks_, OnBufferingState(Pipeline::kHaveMetadata)); |
| |
| if (audio_stream_) { |
| EXPECT_CALL(*audio_renderer_, SetPlaybackRate(0.0f)); |
| EXPECT_CALL(*audio_renderer_, SetVolume(1.0f)); |
| |
| // Startup sequence. |
| EXPECT_CALL(*audio_renderer_, Preroll(base::TimeDelta(), _)) |
| .WillOnce(RunCallback<1>(PIPELINE_OK)); |
| EXPECT_CALL(*audio_renderer_, Play(_)) |
| .WillOnce(RunClosure<0>()); |
| } |
| EXPECT_CALL(callbacks_, OnBufferingState(Pipeline::kPrerollCompleted)); |
| } |
| |
| pipeline_->Start( |
| filter_collection_.Pass(), |
| base::Bind(&CallbackHelper::OnEnded, base::Unretained(&callbacks_)), |
| base::Bind(&CallbackHelper::OnError, base::Unretained(&callbacks_)), |
| base::Bind(&CallbackHelper::OnStart, base::Unretained(&callbacks_)), |
| base::Bind(&CallbackHelper::OnBufferingState, |
| base::Unretained(&callbacks_)), |
| base::Bind(&CallbackHelper::OnDurationChange, |
| base::Unretained(&callbacks_))); |
| message_loop_.RunUntilIdle(); |
| } |
| |
| void CreateAudioStream() { |
| audio_stream_ = CreateStream(DemuxerStream::AUDIO); |
| } |
| |
| void CreateVideoStream() { |
| video_stream_ = CreateStream(DemuxerStream::VIDEO); |
| video_stream_->set_video_decoder_config(video_decoder_config_); |
| } |
| |
| void CreateTextStream() { |
| scoped_ptr<FakeTextTrackStream> text_stream(new FakeTextTrackStream); |
| text_stream_ = text_stream.Pass(); |
| } |
| |
| MockDemuxerStream* audio_stream() { |
| return audio_stream_.get(); |
| } |
| |
| MockDemuxerStream* video_stream() { |
| return video_stream_.get(); |
| } |
| |
| FakeTextTrackStream* text_stream() { |
| return text_stream_.get(); |
| } |
| |
| void ExpectSeek(const base::TimeDelta& seek_time) { |
| // Every filter should receive a call to Seek(). |
| EXPECT_CALL(*demuxer_, Seek(seek_time, _)) |
| .WillOnce(RunCallback<1>(PIPELINE_OK)); |
| |
| if (audio_stream_) { |
| EXPECT_CALL(*audio_renderer_, Pause(_)) |
| .WillOnce(RunClosure<0>()); |
| EXPECT_CALL(*audio_renderer_, Flush(_)) |
| .WillOnce(RunClosure<0>()); |
| EXPECT_CALL(*audio_renderer_, Preroll(seek_time, _)) |
| .WillOnce(RunCallback<1>(PIPELINE_OK)); |
| EXPECT_CALL(*audio_renderer_, SetPlaybackRate(_)); |
| EXPECT_CALL(*audio_renderer_, SetVolume(_)); |
| EXPECT_CALL(*audio_renderer_, Play(_)) |
| .WillOnce(RunClosure<0>()); |
| } |
| |
| if (video_stream_) { |
| EXPECT_CALL(*video_renderer_, Pause(_)) |
| .WillOnce(RunClosure<0>()); |
| EXPECT_CALL(*video_renderer_, Flush(_)) |
| .WillOnce(RunClosure<0>()); |
| EXPECT_CALL(*video_renderer_, Preroll(seek_time, _)) |
| .WillOnce(RunCallback<1>(PIPELINE_OK)); |
| EXPECT_CALL(*video_renderer_, SetPlaybackRate(_)); |
| EXPECT_CALL(*video_renderer_, Play(_)) |
| .WillOnce(RunClosure<0>()); |
| } |
| |
| EXPECT_CALL(callbacks_, OnBufferingState(Pipeline::kPrerollCompleted)); |
| |
| // We expect a successful seek callback. |
| EXPECT_CALL(callbacks_, OnSeek(PIPELINE_OK)); |
| } |
| |
| void DoSeek(const base::TimeDelta& seek_time) { |
| pipeline_->Seek(seek_time, |
| base::Bind(&CallbackHelper::OnSeek, |
| base::Unretained(&callbacks_))); |
| |
| // We expect the time to be updated only after the seek has completed. |
| EXPECT_NE(seek_time, pipeline_->GetMediaTime()); |
| message_loop_.RunUntilIdle(); |
| EXPECT_EQ(seek_time, pipeline_->GetMediaTime()); |
| } |
| |
| void ExpectStop() { |
| if (demuxer_) |
| EXPECT_CALL(*demuxer_, Stop(_)).WillOnce(RunClosure<0>()); |
| |
| if (audio_stream_) |
| EXPECT_CALL(*audio_renderer_, Stop(_)).WillOnce(RunClosure<0>()); |
| |
| if (video_stream_) |
| EXPECT_CALL(*video_renderer_, Stop(_)).WillOnce(RunClosure<0>()); |
| } |
| |
| MOCK_METHOD2(OnAddTextTrack, void(const TextTrackConfig&, |
| const AddTextTrackDoneCB&)); |
| |
| void DoOnAddTextTrack(const TextTrackConfig& config, |
| const AddTextTrackDoneCB& done_cb) { |
| scoped_ptr<TextTrack> text_track(new MockTextTrack); |
| done_cb.Run(text_track.Pass()); |
| } |
| |
| // Fixture members. |
| StrictMock<CallbackHelper> callbacks_; |
| base::SimpleTestTickClock test_tick_clock_; |
| base::MessageLoop message_loop_; |
| scoped_ptr<Pipeline> pipeline_; |
| |
| scoped_ptr<FilterCollection> filter_collection_; |
| scoped_ptr<MockDemuxer> demuxer_; |
| MockVideoRenderer* video_renderer_; |
| MockAudioRenderer* audio_renderer_; |
| StrictMock<CallbackHelper> text_renderer_callbacks_; |
| TextRenderer* text_renderer_; |
| scoped_ptr<StrictMock<MockDemuxerStream> > audio_stream_; |
| scoped_ptr<StrictMock<MockDemuxerStream> > video_stream_; |
| scoped_ptr<FakeTextTrackStream> text_stream_; |
| AudioRenderer::TimeCB audio_time_cb_; |
| VideoDecoderConfig video_decoder_config_; |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(PipelineTest); |
| }; |
| |
| // Test that playback controls methods no-op when the pipeline hasn't been |
| // started. |
| TEST_F(PipelineTest, NotStarted) { |
| const base::TimeDelta kZero; |
| |
| EXPECT_FALSE(pipeline_->IsRunning()); |
| EXPECT_FALSE(pipeline_->HasAudio()); |
| EXPECT_FALSE(pipeline_->HasVideo()); |
| |
| // Setting should still work. |
| EXPECT_EQ(0.0f, pipeline_->GetPlaybackRate()); |
| pipeline_->SetPlaybackRate(-1.0f); |
| EXPECT_EQ(0.0f, pipeline_->GetPlaybackRate()); |
| pipeline_->SetPlaybackRate(1.0f); |
| EXPECT_EQ(1.0f, pipeline_->GetPlaybackRate()); |
| |
| // Setting should still work. |
| EXPECT_EQ(1.0f, pipeline_->GetVolume()); |
| pipeline_->SetVolume(-1.0f); |
| EXPECT_EQ(1.0f, pipeline_->GetVolume()); |
| 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()); |
| |
| EXPECT_EQ(0, pipeline_->GetTotalBytes()); |
| |
| // Should always get set to zero. |
| gfx::Size size(1, 1); |
| pipeline_->GetNaturalVideoSize(&size); |
| EXPECT_EQ(0, size.width()); |
| EXPECT_EQ(0, size.height()); |
| } |
| |
| TEST_F(PipelineTest, NeverInitializes) { |
| // Don't execute the callback passed into Initialize(). |
| EXPECT_CALL(*demuxer_, Initialize(_, _, _)); |
| |
| // This test hangs during initialization by never calling |
| // InitializationComplete(). StrictMock<> will ensure that the callback is |
| // never executed. |
| pipeline_->Start( |
| filter_collection_.Pass(), |
| base::Bind(&CallbackHelper::OnEnded, base::Unretained(&callbacks_)), |
| base::Bind(&CallbackHelper::OnError, base::Unretained(&callbacks_)), |
| base::Bind(&CallbackHelper::OnStart, base::Unretained(&callbacks_)), |
| base::Bind(&CallbackHelper::OnBufferingState, |
| base::Unretained(&callbacks_)), |
| base::Bind(&CallbackHelper::OnDurationChange, |
| base::Unretained(&callbacks_))); |
| message_loop_.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_); |
| EXPECT_CALL(callbacks_, OnStart(PIPELINE_OK)); |
| } |
| |
| TEST_F(PipelineTest, URLNotFound) { |
| EXPECT_CALL(*demuxer_, Initialize(_, _, _)) |
| .WillOnce(RunCallback<1>(PIPELINE_ERROR_URL_NOT_FOUND)); |
| EXPECT_CALL(*demuxer_, Stop(_)) |
| .WillOnce(RunClosure<0>()); |
| |
| InitializePipeline(PIPELINE_ERROR_URL_NOT_FOUND); |
| } |
| |
| TEST_F(PipelineTest, NoStreams) { |
| EXPECT_CALL(*demuxer_, Initialize(_, _, _)) |
| .WillOnce(RunCallback<1>(PIPELINE_OK)); |
| EXPECT_CALL(*demuxer_, Stop(_)) |
| .WillOnce(RunClosure<0>()); |
| |
| InitializePipeline(PIPELINE_ERROR_COULD_NOT_RENDER); |
| } |
| |
| TEST_F(PipelineTest, AudioStream) { |
| CreateAudioStream(); |
| MockDemuxerStreamVector streams; |
| streams.push_back(audio_stream()); |
| |
| InitializeDemuxer(&streams); |
| InitializeAudioRenderer(audio_stream(), false); |
| |
| InitializePipeline(PIPELINE_OK); |
| EXPECT_TRUE(pipeline_->HasAudio()); |
| EXPECT_FALSE(pipeline_->HasVideo()); |
| } |
| |
| TEST_F(PipelineTest, VideoStream) { |
| CreateVideoStream(); |
| MockDemuxerStreamVector streams; |
| streams.push_back(video_stream()); |
| |
| InitializeDemuxer(&streams); |
| InitializeVideoRenderer(video_stream()); |
| |
| InitializePipeline(PIPELINE_OK); |
| EXPECT_FALSE(pipeline_->HasAudio()); |
| EXPECT_TRUE(pipeline_->HasVideo()); |
| } |
| |
| TEST_F(PipelineTest, AudioVideoStream) { |
| CreateAudioStream(); |
| CreateVideoStream(); |
| MockDemuxerStreamVector streams; |
| streams.push_back(audio_stream()); |
| streams.push_back(video_stream()); |
| |
| InitializeDemuxer(&streams); |
| InitializeAudioRenderer(audio_stream(), false); |
| InitializeVideoRenderer(video_stream()); |
| |
| InitializePipeline(PIPELINE_OK); |
| EXPECT_TRUE(pipeline_->HasAudio()); |
| EXPECT_TRUE(pipeline_->HasVideo()); |
| } |
| |
| TEST_F(PipelineTest, VideoTextStream) { |
| CreateVideoStream(); |
| CreateTextStream(); |
| MockDemuxerStreamVector streams; |
| streams.push_back(video_stream()); |
| |
| InitializeDemuxer(&streams); |
| InitializeVideoRenderer(video_stream()); |
| |
| InitializePipeline(PIPELINE_OK); |
| EXPECT_FALSE(pipeline_->HasAudio()); |
| EXPECT_TRUE(pipeline_->HasVideo()); |
| |
| AddTextStream(); |
| message_loop_.RunUntilIdle(); |
| } |
| |
| TEST_F(PipelineTest, VideoAudioTextStream) { |
| CreateVideoStream(); |
| CreateAudioStream(); |
| CreateTextStream(); |
| MockDemuxerStreamVector streams; |
| streams.push_back(video_stream()); |
| streams.push_back(audio_stream()); |
| |
| InitializeDemuxer(&streams); |
| InitializeVideoRenderer(video_stream()); |
| InitializeAudioRenderer(audio_stream(), false); |
| |
| InitializePipeline(PIPELINE_OK); |
| EXPECT_TRUE(pipeline_->HasAudio()); |
| EXPECT_TRUE(pipeline_->HasVideo()); |
| |
| AddTextStream(); |
| message_loop_.RunUntilIdle(); |
| } |
| |
| TEST_F(PipelineTest, Seek) { |
| CreateAudioStream(); |
| CreateVideoStream(); |
| CreateTextStream(); |
| MockDemuxerStreamVector streams; |
| streams.push_back(audio_stream()); |
| streams.push_back(video_stream()); |
| |
| InitializeDemuxer(&streams, base::TimeDelta::FromSeconds(3000)); |
| InitializeAudioRenderer(audio_stream(), false); |
| InitializeVideoRenderer(video_stream()); |
| |
| // Initialize then seek! |
| InitializePipeline(PIPELINE_OK); |
| |
| AddTextStream(); |
| message_loop_.RunUntilIdle(); |
| |
| // Every filter should receive a call to Seek(). |
| base::TimeDelta expected = base::TimeDelta::FromSeconds(2000); |
| ExpectSeek(expected); |
| DoSeek(expected); |
| } |
| |
| TEST_F(PipelineTest, SetVolume) { |
| CreateAudioStream(); |
| MockDemuxerStreamVector streams; |
| streams.push_back(audio_stream()); |
| |
| InitializeDemuxer(&streams); |
| InitializeAudioRenderer(audio_stream(), false); |
| |
| // The audio renderer should receive a call to SetVolume(). |
| float expected = 0.5f; |
| EXPECT_CALL(*audio_renderer_, SetVolume(expected)); |
| |
| // Initialize then set volume! |
| InitializePipeline(PIPELINE_OK); |
| pipeline_->SetVolume(expected); |
| } |
| |
| TEST_F(PipelineTest, Properties) { |
| CreateVideoStream(); |
| MockDemuxerStreamVector streams; |
| streams.push_back(video_stream()); |
| |
| const base::TimeDelta kDuration = base::TimeDelta::FromSeconds(100); |
| InitializeDemuxer(&streams, kDuration); |
| InitializeVideoRenderer(video_stream()); |
| |
| InitializePipeline(PIPELINE_OK); |
| EXPECT_EQ(kDuration.ToInternalValue(), |
| pipeline_->GetMediaDuration().ToInternalValue()); |
| EXPECT_EQ(kTotalBytes, pipeline_->GetTotalBytes()); |
| EXPECT_FALSE(pipeline_->DidLoadingProgress()); |
| } |
| |
| TEST_F(PipelineTest, GetBufferedTimeRanges) { |
| CreateVideoStream(); |
| MockDemuxerStreamVector streams; |
| streams.push_back(video_stream()); |
| |
| const base::TimeDelta kDuration = base::TimeDelta::FromSeconds(100); |
| InitializeDemuxer(&streams, kDuration); |
| InitializeVideoRenderer(video_stream()); |
| |
| InitializePipeline(PIPELINE_OK); |
| |
| EXPECT_EQ(0u, pipeline_->GetBufferedTimeRanges().size()); |
| |
| EXPECT_FALSE(pipeline_->DidLoadingProgress()); |
| pipeline_->AddBufferedByteRange(0, kTotalBytes / 8); |
| EXPECT_TRUE(pipeline_->DidLoadingProgress()); |
| EXPECT_FALSE(pipeline_->DidLoadingProgress()); |
| EXPECT_EQ(1u, pipeline_->GetBufferedTimeRanges().size()); |
| EXPECT_EQ(base::TimeDelta(), pipeline_->GetBufferedTimeRanges().start(0)); |
| EXPECT_EQ(kDuration / 8, pipeline_->GetBufferedTimeRanges().end(0)); |
| pipeline_->AddBufferedTimeRange(base::TimeDelta(), kDuration / 8); |
| EXPECT_EQ(base::TimeDelta(), pipeline_->GetBufferedTimeRanges().start(0)); |
| EXPECT_EQ(kDuration / 8, pipeline_->GetBufferedTimeRanges().end(0)); |
| |
| base::TimeDelta kSeekTime = kDuration / 2; |
| ExpectSeek(kSeekTime); |
| DoSeek(kSeekTime); |
| |
| EXPECT_TRUE(pipeline_->DidLoadingProgress()); |
| EXPECT_FALSE(pipeline_->DidLoadingProgress()); |
| pipeline_->AddBufferedByteRange(kTotalBytes / 2, |
| kTotalBytes / 2 + kTotalBytes / 8); |
| EXPECT_TRUE(pipeline_->DidLoadingProgress()); |
| EXPECT_FALSE(pipeline_->DidLoadingProgress()); |
| EXPECT_EQ(2u, pipeline_->GetBufferedTimeRanges().size()); |
| EXPECT_EQ(base::TimeDelta(), pipeline_->GetBufferedTimeRanges().start(0)); |
| EXPECT_EQ(kDuration / 8, pipeline_->GetBufferedTimeRanges().end(0)); |
| EXPECT_EQ(kDuration / 2, pipeline_->GetBufferedTimeRanges().start(1)); |
| EXPECT_EQ(kDuration / 2 + kDuration / 8, |
| pipeline_->GetBufferedTimeRanges().end(1)); |
| |
| pipeline_->AddBufferedTimeRange(kDuration / 4, 3 * kDuration / 8); |
| EXPECT_EQ(base::TimeDelta(), pipeline_->GetBufferedTimeRanges().start(0)); |
| EXPECT_EQ(kDuration / 8, pipeline_->GetBufferedTimeRanges().end(0)); |
| EXPECT_EQ(kDuration / 4, pipeline_->GetBufferedTimeRanges().start(1)); |
| EXPECT_EQ(3* kDuration / 8, pipeline_->GetBufferedTimeRanges().end(1)); |
| EXPECT_EQ(kDuration / 2, pipeline_->GetBufferedTimeRanges().start(2)); |
| EXPECT_EQ(kDuration / 2 + kDuration / 8, |
| pipeline_->GetBufferedTimeRanges().end(2)); |
| } |
| |
| TEST_F(PipelineTest, DisableAudioRenderer) { |
| CreateAudioStream(); |
| CreateVideoStream(); |
| MockDemuxerStreamVector streams; |
| streams.push_back(audio_stream()); |
| streams.push_back(video_stream()); |
| |
| InitializeDemuxer(&streams); |
| InitializeAudioRenderer(audio_stream(), false); |
| InitializeVideoRenderer(video_stream()); |
| |
| InitializePipeline(PIPELINE_OK); |
| EXPECT_TRUE(pipeline_->HasAudio()); |
| EXPECT_TRUE(pipeline_->HasVideo()); |
| |
| EXPECT_CALL(*demuxer_, OnAudioRendererDisabled()); |
| pipeline_->OnAudioDisabled(); |
| |
| // Verify that ended event is fired when video ends. |
| EXPECT_CALL(callbacks_, OnEnded()); |
| pipeline_->OnVideoRendererEnded(); |
| } |
| |
| TEST_F(PipelineTest, DisableAudioRendererDuringInit) { |
| CreateAudioStream(); |
| CreateVideoStream(); |
| MockDemuxerStreamVector streams; |
| streams.push_back(audio_stream()); |
| streams.push_back(video_stream()); |
| |
| InitializeDemuxer(&streams); |
| InitializeAudioRenderer(audio_stream(), true); |
| InitializeVideoRenderer(video_stream()); |
| |
| EXPECT_CALL(*demuxer_, OnAudioRendererDisabled()); |
| |
| InitializePipeline(PIPELINE_OK); |
| EXPECT_FALSE(pipeline_->HasAudio()); |
| EXPECT_TRUE(pipeline_->HasVideo()); |
| |
| // Verify that ended event is fired when video ends. |
| EXPECT_CALL(callbacks_, OnEnded()); |
| pipeline_->OnVideoRendererEnded(); |
| } |
| |
| TEST_F(PipelineTest, EndedCallback) { |
| CreateAudioStream(); |
| CreateVideoStream(); |
| CreateTextStream(); |
| MockDemuxerStreamVector streams; |
| streams.push_back(audio_stream()); |
| streams.push_back(video_stream()); |
| |
| InitializeDemuxer(&streams); |
| InitializeAudioRenderer(audio_stream(), false); |
| InitializeVideoRenderer(video_stream()); |
| InitializePipeline(PIPELINE_OK); |
| |
| AddTextStream(); |
| |
| // The ended callback shouldn't run until all renderers have ended. |
| pipeline_->OnAudioRendererEnded(); |
| message_loop_.RunUntilIdle(); |
| |
| pipeline_->OnVideoRendererEnded(); |
| message_loop_.RunUntilIdle(); |
| |
| EXPECT_CALL(callbacks_, OnEnded()); |
| text_stream()->SendEosNotification(); |
| message_loop_.RunUntilIdle(); |
| } |
| |
| TEST_F(PipelineTest, AudioStreamShorterThanVideo) { |
| base::TimeDelta duration = base::TimeDelta::FromSeconds(10); |
| |
| CreateAudioStream(); |
| CreateVideoStream(); |
| MockDemuxerStreamVector streams; |
| streams.push_back(audio_stream()); |
| streams.push_back(video_stream()); |
| |
| // Replace the clock so we can simulate wallclock time advancing w/o using |
| // Sleep(). |
| pipeline_->SetClockForTesting(new Clock(&test_tick_clock_)); |
| |
| InitializeDemuxer(&streams, duration); |
| InitializeAudioRenderer(audio_stream(), false); |
| InitializeVideoRenderer(video_stream()); |
| InitializePipeline(PIPELINE_OK); |
| |
| EXPECT_EQ(0, pipeline_->GetMediaTime().ToInternalValue()); |
| |
| float playback_rate = 1.0f; |
| EXPECT_CALL(*video_renderer_, SetPlaybackRate(playback_rate)); |
| EXPECT_CALL(*audio_renderer_, SetPlaybackRate(playback_rate)); |
| pipeline_->SetPlaybackRate(playback_rate); |
| message_loop_.RunUntilIdle(); |
| |
| InSequence s; |
| |
| // Verify that the clock doesn't advance since it hasn't been started by |
| // a time update from the audio stream. |
| int64 start_time = pipeline_->GetMediaTime().ToInternalValue(); |
| test_tick_clock_.Advance(base::TimeDelta::FromMilliseconds(100)); |
| EXPECT_EQ(pipeline_->GetMediaTime().ToInternalValue(), start_time); |
| |
| // Signal end of audio stream. |
| pipeline_->OnAudioRendererEnded(); |
| message_loop_.RunUntilIdle(); |
| |
| // Verify that the clock advances. |
| start_time = pipeline_->GetMediaTime().ToInternalValue(); |
| test_tick_clock_.Advance(base::TimeDelta::FromMilliseconds(100)); |
| EXPECT_GT(pipeline_->GetMediaTime().ToInternalValue(), start_time); |
| |
| // Signal end of video stream and make sure OnEnded() callback occurs. |
| EXPECT_CALL(callbacks_, OnEnded()); |
| pipeline_->OnVideoRendererEnded(); |
| } |
| |
| TEST_F(PipelineTest, ErrorDuringSeek) { |
| CreateAudioStream(); |
| MockDemuxerStreamVector streams; |
| streams.push_back(audio_stream()); |
| |
| InitializeDemuxer(&streams); |
| InitializeAudioRenderer(audio_stream(), false); |
| InitializePipeline(PIPELINE_OK); |
| |
| float playback_rate = 1.0f; |
| EXPECT_CALL(*audio_renderer_, SetPlaybackRate(playback_rate)); |
| pipeline_->SetPlaybackRate(playback_rate); |
| message_loop_.RunUntilIdle(); |
| |
| base::TimeDelta seek_time = base::TimeDelta::FromSeconds(5); |
| |
| // Preroll() isn't called as the demuxer errors out first. |
| EXPECT_CALL(*audio_renderer_, Pause(_)) |
| .WillOnce(RunClosure<0>()); |
| EXPECT_CALL(*audio_renderer_, Flush(_)) |
| .WillOnce(RunClosure<0>()); |
| EXPECT_CALL(*audio_renderer_, Stop(_)) |
| .WillOnce(RunClosure<0>()); |
| |
| EXPECT_CALL(*demuxer_, Seek(seek_time, _)) |
| .WillOnce(RunCallback<1>(PIPELINE_ERROR_READ)); |
| EXPECT_CALL(*demuxer_, Stop(_)) |
| .WillOnce(RunClosure<0>()); |
| |
| pipeline_->Seek(seek_time, base::Bind(&CallbackHelper::OnSeek, |
| base::Unretained(&callbacks_))); |
| EXPECT_CALL(callbacks_, OnSeek(PIPELINE_ERROR_READ)); |
| message_loop_.RunUntilIdle(); |
| } |
| |
| // Invoked function OnError. This asserts that the pipeline does not enqueue |
| // non-teardown related tasks while tearing down. |
| static void TestNoCallsAfterError( |
| Pipeline* pipeline, base::MessageLoop* message_loop, |
| PipelineStatus /* status */) { |
| CHECK(pipeline); |
| CHECK(message_loop); |
| |
| // When we get to this stage, the message loop should be empty. |
| EXPECT_TRUE(message_loop->IsIdleForTesting()); |
| |
| // Make calls on pipeline after error has occurred. |
| pipeline->SetPlaybackRate(0.5f); |
| pipeline->SetVolume(0.5f); |
| |
| // No additional tasks should be queued as a result of these calls. |
| EXPECT_TRUE(message_loop->IsIdleForTesting()); |
| } |
| |
| TEST_F(PipelineTest, NoMessageDuringTearDownFromError) { |
| CreateAudioStream(); |
| MockDemuxerStreamVector streams; |
| streams.push_back(audio_stream()); |
| |
| InitializeDemuxer(&streams); |
| InitializeAudioRenderer(audio_stream(), false); |
| InitializePipeline(PIPELINE_OK); |
| |
| // Trigger additional requests on the pipeline during tear down from error. |
| base::Callback<void(PipelineStatus)> cb = base::Bind( |
| &TestNoCallsAfterError, pipeline_.get(), &message_loop_); |
| ON_CALL(callbacks_, OnError(_)) |
| .WillByDefault(Invoke(&cb, &base::Callback<void(PipelineStatus)>::Run)); |
| |
| base::TimeDelta seek_time = base::TimeDelta::FromSeconds(5); |
| |
| // Seek() isn't called as the demuxer errors out first. |
| EXPECT_CALL(*audio_renderer_, Pause(_)) |
| .WillOnce(RunClosure<0>()); |
| EXPECT_CALL(*audio_renderer_, Flush(_)) |
| .WillOnce(RunClosure<0>()); |
| EXPECT_CALL(*audio_renderer_, Stop(_)) |
| .WillOnce(RunClosure<0>()); |
| |
| EXPECT_CALL(*demuxer_, Seek(seek_time, _)) |
| .WillOnce(RunCallback<1>(PIPELINE_ERROR_READ)); |
| EXPECT_CALL(*demuxer_, Stop(_)) |
| .WillOnce(RunClosure<0>()); |
| |
| pipeline_->Seek(seek_time, base::Bind(&CallbackHelper::OnSeek, |
| base::Unretained(&callbacks_))); |
| EXPECT_CALL(callbacks_, OnSeek(PIPELINE_ERROR_READ)); |
| message_loop_.RunUntilIdle(); |
| } |
| |
| TEST_F(PipelineTest, StartTimeIsZero) { |
| CreateVideoStream(); |
| MockDemuxerStreamVector streams; |
| streams.push_back(video_stream()); |
| |
| const base::TimeDelta kDuration = base::TimeDelta::FromSeconds(100); |
| InitializeDemuxer(&streams, kDuration); |
| InitializeVideoRenderer(video_stream()); |
| |
| InitializePipeline(PIPELINE_OK); |
| EXPECT_FALSE(pipeline_->HasAudio()); |
| EXPECT_TRUE(pipeline_->HasVideo()); |
| |
| EXPECT_EQ(base::TimeDelta(), pipeline_->GetMediaTime()); |
| } |
| |
| TEST_F(PipelineTest, StartTimeIsNonZero) { |
| const base::TimeDelta kStartTime = base::TimeDelta::FromSeconds(4); |
| const base::TimeDelta kDuration = base::TimeDelta::FromSeconds(100); |
| |
| EXPECT_CALL(*demuxer_, GetStartTime()) |
| .WillRepeatedly(Return(kStartTime)); |
| |
| CreateVideoStream(); |
| MockDemuxerStreamVector streams; |
| streams.push_back(video_stream()); |
| |
| InitializeDemuxer(&streams, kDuration); |
| InitializeVideoRenderer(video_stream()); |
| |
| InitializePipeline(PIPELINE_OK); |
| EXPECT_FALSE(pipeline_->HasAudio()); |
| EXPECT_TRUE(pipeline_->HasVideo()); |
| |
| EXPECT_EQ(kStartTime, pipeline_->GetMediaTime()); |
| } |
| |
| static void RunTimeCB(const AudioRenderer::TimeCB& time_cb, |
| int time_in_ms, |
| int max_time_in_ms) { |
| time_cb.Run(base::TimeDelta::FromMilliseconds(time_in_ms), |
| base::TimeDelta::FromMilliseconds(max_time_in_ms)); |
| } |
| |
| TEST_F(PipelineTest, AudioTimeUpdateDuringSeek) { |
| CreateAudioStream(); |
| MockDemuxerStreamVector streams; |
| streams.push_back(audio_stream()); |
| |
| InitializeDemuxer(&streams); |
| InitializeAudioRenderer(audio_stream(), false); |
| InitializePipeline(PIPELINE_OK); |
| |
| float playback_rate = 1.0f; |
| EXPECT_CALL(*audio_renderer_, SetPlaybackRate(playback_rate)); |
| pipeline_->SetPlaybackRate(playback_rate); |
| message_loop_.RunUntilIdle(); |
| |
| // Provide an initial time update so that the pipeline transitions out of the |
| // "waiting for time update" state. |
| audio_time_cb_.Run(base::TimeDelta::FromMilliseconds(100), |
| base::TimeDelta::FromMilliseconds(500)); |
| |
| base::TimeDelta seek_time = base::TimeDelta::FromSeconds(5); |
| |
| // Arrange to trigger a time update while the demuxer is in the middle of |
| // seeking. This update should be ignored by the pipeline and the clock should |
| // not get updated. |
| base::Closure closure = base::Bind(&RunTimeCB, audio_time_cb_, 300, 700); |
| EXPECT_CALL(*demuxer_, Seek(seek_time, _)) |
| .WillOnce(DoAll(InvokeWithoutArgs(&closure, &base::Closure::Run), |
| RunCallback<1>(PIPELINE_OK))); |
| |
| EXPECT_CALL(*audio_renderer_, Pause(_)) |
| .WillOnce(RunClosure<0>()); |
| EXPECT_CALL(*audio_renderer_, Flush(_)) |
| .WillOnce(RunClosure<0>()); |
| EXPECT_CALL(*audio_renderer_, Preroll(seek_time, _)) |
| .WillOnce(RunCallback<1>(PIPELINE_OK)); |
| EXPECT_CALL(*audio_renderer_, SetPlaybackRate(_)); |
| EXPECT_CALL(*audio_renderer_, SetVolume(_)); |
| EXPECT_CALL(*audio_renderer_, Play(_)) |
| .WillOnce(RunClosure<0>()); |
| |
| EXPECT_CALL(callbacks_, OnBufferingState(Pipeline::kPrerollCompleted)); |
| EXPECT_CALL(callbacks_, OnSeek(PIPELINE_OK)); |
| DoSeek(seek_time); |
| |
| EXPECT_EQ(pipeline_->GetMediaTime(), seek_time); |
| |
| // Now that the seek is complete, verify that time updates advance the current |
| // time. |
| base::TimeDelta new_time = seek_time + base::TimeDelta::FromMilliseconds(100); |
| audio_time_cb_.Run(new_time, new_time); |
| |
| EXPECT_EQ(pipeline_->GetMediaTime(), new_time); |
| } |
| |
| static void DeletePipeline(scoped_ptr<Pipeline> pipeline) { |
| // |pipeline| will go out of scope. |
| } |
| |
| TEST_F(PipelineTest, DeleteAfterStop) { |
| CreateAudioStream(); |
| MockDemuxerStreamVector streams; |
| streams.push_back(audio_stream()); |
| InitializeDemuxer(&streams); |
| InitializeAudioRenderer(audio_stream(), false); |
| InitializePipeline(PIPELINE_OK); |
| |
| ExpectStop(); |
| |
| Pipeline* pipeline = pipeline_.get(); |
| pipeline->Stop(base::Bind(&DeletePipeline, base::Passed(&pipeline_))); |
| message_loop_.RunUntilIdle(); |
| } |
| |
| class PipelineTeardownTest : public PipelineTest { |
| public: |
| enum TeardownState { |
| kInitDemuxer, |
| kInitAudioRenderer, |
| kInitVideoRenderer, |
| kPausing, |
| kFlushing, |
| kSeeking, |
| kPrerolling, |
| kStarting, |
| kPlaying, |
| }; |
| |
| enum StopOrError { |
| kStop, |
| kError, |
| kErrorAndStop, |
| }; |
| |
| PipelineTeardownTest() {} |
| virtual ~PipelineTeardownTest() {} |
| |
| void RunTest(TeardownState state, StopOrError stop_or_error) { |
| switch (state) { |
| case kInitDemuxer: |
| case kInitAudioRenderer: |
| case kInitVideoRenderer: |
| DoInitialize(state, stop_or_error); |
| break; |
| |
| case kPausing: |
| case kFlushing: |
| case kSeeking: |
| case kPrerolling: |
| case kStarting: |
| DoInitialize(state, stop_or_error); |
| DoSeek(state, stop_or_error); |
| break; |
| |
| case kPlaying: |
| DoInitialize(state, stop_or_error); |
| DoStopOrError(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) { |
| PipelineStatus expected_status = |
| SetInitializeExpectations(state, stop_or_error); |
| |
| EXPECT_CALL(callbacks_, OnStart(expected_status)); |
| pipeline_->Start( |
| filter_collection_.Pass(), |
| base::Bind(&CallbackHelper::OnEnded, base::Unretained(&callbacks_)), |
| base::Bind(&CallbackHelper::OnError, base::Unretained(&callbacks_)), |
| base::Bind(&CallbackHelper::OnStart, base::Unretained(&callbacks_)), |
| base::Bind(&CallbackHelper::OnBufferingState, |
| base::Unretained(&callbacks_)), |
| base::Bind(&CallbackHelper::OnDurationChange, |
| base::Unretained(&callbacks_))); |
| message_loop_.RunUntilIdle(); |
| } |
| |
| PipelineStatus SetInitializeExpectations(TeardownState state, |
| StopOrError stop_or_error) { |
| PipelineStatus status = PIPELINE_OK; |
| base::Closure stop_cb = base::Bind( |
| &CallbackHelper::OnStop, base::Unretained(&callbacks_)); |
| |
| if (state == kInitDemuxer) { |
| if (stop_or_error == kStop) { |
| EXPECT_CALL(*demuxer_, Initialize(_, _, _)) |
| .WillOnce(DoAll(Stop(pipeline_.get(), stop_cb), |
| RunCallback<1>(PIPELINE_OK))); |
| EXPECT_CALL(callbacks_, OnStop()); |
| } else { |
| status = DEMUXER_ERROR_COULD_NOT_OPEN; |
| EXPECT_CALL(*demuxer_, Initialize(_, _, _)) |
| .WillOnce(RunCallback<1>(status)); |
| } |
| |
| EXPECT_CALL(*demuxer_, Stop(_)).WillOnce(RunClosure<0>()); |
| return status; |
| } |
| |
| CreateAudioStream(); |
| CreateVideoStream(); |
| MockDemuxerStreamVector streams; |
| streams.push_back(audio_stream()); |
| streams.push_back(video_stream()); |
| InitializeDemuxer(&streams, base::TimeDelta::FromSeconds(3000)); |
| |
| if (state == kInitAudioRenderer) { |
| if (stop_or_error == kStop) { |
| EXPECT_CALL(*audio_renderer_, Initialize(_, _, _, _, _, _, _, _)) |
| .WillOnce(DoAll(Stop(pipeline_.get(), stop_cb), |
| RunCallback<1>(PIPELINE_OK))); |
| EXPECT_CALL(callbacks_, OnStop()); |
| } else { |
| status = PIPELINE_ERROR_INITIALIZATION_FAILED; |
| EXPECT_CALL(*audio_renderer_, Initialize(_, _, _, _, _, _, _, _)) |
| .WillOnce(RunCallback<1>(status)); |
| } |
| |
| EXPECT_CALL(*demuxer_, Stop(_)).WillOnce(RunClosure<0>()); |
| EXPECT_CALL(*audio_renderer_, Stop(_)).WillOnce(RunClosure<0>()); |
| return status; |
| } |
| |
| EXPECT_CALL(*audio_renderer_, Initialize(_, _, _, _, _, _, _, _)) |
| .WillOnce(RunCallback<1>(PIPELINE_OK)); |
| |
| if (state == kInitVideoRenderer) { |
| if (stop_or_error == kStop) { |
| EXPECT_CALL(*video_renderer_, Initialize(_, _, _, _, _, _, _, _, _)) |
| .WillOnce(DoAll(Stop(pipeline_.get(), stop_cb), |
| RunCallback<1>(PIPELINE_OK))); |
| EXPECT_CALL(callbacks_, OnStop()); |
| } else { |
| status = PIPELINE_ERROR_INITIALIZATION_FAILED; |
| EXPECT_CALL(*video_renderer_, Initialize(_, _, _, _, _, _, _, _, _)) |
| .WillOnce(RunCallback<1>(status)); |
| } |
| |
| EXPECT_CALL(*demuxer_, Stop(_)).WillOnce(RunClosure<0>()); |
| EXPECT_CALL(*audio_renderer_, Stop(_)).WillOnce(RunClosure<0>()); |
| EXPECT_CALL(*video_renderer_, Stop(_)).WillOnce(RunClosure<0>()); |
| return status; |
| } |
| |
| EXPECT_CALL(*video_renderer_, Initialize(_, _, _, _, _, _, _, _, _)) |
| .WillOnce(RunCallback<1>(PIPELINE_OK)); |
| |
| EXPECT_CALL(callbacks_, OnBufferingState(Pipeline::kHaveMetadata)); |
| |
| // If we get here it's a successful initialization. |
| EXPECT_CALL(*audio_renderer_, Preroll(base::TimeDelta(), _)) |
| .WillOnce(RunCallback<1>(PIPELINE_OK)); |
| EXPECT_CALL(*video_renderer_, Preroll(base::TimeDelta(), _)) |
| .WillOnce(RunCallback<1>(PIPELINE_OK)); |
| |
| EXPECT_CALL(*audio_renderer_, SetPlaybackRate(0.0f)); |
| EXPECT_CALL(*video_renderer_, SetPlaybackRate(0.0f)); |
| EXPECT_CALL(*audio_renderer_, SetVolume(1.0f)); |
| |
| EXPECT_CALL(*audio_renderer_, Play(_)) |
| .WillOnce(RunClosure<0>()); |
| EXPECT_CALL(*video_renderer_, Play(_)) |
| .WillOnce(RunClosure<0>()); |
| |
| if (status == PIPELINE_OK) |
| EXPECT_CALL(callbacks_, OnBufferingState(Pipeline::kPrerollCompleted)); |
| |
| return status; |
| } |
| |
| void DoSeek(TeardownState state, StopOrError stop_or_error) { |
| InSequence s; |
| PipelineStatus status = SetSeekExpectations(state, stop_or_error); |
| |
| EXPECT_CALL(*demuxer_, Stop(_)).WillOnce(RunClosure<0>()); |
| EXPECT_CALL(*audio_renderer_, Stop(_)).WillOnce(RunClosure<0>()); |
| EXPECT_CALL(*video_renderer_, Stop(_)).WillOnce(RunClosure<0>()); |
| EXPECT_CALL(callbacks_, OnSeek(status)); |
| |
| if (status == PIPELINE_OK) { |
| EXPECT_CALL(callbacks_, OnStop()); |
| } |
| |
| pipeline_->Seek(base::TimeDelta::FromSeconds(10), base::Bind( |
| &CallbackHelper::OnSeek, base::Unretained(&callbacks_))); |
| message_loop_.RunUntilIdle(); |
| } |
| |
| PipelineStatus SetSeekExpectations(TeardownState state, |
| StopOrError stop_or_error) { |
| PipelineStatus status = PIPELINE_OK; |
| base::Closure stop_cb = base::Bind( |
| &CallbackHelper::OnStop, base::Unretained(&callbacks_)); |
| |
| if (state == kPausing) { |
| if (stop_or_error == kStop) { |
| EXPECT_CALL(*audio_renderer_, Pause(_)) |
| .WillOnce(DoAll(Stop(pipeline_.get(), stop_cb), RunClosure<0>())); |
| } else { |
| status = PIPELINE_ERROR_READ; |
| EXPECT_CALL(*audio_renderer_, Pause(_)).WillOnce( |
| DoAll(SetError(pipeline_.get(), status), RunClosure<0>())); |
| } |
| |
| return status; |
| } |
| |
| EXPECT_CALL(*audio_renderer_, Pause(_)).WillOnce(RunClosure<0>()); |
| EXPECT_CALL(*video_renderer_, Pause(_)).WillOnce(RunClosure<0>()); |
| |
| if (state == kFlushing) { |
| if (stop_or_error == kStop) { |
| EXPECT_CALL(*audio_renderer_, Flush(_)) |
| .WillOnce(DoAll(Stop(pipeline_.get(), stop_cb), RunClosure<0>())); |
| } else { |
| status = PIPELINE_ERROR_READ; |
| EXPECT_CALL(*audio_renderer_, Flush(_)).WillOnce( |
| DoAll(SetError(pipeline_.get(), status), RunClosure<0>())); |
| } |
| |
| return status; |
| } |
| |
| EXPECT_CALL(*audio_renderer_, Flush(_)).WillOnce(RunClosure<0>()); |
| EXPECT_CALL(*video_renderer_, Flush(_)).WillOnce(RunClosure<0>()); |
| |
| if (state == kSeeking) { |
| if (stop_or_error == kStop) { |
| EXPECT_CALL(*demuxer_, Seek(_, _)) |
| .WillOnce(DoAll(Stop(pipeline_.get(), stop_cb), |
| RunCallback<1>(PIPELINE_OK))); |
| } else { |
| status = PIPELINE_ERROR_READ; |
| EXPECT_CALL(*demuxer_, Seek(_, _)) |
| .WillOnce(RunCallback<1>(status)); |
| } |
| |
| return status; |
| } |
| |
| EXPECT_CALL(*demuxer_, Seek(_, _)) |
| .WillOnce(RunCallback<1>(PIPELINE_OK)); |
| |
| if (state == kPrerolling) { |
| if (stop_or_error == kStop) { |
| EXPECT_CALL(*audio_renderer_, Preroll(_, _)) |
| .WillOnce(DoAll(Stop(pipeline_.get(), stop_cb), |
| RunCallback<1>(PIPELINE_OK))); |
| } else { |
| status = PIPELINE_ERROR_READ; |
| EXPECT_CALL(*audio_renderer_, Preroll(_, _)) |
| .WillOnce(RunCallback<1>(status)); |
| } |
| |
| return status; |
| } |
| |
| EXPECT_CALL(*audio_renderer_, Preroll(_, _)) |
| .WillOnce(RunCallback<1>(PIPELINE_OK)); |
| EXPECT_CALL(*video_renderer_, Preroll(_, _)) |
| .WillOnce(RunCallback<1>(PIPELINE_OK)); |
| |
| // Playback rate and volume are updated prior to starting. |
| EXPECT_CALL(*audio_renderer_, SetPlaybackRate(0.0f)); |
| EXPECT_CALL(*video_renderer_, SetPlaybackRate(0.0f)); |
| EXPECT_CALL(*audio_renderer_, SetVolume(1.0f)); |
| |
| if (state == kStarting) { |
| if (stop_or_error == kStop) { |
| EXPECT_CALL(*audio_renderer_, Play(_)) |
| .WillOnce(DoAll(Stop(pipeline_.get(), stop_cb), RunClosure<0>())); |
| } else { |
| status = PIPELINE_ERROR_READ; |
| EXPECT_CALL(*audio_renderer_, Play(_)).WillOnce( |
| DoAll(SetError(pipeline_.get(), status), RunClosure<0>())); |
| } |
| return status; |
| } |
| |
| NOTREACHED() << "State not supported: " << state; |
| return status; |
| } |
| |
| void DoStopOrError(StopOrError stop_or_error) { |
| InSequence s; |
| |
| EXPECT_CALL(*demuxer_, Stop(_)).WillOnce(RunClosure<0>()); |
| EXPECT_CALL(*audio_renderer_, Stop(_)).WillOnce(RunClosure<0>()); |
| EXPECT_CALL(*video_renderer_, Stop(_)).WillOnce(RunClosure<0>()); |
| |
| switch (stop_or_error) { |
| case kStop: |
| EXPECT_CALL(callbacks_, OnStop()); |
| pipeline_->Stop(base::Bind( |
| &CallbackHelper::OnStop, base::Unretained(&callbacks_))); |
| break; |
| |
| case kError: |
| EXPECT_CALL(callbacks_, OnError(PIPELINE_ERROR_READ)); |
| pipeline_->SetErrorForTesting(PIPELINE_ERROR_READ); |
| break; |
| |
| case kErrorAndStop: |
| EXPECT_CALL(callbacks_, OnStop()); |
| pipeline_->SetErrorForTesting(PIPELINE_ERROR_READ); |
| pipeline_->Stop(base::Bind( |
| &CallbackHelper::OnStop, base::Unretained(&callbacks_))); |
| break; |
| } |
| |
| message_loop_.RunUntilIdle(); |
| } |
| |
| DISALLOW_COPY_AND_ASSIGN(PipelineTeardownTest); |
| }; |
| |
| #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, InitAudioRenderer); |
| INSTANTIATE_TEARDOWN_TEST(Stop, InitVideoRenderer); |
| INSTANTIATE_TEARDOWN_TEST(Stop, Pausing); |
| INSTANTIATE_TEARDOWN_TEST(Stop, Flushing); |
| INSTANTIATE_TEARDOWN_TEST(Stop, Seeking); |
| INSTANTIATE_TEARDOWN_TEST(Stop, Prerolling); |
| INSTANTIATE_TEARDOWN_TEST(Stop, Starting); |
| INSTANTIATE_TEARDOWN_TEST(Stop, Playing); |
| |
| INSTANTIATE_TEARDOWN_TEST(Error, InitDemuxer); |
| INSTANTIATE_TEARDOWN_TEST(Error, InitAudioRenderer); |
| INSTANTIATE_TEARDOWN_TEST(Error, InitVideoRenderer); |
| INSTANTIATE_TEARDOWN_TEST(Error, Pausing); |
| INSTANTIATE_TEARDOWN_TEST(Error, Flushing); |
| INSTANTIATE_TEARDOWN_TEST(Error, Seeking); |
| INSTANTIATE_TEARDOWN_TEST(Error, Prerolling); |
| INSTANTIATE_TEARDOWN_TEST(Error, Starting); |
| INSTANTIATE_TEARDOWN_TEST(Error, Playing); |
| |
| INSTANTIATE_TEARDOWN_TEST(ErrorAndStop, Playing); |
| |
| } // namespace media |