blob: b4df149e4f5ae9ab67e256b45d2b31c7502cba6d [file]
// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "services/audio/loopback_coordinator.h"
#include "base/check.h"
#include "base/functional/bind.h"
#include "base/logging.h"
namespace audio {
namespace {
// Matcher implementation that matches a specific group ID.
class MatchingGroupIdMatcher : public LoopbackGroupObserver::Matcher {
public:
explicit MatchingGroupIdMatcher(
const base::UnguessableToken& group_id_to_match)
: group_id_to_match_(group_id_to_match) {}
~MatchingGroupIdMatcher() override = default;
bool Match(const LoopbackCoordinator::Member& member) const override {
return member.group_id == group_id_to_match_;
}
private:
const base::UnguessableToken group_id_to_match_;
};
// Matcher implementation that excludes a specific group ID.
class ExcludingGroupIdMatcher : public LoopbackGroupObserver::Matcher {
public:
explicit ExcludingGroupIdMatcher(
const base::UnguessableToken& group_id_to_exclude)
: group_id_to_exclude_(group_id_to_exclude) {}
~ExcludingGroupIdMatcher() override = default;
bool Match(const LoopbackCoordinator::Member& member) const override {
return member.group_id != group_id_to_exclude_;
}
private:
const base::UnguessableToken group_id_to_exclude_;
};
} // namespace
LoopbackCoordinator::LoopbackCoordinator() {
// The sequence checker is automatically bound to the sequence of creation.
}
LoopbackCoordinator::~LoopbackCoordinator() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
CHECK(observers_.empty())
<< "LoopbackCoordinator destroyed with active observers.";
CHECK(members_.empty())
<< "LoopbackCoordinator destroyed with active members.";
}
void LoopbackCoordinator::AddMember(const base::UnguessableToken& group_id,
LoopbackSource* loopback_source) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
CHECK(loopback_source);
// Attempt to add the new member to the set.
const auto [iterator, was_inserted] =
members_.emplace(group_id, loopback_source);
if (!was_inserted) {
// Already added;
return;
}
// Notify observers that a new member has been added.
for (const auto& observer : observers_) {
observer->OnMemberAdded(*iterator);
}
}
void LoopbackCoordinator::RemoveMember(LoopbackSource* loopback_source) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
CHECK(loopback_source);
// Find the member using the provided LoopbackSource*.
const auto it = members_.find(loopback_source);
if (it == members_.end()) {
// Already removed.
return;
}
// Per the contract, notify observers *before* removing the member.
const Member member_to_remove = *it;
for (const auto& observer : observers_) {
observer->OnMemberRemoved(member_to_remove);
}
members_.erase(it);
}
void LoopbackCoordinator::AddObserver(Observer* observer) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
CHECK(observer);
observers_.insert(observer);
}
void LoopbackCoordinator::RemoveObserver(Observer* observer) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
observers_.erase(observer);
}
void LoopbackCoordinator::ForEachMember(
base::RepeatingCallback<void(const Member&)> callback) const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
for (const Member& member : members_) {
callback.Run(member);
}
}
// static
std::unique_ptr<LoopbackGroupObserver>
LoopbackGroupObserver::CreateMatchingGroupObserver(
LoopbackCoordinator* coordinator,
const base::UnguessableToken& group_id) {
return std::make_unique<LoopbackGroupObserver>(
coordinator, std::make_unique<MatchingGroupIdMatcher>(group_id));
}
// static
std::unique_ptr<LoopbackGroupObserver>
LoopbackGroupObserver::CreateExcludingGroupObserver(
LoopbackCoordinator* coordinator,
const base::UnguessableToken& group_id) {
return std::make_unique<LoopbackGroupObserver>(
coordinator, std::make_unique<ExcludingGroupIdMatcher>(group_id));
}
LoopbackGroupObserver::LoopbackGroupObserver(LoopbackCoordinator* coordinator,
std::unique_ptr<Matcher> matcher)
: coordinator_(coordinator), matcher_(std::move(matcher)) {
CHECK(matcher_);
}
LoopbackGroupObserver::~LoopbackGroupObserver() {
StopObserving();
}
void LoopbackGroupObserver::StartObserving(Listener* listener) {
if (listener_) {
return;
}
listener_ = listener;
coordinator_->AddObserver(this);
}
void LoopbackGroupObserver::StopObserving() {
if (!listener_) {
return;
}
coordinator_->RemoveObserver(this);
listener_ = nullptr;
}
void LoopbackGroupObserver::ForEachSource(SourceCallback callback) const {
coordinator_->ForEachMember(base::BindRepeating(
[](const Matcher* matcher, const SourceCallback& inner_callback,
const LoopbackCoordinator::Member& member) {
if (matcher->Match(member)) {
inner_callback.Run(member.loopback_source);
}
},
matcher_.get(), callback));
}
void LoopbackGroupObserver::OnMemberAdded(
const LoopbackCoordinator::Member& member) {
CHECK(listener_);
if (matcher_->Match(member)) {
listener_->OnSourceAdded(member.loopback_source);
}
}
void LoopbackGroupObserver::OnMemberRemoved(
const LoopbackCoordinator::Member& member) {
CHECK(listener_);
if (matcher_->Match(member)) {
listener_->OnSourceRemoved(member.loopback_source);
}
}
} // namespace audio