Chromium Code Reviews| 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 "media/mojo/services/mojo_renderer_service.h" | 5 #include "media/mojo/services/mojo_renderer_service.h" |
| 6 | 6 |
| 7 #include "base/bind.h" | 7 #include "base/bind.h" |
| 8 #include "base/callback_helpers.h" | 8 #include "base/callback_helpers.h" |
| 9 #include "base/memory/scoped_vector.h" | 9 #include "base/memory/scoped_vector.h" |
| 10 #include "media/audio/audio_manager.h" | 10 #include "media/audio/audio_manager.h" |
| 11 #include "media/audio/audio_manager_base.h" | 11 #include "media/audio/audio_manager_base.h" |
| 12 #include "media/audio/null_audio_sink.h" | 12 #include "media/audio/null_audio_sink.h" |
| 13 #include "media/base/audio_decoder.h" | 13 #include "media/base/audio_decoder.h" |
| 14 #include "media/base/audio_renderer.h" | 14 #include "media/base/audio_renderer.h" |
| 15 #include "media/base/audio_renderer_sink.h" | 15 #include "media/base/audio_renderer_sink.h" |
| 16 #include "media/base/decryptor.h" | 16 #include "media/base/decryptor.h" |
| 17 #include "media/base/media_log.h" | 17 #include "media/base/media_log.h" |
| 18 #include "media/base/video_renderer.h" | |
| 18 #include "media/filters/audio_renderer_impl.h" | 19 #include "media/filters/audio_renderer_impl.h" |
| 19 #include "media/filters/ffmpeg_audio_decoder.h" | 20 #include "media/filters/ffmpeg_audio_decoder.h" |
| 20 #include "media/filters/opus_audio_decoder.h" | 21 #include "media/filters/opus_audio_decoder.h" |
| 22 #include "media/filters/renderer_impl.h" | |
| 21 #include "media/mojo/services/mojo_demuxer_stream_adapter.h" | 23 #include "media/mojo/services/mojo_demuxer_stream_adapter.h" |
| 22 #include "mojo/application/application_runner_chromium.h" | 24 #include "mojo/application/application_runner_chromium.h" |
| 23 #include "mojo/public/c/system/main.h" | 25 #include "mojo/public/c/system/main.h" |
| 24 #include "mojo/public/cpp/application/application_connection.h" | 26 #include "mojo/public/cpp/application/application_connection.h" |
| 25 #include "mojo/public/cpp/application/application_delegate.h" | 27 #include "mojo/public/cpp/application/application_delegate.h" |
| 26 #include "mojo/public/cpp/application/interface_factory_impl.h" | 28 #include "mojo/public/cpp/application/interface_factory_impl.h" |
| 27 | 29 |
| 28 namespace media { | 30 namespace media { |
| 29 | 31 |
| 30 // Time interval to update media time. | 32 // Time interval to update media time. |
| 31 const int kTimeUpdateIntervalMs = 50; | 33 const int kTimeUpdateIntervalMs = 50; |
| 32 | 34 |
| 33 #if !defined(OS_ANDROID) | 35 #if !defined(OS_ANDROID) |
| 34 static void LogMediaSourceError(const scoped_refptr<MediaLog>& media_log, | 36 static void LogMediaSourceError(const scoped_refptr<MediaLog>& media_log, |
| 35 const std::string& error) { | 37 const std::string& error) { |
| 36 media_log->AddEvent(media_log->CreateMediaSourceErrorEvent(error)); | 38 media_log->AddEvent(media_log->CreateMediaSourceErrorEvent(error)); |
| 37 } | 39 } |
| 38 #endif | 40 #endif |
| 39 | 41 |
| 40 static base::TimeDelta TimeUpdateInterval() { | 42 // Shim DemuxerStreamProvider wrapper for a single DemuxerStream. |
| 41 return base::TimeDelta::FromMilliseconds(kTimeUpdateIntervalMs); | 43 // TODO(dalecurtis): Once we support more than one DemuxerStream we'll need a |
| 42 } | 44 // more complicated shim which can handle a mojo::Array<DemuxerStream>. |
| 45 class DemuxerStreamProviderShim : public DemuxerStreamProvider { | |
| 46 public: | |
| 47 DemuxerStreamProviderShim(scoped_ptr<MojoDemuxerStreamAdapter> stream) | |
| 48 : stream_(stream.Pass()) {} | |
| 49 | |
| 50 ~DemuxerStreamProviderShim() override {} | |
| 51 | |
| 52 DemuxerStream* GetStream(DemuxerStream::Type type) override { | |
| 53 DCHECK_EQ(type, stream_->type()); | |
| 54 return stream_.get(); | |
| 55 }; | |
| 56 | |
| 57 Liveness GetLiveness() const override { | |
| 58 return DemuxerStreamProvider::LIVENESS_UNKNOWN; | |
| 59 } | |
| 60 | |
| 61 private: | |
| 62 scoped_ptr<MojoDemuxerStreamAdapter> stream_; | |
| 63 | |
| 64 DISALLOW_COPY_AND_ASSIGN(DemuxerStreamProviderShim); | |
| 65 }; | |
| 66 | |
| 67 // Stub VideoRenderer, scoped_ptr<VideoRenderer> doesn't take a nullptr nicely. | |
|
xhwang
2014/10/28 17:13:33
This is unfortunate...
Does this work for you?
s
DaleCurtis
2014/10/28 21:51:29
\o/, yes :)
| |
| 68 class VideoRendererStub : public VideoRenderer { | |
| 69 public: | |
| 70 ~VideoRendererStub() override {} | |
| 71 void Initialize(DemuxerStream* stream, | |
| 72 bool low_delay, | |
| 73 const PipelineStatusCB& init_cb, | |
| 74 const StatisticsCB& statistics_cb, | |
| 75 const BufferingStateCB& buffering_state_cb, | |
| 76 const base::Closure& ended_cb, | |
| 77 const PipelineStatusCB& error_cb, | |
| 78 const TimeDeltaCB& get_time_cb) override { | |
| 79 NOTREACHED(); | |
| 80 } | |
| 81 | |
| 82 void Flush(const base::Closure& callback) override { | |
| 83 NOTREACHED(); | |
| 84 } | |
| 85 | |
| 86 void StartPlayingFrom(base::TimeDelta timestamp) override { | |
| 87 NOTREACHED(); | |
| 88 } | |
| 89 }; | |
| 43 | 90 |
| 44 class MojoRendererApplication | 91 class MojoRendererApplication |
| 45 : public mojo::ApplicationDelegate, | 92 : public mojo::ApplicationDelegate, |
| 46 public mojo::InterfaceFactory<mojo::MediaRenderer> { | 93 public mojo::InterfaceFactory<mojo::MediaRenderer> { |
| 47 public: | 94 public: |
| 48 // mojo::ApplicationDelegate implementation. | 95 // mojo::ApplicationDelegate implementation. |
| 49 bool ConfigureIncomingConnection( | 96 bool ConfigureIncomingConnection( |
| 50 mojo::ApplicationConnection* connection) override { | 97 mojo::ApplicationConnection* connection) override { |
| 51 connection->AddService(this); | 98 connection->AddService(this); |
| 52 return true; | 99 return true; |
| 53 } | 100 } |
| 54 | 101 |
| 55 // mojo::InterfaceFactory<mojo::MediaRenderer> implementation. | 102 // mojo::InterfaceFactory<mojo::MediaRenderer> implementation. |
| 56 void Create(mojo::ApplicationConnection* connection, | 103 void Create(mojo::ApplicationConnection* connection, |
| 57 mojo::InterfaceRequest<mojo::MediaRenderer> request) override { | 104 mojo::InterfaceRequest<mojo::MediaRenderer> request) override { |
| 58 mojo::BindToRequest(new MojoRendererService(connection), &request); | 105 mojo::BindToRequest(new MojoRendererService(connection), &request); |
| 59 } | 106 } |
| 60 }; | 107 }; |
| 61 | 108 |
| 62 // TODO(xhwang): This class looks insanely similar to RendererImpl. We should | 109 static void MojoTrampoline(const mojo::Closure& closure) { |
|
xhwang
2014/10/28 17:13:33
I wonder whether we have some generic way to conve
DaleCurtis
2014/10/28 21:51:29
Yeah I think that'd be useful, but am too weak in
xhwang
2014/10/28 22:43:28
Agreed :)
| |
| 63 // really host a Renderer in this class instead of a AudioRenderer. | 110 closure.Run(); |
| 111 } | |
| 64 | 112 |
| 65 MojoRendererService::MojoRendererService( | 113 MojoRendererService::MojoRendererService( |
| 66 mojo::ApplicationConnection* connection) | 114 mojo::ApplicationConnection* connection) |
| 67 : state_(STATE_UNINITIALIZED), | 115 : state_(STATE_UNINITIALIZED), |
| 68 time_source_(NULL), | 116 last_media_time_(0), |
| 69 time_ticking_(false), | |
| 70 ended_(false), | |
| 71 // AudioManager() has been created by WebMediaPlayerFactory. This will | 117 // AudioManager() has been created by WebMediaPlayerFactory. This will |
| 72 // be problematic when MojoRendererService really runs in a separate | 118 // be problematic when MojoRendererService really runs in a separate |
| 73 // process. | 119 // process. |
| 74 // TODO(xhwang/dalecurtis): Figure out what config we should use. | 120 // TODO(xhwang/dalecurtis): Figure out what config we should use. |
| 75 audio_manager_( | 121 audio_manager_(AudioManager::Get() |
| 76 media::AudioManager::Get() | 122 ? AudioManager::Get() |
| 77 ? media::AudioManager::Get() | 123 : AudioManager::Create(&fake_audio_log_factory_)), |
| 78 : media::AudioManager::Create(&fake_audio_log_factory_)), | |
| 79 audio_hardware_config_( | 124 audio_hardware_config_( |
| 80 audio_manager_->GetInputStreamParameters( | 125 audio_manager_->GetInputStreamParameters( |
| 81 media::AudioManagerBase::kDefaultDeviceId), | 126 AudioManagerBase::kDefaultDeviceId), |
| 82 audio_manager_->GetDefaultOutputStreamParameters()), | 127 audio_manager_->GetDefaultOutputStreamParameters()), |
| 83 weak_factory_(this), | 128 weak_factory_(this), |
| 84 weak_this_(weak_factory_.GetWeakPtr()) { | 129 weak_this_(weak_factory_.GetWeakPtr()) { |
| 85 DVLOG(1) << __FUNCTION__; | 130 DVLOG(1) << __FUNCTION__; |
| 86 | 131 |
| 87 scoped_refptr<base::SingleThreadTaskRunner> runner( | 132 scoped_refptr<base::SingleThreadTaskRunner> task_runner( |
| 88 base::MessageLoop::current()->task_runner()); | 133 base::MessageLoop::current()->task_runner()); |
| 89 scoped_refptr<MediaLog> media_log(new MediaLog()); | 134 scoped_refptr<MediaLog> media_log(new MediaLog()); |
| 90 | 135 |
| 91 // TODO(xhwang): Provide a more general way to add new decoders. | 136 // TODO(xhwang): Provide a more general way to add new decoders. |
| 92 ScopedVector<AudioDecoder> audio_decoders; | 137 ScopedVector<AudioDecoder> audio_decoders; |
| 93 | 138 |
| 94 #if !defined(OS_ANDROID) | 139 #if !defined(OS_ANDROID) |
| 95 audio_decoders.push_back(new media::FFmpegAudioDecoder( | 140 audio_decoders.push_back(new FFmpegAudioDecoder( |
| 96 runner, base::Bind(&LogMediaSourceError, media_log))); | 141 task_runner, base::Bind(&LogMediaSourceError, media_log))); |
| 97 audio_decoders.push_back(new media::OpusAudioDecoder(runner)); | 142 audio_decoders.push_back(new OpusAudioDecoder(task_runner)); |
| 98 #endif | 143 #endif |
| 99 | 144 |
| 100 audio_renderer_.reset(new AudioRendererImpl( | 145 scoped_ptr<AudioRenderer> audio_renderer(new AudioRendererImpl( |
| 101 runner, | 146 task_runner, |
| 102 // TODO(tim): We should use |connection| passed to MojoRendererService | 147 // TODO(tim): We should use |connection| passed to MojoRendererService |
| 103 // to connect to a MojoAudioRendererSink implementation that we would | 148 // to connect to a MojoAudioRendererSink implementation that we would |
| 104 // wrap in an AudioRendererSink and pass in here. | 149 // wrap in an AudioRendererSink and pass in here. |
| 105 new NullAudioSink(runner), | 150 new NullAudioSink(task_runner), |
| 106 audio_decoders.Pass(), | 151 audio_decoders.Pass(), |
| 107 // TODO(tim): Not needed for now? | 152 // TODO(tim): Not needed for now? |
| 108 SetDecryptorReadyCB(), | 153 SetDecryptorReadyCB(), |
| 109 audio_hardware_config_, | 154 audio_hardware_config_, |
| 110 media_log)); | 155 media_log)); |
| 156 | |
| 157 // Create renderer. | |
| 158 renderer_.reset(new RendererImpl(task_runner, | |
| 159 audio_renderer.Pass(), | |
| 160 make_scoped_ptr(new VideoRendererStub()))); | |
| 111 } | 161 } |
| 112 | 162 |
| 113 MojoRendererService::~MojoRendererService() { | 163 MojoRendererService::~MojoRendererService() { |
| 114 } | 164 } |
| 115 | 165 |
| 116 void MojoRendererService::Initialize(mojo::DemuxerStreamPtr stream, | 166 void MojoRendererService::Initialize(mojo::DemuxerStreamPtr stream, |
| 117 const mojo::Callback<void()>& callback) { | 167 const mojo::Closure& callback) { |
| 118 DVLOG(1) << __FUNCTION__; | 168 DVLOG(1) << __FUNCTION__; |
| 119 DCHECK_EQ(state_, STATE_UNINITIALIZED) << state_; | 169 DCHECK_EQ(state_, STATE_UNINITIALIZED); |
| 120 DCHECK(client()); | 170 DCHECK(client()); |
| 121 | 171 |
| 122 init_cb_ = callback; | |
| 123 state_ = STATE_INITIALIZING; | 172 state_ = STATE_INITIALIZING; |
| 124 stream_.reset(new MojoDemuxerStreamAdapter( | 173 stream_provider_.reset(new DemuxerStreamProviderShim( |
| 125 stream.Pass(), | 174 make_scoped_ptr(new MojoDemuxerStreamAdapter( |
| 126 base::Bind(&MojoRendererService::OnStreamReady, weak_this_))); | 175 stream.Pass(), |
| 176 base::Bind(&MojoRendererService::OnStreamReady, | |
| 177 weak_this_, | |
| 178 callback))).Pass())); | |
|
xhwang
2014/10/28 17:13:33
Imagine when we have two streams, how will this ca
DaleCurtis
2014/10/28 21:51:29
I was thinking the DemuxerStreamProviderShim would
xhwang
2014/10/28 22:43:28
I think we are dropping SerialRunner...
Route cal
| |
| 127 } | 179 } |
| 128 | 180 |
| 129 void MojoRendererService::Flush(const mojo::Callback<void()>& callback) { | 181 void MojoRendererService::Flush(const mojo::Closure& callback) { |
| 130 DVLOG(2) << __FUNCTION__; | 182 DVLOG(2) << __FUNCTION__; |
| 131 DCHECK_EQ(state_, STATE_PLAYING) << state_; | 183 DCHECK_EQ(state_, STATE_PLAYING); |
| 132 | 184 |
| 133 state_ = STATE_FLUSHING; | 185 state_ = STATE_FLUSHING; |
| 134 if (time_ticking_) | 186 renderer_->Flush(base::Bind(&MojoTrampoline, callback)); |
| 135 PausePlayback(); | |
| 136 | |
| 137 // TODO(xhwang): This is not completed. Finish the flushing path. | |
| 138 NOTIMPLEMENTED(); | |
| 139 } | 187 } |
| 140 | 188 |
| 141 void MojoRendererService::StartPlayingFrom(int64_t time_delta_usec) { | 189 void MojoRendererService::StartPlayingFrom(int64_t time_delta_usec) { |
| 142 DVLOG(2) << __FUNCTION__ << ": " << time_delta_usec; | 190 DVLOG(2) << __FUNCTION__ << ": " << time_delta_usec; |
| 143 base::TimeDelta time = base::TimeDelta::FromMicroseconds(time_delta_usec); | 191 renderer_->StartPlayingFrom( |
| 144 time_source_->SetMediaTime(time); | 192 base::TimeDelta::FromMicroseconds(time_delta_usec)); |
| 145 audio_renderer_->StartPlaying(); | 193 |
| 194 // TODO(dalecurtis): Time updates shouldn't always tick, but without adding | |
| 195 // a bunch of additional state to this class plus RendererImpl, how to tell? | |
|
xhwang
2014/10/28 17:13:33
hmm, I thought after StartPlayingFrom() (i.e. as l
DaleCurtis
2014/10/28 21:51:29
Well this class can't determine if time is actuall
xhwang
2014/10/28 22:43:28
I see. Underflow is indeed a legit case.
I was su
DaleCurtis
2014/10/29 00:11:19
There is no PausePlayback() method, do you mean wh
DaleCurtis
2014/10/29 00:13:03
Also note, that if the first media time is 0 then
| |
| 196 SchedulePeriodicMediaTimeUpdates(); | |
| 146 } | 197 } |
| 147 | 198 |
| 148 void MojoRendererService::SetPlaybackRate(float playback_rate) { | 199 void MojoRendererService::SetPlaybackRate(float playback_rate) { |
| 149 DVLOG(2) << __FUNCTION__ << ": " << playback_rate; | 200 DVLOG(2) << __FUNCTION__ << ": " << playback_rate; |
| 150 | 201 DCHECK_EQ(state_, STATE_PLAYING); |
| 151 // Playback rate changes are only carried out while playing. | 202 renderer_->SetPlaybackRate(playback_rate); |
| 152 if (state_ != STATE_PLAYING) | |
| 153 return; | |
| 154 | |
| 155 time_source_->SetPlaybackRate(playback_rate); | |
| 156 } | 203 } |
| 157 | 204 |
| 158 void MojoRendererService::SetVolume(float volume) { | 205 void MojoRendererService::SetVolume(float volume) { |
| 159 if (audio_renderer_) | 206 renderer_->SetVolume(volume); |
| 160 audio_renderer_->SetVolume(volume); | |
| 161 } | 207 } |
| 162 | 208 |
| 163 void MojoRendererService::OnStreamReady() { | 209 void MojoRendererService::OnStreamReady(const mojo::Closure& callback) { |
| 164 DCHECK_EQ(state_, STATE_INITIALIZING) << state_; | 210 DCHECK_EQ(state_, STATE_INITIALIZING); |
| 165 audio_renderer_->Initialize( | 211 |
| 166 stream_.get(), | 212 renderer_->Initialize( |
| 167 base::Bind(&MojoRendererService::OnAudioRendererInitializeDone, | 213 stream_provider_.get(), |
| 168 weak_this_), | 214 base::Bind( |
| 215 &MojoRendererService::OnRendererInitializeDone, weak_this_, callback), | |
| 169 base::Bind(&MojoRendererService::OnUpdateStatistics, weak_this_), | 216 base::Bind(&MojoRendererService::OnUpdateStatistics, weak_this_), |
| 170 base::Bind(&MojoRendererService::OnBufferingStateChanged, weak_this_), | 217 base::Bind(&MojoRendererService::OnRendererEnded, weak_this_), |
| 171 base::Bind(&MojoRendererService::OnAudioRendererEnded, weak_this_), | 218 base::Bind(&MojoRendererService::OnError, weak_this_), |
| 172 base::Bind(&MojoRendererService::OnError, weak_this_)); | 219 base::Bind(&MojoRendererService::OnBufferingStateChanged, weak_this_)); |
| 173 } | 220 } |
| 174 | 221 |
| 175 void MojoRendererService::OnAudioRendererInitializeDone(PipelineStatus status) { | 222 void MojoRendererService::OnRendererInitializeDone( |
| 176 DVLOG(1) << __FUNCTION__ << ": " << status; | 223 const mojo::Closure& callback) { |
| 177 DCHECK_EQ(state_, STATE_INITIALIZING) << state_; | 224 DVLOG(1) << __FUNCTION__; |
| 178 | 225 |
| 179 if (status != PIPELINE_OK) { | 226 if (state_ != STATE_INITIALIZING) { |
| 180 state_ = STATE_ERROR; | 227 DCHECK_EQ(state_, STATE_ERROR); |
| 181 audio_renderer_.reset(); | 228 renderer_.reset(); |
| 182 client()->OnError(); | 229 } else { |
| 183 init_cb_.Run(); | 230 DCHECK_EQ(state_, STATE_INITIALIZING); |
| 184 init_cb_.reset(); | 231 state_ = STATE_PLAYING; |
| 185 return; | |
| 186 } | 232 } |
|
xhwang
2014/10/28 17:13:33
How about
if (state_ == STATE_ERROR) {
renderer
DaleCurtis
2014/10/28 21:51:29
Done.
| |
| 187 | 233 |
| 188 time_source_ = audio_renderer_->GetTimeSource(); | 234 callback.Run(); |
| 189 | |
| 190 state_ = STATE_PLAYING; | |
| 191 init_cb_.Run(); | |
| 192 init_cb_.reset(); | |
| 193 } | 235 } |
| 194 | 236 |
| 195 void MojoRendererService::OnUpdateStatistics(const PipelineStatistics& stats) { | 237 void MojoRendererService::OnUpdateStatistics(const PipelineStatistics& stats) { |
| 196 NOTIMPLEMENTED(); | 238 NOTIMPLEMENTED(); |
| 197 } | 239 } |
| 198 | 240 |
| 199 void MojoRendererService::UpdateMediaTime() { | 241 void MojoRendererService::UpdateMediaTime(bool only_if_changed) { |
| 200 uint64_t media_time = time_source_->CurrentMediaTime().InMicroseconds(); | 242 const uint64_t media_time = renderer_->GetMediaTime().InMicroseconds(); |
| 243 if (only_if_changed && media_time == last_media_time_) | |
| 244 return; | |
|
xhwang
2014/10/28 17:13:33
See comments above... How often is media_time == l
DaleCurtis
2014/10/28 21:51:29
This is just to prevent unnecessary time update ca
xhwang
2014/10/28 22:43:28
See my comment above about dropping |only_if_chang
| |
| 245 | |
| 201 client()->OnTimeUpdate(media_time, media_time); | 246 client()->OnTimeUpdate(media_time, media_time); |
| 247 last_media_time_ = media_time; | |
| 202 } | 248 } |
| 203 | 249 |
| 204 void MojoRendererService::SchedulePeriodicMediaTimeUpdates() { | 250 void MojoRendererService::SchedulePeriodicMediaTimeUpdates() { |
| 205 // Update media time immediately. | 251 UpdateMediaTime(false); |
| 206 UpdateMediaTime(); | |
| 207 | |
| 208 // Then setup periodic time update. | |
| 209 time_update_timer_.Start( | 252 time_update_timer_.Start( |
| 210 FROM_HERE, | 253 FROM_HERE, |
| 211 TimeUpdateInterval(), | 254 base::TimeDelta::FromMilliseconds(kTimeUpdateIntervalMs), |
| 212 base::Bind(&MojoRendererService::UpdateMediaTime, weak_this_)); | 255 base::Bind(&MojoRendererService::UpdateMediaTime, weak_this_, true)); |
| 213 } | 256 } |
| 214 | 257 |
| 215 void MojoRendererService::OnBufferingStateChanged( | 258 void MojoRendererService::OnBufferingStateChanged( |
| 216 media::BufferingState new_buffering_state) { | 259 BufferingState new_buffering_state) { |
| 217 DVLOG(2) << __FUNCTION__ << "(" << buffering_state_ << ", " | 260 DVLOG(2) << __FUNCTION__ << "(" << new_buffering_state << ") "; |
| 218 << new_buffering_state << ") "; | 261 client()->OnBufferingStateChange( |
| 219 bool was_waiting_for_enough_data = WaitingForEnoughData(); | 262 static_cast<mojo::BufferingState>(new_buffering_state)); |
| 220 | |
| 221 buffering_state_ = new_buffering_state; | |
| 222 | |
| 223 // Renderer underflowed. | |
| 224 if (!was_waiting_for_enough_data && WaitingForEnoughData()) { | |
| 225 PausePlayback(); | |
| 226 // TODO(xhwang): Notify client of underflow condition. | |
| 227 return; | |
| 228 } | |
| 229 | |
| 230 // Renderer prerolled. | |
| 231 if (was_waiting_for_enough_data && !WaitingForEnoughData()) { | |
| 232 StartPlayback(); | |
| 233 client()->OnBufferingStateChange( | |
| 234 static_cast<mojo::BufferingState>(new_buffering_state)); | |
| 235 return; | |
| 236 } | |
| 237 } | 263 } |
| 238 | 264 |
| 239 void MojoRendererService::OnAudioRendererEnded() { | 265 void MojoRendererService::OnRendererEnded() { |
| 240 DVLOG(1) << __FUNCTION__; | 266 DVLOG(1) << __FUNCTION__; |
| 241 | |
| 242 if (state_ != STATE_PLAYING) | |
| 243 return; | |
| 244 | |
| 245 DCHECK(!ended_); | |
| 246 ended_ = true; | |
| 247 | |
| 248 if (time_ticking_) | |
| 249 PausePlayback(); | |
| 250 | |
| 251 client()->OnEnded(); | 267 client()->OnEnded(); |
| 268 time_update_timer_.Reset(); | |
| 252 } | 269 } |
| 253 | 270 |
| 254 void MojoRendererService::OnError(PipelineStatus error) { | 271 void MojoRendererService::OnError(PipelineStatus error) { |
| 272 DVLOG(1) << __FUNCTION__; | |
| 273 state_ = STATE_ERROR; | |
| 255 client()->OnError(); | 274 client()->OnError(); |
| 256 } | 275 } |
| 257 | 276 |
| 258 bool MojoRendererService::WaitingForEnoughData() const { | |
| 259 DCHECK(audio_renderer_); | |
| 260 | |
| 261 return state_ == STATE_PLAYING && buffering_state_ != BUFFERING_HAVE_ENOUGH; | |
| 262 } | |
| 263 | |
| 264 void MojoRendererService::StartPlayback() { | |
| 265 DVLOG(1) << __FUNCTION__; | |
| 266 DCHECK_EQ(state_, STATE_PLAYING); | |
| 267 DCHECK(!time_ticking_); | |
| 268 DCHECK(!WaitingForEnoughData()); | |
| 269 | |
| 270 time_ticking_ = true; | |
| 271 time_source_->StartTicking(); | |
| 272 | |
| 273 SchedulePeriodicMediaTimeUpdates(); | |
| 274 } | |
| 275 | |
| 276 void MojoRendererService::PausePlayback() { | |
| 277 DVLOG(1) << __FUNCTION__; | |
| 278 DCHECK(time_ticking_); | |
| 279 switch (state_) { | |
| 280 case STATE_PLAYING: | |
| 281 DCHECK(ended_ || WaitingForEnoughData()) | |
| 282 << "Playback should only pause due to ending or underflowing"; | |
| 283 break; | |
| 284 | |
| 285 case STATE_FLUSHING: | |
| 286 // It's OK to pause playback when flushing. | |
| 287 break; | |
| 288 | |
| 289 case STATE_UNINITIALIZED: | |
| 290 case STATE_INITIALIZING: | |
| 291 case STATE_ERROR: | |
| 292 NOTREACHED() << "Invalid state: " << state_; | |
| 293 break; | |
| 294 } | |
| 295 | |
| 296 time_ticking_ = false; | |
| 297 time_source_->StopTicking(); | |
| 298 | |
| 299 // Cancel repeating time update timer and update the current media time. | |
| 300 time_update_timer_.Stop(); | |
| 301 UpdateMediaTime(); | |
| 302 } | |
| 303 | |
| 304 } // namespace media | 277 } // namespace media |
| 305 | 278 |
| 306 MojoResult MojoMain(MojoHandle shell_handle) { | 279 MojoResult MojoMain(MojoHandle shell_handle) { |
| 307 mojo::ApplicationRunnerChromium runner(new media::MojoRendererApplication); | 280 mojo::ApplicationRunnerChromium runner(new media::MojoRendererApplication); |
| 308 return runner.Run(shell_handle); | 281 return runner.Run(shell_handle); |
| 309 } | 282 } |
| OLD | NEW |