Commit 17349ec7 authored by Becca Hughes's avatar Becca Hughes Committed by Commit Bot

[Media Session] Add AudioFocus mojo API

Adds a mojo AudioFocus API that can be used to access
AudioFocusManager inside the media session service.

This also removes the RequestId when requesting or
abandoning audio focus and replaces it with a
AudioFocusRequestClient interface. We still keep the
ID around though as it is useful for testing and
identifying requests.

BUG=875004

Change-Id: I88374a08a14171f747a120beaa380c3b126bfb4f
Reviewed-on: https://chromium-review.googlesource.com/c/1199923
Commit-Queue: Becca Hughes <beccahughes@chromium.org>
Reviewed-by: default avatarKen Rockot <rockot@chromium.org>
Reviewed-by: default avatarDominick Ng <dominickn@chromium.org>
Reviewed-by: default avatarapacible <apacible@chromium.org>
Cr-Commit-Position: refs/heads/master@{#596404}
parent 7c2a2894
...@@ -190,6 +190,7 @@ service_manifest("packaged_services_manifest") { ...@@ -190,6 +190,7 @@ service_manifest("packaged_services_manifest") {
"//services/audio:manifest", "//services/audio:manifest",
"//services/data_decoder:manifest", "//services/data_decoder:manifest",
"//services/device:manifest", "//services/device:manifest",
"//services/media_session:manifest",
"//services/metrics:manifest", "//services/metrics:manifest",
"//services/network:manifest", "//services/network:manifest",
"//services/resource_coordinator:manifest", "//services/resource_coordinator:manifest",
......
...@@ -117,6 +117,7 @@ ...@@ -117,6 +117,7 @@
], ],
"file": [ "file:filesystem", "file:leveldb" ], "file": [ "file:filesystem", "file:leveldb" ],
"media": [ "media:media" ], "media": [ "media:media" ],
"media_session": [ "media_session:app" ],
"metrics": [ "url_keyed_metrics" ], "metrics": [ "url_keyed_metrics" ],
"network": [ "network": [
"network_service", "network_service",
......
...@@ -4,8 +4,10 @@ ...@@ -4,8 +4,10 @@
#include "services/media_session/audio_focus_manager.h" #include "services/media_session/audio_focus_manager.h"
#include <iterator>
#include <utility>
#include "base/atomic_sequence_num.h" #include "base/atomic_sequence_num.h"
#include "base/memory/ptr_util.h"
#include "base/threading/thread_task_runner_handle.h" #include "base/threading/thread_task_runner_handle.h"
#include "mojo/public/cpp/bindings/interface_request.h" #include "mojo/public/cpp/bindings/interface_request.h"
#include "services/media_session/public/mojom/audio_focus.mojom.h" #include "services/media_session/public/mojom/audio_focus.mojom.h"
...@@ -23,66 +25,149 @@ int GenerateAudioFocusRequestId() { ...@@ -23,66 +25,149 @@ int GenerateAudioFocusRequestId() {
} // namespace } // namespace
class AudioFocusManager::StackRow : public mojom::AudioFocusRequestClient {
public:
StackRow(AudioFocusManager* owner,
mojom::AudioFocusRequestClientRequest request,
mojom::MediaSessionPtr session,
mojom::MediaSessionInfoPtr session_info,
mojom::AudioFocusType audio_focus_type,
RequestId id)
: id_(id),
session_(std::move(session)),
session_info_(std::move(session_info)),
audio_focus_type_(audio_focus_type),
binding_(this, std::move(request)),
owner_(owner) {
// Listen for mojo errors.
binding_.set_connection_error_handler(
base::BindOnce(&AudioFocusManager::StackRow::OnConnectionError,
base::Unretained(this)));
session_.set_connection_error_handler(
base::BindOnce(&AudioFocusManager::StackRow::OnConnectionError,
base::Unretained(this)));
}
~StackRow() override = default;
// mojom::AudioFocusRequestClient.
void RequestAudioFocus(mojom::MediaSessionInfoPtr session_info,
mojom::AudioFocusType type,
RequestAudioFocusCallback callback) override {
session_info_ = std::move(session_info);
if (IsActive() && owner_->IsSessionOnTopOfAudioFocusStack(id(), type)) {
// Early returning if |media_session| is already on top (has focus) and is
// active.
std::move(callback).Run();
return;
}
// Remove this StackRow for the audio focus stack.
std::unique_ptr<StackRow> row = owner_->RemoveFocusEntryIfPresent(id());
DCHECK(row);
owner_->RequestAudioFocusInternal(std::move(row), type,
std::move(callback));
}
void AbandonAudioFocus() override { owner_->AbandonAudioFocusInternal(id_); }
void MediaSessionInfoChanged(mojom::MediaSessionInfoPtr info) override {
session_info_ = std::move(info);
}
void GetRequestId(GetRequestIdCallback callback) override {
std::move(callback).Run(id());
}
mojom::MediaSession* session() { return session_.get(); }
const mojom::MediaSessionInfoPtr& info() const { return session_info_; }
mojom::AudioFocusType audio_focus_type() const { return audio_focus_type_; }
void SetAudioFocusType(mojom::AudioFocusType type) {
audio_focus_type_ = type;
}
bool IsActive() const {
return session_info_->state ==
mojom::MediaSessionInfo::SessionState::kActive;
}
RequestId id() const { return id_; }
// Flush any pending mojo messages for testing.
void FlushForTesting() {
session_.FlushForTesting();
binding_.FlushForTesting();
}
private:
void OnConnectionError() {
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(&AudioFocusManager::AbandonAudioFocusInternal,
base::Unretained(owner_), id_));
}
const RequestId id_;
mojom::MediaSessionPtr session_;
mojom::MediaSessionInfoPtr session_info_;
mojom::AudioFocusType audio_focus_type_;
mojo::Binding<mojom::AudioFocusRequestClient> binding_;
// Weak pointer to the owning |AudioFocusManager| instance.
AudioFocusManager* owner_;
DISALLOW_COPY_AND_ASSIGN(StackRow);
};
// static // static
AudioFocusManager* AudioFocusManager::GetInstance() { AudioFocusManager* AudioFocusManager::GetInstance() {
return base::Singleton<AudioFocusManager>::get(); return base::Singleton<AudioFocusManager>::get();
} }
AudioFocusManager::RequestResponse AudioFocusManager::RequestAudioFocus( void AudioFocusManager::RequestAudioFocus(
mojom::AudioFocusRequestClientRequest request,
mojom::MediaSessionPtr media_session, mojom::MediaSessionPtr media_session,
mojom::MediaSessionInfoPtr session_info, mojom::MediaSessionInfoPtr session_info,
mojom::AudioFocusType type, mojom::AudioFocusType type,
base::Optional<RequestId> previous_id) { RequestAudioFocusCallback callback) {
DCHECK(!session_info.is_null()); RequestAudioFocusInternal(
DCHECK(media_session.is_bound()); std::make_unique<StackRow>(
this, std::move(request), std::move(media_session),
if (!audio_focus_stack_.empty() && std::move(session_info), type, GenerateAudioFocusRequestId()),
previous_id == audio_focus_stack_.back()->id() && type, std::move(callback));
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 AudioFocusManager::RequestResponse(previous_id.value(), true);
}
// If we have a previous ID then we should remove it from the stack. void AudioFocusManager::GetFocusRequests(GetFocusRequestsCallback callback) {
if (previous_id.has_value()) std::vector<mojom::AudioFocusRequestStatePtr> requests;
RemoveFocusEntryIfPresent(previous_id.value());
if (type == mojom::AudioFocusType::kGainTransientMayDuck) { for (const auto& row : audio_focus_stack_) {
for (auto& old_session : audio_focus_stack_) auto request = mojom::AudioFocusRequestState::New();
old_session->session()->StartDucking(); request->session_info = row->info().Clone();
} else { request->audio_focus_type = row->audio_focus_type();
for (auto& old_session : audio_focus_stack_) { request->request_id = row->id();
// If the session has the force duck bit set then we should duck the requests.push_back(std::move(request));
// session instead of suspending it.
if (old_session->info()->force_duck) {
old_session->session()->StartDucking();
} else {
old_session->session()->Suspend(
mojom::MediaSession::SuspendType::kSystem);
}
}
} }
audio_focus_stack_.push_back(std::make_unique<AudioFocusManager::StackRow>( std::move(callback).Run(std::move(requests));
std::move(media_session), std::move(session_info), type, }
previous_id ? *previous_id : GenerateAudioFocusRequestId()));
AudioFocusManager::StackRow* row = audio_focus_stack_.back().get(); void AudioFocusManager::GetDebugInfoForRequest(
row->session()->StopDucking(); uint64_t request_id,
GetDebugInfoForRequestCallback callback) {
for (auto& row : audio_focus_stack_) {
if (row->id() != request_id)
continue;
// Notify observers that we were gained audio focus. row->session()->GetDebugInfo(std::move(callback));
observers_.ForAllPtrs([&row, type](mojom::AudioFocusObserver* observer) { return;
observer->OnFocusGained(row->info().Clone(), type); }
});
// We always grant the audio focus request but this may not always be the case std::move(callback).Run(mojom::MediaSessionDebugInfo::New());
// in the future.
return AudioFocusManager::RequestResponse(row->id(), true);
} }
void AudioFocusManager::AbandonAudioFocus(RequestId id) { void AudioFocusManager::AbandonAudioFocusInternal(RequestId id) {
if (audio_focus_stack_.empty()) if (audio_focus_stack_.empty())
return; return;
...@@ -127,29 +212,63 @@ void AudioFocusManager::AbandonAudioFocus(RequestId id) { ...@@ -127,29 +212,63 @@ void AudioFocusManager::AbandonAudioFocus(RequestId id) {
}); });
} }
mojo::InterfacePtrSetElementId AudioFocusManager::AddObserver( void AudioFocusManager::AddObserver(mojom::AudioFocusObserverPtr observer) {
mojom::AudioFocusObserverPtr observer) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
return observers_.AddPtr(std::move(observer)); observers_.AddPtr(std::move(observer));
} }
mojom::AudioFocusType AudioFocusManager::GetFocusTypeForSession(RequestId id) { void AudioFocusManager::BindToInterface(
for (auto& row : audio_focus_stack_) { mojom::AudioFocusManagerRequest request) {
if (row->id() == id) DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
return row->audio_focus_type(); bindings_.AddBinding(this, std::move(request));
} }
NOTREACHED(); void AudioFocusManager::CloseAllMojoObjects() {
return mojom::AudioFocusType::kGain; DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
observers_.CloseAll();
bindings_.CloseAllBindings();
} }
void AudioFocusManager::RemoveObserver(mojo::InterfacePtrSetElementId id) { void AudioFocusManager::RequestAudioFocusInternal(
observers_.RemovePtr(id); std::unique_ptr<StackRow> row,
mojom::AudioFocusType type,
base::OnceCallback<void()> callback) {
if (type == mojom::AudioFocusType::kGainTransientMayDuck) {
for (auto& old_session : audio_focus_stack_)
old_session->session()->StartDucking();
} else {
for (auto& old_session : audio_focus_stack_) {
// If the session has the force duck bit set then we should duck the
// session instead of suspending it.
if (old_session->info()->force_duck) {
old_session->session()->StartDucking();
} else {
old_session->session()->Suspend(
mojom::MediaSession::SuspendType::kSystem);
}
}
}
row->SetAudioFocusType(type);
row->session()->StopDucking();
audio_focus_stack_.push_back(std::move(row));
// Notify observers that we were gained audio focus.
mojom::MediaSessionInfoPtr session_info =
audio_focus_stack_.back()->info().Clone();
observers_.ForAllPtrs(
[&session_info, type](mojom::AudioFocusObserver* observer) {
observer->OnFocusGained(session_info.Clone(), type);
});
// We always grant the audio focus request but this may not always be the case
// in the future.
std::move(callback).Run();
} }
void AudioFocusManager::ResetForTesting() { void AudioFocusManager::ResetForTesting() {
audio_focus_stack_.clear(); audio_focus_stack_.clear();
observers_.CloseAll(); CloseAllMojoObjects();
} }
void AudioFocusManager::FlushForTesting() { void AudioFocusManager::FlushForTesting() {
...@@ -163,70 +282,32 @@ AudioFocusManager::AudioFocusManager() { ...@@ -163,70 +282,32 @@ AudioFocusManager::AudioFocusManager() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
} }
AudioFocusManager::~AudioFocusManager() = default; AudioFocusManager::~AudioFocusManager() {
DCHECK(observers_.empty());
DCHECK(bindings_.empty());
}
std::unique_ptr<AudioFocusManager::StackRow>
AudioFocusManager::RemoveFocusEntryIfPresent(RequestId id) {
std::unique_ptr<StackRow> row;
void AudioFocusManager::RemoveFocusEntryIfPresent(
AudioFocusManager::RequestId id) {
for (auto iter = audio_focus_stack_.begin(); iter != audio_focus_stack_.end(); for (auto iter = audio_focus_stack_.begin(); iter != audio_focus_stack_.end();
++iter) { ++iter) {
if ((*iter)->id() == id) { if ((*iter)->id() == id) {
row.swap((*iter));
audio_focus_stack_.erase(iter); audio_focus_stack_.erase(iter);
break; break;
} }
} }
}
AudioFocusManager::StackRow::StackRow(mojom::MediaSessionPtr session,
mojom::MediaSessionInfoPtr current_info,
mojom::AudioFocusType audio_focus_type,
AudioFocusManager::RequestId id)
: id_(id),
session_(std::move(session)),
current_info_(std::move(current_info)),
audio_focus_type_(audio_focus_type),
binding_(this) {
mojom::MediaSessionObserverPtr observer;
binding_.Bind(mojo::MakeRequest(&observer));
// Listen for mojo errors.
binding_.set_connection_error_handler(base::BindOnce(
&AudioFocusManager::StackRow::OnConnectionError, base::Unretained(this)));
session_.set_connection_error_handler(base::BindOnce(
&AudioFocusManager::StackRow::OnConnectionError, base::Unretained(this)));
// Listen to info changes on the MediaSession.
session_->AddObserver(std::move(observer));
};
AudioFocusManager::StackRow::~StackRow() = default;
void AudioFocusManager::StackRow::MediaSessionInfoChanged(
mojom::MediaSessionInfoPtr info) {
current_info_ = std::move(info);
}
mojom::MediaSession* AudioFocusManager::StackRow::session() {
return session_.get();
}
const mojom::MediaSessionInfoPtr& AudioFocusManager::StackRow::info() const {
return current_info_;
}
bool AudioFocusManager::StackRow::IsActive() const {
return current_info_->state == mojom::MediaSessionInfo::SessionState::kActive;
}
void AudioFocusManager::StackRow::FlushForTesting() { return row;
session_.FlushForTesting();
binding_.FlushForTesting();
} }
void AudioFocusManager::StackRow::OnConnectionError() { bool AudioFocusManager::IsSessionOnTopOfAudioFocusStack(
base::ThreadTaskRunnerHandle::Get()->PostTask( RequestId id,
FROM_HERE, mojom::AudioFocusType type) const {
base::BindOnce(&AudioFocusManager::AbandonAudioFocus, return !audio_focus_stack_.empty() && audio_focus_stack_.back()->id() == id &&
base::Unretained(AudioFocusManager::GetInstance()), id())); audio_focus_stack_.back()->audio_focus_type() == type;
} }
} // namespace media_session } // namespace media_session
...@@ -11,48 +11,50 @@ ...@@ -11,48 +11,50 @@
#include "base/memory/singleton.h" #include "base/memory/singleton.h"
#include "base/threading/thread_checker.h" #include "base/threading/thread_checker.h"
#include "mojo/public/cpp/bindings/binding.h" #include "mojo/public/cpp/bindings/binding.h"
#include "mojo/public/cpp/bindings/binding_set.h"
#include "mojo/public/cpp/bindings/interface_ptr.h" #include "mojo/public/cpp/bindings/interface_ptr.h"
#include "mojo/public/cpp/bindings/interface_ptr_set.h" #include "mojo/public/cpp/bindings/interface_ptr_set.h"
#include "services/media_session/public/mojom/audio_focus.mojom.h" #include "services/media_session/public/mojom/audio_focus.mojom.h"
namespace media_session { namespace media_session {
class AudioFocusManager { class AudioFocusManager : public mojom::AudioFocusManager {
public: public:
using RequestId = int; using RequestId = uint64_t;
using RequestResponse = std::pair<RequestId, bool>;
// Returns Chromium's internal AudioFocusManager. // Returns Chromium's internal AudioFocusManager.
static AudioFocusManager* GetInstance(); static AudioFocusManager* GetInstance();
// Requests audio focus with |type| for the |media_session| with // mojom::AudioFocusManager.
// |session_info|. The function will return a |RequestResponse| which contains void RequestAudioFocus(mojom::AudioFocusRequestClientRequest request,
// a |RequestId| and a boolean as to whether the request was successful. If a mojom::MediaSessionPtr media_session,
// session wishes to request a different focus type it should provide its mojom::MediaSessionInfoPtr session_info,
// previous request id as |previous_id|. mojom::AudioFocusType type,
RequestResponse RequestAudioFocus(mojom::MediaSessionPtr media_session, RequestAudioFocusCallback callback) override;
mojom::MediaSessionInfoPtr session_info, void GetFocusRequests(GetFocusRequestsCallback callback) override;
mojom::AudioFocusType type, void GetDebugInfoForRequest(uint64_t request_id,
base::Optional<RequestId> previous_id); GetDebugInfoForRequestCallback callback) override;
void AddObserver(mojom::AudioFocusObserverPtr observer) override;
// Abandons audio focus for a request with |id|. The next request on top of // Bind to a mojom::AudioFocusManagerRequest.
// the stack will be granted audio focus. void BindToInterface(mojom::AudioFocusManagerRequest request);
void AbandonAudioFocus(RequestId id);
// Returns the current audio focus type for a request with |id|. // This will close all Mojo bindings and interface pointers. This should be
mojom::AudioFocusType GetFocusTypeForSession(RequestId id); // called by the MediaSession service before it is destroyed.
void CloseAllMojoObjects();
// Adds/removes audio focus observers.
mojo::InterfacePtrSetElementId AddObserver(mojom::AudioFocusObserverPtr);
void RemoveObserver(mojo::InterfacePtrSetElementId);
private: private:
friend struct base::DefaultSingletonTraits<AudioFocusManager>; friend struct base::DefaultSingletonTraits<AudioFocusManager>;
friend class AudioFocusManagerTest; friend class AudioFocusManagerTest;
// Media internals UI needs access to internal state. // StackRow is an AudioFocusRequestClient and allows a media session to
friend class MediaInternalsAudioFocusTest; // control its audio focus.
friend class MediaInternals; class StackRow;
void RequestAudioFocusInternal(std::unique_ptr<StackRow> row,
mojom::AudioFocusType type,
base::OnceCallback<void()> callback);
void AbandonAudioFocusInternal(RequestId id);
// Flush for testing will flush any pending messages to the observers. // Flush for testing will flush any pending messages to the observers.
void FlushForTesting(); void FlushForTesting();
...@@ -61,51 +63,20 @@ class AudioFocusManager { ...@@ -61,51 +63,20 @@ class AudioFocusManager {
void ResetForTesting(); void ResetForTesting();
AudioFocusManager(); AudioFocusManager();
~AudioFocusManager(); ~AudioFocusManager() override;
std::unique_ptr<StackRow> RemoveFocusEntryIfPresent(RequestId id);
void RemoveFocusEntryIfPresent(RequestId id); bool IsSessionOnTopOfAudioFocusStack(RequestId id,
mojom::AudioFocusType type) const;
// Holds mojo bindings for the Audio Focus Manager API.
mojo::BindingSet<mojom::AudioFocusManager> bindings_;
// Weak reference of managed observers. Observers are expected to remove // Weak reference of managed observers. Observers are expected to remove
// themselves before being destroyed. // themselves before being destroyed.
mojo::InterfacePtrSet<mojom::AudioFocusObserver> observers_; mojo::InterfacePtrSet<mojom::AudioFocusObserver> observers_;
// StackRow is a MediaSessionObserver and holds a cached copy of the latest
// MediaSessionInfo associated with the MediaSession. By keeping the info
// cached and readily available we can make audio focus decisions quickly
// without waiting on MediaSessions.
class StackRow : public mojom::MediaSessionObserver {
public:
StackRow(mojom::MediaSessionPtr session,
mojom::MediaSessionInfoPtr current_info,
mojom::AudioFocusType audio_focus_type,
RequestId id);
~StackRow() override;
// mojom::MediaSessionObserver.
void MediaSessionInfoChanged(mojom::MediaSessionInfoPtr info) override;
mojom::MediaSession* session();
const mojom::MediaSessionInfoPtr& info() const;
mojom::AudioFocusType audio_focus_type() const { return audio_focus_type_; }
bool IsActive() const;
int id() { return id_; }
// Flush any pending mojo messages for testing.
void FlushForTesting();
private:
void OnConnectionError();
int id_;
mojom::MediaSessionPtr session_;
mojom::MediaSessionInfoPtr current_info_;
mojom::AudioFocusType audio_focus_type_;
mojo::Binding<mojom::MediaSessionObserver> binding_;
DISALLOW_COPY_AND_ASSIGN(StackRow);
};
// A stack of Mojo interface pointers and their requested audio focus type. // A stack of Mojo interface pointers and their requested audio focus type.
// A MediaSession must abandon audio focus before its destruction. // A MediaSession must abandon audio focus before its destruction.
std::list<std::unique_ptr<StackRow>> audio_focus_stack_; std::list<std::unique_ptr<StackRow>> audio_focus_stack_;
......
...@@ -5,22 +5,32 @@ ...@@ -5,22 +5,32 @@
#include "services/media_session/audio_focus_manager.h" #include "services/media_session/audio_focus_manager.h"
#include <memory> #include <memory>
#include <utility>
#include <vector>
#include "base/callback.h"
#include "base/command_line.h" #include "base/command_line.h"
#include "base/run_loop.h" #include "base/run_loop.h"
#include "base/test/scoped_task_environment.h" #include "base/test/scoped_task_environment.h"
#include "mojo/public/cpp/bindings/binding_set.h" #include "mojo/public/cpp/bindings/binding_set.h"
#include "mojo/public/cpp/bindings/interface_request.h"
#include "services/media_session/media_session_service.h"
#include "services/media_session/public/cpp/test/audio_focus_test_util.h" #include "services/media_session/public/cpp/test/audio_focus_test_util.h"
#include "services/media_session/public/mojom/audio_focus.mojom.h" #include "services/media_session/public/mojom/audio_focus.mojom.h"
#include "services/service_manager/public/cpp/test/test_connector_factory.h"
#include "testing/gtest/include/gtest/gtest.h" #include "testing/gtest/include/gtest/gtest.h"
namespace media_session { namespace media_session {
class AudioFocusManagerTest;
namespace { namespace {
static const base::Optional<AudioFocusManager::RequestId> kNoRequestId; const AudioFocusManager::RequestId kNoFocusedSession = -1;
static const AudioFocusManager::RequestId kNoFocusedSession = -1; const char kExampleDebugInfoName[] = "name";
const char kExampleDebugInfoOwner[] = "owner";
const char kExampleDebugInfoState[] = "state";
class MockMediaSession : public mojom::MediaSession { class MockMediaSession : public mojom::MediaSession {
public: public:
...@@ -48,26 +58,53 @@ class MockMediaSession : public mojom::MediaSession { ...@@ -48,26 +58,53 @@ class MockMediaSession : public mojom::MediaSession {
std::move(callback).Run(GetSessionInfoSync()); std::move(callback).Run(GetSessionInfoSync());
} }
void AddObserver(mojom::MediaSessionObserverPtr observer) override { void AddObserver(mojom::MediaSessionObserverPtr observer) override {}
observers_.AddPtr(std::move(observer));
}
void GetDebugInfo(GetDebugInfoCallback callback) override {} void GetDebugInfo(GetDebugInfoCallback callback) override {
mojom::MediaSessionDebugInfoPtr debug_info(
mojom::MediaSessionDebugInfo::New());
debug_info->name = kExampleDebugInfoName;
debug_info->owner = kExampleDebugInfoOwner;
debug_info->state = kExampleDebugInfoState;
std::move(callback).Run(std::move(debug_info));
}
void BindToMojoRequest(mojo::InterfaceRequest<mojom::MediaSession> request) { void BindToMojoRequest(mojo::InterfaceRequest<mojom::MediaSession> request) {
bindings_.AddBinding(this, std::move(request)); bindings_.AddBinding(this, std::move(request));
} }
protected:
// Audio Focus methods should be called from AudioFocusManagerTest.
friend class media_session::AudioFocusManagerTest;
mojom::AudioFocusRequestClient* audio_focus_request() {
return afr_client_.get();
}
bool HasAudioFocusRequest() const { return afr_client_.is_bound(); }
void ResetAudioFocusRequest() { afr_client_.reset(); }
void SetAudioFocusRequestClient(
mojom::AudioFocusRequestClientPtr afr_client) {
afr_client_ = std::move(afr_client);
}
void FlushForTesting() { afr_client_.FlushForTesting(); }
private:
void SetState(mojom::MediaSessionInfo::SessionState state) { void SetState(mojom::MediaSessionInfo::SessionState state) {
state_ = state; state_ = state;
NotifyObservers(); NotifyObservers();
} }
bool has_observers() const { return !observers_.empty(); } void NotifyObservers() {
if (afr_client_.is_bound())
void CloseAllObservers() { observers_.CloseAll(); } afr_client_->MediaSessionInfoChanged(GetSessionInfoSync());
}
private:
mojom::MediaSessionInfoPtr GetSessionInfoSync() { mojom::MediaSessionInfoPtr GetSessionInfoSync() {
mojom::MediaSessionInfoPtr info(mojom::MediaSessionInfo::New()); mojom::MediaSessionInfoPtr info(mojom::MediaSessionInfo::New());
info->force_duck = force_duck_; info->force_duck = force_duck_;
...@@ -77,21 +114,13 @@ class MockMediaSession : public mojom::MediaSession { ...@@ -77,21 +114,13 @@ class MockMediaSession : public mojom::MediaSession {
return info; return info;
} }
void NotifyObservers() { mojom::AudioFocusRequestClientPtr afr_client_;
observers_.ForAllPtrs([this](mojom::MediaSessionObserver* observer) {
observer->MediaSessionInfoChanged(GetSessionInfoSync());
});
// This will flush all pending async messages on the observers.
observers_.FlushForTesting();
}
const bool force_duck_ = false; const bool force_duck_ = false;
bool is_ducking_ = false; bool is_ducking_ = false;
mojom::MediaSessionInfo::SessionState state_ = mojom::MediaSessionInfo::SessionState state_ =
mojom::MediaSessionInfo::SessionState::kInactive; mojom::MediaSessionInfo::SessionState::kInactive;
mojo::InterfacePtrSet<mojom::MediaSessionObserver> observers_;
mojo::BindingSet<mojom::MediaSession> bindings_; mojo::BindingSet<mojom::MediaSession> bindings_;
}; };
...@@ -102,6 +131,15 @@ class AudioFocusManagerTest : public testing::Test { ...@@ -102,6 +131,15 @@ class AudioFocusManagerTest : public testing::Test {
AudioFocusManagerTest() = default; AudioFocusManagerTest() = default;
void SetUp() override { void SetUp() override {
// Create an instance of the MediaSessionService.
connector_factory_ =
service_manager::TestConnectorFactory::CreateForUniqueService(
MediaSessionService::Create());
connector_ = connector_factory_->CreateConnector();
// Bind |audio_focus_ptr_| to AudioFocusManager.
connector_->BindInterface("test", mojo::MakeRequest(&audio_focus_ptr_));
// AudioFocusManager is a singleton so we should make sure we reset any // AudioFocusManager is a singleton so we should make sure we reset any
// state in between tests. // state in between tests.
AudioFocusManager::GetInstance()->ResetForTesting(); AudioFocusManager::GetInstance()->ResetForTesting();
...@@ -112,26 +150,23 @@ class AudioFocusManagerTest : public testing::Test { ...@@ -112,26 +150,23 @@ class AudioFocusManagerTest : public testing::Test {
base::RunLoop().RunUntilIdle(); base::RunLoop().RunUntilIdle();
} }
AudioFocusManager::RequestId GetAudioFocusedSession() const { AudioFocusManager::RequestId GetAudioFocusedSession() {
const AudioFocusManager* manager = AudioFocusManager::GetInstance(); const auto audio_focus_requests = GetRequests();
const auto& audio_focus_stack = manager->audio_focus_stack_; for (auto iter = audio_focus_requests.rbegin();
iter != audio_focus_requests.rend(); ++iter) {
for (auto iter = audio_focus_stack.rbegin(); if ((*iter)->audio_focus_type == mojom::AudioFocusType::kGain)
iter != audio_focus_stack.rend(); ++iter) { return (*iter)->request_id;
if ((*iter)->audio_focus_type() == mojom::AudioFocusType::kGain)
return (*iter)->id();
} }
return kNoFocusedSession; return kNoFocusedSession;
} }
int GetTransientMaybeDuckCount() const { int GetTransientMaybeDuckCount() {
const AudioFocusManager* manager = AudioFocusManager::GetInstance(); const auto audio_focus_requests = GetRequests();
const auto& audio_focus_stack = manager->audio_focus_stack_;
int count = 0; int count = 0;
for (auto iter = audio_focus_stack.rbegin(); for (auto iter = audio_focus_requests.rbegin();
iter != audio_focus_stack.rend(); ++iter) { iter != audio_focus_requests.rend(); ++iter) {
if ((*iter)->audio_focus_type() == if ((*iter)->audio_focus_type ==
mojom::AudioFocusType::kGainTransientMayDuck) mojom::AudioFocusType::kGainTransientMayDuck)
++count; ++count;
else else
...@@ -141,36 +176,73 @@ class AudioFocusManagerTest : public testing::Test { ...@@ -141,36 +176,73 @@ class AudioFocusManagerTest : public testing::Test {
return count; return count;
} }
void AbandonAudioFocus(AudioFocusManager::RequestId id) { void AbandonAudioFocus(MockMediaSession* session) {
AudioFocusManager::GetInstance()->AbandonAudioFocus(id); AbandonAudioFocusNoReset(session);
FlushForTesting(); session->ResetAudioFocusRequest();
} }
AudioFocusManager::RequestId RequestAudioFocus( void AbandonAudioFocusNoReset(MockMediaSession* session) {
MockMediaSession* session, DCHECK(session->HasAudioFocusRequest());
mojom::AudioFocusType audio_focus_type) { session->audio_focus_request()->AbandonAudioFocus();
return RequestAudioFocus(session, audio_focus_type, kNoRequestId); session->FlushForTesting();
FlushForTesting();
} }
AudioFocusManager::RequestId RequestAudioFocus( AudioFocusManager::RequestId RequestAudioFocus(
MockMediaSession* session, MockMediaSession* session,
mojom::AudioFocusType audio_focus_type, mojom::AudioFocusType audio_focus_type) {
base::Optional<AudioFocusManager::RequestId> previous_id) { bool result;
mojom::MediaSessionPtr media_session; base::OnceClosure callback =
session->BindToMojoRequest(mojo::MakeRequest(&media_session)); base::BindOnce([](bool* out_result) { *out_result = true; }, &result);
AudioFocusManager::RequestResponse response = if (session->HasAudioFocusRequest()) {
AudioFocusManager::GetInstance()->RequestAudioFocus( // Request audio focus through the existing request.
std::move(media_session), test::GetMediaSessionInfoSync(session), session->audio_focus_request()->RequestAudioFocus(
audio_focus_type, previous_id); test::GetMediaSessionInfoSync(session), audio_focus_type,
std::move(callback));
session->FlushForTesting();
} else {
// Build a new audio focus request.
mojom::AudioFocusRequestClientPtr afr_client;
mojom::MediaSessionPtr media_session;
session->BindToMojoRequest(mojo::MakeRequest(&media_session));
GetService()->RequestAudioFocus(mojo::MakeRequest(&afr_client),
std::move(media_session),
test::GetMediaSessionInfoSync(session),
audio_focus_type, std::move(callback));
session->SetAudioFocusRequestClient(std::move(afr_client));
FlushForTesting();
}
// If the audio focus was granted then we should set the session state to // If the audio focus was granted then we should set the session state to
// active. // active.
if (response.second) if (result)
session->SetState(mojom::MediaSessionInfo::SessionState::kActive); session->SetState(mojom::MediaSessionInfo::SessionState::kActive);
FlushForTesting(); FlushForTesting();
return response.first; return GetRequestIdForSession(session);
}
mojom::MediaSessionDebugInfoPtr GetDebugInfo(
AudioFocusManager::RequestId request_id) {
mojom::MediaSessionDebugInfoPtr result;
base::OnceCallback<void(mojom::MediaSessionDebugInfoPtr)> callback =
base::BindOnce(
[](mojom::MediaSessionDebugInfoPtr* out_result,
mojom::MediaSessionDebugInfoPtr result) {
*out_result = std::move(result);
},
&result);
GetService()->GetDebugInfoForRequest(request_id, std::move(callback));
FlushForTesting();
return result;
} }
mojom::MediaSessionInfo::SessionState GetState(MockMediaSession* session) { mojom::MediaSessionInfo::SessionState GetState(MockMediaSession* session) {
...@@ -183,17 +255,56 @@ class AudioFocusManagerTest : public testing::Test { ...@@ -183,17 +255,56 @@ class AudioFocusManagerTest : public testing::Test {
mojom::AudioFocusObserverPtr observer_ptr; mojom::AudioFocusObserverPtr observer_ptr;
observer->BindToMojoRequest(mojo::MakeRequest(&observer_ptr)); observer->BindToMojoRequest(mojo::MakeRequest(&observer_ptr));
AudioFocusManager::GetInstance()->AddObserver(std::move(observer_ptr)); GetService()->AddObserver(std::move(observer_ptr));
FlushForTesting();
return observer; return observer;
} }
private: private:
std::vector<mojom::AudioFocusRequestStatePtr> GetRequests() {
std::vector<mojom::AudioFocusRequestStatePtr> result;
GetService()->GetFocusRequests(base::BindOnce(
[](std::vector<mojom::AudioFocusRequestStatePtr>* out,
std::vector<mojom::AudioFocusRequestStatePtr> requests) {
for (auto& request : requests)
out->push_back(request.Clone());
},
&result));
FlushForTesting();
return result;
}
AudioFocusManager::RequestId GetRequestIdForSession(
MockMediaSession* session) {
DCHECK(session->HasAudioFocusRequest());
AudioFocusManager::RequestId id = kNoFocusedSession;
session->audio_focus_request()->GetRequestId(
base::BindOnce([](AudioFocusManager::RequestId* id,
uint64_t received_id) { *id = received_id; },
&id));
session->FlushForTesting();
EXPECT_NE(kNoFocusedSession, id);
return id;
}
mojom::AudioFocusManager* GetService() const {
return audio_focus_ptr_.get();
}
void FlushForTesting() { void FlushForTesting() {
audio_focus_ptr_.FlushForTesting();
AudioFocusManager::GetInstance()->FlushForTesting(); AudioFocusManager::GetInstance()->FlushForTesting();
} }
base::test::ScopedTaskEnvironment task_environment_; base::test::ScopedTaskEnvironment task_environment_;
std::unique_ptr<service_manager::TestConnectorFactory> connector_factory_;
std::unique_ptr<service_manager::Connector> connector_;
mojom::AudioFocusManagerPtr audio_focus_ptr_;
DISALLOW_COPY_AND_ASSIGN(AudioFocusManagerTest); DISALLOW_COPY_AND_ASSIGN(AudioFocusManagerTest);
}; };
...@@ -204,15 +315,6 @@ TEST_F(AudioFocusManagerTest, InstanceAvailableAndSame) { ...@@ -204,15 +315,6 @@ TEST_F(AudioFocusManagerTest, InstanceAvailableAndSame) {
EXPECT_EQ(audio_focus_manager, AudioFocusManager::GetInstance()); EXPECT_EQ(audio_focus_manager, AudioFocusManager::GetInstance());
} }
TEST_F(AudioFocusManagerTest, AddObserverOnRequest) {
MockMediaSession media_session_1;
EXPECT_FALSE(media_session_1.has_observers());
RequestAudioFocus(&media_session_1, mojom::AudioFocusType::kGain,
kNoRequestId);
EXPECT_TRUE(media_session_1.has_observers());
}
TEST_F(AudioFocusManagerTest, RequestAudioFocusGain_ReplaceFocusedEntry) { TEST_F(AudioFocusManagerTest, RequestAudioFocusGain_ReplaceFocusedEntry) {
MockMediaSession media_session_1; MockMediaSession media_session_1;
MockMediaSession media_session_2; MockMediaSession media_session_2;
...@@ -254,7 +356,7 @@ TEST_F(AudioFocusManagerTest, RequestAudioFocusGain_Duplicate) { ...@@ -254,7 +356,7 @@ TEST_F(AudioFocusManagerTest, RequestAudioFocusGain_Duplicate) {
RequestAudioFocus(&media_session, mojom::AudioFocusType::kGain); RequestAudioFocus(&media_session, mojom::AudioFocusType::kGain);
EXPECT_EQ(request_id, GetAudioFocusedSession()); EXPECT_EQ(request_id, GetAudioFocusedSession());
RequestAudioFocus(&media_session, mojom::AudioFocusType::kGain, request_id); RequestAudioFocus(&media_session, mojom::AudioFocusType::kGain);
EXPECT_EQ(request_id, GetAudioFocusedSession()); EXPECT_EQ(request_id, GetAudioFocusedSession());
} }
...@@ -266,7 +368,7 @@ TEST_F(AudioFocusManagerTest, RequestAudioFocusGain_FromTransient) { ...@@ -266,7 +368,7 @@ TEST_F(AudioFocusManagerTest, RequestAudioFocusGain_FromTransient) {
EXPECT_EQ(kNoFocusedSession, GetAudioFocusedSession()); EXPECT_EQ(kNoFocusedSession, GetAudioFocusedSession());
EXPECT_EQ(1, GetTransientMaybeDuckCount()); EXPECT_EQ(1, GetTransientMaybeDuckCount());
RequestAudioFocus(&media_session, mojom::AudioFocusType::kGain, request_id); RequestAudioFocus(&media_session, mojom::AudioFocusType::kGain);
EXPECT_EQ(request_id, GetAudioFocusedSession()); EXPECT_EQ(request_id, GetAudioFocusedSession());
EXPECT_EQ(0, GetTransientMaybeDuckCount()); EXPECT_EQ(0, GetTransientMaybeDuckCount());
} }
...@@ -276,11 +378,12 @@ TEST_F(AudioFocusManagerTest, RequestAudioFocusTransient_FromGain) { ...@@ -276,11 +378,12 @@ TEST_F(AudioFocusManagerTest, RequestAudioFocusTransient_FromGain) {
AudioFocusManager::RequestId request_id = AudioFocusManager::RequestId request_id =
RequestAudioFocus(&media_session, mojom::AudioFocusType::kGain); RequestAudioFocus(&media_session, mojom::AudioFocusType::kGain);
EXPECT_EQ(request_id, GetAudioFocusedSession()); EXPECT_EQ(request_id, GetAudioFocusedSession());
EXPECT_EQ(0, GetTransientMaybeDuckCount()); EXPECT_EQ(0, GetTransientMaybeDuckCount());
RequestAudioFocus(&media_session, RequestAudioFocus(&media_session,
mojom::AudioFocusType::kGainTransientMayDuck, request_id); mojom::AudioFocusType::kGainTransientMayDuck);
EXPECT_EQ(kNoFocusedSession, GetAudioFocusedSession()); EXPECT_EQ(kNoFocusedSession, GetAudioFocusedSession());
EXPECT_EQ(1, GetTransientMaybeDuckCount()); EXPECT_EQ(1, GetTransientMaybeDuckCount());
EXPECT_NE(mojom::MediaSessionInfo::SessionState::kDucking, EXPECT_NE(mojom::MediaSessionInfo::SessionState::kDucking,
...@@ -291,8 +394,7 @@ TEST_F(AudioFocusManagerTest, RequestAudioFocusTransient_FromGainWhileDucking) { ...@@ -291,8 +394,7 @@ TEST_F(AudioFocusManagerTest, RequestAudioFocusTransient_FromGainWhileDucking) {
MockMediaSession media_session_1; MockMediaSession media_session_1;
MockMediaSession media_session_2; MockMediaSession media_session_2;
AudioFocusManager::RequestId request_id = RequestAudioFocus(&media_session_1, mojom::AudioFocusType::kGain);
RequestAudioFocus(&media_session_1, mojom::AudioFocusType::kGain);
EXPECT_EQ(0, GetTransientMaybeDuckCount()); EXPECT_EQ(0, GetTransientMaybeDuckCount());
EXPECT_EQ(mojom::MediaSessionInfo::SessionState::kActive, EXPECT_EQ(mojom::MediaSessionInfo::SessionState::kActive,
GetState(&media_session_1)); GetState(&media_session_1));
...@@ -304,7 +406,7 @@ TEST_F(AudioFocusManagerTest, RequestAudioFocusTransient_FromGainWhileDucking) { ...@@ -304,7 +406,7 @@ TEST_F(AudioFocusManagerTest, RequestAudioFocusTransient_FromGainWhileDucking) {
GetState(&media_session_1)); GetState(&media_session_1));
RequestAudioFocus(&media_session_1, RequestAudioFocus(&media_session_1,
mojom::AudioFocusType::kGainTransientMayDuck, request_id); mojom::AudioFocusType::kGainTransientMayDuck);
EXPECT_EQ(2, GetTransientMaybeDuckCount()); EXPECT_EQ(2, GetTransientMaybeDuckCount());
EXPECT_EQ(mojom::MediaSessionInfo::SessionState::kActive, EXPECT_EQ(mojom::MediaSessionInfo::SessionState::kActive,
GetState(&media_session_1)); GetState(&media_session_1));
...@@ -317,25 +419,36 @@ TEST_F(AudioFocusManagerTest, AbandonAudioFocus_RemovesFocusedEntry) { ...@@ -317,25 +419,36 @@ TEST_F(AudioFocusManagerTest, AbandonAudioFocus_RemovesFocusedEntry) {
RequestAudioFocus(&media_session, mojom::AudioFocusType::kGain); RequestAudioFocus(&media_session, mojom::AudioFocusType::kGain);
EXPECT_EQ(request_id, GetAudioFocusedSession()); EXPECT_EQ(request_id, GetAudioFocusedSession());
AbandonAudioFocus(request_id); AbandonAudioFocus(&media_session);
EXPECT_EQ(kNoFocusedSession, GetAudioFocusedSession()); EXPECT_EQ(kNoFocusedSession, GetAudioFocusedSession());
} }
TEST_F(AudioFocusManagerTest, AbandonAudioFocus_NoAssociatedEntry) { TEST_F(AudioFocusManagerTest, AbandonAudioFocus_MultipleCalls) {
AbandonAudioFocus(kNoFocusedSession); MockMediaSession media_session;
AudioFocusManager::RequestId request_id =
RequestAudioFocus(&media_session, mojom::AudioFocusType::kGain);
EXPECT_EQ(request_id, GetAudioFocusedSession());
AbandonAudioFocusNoReset(&media_session);
std::unique_ptr<test::TestAudioFocusObserver> observer = CreateObserver();
AbandonAudioFocus(&media_session);
EXPECT_EQ(kNoFocusedSession, GetAudioFocusedSession()); EXPECT_EQ(kNoFocusedSession, GetAudioFocusedSession());
EXPECT_TRUE(observer->focus_lost_session_.is_null());
} }
TEST_F(AudioFocusManagerTest, AbandonAudioFocus_RemovesTransientEntry) { TEST_F(AudioFocusManagerTest, AbandonAudioFocus_RemovesTransientEntry) {
MockMediaSession media_session; MockMediaSession media_session;
AudioFocusManager::RequestId request_id = RequestAudioFocus( RequestAudioFocus(&media_session,
&media_session, mojom::AudioFocusType::kGainTransientMayDuck); mojom::AudioFocusType::kGainTransientMayDuck);
EXPECT_EQ(1, GetTransientMaybeDuckCount()); EXPECT_EQ(1, GetTransientMaybeDuckCount());
{ {
std::unique_ptr<test::TestAudioFocusObserver> observer = CreateObserver(); std::unique_ptr<test::TestAudioFocusObserver> observer = CreateObserver();
AbandonAudioFocus(request_id); AbandonAudioFocus(&media_session);
EXPECT_EQ(0, GetTransientMaybeDuckCount()); EXPECT_EQ(0, GetTransientMaybeDuckCount());
EXPECT_TRUE(observer->focus_lost_session_.Equals( EXPECT_TRUE(observer->focus_lost_session_.Equals(
...@@ -347,22 +460,21 @@ TEST_F(AudioFocusManagerTest, AbandonAudioFocus_WhileDuckingThenResume) { ...@@ -347,22 +460,21 @@ TEST_F(AudioFocusManagerTest, AbandonAudioFocus_WhileDuckingThenResume) {
MockMediaSession media_session_1; MockMediaSession media_session_1;
MockMediaSession media_session_2; MockMediaSession media_session_2;
AudioFocusManager::RequestId request_id_1 = RequestAudioFocus(&media_session_1, mojom::AudioFocusType::kGain);
RequestAudioFocus(&media_session_1, mojom::AudioFocusType::kGain);
EXPECT_EQ(0, GetTransientMaybeDuckCount()); EXPECT_EQ(0, GetTransientMaybeDuckCount());
EXPECT_NE(mojom::MediaSessionInfo::SessionState::kDucking, EXPECT_NE(mojom::MediaSessionInfo::SessionState::kDucking,
GetState(&media_session_1)); GetState(&media_session_1));
AudioFocusManager::RequestId request_id_2 = RequestAudioFocus( RequestAudioFocus(&media_session_2,
&media_session_2, mojom::AudioFocusType::kGainTransientMayDuck); mojom::AudioFocusType::kGainTransientMayDuck);
EXPECT_EQ(1, GetTransientMaybeDuckCount()); EXPECT_EQ(1, GetTransientMaybeDuckCount());
EXPECT_EQ(mojom::MediaSessionInfo::SessionState::kDucking, EXPECT_EQ(mojom::MediaSessionInfo::SessionState::kDucking,
GetState(&media_session_1)); GetState(&media_session_1));
AbandonAudioFocus(request_id_1); AbandonAudioFocus(&media_session_1);
EXPECT_EQ(1, GetTransientMaybeDuckCount()); EXPECT_EQ(1, GetTransientMaybeDuckCount());
AbandonAudioFocus(request_id_2); AbandonAudioFocus(&media_session_2);
EXPECT_EQ(0, GetTransientMaybeDuckCount()); EXPECT_EQ(0, GetTransientMaybeDuckCount());
RequestAudioFocus(&media_session_1, mojom::AudioFocusType::kGain); RequestAudioFocus(&media_session_1, mojom::AudioFocusType::kGain);
...@@ -379,13 +491,13 @@ TEST_F(AudioFocusManagerTest, AbandonAudioFocus_StopsDucking) { ...@@ -379,13 +491,13 @@ TEST_F(AudioFocusManagerTest, AbandonAudioFocus_StopsDucking) {
EXPECT_NE(mojom::MediaSessionInfo::SessionState::kDucking, EXPECT_NE(mojom::MediaSessionInfo::SessionState::kDucking,
GetState(&media_session_1)); GetState(&media_session_1));
AudioFocusManager::RequestId request_id_2 = RequestAudioFocus( RequestAudioFocus(&media_session_2,
&media_session_2, mojom::AudioFocusType::kGainTransientMayDuck); mojom::AudioFocusType::kGainTransientMayDuck);
EXPECT_EQ(1, GetTransientMaybeDuckCount()); EXPECT_EQ(1, GetTransientMaybeDuckCount());
EXPECT_EQ(mojom::MediaSessionInfo::SessionState::kDucking, EXPECT_EQ(mojom::MediaSessionInfo::SessionState::kDucking,
GetState(&media_session_1)); GetState(&media_session_1));
AbandonAudioFocus(request_id_2); AbandonAudioFocus(&media_session_2);
EXPECT_EQ(0, GetTransientMaybeDuckCount()); EXPECT_EQ(0, GetTransientMaybeDuckCount());
EXPECT_NE(mojom::MediaSessionInfo::SessionState::kDucking, EXPECT_NE(mojom::MediaSessionInfo::SessionState::kDucking,
GetState(&media_session_1)); GetState(&media_session_1));
...@@ -426,21 +538,21 @@ TEST_F(AudioFocusManagerTest, DuckWithMultipleTransients) { ...@@ -426,21 +538,21 @@ TEST_F(AudioFocusManagerTest, DuckWithMultipleTransients) {
EXPECT_NE(mojom::MediaSessionInfo::SessionState::kDucking, EXPECT_NE(mojom::MediaSessionInfo::SessionState::kDucking,
GetState(&media_session_1)); GetState(&media_session_1));
AudioFocusManager::RequestId request_id_2 = RequestAudioFocus( RequestAudioFocus(&media_session_2,
&media_session_2, mojom::AudioFocusType::kGainTransientMayDuck); mojom::AudioFocusType::kGainTransientMayDuck);
EXPECT_EQ(mojom::MediaSessionInfo::SessionState::kDucking, EXPECT_EQ(mojom::MediaSessionInfo::SessionState::kDucking,
GetState(&media_session_1)); GetState(&media_session_1));
AudioFocusManager::RequestId request_id_3 = RequestAudioFocus( RequestAudioFocus(&media_session_3,
&media_session_3, mojom::AudioFocusType::kGainTransientMayDuck); mojom::AudioFocusType::kGainTransientMayDuck);
EXPECT_EQ(mojom::MediaSessionInfo::SessionState::kDucking, EXPECT_EQ(mojom::MediaSessionInfo::SessionState::kDucking,
GetState(&media_session_1)); GetState(&media_session_1));
AbandonAudioFocus(request_id_2); AbandonAudioFocus(&media_session_2);
EXPECT_EQ(mojom::MediaSessionInfo::SessionState::kDucking, EXPECT_EQ(mojom::MediaSessionInfo::SessionState::kDucking,
GetState(&media_session_1)); GetState(&media_session_1));
AbandonAudioFocus(request_id_3); AbandonAudioFocus(&media_session_3);
EXPECT_NE(mojom::MediaSessionInfo::SessionState::kDucking, EXPECT_NE(mojom::MediaSessionInfo::SessionState::kDucking,
GetState(&media_session_1)); GetState(&media_session_1));
} }
...@@ -510,18 +622,10 @@ TEST_F(AudioFocusManagerTest, ...@@ -510,18 +622,10 @@ TEST_F(AudioFocusManagerTest,
EXPECT_EQ(mojom::MediaSessionInfo::SessionState::kDucking, EXPECT_EQ(mojom::MediaSessionInfo::SessionState::kDucking,
GetState(&media_session_1)); GetState(&media_session_1));
AbandonAudioFocus(request_id_3); AbandonAudioFocus(&media_session_3);
EXPECT_EQ(request_id_1, GetAudioFocusedSession()); EXPECT_EQ(request_id_1, GetAudioFocusedSession());
} }
TEST_F(AudioFocusManagerTest, AudioFocusObserver_AbandonNoop) {
std::unique_ptr<test::TestAudioFocusObserver> observer = CreateObserver();
AbandonAudioFocus(kNoFocusedSession);
EXPECT_EQ(kNoFocusedSession, GetAudioFocusedSession());
EXPECT_TRUE(observer->focus_lost_session_.is_null());
}
TEST_F(AudioFocusManagerTest, AudioFocusObserver_RequestNoop) { TEST_F(AudioFocusManagerTest, AudioFocusObserver_RequestNoop) {
MockMediaSession media_session; MockMediaSession media_session;
AudioFocusManager::RequestId request_id; AudioFocusManager::RequestId request_id;
...@@ -538,7 +642,7 @@ TEST_F(AudioFocusManagerTest, AudioFocusObserver_RequestNoop) { ...@@ -538,7 +642,7 @@ TEST_F(AudioFocusManagerTest, AudioFocusObserver_RequestNoop) {
{ {
std::unique_ptr<test::TestAudioFocusObserver> observer = CreateObserver(); std::unique_ptr<test::TestAudioFocusObserver> observer = CreateObserver();
RequestAudioFocus(&media_session, mojom::AudioFocusType::kGain, request_id); RequestAudioFocus(&media_session, mojom::AudioFocusType::kGain);
EXPECT_EQ(request_id, GetAudioFocusedSession()); EXPECT_EQ(request_id, GetAudioFocusedSession());
EXPECT_TRUE(observer->focus_gained_session_.is_null()); EXPECT_TRUE(observer->focus_gained_session_.is_null());
...@@ -547,12 +651,11 @@ TEST_F(AudioFocusManagerTest, AudioFocusObserver_RequestNoop) { ...@@ -547,12 +651,11 @@ TEST_F(AudioFocusManagerTest, AudioFocusObserver_RequestNoop) {
TEST_F(AudioFocusManagerTest, AudioFocusObserver_TransientMayDuck) { TEST_F(AudioFocusManagerTest, AudioFocusObserver_TransientMayDuck) {
MockMediaSession media_session; MockMediaSession media_session;
AudioFocusManager::RequestId request_id;
{ {
std::unique_ptr<test::TestAudioFocusObserver> observer = CreateObserver(); std::unique_ptr<test::TestAudioFocusObserver> observer = CreateObserver();
request_id = RequestAudioFocus( RequestAudioFocus(&media_session,
&media_session, mojom::AudioFocusType::kGainTransientMayDuck); mojom::AudioFocusType::kGainTransientMayDuck);
EXPECT_EQ(1, GetTransientMaybeDuckCount()); EXPECT_EQ(1, GetTransientMaybeDuckCount());
EXPECT_EQ(mojom::AudioFocusType::kGainTransientMayDuck, EXPECT_EQ(mojom::AudioFocusType::kGainTransientMayDuck,
...@@ -562,7 +665,7 @@ TEST_F(AudioFocusManagerTest, AudioFocusObserver_TransientMayDuck) { ...@@ -562,7 +665,7 @@ TEST_F(AudioFocusManagerTest, AudioFocusObserver_TransientMayDuck) {
{ {
std::unique_ptr<test::TestAudioFocusObserver> observer = CreateObserver(); std::unique_ptr<test::TestAudioFocusObserver> observer = CreateObserver();
AbandonAudioFocus(request_id); AbandonAudioFocus(&media_session);
EXPECT_EQ(0, GetTransientMaybeDuckCount()); EXPECT_EQ(0, GetTransientMaybeDuckCount());
EXPECT_TRUE(observer->focus_lost_session_.Equals( EXPECT_TRUE(observer->focus_lost_session_.Equals(
...@@ -570,4 +673,20 @@ TEST_F(AudioFocusManagerTest, AudioFocusObserver_TransientMayDuck) { ...@@ -570,4 +673,20 @@ TEST_F(AudioFocusManagerTest, AudioFocusObserver_TransientMayDuck) {
} }
} }
TEST_F(AudioFocusManagerTest, GetDebugInfo) {
MockMediaSession media_session;
AudioFocusManager::RequestId request_id =
RequestAudioFocus(&media_session, mojom::AudioFocusType::kGain);
mojom::MediaSessionDebugInfoPtr debug_info = GetDebugInfo(request_id);
EXPECT_EQ(kExampleDebugInfoName, debug_info->name);
EXPECT_EQ(kExampleDebugInfoOwner, debug_info->owner);
EXPECT_EQ(kExampleDebugInfoState, debug_info->state);
}
TEST_F(AudioFocusManagerTest, GetDebugInfo_BadRequestId) {
mojom::MediaSessionDebugInfoPtr debug_info = GetDebugInfo(kNoFocusedSession);
EXPECT_TRUE(debug_info->name.empty());
}
} // namespace media_session } // namespace media_session
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
"service_manager:connector": { "service_manager:connector": {
"provides": { "provides": {
"app": [ "app": [
"media_session.mojom.AudioFocus" "media_session.mojom.AudioFocusManager"
], ],
"tests": [ "*" ] "tests": [ "*" ]
} }
......
...@@ -4,6 +4,8 @@ ...@@ -4,6 +4,8 @@
#include "services/media_session/media_session_service.h" #include "services/media_session/media_session_service.h"
#include "base/bind.h"
#include "services/media_session/audio_focus_manager.h"
#include "services/service_manager/public/cpp/service_context.h" #include "services/service_manager/public/cpp/service_context.h"
namespace media_session { namespace media_session {
...@@ -14,12 +16,17 @@ std::unique_ptr<service_manager::Service> MediaSessionService::Create() { ...@@ -14,12 +16,17 @@ std::unique_ptr<service_manager::Service> MediaSessionService::Create() {
MediaSessionService::MediaSessionService() = default; MediaSessionService::MediaSessionService() = default;
MediaSessionService::~MediaSessionService() = default; MediaSessionService::~MediaSessionService() {
AudioFocusManager::GetInstance()->CloseAllMojoObjects();
}
void MediaSessionService::OnStart() { void MediaSessionService::OnStart() {
DLOG(ERROR) << "start";
ref_factory_.reset(new service_manager::ServiceContextRefFactory( ref_factory_.reset(new service_manager::ServiceContextRefFactory(
context()->CreateQuitClosure())); context()->CreateQuitClosure()));
registry_.AddInterface(
base::BindRepeating(&AudioFocusManager::BindToInterface,
base::Unretained(AudioFocusManager::GetInstance())));
} }
void MediaSessionService::OnBindInterface( void MediaSessionService::OnBindInterface(
......
...@@ -9,15 +9,23 @@ import "services/media_session/public/mojom/media_session.mojom"; ...@@ -9,15 +9,23 @@ import "services/media_session/public/mojom/media_session.mojom";
// These are the different types of audio focus that can be requested. // These are the different types of audio focus that can be requested.
enum AudioFocusType { enum AudioFocusType {
// Request permanent audio focus when you plan to play audio for the // Request permanent audio focus when you plan to play audio for the
// foreseeable future (for example, when playing music) and you expect // foreseeable future (for example, when playing music) and you expect the
// the previous holder of audio focus to stop playing. // previous holder of audio focus to stop playing.
kGain, kGain,
// Request transient focus when you expect to play audio for only a // Request transient focus when you expect to play audio for only a short
// short time and you expect the previous holder to pause playing. // time and you expect the previous holder to pause playing.
kGainTransientMayDuck, kGainTransientMayDuck,
}; };
// Contains information about |MediaSessions| that have requested audio focus
// and their current requested type.
struct AudioFocusRequestState {
MediaSessionInfo session_info;
AudioFocusType audio_focus_type;
uint64 request_id;
};
// The observer for audio focus events. // The observer for audio focus events.
interface AudioFocusObserver { interface AudioFocusObserver {
// The given |session| gained audio focus with the specified |type|. // The given |session| gained audio focus with the specified |type|.
...@@ -26,3 +34,42 @@ interface AudioFocusObserver { ...@@ -26,3 +34,42 @@ interface AudioFocusObserver {
// The given |session| lost audio focus. // The given |session| lost audio focus.
OnFocusLost(MediaSessionInfo session); OnFocusLost(MediaSessionInfo session);
}; };
// Controls audio focus for an associated request.
interface AudioFocusRequestClient {
// Requests updated audio focus for this request. If the request was granted
// then the callback will resolve.
RequestAudioFocus(MediaSessionInfo session_info, AudioFocusType type) => ();
// Abandons audio focus for this request.
AbandonAudioFocus();
// Notifies the audio focus backend when the associated session info changes.
MediaSessionInfoChanged(MediaSessionInfo session_info);
// Retrieve a unique ID for this request.
GetRequestId() => (uint64 request_id);
};
// Controls audio focus across the entire system.
interface AudioFocusManager {
// Requests audio focus with |type| for the |media_session| with
// |session_info|. Media sessions should provide a |request| that will
// provide an AudioFocusRequestClient that can be used to control this
// request. The callback will resolve when audio focus has been granted.
RequestAudioFocus(AudioFocusRequestClient& client,
MediaSession media_session,
MediaSessionInfo session_info,
AudioFocusType type) => ();
// Gets all the information about all |MediaSessions| that have requested
// audio focus and their current requested type.
GetFocusRequests() => (array<AudioFocusRequestState> requests);
// Gets debugging information for a |MediaSession| with |request_id|.
GetDebugInfoForRequest(uint64 request_id)
=> (MediaSessionDebugInfo debug_info);
// Adds observers that receive audio focus events.
AddObserver(AudioFocusObserver observer);
};
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