| OLD | NEW |
| 1 // Copyright 2014 The Chromium Authors. All rights reserved. | 1 // Copyright 2014 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include <stdint.h> | 5 #include <stdint.h> |
| 6 | 6 |
| 7 #include <vector> | 7 #include <vector> |
| 8 | 8 |
| 9 #include "base/bind.h" | 9 #include "base/bind.h" |
| 10 #include "base/macros.h" | 10 #include "base/macros.h" |
| (...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 46 CallbackHelper() {} | 46 CallbackHelper() {} |
| 47 virtual ~CallbackHelper() {} | 47 virtual ~CallbackHelper() {} |
| 48 | 48 |
| 49 MOCK_METHOD1(OnInitialize, void(PipelineStatus)); | 49 MOCK_METHOD1(OnInitialize, void(PipelineStatus)); |
| 50 MOCK_METHOD0(OnFlushed, void()); | 50 MOCK_METHOD0(OnFlushed, void()); |
| 51 MOCK_METHOD0(OnEnded, void()); | 51 MOCK_METHOD0(OnEnded, void()); |
| 52 MOCK_METHOD1(OnError, void(PipelineStatus)); | 52 MOCK_METHOD1(OnError, void(PipelineStatus)); |
| 53 MOCK_METHOD1(OnUpdateStatistics, void(const PipelineStatistics&)); | 53 MOCK_METHOD1(OnUpdateStatistics, void(const PipelineStatistics&)); |
| 54 MOCK_METHOD1(OnBufferingStateChange, void(BufferingState)); | 54 MOCK_METHOD1(OnBufferingStateChange, void(BufferingState)); |
| 55 MOCK_METHOD0(OnWaitingForDecryptionKey, void()); | 55 MOCK_METHOD0(OnWaitingForDecryptionKey, void()); |
| 56 MOCK_METHOD1(OnCdmAttached, void(bool)); |
| 56 | 57 |
| 57 private: | 58 private: |
| 58 DISALLOW_COPY_AND_ASSIGN(CallbackHelper); | 59 DISALLOW_COPY_AND_ASSIGN(CallbackHelper); |
| 59 }; | 60 }; |
| 60 | 61 |
| 61 RendererImplTest() | 62 RendererImplTest() |
| 62 : demuxer_(new StrictMock<MockDemuxer>()), | 63 : demuxer_(new StrictMock<MockDemuxer>()), |
| 63 video_renderer_(new StrictMock<MockVideoRenderer>()), | 64 video_renderer_(new StrictMock<MockVideoRenderer>()), |
| 64 audio_renderer_(new StrictMock<MockAudioRenderer>()), | 65 audio_renderer_(new StrictMock<MockAudioRenderer>()), |
| 65 renderer_impl_( | 66 renderer_impl_( |
| 66 new RendererImpl(message_loop_.task_runner(), | 67 new RendererImpl(message_loop_.task_runner(), |
| 67 scoped_ptr<AudioRenderer>(audio_renderer_), | 68 scoped_ptr<AudioRenderer>(audio_renderer_), |
| 68 scoped_ptr<VideoRenderer>(video_renderer_))) { | 69 scoped_ptr<VideoRenderer>(video_renderer_))), |
| 70 cdm_context_(new StrictMock<MockCdmContext>()), |
| 71 initialization_status_(PIPELINE_ERROR_OPERATION_PENDING) { |
| 69 // SetDemuxerExpectations() adds overriding expectations for expected | 72 // SetDemuxerExpectations() adds overriding expectations for expected |
| 70 // non-NULL streams. | 73 // non-NULL streams. |
| 71 DemuxerStream* null_pointer = NULL; | 74 DemuxerStream* null_pointer = NULL; |
| 72 EXPECT_CALL(*demuxer_, GetStream(_)) | 75 EXPECT_CALL(*demuxer_, GetStream(_)) |
| 73 .WillRepeatedly(Return(null_pointer)); | 76 .WillRepeatedly(Return(null_pointer)); |
| 74 } | 77 } |
| 75 | 78 |
| 76 virtual ~RendererImplTest() { | 79 virtual ~RendererImplTest() { Destroy(); } |
| 80 |
| 81 protected: |
| 82 typedef std::vector<MockDemuxerStream*> MockDemuxerStreamVector; |
| 83 |
| 84 void Destroy() { |
| 77 renderer_impl_.reset(); | 85 renderer_impl_.reset(); |
| 78 base::RunLoop().RunUntilIdle(); | 86 base::RunLoop().RunUntilIdle(); |
| 79 } | 87 } |
| 80 | 88 |
| 81 protected: | |
| 82 typedef std::vector<MockDemuxerStream*> MockDemuxerStreamVector; | |
| 83 | |
| 84 scoped_ptr<StrictMock<MockDemuxerStream> > CreateStream( | 89 scoped_ptr<StrictMock<MockDemuxerStream> > CreateStream( |
| 85 DemuxerStream::Type type) { | 90 DemuxerStream::Type type) { |
| 86 scoped_ptr<StrictMock<MockDemuxerStream> > stream( | 91 scoped_ptr<StrictMock<MockDemuxerStream> > stream( |
| 87 new StrictMock<MockDemuxerStream>(type)); | 92 new StrictMock<MockDemuxerStream>(type)); |
| 88 return stream; | 93 return stream; |
| 89 } | 94 } |
| 90 | 95 |
| 91 // Sets up expectations to allow the audio renderer to initialize. | 96 // Sets up expectations to allow the audio renderer to initialize. |
| 92 void SetAudioRendererInitializeExpectations(PipelineStatus status) { | 97 void SetAudioRendererInitializeExpectations(PipelineStatus status) { |
| 93 EXPECT_CALL(*audio_renderer_, | 98 EXPECT_CALL(*audio_renderer_, |
| 94 Initialize(audio_stream_.get(), _, _, _, _, _, _, _)) | 99 Initialize(audio_stream_.get(), _, _, _, _, _, _, _)) |
| 95 .WillOnce(DoAll(SaveArg<4>(&audio_buffering_state_cb_), | 100 .WillOnce(DoAll(SaveArg<4>(&audio_buffering_state_cb_), |
| 96 SaveArg<5>(&audio_ended_cb_), | 101 SaveArg<5>(&audio_ended_cb_), |
| 97 SaveArg<6>(&audio_error_cb_), RunCallback<1>(status))); | 102 SaveArg<6>(&audio_error_cb_), RunCallback<1>(status))); |
| 98 } | 103 } |
| 99 | 104 |
| 100 // Sets up expectations to allow the video renderer to initialize. | 105 // Sets up expectations to allow the video renderer to initialize. |
| 101 void SetVideoRendererInitializeExpectations(PipelineStatus status) { | 106 void SetVideoRendererInitializeExpectations(PipelineStatus status) { |
| 102 EXPECT_CALL(*video_renderer_, | 107 EXPECT_CALL(*video_renderer_, |
| 103 Initialize(video_stream_.get(), _, _, _, _, _, _, _, _)) | 108 Initialize(video_stream_.get(), _, _, _, _, _, _, _, _)) |
| 104 .WillOnce(DoAll(SaveArg<4>(&video_buffering_state_cb_), | 109 .WillOnce(DoAll(SaveArg<4>(&video_buffering_state_cb_), |
| 105 SaveArg<5>(&video_ended_cb_), RunCallback<1>(status))); | 110 SaveArg<5>(&video_ended_cb_), RunCallback<1>(status))); |
| 106 } | 111 } |
| 107 | 112 |
| 108 void InitializeAndExpect(PipelineStatus start_status) { | 113 void InitializeAndExpect(PipelineStatus start_status) { |
| 109 EXPECT_CALL(callbacks_, OnInitialize(start_status)); | 114 EXPECT_CALL(callbacks_, OnInitialize(start_status)) |
| 115 .WillOnce(SaveArg<0>(&initialization_status_)); |
| 110 EXPECT_CALL(callbacks_, OnWaitingForDecryptionKey()).Times(0); | 116 EXPECT_CALL(callbacks_, OnWaitingForDecryptionKey()).Times(0); |
| 111 | 117 |
| 112 if (start_status == PIPELINE_OK && audio_stream_) { | 118 if (start_status == PIPELINE_OK && audio_stream_) { |
| 113 EXPECT_CALL(*audio_renderer_, GetTimeSource()) | 119 EXPECT_CALL(*audio_renderer_, GetTimeSource()) |
| 114 .WillOnce(Return(&time_source_)); | 120 .WillOnce(Return(&time_source_)); |
| 115 } else { | 121 } else { |
| 116 renderer_impl_->set_time_source_for_testing(&time_source_); | 122 renderer_impl_->set_time_source_for_testing(&time_source_); |
| 117 } | 123 } |
| 118 | 124 |
| 119 renderer_impl_->Initialize( | 125 renderer_impl_->Initialize( |
| (...skipping 11 matching lines...) Expand all Loading... |
| 131 base::RunLoop().RunUntilIdle(); | 137 base::RunLoop().RunUntilIdle(); |
| 132 } | 138 } |
| 133 | 139 |
| 134 void CreateAudioStream() { | 140 void CreateAudioStream() { |
| 135 audio_stream_ = CreateStream(DemuxerStream::AUDIO); | 141 audio_stream_ = CreateStream(DemuxerStream::AUDIO); |
| 136 streams_.push_back(audio_stream_.get()); | 142 streams_.push_back(audio_stream_.get()); |
| 137 EXPECT_CALL(*demuxer_, GetStream(DemuxerStream::AUDIO)) | 143 EXPECT_CALL(*demuxer_, GetStream(DemuxerStream::AUDIO)) |
| 138 .WillRepeatedly(Return(audio_stream_.get())); | 144 .WillRepeatedly(Return(audio_stream_.get())); |
| 139 } | 145 } |
| 140 | 146 |
| 141 void CreateVideoStream() { | 147 void CreateVideoStream(bool is_encrypted = false) { |
| 142 video_stream_ = CreateStream(DemuxerStream::VIDEO); | 148 video_stream_ = CreateStream(DemuxerStream::VIDEO); |
| 143 video_stream_->set_video_decoder_config(video_decoder_config_); | 149 video_stream_->set_video_decoder_config( |
| 150 is_encrypted ? TestVideoConfig::NormalEncrypted() |
| 151 : TestVideoConfig::Normal()); |
| 144 streams_.push_back(video_stream_.get()); | 152 streams_.push_back(video_stream_.get()); |
| 145 EXPECT_CALL(*demuxer_, GetStream(DemuxerStream::VIDEO)) | 153 EXPECT_CALL(*demuxer_, GetStream(DemuxerStream::VIDEO)) |
| 146 .WillRepeatedly(Return(video_stream_.get())); | 154 .WillRepeatedly(Return(video_stream_.get())); |
| 147 } | 155 } |
| 148 | 156 |
| 157 void CreateEncryptedVideoStream() { CreateVideoStream(true); } |
| 158 |
| 149 void CreateAudioAndVideoStream() { | 159 void CreateAudioAndVideoStream() { |
| 150 CreateAudioStream(); | 160 CreateAudioStream(); |
| 151 CreateVideoStream(); | 161 CreateVideoStream(); |
| 152 } | 162 } |
| 153 | 163 |
| 154 void InitializeWithAudio() { | 164 void InitializeWithAudio() { |
| 155 CreateAudioStream(); | 165 CreateAudioStream(); |
| 156 SetAudioRendererInitializeExpectations(PIPELINE_OK); | 166 SetAudioRendererInitializeExpectations(PIPELINE_OK); |
| 157 InitializeAndExpect(PIPELINE_OK); | 167 InitializeAndExpect(PIPELINE_OK); |
| 158 } | 168 } |
| (...skipping 82 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 241 return true; | 251 return true; |
| 242 | 252 |
| 243 DCHECK_EQ(start_time_ms, GetMediaTimeMs()); | 253 DCHECK_EQ(start_time_ms, GetMediaTimeMs()); |
| 244 return false; | 254 return false; |
| 245 } | 255 } |
| 246 | 256 |
| 247 bool IsMediaTimeAdvancing() { | 257 bool IsMediaTimeAdvancing() { |
| 248 return IsMediaTimeAdvancing(1.0); | 258 return IsMediaTimeAdvancing(1.0); |
| 249 } | 259 } |
| 250 | 260 |
| 261 void SetCdmAndExpect(bool expected_result) { |
| 262 EXPECT_CALL(callbacks_, OnCdmAttached(expected_result)); |
| 263 renderer_impl_->SetCdm(cdm_context_.get(), |
| 264 base::Bind(&CallbackHelper::OnCdmAttached, |
| 265 base::Unretained(&callbacks_))); |
| 266 base::RunLoop().RunUntilIdle(); |
| 267 } |
| 268 |
| 251 // Fixture members. | 269 // Fixture members. |
| 252 base::MessageLoop message_loop_; | 270 base::MessageLoop message_loop_; |
| 253 StrictMock<CallbackHelper> callbacks_; | 271 StrictMock<CallbackHelper> callbacks_; |
| 254 base::SimpleTestTickClock test_tick_clock_; | 272 base::SimpleTestTickClock test_tick_clock_; |
| 255 | 273 |
| 256 scoped_ptr<StrictMock<MockDemuxer> > demuxer_; | 274 scoped_ptr<StrictMock<MockDemuxer> > demuxer_; |
| 257 StrictMock<MockVideoRenderer>* video_renderer_; | 275 StrictMock<MockVideoRenderer>* video_renderer_; |
| 258 StrictMock<MockAudioRenderer>* audio_renderer_; | 276 StrictMock<MockAudioRenderer>* audio_renderer_; |
| 259 scoped_ptr<RendererImpl> renderer_impl_; | 277 scoped_ptr<RendererImpl> renderer_impl_; |
| 278 scoped_ptr<StrictMock<MockCdmContext>> cdm_context_; |
| 260 | 279 |
| 261 StrictMock<MockTimeSource> time_source_; | 280 StrictMock<MockTimeSource> time_source_; |
| 262 scoped_ptr<StrictMock<MockDemuxerStream> > audio_stream_; | 281 scoped_ptr<StrictMock<MockDemuxerStream> > audio_stream_; |
| 263 scoped_ptr<StrictMock<MockDemuxerStream> > video_stream_; | 282 scoped_ptr<StrictMock<MockDemuxerStream> > video_stream_; |
| 264 MockDemuxerStreamVector streams_; | 283 MockDemuxerStreamVector streams_; |
| 265 BufferingStateCB audio_buffering_state_cb_; | 284 BufferingStateCB audio_buffering_state_cb_; |
| 266 BufferingStateCB video_buffering_state_cb_; | 285 BufferingStateCB video_buffering_state_cb_; |
| 267 base::Closure audio_ended_cb_; | 286 base::Closure audio_ended_cb_; |
| 268 base::Closure video_ended_cb_; | 287 base::Closure video_ended_cb_; |
| 269 PipelineStatusCB audio_error_cb_; | 288 PipelineStatusCB audio_error_cb_; |
| 270 VideoDecoderConfig video_decoder_config_; | 289 VideoDecoderConfig video_decoder_config_; |
| 290 PipelineStatus initialization_status_; |
| 271 | 291 |
| 272 private: | 292 private: |
| 273 DISALLOW_COPY_AND_ASSIGN(RendererImplTest); | 293 DISALLOW_COPY_AND_ASSIGN(RendererImplTest); |
| 274 }; | 294 }; |
| 275 | 295 |
| 276 TEST_F(RendererImplTest, DestroyBeforeInitialize) { | 296 TEST_F(RendererImplTest, Destroy_BeforeInitialize) { |
| 277 // |renderer_impl_| will be destroyed in the dtor. | 297 Destroy(); |
| 298 } |
| 299 |
| 300 TEST_F(RendererImplTest, Destroy_PendingInitialize) { |
| 301 CreateAudioAndVideoStream(); |
| 302 |
| 303 SetAudioRendererInitializeExpectations(PIPELINE_OK); |
| 304 // Not returning the video initialization callback. |
| 305 EXPECT_CALL(*video_renderer_, |
| 306 Initialize(video_stream_.get(), _, _, _, _, _, _, _, _)); |
| 307 |
| 308 InitializeAndExpect(PIPELINE_ERROR_ABORT); |
| 309 EXPECT_EQ(PIPELINE_ERROR_OPERATION_PENDING, initialization_status_); |
| 310 |
| 311 Destroy(); |
| 312 } |
| 313 |
| 314 TEST_F(RendererImplTest, Destroy_PendingInitializeWithoutCdm) { |
| 315 CreateAudioStream(); |
| 316 CreateEncryptedVideoStream(); |
| 317 |
| 318 // Audio is clear and video is encrypted. Initialization will not start |
| 319 // because no CDM is set. So neither AudioRenderer::Initialize() nor |
| 320 // VideoRenderer::Initialize() should not be called. The InitCB will be |
| 321 // aborted when |renderer_impl_| is destructed. |
| 322 InitializeAndExpect(PIPELINE_ERROR_ABORT); |
| 323 EXPECT_EQ(PIPELINE_ERROR_OPERATION_PENDING, initialization_status_); |
| 324 |
| 325 Destroy(); |
| 326 } |
| 327 |
| 328 TEST_F(RendererImplTest, Destroy_PendingInitializeAfterSetCdm) { |
| 329 CreateAudioStream(); |
| 330 CreateEncryptedVideoStream(); |
| 331 |
| 332 // Audio is clear and video is encrypted. Initialization will not start |
| 333 // because no CDM is set. |
| 334 InitializeAndExpect(PIPELINE_ERROR_ABORT); |
| 335 EXPECT_EQ(PIPELINE_ERROR_OPERATION_PENDING, initialization_status_); |
| 336 |
| 337 SetAudioRendererInitializeExpectations(PIPELINE_OK); |
| 338 // Not returning the video initialization callback. So initialization will |
| 339 // be pending. |
| 340 EXPECT_CALL(*video_renderer_, |
| 341 Initialize(video_stream_.get(), _, _, _, _, _, _, _, _)); |
| 342 |
| 343 // SetCdm() will trigger the initialization to start. But it will not complete |
| 344 // because the |video_renderer_| is not returning the initialization callback. |
| 345 SetCdmAndExpect(false); |
| 346 EXPECT_EQ(PIPELINE_ERROR_OPERATION_PENDING, initialization_status_); |
| 347 |
| 348 Destroy(); |
| 278 } | 349 } |
| 279 | 350 |
| 280 TEST_F(RendererImplTest, InitializeWithAudio) { | 351 TEST_F(RendererImplTest, InitializeWithAudio) { |
| 281 InitializeWithAudio(); | 352 InitializeWithAudio(); |
| 282 } | 353 } |
| 283 | 354 |
| 284 TEST_F(RendererImplTest, InitializeWithVideo) { | 355 TEST_F(RendererImplTest, InitializeWithVideo) { |
| 285 InitializeWithVideo(); | 356 InitializeWithVideo(); |
| 286 } | 357 } |
| 287 | 358 |
| (...skipping 20 matching lines...) Expand all Loading... |
| 308 InitializeAndExpect(PIPELINE_ERROR_INITIALIZATION_FAILED); | 379 InitializeAndExpect(PIPELINE_ERROR_INITIALIZATION_FAILED); |
| 309 } | 380 } |
| 310 | 381 |
| 311 TEST_F(RendererImplTest, InitializeWithAudioVideo_VideoRendererFailed) { | 382 TEST_F(RendererImplTest, InitializeWithAudioVideo_VideoRendererFailed) { |
| 312 CreateAudioAndVideoStream(); | 383 CreateAudioAndVideoStream(); |
| 313 SetAudioRendererInitializeExpectations(PIPELINE_OK); | 384 SetAudioRendererInitializeExpectations(PIPELINE_OK); |
| 314 SetVideoRendererInitializeExpectations(PIPELINE_ERROR_INITIALIZATION_FAILED); | 385 SetVideoRendererInitializeExpectations(PIPELINE_ERROR_INITIALIZATION_FAILED); |
| 315 InitializeAndExpect(PIPELINE_ERROR_INITIALIZATION_FAILED); | 386 InitializeAndExpect(PIPELINE_ERROR_INITIALIZATION_FAILED); |
| 316 } | 387 } |
| 317 | 388 |
| 389 TEST_F(RendererImplTest, SetCdmBeforeInitialize) { |
| 390 // CDM will be successfully attached immediately if set before RendererImpl |
| 391 // initialization, regardless of the later initialization result. |
| 392 SetCdmAndExpect(true); |
| 393 } |
| 394 |
| 395 TEST_F(RendererImplTest, SetCdmAfterInitialize_ClearStream) { |
| 396 InitializeWithAudioAndVideo(); |
| 397 EXPECT_EQ(PIPELINE_OK, initialization_status_); |
| 398 |
| 399 // CDM will be successfully attached immediately since initialization is |
| 400 // completed. |
| 401 SetCdmAndExpect(true); |
| 402 } |
| 403 |
| 404 TEST_F(RendererImplTest, SetCdmAfterInitialize_EncryptedStream_Success) { |
| 405 CreateAudioStream(); |
| 406 CreateEncryptedVideoStream(); |
| 407 |
| 408 SetAudioRendererInitializeExpectations(PIPELINE_OK); |
| 409 SetVideoRendererInitializeExpectations(PIPELINE_OK); |
| 410 InitializeAndExpect(PIPELINE_OK); |
| 411 // Initialization is pending until CDM is set. |
| 412 EXPECT_EQ(PIPELINE_ERROR_OPERATION_PENDING, initialization_status_); |
| 413 |
| 414 SetCdmAndExpect(true); |
| 415 EXPECT_EQ(PIPELINE_OK, initialization_status_); |
| 416 } |
| 417 |
| 418 TEST_F(RendererImplTest, SetCdmAfterInitialize_EncryptedStream_Failure) { |
| 419 CreateAudioStream(); |
| 420 CreateEncryptedVideoStream(); |
| 421 |
| 422 SetAudioRendererInitializeExpectations(PIPELINE_OK); |
| 423 SetVideoRendererInitializeExpectations(PIPELINE_ERROR_INITIALIZATION_FAILED); |
| 424 InitializeAndExpect(PIPELINE_ERROR_INITIALIZATION_FAILED); |
| 425 // Initialization is pending until CDM is set. |
| 426 EXPECT_EQ(PIPELINE_ERROR_OPERATION_PENDING, initialization_status_); |
| 427 |
| 428 SetCdmAndExpect(false); |
| 429 EXPECT_EQ(PIPELINE_ERROR_INITIALIZATION_FAILED, initialization_status_); |
| 430 } |
| 431 |
| 432 TEST_F(RendererImplTest, SetCdmMultipleTimes) { |
| 433 SetCdmAndExpect(true); |
| 434 SetCdmAndExpect(false); // Do not support switching CDM. |
| 435 } |
| 436 |
| 318 TEST_F(RendererImplTest, StartPlayingFrom) { | 437 TEST_F(RendererImplTest, StartPlayingFrom) { |
| 319 InitializeWithAudioAndVideo(); | 438 InitializeWithAudioAndVideo(); |
| 320 Play(); | 439 Play(); |
| 321 } | 440 } |
| 322 | 441 |
| 323 TEST_F(RendererImplTest, StartPlayingFromWithPlaybackRate) { | 442 TEST_F(RendererImplTest, StartPlayingFromWithPlaybackRate) { |
| 324 InitializeWithAudioAndVideo(); | 443 InitializeWithAudioAndVideo(); |
| 325 | 444 |
| 326 // Play with a zero playback rate shouldn't start time. | 445 // Play with a zero playback rate shouldn't start time. |
| 327 Play(); | 446 Play(); |
| (...skipping 271 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 599 .WillOnce( | 718 .WillOnce( |
| 600 SetBufferingState(&audio_buffering_state_cb_, BUFFERING_HAVE_ENOUGH)); | 719 SetBufferingState(&audio_buffering_state_cb_, BUFFERING_HAVE_ENOUGH)); |
| 601 EXPECT_CALL(*video_renderer_, StartPlayingFrom(kStartTime)); | 720 EXPECT_CALL(*video_renderer_, StartPlayingFrom(kStartTime)); |
| 602 renderer_impl_->StartPlayingFrom(kStartTime); | 721 renderer_impl_->StartPlayingFrom(kStartTime); |
| 603 | 722 |
| 604 // Nothing else should primed on the message loop. | 723 // Nothing else should primed on the message loop. |
| 605 base::RunLoop().RunUntilIdle(); | 724 base::RunLoop().RunUntilIdle(); |
| 606 } | 725 } |
| 607 | 726 |
| 608 } // namespace media | 727 } // namespace media |
| OLD | NEW |