| OLD | NEW |
| 1 // Copyright 2015 The Chromium Authors. All rights reserved. | 1 // Copyright 2015 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 "chromecast/media/audio/cast_audio_output_stream.h" | 5 #include "chromecast/media/audio/cast_audio_output_stream.h" |
| 6 | 6 |
| 7 #include "base/bind.h" | 7 #include "base/bind.h" |
| 8 #include "base/callback_helpers.h" |
| 8 #include "base/synchronization/waitable_event.h" | 9 #include "base/synchronization/waitable_event.h" |
| 9 #include "base/threading/thread_checker.h" | 10 #include "base/threading/thread_checker.h" |
| 10 #include "chromecast/base/metrics/cast_metrics_helper.h" | 11 #include "chromecast/base/metrics/cast_metrics_helper.h" |
| 11 #include "chromecast/base/task_runner_impl.h" | 12 #include "chromecast/base/task_runner_impl.h" |
| 12 #include "chromecast/media/audio/cast_audio_manager.h" | 13 #include "chromecast/media/audio/cast_audio_manager.h" |
| 13 #include "chromecast/media/base/media_message_loop.h" | 14 #include "chromecast/media/base/media_message_loop.h" |
| 14 #include "chromecast/media/cma/base/cast_decoder_buffer_impl.h" | 15 #include "chromecast/media/cma/base/cast_decoder_buffer_impl.h" |
| 15 #include "chromecast/media/cma/base/decoder_buffer_adapter.h" | 16 #include "chromecast/media/cma/base/decoder_buffer_adapter.h" |
| 16 #include "chromecast/media/cma/pipeline/frame_status_cb_impl.h" | |
| 17 #include "chromecast/public/media/audio_pipeline_device.h" | |
| 18 #include "chromecast/public/media/decoder_config.h" | 17 #include "chromecast/public/media/decoder_config.h" |
| 19 #include "chromecast/public/media/decrypt_context.h" | 18 #include "chromecast/public/media/decrypt_context.h" |
| 20 #include "chromecast/public/media/media_clock_device.h" | |
| 21 #include "chromecast/public/media/media_pipeline_backend.h" | 19 #include "chromecast/public/media/media_pipeline_backend.h" |
| 22 #include "chromecast/public/media/media_pipeline_device_params.h" | 20 #include "chromecast/public/media/media_pipeline_device_params.h" |
| 23 #include "media/base/bind_to_current_loop.h" | 21 #include "media/base/bind_to_current_loop.h" |
| 24 #include "media/base/decoder_buffer.h" | 22 #include "media/base/decoder_buffer.h" |
| 25 | 23 |
| 26 namespace chromecast { | 24 namespace chromecast { |
| 27 namespace media { | 25 namespace media { |
| 26 namespace { |
| 28 | 27 |
| 29 namespace { | 28 MediaPipelineBackend::AudioDecoder* InitializeBackend( |
| 30 bool InitClockDevice(MediaClockDevice* clock_device) { | 29 const ::media::AudioParameters& audio_params, |
| 31 DCHECK(clock_device); | 30 MediaPipelineBackend* backend, |
| 32 DCHECK_EQ(clock_device->GetState(), MediaClockDevice::kStateUninitialized); | 31 MediaPipelineBackend::Delegate* delegate) { |
| 32 DCHECK(backend); |
| 33 DCHECK(delegate); |
| 33 | 34 |
| 34 if (!clock_device->SetState(media::MediaClockDevice::kStateIdle)) | 35 MediaPipelineBackend::AudioDecoder* decoder = backend->CreateAudioDecoder(); |
| 35 return false; | 36 if (!decoder) |
| 36 | 37 return nullptr; |
| 37 if (!clock_device->ResetTimeline(0)) | |
| 38 return false; | |
| 39 | |
| 40 if (!clock_device->SetRate(1.0)) | |
| 41 return false; | |
| 42 | |
| 43 return true; | |
| 44 } | |
| 45 | |
| 46 bool InitAudioDevice(const ::media::AudioParameters& audio_params, | |
| 47 AudioPipelineDevice* audio_device) { | |
| 48 DCHECK(audio_device); | |
| 49 DCHECK_EQ(audio_device->GetState(), AudioPipelineDevice::kStateUninitialized); | |
| 50 | 38 |
| 51 AudioConfig audio_config; | 39 AudioConfig audio_config; |
| 52 audio_config.codec = kCodecPCM; | 40 audio_config.codec = kCodecPCM; |
| 53 audio_config.sample_format = kSampleFormatS16; | 41 audio_config.sample_format = kSampleFormatS16; |
| 54 audio_config.bytes_per_channel = audio_params.bits_per_sample() / 8; | 42 audio_config.bytes_per_channel = audio_params.bits_per_sample() / 8; |
| 55 audio_config.channel_number = audio_params.channels(); | 43 audio_config.channel_number = audio_params.channels(); |
| 56 audio_config.samples_per_second = audio_params.sample_rate(); | 44 audio_config.samples_per_second = audio_params.sample_rate(); |
| 57 audio_config.extra_data = nullptr; | 45 audio_config.extra_data = nullptr; |
| 58 audio_config.extra_data_size = 0; | 46 audio_config.extra_data_size = 0; |
| 59 audio_config.is_encrypted = false; | 47 audio_config.is_encrypted = false; |
| 60 if (!audio_device->SetConfig(audio_config)) | |
| 61 return false; | |
| 62 | 48 |
| 63 if (!audio_device->SetState(AudioPipelineDevice::kStateIdle)) | 49 if (!decoder->SetConfig(audio_config)) |
| 64 return false; | 50 return nullptr; |
| 65 | 51 |
| 66 return true; | 52 if (!backend->Initialize(delegate)) |
| 53 return nullptr; |
| 54 |
| 55 return decoder; |
| 67 } | 56 } |
| 57 |
| 68 } // namespace | 58 } // namespace |
| 69 | 59 |
| 70 // Backend represents a MediaPipelineBackend adapter that runs on cast | 60 // Backend represents a MediaPipelineBackend adapter that runs on cast |
| 71 // media thread (media::MediaMessageLoop::GetTaskRunner). | 61 // media thread (media::MediaMessageLoop::GetTaskRunner). |
| 72 // It can be created and destroyed on any thread, but all other member functions | 62 // It can be created and destroyed on any thread, but all other member functions |
| 73 // must be called on a single thread. | 63 // must be called on a single thread. |
| 74 class CastAudioOutputStream::Backend { | 64 class CastAudioOutputStream::Backend : public MediaPipelineBackend::Delegate { |
| 75 public: | 65 public: |
| 76 typedef base::Callback<void(bool)> PushFrameCompletionCallback; | 66 typedef base::Callback<void(bool)> PushBufferCompletionCallback; |
| 77 | 67 |
| 78 Backend(const ::media::AudioParameters& audio_params) | 68 Backend(const ::media::AudioParameters& audio_params) |
| 79 : audio_params_(audio_params) { | 69 : audio_params_(audio_params), |
| 70 decoder_(nullptr), |
| 71 first_start_(true), |
| 72 error_(false), |
| 73 backend_buffer_(nullptr) { |
| 80 thread_checker_.DetachFromThread(); | 74 thread_checker_.DetachFromThread(); |
| 81 } | 75 } |
| 82 ~Backend() {} | 76 ~Backend() override {} |
| 83 | 77 |
| 84 void Open(CastAudioManager* audio_manager, | 78 void Open(CastAudioManager* audio_manager, |
| 85 bool* success, | 79 bool* success, |
| 86 base::WaitableEvent* completion_event) { | 80 base::WaitableEvent* completion_event) { |
| 87 DCHECK(thread_checker_.CalledOnValidThread()); | 81 DCHECK(thread_checker_.CalledOnValidThread()); |
| 88 DCHECK(backend_ == nullptr); | 82 DCHECK(backend_ == nullptr); |
| 83 DCHECK(audio_manager); |
| 84 DCHECK(success); |
| 85 DCHECK(completion_event); |
| 89 | 86 |
| 90 backend_task_runner_.reset(new TaskRunnerImpl()); | 87 backend_task_runner_.reset(new TaskRunnerImpl()); |
| 91 MediaPipelineDeviceParams device_params( | 88 MediaPipelineDeviceParams device_params( |
| 92 MediaPipelineDeviceParams::kModeIgnorePts, backend_task_runner_.get()); | 89 MediaPipelineDeviceParams::kModeIgnorePts, backend_task_runner_.get()); |
| 93 | 90 backend_ = audio_manager->CreateMediaPipelineBackend(device_params); |
| 94 scoped_ptr<MediaPipelineBackend> pipeline_backend = | 91 if (backend_) |
| 95 audio_manager->CreateMediaPipelineBackend(device_params); | 92 decoder_ = InitializeBackend(audio_params_, backend_.get(), this); |
| 96 if (pipeline_backend && InitClockDevice(pipeline_backend->GetClock()) && | 93 *success = decoder_ != nullptr; |
| 97 InitAudioDevice(audio_params_, pipeline_backend->GetAudio())) { | |
| 98 backend_ = pipeline_backend.Pass(); | |
| 99 } | |
| 100 *success = backend_ != nullptr; | |
| 101 completion_event->Signal(); | 94 completion_event->Signal(); |
| 102 } | 95 } |
| 103 | 96 |
| 104 void Close() { | 97 void Close() { |
| 105 DCHECK(thread_checker_.CalledOnValidThread()); | 98 DCHECK(thread_checker_.CalledOnValidThread()); |
| 106 | 99 |
| 107 if (backend_) { | 100 if (backend_) |
| 108 backend_->GetClock()->SetState(MediaClockDevice::kStateIdle); | 101 backend_->Stop(); |
| 109 backend_->GetAudio()->SetState(AudioPipelineDevice::kStateIdle); | |
| 110 } | |
| 111 backend_.reset(); | 102 backend_.reset(); |
| 112 backend_task_runner_.reset(); | 103 backend_task_runner_.reset(); |
| 113 } | 104 } |
| 114 | 105 |
| 115 void Start() { | 106 void Start() { |
| 116 DCHECK(thread_checker_.CalledOnValidThread()); | 107 DCHECK(thread_checker_.CalledOnValidThread()); |
| 108 DCHECK(backend_); |
| 117 | 109 |
| 118 MediaClockDevice* clock_device = backend_->GetClock(); | 110 if (first_start_) { |
| 119 clock_device->SetState(MediaClockDevice::kStateRunning); | 111 first_start_ = false; |
| 120 clock_device->SetRate(1.0f); | 112 backend_->Start(0); |
| 121 | 113 } else { |
| 122 AudioPipelineDevice* audio_device = backend_->GetAudio(); | 114 backend_->Resume(); |
| 123 audio_device->SetState(AudioPipelineDevice::kStateRunning); | 115 } |
| 124 } | 116 } |
| 125 | 117 |
| 126 void Stop() { | 118 void Stop() { |
| 127 DCHECK(thread_checker_.CalledOnValidThread()); | 119 DCHECK(thread_checker_.CalledOnValidThread()); |
| 120 DCHECK(backend_); |
| 128 | 121 |
| 129 MediaClockDevice* clock_device = backend_->GetClock(); | 122 backend_->Pause(); |
| 130 clock_device->SetRate(0.0f); | |
| 131 } | 123 } |
| 132 | 124 |
| 133 void PushFrame(scoped_refptr<media::DecoderBufferBase> decoder_buffer, | 125 void PushBuffer(scoped_refptr<media::DecoderBufferBase> decoder_buffer, |
| 134 const PushFrameCompletionCallback& completion_cb) { | 126 const PushBufferCompletionCallback& completion_cb) { |
| 135 DCHECK(thread_checker_.CalledOnValidThread()); | 127 DCHECK(thread_checker_.CalledOnValidThread()); |
| 128 DCHECK(decoder_); |
| 129 DCHECK(!completion_cb.is_null()); |
| 130 DCHECK(completion_cb_.is_null()); |
| 131 if (error_) { |
| 132 completion_cb.Run(false); |
| 133 return; |
| 134 } |
| 136 | 135 |
| 137 AudioPipelineDevice* audio_device = backend_->GetAudio(); | 136 backend_buffer_.set_buffer(decoder_buffer); |
| 138 MediaComponentDevice::FrameStatus status = | |
| 139 audio_device->PushFrame(nullptr, // decrypt_context | |
| 140 new CastDecoderBufferImpl(decoder_buffer), | |
| 141 new media::FrameStatusCBImpl(base::Bind( | |
| 142 &Backend::OnPushFrameStatus, | |
| 143 base::Unretained(this), completion_cb))); | |
| 144 | 137 |
| 145 if (status != MediaComponentDevice::kFramePending) | 138 MediaPipelineBackend::BufferStatus status = |
| 146 OnPushFrameStatus(completion_cb, status); | 139 decoder_->PushBuffer(nullptr /* decrypt_context */, &backend_buffer_); |
| 140 completion_cb_ = completion_cb; |
| 141 if (status != MediaPipelineBackend::kBufferPending) |
| 142 OnPushBufferComplete(decoder_, status); |
| 147 } | 143 } |
| 148 | 144 |
| 149 void SetVolume(double volume) { | 145 void SetVolume(double volume) { |
| 150 DCHECK(thread_checker_.CalledOnValidThread()); | 146 DCHECK(thread_checker_.CalledOnValidThread()); |
| 147 DCHECK(decoder_); |
| 148 decoder_->SetVolume(volume); |
| 149 } |
| 151 | 150 |
| 152 AudioPipelineDevice* audio_device = backend_->GetAudio(); | 151 // MediaPipelineBackend::Delegate implementation |
| 153 audio_device->SetStreamVolumeMultiplier(volume); | 152 void OnVideoResolutionChanged(MediaPipelineBackend::VideoDecoder* decoder, |
| 153 const Size& size) override {} |
| 154 |
| 155 void OnPushBufferComplete( |
| 156 MediaPipelineBackend::Decoder* decoder, |
| 157 MediaPipelineBackend::BufferStatus status) override { |
| 158 DCHECK(thread_checker_.CalledOnValidThread()); |
| 159 DCHECK_NE(status, MediaPipelineBackend::kBufferPending); |
| 160 |
| 161 base::ResetAndReturn(&completion_cb_) |
| 162 .Run(status == MediaPipelineBackend::kBufferSuccess); |
| 163 } |
| 164 |
| 165 void OnEndOfStream(MediaPipelineBackend::Decoder* decoder) override {} |
| 166 |
| 167 void OnDecoderError(MediaPipelineBackend::Decoder* decoder) override { |
| 168 error_ = true; |
| 169 if (!completion_cb_.is_null()) |
| 170 OnPushBufferComplete(decoder_, MediaPipelineBackend::kBufferFailed); |
| 154 } | 171 } |
| 155 | 172 |
| 156 private: | 173 private: |
| 157 void OnPushFrameStatus(const PushFrameCompletionCallback& completion_cb, | |
| 158 MediaComponentDevice::FrameStatus status) { | |
| 159 DCHECK(thread_checker_.CalledOnValidThread()); | |
| 160 DCHECK_NE(status, MediaComponentDevice::kFramePending); | |
| 161 | |
| 162 completion_cb.Run(status == MediaComponentDevice::kFrameSuccess); | |
| 163 } | |
| 164 | |
| 165 const ::media::AudioParameters audio_params_; | 174 const ::media::AudioParameters audio_params_; |
| 166 scoped_ptr<MediaPipelineBackend> backend_; | 175 scoped_ptr<MediaPipelineBackend> backend_; |
| 167 scoped_ptr<TaskRunnerImpl> backend_task_runner_; | 176 scoped_ptr<TaskRunnerImpl> backend_task_runner_; |
| 177 MediaPipelineBackend::AudioDecoder* decoder_; |
| 178 PushBufferCompletionCallback completion_cb_; |
| 179 bool first_start_; |
| 180 bool error_; |
| 181 CastDecoderBufferImpl backend_buffer_; |
| 168 base::ThreadChecker thread_checker_; | 182 base::ThreadChecker thread_checker_; |
| 169 DISALLOW_COPY_AND_ASSIGN(Backend); | 183 DISALLOW_COPY_AND_ASSIGN(Backend); |
| 170 }; | 184 }; |
| 171 | 185 |
| 172 // CastAudioOutputStream runs on audio thread (AudioManager::GetTaskRunner). | 186 // CastAudioOutputStream runs on audio thread (AudioManager::GetTaskRunner). |
| 173 CastAudioOutputStream::CastAudioOutputStream( | 187 CastAudioOutputStream::CastAudioOutputStream( |
| 174 const ::media::AudioParameters& audio_params, | 188 const ::media::AudioParameters& audio_params, |
| 175 CastAudioManager* audio_manager) | 189 CastAudioManager* audio_manager) |
| 176 : audio_params_(audio_params), | 190 : audio_params_(audio_params), |
| 177 audio_manager_(audio_manager), | 191 audio_manager_(audio_manager), |
| (...skipping 63 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 241 DCHECK(audio_task_runner_->BelongsToCurrentThread()); | 255 DCHECK(audio_task_runner_->BelongsToCurrentThread()); |
| 242 DCHECK(source_callback); | 256 DCHECK(source_callback); |
| 243 | 257 |
| 244 source_callback_ = source_callback; | 258 source_callback_ = source_callback; |
| 245 backend_task_runner_->PostTask( | 259 backend_task_runner_->PostTask( |
| 246 FROM_HERE, base::Bind(&Backend::Start, base::Unretained(backend_.get()))); | 260 FROM_HERE, base::Bind(&Backend::Start, base::Unretained(backend_.get()))); |
| 247 | 261 |
| 248 next_push_time_ = base::TimeTicks::Now(); | 262 next_push_time_ = base::TimeTicks::Now(); |
| 249 if (!push_in_progress_) { | 263 if (!push_in_progress_) { |
| 250 audio_task_runner_->PostTask(FROM_HERE, | 264 audio_task_runner_->PostTask(FROM_HERE, |
| 251 base::Bind(&CastAudioOutputStream::PushFrame, | 265 base::Bind(&CastAudioOutputStream::PushBuffer, |
| 252 weak_factory_.GetWeakPtr())); | 266 weak_factory_.GetWeakPtr())); |
| 253 push_in_progress_ = true; | 267 push_in_progress_ = true; |
| 254 } | 268 } |
| 255 | 269 |
| 256 metrics::CastMetricsHelper::GetInstance()->LogTimeToFirstAudio(); | 270 metrics::CastMetricsHelper::GetInstance()->LogTimeToFirstAudio(); |
| 257 } | 271 } |
| 258 | 272 |
| 259 void CastAudioOutputStream::Stop() { | 273 void CastAudioOutputStream::Stop() { |
| 260 DCHECK(audio_task_runner_->BelongsToCurrentThread()); | 274 DCHECK(audio_task_runner_->BelongsToCurrentThread()); |
| 261 | 275 |
| (...skipping 19 matching lines...) Expand all Loading... |
| 281 | 295 |
| 282 void CastAudioOutputStream::OnClosed() { | 296 void CastAudioOutputStream::OnClosed() { |
| 283 DCHECK(audio_task_runner_->BelongsToCurrentThread()); | 297 DCHECK(audio_task_runner_->BelongsToCurrentThread()); |
| 284 | 298 |
| 285 VLOG(1) << __FUNCTION__ << " : " << this; | 299 VLOG(1) << __FUNCTION__ << " : " << this; |
| 286 // Signal to the manager that we're closed and can be removed. | 300 // Signal to the manager that we're closed and can be removed. |
| 287 // This should be the last call in the function as it deletes "this". | 301 // This should be the last call in the function as it deletes "this". |
| 288 audio_manager_->ReleaseOutputStream(this); | 302 audio_manager_->ReleaseOutputStream(this); |
| 289 } | 303 } |
| 290 | 304 |
| 291 void CastAudioOutputStream::PushFrame() { | 305 void CastAudioOutputStream::PushBuffer() { |
| 292 DCHECK(audio_task_runner_->BelongsToCurrentThread()); | 306 DCHECK(audio_task_runner_->BelongsToCurrentThread()); |
| 293 DCHECK(push_in_progress_); | 307 DCHECK(push_in_progress_); |
| 294 | 308 |
| 295 if (!source_callback_) { | 309 if (!source_callback_) { |
| 296 push_in_progress_ = false; | 310 push_in_progress_ = false; |
| 297 return; | 311 return; |
| 298 } | 312 } |
| 299 | 313 |
| 300 uint32_t bytes_delay = 0; | 314 uint32_t bytes_delay = 0; |
| 301 int frame_count = source_callback_->OnMoreData(audio_bus_.get(), bytes_delay); | 315 int frame_count = source_callback_->OnMoreData(audio_bus_.get(), bytes_delay); |
| 302 VLOG(3) << "frames_filled=" << frame_count << " with latency=" << bytes_delay; | 316 VLOG(3) << "frames_filled=" << frame_count << " with latency=" << bytes_delay; |
| 303 | 317 |
| 304 DCHECK_EQ(frame_count, audio_bus_->frames()); | 318 DCHECK_EQ(frame_count, audio_bus_->frames()); |
| 305 DCHECK_EQ(static_cast<int>(decoder_buffer_->data_size()), | 319 DCHECK_EQ(static_cast<int>(decoder_buffer_->data_size()), |
| 306 frame_count * audio_params_.GetBytesPerFrame()); | 320 frame_count * audio_params_.GetBytesPerFrame()); |
| 307 audio_bus_->ToInterleaved(frame_count, audio_params_.bits_per_sample() / 8, | 321 audio_bus_->ToInterleaved(frame_count, audio_params_.bits_per_sample() / 8, |
| 308 decoder_buffer_->writable_data()); | 322 decoder_buffer_->writable_data()); |
| 309 | 323 |
| 310 auto completion_cb = ::media::BindToCurrentLoop(base::Bind( | 324 auto completion_cb = ::media::BindToCurrentLoop( |
| 311 &CastAudioOutputStream::OnPushFrameComplete, weak_factory_.GetWeakPtr())); | 325 base::Bind(&CastAudioOutputStream::OnPushBufferComplete, |
| 312 backend_task_runner_->PostTask( | 326 weak_factory_.GetWeakPtr())); |
| 313 FROM_HERE, | 327 backend_task_runner_->PostTask(FROM_HERE, |
| 314 base::Bind(&Backend::PushFrame, base::Unretained(backend_.get()), | 328 base::Bind(&Backend::PushBuffer, |
| 315 decoder_buffer_, completion_cb)); | 329 base::Unretained(backend_.get()), |
| 330 decoder_buffer_, |
| 331 completion_cb)); |
| 316 } | 332 } |
| 317 | 333 |
| 318 void CastAudioOutputStream::OnPushFrameComplete(bool success) { | 334 void CastAudioOutputStream::OnPushBufferComplete(bool success) { |
| 319 DCHECK(audio_task_runner_->BelongsToCurrentThread()); | 335 DCHECK(audio_task_runner_->BelongsToCurrentThread()); |
| 320 DCHECK(push_in_progress_); | 336 DCHECK(push_in_progress_); |
| 321 | 337 |
| 322 push_in_progress_ = false; | 338 push_in_progress_ = false; |
| 323 | 339 |
| 324 if (!source_callback_) { | 340 if (!source_callback_) { |
| 325 return; | 341 return; |
| 326 } | 342 } |
| 327 if (!success) { | 343 if (!success) { |
| 328 source_callback_->OnError(this); | 344 source_callback_->OnError(this); |
| 329 return; | 345 return; |
| 330 } | 346 } |
| 331 | 347 |
| 332 // Schedule next push frame. | 348 // Schedule next push buffer. |
| 333 // Need to account for time spent in pulling and pushing frame as well | 349 // Need to account for time spent in pulling and pushing buffer as well |
| 334 // as the imprecision of PostDelayedTask(). | 350 // as the imprecision of PostDelayedTask(). |
| 335 const base::TimeTicks now = base::TimeTicks::Now(); | 351 const base::TimeTicks now = base::TimeTicks::Now(); |
| 336 base::TimeDelta delay = next_push_time_ + buffer_duration_ - now; | 352 base::TimeDelta delay = next_push_time_ + buffer_duration_ - now; |
| 337 delay = std::max(delay, base::TimeDelta()); | 353 delay = std::max(delay, base::TimeDelta()); |
| 338 next_push_time_ = now + delay; | 354 next_push_time_ = now + delay; |
| 339 | 355 |
| 340 audio_task_runner_->PostDelayedTask( | 356 audio_task_runner_->PostDelayedTask( |
| 341 FROM_HERE, | 357 FROM_HERE, |
| 342 base::Bind(&CastAudioOutputStream::PushFrame, weak_factory_.GetWeakPtr()), | 358 base::Bind(&CastAudioOutputStream::PushBuffer, |
| 359 weak_factory_.GetWeakPtr()), |
| 343 delay); | 360 delay); |
| 344 push_in_progress_ = true; | 361 push_in_progress_ = true; |
| 345 } | 362 } |
| 346 | 363 |
| 347 } // namespace media | 364 } // namespace media |
| 348 } // namespace chromecast | 365 } // namespace chromecast |
| OLD | NEW |