| // Copyright 2013 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 <utility> |
| |
| #include "base/bind.h" |
| #include "base/callback.h" |
| #include "base/callback_helpers.h" |
| #include "base/debug/stack_trace.h" |
| #include "base/message_loop/message_loop.h" |
| #include "base/stl_util.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/synchronization/lock.h" |
| #include "base/timer/timer.h" |
| #include "media/base/data_buffer.h" |
| #include "media/base/gmock_callback_support.h" |
| #include "media/base/limits.h" |
| #include "media/base/mock_filters.h" |
| #include "media/base/test_helpers.h" |
| #include "media/base/video_frame.h" |
| #include "media/filters/video_renderer_impl.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| using ::testing::_; |
| using ::testing::AnyNumber; |
| using ::testing::InSequence; |
| using ::testing::Invoke; |
| using ::testing::NiceMock; |
| using ::testing::NotNull; |
| using ::testing::Return; |
| using ::testing::StrictMock; |
| |
| namespace media { |
| |
| static const int kFrameDurationInMs = 10; |
| static const int kVideoDurationInMs = kFrameDurationInMs * 100; |
| |
| class VideoRendererImplTest : public ::testing::Test { |
| public: |
| VideoRendererImplTest() |
| : decoder_(new MockVideoDecoder()), |
| demuxer_stream_(DemuxerStream::VIDEO) { |
| ScopedVector<VideoDecoder> decoders; |
| decoders.push_back(decoder_); |
| |
| renderer_.reset(new VideoRendererImpl( |
| message_loop_.message_loop_proxy(), |
| decoders.Pass(), |
| media::SetDecryptorReadyCB(), |
| base::Bind(&VideoRendererImplTest::OnPaint, base::Unretained(this)), |
| base::Bind(&VideoRendererImplTest::OnSetOpaque, base::Unretained(this)), |
| true)); |
| |
| demuxer_stream_.set_video_decoder_config(TestVideoConfig::Normal()); |
| |
| // We expect these to be called but we don't care how/when. |
| EXPECT_CALL(demuxer_stream_, Read(_)) |
| .WillRepeatedly(RunCallback<0>(DemuxerStream::kOk, |
| DecoderBuffer::CreateEOSBuffer())); |
| EXPECT_CALL(*decoder_, Stop(_)) |
| .WillRepeatedly(Invoke(this, &VideoRendererImplTest::StopRequested)); |
| EXPECT_CALL(statistics_cb_object_, OnStatistics(_)) |
| .Times(AnyNumber()); |
| EXPECT_CALL(*this, OnTimeUpdate(_)) |
| .Times(AnyNumber()); |
| EXPECT_CALL(*this, OnSetOpaque(_)) |
| .Times(AnyNumber()); |
| } |
| |
| virtual ~VideoRendererImplTest() {} |
| |
| // Callbacks passed into VideoRendererImpl(). |
| MOCK_CONST_METHOD1(OnSetOpaque, void(bool)); |
| |
| // Callbacks passed into Initialize(). |
| MOCK_METHOD1(OnTimeUpdate, void(base::TimeDelta)); |
| MOCK_METHOD1(OnNaturalSizeChanged, void(const gfx::Size&)); |
| |
| void Initialize() { |
| InitializeWithDuration(kVideoDurationInMs); |
| } |
| |
| void InitializeWithDuration(int duration_ms) { |
| duration_ = base::TimeDelta::FromMilliseconds(duration_ms); |
| |
| // Monitor decodes from the decoder. |
| EXPECT_CALL(*decoder_, Decode(_, _)) |
| .WillRepeatedly(Invoke(this, &VideoRendererImplTest::FrameRequested)); |
| |
| EXPECT_CALL(*decoder_, Reset(_)) |
| .WillRepeatedly(Invoke(this, &VideoRendererImplTest::FlushRequested)); |
| |
| InSequence s; |
| |
| EXPECT_CALL(*decoder_, Initialize(_, _)) |
| .WillOnce(RunCallback<1>(PIPELINE_OK)); |
| |
| // Set playback rate before anything else happens. |
| renderer_->SetPlaybackRate(1.0f); |
| |
| // Initialize, we shouldn't have any reads. |
| InitializeRenderer(PIPELINE_OK); |
| |
| // We expect the video size to be set. |
| EXPECT_CALL(*this, |
| OnNaturalSizeChanged(TestVideoConfig::NormalCodedSize())); |
| |
| // Start prerolling. |
| QueuePrerollFrames(0); |
| Preroll(0, PIPELINE_OK); |
| } |
| |
| void InitializeRenderer(PipelineStatus expected) { |
| SCOPED_TRACE(base::StringPrintf("InitializeRenderer(%d)", expected)); |
| WaitableMessageLoopEvent event; |
| CallInitialize(event.GetPipelineStatusCB()); |
| event.RunAndWaitForStatus(expected); |
| } |
| |
| void CallInitialize(const PipelineStatusCB& status_cb) { |
| renderer_->Initialize( |
| &demuxer_stream_, |
| status_cb, |
| base::Bind(&MockStatisticsCB::OnStatistics, |
| base::Unretained(&statistics_cb_object_)), |
| base::Bind(&VideoRendererImplTest::OnTimeUpdate, |
| base::Unretained(this)), |
| base::Bind(&VideoRendererImplTest::OnNaturalSizeChanged, |
| base::Unretained(this)), |
| ended_event_.GetClosure(), |
| error_event_.GetPipelineStatusCB(), |
| base::Bind(&VideoRendererImplTest::GetTime, base::Unretained(this)), |
| base::Bind(&VideoRendererImplTest::GetDuration, |
| base::Unretained(this))); |
| } |
| |
| void Play() { |
| SCOPED_TRACE("Play()"); |
| WaitableMessageLoopEvent event; |
| renderer_->Play(event.GetClosure()); |
| event.RunAndWait(); |
| } |
| |
| void Preroll(int timestamp_ms, PipelineStatus expected) { |
| SCOPED_TRACE(base::StringPrintf("Preroll(%d, %d)", timestamp_ms, expected)); |
| WaitableMessageLoopEvent event; |
| renderer_->Preroll( |
| base::TimeDelta::FromMilliseconds(timestamp_ms), |
| event.GetPipelineStatusCB()); |
| event.RunAndWaitForStatus(expected); |
| } |
| |
| void Pause() { |
| SCOPED_TRACE("Pause()"); |
| WaitableMessageLoopEvent event; |
| renderer_->Pause(event.GetClosure()); |
| event.RunAndWait(); |
| } |
| |
| void Flush() { |
| SCOPED_TRACE("Flush()"); |
| WaitableMessageLoopEvent event; |
| renderer_->Flush(event.GetClosure()); |
| event.RunAndWait(); |
| } |
| |
| void Stop() { |
| SCOPED_TRACE("Stop()"); |
| WaitableMessageLoopEvent event; |
| renderer_->Stop(event.GetClosure()); |
| event.RunAndWait(); |
| } |
| |
| void Shutdown() { |
| Pause(); |
| Flush(); |
| Stop(); |
| } |
| |
| // Queues a VideoFrame with |next_frame_timestamp_|. |
| void QueueNextFrame() { |
| DCHECK_EQ(&message_loop_, base::MessageLoop::current()); |
| DCHECK_LT(next_frame_timestamp_.InMicroseconds(), |
| duration_.InMicroseconds()); |
| |
| gfx::Size natural_size = TestVideoConfig::NormalCodedSize(); |
| scoped_refptr<VideoFrame> frame = VideoFrame::CreateFrame( |
| VideoFrame::YV12, natural_size, gfx::Rect(natural_size), natural_size, |
| next_frame_timestamp_); |
| decode_results_.push_back(std::make_pair( |
| VideoDecoder::kOk, frame)); |
| next_frame_timestamp_ += |
| base::TimeDelta::FromMilliseconds(kFrameDurationInMs); |
| } |
| |
| void QueueEndOfStream() { |
| DCHECK_EQ(&message_loop_, base::MessageLoop::current()); |
| decode_results_.push_back(std::make_pair( |
| VideoDecoder::kOk, VideoFrame::CreateEOSFrame())); |
| } |
| |
| void QueueDecodeError() { |
| DCHECK_EQ(&message_loop_, base::MessageLoop::current()); |
| scoped_refptr<VideoFrame> null_frame; |
| decode_results_.push_back(std::make_pair( |
| VideoDecoder::kDecodeError, null_frame)); |
| } |
| |
| void QueueAbortedRead() { |
| DCHECK_EQ(&message_loop_, base::MessageLoop::current()); |
| scoped_refptr<VideoFrame> null_frame; |
| decode_results_.push_back(std::make_pair( |
| VideoDecoder::kOk, null_frame)); |
| } |
| |
| void QueuePrerollFrames(int timestamp_ms) { |
| DCHECK_EQ(&message_loop_, base::MessageLoop::current()); |
| next_frame_timestamp_ = base::TimeDelta(); |
| base::TimeDelta timestamp = base::TimeDelta::FromMilliseconds(timestamp_ms); |
| while (next_frame_timestamp_ < timestamp) { |
| QueueNextFrame(); |
| } |
| |
| // Queue the frame at |timestamp| plus additional ones for prerolling. |
| for (int i = 0; i < limits::kMaxVideoFrames; ++i) { |
| QueueNextFrame(); |
| } |
| } |
| |
| void ResetCurrentFrame() { |
| base::AutoLock l(lock_); |
| current_frame_ = NULL; |
| } |
| |
| scoped_refptr<VideoFrame> GetCurrentFrame() { |
| base::AutoLock l(lock_); |
| return current_frame_; |
| } |
| |
| int GetCurrentTimestampInMs() { |
| scoped_refptr<VideoFrame> frame = GetCurrentFrame(); |
| if (!frame.get()) |
| return -1; |
| return frame->GetTimestamp().InMilliseconds(); |
| } |
| |
| void WaitForError(PipelineStatus expected) { |
| SCOPED_TRACE(base::StringPrintf("WaitForError(%d)", expected)); |
| error_event_.RunAndWaitForStatus(expected); |
| } |
| |
| void WaitForEnded() { |
| SCOPED_TRACE("WaitForEnded()"); |
| ended_event_.RunAndWait(); |
| } |
| |
| void WaitForPendingRead() { |
| SCOPED_TRACE("WaitForPendingRead()"); |
| if (!read_cb_.is_null()) |
| return; |
| |
| DCHECK(wait_for_pending_read_cb_.is_null()); |
| |
| WaitableMessageLoopEvent event; |
| wait_for_pending_read_cb_ = event.GetClosure(); |
| event.RunAndWait(); |
| |
| DCHECK(!read_cb_.is_null()); |
| DCHECK(wait_for_pending_read_cb_.is_null()); |
| } |
| |
| void SatisfyPendingRead() { |
| CHECK(!read_cb_.is_null()); |
| CHECK(!decode_results_.empty()); |
| |
| base::Closure closure = base::Bind( |
| read_cb_, decode_results_.front().first, |
| decode_results_.front().second); |
| |
| read_cb_.Reset(); |
| decode_results_.pop_front(); |
| |
| message_loop_.PostTask(FROM_HERE, closure); |
| } |
| |
| void AdvanceTimeInMs(int time_ms) { |
| DCHECK_EQ(&message_loop_, base::MessageLoop::current()); |
| base::AutoLock l(lock_); |
| time_ += base::TimeDelta::FromMilliseconds(time_ms); |
| DCHECK_LE(time_.InMicroseconds(), duration_.InMicroseconds()); |
| } |
| |
| protected: |
| // Fixture members. |
| scoped_ptr<VideoRendererImpl> renderer_; |
| MockVideoDecoder* decoder_; // Owned by |renderer_|. |
| NiceMock<MockDemuxerStream> demuxer_stream_; |
| MockStatisticsCB statistics_cb_object_; |
| |
| private: |
| base::TimeDelta GetTime() { |
| base::AutoLock l(lock_); |
| return time_; |
| } |
| |
| base::TimeDelta GetDuration() { |
| return duration_; |
| } |
| |
| void OnPaint(const scoped_refptr<VideoFrame>& frame) { |
| base::AutoLock l(lock_); |
| current_frame_ = frame; |
| } |
| |
| void FrameRequested(const scoped_refptr<DecoderBuffer>& buffer, |
| const VideoDecoder::DecodeCB& read_cb) { |
| DCHECK_EQ(&message_loop_, base::MessageLoop::current()); |
| CHECK(read_cb_.is_null()); |
| read_cb_ = read_cb; |
| |
| // Wake up WaitForPendingRead() if needed. |
| if (!wait_for_pending_read_cb_.is_null()) |
| base::ResetAndReturn(&wait_for_pending_read_cb_).Run(); |
| |
| if (decode_results_.empty()) |
| return; |
| |
| SatisfyPendingRead(); |
| } |
| |
| void FlushRequested(const base::Closure& callback) { |
| DCHECK_EQ(&message_loop_, base::MessageLoop::current()); |
| decode_results_.clear(); |
| if (!read_cb_.is_null()) { |
| QueueAbortedRead(); |
| SatisfyPendingRead(); |
| } |
| |
| message_loop_.PostTask(FROM_HERE, callback); |
| } |
| |
| void StopRequested(const base::Closure& callback) { |
| DCHECK_EQ(&message_loop_, base::MessageLoop::current()); |
| decode_results_.clear(); |
| if (!read_cb_.is_null()) { |
| QueueAbortedRead(); |
| SatisfyPendingRead(); |
| } |
| |
| message_loop_.PostTask(FROM_HERE, callback); |
| } |
| |
| base::MessageLoop message_loop_; |
| |
| // Used to protect |time_| and |current_frame_|. |
| base::Lock lock_; |
| base::TimeDelta time_; |
| scoped_refptr<VideoFrame> current_frame_; |
| |
| // Used for satisfying reads. |
| VideoDecoder::DecodeCB read_cb_; |
| base::TimeDelta next_frame_timestamp_; |
| base::TimeDelta duration_; |
| |
| WaitableMessageLoopEvent error_event_; |
| WaitableMessageLoopEvent ended_event_; |
| |
| // Run during FrameRequested() to unblock WaitForPendingRead(). |
| base::Closure wait_for_pending_read_cb_; |
| |
| std::deque<std::pair< |
| VideoDecoder::Status, scoped_refptr<VideoFrame> > > decode_results_; |
| |
| DISALLOW_COPY_AND_ASSIGN(VideoRendererImplTest); |
| }; |
| |
| TEST_F(VideoRendererImplTest, DoNothing) { |
| // Test that creation and deletion doesn't depend on calls to Initialize() |
| // and/or Stop(). |
| } |
| |
| TEST_F(VideoRendererImplTest, StopWithoutInitialize) { |
| Stop(); |
| } |
| |
| TEST_F(VideoRendererImplTest, Initialize) { |
| Initialize(); |
| EXPECT_EQ(0, GetCurrentTimestampInMs()); |
| Shutdown(); |
| } |
| |
| static void ExpectNotCalled(PipelineStatus) { |
| base::debug::StackTrace stack; |
| ADD_FAILURE() << "Expected callback not to be called\n" << stack.ToString(); |
| } |
| |
| TEST_F(VideoRendererImplTest, StopWhileInitializing) { |
| EXPECT_CALL(*decoder_, Initialize(_, _)) |
| .WillOnce(RunCallback<1>(PIPELINE_OK)); |
| CallInitialize(base::Bind(&ExpectNotCalled)); |
| Stop(); |
| |
| // ~VideoRendererImpl() will CHECK() if we left anything initialized. |
| } |
| |
| TEST_F(VideoRendererImplTest, StopWhileFlushing) { |
| Initialize(); |
| Pause(); |
| renderer_->Flush(base::Bind(&ExpectNotCalled, PIPELINE_OK)); |
| Stop(); |
| |
| // ~VideoRendererImpl() will CHECK() if we left anything initialized. |
| } |
| |
| TEST_F(VideoRendererImplTest, Play) { |
| Initialize(); |
| Play(); |
| Shutdown(); |
| } |
| |
| TEST_F(VideoRendererImplTest, EndOfStream_DefaultFrameDuration) { |
| Initialize(); |
| Play(); |
| |
| // Verify that the ended callback fires when the default last frame duration |
| // has elapsed. |
| int end_timestamp = kFrameDurationInMs * limits::kMaxVideoFrames + |
| VideoRendererImpl::kMaxLastFrameDuration().InMilliseconds(); |
| EXPECT_LT(end_timestamp, kVideoDurationInMs); |
| |
| QueueEndOfStream(); |
| AdvanceTimeInMs(end_timestamp); |
| WaitForEnded(); |
| |
| Shutdown(); |
| } |
| |
| TEST_F(VideoRendererImplTest, EndOfStream_ClipDuration) { |
| int duration = kVideoDurationInMs + kFrameDurationInMs / 2; |
| InitializeWithDuration(duration); |
| Play(); |
| |
| // Render all frames except for the last |limits::kMaxVideoFrames| frames |
| // and deliver all the frames between the start and |duration|. The preroll |
| // inside Initialize() makes this a little confusing, but |timestamp| is |
| // the current render time and QueueNextFrame() delivers a frame with a |
| // timestamp that is |timestamp| + limits::kMaxVideoFrames * |
| // kFrameDurationInMs. |
| int timestamp = kFrameDurationInMs; |
| int end_timestamp = duration - limits::kMaxVideoFrames * kFrameDurationInMs; |
| for (; timestamp < end_timestamp; timestamp += kFrameDurationInMs) { |
| QueueNextFrame(); |
| } |
| |
| // Queue the end of stream frame and wait for the last frame to be rendered. |
| QueueEndOfStream(); |
| AdvanceTimeInMs(duration); |
| WaitForEnded(); |
| |
| Shutdown(); |
| } |
| |
| TEST_F(VideoRendererImplTest, DecodeError_Playing) { |
| Initialize(); |
| Play(); |
| |
| QueueDecodeError(); |
| AdvanceTimeInMs(kVideoDurationInMs); |
| WaitForError(PIPELINE_ERROR_DECODE); |
| Shutdown(); |
| } |
| |
| TEST_F(VideoRendererImplTest, DecodeError_DuringPreroll) { |
| Initialize(); |
| Pause(); |
| Flush(); |
| |
| QueueDecodeError(); |
| Preroll(kFrameDurationInMs * 6, PIPELINE_ERROR_DECODE); |
| Shutdown(); |
| } |
| |
| TEST_F(VideoRendererImplTest, Preroll_Exact) { |
| Initialize(); |
| Pause(); |
| Flush(); |
| QueuePrerollFrames(kFrameDurationInMs * 6); |
| |
| Preroll(kFrameDurationInMs * 6, PIPELINE_OK); |
| EXPECT_EQ(kFrameDurationInMs * 6, GetCurrentTimestampInMs()); |
| Shutdown(); |
| } |
| |
| TEST_F(VideoRendererImplTest, Preroll_RightBefore) { |
| Initialize(); |
| Pause(); |
| Flush(); |
| QueuePrerollFrames(kFrameDurationInMs * 6); |
| |
| Preroll(kFrameDurationInMs * 6 - 1, PIPELINE_OK); |
| EXPECT_EQ(kFrameDurationInMs * 5, GetCurrentTimestampInMs()); |
| Shutdown(); |
| } |
| |
| TEST_F(VideoRendererImplTest, Preroll_RightAfter) { |
| Initialize(); |
| Pause(); |
| Flush(); |
| QueuePrerollFrames(kFrameDurationInMs * 6); |
| |
| Preroll(kFrameDurationInMs * 6 + 1, PIPELINE_OK); |
| EXPECT_EQ(kFrameDurationInMs * 6, GetCurrentTimestampInMs()); |
| Shutdown(); |
| } |
| |
| TEST_F(VideoRendererImplTest, PlayAfterPreroll) { |
| Initialize(); |
| Pause(); |
| Flush(); |
| QueuePrerollFrames(kFrameDurationInMs * 4); |
| |
| Preroll(kFrameDurationInMs * 4, PIPELINE_OK); |
| EXPECT_EQ(kFrameDurationInMs * 4, GetCurrentTimestampInMs()); |
| |
| Play(); |
| // Advance time past prerolled time to trigger a Read(). |
| AdvanceTimeInMs(5 * kFrameDurationInMs); |
| WaitForPendingRead(); |
| Shutdown(); |
| } |
| |
| TEST_F(VideoRendererImplTest, GetCurrentFrame_Initialized) { |
| Initialize(); |
| EXPECT_TRUE(GetCurrentFrame().get()); // Due to prerolling. |
| Shutdown(); |
| } |
| |
| TEST_F(VideoRendererImplTest, GetCurrentFrame_Playing) { |
| Initialize(); |
| Play(); |
| EXPECT_TRUE(GetCurrentFrame().get()); |
| Shutdown(); |
| } |
| |
| TEST_F(VideoRendererImplTest, GetCurrentFrame_Paused) { |
| Initialize(); |
| Play(); |
| Pause(); |
| EXPECT_TRUE(GetCurrentFrame().get()); |
| Shutdown(); |
| } |
| |
| TEST_F(VideoRendererImplTest, GetCurrentFrame_Flushed) { |
| Initialize(); |
| Play(); |
| Pause(); |
| |
| // Frame shouldn't be updated. |
| ResetCurrentFrame(); |
| Flush(); |
| EXPECT_FALSE(GetCurrentFrame().get()); |
| |
| Shutdown(); |
| } |
| |
| TEST_F(VideoRendererImplTest, GetCurrentFrame_EndOfStream) { |
| Initialize(); |
| Play(); |
| Pause(); |
| Flush(); |
| |
| // Preroll only end of stream frames. |
| QueueEndOfStream(); |
| |
| // Frame shouldn't be updated. |
| ResetCurrentFrame(); |
| Preroll(0, PIPELINE_OK); |
| EXPECT_FALSE(GetCurrentFrame().get()); |
| |
| // Start playing, we should immediately get notified of end of stream. |
| Play(); |
| WaitForEnded(); |
| |
| Shutdown(); |
| } |
| |
| TEST_F(VideoRendererImplTest, GetCurrentFrame_Shutdown) { |
| Initialize(); |
| |
| // Frame shouldn't be updated. |
| ResetCurrentFrame(); |
| Shutdown(); |
| EXPECT_FALSE(GetCurrentFrame().get()); |
| } |
| |
| // Stop() is called immediately during an error. |
| TEST_F(VideoRendererImplTest, GetCurrentFrame_Error) { |
| Initialize(); |
| |
| // Frame shouldn't be updated. |
| ResetCurrentFrame(); |
| Stop(); |
| EXPECT_FALSE(GetCurrentFrame().get()); |
| } |
| |
| // Verify that a late decoder response doesn't break invariants in the renderer. |
| TEST_F(VideoRendererImplTest, StopDuringOutstandingRead) { |
| Initialize(); |
| Play(); |
| |
| // Advance time a bit to trigger a Read(). |
| AdvanceTimeInMs(kFrameDurationInMs); |
| WaitForPendingRead(); |
| |
| WaitableMessageLoopEvent event; |
| renderer_->Stop(event.GetClosure()); |
| |
| event.RunAndWait(); |
| } |
| |
| TEST_F(VideoRendererImplTest, AbortPendingRead_Playing) { |
| Initialize(); |
| Play(); |
| |
| // Advance time a bit to trigger a Read(). |
| AdvanceTimeInMs(kFrameDurationInMs); |
| WaitForPendingRead(); |
| |
| QueueAbortedRead(); |
| SatisfyPendingRead(); |
| |
| Pause(); |
| Flush(); |
| QueuePrerollFrames(kFrameDurationInMs * 6); |
| Preroll(kFrameDurationInMs * 6, PIPELINE_OK); |
| EXPECT_EQ(kFrameDurationInMs * 6, GetCurrentTimestampInMs()); |
| Shutdown(); |
| } |
| |
| TEST_F(VideoRendererImplTest, AbortPendingRead_Flush) { |
| Initialize(); |
| Play(); |
| |
| // Advance time a bit to trigger a Read(). |
| AdvanceTimeInMs(kFrameDurationInMs); |
| WaitForPendingRead(); |
| |
| Pause(); |
| Flush(); |
| Shutdown(); |
| } |
| |
| TEST_F(VideoRendererImplTest, AbortPendingRead_Preroll) { |
| Initialize(); |
| Pause(); |
| Flush(); |
| |
| QueueAbortedRead(); |
| Preroll(kFrameDurationInMs * 6, PIPELINE_OK); |
| Shutdown(); |
| } |
| |
| TEST_F(VideoRendererImplTest, VideoDecoder_InitFailure) { |
| InSequence s; |
| |
| EXPECT_CALL(*decoder_, Initialize(_, _)) |
| .WillOnce(RunCallback<1>(DECODER_ERROR_NOT_SUPPORTED)); |
| InitializeRenderer(DECODER_ERROR_NOT_SUPPORTED); |
| |
| Stop(); |
| } |
| |
| } // namespace media |