Reland "Letting Flash join MediaSession (stack implementaion)"
This CL lets Flash join and be managed by MediaSession. Flash will take "Gain" audio focus, and sessions with Flash will duck instead of being suspended. Test flags: --enable-default-media-session --ppapi-flash-path=/opt/google/chrome/PepperFlash/libpepflashplayer.so --enable-features=flash-join-media-session Test URL (can open multiple tabs and try): http://xxyzzzq.github.io/sandbox/media-session/flash-test.html BUG=619084 TBR=avayvod@chromium.org,jochen@chromium.org,mlamouri@chromium.org Review-Url: https://codereview.chromium.org/2381963004 Cr-Commit-Position: refs/heads/master@{#422142}
This commit is contained in:
@ -1718,6 +1718,8 @@ source_set("browser") {
|
||||
sources -= [
|
||||
"browser_ipc_logging.cc",
|
||||
"device_sensors/data_fetcher_shared_memory_default.cc",
|
||||
"media/session/audio_focus_manager.cc",
|
||||
"media/session/audio_focus_manager.h",
|
||||
"media/session/media_session_delegate_default.cc",
|
||||
"power_usage_monitor_impl.cc",
|
||||
"power_usage_monitor_impl.h",
|
||||
|
@ -4,28 +4,12 @@
|
||||
|
||||
#include "content/browser/media/session/audio_focus_manager.h"
|
||||
|
||||
#include "base/memory/ptr_util.h"
|
||||
#include "content/browser/media/session/media_session.h"
|
||||
#include "content/public/browser/web_contents.h"
|
||||
|
||||
namespace content {
|
||||
|
||||
AudioFocusManager::AudioFocusEntry::AudioFocusEntry(
|
||||
WebContents* web_contents,
|
||||
AudioFocusManager* audio_focus_manager,
|
||||
AudioFocusType type)
|
||||
: WebContentsObserver(web_contents),
|
||||
audio_focus_manager_(audio_focus_manager) {}
|
||||
|
||||
AudioFocusManager::AudioFocusType
|
||||
AudioFocusManager::AudioFocusEntry::type() const {
|
||||
return type_;
|
||||
}
|
||||
|
||||
void AudioFocusManager::AudioFocusEntry::WebContentsDestroyed() {
|
||||
audio_focus_manager_->OnWebContentsDestroyed(web_contents());
|
||||
// |this| will be destroyed now.
|
||||
}
|
||||
|
||||
// static
|
||||
AudioFocusManager* AudioFocusManager::GetInstance() {
|
||||
return base::Singleton<AudioFocusManager>::get();
|
||||
@ -33,83 +17,77 @@ AudioFocusManager* AudioFocusManager::GetInstance() {
|
||||
|
||||
void AudioFocusManager::RequestAudioFocus(MediaSession* media_session,
|
||||
AudioFocusType type) {
|
||||
WebContents* web_contents = media_session->web_contents();
|
||||
|
||||
if (type == AudioFocusType::GainTransientMayDuck) {
|
||||
MaybeRemoveFocusEntry(web_contents);
|
||||
transient_entries_[web_contents].reset(
|
||||
new AudioFocusEntry(web_contents, this, type));
|
||||
MaybeStartDucking();
|
||||
if (!audio_focus_stack_.empty() &&
|
||||
audio_focus_stack_.back() == media_session &&
|
||||
audio_focus_stack_.back()->audio_focus_type() == type &&
|
||||
audio_focus_stack_.back()->IsActive()) {
|
||||
// Early returning if |media_session| is already on top (has focus) and is
|
||||
// active.
|
||||
return;
|
||||
}
|
||||
|
||||
DCHECK(type == AudioFocusType::Gain);
|
||||
RequestAudioFocusGain(web_contents);
|
||||
MaybeRemoveFocusEntry(media_session);
|
||||
|
||||
// TODO(zqzhang): It seems like MediaSession is exposed to AudioFocusManager
|
||||
// too much. Maybe it's better to do some abstraction and refactoring to clean
|
||||
// up the relation between AudioFocusManager and MediaSession.
|
||||
// See https://crbug.com/651069
|
||||
if (type == AudioFocusType::GainTransientMayDuck) {
|
||||
for (const auto old_session : audio_focus_stack_) {
|
||||
old_session->StartDucking();
|
||||
}
|
||||
} else {
|
||||
for (const auto old_session : audio_focus_stack_) {
|
||||
if (old_session->IsActive()) {
|
||||
if (old_session->HasPepper())
|
||||
old_session->StartDucking();
|
||||
else
|
||||
old_session->Suspend(MediaSession::SuspendType::SYSTEM);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
audio_focus_stack_.push_back(media_session);
|
||||
audio_focus_stack_.back()->StopDucking();
|
||||
}
|
||||
|
||||
void AudioFocusManager::AbandonAudioFocus(MediaSession* media_session) {
|
||||
AbandonAudioFocusInternal(media_session->web_contents());
|
||||
if (audio_focus_stack_.empty())
|
||||
return;
|
||||
|
||||
if (audio_focus_stack_.back() != media_session) {
|
||||
MaybeRemoveFocusEntry(media_session);
|
||||
return;
|
||||
}
|
||||
|
||||
audio_focus_stack_.pop_back();
|
||||
if (audio_focus_stack_.empty())
|
||||
return;
|
||||
|
||||
// Allow the top-most MediaSession having Pepper to unduck pepper even if it's
|
||||
// not active.
|
||||
for (auto iter = audio_focus_stack_.rbegin();
|
||||
iter != audio_focus_stack_.rend(); ++iter) {
|
||||
if (!(*iter)->HasPepper())
|
||||
continue;
|
||||
|
||||
MediaSession* pepper_session = *iter;
|
||||
pepper_session->StopDucking();
|
||||
MaybeRemoveFocusEntry(pepper_session);
|
||||
audio_focus_stack_.push_back(pepper_session);
|
||||
return;
|
||||
}
|
||||
// Only try to unduck the new MediaSession on top. The session might be still
|
||||
// inactive but it will not be resumed (so it doesn't surprise the user).
|
||||
audio_focus_stack_.back()->StopDucking();
|
||||
}
|
||||
|
||||
AudioFocusManager::AudioFocusManager() = default;
|
||||
|
||||
AudioFocusManager::~AudioFocusManager() = default;
|
||||
|
||||
void AudioFocusManager::RequestAudioFocusGain(WebContents* web_contents) {
|
||||
MaybeRemoveTransientEntry(web_contents);
|
||||
|
||||
if (focus_entry_) {
|
||||
if (focus_entry_->web_contents() == web_contents)
|
||||
return;
|
||||
|
||||
MediaSession* other_session =
|
||||
MediaSession::Get(focus_entry_->web_contents());
|
||||
if (other_session->IsActive())
|
||||
other_session->Suspend(MediaSession::SuspendType::SYSTEM);
|
||||
}
|
||||
|
||||
focus_entry_.reset(
|
||||
new AudioFocusEntry(web_contents, this, AudioFocusType::Gain));
|
||||
MaybeStartDucking();
|
||||
}
|
||||
|
||||
void AudioFocusManager::OnWebContentsDestroyed(WebContents* web_contents) {
|
||||
AbandonAudioFocusInternal(web_contents);
|
||||
}
|
||||
|
||||
void AudioFocusManager::AbandonAudioFocusInternal(WebContents* web_contents) {
|
||||
MaybeRemoveTransientEntry(web_contents);
|
||||
MaybeRemoveFocusEntry(web_contents);
|
||||
}
|
||||
|
||||
void AudioFocusManager::MaybeStartDucking() const {
|
||||
if (TransientMayDuckEntriesCount() != 1 || !focus_entry_)
|
||||
return;
|
||||
|
||||
MediaSession::Get(focus_entry_->web_contents())->StartDucking();
|
||||
}
|
||||
|
||||
void AudioFocusManager::MaybeStopDucking() const {
|
||||
if (TransientMayDuckEntriesCount() != 0 || !focus_entry_)
|
||||
return;
|
||||
|
||||
MediaSession::Get(focus_entry_->web_contents())->StopDucking();
|
||||
}
|
||||
|
||||
int AudioFocusManager::TransientMayDuckEntriesCount() const {
|
||||
return transient_entries_.size();
|
||||
}
|
||||
|
||||
void AudioFocusManager::MaybeRemoveTransientEntry(WebContents* web_contents) {
|
||||
transient_entries_.erase(web_contents);
|
||||
MaybeStopDucking();
|
||||
}
|
||||
|
||||
void AudioFocusManager::MaybeRemoveFocusEntry(WebContents* web_contents) {
|
||||
if (focus_entry_ && focus_entry_->web_contents() == web_contents) {
|
||||
MediaSession::Get(focus_entry_->web_contents())->StopDucking();
|
||||
focus_entry_.reset();
|
||||
}
|
||||
void AudioFocusManager::MaybeRemoveFocusEntry(MediaSession* media_session) {
|
||||
audio_focus_stack_.remove(media_session);
|
||||
}
|
||||
|
||||
} // namespace content
|
||||
|
@ -5,6 +5,7 @@
|
||||
#ifndef CONTENT_BROWSER_MEDIA_SESSION_AUDIO_FOCUS_MANAGER_H_
|
||||
#define CONTENT_BROWSER_MEDIA_SESSION_AUDIO_FOCUS_MANAGER_H_
|
||||
|
||||
#include <list>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "base/memory/singleton.h"
|
||||
@ -14,7 +15,6 @@
|
||||
namespace content {
|
||||
|
||||
class MediaSession;
|
||||
class WebContents;
|
||||
|
||||
class CONTENT_EXPORT AudioFocusManager {
|
||||
public:
|
||||
@ -34,61 +34,14 @@ class CONTENT_EXPORT AudioFocusManager {
|
||||
friend struct base::DefaultSingletonTraits<AudioFocusManager>;
|
||||
friend class AudioFocusManagerTest;
|
||||
|
||||
// TODO(mlamouri): in order to allow multiple MediaSession per WebContents, we
|
||||
// will have to keep track of MediaSession's. Though, we can easily keep track
|
||||
// of WebContents' life time right now but not MediaSession's.
|
||||
class AudioFocusEntry : public WebContentsObserver {
|
||||
public:
|
||||
AudioFocusEntry(WebContents* web_contents,
|
||||
AudioFocusManager* audio_focus_manager,
|
||||
AudioFocusType type);
|
||||
|
||||
AudioFocusType type() const;
|
||||
|
||||
private:
|
||||
// WebContentsObserver implementation.
|
||||
void WebContentsDestroyed() override;
|
||||
|
||||
AudioFocusManager* audio_focus_manager_; // Owns |this|.
|
||||
AudioFocusType type_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(AudioFocusEntry);
|
||||
};
|
||||
|
||||
AudioFocusManager();
|
||||
~AudioFocusManager();
|
||||
|
||||
void OnWebContentsDestroyed(WebContents* web_contents);
|
||||
void MaybeRemoveFocusEntry(MediaSession* media_session);
|
||||
|
||||
// Internal usage of AbandonAudioFocus using WebContents.
|
||||
void AbandonAudioFocusInternal(WebContents* web_contents);
|
||||
|
||||
// This method is meant to be called when a new session is of type
|
||||
// GainTransientMayDuck. If it is the first one, other clients will be asked
|
||||
// to duck.
|
||||
void MaybeStartDucking() const;
|
||||
|
||||
// This method is meant to be called when a session is no longer of type
|
||||
// GainTransientMayDuck. If it was the last one, other clients will be asked
|
||||
// to no longer duck.
|
||||
void MaybeStopDucking() const;
|
||||
|
||||
// Returns how many sessions require current audio focused session to duck.
|
||||
int TransientMayDuckEntriesCount() const;
|
||||
|
||||
// Internal method to request audio focus of type AudioFocusType::Gain.
|
||||
void RequestAudioFocusGain(WebContents* web_contents);
|
||||
|
||||
// Removes the entry associated with |web_contents| from the
|
||||
// |transient_entries_| if there is one.
|
||||
void MaybeRemoveTransientEntry(WebContents* web_contents);
|
||||
|
||||
// Removes the focused session if it is associated with |web_contents|.
|
||||
void MaybeRemoveFocusEntry(WebContents* web_contents);
|
||||
|
||||
std::unordered_map<WebContents*, std::unique_ptr<AudioFocusEntry>>
|
||||
transient_entries_;
|
||||
std::unique_ptr<AudioFocusEntry> focus_entry_;
|
||||
// Weak reference of managed MediaSessions. A MediaSession must abandon audio
|
||||
// foucs before its destruction.
|
||||
std::list<MediaSession*> audio_focus_stack_;
|
||||
};
|
||||
|
||||
} // namespace content
|
||||
|
@ -4,14 +4,30 @@
|
||||
|
||||
#include "content/browser/media/session/audio_focus_manager.h"
|
||||
|
||||
#include "base/command_line.h"
|
||||
#include "content/browser/media/session/media_session.h"
|
||||
#include "content/browser/media/session/media_session_observer.h"
|
||||
#include "content/public/test/mock_render_process_host.h"
|
||||
#include "content/public/test/test_browser_context.h"
|
||||
#include "content/public/test/test_browser_thread.h"
|
||||
#include "content/test/test_web_contents.h"
|
||||
#include "media/base/media_content_type.h"
|
||||
#include "media/base/media_switches.h"
|
||||
|
||||
namespace content {
|
||||
|
||||
namespace {
|
||||
|
||||
class MockMediaSessionObserver : public MediaSessionObserver {
|
||||
public:
|
||||
void OnSuspend(int player_id) override {}
|
||||
void OnResume(int player_id) override {}
|
||||
void OnSetVolumeMultiplier(
|
||||
int player_id, double volume_multiplier) override {}
|
||||
};
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
using AudioFocusType = AudioFocusManager::AudioFocusType;
|
||||
using SuspendType = MediaSession::SuspendType;
|
||||
|
||||
@ -20,9 +36,12 @@ class AudioFocusManagerTest : public testing::Test {
|
||||
AudioFocusManagerTest() : ui_thread_(BrowserThread::UI, &message_loop_) {}
|
||||
|
||||
void SetUp() override {
|
||||
base::CommandLine::ForCurrentProcess()->AppendSwitch(
|
||||
switches::kEnableDefaultMediaSession);
|
||||
rph_factory_.reset(new MockRenderProcessHostFactory());
|
||||
SiteInstanceImpl::set_render_process_host_factory(rph_factory_.get());
|
||||
browser_context_.reset(new TestBrowserContext());
|
||||
pepper_observer_.reset(new MockMediaSessionObserver());
|
||||
}
|
||||
|
||||
void TearDown() override {
|
||||
@ -31,25 +50,55 @@ class AudioFocusManagerTest : public testing::Test {
|
||||
rph_factory_.reset();
|
||||
}
|
||||
|
||||
WebContents* GetAudioFocusedContent() const {
|
||||
if (!AudioFocusManager::GetInstance()->focus_entry_)
|
||||
return nullptr;
|
||||
return AudioFocusManager::GetInstance()->focus_entry_->web_contents();
|
||||
MediaSession* GetAudioFocusedSession() const {
|
||||
const auto& audio_focus_stack =
|
||||
AudioFocusManager::GetInstance()->audio_focus_stack_;
|
||||
for (auto iter = audio_focus_stack.rbegin();
|
||||
iter != audio_focus_stack.rend(); ++iter) {
|
||||
if ((*iter)->audio_focus_type() ==
|
||||
AudioFocusManager::AudioFocusType::Gain)
|
||||
return (*iter);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int GetTransientMaybeDuckCount() const {
|
||||
return AudioFocusManager::GetInstance()->TransientMayDuckEntriesCount();
|
||||
int count = 0;
|
||||
const auto& audio_focus_stack =
|
||||
AudioFocusManager::GetInstance()->audio_focus_stack_;
|
||||
for (auto iter = audio_focus_stack.rbegin();
|
||||
iter != audio_focus_stack.rend(); ++iter) {
|
||||
if ((*iter)->audio_focus_type() ==
|
||||
AudioFocusManager::AudioFocusType::GainTransientMayDuck) {
|
||||
++count;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
double IsSessionDucking(MediaSession* session) {
|
||||
return session->is_ducking_; // Quack! Quack!
|
||||
}
|
||||
|
||||
void RequestAudioFocus(MediaSession* session,
|
||||
AudioFocusManager::AudioFocusType audio_focus_type) {
|
||||
session->RequestSystemAudioFocus(audio_focus_type);
|
||||
}
|
||||
|
||||
void AbandonAudioFocus(MediaSession* session) {
|
||||
session->AbandonSystemAudioFocusIfNeeded();
|
||||
}
|
||||
|
||||
WebContents* CreateWebContents() {
|
||||
return TestWebContents::Create(browser_context_.get(),
|
||||
SiteInstance::SiteInstance::Create(browser_context_.get()));
|
||||
}
|
||||
|
||||
std::unique_ptr<MediaSessionObserver> pepper_observer_;
|
||||
|
||||
private:
|
||||
base::MessageLoopForUI message_loop_;
|
||||
TestBrowserThread ui_thread_;
|
||||
@ -74,48 +123,42 @@ TEST_F(AudioFocusManagerTest, RequestAudioFocusGain_ReplaceFocusedEntry) {
|
||||
std::unique_ptr<WebContents> web_contents_3(CreateWebContents());
|
||||
MediaSession* media_session_3 = MediaSession::Get(web_contents_3.get());
|
||||
|
||||
ASSERT_EQ(nullptr, GetAudioFocusedContent());
|
||||
ASSERT_EQ(nullptr, GetAudioFocusedSession());
|
||||
|
||||
AudioFocusManager::GetInstance()->RequestAudioFocus(
|
||||
media_session_1, AudioFocusManager::AudioFocusType::Gain);
|
||||
ASSERT_EQ(web_contents_1.get(), GetAudioFocusedContent());
|
||||
RequestAudioFocus(media_session_1, AudioFocusManager::AudioFocusType::Gain);
|
||||
ASSERT_EQ(media_session_1, GetAudioFocusedSession());
|
||||
|
||||
AudioFocusManager::GetInstance()->RequestAudioFocus(
|
||||
media_session_2, AudioFocusManager::AudioFocusType::Gain);
|
||||
ASSERT_EQ(web_contents_2.get(), GetAudioFocusedContent());
|
||||
RequestAudioFocus(media_session_2, AudioFocusManager::AudioFocusType::Gain);
|
||||
ASSERT_EQ(media_session_2, GetAudioFocusedSession());
|
||||
|
||||
AudioFocusManager::GetInstance()->RequestAudioFocus(
|
||||
media_session_3, AudioFocusManager::AudioFocusType::Gain);
|
||||
ASSERT_EQ(web_contents_3.get(), GetAudioFocusedContent());
|
||||
RequestAudioFocus(media_session_3, AudioFocusManager::AudioFocusType::Gain);
|
||||
ASSERT_EQ(media_session_3, GetAudioFocusedSession());
|
||||
}
|
||||
|
||||
TEST_F(AudioFocusManagerTest, RequestAudioFocusGain_Duplicate) {
|
||||
std::unique_ptr<WebContents> web_contents(CreateWebContents());
|
||||
MediaSession* media_session = MediaSession::Get(web_contents.get());
|
||||
|
||||
ASSERT_EQ(nullptr, GetAudioFocusedContent());
|
||||
ASSERT_EQ(nullptr, GetAudioFocusedSession());
|
||||
|
||||
AudioFocusManager::GetInstance()->RequestAudioFocus(
|
||||
media_session, AudioFocusManager::AudioFocusType::Gain);
|
||||
ASSERT_EQ(web_contents.get(), GetAudioFocusedContent());
|
||||
RequestAudioFocus(media_session, AudioFocusManager::AudioFocusType::Gain);
|
||||
ASSERT_EQ(media_session, GetAudioFocusedSession());
|
||||
|
||||
AudioFocusManager::GetInstance()->RequestAudioFocus(
|
||||
media_session, AudioFocusManager::AudioFocusType::Gain);
|
||||
ASSERT_EQ(web_contents.get(), GetAudioFocusedContent());
|
||||
RequestAudioFocus(media_session, AudioFocusManager::AudioFocusType::Gain);
|
||||
ASSERT_EQ(media_session, GetAudioFocusedSession());
|
||||
}
|
||||
|
||||
TEST_F(AudioFocusManagerTest, RequestAudioFocusGain_FromTransient) {
|
||||
std::unique_ptr<WebContents> web_contents(CreateWebContents());
|
||||
MediaSession* media_session = MediaSession::Get(web_contents.get());
|
||||
|
||||
AudioFocusManager::GetInstance()->RequestAudioFocus(
|
||||
RequestAudioFocus(
|
||||
media_session, AudioFocusManager::AudioFocusType::GainTransientMayDuck);
|
||||
ASSERT_EQ(nullptr, GetAudioFocusedContent());
|
||||
ASSERT_EQ(nullptr, GetAudioFocusedSession());
|
||||
ASSERT_EQ(1, GetTransientMaybeDuckCount());
|
||||
|
||||
AudioFocusManager::GetInstance()->RequestAudioFocus(
|
||||
media_session, AudioFocusManager::AudioFocusType::Gain);
|
||||
ASSERT_EQ(web_contents.get(), GetAudioFocusedContent());
|
||||
RequestAudioFocus(media_session, AudioFocusManager::AudioFocusType::Gain);
|
||||
ASSERT_EQ(media_session, GetAudioFocusedSession());
|
||||
ASSERT_EQ(0, GetTransientMaybeDuckCount());
|
||||
}
|
||||
|
||||
@ -123,14 +166,13 @@ TEST_F(AudioFocusManagerTest, RequestAudioFocusTransient_FromGain) {
|
||||
std::unique_ptr<WebContents> web_contents(CreateWebContents());
|
||||
MediaSession* media_session = MediaSession::Get(web_contents.get());
|
||||
|
||||
AudioFocusManager::GetInstance()->RequestAudioFocus(
|
||||
media_session, AudioFocusManager::AudioFocusType::Gain);
|
||||
ASSERT_EQ(web_contents.get(), GetAudioFocusedContent());
|
||||
RequestAudioFocus(media_session, AudioFocusManager::AudioFocusType::Gain);
|
||||
ASSERT_EQ(media_session, GetAudioFocusedSession());
|
||||
ASSERT_EQ(0, GetTransientMaybeDuckCount());
|
||||
|
||||
AudioFocusManager::GetInstance()->RequestAudioFocus(
|
||||
RequestAudioFocus(
|
||||
media_session, AudioFocusManager::AudioFocusType::GainTransientMayDuck);
|
||||
ASSERT_EQ(nullptr, GetAudioFocusedContent());
|
||||
ASSERT_EQ(nullptr, GetAudioFocusedSession());
|
||||
ASSERT_EQ(1, GetTransientMaybeDuckCount());
|
||||
ASSERT_FALSE(IsSessionDucking(media_session));
|
||||
}
|
||||
@ -142,17 +184,16 @@ TEST_F(AudioFocusManagerTest, RequestAudioFocusTransient_FromGainWhileDucking) {
|
||||
std::unique_ptr<WebContents> web_contents_2(CreateWebContents());
|
||||
MediaSession* media_session_2 = MediaSession::Get(web_contents_2.get());
|
||||
|
||||
AudioFocusManager::GetInstance()->RequestAudioFocus(
|
||||
media_session_1, AudioFocusManager::AudioFocusType::Gain);
|
||||
RequestAudioFocus(media_session_1, AudioFocusManager::AudioFocusType::Gain);
|
||||
ASSERT_EQ(0, GetTransientMaybeDuckCount());
|
||||
ASSERT_FALSE(IsSessionDucking(media_session_1));
|
||||
|
||||
AudioFocusManager::GetInstance()->RequestAudioFocus(
|
||||
RequestAudioFocus(
|
||||
media_session_2, AudioFocusManager::AudioFocusType::GainTransientMayDuck);
|
||||
ASSERT_EQ(1, GetTransientMaybeDuckCount());
|
||||
ASSERT_TRUE(IsSessionDucking(media_session_1));
|
||||
|
||||
AudioFocusManager::GetInstance()->RequestAudioFocus(
|
||||
RequestAudioFocus(
|
||||
media_session_1, AudioFocusManager::AudioFocusType::GainTransientMayDuck);
|
||||
ASSERT_EQ(2, GetTransientMaybeDuckCount());
|
||||
ASSERT_FALSE(IsSessionDucking(media_session_1));
|
||||
@ -162,31 +203,30 @@ TEST_F(AudioFocusManagerTest, AbandonAudioFocus_RemovesFocusedEntry) {
|
||||
std::unique_ptr<WebContents> web_contents(CreateWebContents());
|
||||
MediaSession* media_session = MediaSession::Get(web_contents.get());
|
||||
|
||||
AudioFocusManager::GetInstance()->RequestAudioFocus(
|
||||
media_session, AudioFocusManager::AudioFocusType::Gain);
|
||||
ASSERT_EQ(web_contents.get(), GetAudioFocusedContent());
|
||||
RequestAudioFocus(media_session, AudioFocusManager::AudioFocusType::Gain);
|
||||
ASSERT_EQ(media_session, GetAudioFocusedSession());
|
||||
|
||||
AudioFocusManager::GetInstance()->AbandonAudioFocus(media_session);
|
||||
ASSERT_EQ(nullptr, GetAudioFocusedContent());
|
||||
AbandonAudioFocus(media_session);
|
||||
ASSERT_EQ(nullptr, GetAudioFocusedSession());
|
||||
}
|
||||
|
||||
TEST_F(AudioFocusManagerTest, AbandonAudioFocus_NoAssociatedEntry) {
|
||||
std::unique_ptr<WebContents> web_contents(CreateWebContents());
|
||||
MediaSession* media_session = MediaSession::Get(web_contents.get());
|
||||
|
||||
AudioFocusManager::GetInstance()->AbandonAudioFocus(media_session);
|
||||
ASSERT_EQ(nullptr, GetAudioFocusedContent());
|
||||
AbandonAudioFocus(media_session);
|
||||
ASSERT_EQ(nullptr, GetAudioFocusedSession());
|
||||
}
|
||||
|
||||
TEST_F(AudioFocusManagerTest, AbandonAudioFocus_RemovesTransientEntry) {
|
||||
std::unique_ptr<WebContents> web_contents(CreateWebContents());
|
||||
MediaSession* media_session = MediaSession::Get(web_contents.get());
|
||||
|
||||
AudioFocusManager::GetInstance()->RequestAudioFocus(
|
||||
RequestAudioFocus(
|
||||
media_session, AudioFocusManager::AudioFocusType::GainTransientMayDuck);
|
||||
ASSERT_EQ(1, GetTransientMaybeDuckCount());
|
||||
|
||||
AudioFocusManager::GetInstance()->AbandonAudioFocus(media_session);
|
||||
AbandonAudioFocus(media_session);
|
||||
ASSERT_EQ(0, GetTransientMaybeDuckCount());
|
||||
}
|
||||
|
||||
@ -197,24 +237,22 @@ TEST_F(AudioFocusManagerTest, AbandonAudioFocus_WhileDuckingThenResume) {
|
||||
std::unique_ptr<WebContents> web_contents_2(CreateWebContents());
|
||||
MediaSession* media_session_2 = MediaSession::Get(web_contents_2.get());
|
||||
|
||||
AudioFocusManager::GetInstance()->RequestAudioFocus(
|
||||
media_session_1, AudioFocusManager::AudioFocusType::Gain);
|
||||
RequestAudioFocus(media_session_1, AudioFocusManager::AudioFocusType::Gain);
|
||||
ASSERT_EQ(0, GetTransientMaybeDuckCount());
|
||||
ASSERT_FALSE(IsSessionDucking(media_session_1));
|
||||
|
||||
AudioFocusManager::GetInstance()->RequestAudioFocus(
|
||||
RequestAudioFocus(
|
||||
media_session_2, AudioFocusManager::AudioFocusType::GainTransientMayDuck);
|
||||
ASSERT_EQ(1, GetTransientMaybeDuckCount());
|
||||
ASSERT_TRUE(IsSessionDucking(media_session_1));
|
||||
|
||||
AudioFocusManager::GetInstance()->AbandonAudioFocus(media_session_1);
|
||||
AbandonAudioFocus(media_session_1);
|
||||
ASSERT_EQ(1, GetTransientMaybeDuckCount());
|
||||
|
||||
AudioFocusManager::GetInstance()->AbandonAudioFocus(media_session_2);
|
||||
AbandonAudioFocus(media_session_2);
|
||||
ASSERT_EQ(0, GetTransientMaybeDuckCount());
|
||||
|
||||
AudioFocusManager::GetInstance()->RequestAudioFocus(
|
||||
media_session_1, AudioFocusManager::AudioFocusType::Gain);
|
||||
RequestAudioFocus(media_session_1, AudioFocusManager::AudioFocusType::Gain);
|
||||
ASSERT_FALSE(IsSessionDucking(media_session_1));
|
||||
}
|
||||
|
||||
@ -225,17 +263,16 @@ TEST_F(AudioFocusManagerTest, AbandonAudioFocus_StopsDucking) {
|
||||
std::unique_ptr<WebContents> web_contents_2(CreateWebContents());
|
||||
MediaSession* media_session_2 = MediaSession::Get(web_contents_2.get());
|
||||
|
||||
AudioFocusManager::GetInstance()->RequestAudioFocus(
|
||||
media_session_1, AudioFocusManager::AudioFocusType::Gain);
|
||||
RequestAudioFocus(media_session_1, AudioFocusManager::AudioFocusType::Gain);
|
||||
ASSERT_EQ(0, GetTransientMaybeDuckCount());
|
||||
ASSERT_FALSE(IsSessionDucking(media_session_1));
|
||||
|
||||
AudioFocusManager::GetInstance()->RequestAudioFocus(
|
||||
RequestAudioFocus(
|
||||
media_session_2, AudioFocusManager::AudioFocusType::GainTransientMayDuck);
|
||||
ASSERT_EQ(1, GetTransientMaybeDuckCount());
|
||||
ASSERT_TRUE(IsSessionDucking(media_session_1));
|
||||
|
||||
AudioFocusManager::GetInstance()->AbandonAudioFocus(media_session_2);
|
||||
AbandonAudioFocus(media_session_2);
|
||||
ASSERT_EQ(0, GetTransientMaybeDuckCount());
|
||||
ASSERT_FALSE(IsSessionDucking(media_session_1));
|
||||
}
|
||||
@ -247,28 +284,26 @@ TEST_F(AudioFocusManagerTest, DuckWhilePlaying) {
|
||||
std::unique_ptr<WebContents> web_contents_2(CreateWebContents());
|
||||
MediaSession* media_session_2 = MediaSession::Get(web_contents_2.get());
|
||||
|
||||
AudioFocusManager::GetInstance()->RequestAudioFocus(
|
||||
media_session_1, AudioFocusManager::AudioFocusType::Gain);
|
||||
RequestAudioFocus(media_session_1, AudioFocusManager::AudioFocusType::Gain);
|
||||
ASSERT_FALSE(IsSessionDucking(media_session_1));
|
||||
|
||||
AudioFocusManager::GetInstance()->RequestAudioFocus(
|
||||
RequestAudioFocus(
|
||||
media_session_2, AudioFocusManager::AudioFocusType::GainTransientMayDuck);
|
||||
ASSERT_TRUE(IsSessionDucking(media_session_1));
|
||||
}
|
||||
|
||||
TEST_F(AudioFocusManagerTest, DuckWhenStarting) {
|
||||
TEST_F(AudioFocusManagerTest, GainSuspendsTransient) {
|
||||
std::unique_ptr<WebContents> web_contents_1(CreateWebContents());
|
||||
MediaSession* media_session_1 = MediaSession::Get(web_contents_1.get());
|
||||
|
||||
std::unique_ptr<WebContents> web_contents_2(CreateWebContents());
|
||||
MediaSession* media_session_2 = MediaSession::Get(web_contents_2.get());
|
||||
|
||||
AudioFocusManager::GetInstance()->RequestAudioFocus(
|
||||
RequestAudioFocus(
|
||||
media_session_2, AudioFocusManager::AudioFocusType::GainTransientMayDuck);
|
||||
|
||||
AudioFocusManager::GetInstance()->RequestAudioFocus(
|
||||
media_session_1, AudioFocusManager::AudioFocusType::Gain);
|
||||
ASSERT_TRUE(IsSessionDucking(media_session_1));
|
||||
RequestAudioFocus(media_session_1, AudioFocusManager::AudioFocusType::Gain);
|
||||
ASSERT_TRUE(media_session_2->IsSuspended());
|
||||
}
|
||||
|
||||
TEST_F(AudioFocusManagerTest, DuckWithMultipleTransients) {
|
||||
@ -281,22 +316,21 @@ TEST_F(AudioFocusManagerTest, DuckWithMultipleTransients) {
|
||||
std::unique_ptr<WebContents> web_contents_3(CreateWebContents());
|
||||
MediaSession* media_session_3 = MediaSession::Get(web_contents_3.get());
|
||||
|
||||
AudioFocusManager::GetInstance()->RequestAudioFocus(
|
||||
media_session_1, AudioFocusManager::AudioFocusType::Gain);
|
||||
RequestAudioFocus(media_session_1, AudioFocusManager::AudioFocusType::Gain);
|
||||
ASSERT_FALSE(IsSessionDucking(media_session_1));
|
||||
|
||||
AudioFocusManager::GetInstance()->RequestAudioFocus(
|
||||
RequestAudioFocus(
|
||||
media_session_2, AudioFocusManager::AudioFocusType::GainTransientMayDuck);
|
||||
ASSERT_TRUE(IsSessionDucking(media_session_1));
|
||||
|
||||
AudioFocusManager::GetInstance()->RequestAudioFocus(
|
||||
RequestAudioFocus(
|
||||
media_session_3, AudioFocusManager::AudioFocusType::GainTransientMayDuck);
|
||||
ASSERT_TRUE(IsSessionDucking(media_session_1));
|
||||
|
||||
AudioFocusManager::GetInstance()->AbandonAudioFocus(media_session_2);
|
||||
AbandonAudioFocus(media_session_2);
|
||||
ASSERT_TRUE(IsSessionDucking(media_session_1));
|
||||
|
||||
AudioFocusManager::GetInstance()->AbandonAudioFocus(media_session_3);
|
||||
AbandonAudioFocus(media_session_3);
|
||||
ASSERT_FALSE(IsSessionDucking(media_session_1));
|
||||
}
|
||||
|
||||
@ -304,19 +338,18 @@ TEST_F(AudioFocusManagerTest, WebContentsDestroyed_ReleasesFocus) {
|
||||
std::unique_ptr<WebContents> web_contents(CreateWebContents());
|
||||
MediaSession* media_session = MediaSession::Get(web_contents.get());
|
||||
|
||||
AudioFocusManager::GetInstance()->RequestAudioFocus(
|
||||
media_session, AudioFocusManager::AudioFocusType::Gain);
|
||||
ASSERT_EQ(web_contents.get(), GetAudioFocusedContent());
|
||||
RequestAudioFocus(media_session, AudioFocusManager::AudioFocusType::Gain);
|
||||
ASSERT_EQ(media_session, GetAudioFocusedSession());
|
||||
|
||||
web_contents.reset();
|
||||
ASSERT_EQ(nullptr, GetAudioFocusedContent());
|
||||
ASSERT_EQ(nullptr, GetAudioFocusedSession());
|
||||
}
|
||||
|
||||
TEST_F(AudioFocusManagerTest, WebContentsDestroyed_ReleasesTransients) {
|
||||
std::unique_ptr<WebContents> web_contents(CreateWebContents());
|
||||
MediaSession* media_session = MediaSession::Get(web_contents.get());
|
||||
|
||||
AudioFocusManager::GetInstance()->RequestAudioFocus(
|
||||
RequestAudioFocus(
|
||||
media_session, AudioFocusManager::AudioFocusType::GainTransientMayDuck);
|
||||
ASSERT_EQ(1, GetTransientMaybeDuckCount());
|
||||
|
||||
@ -331,11 +364,10 @@ TEST_F(AudioFocusManagerTest, WebContentsDestroyed_StopsDucking) {
|
||||
std::unique_ptr<WebContents> web_contents_2(CreateWebContents());
|
||||
MediaSession* media_session_2 = MediaSession::Get(web_contents_2.get());
|
||||
|
||||
AudioFocusManager::GetInstance()->RequestAudioFocus(
|
||||
media_session_1, AudioFocusManager::AudioFocusType::Gain);
|
||||
RequestAudioFocus(media_session_1, AudioFocusManager::AudioFocusType::Gain);
|
||||
ASSERT_FALSE(IsSessionDucking(media_session_1));
|
||||
|
||||
AudioFocusManager::GetInstance()->RequestAudioFocus(
|
||||
RequestAudioFocus(
|
||||
media_session_2, AudioFocusManager::AudioFocusType::GainTransientMayDuck);
|
||||
ASSERT_TRUE(IsSessionDucking(media_session_1));
|
||||
|
||||
@ -343,4 +375,61 @@ TEST_F(AudioFocusManagerTest, WebContentsDestroyed_StopsDucking) {
|
||||
ASSERT_FALSE(IsSessionDucking(media_session_1));
|
||||
}
|
||||
|
||||
TEST_F(AudioFocusManagerTest, PepperRequestsGainFocus) {
|
||||
std::unique_ptr<WebContents> web_contents(CreateWebContents());
|
||||
MediaSession* media_session = MediaSession::Get(web_contents.get());
|
||||
|
||||
media_session->AddPlayer(
|
||||
pepper_observer_.get(), 0, media::MediaContentType::Pepper);
|
||||
ASSERT_EQ(media_session, GetAudioFocusedSession());
|
||||
|
||||
media_session->RemovePlayer(pepper_observer_.get(), 0);
|
||||
ASSERT_EQ(nullptr, GetAudioFocusedSession());
|
||||
}
|
||||
|
||||
TEST_F(AudioFocusManagerTest, GainDucksPepper) {
|
||||
std::unique_ptr<WebContents> web_contents_1(CreateWebContents());
|
||||
MediaSession* media_session_1 = MediaSession::Get(web_contents_1.get());
|
||||
|
||||
std::unique_ptr<WebContents> web_contents_2(CreateWebContents());
|
||||
MediaSession* media_session_2 = MediaSession::Get(web_contents_2.get());
|
||||
|
||||
media_session_1->AddPlayer(
|
||||
pepper_observer_.get(), 0, media::MediaContentType::Pepper);
|
||||
|
||||
RequestAudioFocus(
|
||||
media_session_2, AudioFocusManager::AudioFocusType::Gain);
|
||||
|
||||
ASSERT_EQ(media_session_2, GetAudioFocusedSession());
|
||||
ASSERT_TRUE(media_session_1->IsActive());
|
||||
ASSERT_TRUE(IsSessionDucking(media_session_1));
|
||||
}
|
||||
|
||||
TEST_F(AudioFocusManagerTest, AbandoningGainFocusRevokesTopMostPepperSession) {
|
||||
std::unique_ptr<WebContents> web_contents_1(CreateWebContents());
|
||||
MediaSession* media_session_1 = MediaSession::Get(web_contents_1.get());
|
||||
|
||||
std::unique_ptr<WebContents> web_contents_2(CreateWebContents());
|
||||
MediaSession* media_session_2 = MediaSession::Get(web_contents_2.get());
|
||||
|
||||
std::unique_ptr<WebContents> web_contents_3(CreateWebContents());
|
||||
MediaSession* media_session_3 = MediaSession::Get(web_contents_3.get());
|
||||
|
||||
media_session_1->AddPlayer(
|
||||
pepper_observer_.get(), 0, media::MediaContentType::Pepper);
|
||||
|
||||
RequestAudioFocus(
|
||||
media_session_2, AudioFocusManager::AudioFocusType::Gain);
|
||||
RequestAudioFocus(
|
||||
media_session_3, AudioFocusManager::AudioFocusType::Gain);
|
||||
|
||||
ASSERT_EQ(media_session_3, GetAudioFocusedSession());
|
||||
ASSERT_TRUE(media_session_2->IsReallySuspended());
|
||||
ASSERT_TRUE(media_session_1->IsActive());
|
||||
ASSERT_TRUE(IsSessionDucking(media_session_1));
|
||||
|
||||
AbandonAudioFocus(media_session_3);
|
||||
ASSERT_EQ(media_session_1, GetAudioFocusedSession());
|
||||
}
|
||||
|
||||
} // namespace content
|
||||
|
@ -60,6 +60,19 @@ MediaSession::~MediaSession() {
|
||||
DCHECK(audio_focus_state_ == State::INACTIVE);
|
||||
}
|
||||
|
||||
void MediaSession::WebContentsDestroyed() {
|
||||
// This should only work for tests. In production, all the players should have
|
||||
// already been removed before WebContents is destroyed.
|
||||
|
||||
// TODO(zqzhang): refactor MediaSession, maybe move the interface used to talk
|
||||
// with AudioFocusManager out to a seperate class. The AudioFocusManager unit
|
||||
// tests then could mock the interface and abandon audio focus when
|
||||
// WebContents is destroyed. See https://crbug.com/651069
|
||||
players_.clear();
|
||||
pepper_players_.clear();
|
||||
AbandonSystemAudioFocusIfNeeded();
|
||||
}
|
||||
|
||||
void MediaSession::SetMetadata(const base::Optional<MediaMetadata>& metadata) {
|
||||
metadata_ = metadata;
|
||||
// TODO(zqzhang): On Android, the metadata is sent though JNI everytime the
|
||||
@ -71,6 +84,9 @@ void MediaSession::SetMetadata(const base::Optional<MediaMetadata>& metadata) {
|
||||
bool MediaSession::AddPlayer(MediaSessionObserver* observer,
|
||||
int player_id,
|
||||
media::MediaContentType media_content_type) {
|
||||
if (media_content_type == media::MediaContentType::Pepper)
|
||||
return AddPepperPlayer(observer, player_id);
|
||||
|
||||
observer->OnSetVolumeMultiplier(player_id, GetVolumeMultiplier());
|
||||
|
||||
// Determine the audio focus type required for playing the new player.
|
||||
@ -96,11 +112,7 @@ bool MediaSession::AddPlayer(MediaSessionObserver* observer,
|
||||
}
|
||||
|
||||
State old_audio_focus_state = audio_focus_state_;
|
||||
State audio_focus_state = RequestSystemAudioFocus(required_audio_focus_type)
|
||||
? State::ACTIVE
|
||||
: State::INACTIVE;
|
||||
SetAudioFocusState(audio_focus_state);
|
||||
audio_focus_type_ = required_audio_focus_type;
|
||||
RequestSystemAudioFocus(required_audio_focus_type);
|
||||
|
||||
if (audio_focus_state_ != State::ACTIVE)
|
||||
return false;
|
||||
@ -122,17 +134,28 @@ void MediaSession::RemovePlayer(MediaSessionObserver* observer,
|
||||
if (it != players_.end())
|
||||
players_.erase(it);
|
||||
|
||||
it = pepper_players_.find(PlayerIdentifier(observer, player_id));
|
||||
if (it != pepper_players_.end())
|
||||
pepper_players_.erase(it);
|
||||
|
||||
AbandonSystemAudioFocusIfNeeded();
|
||||
}
|
||||
|
||||
void MediaSession::RemovePlayers(MediaSessionObserver* observer) {
|
||||
for (auto it = players_.begin(); it != players_.end();) {
|
||||
for (auto it = players_.begin(); it != players_.end(); ) {
|
||||
if (it->observer == observer)
|
||||
players_.erase(it++);
|
||||
else
|
||||
++it;
|
||||
}
|
||||
|
||||
for (auto it = pepper_players_.begin(); it != pepper_players_.end(); ) {
|
||||
if (it->observer == observer)
|
||||
pepper_players_.erase(it++);
|
||||
else
|
||||
++it;
|
||||
}
|
||||
|
||||
AbandonSystemAudioFocusIfNeeded();
|
||||
}
|
||||
|
||||
@ -190,8 +213,8 @@ void MediaSession::Suspend(SuspendType suspend_type) {
|
||||
|
||||
void MediaSession::Stop(SuspendType suspend_type) {
|
||||
DCHECK(audio_focus_state_ != State::INACTIVE);
|
||||
|
||||
DCHECK(suspend_type != SuspendType::CONTENT);
|
||||
DCHECK(!HasPepper());
|
||||
|
||||
// TODO(mlamouri): merge the logic between UI and SYSTEM.
|
||||
if (suspend_type == SuspendType::SYSTEM) {
|
||||
@ -204,6 +227,7 @@ void MediaSession::Stop(SuspendType suspend_type) {
|
||||
|
||||
DCHECK(audio_focus_state_ == State::SUSPENDED);
|
||||
players_.clear();
|
||||
|
||||
AbandonSystemAudioFocusIfNeeded();
|
||||
}
|
||||
|
||||
@ -224,6 +248,8 @@ void MediaSession::StopDucking() {
|
||||
void MediaSession::UpdateVolumeMultiplier() {
|
||||
for (const auto& it : players_)
|
||||
it.observer->OnSetVolumeMultiplier(it.player_id, GetVolumeMultiplier());
|
||||
for (const auto& it : pepper_players_)
|
||||
it.observer->OnSetVolumeMultiplier(it.player_id, GetVolumeMultiplier());
|
||||
}
|
||||
|
||||
double MediaSession::GetVolumeMultiplier() const {
|
||||
@ -250,6 +276,10 @@ bool MediaSession::IsControllable() const {
|
||||
audio_focus_type_ == AudioFocusManager::AudioFocusType::Gain;
|
||||
}
|
||||
|
||||
bool MediaSession::HasPepper() const {
|
||||
return !pepper_players_.empty();
|
||||
}
|
||||
|
||||
std::unique_ptr<base::CallbackList<void(MediaSession::State)>::Subscription>
|
||||
MediaSession::RegisterMediaSessionStateChangedCallbackForTest(
|
||||
const StateChangedCallback& cb) {
|
||||
@ -265,11 +295,6 @@ bool MediaSession::IsActiveForTest() const {
|
||||
return audio_focus_state_ == State::ACTIVE;
|
||||
}
|
||||
|
||||
AudioFocusManager::AudioFocusType MediaSession::audio_focus_type_for_test()
|
||||
const {
|
||||
return audio_focus_type_;
|
||||
}
|
||||
|
||||
MediaSessionUmaHelper* MediaSession::uma_helper_for_test() {
|
||||
return &uma_helper_;
|
||||
}
|
||||
@ -281,6 +306,8 @@ void MediaSession::RemoveAllPlayersForTest() {
|
||||
|
||||
void MediaSession::OnSuspendInternal(SuspendType suspend_type,
|
||||
State new_state) {
|
||||
DCHECK(!HasPepper());
|
||||
|
||||
DCHECK(new_state == State::SUSPENDED || new_state == State::INACTIVE);
|
||||
// UI suspend cannot use State::INACTIVE.
|
||||
DCHECK(suspend_type == SuspendType::SYSTEM || new_state == State::SUSPENDED);
|
||||
@ -323,6 +350,9 @@ void MediaSession::OnSuspendInternal(SuspendType suspend_type,
|
||||
it.observer->OnSuspend(it.player_id);
|
||||
}
|
||||
|
||||
for (const auto& it : pepper_players_)
|
||||
it.observer->OnSetVolumeMultiplier(it.player_id, kDuckingVolumeMultiplier);
|
||||
|
||||
UpdateWebContents();
|
||||
}
|
||||
|
||||
@ -335,6 +365,9 @@ void MediaSession::OnResumeInternal(SuspendType suspend_type) {
|
||||
for (const auto& it : players_)
|
||||
it.observer->OnResume(it.player_id);
|
||||
|
||||
for (const auto& it : pepper_players_)
|
||||
it.observer->OnSetVolumeMultiplier(it.player_id, GetVolumeMultiplier());
|
||||
|
||||
UpdateWebContents();
|
||||
}
|
||||
|
||||
@ -353,13 +386,19 @@ bool MediaSession::RequestSystemAudioFocus(
|
||||
AudioFocusManager::AudioFocusType audio_focus_type) {
|
||||
bool result = delegate_->RequestAudioFocus(audio_focus_type);
|
||||
uma_helper_.RecordRequestAudioFocusResult(result);
|
||||
|
||||
// MediaSession must change its state & audio focus type AFTER requesting
|
||||
// audio focus.
|
||||
SetAudioFocusState(result ? State::ACTIVE : State::INACTIVE);
|
||||
audio_focus_type_ = audio_focus_type;
|
||||
return result;
|
||||
}
|
||||
|
||||
void MediaSession::AbandonSystemAudioFocusIfNeeded() {
|
||||
if (audio_focus_state_ == State::INACTIVE || !players_.empty())
|
||||
if (audio_focus_state_ == State::INACTIVE || !players_.empty() ||
|
||||
!pepper_players_.empty()) {
|
||||
return;
|
||||
|
||||
}
|
||||
delegate_->AbandonAudioFocus();
|
||||
|
||||
SetAudioFocusState(State::INACTIVE);
|
||||
@ -389,4 +428,17 @@ void MediaSession::SetAudioFocusState(State audio_focus_state) {
|
||||
}
|
||||
}
|
||||
|
||||
bool MediaSession::AddPepperPlayer(MediaSessionObserver* observer,
|
||||
int player_id) {
|
||||
bool success = RequestSystemAudioFocus(
|
||||
AudioFocusManager::AudioFocusType::Gain);
|
||||
DCHECK(success);
|
||||
|
||||
pepper_players_.insert(PlayerIdentifier(observer, player_id));
|
||||
|
||||
observer->OnSetVolumeMultiplier(player_id, GetVolumeMultiplier());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace content
|
||||
|
@ -58,6 +58,13 @@ class MediaSession : public WebContentsObserver,
|
||||
CONTENT,
|
||||
};
|
||||
|
||||
// Only visible to tests.
|
||||
enum class State {
|
||||
ACTIVE,
|
||||
SUSPENDED,
|
||||
INACTIVE
|
||||
};
|
||||
|
||||
// Returns the MediaSession associated to this WebContents. Creates one if
|
||||
// none is currently available.
|
||||
CONTENT_EXPORT static MediaSession* Get(WebContents* web_contents);
|
||||
@ -124,11 +131,23 @@ class MediaSession : public WebContentsObserver,
|
||||
// instead of checking if the state is SUSPENDED. In order to not have to
|
||||
// change all the callers and make the current refactoring ridiculously huge,
|
||||
// this method is introduced temporarily and will be removed later.
|
||||
bool IsReallySuspended() const;
|
||||
CONTENT_EXPORT bool IsReallySuspended() const;
|
||||
|
||||
// Returns if the session is currently suspended or inactive.
|
||||
CONTENT_EXPORT bool IsSuspended() const;
|
||||
|
||||
// Returns the audio focus type. The type is updated everytime after the
|
||||
// session requests audio focus.
|
||||
CONTENT_EXPORT AudioFocusManager::AudioFocusType audio_focus_type() const {
|
||||
return audio_focus_type_;
|
||||
}
|
||||
|
||||
// Returns whether the session has Pepper instances.
|
||||
bool HasPepper() const;
|
||||
|
||||
// WebContentsObserver implementation
|
||||
void WebContentsDestroyed() override;
|
||||
|
||||
private:
|
||||
friend class content::WebContentsUserData<MediaSession>;
|
||||
friend class ::MediaSessionBrowserTest;
|
||||
@ -139,17 +158,9 @@ class MediaSession : public WebContentsObserver,
|
||||
CONTENT_EXPORT void SetDelegateForTests(
|
||||
std::unique_ptr<MediaSessionDelegate> delegate);
|
||||
CONTENT_EXPORT bool IsActiveForTest() const;
|
||||
CONTENT_EXPORT AudioFocusManager::AudioFocusType audio_focus_type_for_test()
|
||||
const;
|
||||
CONTENT_EXPORT void RemoveAllPlayersForTest();
|
||||
CONTENT_EXPORT MediaSessionUmaHelper* uma_helper_for_test();
|
||||
|
||||
enum class State {
|
||||
ACTIVE,
|
||||
SUSPENDED,
|
||||
INACTIVE
|
||||
};
|
||||
|
||||
// Representation of a player for the MediaSession.
|
||||
struct PlayerIdentifier {
|
||||
PlayerIdentifier(MediaSessionObserver* observer, int player_id);
|
||||
@ -179,12 +190,12 @@ class MediaSession : public WebContentsObserver,
|
||||
|
||||
// Requests audio focus to the MediaSessionDelegate.
|
||||
// Returns whether the request was granted.
|
||||
bool RequestSystemAudioFocus(
|
||||
CONTENT_EXPORT bool RequestSystemAudioFocus(
|
||||
AudioFocusManager::AudioFocusType audio_focus_type);
|
||||
|
||||
// To be called after a call to AbandonAudioFocus() in order request the
|
||||
// delegate to abandon the audio focus.
|
||||
void AbandonSystemAudioFocusIfNeeded();
|
||||
CONTENT_EXPORT void AbandonSystemAudioFocusIfNeeded();
|
||||
|
||||
// Notifies WebContents about the state change of the media session.
|
||||
void UpdateWebContents();
|
||||
@ -205,8 +216,12 @@ class MediaSession : public WebContentsObserver,
|
||||
RegisterMediaSessionStateChangedCallbackForTest(
|
||||
const StateChangedCallback& cb);
|
||||
|
||||
CONTENT_EXPORT bool AddPepperPlayer(MediaSessionObserver* observer,
|
||||
int player_id);
|
||||
|
||||
std::unique_ptr<MediaSessionDelegate> delegate_;
|
||||
PlayersMap players_;
|
||||
PlayersMap pepper_players_;
|
||||
|
||||
State audio_focus_state_;
|
||||
SuspendType suspend_type_;
|
||||
|
@ -114,7 +114,7 @@ class MediaSessionBrowserTest : public content::ContentBrowserTest {
|
||||
bool HasAudioFocus() { return media_session_->IsActiveForTest(); }
|
||||
|
||||
content::AudioFocusManager::AudioFocusType GetSessionAudioFocusType() {
|
||||
return media_session_->audio_focus_type_for_test();
|
||||
return media_session_->audio_focus_type();
|
||||
}
|
||||
|
||||
bool IsControllable() { return media_session_->IsControllable(); }
|
||||
|
@ -1599,6 +1599,7 @@ test("content_unittests") {
|
||||
"../renderer/media/android/webmediasession_android_unittest.cc",
|
||||
]
|
||||
sources -= [
|
||||
"../browser/media/session/audio_focus_manager_unittest.cc",
|
||||
"../browser/power_usage_monitor_impl_unittest.cc",
|
||||
"../browser/webui/url_data_manager_backend_unittest.cc",
|
||||
]
|
||||
|
98
docs/audio_focus.md
Normal file
98
docs/audio_focus.md
Normal file
@ -0,0 +1,98 @@
|
||||
# Audio Focus Handling
|
||||
|
||||
A MediaSession collects all audio-producing objects in one tab. It is usually
|
||||
unpleasant when multiple MediaSessions play sound at the same time. Audio focus
|
||||
handling manages the MediaSessions and mixes them in proper ways. This is part
|
||||
of the default media session on desktop project.
|
||||
|
||||
[TOC]
|
||||
|
||||
## Processing model
|
||||
|
||||
### Audio focus types
|
||||
|
||||
There are "persistent" and "transient" audio focus types.
|
||||
|
||||
* Persistent audios are used for long media playback, and they should not mix
|
||||
with each other. When they start to play, they should pause all other
|
||||
playbacks.
|
||||
* Transient audios are used for short media playback such as a ping for incoming
|
||||
message. When they start to play, they should play on top of other playbacks
|
||||
and the other playbacks should duck (have reduced volume).
|
||||
|
||||
### `MediaSession`
|
||||
|
||||
Audio-producing objects should join `MediaSession` when they want to produce
|
||||
sound. `MediaSession` has the following states:
|
||||
|
||||
* ACTIVE: the `MediaSession` has audio focus and its audio-producing objects can
|
||||
play.
|
||||
* SUSPENDED: the MediaSession does not have audio focus. All audio-producing
|
||||
objects are paused and can be resumed when the session gains audio focus.
|
||||
* INACTIVE: the MediaSession does not have audio focus, and there is no
|
||||
audio-producing objects in this `MediaSession`.
|
||||
|
||||
Besides, `MediaSession` has a `DUCKING` flag, which means its managed
|
||||
audio-producing objects has lowered volume. The flag is orthogonal with
|
||||
`MediaSession` state.
|
||||
|
||||
### `AudioFocusManager`
|
||||
|
||||
`AudioFocusManager` is a global instance which manages the state of
|
||||
`MediaSession`s. It is used for platforms (e.g. Android) that do not have a
|
||||
system audio focus.
|
||||
|
||||
When an audio-producing object wants to play audio, it should join `MediaSession`
|
||||
and tell which kind of audio focus type it requires. `MediaSession` will then
|
||||
request audio focus from `AudioFocusManager`, and will allow the object to play
|
||||
sound if successful. `AudioFocusManager` will notify other `MediaSession`s if
|
||||
their states are changed.
|
||||
|
||||
When an audio-producing object stops playing audio, it should be removed from
|
||||
its `MediaSession`, and `MediaSession` should abandon its audio focus if its
|
||||
audio-producing objects is empty. `AudioFocusManager` will notify other
|
||||
`MediaSession`s of state change if necessary.
|
||||
|
||||
## The algorithm for handling audio focus
|
||||
|
||||
`AudioFocusManager` uses a stack implementation. It keeps track of all
|
||||
ACTIVE/SUSPENDED `MediaSession`s. When a `MediaSession` requests audio focus, it
|
||||
will be put at the top of the stack, and will be removed from the stack when it
|
||||
abandons audio focus.
|
||||
|
||||
The algorithm is as follows:
|
||||
|
||||
* When a `MediaSession` requests audio focus:
|
||||
|
||||
* Remove it from the audio focus stack if it's already there, and place it at
|
||||
the top of audio focus stack, grant focus to the session and let it play.
|
||||
* If the session is persistent, suspend all the other sessions on the stack.
|
||||
* If the session is transient, we should duck any active persistent audio
|
||||
focus entry if present:
|
||||
|
||||
* If the next top entry is transient, do nothing, since if there is any
|
||||
persistent session that is active, it is already ducking.
|
||||
* If the next top entry is persistent, let the next top entry start ducking,
|
||||
since it is the only active persisten session.
|
||||
|
||||
* When a `MediaSession` abandons audio focus:
|
||||
|
||||
* If the session is not on the top, just remove it from the stack.
|
||||
* If the session is on the top, remove it from the stack.
|
||||
|
||||
* If the stack becomes empty, do nothing.
|
||||
* If the next top session is transient, do nothing.
|
||||
* If the next top session is persistent, stop ducking it.
|
||||
|
||||
### Handling Pepper
|
||||
|
||||
Pepper is different from media elements since it has a different model. Pepper
|
||||
cannot be paused, but its volume can be changed. When considering Pepper, the
|
||||
above algorithm must be modified.
|
||||
|
||||
When Pepper joins `MediaSession`, it should request persistent focus type. When
|
||||
AudioFocusManager wants to suspend a `MediaSession`, it must check whether the
|
||||
session has Pepper instance, and if yes, it should duck the session instead.
|
||||
|
||||
Also, whenever a session abandons focus, and the next top session is INACTIVE,
|
||||
`AudioFocusManager` should find the next session having Pepper and unduck it.
|
Reference in New Issue
Block a user