1 // Copyright 2018 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "services/media_session/audio_focus_manager.h"
10 #include "base/containers/adapters.h"
11 #include "base/containers/cxx20_erase.h"
12 #include "base/functional/bind.h"
13 #include "base/power_monitor/power_monitor.h"
14 #include "base/power_monitor/power_observer.h"
15 #include "base/unguessable_token.h"
16 #include "mojo/public/cpp/bindings/remote.h"
17 #include "services/media_session/audio_focus_request.h"
18 #include "services/media_session/public/cpp/features.h"
19 #include "services/media_session/public/mojom/audio_focus.mojom.h"
21 namespace media_session {
25 mojom::EnforcementMode GetDefaultEnforcementMode() {
26 if (base::FeatureList::IsEnabled(features::kAudioFocusEnforcement)) {
27 if (base::FeatureList::IsEnabled(features::kAudioFocusSessionGrouping))
28 return mojom::EnforcementMode::kSingleGroup;
29 return mojom::EnforcementMode::kSingleSession;
32 return mojom::EnforcementMode::kNone;
37 // MediaPowerDelegate will pause all playback if the device is suspended.
38 class MediaPowerDelegate : public base::PowerSuspendObserver {
40 explicit MediaPowerDelegate(base::WeakPtr<AudioFocusManager> owner)
42 base::PowerMonitor::AddPowerSuspendObserver(this);
45 MediaPowerDelegate(const MediaPowerDelegate&) = delete;
46 MediaPowerDelegate& operator=(const MediaPowerDelegate&) = delete;
48 ~MediaPowerDelegate() override {
49 base::PowerMonitor::RemovePowerSuspendObserver(this);
52 // base::PowerSuspendObserver:
53 void OnSuspend() override {
55 owner_->SuspendAllSessions();
59 const base::WeakPtr<AudioFocusManager> owner_;
62 class AudioFocusManager::SourceObserverHolder {
64 SourceObserverHolder(AudioFocusManager* owner,
65 const base::UnguessableToken& source_id,
66 mojo::PendingRemote<mojom::AudioFocusObserver> observer)
67 : identity_(source_id), observer_(std::move(observer)) {
68 // Set a connection error handler so that we will remove observers that have
69 // had an error / been closed.
70 observer_.set_disconnect_handler(base::BindOnce(
71 &AudioFocusManager::CleanupSourceObservers, base::Unretained(owner)));
74 SourceObserverHolder(const SourceObserverHolder&) = delete;
75 SourceObserverHolder& operator=(const SourceObserverHolder&) = delete;
77 ~SourceObserverHolder() = default;
79 bool is_valid() const { return observer_.is_connected(); }
81 const base::UnguessableToken& identity() const { return identity_; }
83 void OnFocusGained(mojom::AudioFocusRequestStatePtr session) {
84 observer_->OnFocusGained(std::move(session));
87 void OnFocusLost(mojom::AudioFocusRequestStatePtr session) {
88 observer_->OnFocusLost(std::move(session));
91 void OnRequestIdReleased(const base::UnguessableToken& request_id) {
92 observer_->OnRequestIdReleased(request_id);
96 const base::UnguessableToken identity_;
97 mojo::Remote<mojom::AudioFocusObserver> observer_;
100 void AudioFocusManager::RequestAudioFocus(
101 mojo::PendingReceiver<mojom::AudioFocusRequestClient> receiver,
102 mojo::PendingRemote<mojom::MediaSession> session,
103 mojom::MediaSessionInfoPtr session_info,
104 mojom::AudioFocusType type,
105 RequestAudioFocusCallback callback) {
106 auto request_id = base::UnguessableToken::Create();
108 RequestAudioFocusInternal(
109 std::make_unique<AudioFocusRequest>(
110 weak_ptr_factory_.GetWeakPtr(), std::move(receiver),
111 std::move(session), std::move(session_info), type, request_id,
112 GetBindingSourceName(), base::UnguessableToken::Create(),
113 GetBindingIdentity()),
116 std::move(callback).Run(request_id);
119 void AudioFocusManager::RequestGroupedAudioFocus(
120 const base::UnguessableToken& request_id,
121 mojo::PendingReceiver<mojom::AudioFocusRequestClient> receiver,
122 mojo::PendingRemote<mojom::MediaSession> session,
123 mojom::MediaSessionInfoPtr session_info,
124 mojom::AudioFocusType type,
125 const base::UnguessableToken& group_id,
126 RequestGroupedAudioFocusCallback callback) {
127 if (IsFocusEntryPresent(request_id)) {
128 std::move(callback).Run(false /* success */);
132 RequestAudioFocusInternal(
133 std::make_unique<AudioFocusRequest>(
134 weak_ptr_factory_.GetWeakPtr(), std::move(receiver),
135 std::move(session), std::move(session_info), type, request_id,
136 GetBindingSourceName(), group_id, GetBindingIdentity()),
139 std::move(callback).Run(true /* success */);
142 void AudioFocusManager::GetFocusRequests(GetFocusRequestsCallback callback) {
143 std::vector<mojom::AudioFocusRequestStatePtr> requests;
145 for (const auto& row : audio_focus_stack_)
146 requests.push_back(row->ToAudioFocusRequestState());
148 std::move(callback).Run(std::move(requests));
151 void AudioFocusManager::GetDebugInfoForRequest(
152 const RequestId& request_id,
153 GetDebugInfoForRequestCallback callback) {
154 for (auto& row : audio_focus_stack_) {
155 if (row->id() != request_id)
158 row->ipc()->GetDebugInfo(base::BindOnce(
159 [](const base::UnguessableToken& group_id,
160 const base::UnguessableToken& identity,
161 GetDebugInfoForRequestCallback callback,
162 mojom::MediaSessionDebugInfoPtr info) {
163 // Inject the |group_id| into the state string. This is because in
164 // some cases the group id is automatically generated by the media
165 // session service so the session is unaware of it.
166 if (!info->state.empty())
168 info->state += "GroupId=" + group_id.ToString();
170 // Inject the identity into the state string.
171 info->state += " Identity=" + identity.ToString();
173 std::move(callback).Run(std::move(info));
175 row->group_id(), row->identity(), std::move(callback)));
179 std::move(callback).Run(mojom::MediaSessionDebugInfo::New());
182 void AudioFocusManager::AbandonAudioFocusInternal(RequestId id) {
183 if (audio_focus_stack_.empty())
186 bool was_top_most_session = audio_focus_stack_.back()->id() == id;
188 auto row = RemoveFocusEntryIfPresent(id);
193 MaybeUpdateActiveSession();
195 // Notify observers that we lost audio focus.
196 mojom::AudioFocusRequestStatePtr session_state =
197 row->ToAudioFocusRequestState();
199 for (const auto& observer : observers_)
200 observer->OnFocusLost(session_state.Clone());
202 for (auto& holder : source_observers_) {
203 if (holder->identity() == row->identity())
204 holder->OnFocusLost(session_state.Clone());
207 if (!was_top_most_session || audio_focus_stack_.empty())
210 // Notify observers that the session on top gained focus.
211 AudioFocusRequest* new_session = audio_focus_stack_.back().get();
213 for (const auto& observer : observers_)
214 observer->OnFocusGained(new_session->ToAudioFocusRequestState());
216 for (auto& holder : source_observers_) {
217 if (holder->identity() == new_session->identity())
218 holder->OnFocusGained(new_session->ToAudioFocusRequestState());
222 void AudioFocusManager::AddObserver(
223 mojo::PendingRemote<mojom::AudioFocusObserver> observer) {
224 DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
225 observers_.Add(std::move(observer));
228 void AudioFocusManager::SetSource(const base::UnguessableToken& identity,
229 const std::string& name) {
230 DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
232 auto& context = receivers_.current_context();
233 context->identity = identity;
234 context->source_name = name;
237 void AudioFocusManager::SetEnforcementMode(mojom::EnforcementMode mode) {
238 if (mode == mojom::EnforcementMode::kDefault)
239 mode = GetDefaultEnforcementMode();
241 if (mode == enforcement_mode_)
244 enforcement_mode_ = mode;
246 if (audio_focus_stack_.empty())
252 void AudioFocusManager::AddSourceObserver(
253 const base::UnguessableToken& source_id,
254 mojo::PendingRemote<mojom::AudioFocusObserver> observer) {
255 DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
256 source_observers_.push_back(std::make_unique<SourceObserverHolder>(
257 this, source_id, std::move(observer)));
260 void AudioFocusManager::GetSourceFocusRequests(
261 const base::UnguessableToken& source_id,
262 GetFocusRequestsCallback callback) {
263 std::vector<mojom::AudioFocusRequestStatePtr> requests;
265 for (const auto& row : audio_focus_stack_) {
266 if (row->identity() == source_id)
267 requests.push_back(row->ToAudioFocusRequestState());
270 std::move(callback).Run(std::move(requests));
273 void AudioFocusManager::RequestIdReleased(
274 const base::UnguessableToken& request_id) {
275 for (const auto& observer : observers_)
276 observer->OnRequestIdReleased(request_id);
278 const base::UnguessableToken& source_id = GetBindingIdentity();
279 for (auto& holder : source_observers_) {
280 if (holder->identity() == source_id)
281 holder->OnRequestIdReleased(request_id);
285 void AudioFocusManager::CreateActiveMediaController(
286 mojo::PendingReceiver<mojom::MediaController> receiver) {
287 DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
288 active_media_controller_.BindToInterface(std::move(receiver));
291 void AudioFocusManager::CreateMediaControllerForSession(
292 mojo::PendingReceiver<mojom::MediaController> receiver,
293 const base::UnguessableToken& receiver_id) {
294 for (auto& row : audio_focus_stack_) {
295 if (row->id() != receiver_id)
298 row->BindToMediaController(std::move(receiver));
303 void AudioFocusManager::SuspendAllSessions() {
304 DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
306 for (auto& row : audio_focus_stack_)
307 row->ipc()->Suspend(mojom::MediaSession::SuspendType::kUI);
310 void AudioFocusManager::BindToInterface(
311 mojo::PendingReceiver<mojom::AudioFocusManager> receiver) {
312 DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
313 receivers_.Add(this, std::move(receiver),
314 std::make_unique<ReceiverContext>());
317 void AudioFocusManager::BindToDebugInterface(
318 mojo::PendingReceiver<mojom::AudioFocusManagerDebug> receiver) {
319 DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
320 debug_receivers_.Add(this, std::move(receiver));
323 void AudioFocusManager::BindToControllerManagerInterface(
324 mojo::PendingReceiver<mojom::MediaControllerManager> receiver) {
325 DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
326 controller_receivers_.Add(this, std::move(receiver));
329 void AudioFocusManager::RequestAudioFocusInternal(
330 std::unique_ptr<AudioFocusRequest> row,
331 mojom::AudioFocusType type) {
332 const auto& identity = row->identity();
333 row->set_audio_focus_type(type);
334 audio_focus_stack_.push_back(std::move(row));
337 MaybeUpdateActiveSession();
339 // Notify observers that we were gained audio focus.
340 mojom::AudioFocusRequestStatePtr session_state =
341 audio_focus_stack_.back()->ToAudioFocusRequestState();
342 for (const auto& observer : observers_)
343 observer->OnFocusGained(session_state.Clone());
345 for (auto& holder : source_observers_) {
346 if (holder->identity() == identity)
347 holder->OnFocusGained(session_state.Clone());
351 void AudioFocusManager::EnforceAudioFocus() {
352 DCHECK_NE(mojom::EnforcementMode::kDefault, enforcement_mode_);
353 if (audio_focus_stack_.empty())
356 EnforcementState state;
358 for (auto& session : base::Reversed(audio_focus_stack_)) {
359 EnforceSingleSession(session.get(), state);
361 // Update the flags based on the audio focus type of this session. If the
362 // session is suspended then any transient audio focus type should not have
364 switch (session->audio_focus_type()) {
365 case mojom::AudioFocusType::kGain:
366 state.should_stop = true;
368 case mojom::AudioFocusType::kGainTransient:
369 if (!session->IsSuspended())
370 state.should_suspend = true;
372 case mojom::AudioFocusType::kGainTransientMayDuck:
373 if (!session->IsSuspended())
374 state.should_duck = true;
376 case mojom::AudioFocusType::kAmbient:
382 void AudioFocusManager::MaybeUpdateActiveSession() {
383 AudioFocusRequest* active = nullptr;
385 for (auto& row : base::Reversed(audio_focus_stack_)) {
386 if (!row->info()->is_controllable)
394 active_media_controller_.SetMediaSession(active);
396 active_media_controller_.ClearMediaSession();
400 AudioFocusManager::AudioFocusManager()
401 : enforcement_mode_(GetDefaultEnforcementMode()) {
402 DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
405 std::make_unique<MediaPowerDelegate>(weak_ptr_factory_.GetWeakPtr());
408 AudioFocusManager::~AudioFocusManager() = default;
410 std::unique_ptr<AudioFocusRequest> AudioFocusManager::RemoveFocusEntryIfPresent(
412 std::unique_ptr<AudioFocusRequest> row;
414 for (auto iter = audio_focus_stack_.begin(); iter != audio_focus_stack_.end();
416 if ((*iter)->id() == id) {
418 audio_focus_stack_.erase(iter);
426 bool AudioFocusManager::IsFocusEntryPresent(
427 const base::UnguessableToken& id) const {
428 for (auto& row : audio_focus_stack_) {
436 const std::string& AudioFocusManager::GetBindingSourceName() const {
437 DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
438 return receivers_.current_context()->source_name;
441 const base::UnguessableToken& AudioFocusManager::GetBindingIdentity() const {
442 DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
443 return receivers_.current_context()->identity;
446 bool AudioFocusManager::IsSessionOnTopOfAudioFocusStack(
448 mojom::AudioFocusType type) const {
449 return !audio_focus_stack_.empty() && audio_focus_stack_.back()->id() == id &&
450 audio_focus_stack_.back()->audio_focus_type() == type;
453 bool AudioFocusManager::ShouldSessionBeSuspended(
454 const AudioFocusRequest* session,
455 const EnforcementState& state) const {
456 bool should_suspend_any = state.should_stop || state.should_suspend;
458 switch (enforcement_mode_) {
459 case mojom::EnforcementMode::kSingleSession:
460 return should_suspend_any;
461 case mojom::EnforcementMode::kSingleGroup:
462 return should_suspend_any &&
463 session->group_id() != audio_focus_stack_.back()->group_id();
464 case mojom::EnforcementMode::kNone:
466 case mojom::EnforcementMode::kDefault:
472 bool AudioFocusManager::ShouldSessionBeDucked(
473 const AudioFocusRequest* session,
474 const EnforcementState& state) const {
475 switch (enforcement_mode_) {
476 case mojom::EnforcementMode::kSingleSession:
477 case mojom::EnforcementMode::kSingleGroup:
478 if (session->info()->force_duck)
479 return state.should_duck || ShouldSessionBeSuspended(session, state);
480 return state.should_duck;
481 case mojom::EnforcementMode::kNone:
483 case mojom::EnforcementMode::kDefault:
489 void AudioFocusManager::EnforceSingleSession(AudioFocusRequest* session,
490 const EnforcementState& state) {
491 if (ShouldSessionBeDucked(session, state)) {
492 session->ipc()->StartDucking();
494 session->ipc()->StopDucking();
497 // If the session wants to be ducked instead of suspended we should stop now.
498 if (session->info()->force_duck)
501 if (ShouldSessionBeSuspended(session, state)) {
502 session->Suspend(state);
504 session->ReleaseTransientHold();
508 void AudioFocusManager::CleanupSourceObservers() {
509 base::EraseIf(source_observers_,
510 [](const auto& holder) { return !holder->is_valid(); });
513 } // namespace media_session