Chromium Code Reviews| Index: content/renderer/media/mojo_audio_output_ipc.cc |
| diff --git a/content/renderer/media/mojo_audio_output_ipc.cc b/content/renderer/media/mojo_audio_output_ipc.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..00dfcfc5b526fd65a314c81fbdf4155c5a34db2c |
| --- /dev/null |
| +++ b/content/renderer/media/mojo_audio_output_ipc.cc |
| @@ -0,0 +1,241 @@ |
| +// Copyright 2017 The Chromium Authors. All rights reserved. |
| +// Use of this source code is governed by a BSD-style license that can be |
| +// found in the LICENSE file. |
| + |
| +#include "content/renderer/media/mojo_audio_output_ipc.h" |
| + |
| +#include <utility> |
| + |
| +#include "media/audio/audio_device_description.h" |
| +#include "mojo/public/cpp/system/platform_handle.h" |
| + |
| +namespace content { |
| + |
| +namespace { |
| + |
| +using AuthorizationCallback = |
| + mojom::RendererAudioOutputStreamFactory::RequestDeviceAuthorizationCallback; |
| + |
| +void TrivialAuthorizedCallback(media::OutputDeviceStatus, |
| + const media::AudioParameters&, |
| + const std::string&) {} |
| + |
| +// This class wraps a callback. If this class is destroyed without the callback |
| +// being called, it will call it with an error. This is needed since the |
| +// AudioOutputDevice could otherwise wait forever for device parameters in the |
| +// case of a connection error. |
| +class AuthorizationCallbackWrapper { |
| + public: |
| + static AuthorizationCallback Wrap(AuthorizationCallback callback) { |
| + return base::BindOnce( |
| + AuthorizationCallbackWrapper::Call, |
| + base::Passed(AuthorizationCallbackWrapper(std::move(callback)))); |
| + } |
| + |
| + AuthorizationCallbackWrapper(AuthorizationCallbackWrapper&& other) |
| + : callback_(std::move(other.callback_)) { |
| + // It's not explicitly stated that moving from a OnceCallback resets the |
| + // moved-from callback, so reset it here. |
| + other.callback_ = AuthorizationCallback(); |
| + } |
| + |
| + ~AuthorizationCallbackWrapper() { |
| + if (callback_) { |
| + std::move(callback_).Run( |
| + media::OutputDeviceStatus::OUTPUT_DEVICE_STATUS_ERROR_INTERNAL, |
| + media::AudioParameters::UnavailableDeviceParams(), std::string()); |
| + } |
| + } |
| + |
| + private: |
| + explicit AuthorizationCallbackWrapper(AuthorizationCallback callback) |
| + : callback_(std::move(callback)) {} |
| + |
| + static void Call(AuthorizationCallbackWrapper callback_wrapper, |
| + media::OutputDeviceStatus status, |
| + const media::AudioParameters& params, |
| + const std::string& device_id) { |
| + if (callback_wrapper.callback_) { |
| + std::move(callback_wrapper.callback_).Run(status, params, device_id); |
| + // Make sure we don't call again in destructor: |
| + callback_wrapper.callback_ = AuthorizationCallback(); |
| + } |
| + } |
| + |
| + AuthorizationCallback callback_; |
| + |
| + DISALLOW_COPY_AND_ASSIGN(AuthorizationCallbackWrapper); |
| +}; |
| + |
| +} // namespace |
| + |
| +MojoAudioOutputIPC::MojoAudioOutputIPC(FactoryAccessor factory_accessor) |
| + : factory_accessor_(std::move(factory_accessor)), weak_factory_(this) { |
| + DETACH_FROM_THREAD(thread_checker_); |
| +} |
| + |
| +MojoAudioOutputIPC::~MojoAudioOutputIPC() { |
| + DCHECK(!AuthorizationRequested() && !StreamCreationRequested()) |
| + << "CloseStream must be called before destructing the AudioOutputIPC"; |
| + // No thread check. |
| + // Destructing |weak_factory_| on any thread is safe since it's not used after |
| + // the final call to CloseStream, where its pointers are invalidated. |
| +} |
| + |
| +void MojoAudioOutputIPC::RequestDeviceAuthorization( |
| + media::AudioOutputIPCDelegate* delegate, |
| + int session_id, |
| + const std::string& device_id, |
| + const url::Origin& security_origin) { |
| + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| + DCHECK(delegate); |
| + DCHECK(!delegate_); |
| + DCHECK(!AuthorizationRequested()); |
| + DCHECK(!StreamCreationRequested()); |
| + delegate_ = delegate; |
| + |
| + // We wrap the callback here so that we are sure to always get the |
| + // authorization reply, even if the connection is closed. |
| + DoRequestDeviceAuthorization( |
| + session_id, device_id, |
| + AuthorizationCallbackWrapper::Wrap( |
|
DaleCurtis
2017/05/24 01:36:54
Hmm, why not just have a bool set here and then no
Max Morin
2017/05/30 14:17:11
MojoAudioOutputIPC is owned by the AudioOutputDevi
|
| + base::BindOnce(&MojoAudioOutputIPC::RecievedDeviceAuthorization, |
| + weak_factory_.GetWeakPtr()))); |
| +} |
| + |
| +void MojoAudioOutputIPC::CreateStream(media::AudioOutputIPCDelegate* delegate, |
| + const media::AudioParameters& params) { |
| + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| + DCHECK(delegate); |
| + DCHECK(!StreamCreationRequested()); |
| + if (!AuthorizationRequested()) { |
| + DCHECK(!delegate_); |
| + delegate_ = delegate; |
| + // No authorization requested yet. Request one for the default device. |
| + // Since the delegate didn't explicitly request authorization, we shouldn't |
| + // send a callback to it. |
| + if (!DoRequestDeviceAuthorization( |
| + 0, media::AudioDeviceDescription::kDefaultDeviceId, |
| + base::Bind(&TrivialAuthorizedCallback))) { |
| + return; |
| + } |
| + } |
| + |
| + DCHECK(delegate_ == delegate); |
|
DaleCurtis
2017/05/24 01:36:54
DCHECK_EQ
DaleCurtis
2017/05/24 01:36:54
DCHECK_EQ
Max Morin
2017/05/30 14:17:11
Done.
|
| + // Since the creation callback won't fire if the provider binding is gone |
| + // and |this| owns |stream_provider_|, unretained is safe. |
| + stream_provider_->Acquire( |
| + mojo::MakeRequest(&stream_), params, |
| + base::Bind(&MojoAudioOutputIPC::StreamCreated, base::Unretained(this))); |
| + |
| + // Unretained is safe because |delegate_| must remain valid until |
| + // CloseStream is called, and |stream_provider_| is reset in CloseStream. |
| + stream_.set_connection_error_handler(base::Bind( |
| + &media::AudioOutputIPCDelegate::OnError, base::Unretained(delegate_))); |
| +} |
| + |
| +void MojoAudioOutputIPC::PlayStream() { |
| + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| + if (stream_.is_bound()) |
| + stream_->Play(); |
| +} |
| + |
| +void MojoAudioOutputIPC::PauseStream() { |
| + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| + if (stream_.is_bound()) |
| + stream_->Pause(); |
| +} |
| + |
| +void MojoAudioOutputIPC::CloseStream() { |
| + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| + stream_provider_.reset(); |
| + stream_.reset(); |
| + delegate_ = nullptr; |
| + |
| + // Cancel any pending callbacks for this stream. |
|
DaleCurtis
2017/05/24 01:36:54
Per above, if you use a bool you'd want to notify
Max Morin
2017/05/30 14:17:11
There could be a sequence of events like "RequestD
|
| + weak_factory_.InvalidateWeakPtrs(); |
| +} |
| + |
| +void MojoAudioOutputIPC::SetVolume(double volume) { |
| + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| + if (stream_.is_bound()) |
| + stream_->SetVolume(volume); |
| +} |
| + |
| +bool MojoAudioOutputIPC::AuthorizationRequested() { |
| + return stream_provider_.is_bound(); |
| +} |
| + |
| +bool MojoAudioOutputIPC::StreamCreationRequested() { |
| + return stream_.is_bound(); |
| +} |
| + |
| +media::mojom::AudioOutputStreamProviderRequest |
| +MojoAudioOutputIPC::MakeProviderRequest() { |
| + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| + DCHECK(!AuthorizationRequested()); |
| + media::mojom::AudioOutputStreamProviderRequest request = |
| + mojo::MakeRequest(&stream_provider_); |
| + |
| + // Unretained is safe because |delegate_| must remain valid until |
| + // CloseStream is called, and |stream_provider_| is reset in CloseStream. |
| + stream_provider_.set_connection_error_handler(base::Bind( |
| + &media::AudioOutputIPCDelegate::OnError, base::Unretained(delegate_))); |
| + return request; |
| +} |
| + |
| +bool MojoAudioOutputIPC::DoRequestDeviceAuthorization( |
| + int session_id, |
| + const std::string& device_id, |
| + AuthorizationCallback callback) { |
| + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| + auto* factory = factory_accessor_.Run(); |
| + if (!factory) { |
| + LOG(ERROR) << "MojoAudioOutputIPC failed to acquire factory"; |
| + |
| + media::AudioOutputIPCDelegate* delegate = delegate_; |
| + CloseStream(); |
| + delegate->OnIPCClosed(); // deletes |this|. |
| + return false; |
| + } |
| + |
| + // We wrap the callback here so that we are sure to always get the |
|
DaleCurtis
2017/05/24 01:36:54
No wrapping done here?
Max Morin
2017/05/30 14:17:10
Done.
|
| + // authorization reply, even if the connection is closed. |
| + factory->RequestDeviceAuthorization(MakeProviderRequest(), session_id, |
| + device_id, std::move(callback)); |
| + return true; |
| +} |
| + |
| +void MojoAudioOutputIPC::RecievedDeviceAuthorization( |
|
DaleCurtis
2017/05/24 01:36:54
Received
Max Morin
2017/05/30 14:17:11
Done.
|
| + media::OutputDeviceStatus status, |
| + const media::AudioParameters& params, |
| + const std::string& device_id) const { |
| + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| + DCHECK(delegate_); |
| + delegate_->OnDeviceAuthorized(status, params, device_id); |
| +} |
| + |
| +void MojoAudioOutputIPC::StreamCreated( |
| + mojo::ScopedSharedBufferHandle shared_memory, |
| + mojo::ScopedHandle socket) { |
| + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| + DCHECK(delegate_); |
| + DCHECK(socket.is_valid()); |
| + DCHECK(shared_memory.is_valid()); |
| + |
| + base::PlatformFile socket_handle; |
| + auto result = mojo::UnwrapPlatformFile(std::move(socket), &socket_handle); |
| + DCHECK_EQ(result, MOJO_RESULT_OK); |
| + |
| + base::SharedMemoryHandle memory_handle; |
| + bool read_only = false; |
| + size_t memory_length = 0; |
| + result = mojo::UnwrapSharedMemoryHandle( |
| + std::move(shared_memory), &memory_handle, &memory_length, &read_only); |
| + DCHECK_EQ(result, MOJO_RESULT_OK); |
| + DCHECK(!read_only); |
| + |
| + delegate_->OnStreamCreated(memory_handle, socket_handle, memory_length); |
| +} |
| + |
| +} // namespace content |