Commit d3444394 authored by Becca Hughes's avatar Becca Hughes Committed by Commit Bot

[Audio Focus] Add sessions to media internals

Add a list of media sessions and their state
to a new audio focus tab on media internals.

BUG=870418

Change-Id: I654b9444c1abc3f5ae12c29d070627e137f5e137
Reviewed-on: https://chromium-review.googlesource.com/1161500
Commit-Queue: Becca Hughes <beccahughes@chromium.org>
Reviewed-by: default avatarMounir Lamouri <mlamouri@chromium.org>
Reviewed-by: default avatarChrome Cunningham <chcunningham@chromium.org>
Cr-Commit-Position: refs/heads/master@{#584216}
parent bbc341fb
...@@ -16,6 +16,8 @@ ...@@ -16,6 +16,8 @@
#include "base/strings/string_number_conversions.h" #include "base/strings/string_number_conversions.h"
#include "base/strings/stringprintf.h" #include "base/strings/stringprintf.h"
#include "build/build_config.h" #include "build/build_config.h"
#include "content/browser/media/session/audio_focus_manager.h"
#include "content/browser/media/session/media_session_impl.h"
#include "content/browser/renderer_host/media/media_stream_manager.h" #include "content/browser/renderer_host/media/media_stream_manager.h"
#include "content/public/browser/browser_context.h" #include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_thread.h" #include "content/public/browser/browser_thread.h"
...@@ -698,6 +700,34 @@ void MediaInternals::SendVideoCaptureDeviceCapabilities() { ...@@ -698,6 +700,34 @@ void MediaInternals::SendVideoCaptureDeviceCapabilities() {
&video_capture_capabilities_cached_data_)); &video_capture_capabilities_cached_data_));
} }
#if !defined(OS_ANDROID)
void MediaInternals::SendAudioFocusState() {
if (!CanUpdate())
return;
base::DictionaryValue audio_focus_data;
const std::list<MediaSessionImpl*>& stack =
AudioFocusManager::GetInstance()->audio_focus_stack_;
// We should go backwards through the stack so the top of the stack is always
// shown first in the list.
base::ListValue stack_data;
for (auto iter = stack.rbegin(); iter != stack.rend(); ++iter) {
MediaSessionImpl::DebugInfo debug_info = (*iter)->GetDebugInfo();
base::DictionaryValue media_session_data;
media_session_data.SetKey("name", base::Value(debug_info.name));
media_session_data.SetKey("owner", base::Value(debug_info.owner));
media_session_data.SetKey("state", base::Value(debug_info.state));
stack_data.GetList().push_back(std::move(media_session_data));
}
audio_focus_data.SetKey("sessions", std::move(stack_data));
SendUpdate(
SerializeUpdate("media.onReceiveAudioFocusState", &audio_focus_data));
}
#endif // !defined(OS_ANDROID)
void MediaInternals::UpdateVideoCaptureDeviceCapabilities( void MediaInternals::UpdateVideoCaptureDeviceCapabilities(
const std::vector<std::tuple<media::VideoCaptureDeviceDescriptor, const std::vector<std::tuple<media::VideoCaptureDeviceDescriptor,
media::VideoCaptureFormats>>& media::VideoCaptureFormats>>&
...@@ -778,6 +808,25 @@ void MediaInternals::OnProcessTerminatedForTesting(int process_id) { ...@@ -778,6 +808,25 @@ void MediaInternals::OnProcessTerminatedForTesting(int process_id) {
uma_handler_->OnProcessTerminated(process_id); uma_handler_->OnProcessTerminated(process_id);
} }
#if !defined(OS_ANDROID)
void MediaInternals::OnFocusGained(MediaSession* media_session,
AudioFocusType type) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
base::BindOnce(&MediaInternals::SendAudioFocusState,
base::Unretained(this)));
}
void MediaInternals::OnFocusLost(MediaSession* media_session) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
base::BindOnce(&MediaInternals::SendAudioFocusState,
base::Unretained(this)));
}
#endif // !defined(OS_ANDROID)
void MediaInternals::SendUpdate(const base::string16& update) { void MediaInternals::SendUpdate(const base::string16& update) {
// SendUpdate() may be called from any thread, but must run on the UI thread. // SendUpdate() may be called from any thread, but must run on the UI thread.
if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) { if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) {
......
...@@ -18,6 +18,8 @@ ...@@ -18,6 +18,8 @@
#include "base/strings/string16.h" #include "base/strings/string16.h"
#include "base/synchronization/lock.h" #include "base/synchronization/lock.h"
#include "base/values.h" #include "base/values.h"
#include "build/build_config.h"
#include "content/browser/media/session/audio_focus_observer.h"
#include "content/common/content_export.h" #include "content/common/content_export.h"
#include "content/public/browser/notification_observer.h" #include "content/public/browser/notification_observer.h"
#include "content/public/browser/notification_registrar.h" #include "content/public/browser/notification_registrar.h"
...@@ -33,10 +35,15 @@ struct MediaLogEvent; ...@@ -33,10 +35,15 @@ struct MediaLogEvent;
namespace content { namespace content {
enum class AudioFocusType;
// This class stores information about currently active media. // This class stores information about currently active media.
// TODO(crbug.com/812557): Remove inheritance from media::AudioLogFactory once // TODO(crbug.com/812557): Remove inheritance from media::AudioLogFactory once
// the creation of the AudioManager instance moves to the audio service. // the creation of the AudioManager instance moves to the audio service.
class CONTENT_EXPORT MediaInternals : public media::AudioLogFactory, class CONTENT_EXPORT MediaInternals : public media::AudioLogFactory,
#if !defined(OS_ANDROID)
public AudioFocusObserver,
#endif
public NotificationObserver { public NotificationObserver {
public: public:
// Called with the update string. // Called with the update string.
...@@ -74,6 +81,11 @@ class CONTENT_EXPORT MediaInternals : public media::AudioLogFactory, ...@@ -74,6 +81,11 @@ class CONTENT_EXPORT MediaInternals : public media::AudioLogFactory,
// UpdateCallback. // UpdateCallback.
void SendVideoCaptureDeviceCapabilities(); void SendVideoCaptureDeviceCapabilities();
#if !defined(OS_ANDROID)
// Sends all audio focus information to each registered UpdateCallback.
void SendAudioFocusState();
#endif
// Called to inform of the capabilities enumerated for video devices. // Called to inform of the capabilities enumerated for video devices.
void UpdateVideoCaptureDeviceCapabilities( void UpdateVideoCaptureDeviceCapabilities(
const std::vector<std::tuple<media::VideoCaptureDeviceDescriptor, const std::vector<std::tuple<media::VideoCaptureDeviceDescriptor,
...@@ -109,6 +121,12 @@ class CONTENT_EXPORT MediaInternals : public media::AudioLogFactory, ...@@ -109,6 +121,12 @@ class CONTENT_EXPORT MediaInternals : public media::AudioLogFactory,
MediaInternals(); MediaInternals();
#if !defined(OS_ANDROID)
// AudioFocusObserver implementation.
void OnFocusGained(MediaSession* media_session, AudioFocusType type) override;
void OnFocusLost(MediaSession* media_session) override;
#endif
// Sends |update| to each registered UpdateCallback. Safe to call from any // Sends |update| to each registered UpdateCallback. Safe to call from any
// thread, but will forward to the IO thread. // thread, but will forward to the IO thread.
void SendUpdate(const base::string16& update); void SendUpdate(const base::string16& update);
......
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
#include "base/bind.h" #include "base/bind.h"
#include "base/location.h" #include "base/location.h"
#include "build/build_config.h"
#include "content/browser/media/media_internals.h" #include "content/browser/media/media_internals.h"
#include "content/browser/media/media_internals_handler.h" #include "content/browser/media/media_internals_handler.h"
#include "content/public/browser/browser_thread.h" #include "content/public/browser/browser_thread.h"
...@@ -48,6 +49,10 @@ void MediaInternalsProxy::GetEverythingOnIOThread() { ...@@ -48,6 +49,10 @@ void MediaInternalsProxy::GetEverythingOnIOThread() {
// TODO(xhwang): Investigate whether we can update on UI thread directly. // TODO(xhwang): Investigate whether we can update on UI thread directly.
MediaInternals::GetInstance()->SendAudioStreamData(); MediaInternals::GetInstance()->SendAudioStreamData();
MediaInternals::GetInstance()->SendVideoCaptureDeviceCapabilities(); MediaInternals::GetInstance()->SendVideoCaptureDeviceCapabilities();
#if !defined(OS_ANDROID)
MediaInternals::GetInstance()->SendAudioFocusState();
#endif
} }
void MediaInternalsProxy::UpdateUIOnUIThread(const base::string16& update) { void MediaInternalsProxy::UpdateUIOnUIThread(const base::string16& update) {
......
...@@ -12,13 +12,20 @@ ...@@ -12,13 +12,20 @@
#include "base/run_loop.h" #include "base/run_loop.h"
#include "base/strings/stringprintf.h" #include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h" #include "base/strings/utf_string_conversions.h"
#include "base/test/scoped_command_line.h"
#include "base/test/test_message_loop.h" #include "base/test/test_message_loop.h"
#include "base/threading/thread_task_runner_handle.h" #include "base/threading/thread_task_runner_handle.h"
#include "build/build_config.h" #include "build/build_config.h"
#include "content/browser/media/session/audio_focus_manager.h"
#include "content/browser/media/session/audio_focus_type.h"
#include "content/browser/media/session/media_session_impl.h"
#include "content/public/test/test_browser_context.h"
#include "content/public/test/test_browser_thread_bundle.h" #include "content/public/test/test_browser_thread_bundle.h"
#include "content/test/test_web_contents.h"
#include "media/base/audio_parameters.h" #include "media/base/audio_parameters.h"
#include "media/base/channel_layout.h" #include "media/base/channel_layout.h"
#include "media/base/media_log.h" #include "media/base/media_log.h"
#include "media/base/media_switches.h"
#include "testing/gmock/include/gmock/gmock.h" #include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h" #include "testing/gtest/include/gtest/gtest.h"
#include "ui/gfx/geometry/size.h" #include "ui/gfx/geometry/size.h"
...@@ -279,4 +286,139 @@ INSTANTIATE_TEST_CASE_P( ...@@ -279,4 +286,139 @@ INSTANTIATE_TEST_CASE_P(
media::AudioLogFactory::AUDIO_OUTPUT_CONTROLLER, media::AudioLogFactory::AUDIO_OUTPUT_CONTROLLER,
media::AudioLogFactory::AUDIO_OUTPUT_STREAM)); media::AudioLogFactory::AUDIO_OUTPUT_STREAM));
// TODO(https://crbug.com/873320): AudioFocusManager is not available on
// Android.
#if !defined(OS_ANDROID)
namespace {
// Test page titles.
const char kTestTitle1[] = "Test Title 1";
const char kTestTitle2[] = "Test Title 2";
} // namespace
class MediaInternalsAudioFocusTest : public testing::Test,
public MediaInternalsTestBase {
public:
void SetUp() override {
update_cb_ =
base::BindRepeating(&MediaInternalsAudioFocusTest::UpdateCallbackImpl,
base::Unretained(this));
scoped_command_line_.GetProcessCommandLine()->AppendSwitch(
switches::kEnableAudioFocus);
content::MediaInternals::GetInstance()->AddUpdateCallback(update_cb_);
browser_context_.reset(new TestBrowserContext());
}
void TearDown() override {
content::MediaInternals::GetInstance()->RemoveUpdateCallback(update_cb_);
browser_context_.reset();
}
protected:
void ExpectValue(base::ListValue expected_list) {
base::DictionaryValue expected_data;
expected_data.SetKey("sessions", std::move(expected_list));
EXPECT_EQ(expected_data, update_data_);
}
std::unique_ptr<TestWebContents> CreateWebContents() {
return TestWebContents::Create(
browser_context_.get(), SiteInstance::Create(browser_context_.get()));
}
base::Value GetAddressAsValue(MediaSessionImpl* media_session) {
std::stringstream stream;
stream << media_session;
return base::Value(stream.str());
}
void RemoveAllPlayersForTest(MediaSessionImpl* session) {
session->RemoveAllPlayersForTest();
}
MediaInternals::UpdateCallback update_cb_;
private:
base::test::ScopedCommandLine scoped_command_line_;
std::unique_ptr<TestBrowserContext> browser_context_;
};
TEST_F(MediaInternalsAudioFocusTest, AudioFocusStateIsUpdated) {
// Create a test media session and request audio focus.
std::unique_ptr<TestWebContents> web_contents1 = CreateWebContents();
web_contents1->SetTitle(base::UTF8ToUTF16(kTestTitle1));
MediaSessionImpl* media_session1 = MediaSessionImpl::Get(web_contents1.get());
media_session1->RequestSystemAudioFocus(AudioFocusType::Gain);
base::RunLoop().RunUntilIdle();
// Check JSON is what we expect.
{
base::DictionaryValue expected_session;
expected_session.SetKey("name", GetAddressAsValue(media_session1));
expected_session.SetKey("owner", base::Value(kTestTitle1));
expected_session.SetKey("state", base::Value("Active"));
base::ListValue expected_list;
expected_list.GetList().push_back(std::move(expected_session));
ExpectValue(std::move(expected_list));
}
// Create another media session.
std::unique_ptr<TestWebContents> web_contents2 = CreateWebContents();
web_contents2->SetTitle(base::UTF8ToUTF16(kTestTitle2));
MediaSessionImpl* media_session2 = MediaSessionImpl::Get(web_contents2.get());
media_session2->RequestSystemAudioFocus(AudioFocusType::GainTransientMayDuck);
base::RunLoop().RunUntilIdle();
// Check JSON is what we expect.
{
base::DictionaryValue expected_session1;
expected_session1.SetKey("name", GetAddressAsValue(media_session2));
expected_session1.SetKey("owner", base::Value(kTestTitle2));
expected_session1.SetKey("state", base::Value("Active"));
base::DictionaryValue expected_session2;
expected_session2.SetKey("name", GetAddressAsValue(media_session1));
expected_session2.SetKey("owner", base::Value(kTestTitle1));
expected_session2.SetKey("state", base::Value("Active Ducked"));
base::ListValue expected_list;
expected_list.GetList().push_back(std::move(expected_session1));
expected_list.GetList().push_back(std::move(expected_session2));
ExpectValue(std::move(expected_list));
}
// Abandon audio focus.
RemoveAllPlayersForTest(media_session2);
base::RunLoop().RunUntilIdle();
// Check JSON is what we expect.
{
base::DictionaryValue expected_session;
expected_session.SetKey("name", GetAddressAsValue(media_session1));
expected_session.SetKey("owner", base::Value(kTestTitle1));
expected_session.SetKey("state", base::Value("Active"));
base::ListValue expected_list;
expected_list.GetList().push_back(std::move(expected_session));
ExpectValue(std::move(expected_list));
}
// Abandon audio focus.
RemoveAllPlayersForTest(media_session1);
base::RunLoop().RunUntilIdle();
// Check JSON is what we expect.
{
base::ListValue expected_list;
ExpectValue(std::move(expected_list));
}
}
#endif // !defined(OS_ANDROID)
} // namespace content } // namespace content
...@@ -35,6 +35,9 @@ class CONTENT_EXPORT AudioFocusManager { ...@@ -35,6 +35,9 @@ class CONTENT_EXPORT AudioFocusManager {
friend struct base::DefaultSingletonTraits<AudioFocusManager>; friend struct base::DefaultSingletonTraits<AudioFocusManager>;
friend class AudioFocusManagerTest; friend class AudioFocusManagerTest;
// Media internals UI needs access to internal state.
friend class MediaInternals;
AudioFocusManager(); AudioFocusManager();
~AudioFocusManager(); ~AudioFocusManager();
......
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
#include <algorithm> #include <algorithm>
#include "base/numerics/ranges.h" #include "base/numerics/ranges.h"
#include "base/strings/string_util.h"
#include "content/browser/media/session/audio_focus_delegate.h" #include "content/browser/media/session/audio_focus_delegate.h"
#include "content/browser/media/session/audio_focus_type.h" #include "content/browser/media/session/audio_focus_type.h"
#include "content/browser/media/session/media_session_controller.h" #include "content/browser/media/session/media_session_controller.h"
...@@ -33,6 +34,12 @@ namespace { ...@@ -33,6 +34,12 @@ namespace {
const double kUnduckedVolumeMultiplier = 1.0; const double kUnduckedVolumeMultiplier = 1.0;
const double kDefaultDuckingVolumeMultiplier = 0.2; const double kDefaultDuckingVolumeMultiplier = 0.2;
const char kDebugInfoOwnerSeparator[] = " - ";
const char kDebugInfoDucked[] = "Ducked";
const char kDebugInfoActive[] = "Active";
const char kDebugInfoInactive[] = "Inactive";
const char kDebugInfoStateSeparator[] = " ";
using MapRenderFrameHostToDepth = std::map<RenderFrameHost*, size_t>; using MapRenderFrameHostToDepth = std::map<RenderFrameHost*, size_t>;
size_t ComputeFrameDepth(RenderFrameHost* rfh, size_t ComputeFrameDepth(RenderFrameHost* rfh,
...@@ -73,6 +80,13 @@ MediaSessionUserAction MediaSessionActionToUserAction( ...@@ -73,6 +80,13 @@ MediaSessionUserAction MediaSessionActionToUserAction(
return MediaSessionUserAction::Count; return MediaSessionUserAction::Count;
} }
// If the string is not empty then push it to the back of a vector.
void MaybePushBackString(std::vector<std::string>& vector,
const std::string& str) {
if (!str.empty())
vector.push_back(str);
}
} // anonymous namespace } // anonymous namespace
using MediaSessionSuspendedSource = using MediaSessionSuspendedSource =
...@@ -585,6 +599,32 @@ bool MediaSessionImpl::RequestSystemAudioFocus( ...@@ -585,6 +599,32 @@ bool MediaSessionImpl::RequestSystemAudioFocus(
return result; return result;
} }
const MediaSessionImpl::DebugInfo MediaSessionImpl::GetDebugInfo() {
MediaSessionImpl::DebugInfo debug_info;
// Convert the address of |this| to a string and use it as the name.
std::stringstream stream;
stream << this;
debug_info.name = stream.str();
// Add the title and the url to the owner.
std::vector<std::string> owner_parts;
MaybePushBackString(owner_parts,
base::UTF16ToUTF8(web_contents()->GetTitle()));
MaybePushBackString(owner_parts,
web_contents()->GetLastCommittedURL().spec());
debug_info.owner = base::JoinString(owner_parts, kDebugInfoOwnerSeparator);
// Add the ducking state to state.
std::vector<std::string> state_parts;
MaybePushBackString(state_parts,
IsActive() ? kDebugInfoActive : kDebugInfoInactive);
MaybePushBackString(state_parts, is_ducking_ ? kDebugInfoDucked : "");
debug_info.state = base::JoinString(state_parts, kDebugInfoStateSeparator);
return debug_info;
}
void MediaSessionImpl::AbandonSystemAudioFocusIfNeeded() { void MediaSessionImpl::AbandonSystemAudioFocusIfNeeded() {
if (audio_focus_state_ == State::INACTIVE || !normal_players_.empty() || if (audio_focus_state_ == State::INACTIVE || !normal_players_.empty() ||
!pepper_players_.empty() || !one_shot_players_.empty()) { !pepper_players_.empty() || !one_shot_players_.empty()) {
......
...@@ -205,6 +205,19 @@ class MediaSessionImpl : public MediaSession, ...@@ -205,6 +205,19 @@ class MediaSessionImpl : public MediaSession,
// Returns whether the request was granted. // Returns whether the request was granted.
CONTENT_EXPORT bool RequestSystemAudioFocus(AudioFocusType audio_focus_type); CONTENT_EXPORT bool RequestSystemAudioFocus(AudioFocusType audio_focus_type);
// Returns debugging information to be displayed on chrome://media-internals.
struct DebugInfo {
// A unique name for the MediaSession.
std::string name;
// The title and URL of the owning WebContents.
std::string owner;
// State information stored in a string e.g. Ducked.
std::string state;
};
const DebugInfo GetDebugInfo();
private: private:
friend class content::WebContentsUserData<MediaSessionImpl>; friend class content::WebContentsUserData<MediaSessionImpl>;
friend class ::MediaSessionImplBrowserTest; friend class ::MediaSessionImplBrowserTest;
...@@ -213,6 +226,7 @@ class MediaSessionImpl : public MediaSession, ...@@ -213,6 +226,7 @@ class MediaSessionImpl : public MediaSession,
friend class content::MediaSessionImplServiceRoutingTest; friend class content::MediaSessionImplServiceRoutingTest;
friend class content::MediaSessionImplStateObserver; friend class content::MediaSessionImplStateObserver;
friend class content::MediaSessionServiceImplBrowserTest; friend class content::MediaSessionServiceImplBrowserTest;
friend class MediaInternalsAudioFocusTest;
CONTENT_EXPORT void SetDelegateForTests( CONTENT_EXPORT void SetDelegateForTests(
std::unique_ptr<AudioFocusDelegate> delegate); std::unique_ptr<AudioFocusDelegate> delegate);
......
...@@ -16,6 +16,8 @@ var ClientRenderer = (function() { ...@@ -16,6 +16,8 @@ var ClientRenderer = (function() {
this.logTable = logElement.querySelector('tbody'); this.logTable = logElement.querySelector('tbody');
this.graphElement = document.getElementById('graphs'); this.graphElement = document.getElementById('graphs');
this.audioPropertyName = document.getElementById('audio-property-name'); this.audioPropertyName = document.getElementById('audio-property-name');
this.audioFocusSessionListElement_ =
document.getElementById('audio-focus-session-list');
this.players = null; this.players = null;
this.selectedPlayer = null; this.selectedPlayer = null;
...@@ -138,6 +140,19 @@ var ClientRenderer = (function() { ...@@ -138,6 +140,19 @@ var ClientRenderer = (function() {
} }
}, },
/**
* Called when the list of audio focus sessions has changed.
* @param sessions A list of media sessions that contain the current state.
*/
audioFocusSessionUpdated: function(sessions) {
removeChildren(this.audioFocusSessionListElement_);
sessions.forEach(session => {
this.audioFocusSessionListElement_.appendChild(
this.createAudioFocusSessionRow_(session));
});
},
/** /**
* Called when an audio component is removed from the collection. * Called when an audio component is removed from the collection.
* @param componentType Integer AudioComponent enum value; must match values * @param componentType Integer AudioComponent enum value; must match values
...@@ -528,6 +543,15 @@ var ClientRenderer = (function() { ...@@ -528,6 +543,15 @@ var ClientRenderer = (function() {
this.drawLog_(); this.drawLog_();
} }
}, },
createAudioFocusSessionRow_: function(session) {
const template = $('audio-focus-session-row');
const span = template.content.querySelectorAll('span');
span[0].textContent = session.name;
span[1].textContent = session.owner;
span[2].textContent = session.state;
return document.importNode(template.content, true);
},
}; };
return ClientRenderer; return ClientRenderer;
......
...@@ -27,6 +27,13 @@ var media = (function() { ...@@ -27,6 +27,13 @@ var media = (function() {
manager.updateVideoCaptureCapabilities(videoCaptureCapabilities); manager.updateVideoCaptureCapabilities(videoCaptureCapabilities);
}; };
media.onReceiveAudioFocusState = function(audioFocusState) {
if (!audioFocusState)
return;
manager.updateAudioFocusSessions(audioFocusState.sessions);
};
media.updateAudioComponent = function(component) { media.updateAudioComponent = function(component) {
var uniqueComponentId = component.owner_id + ':' + component.component_id; var uniqueComponentId = component.owner_id + ':' + component.component_id;
switch (component.status) { switch (component.status) {
......
...@@ -40,6 +40,14 @@ var Manager = (function() { ...@@ -40,6 +40,14 @@ var Manager = (function() {
} }
Manager.prototype = { Manager.prototype = {
/**
* Updates the audio focus state.
* @param sessions A list of media sessions that contain the current state.
*/
updateAudioFocusSessions: function(sessions) {
this.clientRenderer_.audioFocusSessionUpdated(sessions);
},
/** /**
* Updates an audio-component. * Updates an audio-component.
* @param componentType Integer AudioComponent enum value; must match values * @param componentType Integer AudioComponent enum value; must match values
......
...@@ -178,11 +178,9 @@ h3 { ...@@ -178,11 +178,9 @@ h3 {
color: rgba(0, 0, 0, .5); color: rgba(0, 0, 0, .5);
} }
label.audio-focus-session,
label.selectable-button { label.selectable-button {
-webkit-user-select: none;
display: inline-block; display: inline-block;
background: #BDF;
cursor: pointer;
border: solid 1px #999; border: solid 1px #999;
border-radius: 3px; border-radius: 3px;
padding: 6px; padding: 6px;
...@@ -191,6 +189,12 @@ label.selectable-button { ...@@ -191,6 +189,12 @@ label.selectable-button {
word-break: break-all; word-break: break-all;
} }
label.selectable-button {
background: #BDF;
cursor: pointer;
user-select: none;
}
input.selectable-button { input.selectable-button {
display: none; display: none;
} }
...@@ -205,6 +209,7 @@ input.selectable-button:hover + label.selectable-button { ...@@ -205,6 +209,7 @@ input.selectable-button:hover + label.selectable-button {
border-color: #666; border-color: #666;
} }
label.audio-focus-session,
label.destructed-player { label.destructed-player {
background-color: #EEE; background-color: #EEE;
} }
...@@ -226,3 +231,6 @@ label.destructed-player { ...@@ -226,3 +231,6 @@ label.destructed-player {
display: none; display: none;
} }
#audio-focus-session-list {
list-style: none;
}
...@@ -25,6 +25,7 @@ found in the LICENSE file. ...@@ -25,6 +25,7 @@ found in the LICENSE file.
<tab>Players</tab> <tab>Players</tab>
<tab>Audio</tab> <tab>Audio</tab>
<tab>Video Capture</tab> <tab>Video Capture</tab>
<tab>Audio Focus</tab>
</tabs> </tabs>
<tabpanels> <tabpanels>
<tabpanel id="players"> <tabpanel id="players">
...@@ -120,6 +121,21 @@ found in the LICENSE file. ...@@ -120,6 +121,21 @@ found in the LICENSE file.
</table> </table>
</div> </div>
</tabpanel> </tabpanel>
<tabpanel id="players">
<div id="list-wrapper">
<h2>Active Sessions</h2>
<ul id="audio-focus-session-list" class="show-none-if-empty"></ul>
</div>
<template id="audio-focus-session-row">
<li>
<label class="audio-focus-session">
<span class="player-name"></span><br />
<span class="player-frame"></span><br />
<span class="player-desc"></span>
</label>
</li>
</template>
</tabpanel>
</tabpanels> </tabpanels>
</tabbox> </tabbox>
<dialog id="clipboard-dialog"> <dialog id="clipboard-dialog">
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment