| // Copyright 2014 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <stdint.h> |
| |
| #include <memory> |
| #include <vector> |
| |
| #include "base/functional/bind.h" |
| #include "base/functional/callback_helpers.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/memory/raw_ptr_exclusion.h" |
| #include "base/run_loop.h" |
| #include "base/task/single_thread_task_runner.h" |
| #include "base/test/gmock_callback_support.h" |
| #include "base/test/gmock_move_support.h" |
| #include "base/test/simple_test_tick_clock.h" |
| #include "base/test/task_environment.h" |
| #include "base/time/time.h" |
| #include "media/base/mock_filters.h" |
| #include "media/base/test_helpers.h" |
| #include "media/renderers/renderer_impl.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "third_party/abseil-cpp/absl/types/optional.h" |
| |
| using ::base::test::RunCallback; |
| using ::base::test::RunClosure; |
| using ::base::test::RunOnceCallback; |
| using ::base::test::RunOnceClosure; |
| using ::testing::_; |
| using ::testing::DoAll; |
| using ::testing::InSequence; |
| using ::testing::Mock; |
| using ::testing::Return; |
| using ::testing::SaveArg; |
| using ::testing::StrictMock; |
| using ::testing::WithArg; |
| using ::testing::WithArgs; |
| |
| namespace media { |
| |
| const int64_t kStartPlayingTimeInMs = 100; |
| |
| ACTION_P2(SetBool, var, value) { |
| *var = value; |
| } |
| |
| ACTION_P3(SetBufferingState, renderer_client, buffering_state, reason) { |
| (*renderer_client)->OnBufferingStateChange(buffering_state, reason); |
| } |
| |
| ACTION_P2(SetError, renderer_client, error) { |
| (*renderer_client)->OnError(error); |
| } |
| |
| ACTION(PostCallback) { |
| base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(FROM_HERE, arg0); |
| } |
| |
| ACTION(PostQuitWhenIdle) { |
| base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask( |
| FROM_HERE, base::RunLoop::QuitCurrentWhenIdleClosureDeprecated()); |
| } |
| |
| class RendererImplTest : public ::testing::Test { |
| public: |
| class CallbackHelper : public MockRendererClient { |
| public: |
| CallbackHelper() = default; |
| |
| CallbackHelper(const CallbackHelper&) = delete; |
| CallbackHelper& operator=(const CallbackHelper&) = delete; |
| |
| virtual ~CallbackHelper() = default; |
| |
| // Completion callbacks. |
| MOCK_METHOD1(OnInitialize, void(PipelineStatus)); |
| MOCK_METHOD0(OnFlushed, void()); |
| MOCK_METHOD1(OnCdmAttached, void(bool)); |
| MOCK_METHOD1(OnDurationChange, void(base::TimeDelta duration)); |
| MOCK_METHOD0(OnVideoTrackChangeComplete, void()); |
| MOCK_METHOD0(OnAudioTrackChangeComplete, void()); |
| }; |
| |
| RendererImplTest() |
| : demuxer_(new StrictMock<MockDemuxer>()), |
| video_renderer_(new StrictMock<MockVideoRenderer>()), |
| audio_renderer_(new StrictMock<MockAudioRenderer>()), |
| renderer_impl_( |
| new RendererImpl(task_environment_.GetMainThreadTaskRunner(), |
| std::unique_ptr<AudioRenderer>(audio_renderer_), |
| std::unique_ptr<VideoRenderer>(video_renderer_))), |
| cdm_context_(new StrictMock<MockCdmContext>()), |
| video_renderer_client_(nullptr), |
| audio_renderer_client_(nullptr), |
| initialization_status_(PIPELINE_OK) { |
| // CreateAudioStream() and CreateVideoStream() overrides expectations for |
| // expected non-NULL streams. |
| EXPECT_CALL(*demuxer_, GetAllStreams()).WillRepeatedly(Return(streams_)); |
| } |
| |
| RendererImplTest(const RendererImplTest&) = delete; |
| RendererImplTest& operator=(const RendererImplTest&) = delete; |
| |
| ~RendererImplTest() override { Destroy(); } |
| |
| protected: |
| void Destroy() { |
| renderer_impl_.reset(); |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| 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 audio renderer to initialize. |
| void SetAudioRendererInitializeExpectations(PipelineStatus status) { |
| EXPECT_CALL(*audio_renderer_, OnInitialize(audio_stream_.get(), _, _, _)) |
| .WillOnce(DoAll(SaveArg<2>(&audio_renderer_client_), |
| RunOnceCallback<3>(status))); |
| } |
| |
| // Sets up expectations to allow the video renderer to initialize. |
| void SetVideoRendererInitializeExpectations(PipelineStatus status) { |
| EXPECT_CALL(*video_renderer_, OnInitialize(video_stream_.get(), _, _, _, _)) |
| .WillOnce(DoAll(SaveArg<2>(&video_renderer_client_), |
| RunOnceCallback<4>(status))); |
| } |
| |
| void InitializeAndExpect(PipelineStatus start_status) { |
| EXPECT_CALL(callbacks_, OnInitialize(start_status)) |
| .WillOnce(SaveArg<0>(&initialization_status_)); |
| if (is_encrypted_ && !is_cdm_set_) |
| EXPECT_CALL(callbacks_, OnWaiting(WaitingReason::kNoCdm)); |
| |
| if (start_status == PIPELINE_OK && audio_stream_) { |
| EXPECT_CALL(*audio_renderer_, GetTimeSource()) |
| .WillOnce(Return(&time_source_)); |
| } else { |
| renderer_impl_->set_time_source_for_testing(&time_source_); |
| } |
| |
| renderer_impl_->Initialize(demuxer_.get(), &callbacks_, |
| base::BindOnce(&CallbackHelper::OnInitialize, |
| base::Unretained(&callbacks_))); |
| base::RunLoop().RunUntilIdle(); |
| |
| if (start_status == PIPELINE_OK && audio_stream_) { |
| ON_CALL(*audio_renderer_, Flush(_)) |
| .WillByDefault([this](base::OnceClosure on_done) { |
| audio_renderer_client_->OnBufferingStateChange( |
| BUFFERING_HAVE_NOTHING, BUFFERING_CHANGE_REASON_UNKNOWN); |
| std::move(on_done).Run(); |
| }); |
| ON_CALL(*audio_renderer_, StartPlaying()) |
| .WillByDefault(SetBufferingState(&audio_renderer_client_, |
| BUFFERING_HAVE_ENOUGH, |
| BUFFERING_CHANGE_REASON_UNKNOWN)); |
| } |
| if (start_status == PIPELINE_OK && video_stream_) { |
| ON_CALL(*video_renderer_, Flush(_)) |
| .WillByDefault([this](base::OnceClosure on_done) { |
| video_renderer_client_->OnBufferingStateChange( |
| BUFFERING_HAVE_NOTHING, BUFFERING_CHANGE_REASON_UNKNOWN); |
| std::move(on_done).Run(); |
| }); |
| ON_CALL(*video_renderer_, StartPlayingFrom(_)) |
| .WillByDefault(SetBufferingState(&video_renderer_client_, |
| BUFFERING_HAVE_ENOUGH, |
| BUFFERING_CHANGE_REASON_UNKNOWN)); |
| } |
| } |
| |
| void CreateAudioStream() { |
| audio_stream_ = CreateStream(DemuxerStream::AUDIO); |
| streams_.push_back(audio_stream_.get()); |
| EXPECT_CALL(*demuxer_, GetAllStreams()).WillRepeatedly(Return(streams_)); |
| } |
| |
| void CreateVideoStream(bool is_encrypted = false) { |
| is_encrypted_ = is_encrypted; |
| video_stream_ = CreateStream(DemuxerStream::VIDEO); |
| video_stream_->set_video_decoder_config( |
| is_encrypted ? TestVideoConfig::NormalEncrypted() |
| : TestVideoConfig::Normal()); |
| streams_.push_back(video_stream_.get()); |
| EXPECT_CALL(*demuxer_, GetAllStreams()).WillRepeatedly(Return(streams_)); |
| } |
| |
| void CreateEncryptedVideoStream() { CreateVideoStream(true); } |
| |
| void CreateAudioAndVideoStream() { |
| CreateAudioStream(); |
| CreateVideoStream(); |
| } |
| |
| void InitializeWithAudio() { |
| CreateAudioStream(); |
| SetAudioRendererInitializeExpectations(PIPELINE_OK); |
| // There is a potential race between HTMLMediaElement/WMPI shutdown and |
| // renderers being initialized which might result in MediaResource |
| // GetAllStreams suddenly returning fewer streams than before or even |
| // returning |
| // and empty stream collection (see crbug.com/668604). So we are going to |
| // check here that GetAllStreams will be invoked exactly 3 times during |
| // RendererImpl initialization to help catch potential issues. Currently the |
| // GetAllStreams is invoked once from the RendererImpl::Initialize via |
| // HasEncryptedStream, once from the RendererImpl::InitializeAudioRenderer |
| // and once from the RendererImpl::InitializeVideoRenderer. |
| EXPECT_CALL(*demuxer_, GetAllStreams()) |
| .Times(3) |
| .WillRepeatedly(Return(streams_)); |
| InitializeAndExpect(PIPELINE_OK); |
| } |
| |
| void InitializeWithVideo() { |
| CreateVideoStream(); |
| SetVideoRendererInitializeExpectations(PIPELINE_OK); |
| // There is a potential race between HTMLMediaElement/WMPI shutdown and |
| // renderers being initialized which might result in MediaResource |
| // GetAllStreams suddenly returning fewer streams than before or even |
| // returning |
| // and empty stream collection (see crbug.com/668604). So we are going to |
| // check here that GetAllStreams will be invoked exactly 3 times during |
| // RendererImpl initialization to help catch potential issues. Currently the |
| // GetAllStreams is invoked once from the RendererImpl::Initialize via |
| // HasEncryptedStream, once from the RendererImpl::InitializeAudioRenderer |
| // and once from the RendererImpl::InitializeVideoRenderer. |
| EXPECT_CALL(*demuxer_, GetAllStreams()) |
| .Times(3) |
| .WillRepeatedly(Return(streams_)); |
| InitializeAndExpect(PIPELINE_OK); |
| } |
| |
| void InitializeWithAudioAndVideo() { |
| CreateAudioAndVideoStream(); |
| SetAudioRendererInitializeExpectations(PIPELINE_OK); |
| SetVideoRendererInitializeExpectations(PIPELINE_OK); |
| InitializeAndExpect(PIPELINE_OK); |
| } |
| |
| void Play() { |
| DCHECK(audio_stream_ || video_stream_); |
| EXPECT_CALL(callbacks_, |
| OnBufferingStateChange(BUFFERING_HAVE_ENOUGH, |
| BUFFERING_CHANGE_REASON_UNKNOWN)); |
| |
| base::TimeDelta start_time(base::Milliseconds(kStartPlayingTimeInMs)); |
| EXPECT_CALL(time_source_, SetMediaTime(start_time)); |
| EXPECT_CALL(time_source_, StartTicking()); |
| |
| if (audio_stream_) { |
| EXPECT_CALL(*audio_renderer_, StartPlaying()); |
| } |
| |
| if (video_stream_) { |
| EXPECT_CALL(*video_renderer_, StartPlayingFrom(start_time)); |
| } |
| |
| renderer_impl_->StartPlayingFrom(start_time); |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| void SetFlushExpectationsForAVRenderers() { |
| if (audio_stream_) |
| EXPECT_CALL(*audio_renderer_, Flush(_)); |
| |
| if (video_stream_) |
| EXPECT_CALL(*video_renderer_, Flush(_)); |
| } |
| |
| void Flush(bool underflowed) { |
| if (!underflowed) |
| EXPECT_CALL(time_source_, StopTicking()); |
| |
| SetFlushExpectationsForAVRenderers(); |
| EXPECT_CALL(callbacks_, OnFlushed()); |
| |
| renderer_impl_->Flush(base::BindOnce(&CallbackHelper::OnFlushed, |
| base::Unretained(&callbacks_))); |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| void SetPlaybackRate(double playback_rate) { |
| EXPECT_CALL(time_source_, SetPlaybackRate(playback_rate)); |
| renderer_impl_->SetPlaybackRate(playback_rate); |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| int64_t GetMediaTimeMs() { |
| return renderer_impl_->GetMediaTime().InMilliseconds(); |
| } |
| |
| bool IsMediaTimeAdvancing(double playback_rate) { |
| int64_t start_time_ms = GetMediaTimeMs(); |
| const int64_t time_to_advance_ms = 100; |
| |
| test_tick_clock_.Advance(base::Milliseconds(time_to_advance_ms)); |
| |
| if (GetMediaTimeMs() == start_time_ms + time_to_advance_ms * playback_rate) |
| return true; |
| |
| DCHECK_EQ(start_time_ms, GetMediaTimeMs()); |
| return false; |
| } |
| |
| bool IsMediaTimeAdvancing() { |
| return IsMediaTimeAdvancing(1.0); |
| } |
| |
| void SetCdmAndExpect(bool expected_result) { |
| EXPECT_CALL(callbacks_, OnCdmAttached(expected_result)) |
| .WillOnce(SaveArg<0>(&is_cdm_set_)); |
| renderer_impl_->SetCdm(cdm_context_.get(), |
| base::BindOnce(&CallbackHelper::OnCdmAttached, |
| base::Unretained(&callbacks_))); |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| void SetAudioTrackSwitchExpectations() { |
| InSequence track_switch_seq; |
| |
| // Called from withing OnEnabledAudioTracksChanged |
| EXPECT_CALL(time_source_, CurrentMediaTime()); |
| EXPECT_CALL(time_source_, CurrentMediaTime()); |
| EXPECT_CALL(time_source_, StopTicking()); |
| EXPECT_CALL(*audio_renderer_, Flush(_)); |
| |
| // Callback into RestartAudioRenderer |
| EXPECT_CALL(*audio_renderer_, StartPlaying()); |
| |
| // Callback into OnBufferingStateChange |
| EXPECT_CALL(time_source_, StartTicking()); |
| EXPECT_CALL(callbacks_, |
| OnBufferingStateChange(BUFFERING_HAVE_ENOUGH, |
| BUFFERING_CHANGE_REASON_UNKNOWN)); |
| } |
| |
| void SetVideoTrackSwitchExpectations() { |
| InSequence track_switch_seq; |
| |
| // Called from withing OnSelectedVideoTrackChanged |
| EXPECT_CALL(time_source_, CurrentMediaTime()); |
| EXPECT_CALL(*video_renderer_, Flush(_)); |
| |
| // Callback into RestartVideoRenderer |
| EXPECT_CALL(*video_renderer_, StartPlayingFrom(_)); |
| |
| // Callback into OnBufferingStateChange |
| EXPECT_CALL(callbacks_, |
| OnBufferingStateChange(BUFFERING_HAVE_ENOUGH, |
| BUFFERING_CHANGE_REASON_UNKNOWN)); |
| } |
| |
| // Fixture members. |
| base::test::SingleThreadTaskEnvironment task_environment_; |
| StrictMock<CallbackHelper> callbacks_; |
| base::SimpleTestTickClock test_tick_clock_; |
| |
| std::unique_ptr<StrictMock<MockDemuxer>> demuxer_; |
| raw_ptr<StrictMock<MockVideoRenderer>> video_renderer_; |
| raw_ptr<StrictMock<MockAudioRenderer>> audio_renderer_; |
| std::unique_ptr<RendererImpl> renderer_impl_; |
| std::unique_ptr<StrictMock<MockCdmContext>> cdm_context_; |
| |
| StrictMock<MockTimeSource> time_source_; |
| std::unique_ptr<StrictMock<MockDemuxerStream>> audio_stream_; |
| std::unique_ptr<StrictMock<MockDemuxerStream>> video_stream_; |
| std::vector<DemuxerStream*> streams_; |
| // This field is not a raw_ptr<> because it was filtered by the rewriter for: |
| // #addr-of |
| RAW_PTR_EXCLUSION RendererClient* video_renderer_client_; |
| // This field is not a raw_ptr<> because it was filtered by the rewriter for: |
| // #addr-of |
| RAW_PTR_EXCLUSION RendererClient* audio_renderer_client_; |
| VideoDecoderConfig video_decoder_config_; |
| PipelineStatus initialization_status_; |
| bool is_encrypted_ = false; |
| bool is_cdm_set_ = false; |
| }; |
| |
| TEST_F(RendererImplTest, Destroy_BeforeInitialize) { |
| Destroy(); |
| } |
| |
| TEST_F(RendererImplTest, Destroy_PendingInitialize) { |
| CreateAudioAndVideoStream(); |
| |
| SetAudioRendererInitializeExpectations(PIPELINE_OK); |
| // Not returning the video initialization callback. |
| EXPECT_CALL(*video_renderer_, OnInitialize(video_stream_.get(), _, _, _, _)); |
| |
| InitializeAndExpect(PIPELINE_ERROR_ABORT); |
| EXPECT_EQ(PIPELINE_OK, initialization_status_); |
| |
| Destroy(); |
| } |
| |
| TEST_F(RendererImplTest, Destroy_PendingInitializeWithoutCdm) { |
| CreateAudioStream(); |
| CreateEncryptedVideoStream(); |
| |
| // Audio is clear and video is encrypted. Initialization will not start |
| // because no CDM is set. So neither AudioRenderer::Initialize() nor |
| // VideoRenderer::Initialize() should not be called. The InitCB will be |
| // aborted when |renderer_impl_| is destructed. |
| InitializeAndExpect(PIPELINE_ERROR_ABORT); |
| EXPECT_EQ(PIPELINE_OK, initialization_status_); |
| |
| Destroy(); |
| } |
| |
| TEST_F(RendererImplTest, Destroy_PendingInitializeAfterSetCdm) { |
| CreateAudioStream(); |
| CreateEncryptedVideoStream(); |
| |
| // Audio is clear and video is encrypted. Initialization will not start |
| // because no CDM is set. |
| InitializeAndExpect(PIPELINE_ERROR_ABORT); |
| EXPECT_EQ(PIPELINE_OK, initialization_status_); |
| |
| SetAudioRendererInitializeExpectations(PIPELINE_OK); |
| // Not returning the video initialization callback. So initialization will |
| // be pending. |
| EXPECT_CALL(*video_renderer_, OnInitialize(video_stream_.get(), _, _, _, _)); |
| |
| // SetCdm() will trigger the initialization to start. But it will not complete |
| // because the |video_renderer_| is not returning the initialization callback. |
| SetCdmAndExpect(true); |
| EXPECT_EQ(PIPELINE_OK, initialization_status_); |
| |
| Destroy(); |
| } |
| |
| TEST_F(RendererImplTest, InitializeWithAudio) { |
| InitializeWithAudio(); |
| } |
| |
| TEST_F(RendererImplTest, InitializeWithVideo) { |
| InitializeWithVideo(); |
| } |
| |
| TEST_F(RendererImplTest, InitializeWithAudioVideo) { |
| InitializeWithAudioAndVideo(); |
| } |
| |
| TEST_F(RendererImplTest, InitializeWithAudio_Failed) { |
| CreateAudioStream(); |
| SetAudioRendererInitializeExpectations(PIPELINE_ERROR_INITIALIZATION_FAILED); |
| InitializeAndExpect(PIPELINE_ERROR_INITIALIZATION_FAILED); |
| } |
| |
| TEST_F(RendererImplTest, InitializeWithVideo_Failed) { |
| CreateVideoStream(); |
| SetVideoRendererInitializeExpectations(PIPELINE_ERROR_INITIALIZATION_FAILED); |
| InitializeAndExpect(PIPELINE_ERROR_INITIALIZATION_FAILED); |
| } |
| |
| TEST_F(RendererImplTest, InitializeWithAudioVideo_AudioRendererFailed) { |
| CreateAudioAndVideoStream(); |
| SetAudioRendererInitializeExpectations(PIPELINE_ERROR_INITIALIZATION_FAILED); |
| // VideoRenderer::Initialize() should not be called. |
| InitializeAndExpect(PIPELINE_ERROR_INITIALIZATION_FAILED); |
| } |
| |
| TEST_F(RendererImplTest, InitializeWithAudioVideo_VideoRendererFailed) { |
| CreateAudioAndVideoStream(); |
| SetAudioRendererInitializeExpectations(PIPELINE_OK); |
| SetVideoRendererInitializeExpectations(PIPELINE_ERROR_INITIALIZATION_FAILED); |
| InitializeAndExpect(PIPELINE_ERROR_INITIALIZATION_FAILED); |
| } |
| |
| TEST_F(RendererImplTest, SetCdmBeforeInitialize) { |
| // CDM will be successfully attached immediately if set before RendererImpl |
| // initialization, regardless of the later initialization result. |
| SetCdmAndExpect(true); |
| } |
| |
| TEST_F(RendererImplTest, SetCdmAfterInitialize_ClearStream) { |
| InitializeWithAudioAndVideo(); |
| EXPECT_EQ(PIPELINE_OK, initialization_status_); |
| |
| // CDM will be successfully attached immediately since initialization is |
| // completed. |
| SetCdmAndExpect(true); |
| } |
| |
| TEST_F(RendererImplTest, SetCdmAfterInitialize_EncryptedStream_Success) { |
| CreateAudioStream(); |
| CreateEncryptedVideoStream(); |
| |
| SetAudioRendererInitializeExpectations(PIPELINE_OK); |
| SetVideoRendererInitializeExpectations(PIPELINE_OK); |
| InitializeAndExpect(PIPELINE_OK); |
| // Initialization is pending until CDM is set. |
| EXPECT_EQ(PIPELINE_OK, initialization_status_); |
| |
| SetCdmAndExpect(true); |
| EXPECT_EQ(PIPELINE_OK, initialization_status_); |
| } |
| |
| TEST_F(RendererImplTest, SetCdmAfterInitialize_EncryptedStream_Failure) { |
| CreateAudioStream(); |
| CreateEncryptedVideoStream(); |
| |
| SetAudioRendererInitializeExpectations(PIPELINE_OK); |
| SetVideoRendererInitializeExpectations(PIPELINE_ERROR_INITIALIZATION_FAILED); |
| InitializeAndExpect(PIPELINE_ERROR_INITIALIZATION_FAILED); |
| // Initialization is pending until CDM is set. |
| EXPECT_EQ(PIPELINE_OK, initialization_status_); |
| |
| SetCdmAndExpect(true); |
| EXPECT_EQ(PIPELINE_ERROR_INITIALIZATION_FAILED, initialization_status_); |
| } |
| |
| TEST_F(RendererImplTest, SetCdmMultipleTimes) { |
| SetCdmAndExpect(true); |
| SetCdmAndExpect(false); // Do not support switching CDM. |
| } |
| |
| TEST_F(RendererImplTest, StartPlayingFrom) { |
| InitializeWithAudioAndVideo(); |
| Play(); |
| } |
| |
| TEST_F(RendererImplTest, StartPlayingFromWithPlaybackRate) { |
| InitializeWithAudioAndVideo(); |
| |
| // Play with a zero playback rate shouldn't start time. |
| Play(); |
| Mock::VerifyAndClearExpectations(video_renderer_); |
| |
| // Positive playback rate when ticking should start time. |
| EXPECT_CALL(*video_renderer_, OnTimeProgressing()); |
| SetPlaybackRate(1.0); |
| Mock::VerifyAndClearExpectations(video_renderer_); |
| |
| // Double notifications shouldn't be sent. |
| SetPlaybackRate(1.0); |
| Mock::VerifyAndClearExpectations(video_renderer_); |
| |
| // Zero playback rate should stop time. |
| EXPECT_CALL(*video_renderer_, OnTimeStopped()); |
| SetPlaybackRate(0.0); |
| Mock::VerifyAndClearExpectations(video_renderer_); |
| |
| // Double notifications shouldn't be sent. |
| SetPlaybackRate(0.0); |
| Mock::VerifyAndClearExpectations(video_renderer_); |
| |
| // Starting playback and flushing should cause time to stop. |
| EXPECT_CALL(*video_renderer_, OnTimeProgressing()); |
| EXPECT_CALL(*video_renderer_, OnTimeStopped()); |
| SetPlaybackRate(1.0); |
| Flush(false); |
| |
| // A positive playback rate when playback isn't started should do nothing. |
| SetPlaybackRate(1.0); |
| } |
| |
| TEST_F(RendererImplTest, FlushAfterInitialization) { |
| InitializeWithAudioAndVideo(); |
| EXPECT_CALL(callbacks_, OnFlushed()); |
| renderer_impl_->Flush(base::BindOnce(&CallbackHelper::OnFlushed, |
| base::Unretained(&callbacks_))); |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| TEST_F(RendererImplTest, FlushAfterPlay) { |
| InitializeWithAudioAndVideo(); |
| Play(); |
| Flush(false); |
| } |
| |
| TEST_F(RendererImplTest, FlushAfterUnderflow) { |
| InitializeWithAudioAndVideo(); |
| Play(); |
| |
| // Simulate underflow. |
| EXPECT_CALL(time_source_, StopTicking()); |
| EXPECT_CALL(callbacks_, |
| OnBufferingStateChange(BUFFERING_HAVE_NOTHING, |
| BUFFERING_CHANGE_REASON_UNKNOWN)); |
| audio_renderer_client_->OnBufferingStateChange( |
| BUFFERING_HAVE_NOTHING, BUFFERING_CHANGE_REASON_UNKNOWN); |
| |
| // Flush while underflowed. We shouldn't call StopTicking() again. |
| Flush(true); |
| } |
| |
| TEST_F(RendererImplTest, SetPlaybackRate) { |
| InitializeWithAudioAndVideo(); |
| SetPlaybackRate(1.0); |
| SetPlaybackRate(2.0); |
| } |
| |
| TEST_F(RendererImplTest, SetVolume) { |
| InitializeWithAudioAndVideo(); |
| EXPECT_CALL(*audio_renderer_, SetVolume(2.0f)); |
| renderer_impl_->SetVolume(2.0f); |
| } |
| |
| TEST_F(RendererImplTest, AudioStreamEnded) { |
| InitializeWithAudio(); |
| Play(); |
| |
| EXPECT_CALL(time_source_, StopTicking()); |
| EXPECT_CALL(callbacks_, OnEnded()); |
| |
| audio_renderer_client_->OnEnded(); |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| TEST_F(RendererImplTest, VideoStreamEnded) { |
| InitializeWithVideo(); |
| Play(); |
| |
| EXPECT_CALL(time_source_, StopTicking()); |
| EXPECT_CALL(callbacks_, OnEnded()); |
| EXPECT_CALL(*video_renderer_, OnTimeStopped()); |
| |
| video_renderer_client_->OnEnded(); |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| TEST_F(RendererImplTest, AudioVideoStreamsEnded) { |
| InitializeWithAudioAndVideo(); |
| Play(); |
| |
| // OnEnded() is called only when all streams have finished. |
| audio_renderer_client_->OnEnded(); |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_CALL(time_source_, StopTicking()); |
| EXPECT_CALL(callbacks_, OnEnded()); |
| EXPECT_CALL(*video_renderer_, OnTimeStopped()); |
| |
| video_renderer_client_->OnEnded(); |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| TEST_F(RendererImplTest, ErrorAfterInitialize) { |
| InitializeWithAudio(); |
| EXPECT_CALL(callbacks_, OnError(HasStatusCode(PIPELINE_ERROR_DECODE))); |
| audio_renderer_client_->OnError(PIPELINE_ERROR_DECODE); |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| TEST_F(RendererImplTest, ErrorDuringPlaying) { |
| InitializeWithAudio(); |
| Play(); |
| |
| EXPECT_CALL(callbacks_, OnError(HasStatusCode(PIPELINE_ERROR_DECODE))); |
| audio_renderer_client_->OnError(PIPELINE_ERROR_DECODE); |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| TEST_F(RendererImplTest, ErrorDuringFlush) { |
| InitializeWithAudio(); |
| Play(); |
| |
| InSequence s; |
| EXPECT_CALL(time_source_, StopTicking()); |
| EXPECT_CALL(*audio_renderer_, Flush(_)) |
| .WillOnce([this](base::OnceClosure on_done) { |
| audio_renderer_client_->OnError(PIPELINE_ERROR_DECODE); |
| std::move(on_done).Run(); |
| }); |
| EXPECT_CALL(callbacks_, OnError(HasStatusCode(PIPELINE_ERROR_DECODE))); |
| EXPECT_CALL(callbacks_, OnFlushed()); |
| renderer_impl_->Flush(base::BindOnce(&CallbackHelper::OnFlushed, |
| base::Unretained(&callbacks_))); |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| TEST_F(RendererImplTest, ErrorAfterFlush) { |
| InitializeWithAudio(); |
| Play(); |
| Flush(false); |
| |
| EXPECT_CALL(callbacks_, OnError(HasStatusCode(PIPELINE_ERROR_DECODE))); |
| audio_renderer_client_->OnError(PIPELINE_ERROR_DECODE); |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| TEST_F(RendererImplTest, ErrorDuringInitialize) { |
| CreateAudioAndVideoStream(); |
| SetAudioRendererInitializeExpectations(PIPELINE_OK); |
| |
| // Force an audio error to occur during video renderer initialization. |
| EXPECT_CALL(*video_renderer_, OnInitialize(video_stream_.get(), _, _, _, _)) |
| .WillOnce(DoAll(SetError(&audio_renderer_client_, PIPELINE_ERROR_DECODE), |
| SaveArg<2>(&video_renderer_client_), |
| RunOnceCallback<4>(PIPELINE_OK))); |
| |
| InitializeAndExpect(PIPELINE_ERROR_DECODE); |
| } |
| |
| TEST_F(RendererImplTest, AudioUnderflow) { |
| InitializeWithAudio(); |
| Play(); |
| |
| // Underflow should occur immediately with a single audio track. |
| EXPECT_CALL(time_source_, StopTicking()); |
| EXPECT_CALL(callbacks_, |
| OnBufferingStateChange(BUFFERING_HAVE_NOTHING, |
| BUFFERING_CHANGE_REASON_UNKNOWN)); |
| audio_renderer_client_->OnBufferingStateChange( |
| BUFFERING_HAVE_NOTHING, BUFFERING_CHANGE_REASON_UNKNOWN); |
| } |
| |
| TEST_F(RendererImplTest, AudioUnderflowWithVideo) { |
| InitializeWithAudioAndVideo(); |
| Play(); |
| |
| // Underflow should be immediate when both audio and video are present and |
| // audio underflows. |
| EXPECT_CALL(time_source_, StopTicking()); |
| EXPECT_CALL(callbacks_, |
| OnBufferingStateChange(BUFFERING_HAVE_NOTHING, |
| BUFFERING_CHANGE_REASON_UNKNOWN)); |
| audio_renderer_client_->OnBufferingStateChange( |
| BUFFERING_HAVE_NOTHING, BUFFERING_CHANGE_REASON_UNKNOWN); |
| } |
| |
| TEST_F(RendererImplTest, VideoUnderflow) { |
| InitializeWithVideo(); |
| Play(); |
| |
| // Underflow should occur immediately with a single video track. |
| EXPECT_CALL(time_source_, StopTicking()); |
| EXPECT_CALL(callbacks_, |
| OnBufferingStateChange(BUFFERING_HAVE_NOTHING, |
| BUFFERING_CHANGE_REASON_UNKNOWN)); |
| video_renderer_client_->OnBufferingStateChange( |
| BUFFERING_HAVE_NOTHING, BUFFERING_CHANGE_REASON_UNKNOWN); |
| } |
| |
| TEST_F(RendererImplTest, VideoUnderflowWithAudio) { |
| InitializeWithAudioAndVideo(); |
| Play(); |
| |
| // Set a zero threshold such that the underflow will be executed on the next |
| // run of the message loop. |
| renderer_impl_->set_video_underflow_threshold_for_testing(base::TimeDelta()); |
| |
| // Underflow should be delayed when both audio and video are present and video |
| // underflows. |
| EXPECT_CALL(callbacks_, |
| OnBufferingStateChange(BUFFERING_HAVE_NOTHING, |
| BUFFERING_CHANGE_REASON_UNKNOWN)); |
| video_renderer_client_->OnBufferingStateChange( |
| BUFFERING_HAVE_NOTHING, BUFFERING_CHANGE_REASON_UNKNOWN); |
| Mock::VerifyAndClearExpectations(&time_source_); |
| |
| EXPECT_CALL(time_source_, StopTicking()); |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| TEST_F(RendererImplTest, VideoUnderflowWithAudioVideoRecovers) { |
| InitializeWithAudioAndVideo(); |
| Play(); |
| |
| // Set a zero threshold such that the underflow will be executed on the next |
| // run of the message loop. |
| renderer_impl_->set_video_underflow_threshold_for_testing(base::TimeDelta()); |
| |
| // Underflow should be delayed when both audio and video are present and video |
| // underflows. |
| EXPECT_CALL(callbacks_, |
| OnBufferingStateChange(BUFFERING_HAVE_NOTHING, |
| BUFFERING_CHANGE_REASON_UNKNOWN)) |
| .Times(0); |
| video_renderer_client_->OnBufferingStateChange( |
| BUFFERING_HAVE_NOTHING, BUFFERING_CHANGE_REASON_UNKNOWN); |
| Mock::VerifyAndClearExpectations(&time_source_); |
| |
| // If video recovers, the underflow should never occur. |
| video_renderer_client_->OnBufferingStateChange( |
| BUFFERING_HAVE_ENOUGH, BUFFERING_CHANGE_REASON_UNKNOWN); |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| TEST_F(RendererImplTest, VideoAndAudioUnderflow) { |
| InitializeWithAudioAndVideo(); |
| Play(); |
| |
| // Set a zero threshold such that the underflow will be executed on the next |
| // run of the message loop. |
| renderer_impl_->set_video_underflow_threshold_for_testing(base::TimeDelta()); |
| |
| // Underflow should be delayed when both audio and video are present and video |
| // underflows. |
| EXPECT_CALL(callbacks_, |
| OnBufferingStateChange(BUFFERING_HAVE_NOTHING, |
| BUFFERING_CHANGE_REASON_UNKNOWN)) |
| .Times(0); |
| video_renderer_client_->OnBufferingStateChange( |
| BUFFERING_HAVE_NOTHING, BUFFERING_CHANGE_REASON_UNKNOWN); |
| Mock::VerifyAndClearExpectations(&time_source_); |
| |
| EXPECT_CALL(callbacks_, |
| OnBufferingStateChange(BUFFERING_HAVE_NOTHING, |
| BUFFERING_CHANGE_REASON_UNKNOWN)); |
| EXPECT_CALL(time_source_, StopTicking()); |
| audio_renderer_client_->OnBufferingStateChange( |
| BUFFERING_HAVE_NOTHING, BUFFERING_CHANGE_REASON_UNKNOWN); |
| |
| // Nothing else should primed on the message loop. |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| TEST_F(RendererImplTest, VideoUnderflowWithAudioFlush) { |
| InitializeWithAudioAndVideo(); |
| Play(); |
| |
| // Set a massive threshold such that it shouldn't fire within this test. |
| renderer_impl_->set_video_underflow_threshold_for_testing(base::Seconds(100)); |
| |
| // Simulate the cases where audio underflows and then video underflows. |
| EXPECT_CALL(time_source_, StopTicking()); |
| EXPECT_CALL(callbacks_, |
| OnBufferingStateChange(BUFFERING_HAVE_NOTHING, |
| BUFFERING_CHANGE_REASON_UNKNOWN)); |
| audio_renderer_client_->OnBufferingStateChange( |
| BUFFERING_HAVE_NOTHING, BUFFERING_CHANGE_REASON_UNKNOWN); |
| video_renderer_client_->OnBufferingStateChange( |
| BUFFERING_HAVE_NOTHING, BUFFERING_CHANGE_REASON_UNKNOWN); |
| Mock::VerifyAndClearExpectations(&time_source_); |
| |
| // Flush the audio and video renderers, both think they're in an underflow |
| // state, but if the video renderer underflow was deferred, RendererImpl would |
| // think it still has enough data. |
| EXPECT_CALL(*audio_renderer_, Flush(_)).WillOnce(RunOnceClosure<0>()); |
| EXPECT_CALL(*video_renderer_, Flush(_)).WillOnce(RunOnceClosure<0>()); |
| EXPECT_CALL(callbacks_, OnFlushed()); |
| renderer_impl_->Flush(base::BindOnce(&CallbackHelper::OnFlushed, |
| base::Unretained(&callbacks_))); |
| base::RunLoop().RunUntilIdle(); |
| |
| // Start playback after the flush, but never return BUFFERING_HAVE_ENOUGH from |
| // the video renderer (which simulates spool up time for the video renderer). |
| const base::TimeDelta kStartTime; |
| EXPECT_CALL(time_source_, SetMediaTime(kStartTime)); |
| EXPECT_CALL(time_source_, StartTicking()); |
| EXPECT_CALL(*audio_renderer_, StartPlaying()); |
| EXPECT_CALL(*video_renderer_, StartPlayingFrom(kStartTime)); |
| EXPECT_CALL(callbacks_, |
| OnBufferingStateChange(BUFFERING_HAVE_ENOUGH, |
| BUFFERING_CHANGE_REASON_UNKNOWN)); |
| renderer_impl_->StartPlayingFrom(kStartTime); |
| |
| // Nothing else should primed on the message loop. |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| TEST_F(RendererImplTest, AudioTrackDisableThenEnable) { |
| InitializeWithAudioAndVideo(); |
| Play(); |
| Mock::VerifyAndClearExpectations(&time_source_); |
| |
| base::RunLoop disable_wait; |
| SetAudioTrackSwitchExpectations(); |
| renderer_impl_->OnEnabledAudioTracksChanged({}, disable_wait.QuitClosure()); |
| disable_wait.Run(); |
| |
| base::RunLoop enable_wait; |
| SetAudioTrackSwitchExpectations(); |
| renderer_impl_->OnEnabledAudioTracksChanged({streams_[0]}, |
| enable_wait.QuitClosure()); |
| enable_wait.Run(); |
| } |
| |
| TEST_F(RendererImplTest, VideoTrackDisableThenEnable) { |
| InitializeWithAudioAndVideo(); |
| Play(); |
| Mock::VerifyAndClearExpectations(&time_source_); |
| |
| base::RunLoop disable_wait; |
| SetVideoTrackSwitchExpectations(); |
| renderer_impl_->OnSelectedVideoTracksChanged({}, disable_wait.QuitClosure()); |
| disable_wait.Run(); |
| |
| base::RunLoop enable_wait; |
| SetVideoTrackSwitchExpectations(); |
| renderer_impl_->OnSelectedVideoTracksChanged({streams_[1]}, |
| enable_wait.QuitClosure()); |
| enable_wait.Run(); |
| |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| TEST_F(RendererImplTest, AudioUnderflowDuringAudioTrackChange) { |
| InitializeWithAudioAndVideo(); |
| Play(); |
| |
| base::RunLoop loop; |
| |
| // Underflow should occur immediately with a single audio track. |
| EXPECT_CALL(time_source_, StopTicking()); |
| |
| // Capture the callback from the audio renderer flush. |
| base::OnceClosure audio_renderer_flush_cb; |
| EXPECT_CALL(*audio_renderer_, Flush(_)) |
| .WillOnce(MoveArg(&audio_renderer_flush_cb)); |
| |
| EXPECT_CALL(time_source_, CurrentMediaTime()).Times(2); |
| std::vector<DemuxerStream*> tracks; |
| renderer_impl_->OnEnabledAudioTracksChanged({}, loop.QuitClosure()); |
| |
| EXPECT_CALL(callbacks_, |
| OnBufferingStateChange(BUFFERING_HAVE_ENOUGH, |
| BUFFERING_CHANGE_REASON_UNKNOWN)); |
| |
| EXPECT_CALL(time_source_, StartTicking()); |
| EXPECT_CALL(*audio_renderer_, StartPlaying()); |
| audio_renderer_client_->OnBufferingStateChange( |
| BUFFERING_HAVE_NOTHING, BUFFERING_CHANGE_REASON_UNKNOWN); |
| std::move(audio_renderer_flush_cb).Run(); |
| loop.Run(); |
| } |
| |
| TEST_F(RendererImplTest, VideoUnderflowDuringVideoTrackChange) { |
| InitializeWithAudioAndVideo(); |
| Play(); |
| |
| base::RunLoop loop; |
| |
| // Capture the callback from the video renderer flush. |
| base::OnceClosure video_renderer_flush_cb; |
| { |
| InSequence track_switch_seq; |
| EXPECT_CALL(time_source_, CurrentMediaTime()); |
| EXPECT_CALL(*video_renderer_, Flush(_)) |
| .WillOnce(MoveArg(&video_renderer_flush_cb)); |
| EXPECT_CALL(*video_renderer_, StartPlayingFrom(_)); |
| EXPECT_CALL(callbacks_, |
| OnBufferingStateChange(BUFFERING_HAVE_ENOUGH, |
| BUFFERING_CHANGE_REASON_UNKNOWN)); |
| } |
| |
| renderer_impl_->OnSelectedVideoTracksChanged({}, loop.QuitClosure()); |
| |
| video_renderer_client_->OnBufferingStateChange( |
| BUFFERING_HAVE_NOTHING, BUFFERING_CHANGE_REASON_UNKNOWN); |
| std::move(video_renderer_flush_cb).Run(); |
| loop.Run(); |
| } |
| |
| TEST_F(RendererImplTest, VideoUnderflowDuringAudioTrackChange) { |
| InitializeWithAudioAndVideo(); |
| Play(); |
| |
| base::RunLoop loop; |
| |
| // Capture the callback from the audio renderer flush. |
| base::OnceClosure audio_renderer_flush_cb; |
| EXPECT_CALL(*audio_renderer_, Flush(_)) |
| .WillOnce(MoveArg(&audio_renderer_flush_cb)); |
| |
| EXPECT_CALL(time_source_, CurrentMediaTime()).Times(2); |
| EXPECT_CALL(time_source_, StopTicking()); |
| renderer_impl_->OnEnabledAudioTracksChanged({}, loop.QuitClosure()); |
| |
| EXPECT_CALL(*audio_renderer_, StartPlaying()); |
| video_renderer_client_->OnBufferingStateChange( |
| BUFFERING_HAVE_NOTHING, BUFFERING_CHANGE_REASON_UNKNOWN); |
| std::move(audio_renderer_flush_cb).Run(); |
| loop.Run(); |
| } |
| |
| TEST_F(RendererImplTest, AudioUnderflowDuringVideoTrackChange) { |
| InitializeWithAudioAndVideo(); |
| Play(); |
| |
| base::RunLoop loop; |
| EXPECT_CALL(callbacks_, |
| OnBufferingStateChange(BUFFERING_HAVE_NOTHING, |
| BUFFERING_CHANGE_REASON_UNKNOWN)); |
| EXPECT_CALL(time_source_, CurrentMediaTime()); |
| |
| // Capture the callback from the audio renderer flush. |
| base::OnceClosure video_renderer_flush_cb; |
| EXPECT_CALL(*video_renderer_, Flush(_)) |
| .WillOnce(MoveArg(&video_renderer_flush_cb)); |
| |
| renderer_impl_->OnSelectedVideoTracksChanged({}, loop.QuitClosure()); |
| |
| EXPECT_CALL(time_source_, StopTicking()); |
| EXPECT_CALL(*video_renderer_, StartPlayingFrom(_)); |
| |
| audio_renderer_client_->OnBufferingStateChange( |
| BUFFERING_HAVE_NOTHING, BUFFERING_CHANGE_REASON_UNKNOWN); |
| |
| std::move(video_renderer_flush_cb).Run(); |
| loop.Run(); |
| } |
| |
| TEST_F(RendererImplTest, VideoResumedFromUnderflowDuringAudioTrackChange) { |
| InitializeWithAudioAndVideo(); |
| Play(); |
| |
| // Underflow the renderer. |
| base::RunLoop underflow_wait; |
| EXPECT_CALL(callbacks_, |
| OnBufferingStateChange(BUFFERING_HAVE_NOTHING, |
| BUFFERING_CHANGE_REASON_UNKNOWN)) |
| .WillOnce(RunClosure(underflow_wait.QuitClosure())); |
| EXPECT_CALL(time_source_, StopTicking()); |
| video_renderer_client_->OnBufferingStateChange( |
| BUFFERING_HAVE_NOTHING, BUFFERING_CHANGE_REASON_UNKNOWN); |
| underflow_wait.Run(); |
| |
| // Start a track change. |
| base::OnceClosure audio_renderer_flush_cb; |
| base::RunLoop track_change; |
| { |
| InSequence track_switch_seq; |
| EXPECT_CALL(time_source_, CurrentMediaTime()).Times(2); |
| EXPECT_CALL(*audio_renderer_, Flush(_)) |
| .WillOnce(MoveArg(&audio_renderer_flush_cb)); |
| } |
| renderer_impl_->OnEnabledAudioTracksChanged({}, track_change.QuitClosure()); |
| |
| // Signal that the renderer has enough data to resume from underflow. |
| // Nothing should bubble up, since we are pending audio track change. |
| EXPECT_CALL(callbacks_, OnBufferingStateChange(_, _)).Times(0); |
| EXPECT_CALL(time_source_, StartTicking()).Times(0); |
| video_renderer_client_->OnBufferingStateChange( |
| BUFFERING_HAVE_ENOUGH, BUFFERING_CHANGE_REASON_UNKNOWN); |
| |
| // Finish the track change. |
| EXPECT_CALL(*audio_renderer_, StartPlaying()); |
| std::move(audio_renderer_flush_cb).Run(); |
| track_change.Run(); |
| } |
| |
| TEST_F(RendererImplTest, AudioResumedFromUnderflowDuringVideoTrackChange) { |
| InitializeWithAudioAndVideo(); |
| Play(); |
| |
| // Underflow the renderer. |
| base::RunLoop underflow_wait; |
| EXPECT_CALL(callbacks_, |
| OnBufferingStateChange(BUFFERING_HAVE_NOTHING, |
| BUFFERING_CHANGE_REASON_UNKNOWN)) |
| .WillOnce(RunClosure(underflow_wait.QuitClosure())); |
| EXPECT_CALL(time_source_, StopTicking()); |
| audio_renderer_client_->OnBufferingStateChange( |
| BUFFERING_HAVE_NOTHING, BUFFERING_CHANGE_REASON_UNKNOWN); |
| underflow_wait.Run(); |
| |
| // Start a track change. |
| base::OnceClosure video_renderer_flush_cb; |
| base::RunLoop track_change; |
| { |
| InSequence track_switch_seq; |
| EXPECT_CALL(time_source_, CurrentMediaTime()); |
| EXPECT_CALL(*video_renderer_, Flush(_)) |
| .WillOnce(MoveArg(&video_renderer_flush_cb)); |
| } |
| renderer_impl_->OnSelectedVideoTracksChanged({}, track_change.QuitClosure()); |
| |
| // Signal that the renderer has enough data to resume from underflow. |
| // Nothing should bubble up, since we are pending audio track change. |
| EXPECT_CALL(callbacks_, OnBufferingStateChange(_, _)).Times(0); |
| EXPECT_CALL(time_source_, StartTicking()).Times(0); |
| audio_renderer_client_->OnBufferingStateChange( |
| BUFFERING_HAVE_ENOUGH, BUFFERING_CHANGE_REASON_UNKNOWN); |
| |
| // Finish the track change. |
| EXPECT_CALL(*video_renderer_, StartPlayingFrom(_)); |
| std::move(video_renderer_flush_cb).Run(); |
| track_change.Run(); |
| } |
| |
| } // namespace media |