Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(405)

Side by Side Diff: media/mojo/services/mojo_renderer_service.cc

Issue 680533004: Modify MojoRendererService to host a RendererImpl. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Created 6 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
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 }
OLDNEW
« media/mojo/services/mojo_renderer_service.h ('K') | « media/mojo/services/mojo_renderer_service.h ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698