Commit 608b2a89 authored by Becca Hughes's avatar Becca Hughes Committed by Commit Bot

[Media Session] Move AudioFocusManager to //services

Move AudioFocusManager and its unit tests to //services.

This CL does not remove the existing AudioFocusManager in
//content as this will be done in a follow up CL with
some mojo work.

BUG=875004

Change-Id: Ife982e671d21ca7be29a6bed38097e842937b35f
Reviewed-on: https://chromium-review.googlesource.com/c/1197351
Commit-Queue: Becca Hughes <beccahughes@chromium.org>
Reviewed-by: default avatarTommy Steimel <steimel@chromium.org>
Cr-Commit-Position: refs/heads/master@{#596367}
parent ca683c0b
......@@ -12,12 +12,15 @@ import("//testing/test.gni")
source_set("lib") {
sources = [
"audio_focus_manager.cc",
"audio_focus_manager.h",
"media_session_service.cc",
"media_session_service.h",
]
deps = [
"//mojo/public/cpp/bindings",
"//services/media_session/public/mojom",
]
public_deps = [
......@@ -34,6 +37,7 @@ service_manifest("manifest") {
source_set("tests") {
testonly = true
sources = [
"audio_focus_manager_unittest.cc",
"media_session_service_unittest.cc",
]
......@@ -41,6 +45,8 @@ source_set("tests") {
":lib",
"//base",
"//base/test:test_support",
"//services/media_session/public/cpp/test:test_support",
"//services/media_session/public/mojom",
"//services/service_manager/public/cpp/test:test_support",
"//testing/gtest",
]
......
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "services/media_session/audio_focus_manager.h"
#include "base/atomic_sequence_num.h"
#include "base/memory/ptr_util.h"
#include "base/threading/thread_task_runner_handle.h"
#include "mojo/public/cpp/bindings/interface_request.h"
#include "services/media_session/public/mojom/audio_focus.mojom.h"
namespace media_session {
namespace {
// Generate a unique audio focus request ID for the audio focus request. The IDs
// are only handed out by the audio focus manager.
int GenerateAudioFocusRequestId() {
static base::AtomicSequenceNumber request_id;
return request_id.GetNext();
}
} // namespace
// static
AudioFocusManager* AudioFocusManager::GetInstance() {
return base::Singleton<AudioFocusManager>::get();
}
AudioFocusManager::RequestResponse AudioFocusManager::RequestAudioFocus(
mojom::MediaSessionPtr media_session,
mojom::MediaSessionInfoPtr session_info,
mojom::AudioFocusType type,
base::Optional<RequestId> previous_id) {
DCHECK(!session_info.is_null());
DCHECK(media_session.is_bound());
if (!audio_focus_stack_.empty() &&
previous_id == audio_focus_stack_.back()->id() &&
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.
if (previous_id.has_value())
RemoveFocusEntryIfPresent(previous_id.value());
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);
}
}
}
audio_focus_stack_.push_back(std::make_unique<AudioFocusManager::StackRow>(
std::move(media_session), std::move(session_info), type,
previous_id ? *previous_id : GenerateAudioFocusRequestId()));
AudioFocusManager::StackRow* row = audio_focus_stack_.back().get();
row->session()->StopDucking();
// Notify observers that we were gained audio focus.
observers_.ForAllPtrs([&row, type](mojom::AudioFocusObserver* observer) {
observer->OnFocusGained(row->info().Clone(), type);
});
// We always grant the audio focus request but this may not always be the case
// in the future.
return AudioFocusManager::RequestResponse(row->id(), true);
}
void AudioFocusManager::AbandonAudioFocus(RequestId id) {
if (audio_focus_stack_.empty())
return;
if (audio_focus_stack_.back()->id() != id) {
RemoveFocusEntryIfPresent(id);
return;
}
auto row = std::move(audio_focus_stack_.back());
audio_focus_stack_.pop_back();
if (audio_focus_stack_.empty()) {
// Notify observers that we lost audio focus.
observers_.ForAllPtrs([&row](mojom::AudioFocusObserver* observer) {
observer->OnFocusLost(row->info().Clone());
});
return;
}
// Allow the top-most MediaSession having force duck to unduck even if
// it is not active.
for (auto iter = audio_focus_stack_.rbegin();
iter != audio_focus_stack_.rend(); ++iter) {
if (!(*iter)->info()->force_duck)
continue;
auto duck_row = std::move(*iter);
duck_row->session()->StopDucking();
audio_focus_stack_.erase(std::next(iter).base());
audio_focus_stack_.push_back(std::move(duck_row));
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()->session()->StopDucking();
// Notify observers that we lost audio focus.
observers_.ForAllPtrs([&row](mojom::AudioFocusObserver* observer) {
observer->OnFocusLost(row->info().Clone());
});
}
mojo::InterfacePtrSetElementId AudioFocusManager::AddObserver(
mojom::AudioFocusObserverPtr observer) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
return observers_.AddPtr(std::move(observer));
}
mojom::AudioFocusType AudioFocusManager::GetFocusTypeForSession(RequestId id) {
for (auto& row : audio_focus_stack_) {
if (row->id() == id)
return row->audio_focus_type();
}
NOTREACHED();
return mojom::AudioFocusType::kGain;
}
void AudioFocusManager::RemoveObserver(mojo::InterfacePtrSetElementId id) {
observers_.RemovePtr(id);
}
void AudioFocusManager::ResetForTesting() {
audio_focus_stack_.clear();
observers_.CloseAll();
}
void AudioFocusManager::FlushForTesting() {
observers_.FlushForTesting();
for (auto& session : audio_focus_stack_)
session->FlushForTesting();
}
AudioFocusManager::AudioFocusManager() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
}
AudioFocusManager::~AudioFocusManager() = default;
void AudioFocusManager::RemoveFocusEntryIfPresent(
AudioFocusManager::RequestId id) {
for (auto iter = audio_focus_stack_.begin(); iter != audio_focus_stack_.end();
++iter) {
if ((*iter)->id() == id) {
audio_focus_stack_.erase(iter);
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() {
session_.FlushForTesting();
binding_.FlushForTesting();
}
void AudioFocusManager::StackRow::OnConnectionError() {
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::BindOnce(&AudioFocusManager::AbandonAudioFocus,
base::Unretained(AudioFocusManager::GetInstance()), id()));
}
} // namespace media_session
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef SERVICES_MEDIA_SESSION_AUDIO_FOCUS_MANAGER_H_
#define SERVICES_MEDIA_SESSION_AUDIO_FOCUS_MANAGER_H_
#include <list>
#include <unordered_map>
#include "base/memory/singleton.h"
#include "base/threading/thread_checker.h"
#include "mojo/public/cpp/bindings/binding.h"
#include "mojo/public/cpp/bindings/interface_ptr.h"
#include "mojo/public/cpp/bindings/interface_ptr_set.h"
#include "services/media_session/public/mojom/audio_focus.mojom.h"
namespace media_session {
class AudioFocusManager {
public:
using RequestId = int;
using RequestResponse = std::pair<RequestId, bool>;
// Returns Chromium's internal AudioFocusManager.
static AudioFocusManager* GetInstance();
// Requests audio focus with |type| for the |media_session| with
// |session_info|. The function will return a |RequestResponse| which contains
// a |RequestId| and a boolean as to whether the request was successful. If a
// session wishes to request a different focus type it should provide its
// previous request id as |previous_id|.
RequestResponse RequestAudioFocus(mojom::MediaSessionPtr media_session,
mojom::MediaSessionInfoPtr session_info,
mojom::AudioFocusType type,
base::Optional<RequestId> previous_id);
// Abandons audio focus for a request with |id|. The next request on top of
// the stack will be granted audio focus.
void AbandonAudioFocus(RequestId id);
// Returns the current audio focus type for a request with |id|.
mojom::AudioFocusType GetFocusTypeForSession(RequestId id);
// Adds/removes audio focus observers.
mojo::InterfacePtrSetElementId AddObserver(mojom::AudioFocusObserverPtr);
void RemoveObserver(mojo::InterfacePtrSetElementId);
private:
friend struct base::DefaultSingletonTraits<AudioFocusManager>;
friend class AudioFocusManagerTest;
// Media internals UI needs access to internal state.
friend class MediaInternalsAudioFocusTest;
friend class MediaInternals;
// Flush for testing will flush any pending messages to the observers.
void FlushForTesting();
// Reset for testing will clear any built up internal state.
void ResetForTesting();
AudioFocusManager();
~AudioFocusManager();
void RemoveFocusEntryIfPresent(RequestId id);
// Weak reference of managed observers. Observers are expected to remove
// themselves before being destroyed.
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 MediaSession must abandon audio focus before its destruction.
std::list<std::unique_ptr<StackRow>> audio_focus_stack_;
// Adding observers should happen on the same thread that the service is
// running on.
THREAD_CHECKER(thread_checker_);
DISALLOW_COPY_AND_ASSIGN(AudioFocusManager);
};
} // namespace media_session
#endif // SERVICES_MEDIA_SESSION_AUDIO_FOCUS_MANAGER_H_
This diff is collapsed.
# Copyright 2018 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
component("test_support") {
output_name = "media_session_test_support_cpp"
sources = [
"audio_focus_test_util.cc",
"audio_focus_test_util.h",
]
deps = [
"//base",
"//services/media_session/public/mojom",
]
defines = [ "IS_MEDIA_SESSION_TEST_SUPPORT_CPP_IMPL" ]
}
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "services/media_session/public/cpp/test/audio_focus_test_util.h"
namespace media_session {
namespace test {
namespace {
void ReceivedSessionInfo(media_session::mojom::MediaSessionInfoPtr* info_out,
base::RepeatingClosure callback,
media_session::mojom::MediaSessionInfoPtr result) {
*info_out = std::move(result);
std::move(callback).Run();
}
} // namespace
TestAudioFocusObserver::TestAudioFocusObserver() : binding_(this) {}
TestAudioFocusObserver::~TestAudioFocusObserver() = default;
void TestAudioFocusObserver::OnFocusGained(
media_session::mojom::MediaSessionInfoPtr session,
media_session::mojom::AudioFocusType type) {
focus_gained_type_ = type;
focus_gained_session_ = std::move(session);
if (wait_for_gained_)
run_loop_.Quit();
}
void TestAudioFocusObserver::OnFocusLost(
media_session::mojom::MediaSessionInfoPtr session) {
focus_lost_session_ = std::move(session);
if (wait_for_lost_)
run_loop_.Quit();
}
void TestAudioFocusObserver::WaitForGainedEvent() {
if (!focus_gained_session_.is_null())
return;
wait_for_gained_ = true;
run_loop_.Run();
}
void TestAudioFocusObserver::WaitForLostEvent() {
if (!focus_lost_session_.is_null())
return;
wait_for_lost_ = true;
run_loop_.Run();
}
void TestAudioFocusObserver::BindToMojoRequest(
media_session::mojom::AudioFocusObserverRequest request) {
binding_.Bind(std::move(request));
}
media_session::mojom::MediaSessionInfoPtr GetMediaSessionInfoSync(
media_session::mojom::MediaSession* session) {
media_session::mojom::MediaSessionInfoPtr session_info;
base::RunLoop run_loop;
session->GetMediaSessionInfo(base::BindOnce(
&ReceivedSessionInfo, &session_info, run_loop.QuitClosure()));
return session_info;
}
} // namespace test
} // namespace media_session
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef SERVICES_MEDIA_SESSION_PUBLIC_CPP_TEST_AUDIO_FOCUS_TEST_UTIL_H_
#define SERVICES_MEDIA_SESSION_PUBLIC_CPP_TEST_AUDIO_FOCUS_TEST_UTIL_H_
#include "base/component_export.h"
#include "base/optional.h"
#include "base/run_loop.h"
#include "mojo/public/cpp/bindings/binding.h"
#include "services/media_session/public/mojom/audio_focus.mojom.h"
namespace media_session {
namespace test {
class COMPONENT_EXPORT(MEDIA_SESSION_TEST_SUPPORT_CPP) TestAudioFocusObserver
: public mojom::AudioFocusObserver {
public:
TestAudioFocusObserver();
~TestAudioFocusObserver() override;
void OnFocusGained(media_session::mojom::MediaSessionInfoPtr,
media_session::mojom::AudioFocusType) override;
void OnFocusLost(media_session::mojom::MediaSessionInfoPtr) override;
void WaitForGainedEvent();
void WaitForLostEvent();
media_session::mojom::AudioFocusType focus_gained_type() const {
DCHECK(!focus_gained_session_.is_null());
return focus_gained_type_;
}
void BindToMojoRequest(media_session::mojom::AudioFocusObserverRequest);
// These store the values we received.
media_session::mojom::MediaSessionInfoPtr focus_gained_session_;
media_session::mojom::MediaSessionInfoPtr focus_lost_session_;
private:
mojo::Binding<mojom::AudioFocusObserver> binding_;
media_session::mojom::AudioFocusType focus_gained_type_;
// If either of these are true we will quit the run loop if we observe a gain
// or lost event.
bool wait_for_gained_ = false;
bool wait_for_lost_ = false;
base::RunLoop run_loop_;
};
COMPONENT_EXPORT(MEDIA_SESSION_TEST_SUPPORT_CPP)
media_session::mojom::MediaSessionInfoPtr GetMediaSessionInfoSync(
media_session::mojom::MediaSession*);
} // namespace test
} // namespace media_session
#endif // SERVICES_MEDIA_SESSION_PUBLIC_CPP_TEST_AUDIO_FOCUS_TEST_UTIL_H_
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