OLD | NEW |
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2012 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 "media/base/pipeline.h" | 5 #include "media/base/pipeline.h" |
6 | 6 |
7 #include <algorithm> | 7 #include <algorithm> |
8 | 8 |
9 #include "base/bind.h" | 9 #include "base/bind.h" |
10 #include "base/callback.h" | 10 #include "base/callback.h" |
11 #include "base/compiler_specific.h" | 11 #include "base/compiler_specific.h" |
12 #include "base/metrics/histogram.h" | 12 #include "base/metrics/histogram.h" |
13 #include "base/message_loop.h" | 13 #include "base/message_loop.h" |
14 #include "base/stl_util.h" | 14 #include "base/stl_util.h" |
15 #include "base/string_util.h" | 15 #include "base/string_util.h" |
16 #include "base/synchronization/condition_variable.h" | 16 #include "base/synchronization/condition_variable.h" |
| 17 #include "media/base/audio_decoder.h" |
17 #include "media/base/clock.h" | 18 #include "media/base/clock.h" |
18 #include "media/base/composite_filter.h" | 19 #include "media/base/composite_filter.h" |
| 20 #include "media/base/composite_filter.h" |
19 #include "media/base/filter_collection.h" | 21 #include "media/base/filter_collection.h" |
| 22 #include "media/base/filters.h" |
20 #include "media/base/media_log.h" | 23 #include "media/base/media_log.h" |
21 | 24 |
22 namespace media { | 25 namespace media { |
23 | 26 |
24 PipelineStatusNotification::PipelineStatusNotification() | 27 PipelineStatusNotification::PipelineStatusNotification() |
25 : cv_(&lock_), status_(PIPELINE_OK), notified_(false) { | 28 : cv_(&lock_), status_(PIPELINE_OK), notified_(false) { |
26 } | 29 } |
27 | 30 |
28 PipelineStatusNotification::~PipelineStatusNotification() { | 31 PipelineStatusNotification::~PipelineStatusNotification() { |
29 DCHECK(notified_); | 32 DCHECK(notified_); |
(...skipping 17 matching lines...) Expand all Loading... |
47 while (!notified_) | 50 while (!notified_) |
48 cv_.Wait(); | 51 cv_.Wait(); |
49 } | 52 } |
50 | 53 |
51 media::PipelineStatus PipelineStatusNotification::status() { | 54 media::PipelineStatus PipelineStatusNotification::status() { |
52 base::AutoLock auto_lock(lock_); | 55 base::AutoLock auto_lock(lock_); |
53 DCHECK(notified_); | 56 DCHECK(notified_); |
54 return status_; | 57 return status_; |
55 } | 58 } |
56 | 59 |
57 class Pipeline::PipelineInitState { | 60 struct Pipeline::PipelineInitState { |
58 public: | 61 scoped_refptr<AudioDecoder> audio_decoder; |
59 scoped_refptr<AudioDecoder> audio_decoder_; | 62 scoped_refptr<VideoDecoder> video_decoder; |
60 scoped_refptr<VideoDecoder> video_decoder_; | 63 scoped_refptr<CompositeFilter> composite; |
61 scoped_refptr<CompositeFilter> composite_; | |
62 }; | 64 }; |
63 | 65 |
64 Pipeline::Pipeline(MessageLoop* message_loop, MediaLog* media_log) | 66 Pipeline::Pipeline(MessageLoop* message_loop, MediaLog* media_log) |
65 : message_loop_(message_loop), | 67 : message_loop_(message_loop), |
66 media_log_(media_log), | 68 media_log_(media_log), |
67 clock_(new Clock(&base::Time::Now)), | 69 clock_(new Clock(&base::Time::Now)), |
68 waiting_for_clock_update_(false), | 70 waiting_for_clock_update_(false), |
69 state_(kCreated), | 71 state_(kCreated), |
70 current_bytes_(0), | 72 current_bytes_(0), |
71 creation_time_(base::Time::Now()), | 73 creation_time_(base::Time::Now()), |
(...skipping 542 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
614 DCHECK_EQ(kCreated, state_); | 616 DCHECK_EQ(kCreated, state_); |
615 filter_collection_ = filter_collection.Pass(); | 617 filter_collection_ = filter_collection.Pass(); |
616 url_ = url; | 618 url_ = url; |
617 ended_callback_ = ended_callback; | 619 ended_callback_ = ended_callback; |
618 error_callback_ = error_callback; | 620 error_callback_ = error_callback; |
619 network_callback_ = network_callback; | 621 network_callback_ = network_callback; |
620 seek_callback_ = start_callback; | 622 seek_callback_ = start_callback; |
621 | 623 |
622 // Kick off initialization. | 624 // Kick off initialization. |
623 pipeline_init_state_.reset(new PipelineInitState()); | 625 pipeline_init_state_.reset(new PipelineInitState()); |
624 pipeline_init_state_->composite_ = new CompositeFilter(message_loop_); | 626 pipeline_init_state_->composite = new CompositeFilter(message_loop_); |
625 pipeline_init_state_->composite_->set_host(this); | 627 pipeline_init_state_->composite->set_host(this); |
626 | 628 |
627 SetState(kInitDemuxer); | 629 SetState(kInitDemuxer); |
628 InitializeDemuxer(); | 630 InitializeDemuxer(); |
629 } | 631 } |
630 | 632 |
631 // Main initialization method called on the pipeline thread. This code attempts | 633 // Main initialization method called on the pipeline thread. This code attempts |
632 // to use the specified filter factory to build a pipeline. | 634 // to use the specified filter factory to build a pipeline. |
633 // Initialization step performed in this method depends on current state of this | 635 // Initialization step performed in this method depends on current state of this |
634 // object, indicated by |state_|. After each step of initialization, this | 636 // object, indicated by |state_|. After each step of initialization, this |
635 // object transits to the next stage. It starts by creating a Demuxer, and then | 637 // object transits to the next stage. It starts by creating a Demuxer, and then |
636 // connects the Demuxer's audio stream to an AudioDecoder which is then | 638 // connects the Demuxer's audio stream to an AudioDecoder which is then |
637 // connected to an AudioRenderer. If the media has video, then it connects a | 639 // connected to an AudioRenderer. If the media has video, then it connects a |
638 // VideoDecoder to the Demuxer's video stream, and then connects the | 640 // VideoDecoder to the Demuxer's video stream, and then connects the |
639 // VideoDecoder to a VideoRenderer. | 641 // VideoDecoder to a VideoRenderer. |
640 // | 642 // |
641 // When all required filters have been created and have called their | 643 // When all required filters have been created and have called their |
642 // FilterHost's InitializationComplete() method, the pipeline will update its | 644 // FilterHost's InitializationComplete() method, the pipeline will update its |
643 // state to kStarted and |init_callback_|, will be executed. | 645 // state to kStarted and |init_callback_|, will be executed. |
644 // | 646 // |
645 // TODO(hclam): InitializeTask() is now starting the pipeline asynchronously. It | 647 // TODO(hclam): InitializeTask() is now starting the pipeline asynchronously. It |
646 // works like a big state change table. If we no longer need to start filters | 648 // works like a big state change table. If we no longer need to start filters |
647 // in order, we need to get rid of all the state change. | 649 // in order, we need to get rid of all the state change. |
648 void Pipeline::InitializeTask(PipelineStatus last_stage_status) { | 650 void Pipeline::InitializeTask(PipelineStatus last_stage_status) { |
649 DCHECK_EQ(MessageLoop::current(), message_loop_); | 651 DCHECK_EQ(MessageLoop::current(), message_loop_); |
650 | 652 |
651 if (last_stage_status != PIPELINE_OK) { | 653 if (last_stage_status != PIPELINE_OK) { |
652 // Currently only VideoDecoders have a recoverable error code. | 654 // Currently only VideoDecoders have a recoverable error code. |
653 if (state_ == kInitVideoDecoder && | 655 if (state_ == kInitVideoDecoder && |
654 last_stage_status == DECODER_ERROR_NOT_SUPPORTED) { | 656 last_stage_status == DECODER_ERROR_NOT_SUPPORTED) { |
655 pipeline_init_state_->composite_->RemoveFilter( | 657 pipeline_init_state_->composite->RemoveFilter( |
656 pipeline_init_state_->video_decoder_.get()); | 658 pipeline_init_state_->video_decoder.get()); |
657 state_ = kInitAudioRenderer; | 659 state_ = kInitAudioRenderer; |
658 } else { | 660 } else { |
659 SetError(last_stage_status); | 661 SetError(last_stage_status); |
660 } | 662 } |
661 } | 663 } |
662 | 664 |
663 // If we have received the stop or error signal, return immediately. | 665 // If we have received the stop or error signal, return immediately. |
664 if (IsPipelineStopPending() || IsPipelineStopped() || !IsPipelineOk()) | 666 if (IsPipelineStopPending() || IsPipelineStopped() || !IsPipelineOk()) |
665 return; | 667 return; |
666 | 668 |
667 DCHECK(state_ == kInitDemuxer || | 669 DCHECK(state_ == kInitDemuxer || |
668 state_ == kInitAudioDecoder || | 670 state_ == kInitAudioDecoder || |
669 state_ == kInitAudioRenderer || | 671 state_ == kInitAudioRenderer || |
670 state_ == kInitVideoDecoder || | 672 state_ == kInitVideoDecoder || |
671 state_ == kInitVideoRenderer); | 673 state_ == kInitVideoRenderer); |
672 | 674 |
673 // Demuxer created, create audio decoder. | 675 // Demuxer created, create audio decoder. |
674 if (state_ == kInitDemuxer) { | 676 if (state_ == kInitDemuxer) { |
675 SetState(kInitAudioDecoder); | 677 SetState(kInitAudioDecoder); |
676 // If this method returns false, then there's no audio stream. | 678 // If this method returns false, then there's no audio stream. |
677 if (InitializeAudioDecoder(demuxer_)) | 679 if (InitializeAudioDecoder(demuxer_)) |
678 return; | 680 return; |
679 } | 681 } |
680 | 682 |
681 // Assuming audio decoder was created, create audio renderer. | 683 // Assuming audio decoder was created, create audio renderer. |
682 if (state_ == kInitAudioDecoder) { | 684 if (state_ == kInitAudioDecoder) { |
683 SetState(kInitAudioRenderer); | 685 SetState(kInitAudioRenderer); |
684 | 686 |
685 // Returns false if there's no audio stream. | 687 // Returns false if there's no audio stream. |
686 if (InitializeAudioRenderer(pipeline_init_state_->audio_decoder_)) { | 688 if (InitializeAudioRenderer(pipeline_init_state_->audio_decoder)) { |
687 base::AutoLock auto_lock(lock_); | 689 base::AutoLock auto_lock(lock_); |
688 has_audio_ = true; | 690 has_audio_ = true; |
689 return; | 691 return; |
690 } | 692 } |
691 } | 693 } |
692 | 694 |
693 // Assuming audio renderer was created, create video decoder. | 695 // Assuming audio renderer was created, create video decoder. |
694 if (state_ == kInitAudioRenderer) { | 696 if (state_ == kInitAudioRenderer) { |
695 // Then perform the stage of initialization, i.e. initialize video decoder. | 697 // Then perform the stage of initialization, i.e. initialize video decoder. |
696 SetState(kInitVideoDecoder); | 698 SetState(kInitVideoDecoder); |
697 if (InitializeVideoDecoder(demuxer_)) | 699 if (InitializeVideoDecoder(demuxer_)) |
698 return; | 700 return; |
699 } | 701 } |
700 | 702 |
701 // Assuming video decoder was created, create video renderer. | 703 // Assuming video decoder was created, create video renderer. |
702 if (state_ == kInitVideoDecoder) { | 704 if (state_ == kInitVideoDecoder) { |
703 SetState(kInitVideoRenderer); | 705 SetState(kInitVideoRenderer); |
704 if (InitializeVideoRenderer(pipeline_init_state_->video_decoder_)) { | 706 if (InitializeVideoRenderer(pipeline_init_state_->video_decoder)) { |
705 base::AutoLock auto_lock(lock_); | 707 base::AutoLock auto_lock(lock_); |
706 has_video_ = true; | 708 has_video_ = true; |
707 return; | 709 return; |
708 } | 710 } |
709 } | 711 } |
710 | 712 |
711 if (state_ == kInitVideoRenderer) { | 713 if (state_ == kInitVideoRenderer) { |
712 if (!IsPipelineOk() || !(HasAudio() || HasVideo())) { | 714 if (!IsPipelineOk() || !(HasAudio() || HasVideo())) { |
713 SetError(PIPELINE_ERROR_COULD_NOT_RENDER); | 715 SetError(PIPELINE_ERROR_COULD_NOT_RENDER); |
714 return; | 716 return; |
715 } | 717 } |
716 | 718 |
717 // Clear the collection of filters. | 719 // Clear the collection of filters. |
718 filter_collection_->Clear(); | 720 filter_collection_->Clear(); |
719 | 721 |
720 pipeline_filter_ = pipeline_init_state_->composite_; | 722 pipeline_filter_ = pipeline_init_state_->composite; |
721 | 723 |
722 // Clear init state since we're done initializing. | 724 // Clear init state since we're done initializing. |
723 pipeline_init_state_.reset(); | 725 pipeline_init_state_.reset(); |
724 | 726 |
725 if (audio_disabled_) { | 727 if (audio_disabled_) { |
726 // Audio was disabled at some point during initialization. Notify | 728 // Audio was disabled at some point during initialization. Notify |
727 // the pipeline filter now that it has been initialized. | 729 // the pipeline filter now that it has been initialized. |
728 demuxer_->OnAudioRendererDisabled(); | 730 demuxer_->OnAudioRendererDisabled(); |
729 pipeline_filter_->OnAudioRendererDisabled(); | 731 pipeline_filter_->OnAudioRendererDisabled(); |
730 } | 732 } |
(...skipping 291 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1022 // We use audio stream to update the clock. So if there is such a stream, | 1024 // We use audio stream to update the clock. So if there is such a stream, |
1023 // we pause the clock until we receive a valid timestamp. | 1025 // we pause the clock until we receive a valid timestamp. |
1024 waiting_for_clock_update_ = true; | 1026 waiting_for_clock_update_ = true; |
1025 if (!has_audio_) { | 1027 if (!has_audio_) { |
1026 clock_->SetMaxTime(clock_->Duration()); | 1028 clock_->SetMaxTime(clock_->Duration()); |
1027 StartClockIfWaitingForTimeUpdate_Locked(); | 1029 StartClockIfWaitingForTimeUpdate_Locked(); |
1028 } | 1030 } |
1029 | 1031 |
1030 // Start monitoring rate of downloading. | 1032 // Start monitoring rate of downloading. |
1031 int bitrate = 0; | 1033 int bitrate = 0; |
1032 if (demuxer_.get()) { | 1034 if (demuxer_) { |
1033 bitrate = demuxer_->GetBitrate(); | 1035 bitrate = demuxer_->GetBitrate(); |
1034 local_source_ = demuxer_->IsLocalSource(); | 1036 local_source_ = demuxer_->IsLocalSource(); |
1035 streaming_ = !demuxer_->IsSeekable(); | 1037 streaming_ = !demuxer_->IsSeekable(); |
1036 } | 1038 } |
1037 // Needs to be locked because most other calls to |download_rate_monitor_| | 1039 // Needs to be locked because most other calls to |download_rate_monitor_| |
1038 // occur on the renderer thread. | 1040 // occur on the renderer thread. |
1039 download_rate_monitor_.Start( | 1041 download_rate_monitor_.Start( |
1040 base::Bind(&Pipeline::OnCanPlayThrough, this), | 1042 base::Bind(&Pipeline::OnCanPlayThrough, this), |
1041 bitrate, streaming_, local_source_); | 1043 bitrate, streaming_, local_source_); |
1042 download_rate_monitor_.SetBufferedBytes(buffered_bytes_, base::Time::Now()); | 1044 download_rate_monitor_.SetBufferedBytes(buffered_bytes_, base::Time::Now()); |
(...skipping 66 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1109 if (!stop_cb.is_null()) { | 1111 if (!stop_cb.is_null()) { |
1110 stop_cb.Run(status_); | 1112 stop_cb.Run(status_); |
1111 } | 1113 } |
1112 } | 1114 } |
1113 | 1115 |
1114 tearing_down_ = false; | 1116 tearing_down_ = false; |
1115 error_caused_teardown_ = false; | 1117 error_caused_teardown_ = false; |
1116 } | 1118 } |
1117 | 1119 |
1118 bool Pipeline::PrepareFilter(scoped_refptr<Filter> filter) { | 1120 bool Pipeline::PrepareFilter(scoped_refptr<Filter> filter) { |
1119 bool ret = pipeline_init_state_->composite_->AddFilter(filter.get()); | 1121 bool ret = pipeline_init_state_->composite->AddFilter(filter.get()); |
1120 if (!ret) | 1122 if (!ret) |
1121 SetError(PIPELINE_ERROR_INITIALIZATION_FAILED); | 1123 SetError(PIPELINE_ERROR_INITIALIZATION_FAILED); |
1122 return ret; | 1124 return ret; |
1123 } | 1125 } |
1124 | 1126 |
1125 void Pipeline::InitializeDemuxer() { | 1127 void Pipeline::InitializeDemuxer() { |
1126 DCHECK_EQ(MessageLoop::current(), message_loop_); | 1128 DCHECK_EQ(MessageLoop::current(), message_loop_); |
1127 DCHECK(IsPipelineOk()); | 1129 DCHECK(IsPipelineOk()); |
1128 | 1130 |
1129 filter_collection_->GetDemuxerFactory()->Build( | 1131 filter_collection_->GetDemuxerFactory()->Build( |
(...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1164 const scoped_refptr<Demuxer>& demuxer) { | 1166 const scoped_refptr<Demuxer>& demuxer) { |
1165 DCHECK_EQ(MessageLoop::current(), message_loop_); | 1167 DCHECK_EQ(MessageLoop::current(), message_loop_); |
1166 DCHECK(IsPipelineOk()); | 1168 DCHECK(IsPipelineOk()); |
1167 | 1169 |
1168 scoped_refptr<DemuxerStream> stream = | 1170 scoped_refptr<DemuxerStream> stream = |
1169 demuxer->GetStream(DemuxerStream::AUDIO); | 1171 demuxer->GetStream(DemuxerStream::AUDIO); |
1170 | 1172 |
1171 if (!stream) | 1173 if (!stream) |
1172 return false; | 1174 return false; |
1173 | 1175 |
1174 scoped_refptr<AudioDecoder> audio_decoder; | 1176 filter_collection_->SelectAudioDecoder(&pipeline_init_state_->audio_decoder); |
1175 filter_collection_->SelectAudioDecoder(&audio_decoder); | |
1176 | 1177 |
1177 if (!audio_decoder) { | 1178 if (!pipeline_init_state_->audio_decoder) { |
1178 SetError(PIPELINE_ERROR_REQUIRED_FILTER_MISSING); | 1179 SetError(PIPELINE_ERROR_REQUIRED_FILTER_MISSING); |
1179 return false; | 1180 return false; |
1180 } | 1181 } |
1181 | 1182 |
1182 if (!PrepareFilter(audio_decoder)) | 1183 pipeline_init_state_->audio_decoder->Initialize( |
1183 return false; | |
1184 | |
1185 pipeline_init_state_->audio_decoder_ = audio_decoder; | |
1186 audio_decoder->Initialize( | |
1187 stream, | 1184 stream, |
1188 base::Bind(&Pipeline::OnFilterInitialize, this), | 1185 base::Bind(&Pipeline::OnFilterInitialize, this), |
1189 base::Bind(&Pipeline::OnUpdateStatistics, this)); | 1186 base::Bind(&Pipeline::OnUpdateStatistics, this)); |
1190 return true; | 1187 return true; |
1191 } | 1188 } |
1192 | 1189 |
1193 bool Pipeline::InitializeVideoDecoder( | 1190 bool Pipeline::InitializeVideoDecoder( |
1194 const scoped_refptr<Demuxer>& demuxer) { | 1191 const scoped_refptr<Demuxer>& demuxer) { |
1195 DCHECK_EQ(MessageLoop::current(), message_loop_); | 1192 DCHECK_EQ(MessageLoop::current(), message_loop_); |
1196 DCHECK(IsPipelineOk()); | 1193 DCHECK(IsPipelineOk()); |
(...skipping 10 matching lines...) Expand all Loading... |
1207 filter_collection_->SelectVideoDecoder(&video_decoder_); | 1204 filter_collection_->SelectVideoDecoder(&video_decoder_); |
1208 | 1205 |
1209 if (!video_decoder_) { | 1206 if (!video_decoder_) { |
1210 SetError(PIPELINE_ERROR_REQUIRED_FILTER_MISSING); | 1207 SetError(PIPELINE_ERROR_REQUIRED_FILTER_MISSING); |
1211 return false; | 1208 return false; |
1212 } | 1209 } |
1213 | 1210 |
1214 if (!PrepareFilter(video_decoder_)) | 1211 if (!PrepareFilter(video_decoder_)) |
1215 return false; | 1212 return false; |
1216 | 1213 |
1217 pipeline_init_state_->video_decoder_ = video_decoder_; | 1214 pipeline_init_state_->video_decoder = video_decoder_; |
1218 video_decoder_->Initialize( | 1215 video_decoder_->Initialize( |
1219 stream, | 1216 stream, |
1220 base::Bind(&Pipeline::OnFilterInitialize, this), | 1217 base::Bind(&Pipeline::OnFilterInitialize, this), |
1221 base::Bind(&Pipeline::OnUpdateStatistics, this)); | 1218 base::Bind(&Pipeline::OnUpdateStatistics, this)); |
1222 return true; | 1219 return true; |
1223 } | 1220 } |
1224 | 1221 |
1225 bool Pipeline::InitializeAudioRenderer( | 1222 bool Pipeline::InitializeAudioRenderer( |
1226 const scoped_refptr<AudioDecoder>& decoder) { | 1223 const scoped_refptr<AudioDecoder>& decoder) { |
1227 DCHECK_EQ(MessageLoop::current(), message_loop_); | 1224 DCHECK_EQ(MessageLoop::current(), message_loop_); |
(...skipping 65 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1293 message_loop_->PostTask(FROM_HERE, base::Bind( | 1290 message_loop_->PostTask(FROM_HERE, base::Bind( |
1294 &Pipeline::FinishDestroyingFiltersTask, this)); | 1291 &Pipeline::FinishDestroyingFiltersTask, this)); |
1295 break; | 1292 break; |
1296 | 1293 |
1297 case kInitDemuxer: | 1294 case kInitDemuxer: |
1298 case kInitAudioDecoder: | 1295 case kInitAudioDecoder: |
1299 case kInitAudioRenderer: | 1296 case kInitAudioRenderer: |
1300 case kInitVideoDecoder: | 1297 case kInitVideoDecoder: |
1301 case kInitVideoRenderer: | 1298 case kInitVideoRenderer: |
1302 // Make it look like initialization was successful. | 1299 // Make it look like initialization was successful. |
1303 pipeline_filter_ = pipeline_init_state_->composite_; | 1300 pipeline_filter_ = pipeline_init_state_->composite; |
1304 pipeline_init_state_.reset(); | 1301 pipeline_init_state_.reset(); |
1305 filter_collection_.reset(); | 1302 filter_collection_.reset(); |
1306 | 1303 |
1307 SetState(kStopping); | 1304 SetState(kStopping); |
1308 DoStop(base::Bind(&Pipeline::OnTeardownStateTransition, this)); | 1305 DoStop(base::Bind(&Pipeline::OnTeardownStateTransition, this)); |
1309 | 1306 |
1310 FinishInitialization(); | 1307 FinishInitialization(); |
1311 break; | 1308 break; |
1312 | 1309 |
1313 case kPausing: | 1310 case kPausing: |
(...skipping 111 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1425 void Pipeline::StartClockIfWaitingForTimeUpdate_Locked() { | 1422 void Pipeline::StartClockIfWaitingForTimeUpdate_Locked() { |
1426 lock_.AssertAcquired(); | 1423 lock_.AssertAcquired(); |
1427 if (!waiting_for_clock_update_) | 1424 if (!waiting_for_clock_update_) |
1428 return; | 1425 return; |
1429 | 1426 |
1430 waiting_for_clock_update_ = false; | 1427 waiting_for_clock_update_ = false; |
1431 clock_->Play(); | 1428 clock_->Play(); |
1432 } | 1429 } |
1433 | 1430 |
1434 } // namespace media | 1431 } // namespace media |
OLD | NEW |