summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--content/browser/media/media_internals.cc287
-rw-r--r--content/browser/media/media_internals.h106
-rw-r--r--content/browser/media/media_internals_handler.cc9
-rw-r--r--content/browser/media/media_internals_handler.h4
-rw-r--r--content/browser/media/media_internals_unittest.cc253
-rw-r--r--content/browser/renderer_host/media/audio_renderer_host.cc31
-rw-r--r--content/browser/renderer_host/media/audio_renderer_host.h3
-rw-r--r--content/browser/resources/media/client_renderer.js140
-rw-r--r--content/browser/resources/media/main.js33
-rw-r--r--content/browser/resources/media/manager.js52
-rw-r--r--content/browser/resources/media/media_internals.css47
-rw-r--r--content/browser/resources/media/media_internals.html26
-rw-r--r--content/test/data/media/webui/integration_test.html46
-rw-r--r--media/audio/audio_logging.h84
14 files changed, 630 insertions, 491 deletions
diff --git a/content/browser/media/media_internals.cc b/content/browser/media/media_internals.cc
index 22f164d..9f420e8 100644
--- a/content/browser/media/media_internals.cc
+++ b/content/browser/media/media_internals.cc
@@ -4,7 +4,6 @@
#include "content/browser/media/media_internals.h"
-#include "base/memory/scoped_ptr.h"
#include "base/strings/string16.h"
#include "base/strings/stringprintf.h"
#include "content/public/browser/browser_thread.h"
@@ -13,120 +12,169 @@
#include "media/base/media_log.h"
#include "media/base/media_log_event.h"
-namespace content {
+namespace {
-MediaInternals* MediaInternals::GetInstance() {
- return Singleton<MediaInternals>::get();
+static base::LazyInstance<content::MediaInternals>::Leaky g_media_internals =
+ LAZY_INSTANCE_INITIALIZER;
+
+string16 SerializeUpdate(const std::string& function,
+ const base::Value* value) {
+ return content::WebUI::GetJavascriptCall(
+ function, std::vector<const base::Value*>(1, value));
}
-MediaInternals::~MediaInternals() {}
+const char kAudioLogStatusKey[] = "status";
+const char kAudioLogUpdateFunction[] = "media.updateAudioComponent";
-namespace {
-std::string FormatAudioStreamName(void* host, int stream_id) {
- return base::StringPrintf("audio_streams.%p:%d", host, stream_id);
-}
-}
+} // namespace
-void MediaInternals::OnDeleteAudioStream(void* host, int stream_id) {
- DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
- DeleteItem(FormatAudioStreamName(host, stream_id));
-}
+namespace content {
-void MediaInternals::OnSetAudioStreamPlaying(
- void* host, int stream_id, bool playing) {
- DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
- UpdateAudioStream(host, stream_id,
- "playing", new base::FundamentalValue(playing));
-}
+class AudioLogImpl : public media::AudioLog {
+ public:
+ AudioLogImpl(int owner_id,
+ media::AudioLogFactory::AudioComponent component,
+ content::MediaInternals* media_internals);
+ virtual ~AudioLogImpl();
+
+ virtual void OnCreated(int component_id,
+ const media::AudioParameters& params,
+ const std::string& input_device_id,
+ const std::string& output_device_id) OVERRIDE;
+ virtual void OnStarted(int component_id) OVERRIDE;
+ virtual void OnStopped(int component_id) OVERRIDE;
+ virtual void OnClosed(int component_id) OVERRIDE;
+ virtual void OnError(int component_id) OVERRIDE;
+ virtual void OnSetVolume(int component_id, double volume) OVERRIDE;
-void MediaInternals::OnAudioStreamCreated(void* host,
- int stream_id,
- const media::AudioParameters& params,
- const std::string& input_device_id) {
- DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ private:
+ void SendSingleStringUpdate(int component_id,
+ const std::string& key,
+ const std::string& value);
+ void StoreComponentMetadata(int component_id, base::DictionaryValue* dict);
+ std::string FormatCacheKey(int component_id);
- StoreAudioStream(host,
- stream_id,
- "input_device_id",
- Value::CreateStringValue(input_device_id));
+ const int owner_id_;
+ const media::AudioLogFactory::AudioComponent component_;
+ content::MediaInternals* const media_internals_;
- StoreAudioStream(
- host, stream_id, "status", Value::CreateStringValue("created"));
+ DISALLOW_COPY_AND_ASSIGN(AudioLogImpl);
+};
- StoreAudioStream(
- host, stream_id, "stream_id", Value::CreateIntegerValue(stream_id));
+AudioLogImpl::AudioLogImpl(int owner_id,
+ media::AudioLogFactory::AudioComponent component,
+ content::MediaInternals* media_internals)
+ : owner_id_(owner_id),
+ component_(component),
+ media_internals_(media_internals) {}
- StoreAudioStream(host,
- stream_id,
- "input_channels",
- Value::CreateIntegerValue(params.input_channels()));
+AudioLogImpl::~AudioLogImpl() {}
- StoreAudioStream(host,
- stream_id,
- "frames_per_buffer",
- Value::CreateIntegerValue(params.frames_per_buffer()));
+void AudioLogImpl::OnCreated(int component_id,
+ const media::AudioParameters& params,
+ const std::string& input_device_id,
+ const std::string& output_device_id) {
+ base::DictionaryValue dict;
+ StoreComponentMetadata(component_id, &dict);
- StoreAudioStream(host,
- stream_id,
- "sample_rate",
- Value::CreateIntegerValue(params.sample_rate()));
+ dict.SetString(kAudioLogStatusKey, "created");
+ dict.SetString("input_device_id", input_device_id);
+ dict.SetInteger("input_channels", params.input_channels());
+ dict.SetInteger("frames_per_buffer", params.frames_per_buffer());
+ dict.SetInteger("sample_rate", params.sample_rate());
+ dict.SetString("output_device_id", output_device_id);
+ dict.SetInteger("output_channels", params.channels());
+ dict.SetString("output_channel_layout",
+ ChannelLayoutToString(params.channel_layout()));
- StoreAudioStream(host,
- stream_id,
- "output_channels",
- Value::CreateIntegerValue(params.channels()));
+ media_internals_->SendUpdateAndCache(
+ FormatCacheKey(component_id), kAudioLogUpdateFunction, &dict);
+}
- StoreAudioStream(
- host,
- stream_id,
- "channel_layout",
- Value::CreateStringValue(ChannelLayoutToString(params.channel_layout())));
+void AudioLogImpl::OnStarted(int component_id) {
+ SendSingleStringUpdate(component_id, kAudioLogStatusKey, "started");
+}
- SendEverything();
+void AudioLogImpl::OnStopped(int component_id) {
+ SendSingleStringUpdate(component_id, kAudioLogStatusKey, "stopped");
}
-void MediaInternals::OnSetAudioStreamStatus(void* host,
- int stream_id,
- const std::string& status) {
- DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
- UpdateAudioStream(host, stream_id,
- "status", new base::StringValue(status));
+void AudioLogImpl::OnClosed(int component_id) {
+ base::DictionaryValue dict;
+ StoreComponentMetadata(component_id, &dict);
+ dict.SetString(kAudioLogStatusKey, "closed");
+ media_internals_->SendUpdateAndPurgeCache(
+ FormatCacheKey(component_id), kAudioLogUpdateFunction, &dict);
}
-void MediaInternals::OnSetAudioStreamVolume(
- void* host, int stream_id, double volume) {
- DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
- UpdateAudioStream(host, stream_id,
- "volume", new base::FundamentalValue(volume));
+void AudioLogImpl::OnError(int component_id) {
+ SendSingleStringUpdate(component_id, "error_occurred", "true");
+}
+
+void AudioLogImpl::OnSetVolume(int component_id, double volume) {
+ base::DictionaryValue dict;
+ StoreComponentMetadata(component_id, &dict);
+ dict.SetDouble("volume", volume);
+ media_internals_->SendUpdateAndCache(
+ FormatCacheKey(component_id), kAudioLogUpdateFunction, &dict);
+}
+
+std::string AudioLogImpl::FormatCacheKey(int component_id) {
+ return base::StringPrintf("%d:%d:%d", owner_id_, component_, component_id);
+}
+
+void AudioLogImpl::SendSingleStringUpdate(int component_id,
+ const std::string& key,
+ const std::string& value) {
+ base::DictionaryValue dict;
+ StoreComponentMetadata(component_id, &dict);
+ dict.SetString(key, value);
+ media_internals_->SendUpdateAndCache(
+ FormatCacheKey(component_id), kAudioLogUpdateFunction, &dict);
}
+void AudioLogImpl::StoreComponentMetadata(int component_id,
+ base::DictionaryValue* dict) {
+ dict->SetInteger("owner_id", owner_id_);
+ dict->SetInteger("component_id", component_id);
+ dict->SetInteger("component_type", component_);
+}
+
+MediaInternals* MediaInternals::GetInstance() {
+ return g_media_internals.Pointer();
+}
+
+MediaInternals::MediaInternals() : owner_ids_() {}
+MediaInternals::~MediaInternals() {}
+
void MediaInternals::OnMediaEvents(
int render_process_id, const std::vector<media::MediaLogEvent>& events) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
-
- // Notify observers that |event| has occured.
+ // Notify observers that |event| has occurred.
for (std::vector<media::MediaLogEvent>::const_iterator event = events.begin();
- event != events.end(); ++event) {
+ event != events.end(); ++event) {
base::DictionaryValue dict;
dict.SetInteger("renderer", render_process_id);
dict.SetInteger("player", event->id);
dict.SetString("type", media::MediaLog::EventTypeToString(event->type));
- int64 ticks = event->time.ToInternalValue();
- double ticks_millis =
- ticks / static_cast<double>(base::Time::kMicrosecondsPerMillisecond);
-
+ // TODO(dalecurtis): This is technically not correct. TimeTicks "can't" be
+ // converted to to a human readable time format. See base/time/time.h.
+ const double ticks = event->time.ToInternalValue();
+ const double ticks_millis = ticks / base::Time::kMicrosecondsPerMillisecond;
dict.SetDouble("ticksMillis", ticks_millis);
dict.Set("params", event->params.DeepCopy());
- SendUpdate("media.onMediaEvent", &dict);
+ SendUpdate(SerializeUpdate("media.onMediaEvent", &dict));
}
}
void MediaInternals::AddUpdateCallback(const UpdateCallback& callback) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
update_callbacks_.push_back(callback);
}
void MediaInternals::RemoveUpdateCallback(const UpdateCallback& callback) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
for (size_t i = 0; i < update_callbacks_.size(); ++i) {
if (update_callbacks_[i].Equals(callback)) {
update_callbacks_.erase(update_callbacks_.begin() + i);
@@ -137,68 +185,61 @@ void MediaInternals::RemoveUpdateCallback(const UpdateCallback& callback) {
}
void MediaInternals::SendEverything() {
- DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
- SendUpdate("media.onReceiveEverything", &data_);
+ string16 everything_update;
+ {
+ base::AutoLock auto_lock(lock_);
+ everything_update = SerializeUpdate(
+ "media.onReceiveEverything", &cached_data_);
+ }
+ SendUpdate(everything_update);
}
-MediaInternals::MediaInternals() {
-}
+void MediaInternals::SendUpdate(const string16& update) {
+ // SendUpdate() may be called from any thread, but must run on the IO thread.
+ // TODO(dalecurtis): This is pretty silly since the update callbacks simply
+ // forward the calls to the UI thread. We should avoid the extra hop.
+ if (!BrowserThread::CurrentlyOn(BrowserThread::IO)) {
+ BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, base::Bind(
+ &MediaInternals::SendUpdate, base::Unretained(this), update));
+ return;
+ }
-void MediaInternals::StoreAudioStream(void* host,
- int stream_id,
- const std::string& property,
- base::Value* value) {
- StoreItem(FormatAudioStreamName(host, stream_id), property, value);
+ for (size_t i = 0; i < update_callbacks_.size(); i++)
+ update_callbacks_[i].Run(update);
}
-void MediaInternals::UpdateAudioStream(void* host,
- int stream_id,
- const std::string& property,
- base::Value* value) {
- UpdateItem("media.updateAudioStream",
- FormatAudioStreamName(host, stream_id),
- property,
- value);
+scoped_ptr<media::AudioLog> MediaInternals::CreateAudioLog(
+ AudioComponent component) {
+ base::AutoLock auto_lock(lock_);
+ return scoped_ptr<media::AudioLog>(new AudioLogImpl(
+ owner_ids_[component]++, component, this));
}
-void MediaInternals::DeleteItem(const std::string& item) {
- data_.Remove(item, NULL);
- scoped_ptr<base::Value> value(new base::StringValue(item));
- SendUpdate("media.onItemDeleted", value.get());
-}
+void MediaInternals::SendUpdateAndCache(const std::string& cache_key,
+ const std::string& function,
+ const base::DictionaryValue* value) {
+ SendUpdate(SerializeUpdate(function, value));
-base::DictionaryValue* MediaInternals::StoreItem(const std::string& id,
- const std::string& property,
- base::Value* value) {
- base::DictionaryValue* item_properties;
- if (!data_.GetDictionary(id, &item_properties)) {
- item_properties = new base::DictionaryValue();
- data_.Set(id, item_properties);
- item_properties->SetString("id", id);
+ base::AutoLock auto_lock(lock_);
+ if (!cached_data_.HasKey(cache_key)) {
+ cached_data_.Set(cache_key, value->DeepCopy());
+ return;
}
- item_properties->Set(property, value);
- return item_properties;
-}
-void MediaInternals::UpdateItem(const std::string& update_fn,
- const std::string& id,
- const std::string& property,
- base::Value* value) {
- base::DictionaryValue* item_properties = StoreItem(id, property, value);
- SendUpdate(update_fn, item_properties);
+ base::DictionaryValue* existing_dict = NULL;
+ CHECK(cached_data_.GetDictionary(cache_key, &existing_dict));
+ existing_dict->MergeDictionary(value);
}
-void MediaInternals::SendUpdate(const std::string& function,
- base::Value* value) {
- // Only bother serializing the update to JSON if someone is watching.
- if (update_callbacks_.empty())
- return;
+void MediaInternals::SendUpdateAndPurgeCache(
+ const std::string& cache_key,
+ const std::string& function,
+ const base::DictionaryValue* value) {
+ SendUpdate(SerializeUpdate(function, value));
- std::vector<const base::Value*> args;
- args.push_back(value);
- string16 update = WebUI::GetJavascriptCall(function, args);
- for (size_t i = 0; i < update_callbacks_.size(); i++)
- update_callbacks_[i].Run(update);
+ base::AutoLock auto_lock(lock_);
+ scoped_ptr<base::Value> out_value;
+ CHECK(cached_data_.Remove(cache_key, &out_value));
}
} // namespace content
diff --git a/content/browser/media/media_internals.h b/content/browser/media/media_internals.h
index 24b314e..8a179e0 100644
--- a/content/browser/media/media_internals.h
+++ b/content/browser/media/media_internals.h
@@ -8,12 +8,14 @@
#include <string>
#include <vector>
-#include "base/memory/ref_counted.h"
-#include "base/memory/singleton.h"
+#include "base/callback_forward.h"
+#include "base/compiler_specific.h"
+#include "base/lazy_instance.h"
#include "base/strings/string16.h"
+#include "base/synchronization/lock.h"
#include "base/values.h"
#include "content/common/content_export.h"
-#include "content/public/common/media_stream_request.h"
+#include "media/audio/audio_logging.h"
namespace media {
class AudioParameters;
@@ -23,90 +25,60 @@ struct MediaLogEvent;
namespace content {
// This class stores information about currently active media.
-// It's constructed on the UI thread but all of its methods are called on the IO
-// thread.
-class CONTENT_EXPORT MediaInternals {
+class CONTENT_EXPORT MediaInternals
+ : NON_EXPORTED_BASE(public media::AudioLogFactory) {
public:
- virtual ~MediaInternals();
-
static MediaInternals* GetInstance();
- // The following methods are virtual for gmock.
-
- // Called when an audio stream is deleted.
- virtual void OnDeleteAudioStream(void* host, int stream_id);
-
- // Called when an audio stream is set to playing or paused.
- virtual void OnSetAudioStreamPlaying(void* host, int stream_id, bool playing);
-
- // Called when an audio stream is created with the parameters that
- // it attempts to use to make the stream.
- virtual void OnAudioStreamCreated(void* host,
- int stream_id,
- const media::AudioParameters& params,
- const std::string& input_device_id);
-
- // Called when the status of an audio stream is set to "created", "closed", or
- // "error".
- virtual void OnSetAudioStreamStatus(void* host, int stream_id,
- const std::string& status);
-
- // Called when the volume of an audio stream is set.
- virtual void OnSetAudioStreamVolume(void* host, int stream_id,
- double volume);
+ virtual ~MediaInternals();
// Called when a MediaEvent occurs.
- virtual void OnMediaEvents(int render_process_id,
- const std::vector<media::MediaLogEvent>& events);
+ void OnMediaEvents(int render_process_id,
+ const std::vector<media::MediaLogEvent>& events);
// Called with the update string.
typedef base::Callback<void(const string16&)> UpdateCallback;
- // Add/remove update callbacks (see above).
+ // Add/remove update callbacks (see above). Must be called on the IO thread.
void AddUpdateCallback(const UpdateCallback& callback);
void RemoveUpdateCallback(const UpdateCallback& callback);
+
+ // Sends all cached data to each registered UpdateCallback.
void SendEverything();
+ // AudioLogFactory implementation. Safe to call from any thread.
+ virtual scoped_ptr<media::AudioLog> CreateAudioLog(
+ AudioComponent component) OVERRIDE;
+
private:
- friend class MockMediaInternals;
+ friend class AudioLogImpl;
friend class MediaInternalsTest;
- friend struct DefaultSingletonTraits<MediaInternals>;
+ friend struct base::DefaultLazyInstanceTraits<MediaInternals>;
MediaInternals();
- // Sets |property| of an audio stream to |value| and notifies observers.
- // (host, stream_id) is a unique id for the audio stream.
- // |host| will never be dereferenced.
- void UpdateAudioStream(void* host, int stream_id,
- const std::string& property, base::Value* value);
-
- // See UpdateAudioStream. The difference is that StoreAudioStream does not
- // immediately send the data to the client, instead it waits until
- // SendEverything is called.
- void StoreAudioStream(void* host,
- int stream_id,
- const std::string& property,
- base::Value* value);
-
- // Removes |item| from |data_|.
- void DeleteItem(const std::string& item);
-
- base::DictionaryValue* StoreItem(const std::string& id,
- const std::string& property,
- base::Value* value);
-
- // Sets data_.id.property = value and notifies attached UIs using update_fn.
- // id may be any depth, e.g. "video.decoders.1.2.3"
- void UpdateItem(const std::string& update_fn, const std::string& id,
- const std::string& property, base::Value* value);
-
- // Calls javascript |function|(|value|) on each attached UI.
- void SendUpdate(const std::string& function, base::Value* value);
-
- base::DictionaryValue data_;
-
+ // Sends |update| to each registered UpdateCallback. Safe to call from any
+ // thread, but will forward to the IO thread.
+ void SendUpdate(const string16& update);
+
+ // Caches |value| under |cache_key| so that future SendEverything() calls will
+ // include the current data. Calls JavaScript |function|(|value|) for each
+ // registered UpdateCallback. SendUpdateAndPurgeCache() is similar but purges
+ // the cache entry after completion instead.
+ void SendUpdateAndCache(const std::string& cache_key,
+ const std::string& function,
+ const base::DictionaryValue* value);
+ void SendUpdateAndPurgeCache(const std::string& cache_key,
+ const std::string& function,
+ const base::DictionaryValue* value);
+ // Must only be accessed on the IO thread.
std::vector<UpdateCallback> update_callbacks_;
+ // All variables below must be accessed under |lock_|.
+ base::Lock lock_;
+ base::DictionaryValue cached_data_;
+ int owner_ids_[AUDIO_COMPONENT_MAX];
+
DISALLOW_COPY_AND_ASSIGN(MediaInternals);
};
diff --git a/content/browser/media/media_internals_handler.cc b/content/browser/media/media_internals_handler.cc
index 1715330..a45dd4f 100644
--- a/content/browser/media/media_internals_handler.cc
+++ b/content/browser/media/media_internals_handler.cc
@@ -16,7 +16,8 @@
namespace content {
MediaInternalsMessageHandler::MediaInternalsMessageHandler()
- : proxy_(new MediaInternalsProxy()) {}
+ : proxy_(new MediaInternalsProxy()),
+ page_load_complete_(false) {}
MediaInternalsMessageHandler::~MediaInternalsMessageHandler() {
proxy_->Detach();
@@ -33,13 +34,15 @@ void MediaInternalsMessageHandler::RegisterMessages() {
void MediaInternalsMessageHandler::OnGetEverything(
const base::ListValue* list) {
+ page_load_complete_ = true;
proxy_->GetEverything();
}
void MediaInternalsMessageHandler::OnUpdate(const string16& update) {
- // Don't try to execute JavaScript in a RenderView that no longer exists.
+ // Don't try to execute JavaScript in a RenderView that no longer exists nor
+ // if the chrome://media-internals page hasn't finished loading.
RenderViewHost* host = web_ui()->GetWebContents()->GetRenderViewHost();
- if (host)
+ if (host && page_load_complete_)
host->ExecuteJavascriptInWebFrame(string16(), update);
}
diff --git a/content/browser/media/media_internals_handler.h b/content/browser/media/media_internals_handler.h
index 4833aee..8ea6f88 100644
--- a/content/browser/media/media_internals_handler.h
+++ b/content/browser/media/media_internals_handler.h
@@ -35,6 +35,10 @@ class MediaInternalsMessageHandler : public WebUIMessageHandler {
private:
scoped_refptr<MediaInternalsProxy> proxy_;
+ // Reflects whether the chrome://media-internals HTML+JS has finished loading.
+ // If not, it's not safe to send JavaScript calls targeting the page yet.
+ bool page_load_complete_;
+
DISALLOW_COPY_AND_ASSIGN(MediaInternalsMessageHandler);
};
diff --git a/content/browser/media/media_internals_unittest.cc b/content/browser/media/media_internals_unittest.cc
index 173ba36..f5e0375 100644
--- a/content/browser/media/media_internals_unittest.cc
+++ b/content/browser/media/media_internals_unittest.cc
@@ -5,173 +5,142 @@
#include "content/browser/media/media_internals.h"
#include "base/bind.h"
-#include "base/memory/scoped_ptr.h"
-#include "base/message_loop/message_loop.h"
-#include "base/strings/stringprintf.h"
-#include "content/public/test/test_browser_thread.h"
+#include "base/bind_helpers.h"
+#include "base/json/json_reader.h"
+#include "base/run_loop.h"
+#include "base/strings/utf_string_conversions.h"
+#include "content/public/test/test_browser_thread_bundle.h"
#include "media/audio/audio_parameters.h"
#include "media/base/channel_layout.h"
-#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
-namespace content {
namespace {
+const int kTestComponentID = 0;
+const char kTestInputDeviceID[] = "test-input-id";
+const char kTestOutputDeviceID[] = "test-output-id";
+} // namespace
-class MockObserverBaseClass {
- public:
- ~MockObserverBaseClass() {}
- virtual void OnUpdate(const string16& javascript) = 0;
-};
+namespace content {
-class MockMediaInternalsObserver : public MockObserverBaseClass {
+class MediaInternalsTest
+ : public testing::TestWithParam<media::AudioLogFactory::AudioComponent> {
public:
- virtual ~MockMediaInternalsObserver() {}
- MOCK_METHOD1(OnUpdate, void(const string16& javascript));
-};
-
-} // namespace
+ MediaInternalsTest()
+ : media_internals_(MediaInternals::GetInstance()),
+ update_cb_(base::Bind(&MediaInternalsTest::UpdateCallbackImpl,
+ base::Unretained(this))),
+ test_params_(media::AudioParameters::AUDIO_PCM_LINEAR,
+ media::CHANNEL_LAYOUT_MONO,
+ 48000,
+ 16,
+ 128),
+ test_component_(GetParam()),
+ audio_log_(media_internals_->CreateAudioLog(test_component_)) {
+ media_internals_->AddUpdateCallback(update_cb_);
+ }
-class MediaInternalsTest : public testing::Test {
- public:
- MediaInternalsTest() : io_thread_(BrowserThread::IO, &loop_) {}
- base::DictionaryValue* data() {
- return &internals_->data_;
+ virtual ~MediaInternalsTest() {
+ media_internals_->RemoveUpdateCallback(update_cb_);
}
- void DeleteItem(const std::string& item) {
- internals_->DeleteItem(item);
+ protected:
+ // Extracts and deserializes the JSON update data; merges into |update_data_|.
+ void UpdateCallbackImpl(const string16& update) {
+ // Each update string looks like "<JavaScript Function Name>({<JSON>});", to
+ // use the JSON reader we need to strip out the JavaScript code.
+ std::string utf8_update = base::UTF16ToUTF8(update);
+ const std::string::size_type first_brace = utf8_update.find('{');
+ const std::string::size_type last_brace = utf8_update.rfind('}');
+ scoped_ptr<base::Value> output_value(base::JSONReader::Read(
+ utf8_update.substr(first_brace, last_brace - first_brace + 1)));
+ CHECK(output_value);
+
+ base::DictionaryValue* output_dict = NULL;
+ CHECK(output_value->GetAsDictionary(&output_dict));
+ update_data_.MergeDictionary(output_dict);
}
- void UpdateItem(const std::string& item, const std::string& property,
- base::Value* value) {
- internals_->UpdateItem(std::string(), item, property, value);
+ void ExpectInt(const std::string& key, int expected_value) {
+ int actual_value = 0;
+ ASSERT_TRUE(update_data_.GetInteger(key, &actual_value));
+ EXPECT_EQ(expected_value, actual_value);
}
- void SendUpdate(const std::string& function, base::Value* value) {
- internals_->SendUpdate(function, value);
+ void ExpectString(const std::string& key, const std::string& expected_value) {
+ std::string actual_value;
+ ASSERT_TRUE(update_data_.GetString(key, &actual_value));
+ EXPECT_EQ(expected_value, actual_value);
}
- protected:
- virtual void SetUp() {
- internals_.reset(new MediaInternals());
+ void ExpectStatus(const std::string& expected_value) {
+ ExpectString("status", expected_value);
}
- base::MessageLoop loop_;
- TestBrowserThread io_thread_;
- scoped_ptr<MediaInternals> internals_;
+ TestBrowserThreadBundle thread_bundle_;
+ MediaInternals* const media_internals_;
+ MediaInternals::UpdateCallback update_cb_;
+ base::DictionaryValue update_data_;
+ const media::AudioParameters test_params_;
+ const media::AudioLogFactory::AudioComponent test_component_;
+ scoped_ptr<media::AudioLog> audio_log_;
};
-TEST_F(MediaInternalsTest, AudioStreamCreatedSendsMessage) {
- media::AudioParameters params =
- media::AudioParameters(media::AudioParameters::AUDIO_PCM_LINEAR,
- media::CHANNEL_LAYOUT_MONO,
- 48000,
- 16,
- 129);
-
- const int stream_id = 0;
- const std::string device_id = "test";
- const std::string name =
- base::StringPrintf("audio_streams.%p:%d", this, stream_id);
-
- internals_->OnAudioStreamCreated(this, stream_id, params, device_id);
-
- std::string channel_layout;
- data()->GetString(name + ".channel_layout", &channel_layout);
- EXPECT_EQ("MONO", channel_layout);
-
- int sample_rate;
- data()->GetInteger(name + ".sample_rate", &sample_rate);
- EXPECT_EQ(params.sample_rate(), sample_rate);
-
- int frames_per_buffer;
- data()->GetInteger(name + ".frames_per_buffer", &frames_per_buffer);
- EXPECT_EQ(params.frames_per_buffer(), frames_per_buffer);
-
- int output_channels;
- data()->GetInteger(name + ".output_channels", &output_channels);
- EXPECT_EQ(params.channels(), output_channels);
-
- std::string device_id_out;
- data()->GetString(name + ".input_device_id", &device_id_out);
- EXPECT_EQ(device_id, device_id_out);
-
- int input_channels;
- data()->GetInteger(name + ".input_channels", &input_channels);
- EXPECT_EQ(params.input_channels(), input_channels);
+TEST_P(MediaInternalsTest, AudioLogCreateStartStopErrorClose) {
+ audio_log_->OnCreated(
+ kTestComponentID, test_params_, kTestInputDeviceID, kTestOutputDeviceID);
+ base::RunLoop().RunUntilIdle();
+
+ ExpectString("output_channel_layout",
+ media::ChannelLayoutToString(test_params_.channel_layout()));
+ ExpectInt("sample_rate", test_params_.sample_rate());
+ ExpectInt("frames_per_buffer", test_params_.frames_per_buffer());
+ ExpectInt("output_channels", test_params_.channels());
+ ExpectInt("input_channels", test_params_.input_channels());
+ ExpectString("output_device_id", kTestOutputDeviceID);
+ ExpectString("input_device_id", kTestInputDeviceID);
+ ExpectInt("component_id", kTestComponentID);
+ ExpectInt("component_type", test_component_);
+ ExpectStatus("created");
+
+ // Verify OnStarted().
+ audio_log_->OnStarted(kTestComponentID);
+ base::RunLoop().RunUntilIdle();
+ ExpectStatus("started");
+
+ // Verify OnStopped().
+ audio_log_->OnStopped(kTestComponentID);
+ base::RunLoop().RunUntilIdle();
+ ExpectStatus("stopped");
+
+ // Verify OnError().
+ const char kErrorKey[] = "error_occurred";
+ std::string no_value;
+ ASSERT_FALSE(update_data_.GetString(kErrorKey, &no_value));
+ audio_log_->OnError(kTestComponentID);
+ base::RunLoop().RunUntilIdle();
+ ExpectString(kErrorKey, "true");
+
+ // Verify OnClosed().
+ audio_log_->OnClosed(kTestComponentID);
+ base::RunLoop().RunUntilIdle();
+ ExpectStatus("closed");
}
-TEST_F(MediaInternalsTest, UpdateAddsNewItem) {
- UpdateItem("some.item", "testing", new base::FundamentalValue(true));
- bool testing = false;
- std::string id;
+TEST_P(MediaInternalsTest, AudioLogCreateClose) {
+ audio_log_->OnCreated(
+ kTestComponentID, test_params_, kTestInputDeviceID, kTestOutputDeviceID);
+ base::RunLoop().RunUntilIdle();
+ ExpectStatus("created");
- EXPECT_TRUE(data()->GetBoolean("some.item.testing", &testing));
- EXPECT_TRUE(testing);
-
- EXPECT_TRUE(data()->GetString("some.item.id", &id));
- EXPECT_EQ(id, "some.item");
-}
-
-TEST_F(MediaInternalsTest, UpdateModifiesExistingItem) {
- UpdateItem("some.item", "testing", new base::FundamentalValue(true));
- UpdateItem("some.item", "value", new base::FundamentalValue(5));
- UpdateItem("some.item", "testing", new base::FundamentalValue(false));
- bool testing = true;
- int value = 0;
- std::string id;
-
- EXPECT_TRUE(data()->GetBoolean("some.item.testing", &testing));
- EXPECT_FALSE(testing);
-
- EXPECT_TRUE(data()->GetInteger("some.item.value", &value));
- EXPECT_EQ(value, 5);
-
- EXPECT_TRUE(data()->GetString("some.item.id", &id));
- EXPECT_EQ(id, "some.item");
-}
-
-TEST_F(MediaInternalsTest, ObserversReceiveNotifications) {
- scoped_ptr<MockMediaInternalsObserver> observer(
- new MockMediaInternalsObserver());
-
- EXPECT_CALL(*observer.get(), OnUpdate(testing::_)).Times(1);
-
- MediaInternals::UpdateCallback callback = base::Bind(
- &MockMediaInternalsObserver::OnUpdate, base::Unretained(observer.get()));
-
- internals_->AddUpdateCallback(callback);
- SendUpdate("fn", data());
-}
-
-TEST_F(MediaInternalsTest, RemovedObserversReceiveNoNotifications) {
- scoped_ptr<MockMediaInternalsObserver> observer(
- new MockMediaInternalsObserver());
-
- EXPECT_CALL(*observer.get(), OnUpdate(testing::_)).Times(0);
-
- MediaInternals::UpdateCallback callback = base::Bind(
- &MockMediaInternalsObserver::OnUpdate, base::Unretained(observer.get()));
-
- internals_->AddUpdateCallback(callback);
- internals_->RemoveUpdateCallback(callback);
- SendUpdate("fn", data());
+ audio_log_->OnClosed(kTestComponentID);
+ base::RunLoop().RunUntilIdle();
+ ExpectStatus("closed");
}
-TEST_F(MediaInternalsTest, DeleteRemovesItem) {
- base::Value* out;
-
- UpdateItem("some.item", "testing", base::Value::CreateNullValue());
- EXPECT_TRUE(data()->Get("some.item", &out));
- EXPECT_TRUE(data()->Get("some", &out));
-
- DeleteItem("some.item");
- EXPECT_FALSE(data()->Get("some.item", &out));
- EXPECT_TRUE(data()->Get("some", &out));
-
- DeleteItem("some");
- EXPECT_FALSE(data()->Get("some.item", &out));
- EXPECT_FALSE(data()->Get("some", &out));
-}
+INSTANTIATE_TEST_CASE_P(
+ MediaInternalsTest, MediaInternalsTest, testing::Values(
+ media::AudioLogFactory::AUDIO_INPUT_CONTROLLER,
+ media::AudioLogFactory::AUDIO_OUTPUT_CONTROLLER,
+ media::AudioLogFactory::AUDIO_OUTPUT_STREAM));
} // namespace content
diff --git a/content/browser/renderer_host/media/audio_renderer_host.cc b/content/browser/renderer_host/media/audio_renderer_host.cc
index 7e6ab82..6282a13 100644
--- a/content/browser/renderer_host/media/audio_renderer_host.cc
+++ b/content/browser/renderer_host/media/audio_renderer_host.cc
@@ -117,7 +117,8 @@ AudioRendererHost::AudioRendererHost(
: render_process_id_(render_process_id),
audio_manager_(audio_manager),
mirroring_manager_(mirroring_manager),
- media_internals_(media_internals),
+ audio_log_(media_internals->CreateAudioLog(
+ media::AudioLogFactory::AUDIO_OUTPUT_CONTROLLER)),
media_stream_manager_(media_stream_manager) {
DCHECK(audio_manager_);
DCHECK(media_stream_manager_);
@@ -379,10 +380,7 @@ void AudioRendererHost::OnCreateStream(
render_process_id_, entry->render_view_id(), entry->controller());
}
audio_entries_.insert(std::make_pair(stream_id, entry.release()));
- if (media_internals_) {
- media_internals_->OnAudioStreamCreated(
- this, stream_id, params, input_device_id);
- }
+ audio_log_->OnCreated(stream_id, params, input_device_id, output_device_id);
}
void AudioRendererHost::OnPlayStream(int stream_id) {
@@ -395,8 +393,7 @@ void AudioRendererHost::OnPlayStream(int stream_id) {
}
entry->controller()->Play();
- if (media_internals_)
- media_internals_->OnSetAudioStreamPlaying(this, stream_id, true);
+ audio_log_->OnStarted(stream_id);
}
void AudioRendererHost::OnPauseStream(int stream_id) {
@@ -409,8 +406,7 @@ void AudioRendererHost::OnPauseStream(int stream_id) {
}
entry->controller()->Pause();
- if (media_internals_)
- media_internals_->OnSetAudioStreamPlaying(this, stream_id, false);
+ audio_log_->OnStopped(stream_id);
}
void AudioRendererHost::OnSetVolume(int stream_id, double volume) {
@@ -426,8 +422,7 @@ void AudioRendererHost::OnSetVolume(int stream_id, double volume) {
if (volume < 0 || volume > 1.0)
return;
entry->controller()->SetVolume(volume);
- if (media_internals_)
- media_internals_->OnSetAudioStreamVolume(this, stream_id, volume);
+ audio_log_->OnSetVolume(stream_id, volume);
}
void AudioRendererHost::SendErrorMessage(int stream_id) {
@@ -453,9 +448,7 @@ void AudioRendererHost::OnCloseStream(int stream_id) {
}
controller->Close(
base::Bind(&AudioRendererHost::DeleteEntry, this, base::Passed(&entry)));
-
- if (media_internals_)
- media_internals_->OnSetAudioStreamStatus(this, stream_id, "closed");
+ audio_log_->OnClosed(stream_id);
}
void AudioRendererHost::DeleteEntry(scoped_ptr<AudioEntry> entry) {
@@ -469,12 +462,6 @@ void AudioRendererHost::DeleteEntry(scoped_ptr<AudioEntry> entry) {
render_process_id_, entry->render_view_id(), entry->stream_id(),
false, -std::numeric_limits<float>::infinity(), false);
}
-
- // Notify the media observer.
- if (media_internals_)
- media_internals_->OnDeleteAudioStream(this, entry->stream_id());
-
- // Note: |entry| will be deleted upon leaving this scope.
}
void AudioRendererHost::ReportErrorAndClose(int stream_id) {
@@ -488,9 +475,7 @@ void AudioRendererHost::ReportErrorAndClose(int stream_id) {
SendErrorMessage(stream_id);
- if (media_internals_)
- media_internals_->OnSetAudioStreamStatus(this, stream_id, "error");
-
+ audio_log_->OnError(stream_id);
OnCloseStream(stream_id);
}
diff --git a/content/browser/renderer_host/media/audio_renderer_host.h b/content/browser/renderer_host/media/audio_renderer_host.h
index cd8b136..1474366 100644
--- a/content/browser/renderer_host/media/audio_renderer_host.h
+++ b/content/browser/renderer_host/media/audio_renderer_host.h
@@ -49,6 +49,7 @@
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/render_view_host.h"
#include "media/audio/audio_io.h"
+#include "media/audio/audio_logging.h"
#include "media/audio/audio_output_controller.h"
#include "media/audio/simple_sources.h"
@@ -156,7 +157,7 @@ class CONTENT_EXPORT AudioRendererHost : public BrowserMessageFilter {
media::AudioManager* const audio_manager_;
AudioMirroringManager* const mirroring_manager_;
- MediaInternals* const media_internals_;
+ scoped_ptr<media::AudioLog> audio_log_;
// Used to access to AudioInputDeviceManager.
MediaStreamManager* media_stream_manager_;
diff --git a/content/browser/resources/media/client_renderer.js b/content/browser/resources/media/client_renderer.js
index 97acba2..0db66c3 100644
--- a/content/browser/resources/media/client_renderer.js
+++ b/content/browser/resources/media/client_renderer.js
@@ -5,13 +5,15 @@
var ClientRenderer = (function() {
var ClientRenderer = function() {
this.playerListElement = document.getElementById('player-list');
- this.audioStreamListElement = document.getElementById('audio-stream-list');
- this.propertiesTable = document.getElementById('property-table');
- this.logTable = document.getElementById('log');
+ this.propertiesTable =
+ document.getElementById('property-table').querySelector('tbody');
+ this.logTable = document.getElementById('log').querySelector('tbody');
this.graphElement = document.getElementById('graphs');
this.selectedPlayer = null;
- this.selectedStream = null;
+ this.selectedAudioComponentType = null;
+ this.selectedAudioComponentId = null;
+ this.selectedAudioCompontentData = null;
this.selectedPlayerLogIndex = 0;
@@ -26,6 +28,8 @@ var ClientRenderer = (function() {
this.clipboardTextarea = document.getElementById('clipboard-textarea');
this.clipboardButton = document.getElementById('copy-button');
this.clipboardButton.onclick = this.copyToClipboard_.bind(this);
+
+ this.hiddenKeys = ['component_id', 'component_type', 'owner_id'];
};
function removeChildren(element) {
@@ -46,18 +50,39 @@ var ClientRenderer = (function() {
};
ClientRenderer.prototype = {
- audioStreamAdded: function(audioStreams, audioStreamAdded) {
- this.redrawAudioStreamList_(audioStreams);
- },
-
- audioStreamUpdated: function(audioStreams, stream, key, value) {
- if (stream === this.selectedStream) {
- this.drawProperties_(stream);
+ /**
+ * Called when an audio component is added to the collection.
+ * @param componentType Integer AudioComponent enum value; must match values
+ * from the AudioLogFactory::AudioComponent enum.
+ * @param components The entire map of components (name -> dict).
+ */
+ audioComponentAdded: function(componentType, components) {
+ this.redrawAudioComponentList_(componentType, components);
+
+ // Redraw the component if it's currently selected.
+ if (this.selectedAudioComponentType == componentType &&
+ this.selectedAudioComponentId &&
+ this.selectedAudioComponentId in components) {
+ this.selectAudioComponent_(
+ componentType, this.selectedAudioComponentId,
+ components[this.selectedAudioComponentId]);
}
},
- audioStreamRemoved: function(audioStreams, audioStreamRemoved) {
- this.redrawAudioStreamList_(audioStreams);
+ /**
+ * Called when an audio component is removed from the collection.
+ * @param componentType Integer AudioComponent enum value; must match values
+ * from the AudioLogFactory::AudioComponent enum.
+ * @param components The entire map of components (name -> dict).
+ */
+ audioComponentRemoved: function(componentType, components) {
+ this.redrawAudioComponentList_(componentType, components);
+
+ // Clear the component if it was previously currently selected.
+ if (this.selectedAudioComponentType == componentType &&
+ !(this.selectedAudioComponentId in components)) {
+ this.selectAudioComponent_(null, null, {});
+ }
},
/**
@@ -96,48 +121,74 @@ var ClientRenderer = (function() {
}
},
- redrawAudioStreamList_: function(streams) {
- removeChildren(this.audioStreamListElement);
+ redrawAudioComponentList_: function(componentType, components) {
+ function redrawList(renderer, baseName, element) {
+ var fragment = document.createDocumentFragment();
+ for (id in components) {
+ var li = document.createElement('li');
+ li.appendChild(createButton(
+ baseName + ' ' + id, renderer.selectAudioComponent_.bind(
+ renderer, componentType, id, components[id])));
+ fragment.appendChild(li);
+ }
+ removeChildren(element);
+ element.appendChild(fragment);
+ }
- for (id in streams) {
- var li = document.createElement('li');
- li.appendChild(createButton(
- id, this.selectAudioStream_.bind(this, streams[id])));
- this.audioStreamListElement.appendChild(li);
+ switch (componentType) {
+ case 0:
+ redrawList(this, 'Controller', document.getElementById(
+ 'audio-input-controller-list'));
+ break;
+ case 1:
+ redrawList(this, 'Controller', document.getElementById(
+ 'audio-output-controller-list'));
+ break;
+ case 2:
+ redrawList(this, 'Stream', document.getElementById(
+ 'audio-output-stream-list'));
+ break;
+ default:
+ break;
}
},
- selectAudioStream_: function(audioStream) {
- this.selectedStream = audioStream;
+ selectAudioComponent_: function(componentType, componentId, componentData) {
this.selectedPlayer = null;
- this.drawProperties_(audioStream);
- removeChildren(this.logTable.querySelector('tbody'));
+ this.selectedAudioComponentType = componentType;
+ this.selectedAudioComponentId = componentId;
+ this.selectedAudioCompontentData = componentData;
+ this.drawProperties_(componentData);
+ removeChildren(this.logTable);
removeChildren(this.graphElement);
},
redrawPlayerList_: function(players) {
- removeChildren(this.playerListElement);
-
+ var fragment = document.createDocumentFragment();
for (id in players) {
- var li = document.createElement('li');
var player = players[id];
var usableName = player.properties.name ||
player.properties.url ||
- 'player ' + player.id;
+ 'Player ' + player.id;
+ var li = document.createElement('li');
li.appendChild(createButton(
usableName, this.selectPlayer_.bind(this, player)));
- this.playerListElement.appendChild(li);
+ fragment.appendChild(li);
}
+ removeChildren(this.playerListElement);
+ this.playerListElement.appendChild(fragment);
},
selectPlayer_: function(player) {
this.selectedPlayer = player;
this.selectedPlayerLogIndex = 0;
- this.selectedStream = null;
+ this.selectedAudioComponentType = null;
+ this.selectedAudioComponentId = null;
+ this.selectedAudioCompontentData = null;
this.drawProperties_(player.properties);
- removeChildren(this.logTable.querySelector('tbody'));
+ removeChildren(this.logTable);
removeChildren(this.graphElement);
this.drawLog_();
this.drawGraphs_();
@@ -145,12 +196,13 @@ var ClientRenderer = (function() {
drawProperties_: function(propertyMap) {
removeChildren(this.propertiesTable);
-
var sortedKeys = Object.keys(propertyMap).sort();
for (var i = 0; i < sortedKeys.length; ++i) {
var key = sortedKeys[i];
- var value = propertyMap[key];
+ if (this.hiddenKeys.indexOf(key) >= 0)
+ continue;
+ var value = propertyMap[key];
var row = this.propertiesTable.insertRow(-1);
var keyCell = row.insertCell(-1);
var valueCell = row.insertCell(-1);
@@ -162,9 +214,11 @@ var ClientRenderer = (function() {
appendEventToLog_: function(event) {
if (this.filterFunction(event.key)) {
- var row = this.logTable.querySelector('tbody').insertRow(-1);
+ var row = this.logTable.insertRow(-1);
- row.insertCell(-1).appendChild(document.createTextNode(
+ var timestampCell = row.insertCell(-1);
+ timestampCell.classList.add('timestamp');
+ timestampCell.appendChild(document.createTextNode(
util.millisecondsToString(event.time)));
row.insertCell(-1).appendChild(document.createTextNode(event.key));
row.insertCell(-1).appendChild(document.createTextNode(event.value));
@@ -249,7 +303,7 @@ var ClientRenderer = (function() {
},
copyToClipboard_: function() {
- var properties = this.selectedStream ||
+ var properties = this.selectedAudioCompontentData ||
this.selectedPlayer.properties || false;
if (!properties) {
return;
@@ -265,18 +319,14 @@ var ClientRenderer = (function() {
}
this.clipboardTextarea.value = stringBuffer.join('');
- this.clipboardTextarea.classList.remove('hidden');
+ this.clipboardTextarea.classList.remove('hiddenClipboard');
this.clipboardTextarea.focus();
this.clipboardTextarea.select();
- // The act of copying anything from the textarea gets canceled
- // if the element in question gets the class 'hidden' (which contains the
- // css property display:none) before the event is finished. For this, it
- // is necessary put the property setting on the event loop to be executed
- // after the copy has taken place.
- this.clipboardTextarea.oncopy = function(event) {
+ // Hide the clipboard element when it loses focus.
+ this.clipboardTextarea.onblur = function(event) {
setTimeout(function(element) {
- event.target.classList.add('hidden');
+ event.target.classList.add('hiddenClipboard');
}, 0);
};
},
@@ -297,7 +347,7 @@ var ClientRenderer = (function() {
};
if (this.selectedPlayer) {
- removeChildren(this.logTable.querySelector('tbody'));
+ removeChildren(this.logTable);
this.selectedPlayerLogIndex = 0;
this.drawLog_();
}
diff --git a/content/browser/resources/media/main.js b/content/browser/resources/media/main.js
index 14b9918..cb239da 100644
--- a/content/browser/resources/media/main.js
+++ b/content/browser/resources/media/main.js
@@ -40,8 +40,8 @@ var media = (function() {
};
media.onReceiveEverything = function(everything) {
- for (var key in everything.audio_streams) {
- media.updateAudioStream(everything.audio_streams[key]);
+ for (var component in everything) {
+ media.updateAudioComponent(everything[component]);
}
};
@@ -123,31 +123,20 @@ var media = (function() {
});
};
- // For whatever reason, addAudioStream is also called on
- // the removal of audio streams.
- media.addAudioStream = function(event) {
- switch (event.status) {
- case 'created':
- manager.addAudioStream(event.id);
- manager.updateAudioStream(event.id, { 'playing': event.playing });
- break;
+ media.updateAudioComponent = function(component) {
+ var uniqueComponentId = component.owner_id + ':' + component.component_id;
+ switch (component.status) {
case 'closed':
- manager.removeAudioStream(event.id);
+ manager.removeAudioComponent(
+ component.component_type, uniqueComponentId);
+ break;
+ default:
+ manager.updateAudioComponent(
+ component.component_type, uniqueComponentId, component);
break;
}
};
- media.updateAudioStream = function(stream) {
- manager.addAudioStream(stream.id);
- manager.updateAudioStream(stream.id, stream);
- };
-
- media.onItemDeleted = function() {
- // This only gets called when an audio stream is removed, which
- // for whatever reason is also handled by addAudioStream...
- // Because it is already handled, we can safely ignore it.
- };
-
media.onPlayerOpen = function(id, timestamp) {
manager.addPlayer(id, timestamp);
};
diff --git a/content/browser/resources/media/manager.js b/content/browser/resources/media/manager.js
index 1ba1301..80cd032 100644
--- a/content/browser/resources/media/manager.js
+++ b/content/browser/resources/media/manager.js
@@ -14,45 +14,47 @@ var Manager = (function() {
function Manager(clientRenderer) {
this.players_ = {};
- this.audioStreams_ = {};
+ this.audioComponents_ = [];
this.clientRenderer_ = clientRenderer;
}
Manager.prototype = {
/**
- * Adds an audio-stream to the dictionary of audio-streams to manage.
- * @param id The unique-id of the audio-stream.
- */
- addAudioStream: function(id) {
- this.audioStreams_[id] = this.audioStreams_[id] || {};
- this.clientRenderer_.audioStreamAdded(this.audioStreams_,
- this.audioStreams_[id]);
- },
-
- /**
- * Sets properties of an audiostream.
- * @param id The unique-id of the audio-stream.
- * @param properties A dictionary of properties to be added to the
- * audio-stream.
+ * Updates an audio-component.
+ * @param componentType Integer AudioComponent enum value; must match values
+ * from the AudioLogFactory::AudioComponent enum.
+ * @param componentId The unique-id of the audio-component.
+ * @param componentData The actual component data dictionary.
*/
- updateAudioStream: function(id, properties) {
- for (var key in properties) {
- this.audioStreams_[id][key] = properties[key];
+ updateAudioComponent: function(componentType, componentId, componentData) {
+ if (!(componentType in this.audioComponents_))
+ this.audioComponents_[componentType] = {};
+ if (!(componentId in this.audioComponents_[componentType])) {
+ this.audioComponents_[componentType][componentId] = componentData;
+ } else {
+ for (var key in componentData) {
+ this.audioComponents_[componentType][componentId][key] =
+ componentData[key];
+ }
}
- this.clientRenderer_.audioStreamAdded(
- this.audioStreams_, this.audioStreams_[id]);
+ this.clientRenderer_.audioComponentAdded(
+ componentType, this.audioComponents_[componentType]);
},
/**
* Removes an audio-stream from the manager.
* @param id The unique-id of the audio-stream.
*/
- removeAudioStream: function(id) {
- this.clientRenderer_.audioStreamRemoved(
- this.audioStreams_, this.audioStreams_[id]);
- delete this.audioStreams_[id];
- },
+ removeAudioComponent: function(componentType, componentId) {
+ if (!(componentType in this.audioComponents_) ||
+ !(componentId in this.audioComponents_[componentType])) {
+ return;
+ }
+ delete this.audioComponents_[componentType][componentId];
+ this.clientRenderer_.audioComponentRemoved(
+ componentType, this.audioComponents_[componentType]);
+ },
/**
* Adds a player to the list of players to manage.
diff --git a/content/browser/resources/media/media_internals.css b/content/browser/resources/media/media_internals.css
index 9e6ab76..041527f 100644
--- a/content/browser/resources/media/media_internals.css
+++ b/content/browser/resources/media/media_internals.css
@@ -9,21 +9,30 @@ body,
padding: 0;
width: 100%;
height: 100%;
-
- font-family: 'Lucida Grande', sans-serif;
}
table {
- border-collapse: collapse;
+ font-family: sans-serif;
+ -webkit-font-smoothing: antialiased;
+ font-size: 115%;
+ width: auto;
+ overflow: auto;
+ display: block;
+}
+th {
+ background-color: rgb(112, 196, 105);
+ font-weight: normal;
+ color: white;
+ padding: 2px;
+ text-align: center;
+ min-width: 230px;
}
td {
- border: 1px solid black;
+ background-color: rgb(238, 238, 238);
+ padding: 2px;
+ color: rgb(111, 111, 111);
word-wrap: break-word;
- max-width: 200px;
-}
-thead {
- color: rgb(50,50,50);
- font-size: 1.1em;
+ min-width: 230px;
}
h1,
@@ -56,26 +65,22 @@ h3 {
}
#player-list-wrapper,
-#audio-stream-list-wrapper {
+#audio-component-list-wrapper {
flex-grow: 1;
align-self: stretch;
min-width: 200px;
- max-width: 200px;
overflow: auto;
}
#player-list-wrapper ul,
#player-list-wrapper li,
-#audio-stream-list-wrapper ul,
-#audio-stream-list-wrapper li {
- margin: 0;
- padding: 0;
+#audio-component-list-wrapper ul,
+#audio-component-list-wrapper li {
+ padding: 0px;
list-style-type: none;
}
#list-wrapper button {
- margin: 0;
- padding: 0;
- width: 170px;
+ padding: 0px;
}
#property-wrapper,
@@ -103,6 +108,10 @@ h3 {
top: 25%;
}
-.hidden {
+.hiddenClipboard {
display: none;
}
+
+.timestamp {
+ min-width: 115px;
+} \ No newline at end of file
diff --git a/content/browser/resources/media/media_internals.html b/content/browser/resources/media/media_internals.html
index 16d0c55..c555096 100644
--- a/content/browser/resources/media/media_internals.html
+++ b/content/browser/resources/media/media_internals.html
@@ -13,16 +13,24 @@ found in the LICENSE file.
</head>
<body>
- <textarea id="clipboard-textarea" class="hidden"></textarea>
+ <textarea id="clipboard-textarea" class="hiddenClipboard"></textarea>
<div id="container">
<div id="list-wrapper">
<div id="player-list-wrapper">
<h2>Players</h2>
<ul id="player-list"></ul>
</div>
- <div id="audio-stream-list-wrapper">
- <h2>Audio Streams</h2>
- <ul id="audio-stream-list"></ul>
+ <div id="audio-component-list-wrapper">
+ <h2>Input Controllers</h2>
+ <ul id="audio-input-controller-list"></ul>
+ </div>
+ <div id="audio-component-list-wrapper">
+ <h2>Output Controllers</h2>
+ <ul id="audio-output-controller-list"></ul>
+ </div>
+ <div id="audio-component-list-wrapper">
+ <h2>Output Streams</h2>
+ <ul id="audio-output-stream-list"></ul>
</div>
</div>
<div id="property-wrapper">
@@ -30,9 +38,11 @@ found in the LICENSE file.
<table id="property-table">
<thead>
<tr>
- <td>Property Name</td> <td>Value</td>
+ <th>Property</th>
+ <th>Value</th>
</tr>
</thead>
+ <tbody></tbody>
</table>
<ul id="graphs"></ul>
</div>
@@ -43,9 +53,9 @@ found in the LICENSE file.
<table id="log">
<thead>
<tr>
- <td>Timestamp</td>
- <td>Property</td>
- <td>Value</td>
+ <th class="timestamp">Timestamp</th>
+ <th>Property</th>
+ <th>Value</th>
</tr>
</thead>
<tbody></tbody>
diff --git a/content/test/data/media/webui/integration_test.html b/content/test/data/media/webui/integration_test.html
index be567a0..63aee24 100644
--- a/content/test/data/media/webui/integration_test.html
+++ b/content/test/data/media/webui/integration_test.html
@@ -15,15 +15,14 @@ found in the LICENSE file.
playerUpdated: doNothing,
playerRemoved: doNothing,
playerAdded: doNothing,
- audioStreamUpdated: doNothing,
- audioStreamAdded: doNothing,
- audioStreamRemoved: doNothing
+ audioComponentAdded: doNothing,
+ audioComponentRemoved: doNothing
};
var manager = new Manager(mockClientRenderer);
media.initialize(manager);
- window.manager= manager;
+ window.manager = manager;
};
// The renderer and player ids are completely arbitrarily.
@@ -59,19 +58,40 @@ found in the LICENSE file.
assertEquals(undefined, window.manager.players_[TEST_NAME]);
};
- // Audio Streams are weird, they are handled separately
- window.testAddAudioStream = function() {
+ window.testAudioComponents = function() {
var event = {
- id: 'ID',
- status: 'created',
- playing: true
+ component_id: 1,
+ component_type: 0,
+ owner_id: 3,
+ status: 'created'
};
- window.media.addAudioStream(event);
+ // Ensure no components are currently present.
+ assertEquals(0, window.manager.audioComponents_.length);
- var player = window.manager.audioStreams_[event.id];
- assertTrue(undefined !== player);
- assertEquals(event.playing, player['playing']);
+ // Test adding an audio component.
+ window.media.updateAudioComponent(event);
+ assertEquals(1, window.manager.audioComponents_.length);
+
+ // The key format is an implementation detail we don't care about, so
+ // just ensure there's only one key and then use it directly.
+ assertEquals(1, Object.keys(
+ window.manager.audioComponents_[event.component_type]).length);
+ for (key in window.manager.audioComponents_[event.component_type]) {
+ var component =
+ window.manager.audioComponents_[event.component_type][key];
+ assertEquals(event.component_id, component['component_id']);
+ assertEquals(event.component_type, component['component_type']);
+ assertEquals(event.owner_id, component['owner_id']);
+ assertEquals(event.status, component['status']);
+ }
+
+ // Test removing an audio component.
+ event.status = 'closed';
+ window.media.updateAudioComponent(event);
+ assertEquals(1, window.manager.audioComponents_.length);
+ assertEquals(0, Object.keys(
+ window.manager.audioComponents_[event.component_type]).length);
};
</script>
</body>
diff --git a/media/audio/audio_logging.h b/media/audio/audio_logging.h
new file mode 100644
index 0000000..1d8366b
--- /dev/null
+++ b/media/audio/audio_logging.h
@@ -0,0 +1,84 @@
+// 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.
+
+#ifndef MEDIA_AUDIO_AUDIO_LOGGING_H_
+#define MEDIA_AUDIO_AUDIO_LOGGING_H_
+
+#include <string>
+
+#include "base/memory/scoped_ptr.h"
+
+namespace media {
+class AudioParameters;
+
+// AudioLog logs state information about an active audio component. Each method
+// takes a |component_id| along with method specific information. Its methods
+// are safe to call from any thread.
+class AudioLog {
+ public:
+ virtual ~AudioLog() {}
+
+ // Called when an audio component is created. |params| are the parameters of
+ // the created stream. |input_device_id| and |output_device_id| are the
+ // respective device ids for input and output. Either one or both may be
+ // specified.
+ virtual void OnCreated(int component_id,
+ const media::AudioParameters& params,
+ const std::string& input_device_id,
+ const std::string& output_device_id) = 0;
+
+ // Called when an audio component is started, generally this is synonymous
+ // with "playing."
+ virtual void OnStarted(int component_id) = 0;
+
+ // Called when an audio component is stopped, generally this is synonymous
+ // with "paused."
+ virtual void OnStopped(int component_id) = 0;
+
+ // Called when an audio component is closed, generally this is synonymous
+ // with "deleted."
+ virtual void OnClosed(int component_id) = 0;
+
+ // Called when an audio component encounters an error.
+ virtual void OnError(int component_id) = 0;
+
+ // Called when an audio component changes volume. |volume| is the new volume.
+ virtual void OnSetVolume(int component_id, double volume) = 0;
+};
+
+// AudioLogFactory dispenses AudioLog instances to owning classes for tracking
+// AudioComponent behavior. All AudioComponents have the concept of an owning
+// class:
+//
+// - AudioInputRendererHost for AudioInputController
+// - AudioRendererHost for AudioOutputController
+// - AudioOutputDispatcherImpl for AudioOutputStream
+//
+// Each of these owning classes may own multiple instances of each component, as
+// such each AudioLog supports logging for multiple instances.
+class AudioLogFactory {
+ public:
+ enum AudioComponent {
+ // Input controllers have a 1:1 mapping with streams, so there's no need to
+ // track both controllers and streams.
+ AUDIO_INPUT_CONTROLLER,
+ // Output controllers may or may not be backed by an active stream, so we
+ // need to track both controllers and streams.
+ AUDIO_OUTPUT_CONTROLLER,
+ AUDIO_OUTPUT_STREAM,
+ AUDIO_COMPONENT_MAX
+ };
+
+ // Create a new AudioLog object for tracking the behavior for one or more
+ // instances of the given component. Each instance of an "owning" class must
+ // create its own AudioLog.
+ virtual scoped_ptr<AudioLog> CreateAudioLog(AudioComponent component) = 0;
+
+ protected:
+ virtual ~AudioLogFactory() {}
+};
+
+} // namespace media
+
+#endif // MEDIA_AUDIO_AUDIO_LOGGING_H_