| // 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 "base/bind.h" |
| #include "base/callback.h" |
| #include "base/callback_helpers.h" |
| #include "base/debug/stack_trace.h" |
| #include "base/message_loop.h" |
| #include "base/stl_util.h" |
| #include "base/stringprintf.h" |
| #include "base/synchronization/lock.h" |
| #include "base/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_base.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| using ::testing::_; |
| using ::testing::AnyNumber; |
| using ::testing::InSequence; |
| using ::testing::Invoke; |
| using ::testing::NotNull; |
| using ::testing::Return; |
| using ::testing::ReturnRef; |
| using ::testing::StrictMock; |
| |
| namespace media { |
| |
| static const int kFrameDurationInMs = 10; |
| static const int kVideoDurationInMs = kFrameDurationInMs * 100; |
| static const VideoFrame::Format kVideoFormat = VideoFrame::YV12; |
| static const gfx::Size kCodedSize(16u, 16u); |
| static const gfx::Rect kVisibleRect(16u, 16u); |
| static const gfx::Size kNaturalSize(16u, 16u); |
| |
| class VideoRendererBaseTest : public ::testing::Test { |
| public: |
| VideoRendererBaseTest() |
| : decoder_(new MockVideoDecoder()), |
| demuxer_stream_(new MockDemuxerStream()), |
| video_config_(kCodecVP8, VIDEO_CODEC_PROFILE_UNKNOWN, kVideoFormat, |
| kCodedSize, kVisibleRect, kNaturalSize, NULL, 0, false) { |
| renderer_ = new VideoRendererBase( |
| message_loop_.message_loop_proxy(), |
| media::SetDecryptorReadyCB(), |
| base::Bind(&VideoRendererBaseTest::OnPaint, base::Unretained(this)), |
| base::Bind(&VideoRendererBaseTest::OnSetOpaque, base::Unretained(this)), |
| true); |
| #if defined(__LB_SHELL__) || defined(COBALT) |
| EXPECT_CALL(*demuxer_stream_, StreamWasEncrypted()) |
| .WillRepeatedly(Return(false)); |
| #endif |
| |
| EXPECT_CALL(*demuxer_stream_, type()) |
| .WillRepeatedly(Return(DemuxerStream::VIDEO)); |
| EXPECT_CALL(*demuxer_stream_, video_decoder_config()) |
| .WillRepeatedly(ReturnRef(video_config_)); |
| |
| // We expect these to be called but we don't care how/when. |
| EXPECT_CALL(*decoder_, Stop(_)) |
| .WillRepeatedly(RunClosure<0>()); |
| EXPECT_CALL(statistics_cb_object_, OnStatistics(_)) |
| .Times(AnyNumber()); |
| EXPECT_CALL(*this, OnTimeUpdate(_)) |
| .Times(AnyNumber()); |
| EXPECT_CALL(*this, OnPaint()) |
| .Times(AnyNumber()); |
| EXPECT_CALL(*this, OnSetOpaque(_)) |
| .Times(AnyNumber()); |
| } |
| |
| virtual ~VideoRendererBaseTest() {} |
| |
| // Callbacks passed into VideoRendererBase(). |
| MOCK_CONST_METHOD0(OnPaint, void()); |
| 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 reads from the decoder. |
| EXPECT_CALL(*decoder_, Read(_)) |
| .WillRepeatedly(Invoke(this, &VideoRendererBaseTest::FrameRequested)); |
| |
| EXPECT_CALL(*decoder_, Reset(_)) |
| .WillRepeatedly(Invoke(this, &VideoRendererBaseTest::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(kNaturalSize)); |
| |
| // 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) { |
| VideoRendererBase::VideoDecoderList decoders; |
| decoders.push_back(decoder_); |
| renderer_->Initialize( |
| demuxer_stream_, decoders, status_cb, |
| base::Bind(&MockStatisticsCB::OnStatistics, |
| base::Unretained(&statistics_cb_object_)), |
| base::Bind(&VideoRendererBaseTest::OnTimeUpdate, |
| base::Unretained(this)), |
| base::Bind(&VideoRendererBaseTest::OnNaturalSizeChanged, |
| base::Unretained(this)), |
| ended_event_.GetClosure(), error_event_.GetPipelineStatusCB(), |
| base::Bind(&VideoRendererBaseTest::GetTime, base::Unretained(this)), |
| base::Bind(&VideoRendererBaseTest::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_, MessageLoop::current()); |
| DCHECK_LT(next_frame_timestamp_.InMicroseconds(), |
| duration_.InMicroseconds()); |
| |
| scoped_refptr<VideoFrame> frame = VideoFrame::CreateFrame( |
| VideoFrame::RGB32, kNaturalSize, gfx::Rect(kNaturalSize), kNaturalSize, |
| 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_, MessageLoop::current()); |
| decode_results_.push_back(std::make_pair( |
| VideoDecoder::kOk, VideoFrame::CreateEmptyFrame())); |
| } |
| |
| void QueueDecodeError() { |
| DCHECK_EQ(&message_loop_, MessageLoop::current()); |
| scoped_refptr<VideoFrame> null_frame; |
| decode_results_.push_back(std::make_pair( |
| VideoDecoder::kDecodeError, null_frame)); |
| } |
| |
| void QueueAbortedRead() { |
| DCHECK_EQ(&message_loop_, 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_, 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(); |
| } |
| } |
| |
| scoped_refptr<VideoFrame> GetCurrentFrame() { |
| scoped_refptr<VideoFrame> frame; |
| renderer_->GetCurrentFrame(&frame); |
| renderer_->PutCurrentFrame(frame); |
| return frame; |
| } |
| |
| int GetCurrentTimestampInMs() { |
| scoped_refptr<VideoFrame> frame = GetCurrentFrame(); |
| if (!frame) |
| 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(pending_read_cb_.is_null()); |
| |
| WaitableMessageLoopEvent event; |
| pending_read_cb_ = event.GetClosure(); |
| event.RunAndWait(); |
| |
| DCHECK(!read_cb_.is_null()); |
| DCHECK(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_, MessageLoop::current()); |
| base::AutoLock l(lock_); |
| time_ += base::TimeDelta::FromMilliseconds(time_ms); |
| DCHECK_LE(time_.InMicroseconds(), duration_.InMicroseconds()); |
| } |
| |
| protected: |
| // Fixture members. |
| scoped_refptr<VideoRendererBase> renderer_; |
| scoped_refptr<MockVideoDecoder> decoder_; |
| scoped_refptr<MockDemuxerStream> demuxer_stream_; |
| MockStatisticsCB statistics_cb_object_; |
| |
| private: |
| base::TimeDelta GetTime() { |
| base::AutoLock l(lock_); |
| return time_; |
| } |
| |
| base::TimeDelta GetDuration() { |
| return duration_; |
| } |
| |
| void FrameRequested(const VideoDecoder::ReadCB& read_cb) { |
| DCHECK_EQ(&message_loop_, MessageLoop::current()); |
| CHECK(read_cb_.is_null()); |
| read_cb_ = read_cb; |
| |
| // Wake up WaitForPendingRead() if needed. |
| if (!pending_read_cb_.is_null()) |
| base::ResetAndReturn(&pending_read_cb_).Run(); |
| |
| if (decode_results_.empty()) |
| return; |
| |
| SatisfyPendingRead(); |
| } |
| |
| void FlushRequested(const base::Closure& callback) { |
| DCHECK_EQ(&message_loop_, MessageLoop::current()); |
| decode_results_.clear(); |
| if (!read_cb_.is_null()) { |
| QueueAbortedRead(); |
| SatisfyPendingRead(); |
| } |
| |
| message_loop_.PostTask(FROM_HERE, callback); |
| } |
| |
| MessageLoop message_loop_; |
| |
| VideoDecoderConfig video_config_; |
| |
| // Used to protect |time_|. |
| base::Lock lock_; |
| base::TimeDelta time_; |
| |
| // Used for satisfying reads. |
| VideoDecoder::ReadCB read_cb_; |
| base::TimeDelta next_frame_timestamp_; |
| base::TimeDelta duration_; |
| |
| WaitableMessageLoopEvent error_event_; |
| WaitableMessageLoopEvent ended_event_; |
| base::Closure pending_read_cb_; |
| |
| std::deque<std::pair< |
| VideoDecoder::Status, scoped_refptr<VideoFrame> > > decode_results_; |
| |
| DISALLOW_COPY_AND_ASSIGN(VideoRendererBaseTest); |
| }; |
| |
| TEST_F(VideoRendererBaseTest, DoNothing) { |
| // Test that creation and deletion doesn't depend on calls to Initialize() |
| // and/or Stop(). |
| } |
| |
| TEST_F(VideoRendererBaseTest, StopWithoutInitialize) { |
| Stop(); |
| } |
| |
| TEST_F(VideoRendererBaseTest, 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(VideoRendererBaseTest, StopWhileInitializing) { |
| EXPECT_CALL(*decoder_, Initialize(_, _, _)) |
| .WillOnce(RunCallback<1>(PIPELINE_OK)); |
| CallInitialize(base::Bind(&ExpectNotCalled)); |
| Stop(); |
| |
| // ~VideoRendererBase() will CHECK() if we left anything initialized. |
| } |
| |
| TEST_F(VideoRendererBaseTest, StopWhileFlushing) { |
| Initialize(); |
| Pause(); |
| renderer_->Flush(base::Bind(&ExpectNotCalled, PIPELINE_OK)); |
| Stop(); |
| |
| // ~VideoRendererBase() will CHECK() if we left anything initialized. |
| } |
| |
| TEST_F(VideoRendererBaseTest, Play) { |
| Initialize(); |
| Play(); |
| Shutdown(); |
| } |
| |
| TEST_F(VideoRendererBaseTest, EndOfStream_DefaultFrameDuration) { |
| Initialize(); |
| Play(); |
| |
| // Verify that the ended callback fires when the default last frame duration |
| // has elapsed. |
| int end_timestamp = kFrameDurationInMs * limits::kMaxVideoFrames + |
| VideoRendererBase::kMaxLastFrameDuration().InMilliseconds(); |
| EXPECT_LT(end_timestamp, kVideoDurationInMs); |
| |
| QueueEndOfStream(); |
| AdvanceTimeInMs(end_timestamp); |
| WaitForEnded(); |
| |
| Shutdown(); |
| } |
| |
| TEST_F(VideoRendererBaseTest, 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(VideoRendererBaseTest, DecodeError_Playing) { |
| Initialize(); |
| Play(); |
| |
| QueueDecodeError(); |
| AdvanceTimeInMs(kVideoDurationInMs); |
| WaitForError(PIPELINE_ERROR_DECODE); |
| Shutdown(); |
| } |
| |
| TEST_F(VideoRendererBaseTest, DecodeError_DuringPreroll) { |
| Initialize(); |
| Pause(); |
| Flush(); |
| |
| QueueDecodeError(); |
| Preroll(kFrameDurationInMs * 6, PIPELINE_ERROR_DECODE); |
| Shutdown(); |
| } |
| |
| TEST_F(VideoRendererBaseTest, Preroll_Exact) { |
| Initialize(); |
| Pause(); |
| Flush(); |
| QueuePrerollFrames(kFrameDurationInMs * 6); |
| |
| Preroll(kFrameDurationInMs * 6, PIPELINE_OK); |
| EXPECT_EQ(kFrameDurationInMs * 6, GetCurrentTimestampInMs()); |
| Shutdown(); |
| } |
| |
| TEST_F(VideoRendererBaseTest, Preroll_RightBefore) { |
| Initialize(); |
| Pause(); |
| Flush(); |
| QueuePrerollFrames(kFrameDurationInMs * 6); |
| |
| Preroll(kFrameDurationInMs * 6 - 1, PIPELINE_OK); |
| EXPECT_EQ(kFrameDurationInMs * 5, GetCurrentTimestampInMs()); |
| Shutdown(); |
| } |
| |
| TEST_F(VideoRendererBaseTest, Preroll_RightAfter) { |
| Initialize(); |
| Pause(); |
| Flush(); |
| QueuePrerollFrames(kFrameDurationInMs * 6); |
| |
| Preroll(kFrameDurationInMs * 6 + 1, PIPELINE_OK); |
| EXPECT_EQ(kFrameDurationInMs * 6, GetCurrentTimestampInMs()); |
| Shutdown(); |
| } |
| |
| TEST_F(VideoRendererBaseTest, GetCurrentFrame_Initialized) { |
| Initialize(); |
| EXPECT_TRUE(GetCurrentFrame()); // Due to prerolling. |
| Shutdown(); |
| } |
| |
| TEST_F(VideoRendererBaseTest, GetCurrentFrame_Playing) { |
| Initialize(); |
| Play(); |
| EXPECT_TRUE(GetCurrentFrame()); |
| Shutdown(); |
| } |
| |
| TEST_F(VideoRendererBaseTest, GetCurrentFrame_Paused) { |
| Initialize(); |
| Play(); |
| Pause(); |
| EXPECT_TRUE(GetCurrentFrame()); |
| Shutdown(); |
| } |
| |
| TEST_F(VideoRendererBaseTest, GetCurrentFrame_Flushed) { |
| Initialize(); |
| Play(); |
| Pause(); |
| Flush(); |
| EXPECT_FALSE(GetCurrentFrame()); |
| Shutdown(); |
| } |
| |
| #if defined(OS_MACOSX) || defined(ADDRESS_SANITIZER) |
| // http://crbug.com/109405 |
| #define MAYBE_GetCurrentFrame_EndOfStream DISABLED_GetCurrentFrame_EndOfStream |
| #else |
| #define MAYBE_GetCurrentFrame_EndOfStream GetCurrentFrame_EndOfStream |
| #endif |
| TEST_F(VideoRendererBaseTest, MAYBE_GetCurrentFrame_EndOfStream) { |
| Initialize(); |
| Play(); |
| Pause(); |
| Flush(); |
| |
| // Preroll only end of stream frames. |
| QueueEndOfStream(); |
| Preroll(0, PIPELINE_OK); |
| EXPECT_FALSE(GetCurrentFrame()); |
| |
| // Start playing, we should immediately get notified of end of stream. |
| Play(); |
| WaitForEnded(); |
| |
| Shutdown(); |
| } |
| |
| TEST_F(VideoRendererBaseTest, GetCurrentFrame_Shutdown) { |
| Initialize(); |
| Shutdown(); |
| EXPECT_FALSE(GetCurrentFrame()); |
| } |
| |
| // Stop() is called immediately during an error. |
| TEST_F(VideoRendererBaseTest, GetCurrentFrame_Error) { |
| Initialize(); |
| Stop(); |
| EXPECT_FALSE(GetCurrentFrame()); |
| } |
| |
| // Verify that shutdown can only proceed after we return the current frame. |
| TEST_F(VideoRendererBaseTest, Shutdown_DuringPaint) { |
| Initialize(); |
| Play(); |
| |
| // Grab the frame. |
| scoped_refptr<VideoFrame> frame; |
| renderer_->GetCurrentFrame(&frame); |
| EXPECT_TRUE(frame); |
| |
| Pause(); |
| |
| // Start flushing -- it won't complete until we return the frame. |
| WaitableMessageLoopEvent event; |
| renderer_->Flush(event.GetClosure()); |
| |
| // Return the frame and wait. |
| renderer_->PutCurrentFrame(frame); |
| event.RunAndWait(); |
| |
| Stop(); |
| } |
| |
| // Verify that a late decoder response doesn't break invariants in the renderer. |
| TEST_F(VideoRendererBaseTest, StopDuringOutstandingRead) { |
| Initialize(); |
| Play(); |
| |
| // Advance time a bit to trigger a Read(). |
| AdvanceTimeInMs(kFrameDurationInMs); |
| WaitForPendingRead(); |
| |
| WaitableMessageLoopEvent event; |
| renderer_->Stop(event.GetClosure()); |
| |
| QueueEndOfStream(); |
| SatisfyPendingRead(); |
| |
| event.RunAndWait(); |
| } |
| |
| TEST_F(VideoRendererBaseTest, 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(VideoRendererBaseTest, AbortPendingRead_Flush) { |
| Initialize(); |
| Play(); |
| |
| // Advance time a bit to trigger a Read(). |
| AdvanceTimeInMs(kFrameDurationInMs); |
| WaitForPendingRead(); |
| |
| Pause(); |
| Flush(); |
| Shutdown(); |
| } |
| |
| TEST_F(VideoRendererBaseTest, AbortPendingRead_Preroll) { |
| Initialize(); |
| Pause(); |
| Flush(); |
| |
| QueueAbortedRead(); |
| Preroll(kFrameDurationInMs * 6, PIPELINE_OK); |
| Shutdown(); |
| } |
| |
| TEST_F(VideoRendererBaseTest, VideoDecoder_InitFailure) { |
| InSequence s; |
| |
| EXPECT_CALL(*decoder_, Initialize(_, _, _)) |
| .WillOnce(RunCallback<1>(DECODER_ERROR_NOT_SUPPORTED)); |
| InitializeRenderer(DECODER_ERROR_NOT_SUPPORTED); |
| |
| Stop(); |
| } |
| |
| } // namespace media |