blob: 5878ce56327d13239844d6ad25a6c390d63bd5fc [file] [log] [blame]
// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/media/audio_ducker.h"
#include "build/build_config.h"
#include "content/public/browser/media_session.h"
#include "content/public/browser/media_session_service.h"
#include "content/public/browser/web_contents.h"
#include "media/base/media_switches.h"
#if BUILDFLAG(IS_WIN)
#include "content/public/browser/audio_service_info.h"
#include "media/audio/win/audio_ducker_win.h"
#endif // BUILDFLAG(IS_WIN)
AudioDucker::AudioDucker(content::Page& page)
: content::PageUserData<AudioDucker>(page),
content::WebContentsObserver(GetWebContents()) {
#if BUILDFLAG(IS_WIN)
if (base::FeatureList::IsEnabled(media::kAudioDuckingWin)) {
// `base::Unretained()` is safe here since we own `windows_ducker_`.
windows_ducker_ =
std::make_unique<media::AudioDuckerWin>(base::BindRepeating(
&AudioDucker::ShouldDuckProcess, base::Unretained(this)));
}
#endif // BUILDFLAG(IS_WIN)
}
AudioDucker::~AudioDucker() {
StopDuckingOtherAudio();
}
bool AudioDucker::StartDuckingOtherAudio() {
if (!base::FeatureList::IsEnabled(media::kAudioDucking)) {
return false;
}
if (ducking_state_ == AudioDuckingState::kDucking) {
return true;
}
if (!BindToAudioFocusManagerIfNecessary()) {
return false;
}
StartDuckingImpl();
ducking_state_ = AudioDuckingState::kDucking;
return true;
}
bool AudioDucker::StopDuckingOtherAudio() {
if (!base::FeatureList::IsEnabled(media::kAudioDucking)) {
return false;
}
if (ducking_state_ == AudioDuckingState::kNoDucking) {
return true;
}
if (!BindToAudioFocusManagerIfNecessary()) {
return false;
}
audio_focus_remote_->StopDuckingAllAudio();
#if BUILDFLAG(IS_WIN)
if (windows_ducker_) {
windows_ducker_->StopDuckingOtherWindowsApplications();
}
#endif // BUILDFLAG(IS_WIN)
ducking_state_ = AudioDuckingState::kNoDucking;
return true;
}
void AudioDucker::MediaSessionCreated(content::MediaSession* session) {
// When a MediaSession is created and we're already ducking, we need to tell
// the AudioFocusManager to start ducking again while exempting the new
// request ID. This will supercede the previous request and replace it with a
// request that has an exempted MediaSession.
if (ducking_state_ == AudioDuckingState::kDucking &&
BindToAudioFocusManagerIfNecessary()) {
StartDuckingImpl();
}
}
void AudioDucker::StartDuckingImpl() {
// Null base::UnguessableTokens cannot be passed via mojo, so convert to an
// empty optional if the request ID is null.
const base::UnguessableToken& request_id =
content::MediaSession::GetRequestIdFromWebContents(GetWebContents());
std::optional<base::UnguessableToken> optional_request_id;
if (!request_id.is_empty()) {
optional_request_id = request_id;
}
audio_focus_remote_->StartDuckingAllAudio(optional_request_id);
#if BUILDFLAG(IS_WIN)
if (windows_ducker_) {
windows_ducker_->StartDuckingOtherWindowsApplications();
}
#endif // BUILDFLAG(IS_WIN)
}
content::WebContents* AudioDucker::GetWebContents() const {
return content::WebContents::FromRenderFrameHost(&page().GetMainDocument());
}
bool AudioDucker::BindToAudioFocusManagerIfNecessary() {
if (audio_focus_remote_.is_bound()) {
return true;
}
content::GetMediaSessionService().BindAudioFocusManager(
audio_focus_remote_.BindNewPipeAndPassReceiver());
audio_focus_remote_.reset_on_disconnect();
return audio_focus_remote_.is_bound();
}
#if BUILDFLAG(IS_WIN)
bool AudioDucker::ShouldDuckProcess(base::ProcessId process_id) const {
// If this audio session is associated with Chrome (either the browser
// process or the audio process), then we don't want to duck it.
if (process_id == base::GetCurrentProcId()) {
return false;
}
base::ProcessId audio_service_process_id =
content::GetProcessIdForAudioService();
if (audio_service_process_id != base::kNullProcessId &&
process_id == audio_service_process_id) {
return false;
}
return true;
}
#endif // BUILDFLAG(IS_WIN)
PAGE_USER_DATA_KEY_IMPL(AudioDucker);