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") ...@@ -12,12 +12,15 @@ import("//testing/test.gni")
source_set("lib") { source_set("lib") {
sources = [ sources = [
"audio_focus_manager.cc",
"audio_focus_manager.h",
"media_session_service.cc", "media_session_service.cc",
"media_session_service.h", "media_session_service.h",
] ]
deps = [ deps = [
"//mojo/public/cpp/bindings", "//mojo/public/cpp/bindings",
"//services/media_session/public/mojom",
] ]
public_deps = [ public_deps = [
...@@ -34,6 +37,7 @@ service_manifest("manifest") { ...@@ -34,6 +37,7 @@ service_manifest("manifest") {
source_set("tests") { source_set("tests") {
testonly = true testonly = true
sources = [ sources = [
"audio_focus_manager_unittest.cc",
"media_session_service_unittest.cc", "media_session_service_unittest.cc",
] ]
...@@ -41,6 +45,8 @@ source_set("tests") { ...@@ -41,6 +45,8 @@ source_set("tests") {
":lib", ":lib",
"//base", "//base",
"//base/test:test_support", "//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", "//services/service_manager/public/cpp/test:test_support",
"//testing/gtest", "//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_
// 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 <memory>
#include "base/command_line.h"
#include "base/run_loop.h"
#include "base/test/scoped_task_environment.h"
#include "mojo/public/cpp/bindings/binding_set.h"
#include "services/media_session/public/cpp/test/audio_focus_test_util.h"
#include "services/media_session/public/mojom/audio_focus.mojom.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace media_session {
namespace {
static const base::Optional<AudioFocusManager::RequestId> kNoRequestId;
static const AudioFocusManager::RequestId kNoFocusedSession = -1;
class MockMediaSession : public mojom::MediaSession {
public:
MockMediaSession() = default;
explicit MockMediaSession(bool force_duck) : force_duck_(force_duck) {}
~MockMediaSession() override {}
void Suspend(SuspendType suspend_type) override {
DCHECK_EQ(SuspendType::kSystem, suspend_type);
SetState(mojom::MediaSessionInfo::SessionState::kSuspended);
}
void StartDucking() override {
is_ducking_ = true;
NotifyObservers();
}
void StopDucking() override {
is_ducking_ = false;
NotifyObservers();
}
void GetMediaSessionInfo(GetMediaSessionInfoCallback callback) override {
std::move(callback).Run(GetSessionInfoSync());
}
void AddObserver(mojom::MediaSessionObserverPtr observer) override {
observers_.AddPtr(std::move(observer));
}
void GetDebugInfo(GetDebugInfoCallback callback) override {}
void BindToMojoRequest(mojo::InterfaceRequest<mojom::MediaSession> request) {
bindings_.AddBinding(this, std::move(request));
}
void SetState(mojom::MediaSessionInfo::SessionState state) {
state_ = state;
NotifyObservers();
}
bool has_observers() const { return !observers_.empty(); }
void CloseAllObservers() { observers_.CloseAll(); }
private:
mojom::MediaSessionInfoPtr GetSessionInfoSync() {
mojom::MediaSessionInfoPtr info(mojom::MediaSessionInfo::New());
info->force_duck = force_duck_;
info->state = state_;
if (is_ducking_)
info->state = mojom::MediaSessionInfo::SessionState::kDucking;
return info;
}
void NotifyObservers() {
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;
bool is_ducking_ = false;
mojom::MediaSessionInfo::SessionState state_ =
mojom::MediaSessionInfo::SessionState::kInactive;
mojo::InterfacePtrSet<mojom::MediaSessionObserver> observers_;
mojo::BindingSet<mojom::MediaSession> bindings_;
};
} // anonymous namespace
class AudioFocusManagerTest : public testing::Test {
public:
AudioFocusManagerTest() = default;
void SetUp() override {
// AudioFocusManager is a singleton so we should make sure we reset any
// state in between tests.
AudioFocusManager::GetInstance()->ResetForTesting();
}
void TearDown() override {
// Run pending tasks.
base::RunLoop().RunUntilIdle();
}
AudioFocusManager::RequestId GetAudioFocusedSession() const {
const AudioFocusManager* manager = AudioFocusManager::GetInstance();
const auto& audio_focus_stack = manager->audio_focus_stack_;
for (auto iter = audio_focus_stack.rbegin();
iter != audio_focus_stack.rend(); ++iter) {
if ((*iter)->audio_focus_type() == mojom::AudioFocusType::kGain)
return (*iter)->id();
}
return kNoFocusedSession;
}
int GetTransientMaybeDuckCount() const {
const AudioFocusManager* manager = AudioFocusManager::GetInstance();
const auto& audio_focus_stack = manager->audio_focus_stack_;
int count = 0;
for (auto iter = audio_focus_stack.rbegin();
iter != audio_focus_stack.rend(); ++iter) {
if ((*iter)->audio_focus_type() ==
mojom::AudioFocusType::kGainTransientMayDuck)
++count;
else
break;
}
return count;
}
void AbandonAudioFocus(AudioFocusManager::RequestId id) {
AudioFocusManager::GetInstance()->AbandonAudioFocus(id);
FlushForTesting();
}
AudioFocusManager::RequestId RequestAudioFocus(
MockMediaSession* session,
mojom::AudioFocusType audio_focus_type) {
return RequestAudioFocus(session, audio_focus_type, kNoRequestId);
}
AudioFocusManager::RequestId RequestAudioFocus(
MockMediaSession* session,
mojom::AudioFocusType audio_focus_type,
base::Optional<AudioFocusManager::RequestId> previous_id) {
mojom::MediaSessionPtr media_session;
session->BindToMojoRequest(mojo::MakeRequest(&media_session));
AudioFocusManager::RequestResponse response =
AudioFocusManager::GetInstance()->RequestAudioFocus(
std::move(media_session), test::GetMediaSessionInfoSync(session),
audio_focus_type, previous_id);
// If the audio focus was granted then we should set the session state to
// active.
if (response.second)
session->SetState(mojom::MediaSessionInfo::SessionState::kActive);
FlushForTesting();
return response.first;
}
mojom::MediaSessionInfo::SessionState GetState(MockMediaSession* session) {
return test::GetMediaSessionInfoSync(session)->state;
}
std::unique_ptr<test::TestAudioFocusObserver> CreateObserver() {
std::unique_ptr<test::TestAudioFocusObserver> observer =
std::make_unique<test::TestAudioFocusObserver>();
mojom::AudioFocusObserverPtr observer_ptr;
observer->BindToMojoRequest(mojo::MakeRequest(&observer_ptr));
AudioFocusManager::GetInstance()->AddObserver(std::move(observer_ptr));
return observer;
}
private:
void FlushForTesting() {
AudioFocusManager::GetInstance()->FlushForTesting();
}
base::test::ScopedTaskEnvironment task_environment_;
DISALLOW_COPY_AND_ASSIGN(AudioFocusManagerTest);
};
TEST_F(AudioFocusManagerTest, InstanceAvailableAndSame) {
AudioFocusManager* audio_focus_manager = AudioFocusManager::GetInstance();
EXPECT_TRUE(!!audio_focus_manager);
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) {
MockMediaSession media_session_1;
MockMediaSession media_session_2;
MockMediaSession media_session_3;
EXPECT_EQ(kNoFocusedSession, GetAudioFocusedSession());
EXPECT_EQ(mojom::MediaSessionInfo::SessionState::kInactive,
GetState(&media_session_1));
EXPECT_EQ(mojom::MediaSessionInfo::SessionState::kInactive,
GetState(&media_session_2));
EXPECT_EQ(mojom::MediaSessionInfo::SessionState::kInactive,
GetState(&media_session_3));
AudioFocusManager::RequestId request_id_1 =
RequestAudioFocus(&media_session_1, mojom::AudioFocusType::kGain);
EXPECT_EQ(request_id_1, GetAudioFocusedSession());
EXPECT_EQ(mojom::MediaSessionInfo::SessionState::kActive,
GetState(&media_session_1));
AudioFocusManager::RequestId request_id_2 =
RequestAudioFocus(&media_session_2, mojom::AudioFocusType::kGain);
EXPECT_EQ(request_id_2, GetAudioFocusedSession());
EXPECT_EQ(mojom::MediaSessionInfo::SessionState::kSuspended,
GetState(&media_session_1));
AudioFocusManager::RequestId request_id_3 =
RequestAudioFocus(&media_session_3, mojom::AudioFocusType::kGain);
EXPECT_EQ(request_id_3, GetAudioFocusedSession());
EXPECT_EQ(mojom::MediaSessionInfo::SessionState::kSuspended,
GetState(&media_session_2));
}
TEST_F(AudioFocusManagerTest, RequestAudioFocusGain_Duplicate) {
MockMediaSession media_session;
EXPECT_EQ(kNoFocusedSession, GetAudioFocusedSession());
AudioFocusManager::RequestId request_id =
RequestAudioFocus(&media_session, mojom::AudioFocusType::kGain);
EXPECT_EQ(request_id, GetAudioFocusedSession());
RequestAudioFocus(&media_session, mojom::AudioFocusType::kGain, request_id);
EXPECT_EQ(request_id, GetAudioFocusedSession());
}
TEST_F(AudioFocusManagerTest, RequestAudioFocusGain_FromTransient) {
MockMediaSession media_session;
AudioFocusManager::RequestId request_id = RequestAudioFocus(
&media_session, mojom::AudioFocusType::kGainTransientMayDuck);
EXPECT_EQ(kNoFocusedSession, GetAudioFocusedSession());
EXPECT_EQ(1, GetTransientMaybeDuckCount());
RequestAudioFocus(&media_session, mojom::AudioFocusType::kGain, request_id);
EXPECT_EQ(request_id, GetAudioFocusedSession());
EXPECT_EQ(0, GetTransientMaybeDuckCount());
}
TEST_F(AudioFocusManagerTest, RequestAudioFocusTransient_FromGain) {
MockMediaSession media_session;
AudioFocusManager::RequestId request_id =
RequestAudioFocus(&media_session, mojom::AudioFocusType::kGain);
EXPECT_EQ(request_id, GetAudioFocusedSession());
EXPECT_EQ(0, GetTransientMaybeDuckCount());
RequestAudioFocus(&media_session,
mojom::AudioFocusType::kGainTransientMayDuck, request_id);
EXPECT_EQ(kNoFocusedSession, GetAudioFocusedSession());
EXPECT_EQ(1, GetTransientMaybeDuckCount());
EXPECT_NE(mojom::MediaSessionInfo::SessionState::kDucking,
GetState(&media_session));
}
TEST_F(AudioFocusManagerTest, RequestAudioFocusTransient_FromGainWhileDucking) {
MockMediaSession media_session_1;
MockMediaSession media_session_2;
AudioFocusManager::RequestId request_id =
RequestAudioFocus(&media_session_1, mojom::AudioFocusType::kGain);
EXPECT_EQ(0, GetTransientMaybeDuckCount());
EXPECT_EQ(mojom::MediaSessionInfo::SessionState::kActive,
GetState(&media_session_1));
RequestAudioFocus(&media_session_2,
mojom::AudioFocusType::kGainTransientMayDuck);
EXPECT_EQ(1, GetTransientMaybeDuckCount());
EXPECT_EQ(mojom::MediaSessionInfo::SessionState::kDucking,
GetState(&media_session_1));
RequestAudioFocus(&media_session_1,
mojom::AudioFocusType::kGainTransientMayDuck, request_id);
EXPECT_EQ(2, GetTransientMaybeDuckCount());
EXPECT_EQ(mojom::MediaSessionInfo::SessionState::kActive,
GetState(&media_session_1));
}
TEST_F(AudioFocusManagerTest, AbandonAudioFocus_RemovesFocusedEntry) {
MockMediaSession media_session;
AudioFocusManager::RequestId request_id =
RequestAudioFocus(&media_session, mojom::AudioFocusType::kGain);
EXPECT_EQ(request_id, GetAudioFocusedSession());
AbandonAudioFocus(request_id);
EXPECT_EQ(kNoFocusedSession, GetAudioFocusedSession());
}
TEST_F(AudioFocusManagerTest, AbandonAudioFocus_NoAssociatedEntry) {
AbandonAudioFocus(kNoFocusedSession);
EXPECT_EQ(kNoFocusedSession, GetAudioFocusedSession());
}
TEST_F(AudioFocusManagerTest, AbandonAudioFocus_RemovesTransientEntry) {
MockMediaSession media_session;
AudioFocusManager::RequestId request_id = RequestAudioFocus(
&media_session, mojom::AudioFocusType::kGainTransientMayDuck);
EXPECT_EQ(1, GetTransientMaybeDuckCount());
{
std::unique_ptr<test::TestAudioFocusObserver> observer = CreateObserver();
AbandonAudioFocus(request_id);
EXPECT_EQ(0, GetTransientMaybeDuckCount());
EXPECT_TRUE(observer->focus_lost_session_.Equals(
test::GetMediaSessionInfoSync(&media_session)));
}
}
TEST_F(AudioFocusManagerTest, AbandonAudioFocus_WhileDuckingThenResume) {
MockMediaSession media_session_1;
MockMediaSession media_session_2;
AudioFocusManager::RequestId request_id_1 =
RequestAudioFocus(&media_session_1, mojom::AudioFocusType::kGain);
EXPECT_EQ(0, GetTransientMaybeDuckCount());
EXPECT_NE(mojom::MediaSessionInfo::SessionState::kDucking,
GetState(&media_session_1));
AudioFocusManager::RequestId request_id_2 = RequestAudioFocus(
&media_session_2, mojom::AudioFocusType::kGainTransientMayDuck);
EXPECT_EQ(1, GetTransientMaybeDuckCount());
EXPECT_EQ(mojom::MediaSessionInfo::SessionState::kDucking,
GetState(&media_session_1));
AbandonAudioFocus(request_id_1);
EXPECT_EQ(1, GetTransientMaybeDuckCount());
AbandonAudioFocus(request_id_2);
EXPECT_EQ(0, GetTransientMaybeDuckCount());
RequestAudioFocus(&media_session_1, mojom::AudioFocusType::kGain);
EXPECT_NE(mojom::MediaSessionInfo::SessionState::kDucking,
GetState(&media_session_1));
}
TEST_F(AudioFocusManagerTest, AbandonAudioFocus_StopsDucking) {
MockMediaSession media_session_1;
MockMediaSession media_session_2;
RequestAudioFocus(&media_session_1, mojom::AudioFocusType::kGain);
EXPECT_EQ(0, GetTransientMaybeDuckCount());
EXPECT_NE(mojom::MediaSessionInfo::SessionState::kDucking,
GetState(&media_session_1));
AudioFocusManager::RequestId request_id_2 = RequestAudioFocus(
&media_session_2, mojom::AudioFocusType::kGainTransientMayDuck);
EXPECT_EQ(1, GetTransientMaybeDuckCount());
EXPECT_EQ(mojom::MediaSessionInfo::SessionState::kDucking,
GetState(&media_session_1));
AbandonAudioFocus(request_id_2);
EXPECT_EQ(0, GetTransientMaybeDuckCount());
EXPECT_NE(mojom::MediaSessionInfo::SessionState::kDucking,
GetState(&media_session_1));
}
TEST_F(AudioFocusManagerTest, DuckWhilePlaying) {
MockMediaSession media_session_1;
MockMediaSession media_session_2;
RequestAudioFocus(&media_session_1, mojom::AudioFocusType::kGain);
EXPECT_NE(mojom::MediaSessionInfo::SessionState::kDucking,
GetState(&media_session_1));
RequestAudioFocus(&media_session_2,
mojom::AudioFocusType::kGainTransientMayDuck);
EXPECT_EQ(mojom::MediaSessionInfo::SessionState::kDucking,
GetState(&media_session_1));
}
TEST_F(AudioFocusManagerTest, GainSuspendsTransient) {
MockMediaSession media_session_1;
MockMediaSession media_session_2;
RequestAudioFocus(&media_session_2,
mojom::AudioFocusType::kGainTransientMayDuck);
RequestAudioFocus(&media_session_1, mojom::AudioFocusType::kGain);
EXPECT_EQ(mojom::MediaSessionInfo::SessionState::kSuspended,
GetState(&media_session_2));
}
TEST_F(AudioFocusManagerTest, DuckWithMultipleTransients) {
MockMediaSession media_session_1;
MockMediaSession media_session_2;
MockMediaSession media_session_3;
RequestAudioFocus(&media_session_1, mojom::AudioFocusType::kGain);
EXPECT_NE(mojom::MediaSessionInfo::SessionState::kDucking,
GetState(&media_session_1));
AudioFocusManager::RequestId request_id_2 = RequestAudioFocus(
&media_session_2, mojom::AudioFocusType::kGainTransientMayDuck);
EXPECT_EQ(mojom::MediaSessionInfo::SessionState::kDucking,
GetState(&media_session_1));
AudioFocusManager::RequestId request_id_3 = RequestAudioFocus(
&media_session_3, mojom::AudioFocusType::kGainTransientMayDuck);
EXPECT_EQ(mojom::MediaSessionInfo::SessionState::kDucking,
GetState(&media_session_1));
AbandonAudioFocus(request_id_2);
EXPECT_EQ(mojom::MediaSessionInfo::SessionState::kDucking,
GetState(&media_session_1));
AbandonAudioFocus(request_id_3);
EXPECT_NE(mojom::MediaSessionInfo::SessionState::kDucking,
GetState(&media_session_1));
}
TEST_F(AudioFocusManagerTest, MediaSessionDestroyed_ReleasesFocus) {
{
MockMediaSession media_session;
AudioFocusManager::RequestId request_id =
RequestAudioFocus(&media_session, mojom::AudioFocusType::kGain);
EXPECT_EQ(request_id, GetAudioFocusedSession());
}
// If the media session is destroyed without abandoning audio focus we do not
// know until we next interact with the manager.
MockMediaSession media_session;
RequestAudioFocus(&media_session,
mojom::AudioFocusType::kGainTransientMayDuck);
EXPECT_EQ(kNoFocusedSession, GetAudioFocusedSession());
}
TEST_F(AudioFocusManagerTest, MediaSessionDestroyed_ReleasesTransients) {
{
MockMediaSession media_session;
RequestAudioFocus(&media_session,
mojom::AudioFocusType::kGainTransientMayDuck);
EXPECT_EQ(1, GetTransientMaybeDuckCount());
}
// If the media session is destroyed without abandoning audio focus we do not
// know until we next interact with the manager.
MockMediaSession media_session;
RequestAudioFocus(&media_session, mojom::AudioFocusType::kGain);
EXPECT_EQ(0, GetTransientMaybeDuckCount());
}
TEST_F(AudioFocusManagerTest, GainDucksForceDuck) {
MockMediaSession media_session_1(true /* force_duck */);
MockMediaSession media_session_2;
RequestAudioFocus(&media_session_1, mojom::AudioFocusType::kGain);
AudioFocusManager::RequestId request_id_2 =
RequestAudioFocus(&media_session_2, mojom::AudioFocusType::kGain);
EXPECT_EQ(request_id_2, GetAudioFocusedSession());
EXPECT_EQ(mojom::MediaSessionInfo::SessionState::kDucking,
GetState(&media_session_1));
}
TEST_F(AudioFocusManagerTest,
AbandoningGainFocusRevokesTopMostForceDuckSession) {
MockMediaSession media_session_1(true /* force_duck */);
MockMediaSession media_session_2;
MockMediaSession media_session_3;
AudioFocusManager::RequestId request_id_1 =
RequestAudioFocus(&media_session_1, mojom::AudioFocusType::kGain);
RequestAudioFocus(&media_session_2, mojom::AudioFocusType::kGain);
AudioFocusManager::RequestId request_id_3 =
RequestAudioFocus(&media_session_3, mojom::AudioFocusType::kGain);
EXPECT_EQ(request_id_3, GetAudioFocusedSession());
EXPECT_EQ(mojom::MediaSessionInfo::SessionState::kSuspended,
GetState(&media_session_2));
EXPECT_EQ(mojom::MediaSessionInfo::SessionState::kDucking,
GetState(&media_session_1));
AbandonAudioFocus(request_id_3);
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) {
MockMediaSession media_session;
AudioFocusManager::RequestId request_id;
{
std::unique_ptr<test::TestAudioFocusObserver> observer = CreateObserver();
request_id =
RequestAudioFocus(&media_session, mojom::AudioFocusType::kGain);
EXPECT_EQ(request_id, GetAudioFocusedSession());
EXPECT_EQ(mojom::AudioFocusType::kGain, observer->focus_gained_type());
EXPECT_FALSE(observer->focus_gained_session_.is_null());
}
{
std::unique_ptr<test::TestAudioFocusObserver> observer = CreateObserver();
RequestAudioFocus(&media_session, mojom::AudioFocusType::kGain, request_id);
EXPECT_EQ(request_id, GetAudioFocusedSession());
EXPECT_TRUE(observer->focus_gained_session_.is_null());
}
}
TEST_F(AudioFocusManagerTest, AudioFocusObserver_TransientMayDuck) {
MockMediaSession media_session;
AudioFocusManager::RequestId request_id;
{
std::unique_ptr<test::TestAudioFocusObserver> observer = CreateObserver();
request_id = RequestAudioFocus(
&media_session, mojom::AudioFocusType::kGainTransientMayDuck);
EXPECT_EQ(1, GetTransientMaybeDuckCount());
EXPECT_EQ(mojom::AudioFocusType::kGainTransientMayDuck,
observer->focus_gained_type());
EXPECT_FALSE(observer->focus_gained_session_.is_null());
}
{
std::unique_ptr<test::TestAudioFocusObserver> observer = CreateObserver();
AbandonAudioFocus(request_id);
EXPECT_EQ(0, GetTransientMaybeDuckCount());
EXPECT_TRUE(observer->focus_lost_session_.Equals(
test::GetMediaSessionInfoSync(&media_session)));
}
}
} // 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.
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