blob: 20e1f24fd630064df627e6bc924a7e209811a750 [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 "base/functional/bind.h"
#include "base/run_loop.h"
#include "base/test/scoped_feature_list.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/ui_test_utils.h"
#include "content/public/browser/media_session.h"
#include "content/public/browser/media_session_service.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "media/base/media_switches.h"
#include "net/dns/mock_host_resolver.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "services/media_session/public/cpp/test/audio_focus_test_util.h"
namespace {
constexpr base::FilePath::CharType kTestPage[] =
FILE_PATH_LITERAL("media/bigbuck-player.html");
constexpr base::FilePath::CharType kTestPageStartWithNoPlayer[] =
FILE_PATH_LITERAL("media/start-with-no-player.html");
} // namespace
using media_session::mojom::MediaSessionInfo;
class AudioDuckerBrowserTest : public InProcessBrowserTest {
public:
AudioDuckerBrowserTest() {
feature_list_.InitAndEnableFeature(media::kAudioDucking);
}
AudioDuckerBrowserTest(const AudioDuckerBrowserTest&) = delete;
AudioDuckerBrowserTest& operator=(const AudioDuckerBrowserTest&) = delete;
~AudioDuckerBrowserTest() override = default;
void SetUpOnMainThread() override {
ResetAudioFocusObserver();
host_resolver()->AddRule("*", "127.0.0.1");
embedded_test_server()->ServeFilesFromSourceDirectory("chrome/test/data");
ASSERT_TRUE(embedded_test_server()->Start());
}
void AddVideo(content::WebContents& web_contents) {
web_contents.GetPrimaryMainFrame()
->ExecuteJavaScriptWithUserGestureForTests(
u"addVideo();", base::NullCallback(),
content::ISOLATED_WORLD_ID_GLOBAL);
}
void PlayVideoAndWaitForAudioFocus(content::WebContents& web_contents) {
ResetAudioFocusObserver();
web_contents.GetPrimaryMainFrame()
->ExecuteJavaScriptWithUserGestureForTests(
u"document.getElementsByTagName('video')[0].play()",
base::NullCallback(), content::ISOLATED_WORLD_ID_GLOBAL);
WaitForAudioFocusGained();
}
void ResetAudioFocusObserver() {
mojo::Remote<media_session::mojom::AudioFocusManager> audio_focus_remote;
content::GetMediaSessionService().BindAudioFocusManager(
audio_focus_remote.BindNewPipeAndPassReceiver());
audio_focus_observer_ =
std::make_unique<media_session::test::TestAudioFocusObserver>();
audio_focus_remote->AddObserver(
audio_focus_observer_->BindNewPipeAndPassRemote());
}
void WaitForAudioFocusGained() {
audio_focus_observer_->WaitForGainedEvent();
}
void FlushAudioFocusManager() {
mojo::Remote<media_session::mojom::AudioFocusManager> audio_focus_remote;
content::GetMediaSessionService().BindAudioFocusManager(
audio_focus_remote.BindNewPipeAndPassReceiver());
base::RunLoop flush_waiter;
audio_focus_remote->FlushForTesting(flush_waiter.QuitClosure());
flush_waiter.Run();
}
void ExpectMediaSessionState(content::MediaSession& media_session,
MediaSessionInfo::SessionState expected_state) {
base::RunLoop waiter;
media_session.GetMediaSessionInfo(base::BindOnce(
[](MediaSessionInfo::SessionState expected_state,
base::OnceClosure wait_closure,
media_session::mojom::MediaSessionInfoPtr info) {
EXPECT_EQ(info->state, expected_state);
std::move(wait_closure).Run();
},
expected_state, waiter.QuitClosure()));
waiter.Run();
}
private:
base::test::ScopedFeatureList feature_list_;
std::unique_ptr<media_session::test::TestAudioFocusObserver>
audio_focus_observer_;
};
IN_PROC_BROWSER_TEST_F(AudioDuckerBrowserTest,
DucksAudioInOtherTabs_MediaPlaying) {
GURL test_page_url = ui_test_utils::GetTestUrl(
base::FilePath(base::FilePath::kCurrentDirectory),
base::FilePath(kTestPage));
// Open a test page and start playing a video.
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), test_page_url));
content::WebContents* web_contents1 =
browser()->tab_strip_model()->GetActiveWebContents();
PlayVideoAndWaitForAudioFocus(*web_contents1);
// Open a second test page and also play a video.
ASSERT_TRUE(ui_test_utils::NavigateToURLWithDisposition(
browser(), test_page_url, WindowOpenDisposition::NEW_FOREGROUND_TAB,
ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP));
content::WebContents* web_contents2 =
browser()->tab_strip_model()->GetActiveWebContents();
PlayVideoAndWaitForAudioFocus(*web_contents2);
content::MediaSession* media_session1 =
content::MediaSession::GetIfExists(web_contents1);
content::MediaSession* media_session2 =
content::MediaSession::GetIfExists(web_contents2);
ASSERT_TRUE(media_session1);
ASSERT_TRUE(media_session2);
AudioDucker* audio_ducker =
AudioDucker::GetOrCreateForPage(web_contents2->GetPrimaryPage());
// Neither page should be ducked.
ExpectMediaSessionState(*media_session1,
MediaSessionInfo::SessionState::kActive);
ExpectMediaSessionState(*media_session2,
MediaSessionInfo::SessionState::kActive);
EXPECT_EQ(AudioDucker::AudioDuckingState::kNoDucking,
audio_ducker->GetAudioDuckingState());
// Tell the AudioDucker associated with the second page to duck other audio.
EXPECT_TRUE(audio_ducker->StartDuckingOtherAudio());
FlushAudioFocusManager();
// The first page should be ducked while the second remains unducked.
ExpectMediaSessionState(*media_session1,
MediaSessionInfo::SessionState::kDucking);
ExpectMediaSessionState(*media_session2,
MediaSessionInfo::SessionState::kActive);
EXPECT_EQ(AudioDucker::AudioDuckingState::kDucking,
audio_ducker->GetAudioDuckingState());
// Tell the AudioDucker to stop ducking.
EXPECT_TRUE(audio_ducker->StopDuckingOtherAudio());
FlushAudioFocusManager();
// Neither page should be ducked.
ExpectMediaSessionState(*media_session1,
MediaSessionInfo::SessionState::kActive);
ExpectMediaSessionState(*media_session2,
MediaSessionInfo::SessionState::kActive);
EXPECT_EQ(AudioDucker::AudioDuckingState::kNoDucking,
audio_ducker->GetAudioDuckingState());
}
IN_PROC_BROWSER_TEST_F(AudioDuckerBrowserTest,
DucksAudioInOtherTabs_NoMediaPlaying) {
GURL test_page_url = ui_test_utils::GetTestUrl(
base::FilePath(base::FilePath::kCurrentDirectory),
base::FilePath(kTestPage));
// Open a test page and start playing a video.
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), test_page_url));
content::WebContents* web_contents1 =
browser()->tab_strip_model()->GetActiveWebContents();
PlayVideoAndWaitForAudioFocus(*web_contents1);
// Open a second test page that has no video.
GURL test_page_no_player_url = ui_test_utils::GetTestUrl(
base::FilePath(base::FilePath::kCurrentDirectory),
base::FilePath(kTestPageStartWithNoPlayer));
ASSERT_TRUE(ui_test_utils::NavigateToURLWithDisposition(
browser(), test_page_no_player_url,
WindowOpenDisposition::NEW_FOREGROUND_TAB,
ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP));
content::WebContents* web_contents2 =
browser()->tab_strip_model()->GetActiveWebContents();
content::MediaSession* media_session1 =
content::MediaSession::GetIfExists(web_contents1);
ASSERT_TRUE(media_session1);
ASSERT_EQ(nullptr, content::MediaSession::GetIfExists(web_contents2));
AudioDucker* audio_ducker =
AudioDucker::GetOrCreateForPage(web_contents2->GetPrimaryPage());
// The first page should not be ducked.
ExpectMediaSessionState(*media_session1,
MediaSessionInfo::SessionState::kActive);
EXPECT_EQ(AudioDucker::AudioDuckingState::kNoDucking,
audio_ducker->GetAudioDuckingState());
// Tell the AudioDucker associated with the second page to duck other audio.
EXPECT_TRUE(audio_ducker->StartDuckingOtherAudio());
FlushAudioFocusManager();
// The first page should be ducked.
ExpectMediaSessionState(*media_session1,
MediaSessionInfo::SessionState::kDucking);
EXPECT_EQ(AudioDucker::AudioDuckingState::kDucking,
audio_ducker->GetAudioDuckingState());
// Add a video into the second page and play it.
AddVideo(*web_contents2);
PlayVideoAndWaitForAudioFocus(*web_contents2);
content::MediaSession* media_session2 =
content::MediaSession::GetIfExists(web_contents2);
ASSERT_TRUE(media_session2);
// The first page should still be ducked but the second page should not be
// ducked.
ExpectMediaSessionState(*media_session1,
MediaSessionInfo::SessionState::kDucking);
ExpectMediaSessionState(*media_session2,
MediaSessionInfo::SessionState::kActive);
EXPECT_EQ(AudioDucker::AudioDuckingState::kDucking,
audio_ducker->GetAudioDuckingState());
// Tell the AudioDucker to stop ducking.
EXPECT_TRUE(audio_ducker->StopDuckingOtherAudio());
FlushAudioFocusManager();
// The first page should no longer be ducked.
ExpectMediaSessionState(*media_session1,
MediaSessionInfo::SessionState::kActive);
ExpectMediaSessionState(*media_session2,
MediaSessionInfo::SessionState::kActive);
EXPECT_EQ(AudioDucker::AudioDuckingState::kNoDucking,
audio_ducker->GetAudioDuckingState());
}