blob: e8ab7a5653b681cbd1762f6a82147af2da533ad9 [file] [log] [blame]
// Copyright 2013 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 "chrome/browser/extensions/api/webrtc_audio_private/webrtc_audio_private_api.h"
#include "base/lazy_instance.h"
#include "base/message_loop/message_loop_proxy.h"
#include "base/strings/string_number_conversions.h"
#include "base/task_runner_util.h"
#include "chrome/browser/extensions/api/tabs/tabs_constants.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/extensions/extension_tab_util.h"
#include "content/public/browser/media_device_id.h"
#include "content/public/browser/web_contents.h"
#include "extensions/browser/event_router.h"
#include "extensions/common/error_utils.h"
#include "media/audio/audio_manager_base.h"
#include "media/audio/audio_output_controller.h"
namespace extensions {
using content::BrowserThread;
using content::RenderViewHost;
using media::AudioDeviceNames;
using media::AudioManager;
namespace wap = api::webrtc_audio_private;
static base::LazyInstance<
ProfileKeyedAPIFactory<WebrtcAudioPrivateEventService> > g_factory =
LAZY_INSTANCE_INITIALIZER;
WebrtcAudioPrivateEventService::WebrtcAudioPrivateEventService(
Profile* profile) : profile_(profile) {
// In unit tests, the SystemMonitor may not be created.
base::SystemMonitor* system_monitor = base::SystemMonitor::Get();
if (system_monitor)
system_monitor->AddDevicesChangedObserver(this);
}
WebrtcAudioPrivateEventService::~WebrtcAudioPrivateEventService() {
}
void WebrtcAudioPrivateEventService::Shutdown() {
// In unit tests, the SystemMonitor may not be created.
base::SystemMonitor* system_monitor = base::SystemMonitor::Get();
if (system_monitor)
system_monitor->RemoveDevicesChangedObserver(this);
}
// static
ProfileKeyedAPIFactory<WebrtcAudioPrivateEventService>*
WebrtcAudioPrivateEventService::GetFactoryInstance() {
return &g_factory.Get();
}
// static
const char* WebrtcAudioPrivateEventService::service_name() {
return "WebrtcAudioPrivateEventService";
}
void WebrtcAudioPrivateEventService::OnDevicesChanged(
base::SystemMonitor::DeviceType device_type) {
switch (device_type) {
case base::SystemMonitor::DEVTYPE_AUDIO_CAPTURE:
case base::SystemMonitor::DEVTYPE_VIDEO_CAPTURE:
SignalEvent();
break;
default:
// No action needed.
break;
}
}
void WebrtcAudioPrivateEventService::SignalEvent() {
using api::webrtc_audio_private::OnSinksChanged::kEventName;
EventRouter* router =
ExtensionSystem::Get(profile_)->event_router();
if (!router || !router->HasEventListener(kEventName))
return;
ExtensionService* extension_service =
ExtensionSystem::Get(profile_)->extension_service();
const ExtensionSet* extensions = extension_service->extensions();
for (ExtensionSet::const_iterator it = extensions->begin();
it != extensions->end(); ++it) {
const std::string& extension_id = (*it)->id();
if (router->ExtensionHasEventListener(extension_id, kEventName) &&
(*it)->HasAPIPermission("webrtcAudioPrivate")) {
scoped_ptr<Event> event(
new Event(kEventName, make_scoped_ptr(new ListValue()).Pass()));
router->DispatchEventToExtension(extension_id, event.Pass());
}
}
}
bool WebrtcAudioPrivateGetSinksFunction::RunImpl() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
AudioManager::Get()->GetMessageLoop()->PostTaskAndReply(
FROM_HERE,
base::Bind(&WebrtcAudioPrivateGetSinksFunction::DoQuery, this),
base::Bind(&WebrtcAudioPrivateGetSinksFunction::DoneOnUIThread, this));
return true;
}
void WebrtcAudioPrivateGetSinksFunction::DoQuery() {
DCHECK(AudioManager::Get()->GetMessageLoop()->BelongsToCurrentThread());
AudioDeviceNames device_names;
AudioManager::Get()->GetAudioOutputDeviceNames(&device_names);
std::vector<linked_ptr<wap::SinkInfo> > results;
for (AudioDeviceNames::const_iterator it = device_names.begin();
it != device_names.end();
++it) {
linked_ptr<wap::SinkInfo> info(new wap::SinkInfo);
info->sink_id = it->unique_id;
info->sink_label = it->device_name;
// TODO(joi): Add other parameters.
results.push_back(info);
}
// It's safe to directly set the results here (from a thread other
// than the UI thread, on which an AsyncExtensionFunction otherwise
// normally runs) because there is one instance of this object per
// function call, no actor outside of this object is modifying the
// results_ member, and the different method invocations on this
// object run strictly in sequence; first RunImpl on the UI thread,
// then DoQuery on the audio IO thread, then DoneOnUIThread on the
// UI thread.
results_.reset(wap::GetSinks::Results::Create(results).release());
}
void WebrtcAudioPrivateGetSinksFunction::DoneOnUIThread() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
SendResponse(true);
}
bool WebrtcAudioPrivateTabIdFunction::DoRunImpl(int tab_id) {
content::WebContents* contents = NULL;
if (!ExtensionTabUtil::GetTabById(
tab_id, GetProfile(), true, NULL, NULL, &contents, NULL)) {
error_ = extensions::ErrorUtils::FormatErrorMessage(
extensions::tabs_constants::kTabNotFoundError,
base::IntToString(tab_id));
return false;
}
RenderViewHost* rvh = contents->GetRenderViewHost();
if (!rvh)
return false;
rvh->GetAudioOutputControllers(base::Bind(
&WebrtcAudioPrivateTabIdFunction::OnControllerList, this));
return true;
}
bool WebrtcAudioPrivateGetActiveSinkFunction::RunImpl() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
scoped_ptr<wap::GetActiveSink::Params> params(
wap::GetActiveSink::Params::Create(*args_));
EXTENSION_FUNCTION_VALIDATE(params.get());
return DoRunImpl(params->tab_id);
}
void WebrtcAudioPrivateGetActiveSinkFunction::OnControllerList(
const RenderViewHost::AudioOutputControllerList& controllers) {
if (controllers.empty()) {
// If there is no current audio stream for the rvh, we return an
// empty string as the sink ID.
DVLOG(2) << "chrome.webrtcAudioPrivate.getActiveSink: No controllers.";
results_.reset(
wap::GetActiveSink::Results::Create(std::string()).release());
SendResponse(true);
} else {
DVLOG(2) << "chrome.webrtcAudioPrivate.getActiveSink: "
<< controllers.size() << " controllers.";
// TODO(joi): Debug-only, DCHECK that all items have the same ID.
(*controllers.begin())->GetOutputDeviceId(
base::Bind(&WebrtcAudioPrivateGetActiveSinkFunction::OnSinkId, this));
}
}
void WebrtcAudioPrivateGetActiveSinkFunction::OnSinkId(const std::string& id) {
std::string result = id;
if (result.empty()) {
DVLOG(2) << "Received empty ID, replacing with default ID.";
result = media::AudioManagerBase::kDefaultDeviceId;
}
results_.reset(wap::GetActiveSink::Results::Create(result).release());
SendResponse(true);
}
WebrtcAudioPrivateSetActiveSinkFunction::
WebrtcAudioPrivateSetActiveSinkFunction()
: message_loop_(base::MessageLoopProxy::current()),
tab_id_(0),
num_remaining_sink_ids_(0) {
}
WebrtcAudioPrivateSetActiveSinkFunction::
~WebrtcAudioPrivateSetActiveSinkFunction() {
}
bool WebrtcAudioPrivateSetActiveSinkFunction::RunImpl() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
scoped_ptr<wap::SetActiveSink::Params> params(
wap::SetActiveSink::Params::Create(*args_));
EXTENSION_FUNCTION_VALIDATE(params.get());
tab_id_ = params->tab_id;
sink_id_ = params->sink_id;
if (sink_id_ == media::AudioManagerBase::kDefaultDeviceId) {
DVLOG(2) << "Received default ID, replacing with empty ID.";
sink_id_ = "";
}
return DoRunImpl(tab_id_);
}
void WebrtcAudioPrivateSetActiveSinkFunction::OnControllerList(
const RenderViewHost::AudioOutputControllerList& controllers) {
num_remaining_sink_ids_ = controllers.size();
if (num_remaining_sink_ids_ == 0) {
error_ = extensions::ErrorUtils::FormatErrorMessage(
"No active stream for tab with id: *.",
base::IntToString(tab_id_));
SendResponse(false);
} else {
RenderViewHost::AudioOutputControllerList::const_iterator it =
controllers.begin();
for (; it != controllers.end(); ++it) {
(*it)->SwitchOutputDevice(sink_id_, base::Bind(
&WebrtcAudioPrivateSetActiveSinkFunction::SwitchDone, this));
}
}
}
void WebrtcAudioPrivateSetActiveSinkFunction::SwitchDone() {
if (--num_remaining_sink_ids_ == 0) {
message_loop_->PostTask(
FROM_HERE,
base::Bind(&WebrtcAudioPrivateSetActiveSinkFunction::DoneOnUIThread,
this));
}
}
void WebrtcAudioPrivateSetActiveSinkFunction::DoneOnUIThread() {
SendResponse(true);
}
WebrtcAudioPrivateGetAssociatedSinkFunction::
WebrtcAudioPrivateGetAssociatedSinkFunction() {
}
WebrtcAudioPrivateGetAssociatedSinkFunction::
~WebrtcAudioPrivateGetAssociatedSinkFunction() {
}
bool WebrtcAudioPrivateGetAssociatedSinkFunction::RunImpl() {
params_ = wap::GetAssociatedSink::Params::Create(*args_);
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
EXTENSION_FUNCTION_VALIDATE(params_.get());
AudioManager::Get()->GetMessageLoop()->PostTaskAndReply(
FROM_HERE,
base::Bind(&WebrtcAudioPrivateGetAssociatedSinkFunction::
GetDevicesOnDeviceThread, this),
base::Bind(
&WebrtcAudioPrivateGetAssociatedSinkFunction::OnGetDevicesDone,
this));
return true;
}
void WebrtcAudioPrivateGetAssociatedSinkFunction::GetDevicesOnDeviceThread() {
DCHECK(AudioManager::Get()->GetMessageLoop()->BelongsToCurrentThread());
AudioManager::Get()->GetAudioInputDeviceNames(&source_devices_);
}
void WebrtcAudioPrivateGetAssociatedSinkFunction::OnGetDevicesDone() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
BrowserThread::PostTaskAndReplyWithResult(
BrowserThread::IO,
FROM_HERE,
base::Bind(&WebrtcAudioPrivateGetAssociatedSinkFunction::
GetRawSourceIDOnIOThread,
this,
GetProfile()->GetResourceContext(),
GURL(params_->security_origin),
params_->source_id_in_origin),
base::Bind(
&WebrtcAudioPrivateGetAssociatedSinkFunction::OnGetRawSourceIDDone,
this));
}
std::string
WebrtcAudioPrivateGetAssociatedSinkFunction::GetRawSourceIDOnIOThread(
content::ResourceContext* context,
GURL security_origin,
const std::string& source_id_in_origin) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
// Find the raw source ID for source_id_in_origin.
std::string raw_source_id;
for (AudioDeviceNames::const_iterator it = source_devices_.begin();
it != source_devices_.end();
++it) {
const std::string& id = it->unique_id;
if (content::DoesMediaDeviceIDMatchHMAC(
context->GetMediaDeviceIDSalt(),
security_origin,
source_id_in_origin,
id)) {
raw_source_id = id;
DVLOG(2) << "Found raw ID " << raw_source_id
<< " for source ID in origin " << source_id_in_origin;
break;
}
}
return raw_source_id;
}
void WebrtcAudioPrivateGetAssociatedSinkFunction::OnGetRawSourceIDDone(
const std::string& raw_source_id) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
base::PostTaskAndReplyWithResult(
AudioManager::Get()->GetMessageLoop(),
FROM_HERE,
base::Bind(&WebrtcAudioPrivateGetAssociatedSinkFunction::
GetAssociatedSinkOnDeviceThread,
this,
raw_source_id),
base::Bind(&WebrtcAudioPrivateGetAssociatedSinkFunction::
OnGetAssociatedSinkDone,
this));
}
std::string
WebrtcAudioPrivateGetAssociatedSinkFunction::GetAssociatedSinkOnDeviceThread(
const std::string& raw_source_id) {
DCHECK(AudioManager::Get()->GetMessageLoop()->BelongsToCurrentThread());
// We return an empty string if there is no associated output device.
std::string result;
if (!raw_source_id.empty()) {
result = AudioManager::Get()->GetAssociatedOutputDeviceID(raw_source_id);
}
return result;
}
void WebrtcAudioPrivateGetAssociatedSinkFunction::OnGetAssociatedSinkDone(
const std::string& associated_sink_id) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
results_.reset(
wap::GetAssociatedSink::Results::Create(associated_sink_id).release());
SendResponse(true);
}
} // namespace extensions