Skip to content

Instantly share code, notes, and snippets.

@SteffenDE
Last active March 27, 2026 13:13
Show Gist options
  • Select an option

  • Save SteffenDE/e804240076aa6a173abe8a774250a5ce to your computer and use it in GitHub Desktop.

Select an option

Save SteffenDE/e804240076aa6a173abe8a774250a5ce to your computer and use it in GitHub Desktop.
Chrome PulseAudio web app + title integration, use default browser in web apps
diff --git a/chrome/browser/chrome_content_browser_client.cc b/chrome/browser/chrome_content_browser_client.cc
index 77c8dba45e..b57b5b21e6 100644
--- a/chrome/browser/chrome_content_browser_client.cc
+++ b/chrome/browser/chrome_content_browser_client.cc
@@ -554,6 +554,7 @@
#include "chrome/browser/web_applications/web_app_helpers.h"
#include "chrome/browser/web_applications/web_app_provider.h"
#include "chrome/browser/web_applications/web_app_registrar.h"
+#include "chrome/browser/web_applications/web_app_tab_helper.h"
#include "chrome/browser/web_applications/web_app_utils.h"
#include "chrome/browser/webauthn/authenticator_request_scheduler.h"
#include "chrome/browser/webauthn/chrome_authenticator_request_delegate.h"
@@ -595,6 +596,10 @@
#include "components/crash/core/app/crashpad.h"
#endif
+#if BUILDFLAG(IS_LINUX)
+#include "chrome/browser/web_applications/os_integration/web_app_shortcut_linux.h"
+#endif
+
#if BUILDFLAG(IS_ANDROID)
#include "base/android/device_info.h"
#include "components/crash/content/browser/crash_handler_host_linux.h"
@@ -7029,6 +7034,36 @@ ChromeContentBrowserClient::GetAutoPipInfo(
#endif // BUILDFLAG(IS_ANDROID)
}
+content::ContentBrowserClient::WebAppAudioStreamInfo
+ChromeContentBrowserClient::GetWebAppInfoForAudioStream(
+ content::WebContents& web_contents) const {
+#if BUILDFLAG(IS_ANDROID)
+ return {};
+#else
+ const webapps::AppId* app_id =
+ web_app::WebAppTabHelper::GetAppId(&web_contents);
+ if (!app_id)
+ return {};
+
+ Profile* profile =
+ Profile::FromBrowserContext(web_contents.GetBrowserContext());
+ web_app::WebAppProvider* provider =
+ web_app::WebAppProvider::GetForWebApps(profile);
+ if (!provider)
+ return {};
+
+ content::ContentBrowserClient::WebAppAudioStreamInfo info;
+ info.app_name = provider->registrar_unsafe().GetAppShortName(*app_id);
+#if BUILDFLAG(IS_LINUX)
+ info.xdg_icon_name =
+ web_app::GetAppDesktopShortcutFilename(profile->GetPath(), *app_id)
+ .RemoveFinalExtension()
+ .value();
+#endif
+ return info;
+#endif // BUILDFLAG(IS_ANDROID)
+}
+
void ChromeContentBrowserClient::RegisterRendererPreferenceWatcher(
content::BrowserContext* browser_context,
mojo::PendingRemote<blink::mojom::RendererPreferenceWatcher> watcher) {
diff --git a/chrome/browser/chrome_content_browser_client.h b/chrome/browser/chrome_content_browser_client.h
index 6aac7e8ab4..d14ea29e15 100644
--- a/chrome/browser/chrome_content_browser_client.h
+++ b/chrome/browser/chrome_content_browser_client.h
@@ -842,6 +842,8 @@ class ChromeContentBrowserClient : public content::ContentBrowserClient {
content::WebContents* web_contents) override;
media::PictureInPictureEventsInfo::AutoPipInfo GetAutoPipInfo(
const content::WebContents& web_contents) const override;
+ WebAppAudioStreamInfo GetWebAppInfoForAudioStream(
+ content::WebContents& web_contents) const override;
void RegisterRendererPreferenceWatcher(
content::BrowserContext* browser_context,
mojo::PendingRemote<blink::mojom::RendererPreferenceWatcher> watcher)
diff --git a/content/browser/media/audio_output_stream_broker.cc b/content/browser/media/audio_output_stream_broker.cc
index 847d33aac9..5131161674 100644
--- a/content/browser/media/audio_output_stream_broker.cc
+++ b/content/browser/media/audio_output_stream_broker.cc
@@ -18,6 +18,7 @@
#include "content/public/browser/media_observer.h"
#include "content/public/common/content_client.h"
#include "media/audio/audio_device_description.h"
+#include "media/mojo/mojom/audio_stream_factory.mojom.h"
#include "media/audio/audio_logging.h"
#include "media/mojo/mojom/audio_data_pipe.mojom.h"
#include "media/mojo/mojom/audio_output_stream.mojom.h"
@@ -138,8 +139,28 @@ void AudioOutputStreamBroker::CreateStream(
TRACE_EVENT_BEGIN("audio", "CreateStream", perfetto::Track::FromPointer(this),
"device id", output_device_id_);
+ factory_ = factory;
stream_creation_start_time_ = base::TimeTicks::Now();
+ // Resolve stream metadata (title, web app identity) before creating the
+ // stream so that PulseAudio sees the correct values at stream creation time.
+ ResolveAudioStreamInfo(
+ render_process_id(), render_frame_id(),
+ base::BindOnce(&AudioOutputStreamBroker::OnAudioStreamInfoResolved,
+ weak_ptr_factory_.GetWeakPtr()));
+}
+
+void AudioOutputStreamBroker::OnAudioStreamInfoResolved(
+ AudioStreamInfo info) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(owning_sequence_);
+ if (!factory_)
+ return;
+
+ auto metadata = media::mojom::AudioStreamMetadata::New();
+ metadata->title = std::move(info.title);
+ metadata->app_name = std::move(info.app_name);
+ metadata->xdg_icon_name = std::move(info.xdg_icon_name);
+
// Set up observer ptr. Unretained is safe because |this| owns
// |observer_receiver_|.
mojo::PendingAssociatedRemote<media::mojom::AudioOutputStreamObserver>
@@ -151,37 +172,30 @@ void AudioOutputStreamBroker::CreateStream(
mojo::PendingRemote<media::mojom::AudioOutputStream> stream;
auto stream_receiver = stream.InitWithNewPipeAndPassReceiver();
- // Note that the component id for AudioLog is used to differentiate between
- // several users of the same audio log. Since this audio log is for a single
- // stream, the component id used doesn't matter.
constexpr int log_component_id = 0;
if (MediaStreamManager::GetPreferredOutputManagerInstance() &&
media::AudioDeviceDescription::IsDefaultDevice(output_device_id_)) {
- // Register the device switcher with PreferredAudioOutputDeviceManager.
- // `output_device_id_` will be updated by the `SwitchAudioOutputDeviceId`,
- // which is called by the PreferredAudioOutputDeviceManager during
- // `AddSwitcher()`.
MediaStreamManager::GetPreferredOutputManagerInstance()->AddSwitcher(
main_frame_token_, this);
- factory->CreateSwitchableOutputStream(
+ factory_->CreateSwitchableOutputStream(
std::move(stream_receiver),
device_switch_interface_.BindNewPipeAndPassReceiver(),
std::move(observer),
MediaInternals::GetInstance()->CreateMojoAudioLog(
media::AudioLogFactory::AudioComponent::kAudioOuputController,
log_component_id, render_process_id(), render_frame_id()),
- output_device_id_, params_, group_id_,
+ output_device_id_, params_, group_id_, std::move(metadata),
base::BindOnce(&AudioOutputStreamBroker::StreamCreated,
weak_ptr_factory_.GetWeakPtr(), std::move(stream)));
} else {
- factory->CreateOutputStream(
+ factory_->CreateOutputStream(
std::move(stream_receiver), std::move(observer),
MediaInternals::GetInstance()->CreateMojoAudioLog(
media::AudioLogFactory::AudioComponent::kAudioOuputController,
log_component_id, render_process_id(), render_frame_id()),
- output_device_id_, params_, group_id_,
+ output_device_id_, params_, group_id_, std::move(metadata),
base::BindOnce(&AudioOutputStreamBroker::StreamCreated,
weak_ptr_factory_.GetWeakPtr(), std::move(stream)));
}
@@ -191,13 +205,11 @@ void AudioOutputStreamBroker::StreamCreated(
mojo::PendingRemote<media::mojom::AudioOutputStream> stream,
media::mojom::ReadWriteAudioDataPipePtr data_pipe) {
DCHECK_CALLED_ON_VALID_SEQUENCE(owning_sequence_);
- // End "CreateStream" trace event.
TRACE_EVENT_END("audio", perfetto::Track::FromPointer(this), "success",
!!data_pipe);
stream_creation_start_time_ = base::TimeTicks();
if (!data_pipe) {
- // Stream creation failed. Signal error.
client_.ResetWithReason(
static_cast<uint32_t>(DisconnectReason::kPlatformError), std::string());
Cleanup(DisconnectReason::kStreamCreationFailed);
diff --git a/content/browser/media/audio_output_stream_broker.h b/content/browser/media/audio_output_stream_broker.h
index d9d9d12d15..37bf924882 100644
--- a/content/browser/media/audio_output_stream_broker.h
+++ b/content/browser/media/audio_output_stream_broker.h
@@ -14,6 +14,7 @@
#include "base/unguessable_token.h"
#include "content/browser/renderer_host/media/audio_output_stream_observer_impl.h"
#include "content/browser/renderer_host/media/preferred_audio_output_device_manager.h"
+#include "content/browser/media/audio_stream_broker_helper.h"
#include "content/common/content_export.h"
#include "content/public/browser/audio_stream_broker.h"
#include "media/base/audio_parameters.h"
@@ -72,6 +73,7 @@ class CONTENT_EXPORT AudioOutputStreamBroker final
void StreamCreated(
mojo::PendingRemote<media::mojom::AudioOutputStream> stream,
media::mojom::ReadWriteAudioDataPipePtr data_pipe);
+ void OnAudioStreamInfoResolved(AudioStreamInfo info);
void ObserverBindingLost(uint32_t reason, const std::string& description);
void Cleanup(DisconnectReason reason);
bool AwaitingCreated() const;
@@ -95,6 +97,10 @@ class CONTENT_EXPORT AudioOutputStreamBroker final
observer_receiver_;
mojo::Remote<media::mojom::DeviceSwitchInterface> device_switch_interface_;
+ // Stored from CreateStream() so we can create the stream after resolving
+ // metadata asynchronously.
+ raw_ptr<media::mojom::AudioStreamFactory> factory_ = nullptr;
+
DisconnectReason disconnect_reason_ = DisconnectReason::kDocumentDestroyed;
base::WeakPtrFactory<AudioOutputStreamBroker> weak_ptr_factory_{this};
diff --git a/content/browser/media/audio_output_stream_broker_unittest.cc b/content/browser/media/audio_output_stream_broker_unittest.cc
index b11eaf31d1..d66ddc25c5 100644
--- a/content/browser/media/audio_output_stream_broker_unittest.cc
+++ b/content/browser/media/audio_output_stream_broker_unittest.cc
@@ -160,6 +160,7 @@ class MockStreamFactory final : public audio::FakeStreamFactory {
const std::string& output_device_id,
const media::AudioParameters& params,
const base::UnguessableToken& group_id,
+ media::mojom::AudioStreamMetadataPtr metadata,
CreateOutputStreamCallback created_callback) final {
// No way to cleanly exit the test here in case of failure, so use CHECK.
CHECK(stream_request_data_);
@@ -183,10 +184,11 @@ class MockStreamFactory final : public audio::FakeStreamFactory {
const std::string& output_device_id,
const media::AudioParameters& params,
const base::UnguessableToken& group_id,
+ media::mojom::AudioStreamMetadataPtr metadata,
CreateOutputStreamCallback created_callback) final {
CreateOutputStream(std::move(stream_receiver), std::move(observer),
std::move(log), output_device_id, params, group_id,
- std::move(created_callback));
+ std::move(metadata), std::move(created_callback));
}
raw_ptr<StreamRequestData> stream_request_data_;
diff --git a/content/browser/media/audio_stream_broker_helper.cc b/content/browser/media/audio_stream_broker_helper.cc
index 0c21518dbc..f3e885a027 100644
--- a/content/browser/media/audio_stream_broker_helper.cc
+++ b/content/browser/media/audio_stream_broker_helper.cc
@@ -4,10 +4,16 @@
#include "content/browser/media/audio_stream_broker_helper.h"
+#include <string>
+
#include "base/functional/bind.h"
#include "base/location.h"
+#include "base/strings/utf_string_conversions.h"
#include "content/browser/renderer_host/render_frame_host_impl.h"
#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/content_browser_client.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/common/content_client.h"
namespace content {
@@ -50,4 +56,30 @@ void NotifyFrameHostOfAudioStreamStopped(int render_process_id,
base::BindOnce(impl, render_process_id, render_frame_id, is_capturing));
}
+void ResolveAudioStreamInfo(
+ int render_process_id,
+ int render_frame_id,
+ base::OnceCallback<void(AudioStreamInfo)> callback) {
+ auto resolve_on_ui = [](int render_process_id, int render_frame_id,
+ base::OnceCallback<void(AudioStreamInfo)> callback) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ AudioStreamInfo info;
+ if (auto* host =
+ RenderFrameHostImpl::FromID(render_process_id, render_frame_id)) {
+ if (auto* web_contents = WebContents::FromRenderFrameHost(host)) {
+ info.title = base::UTF16ToUTF8(web_contents->GetTitle());
+ auto app_info = GetContentClient()->browser()
+ ->GetWebAppInfoForAudioStream(*web_contents);
+ info.app_name = std::move(app_info.app_name);
+ info.xdg_icon_name = std::move(app_info.xdg_icon_name);
+ }
+ }
+ GetIOThreadTaskRunner({})->PostTask(
+ FROM_HERE, base::BindOnce(std::move(callback), std::move(info)));
+ };
+ GetUIThreadTaskRunner({})->PostTask(
+ FROM_HERE, base::BindOnce(resolve_on_ui, render_process_id,
+ render_frame_id, std::move(callback)));
+}
+
} // namespace content
diff --git a/content/browser/media/audio_stream_broker_helper.h b/content/browser/media/audio_stream_broker_helper.h
index ca6aa1c8ac..00069f16a6 100644
--- a/content/browser/media/audio_stream_broker_helper.h
+++ b/content/browser/media/audio_stream_broker_helper.h
@@ -5,6 +5,10 @@
#ifndef CONTENT_BROWSER_MEDIA_AUDIO_STREAM_BROKER_HELPER_H_
#define CONTENT_BROWSER_MEDIA_AUDIO_STREAM_BROKER_HELPER_H_
+#include <string>
+
+#include "base/functional/callback.h"
+
namespace content {
// Thread-safe utility that notifies the RenderFrameHost identified by
@@ -20,6 +24,21 @@ void NotifyFrameHostOfAudioStreamStopped(int render_process_id,
int render_frame_id,
bool is_capturing);
+struct AudioStreamInfo {
+ std::string title;
+ std::string app_name;
+ std::string xdg_icon_name;
+};
+
+// Resolves audio stream metadata for the WebContents associated with the given
+// render frame. Posts to the UI thread to look up title (via GetTitle()) and
+// web-app identity (via ContentBrowserClient::GetWebAppInfoForAudioStream()),
+// then runs |callback| on the IO thread.
+void ResolveAudioStreamInfo(
+ int render_process_id,
+ int render_frame_id,
+ base::OnceCallback<void(AudioStreamInfo)> callback);
+
} // namespace content
#endif // CONTENT_BROWSER_MEDIA_AUDIO_STREAM_BROKER_HELPER_H_
diff --git a/content/browser/media/forwarding_audio_stream_factory.cc b/content/browser/media/forwarding_audio_stream_factory.cc
index c3fb32fe98..e96706389d 100644
--- a/content/browser/media/forwarding_audio_stream_factory.cc
+++ b/content/browser/media/forwarding_audio_stream_factory.cc
@@ -7,6 +7,7 @@
#include <utility>
#include "base/check_op.h"
+#include "base/strings/utf_string_conversions.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/location.h"
@@ -174,6 +175,14 @@ void ForwardingAudioStreamFactory::Core::SetMuted(bool muted) {
muter_->Connect(remote_factory_.get());
}
+void ForwardingAudioStreamFactory::Core::SetStreamLabel(
+ const std::string& label) {
+ DCHECK_CURRENTLY_ON(BrowserThread::IO);
+ if (outputs_.empty())
+ return;
+ GetFactory()->SetStreamLabelForGroup(group_id_, label);
+}
+
void ForwardingAudioStreamFactory::Core::AddLoopbackSink(
AudioStreamBroker::LoopbackSink* sink) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
@@ -295,6 +304,15 @@ void ForwardingAudioStreamFactory::RenderFrameDeleted(
render_frame_host->GetRoutingID()));
}
+void ForwardingAudioStreamFactory::TitleWasSet(NavigationEntry* entry) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ std::string title = base::UTF16ToUTF8(web_contents()->GetTitle());
+ GetIOThreadTaskRunner({})->PostTask(
+ FROM_HERE,
+ base::BindOnce(&Core::SetStreamLabel, base::Unretained(core_.get()),
+ std::move(title)));
+}
+
void ForwardingAudioStreamFactory::OverrideAudioStreamFactoryBinderForTesting(
AudioStreamFactoryBinder binder) {
GetAudioStreamFactoryBinderOverride() = std::move(binder);
diff --git a/content/browser/media/forwarding_audio_stream_factory.h b/content/browser/media/forwarding_audio_stream_factory.h
index 0a12aaa7ee..193ec2985e 100644
--- a/content/browser/media/forwarding_audio_stream_factory.h
+++ b/content/browser/media/forwarding_audio_stream_factory.h
@@ -108,6 +108,10 @@ class CONTENT_EXPORT ForwardingAudioStreamFactory final
// factory.
void SetMuted(bool muted);
+ // Updates the stream label for all output streams in this group.
+ void SetStreamLabel(const std::string& label);
+
+
// AudioStreamLoopback::Source implementation
void AddLoopbackSink(AudioStreamBroker::LoopbackSink* sink) final;
void RemoveLoopbackSink(AudioStreamBroker::LoopbackSink* sink) final;
@@ -206,6 +210,7 @@ class CONTENT_EXPORT ForwardingAudioStreamFactory final
// WebContentsObserver implementation. We observe these events so that we can
// clean up streams belonging to a frame when that frame is destroyed.
void RenderFrameDeleted(RenderFrameHost* render_frame_host) final;
+ void TitleWasSet(NavigationEntry* entry) final;
Core* core() { return core_.get(); }
diff --git a/content/browser/renderer_host/media/render_frame_audio_output_stream_factory_unittest.cc b/content/browser/renderer_host/media/render_frame_audio_output_stream_factory_unittest.cc
index ae1eb8a444..fd492ea047 100644
--- a/content/browser/renderer_host/media/render_frame_audio_output_stream_factory_unittest.cc
+++ b/content/browser/renderer_host/media/render_frame_audio_output_stream_factory_unittest.cc
@@ -98,6 +98,7 @@ class RenderFrameAudioOutputStreamFactoryTest
const std::string& output_device_id,
const media::AudioParameters& params,
const base::UnguessableToken& group_id,
+ media::mojom::AudioStreamMetadataPtr metadata,
CreateOutputStreamCallback created_callback) override {
last_created_callback_ = std::move(created_callback);
}
@@ -112,6 +113,7 @@ class RenderFrameAudioOutputStreamFactoryTest
const std::string& device_id,
const media::AudioParameters& params,
const base::UnguessableToken& group_id,
+ media::mojom::AudioStreamMetadataPtr metadata,
CreateOutputStreamCallback created_callback) override {
last_created_callback_ = std::move(created_callback);
}
diff --git a/content/public/browser/content_browser_client.cc b/content/public/browser/content_browser_client.cc
index 368d4b51b5..840a4d0598 100644
--- a/content/public/browser/content_browser_client.cc
+++ b/content/public/browser/content_browser_client.cc
@@ -1390,6 +1390,12 @@ ContentBrowserClient::GetAutoPipInfo(const WebContents& web_contents) const {
return media::PictureInPictureEventsInfo::AutoPipInfo();
}
+ContentBrowserClient::WebAppAudioStreamInfo
+ContentBrowserClient::GetWebAppInfoForAudioStream(
+ WebContents& web_contents) const {
+ return {};
+}
+
void ContentBrowserClient::RegisterRendererPreferenceWatcher(
BrowserContext* browser_context,
mojo::PendingRemote<blink::mojom::RendererPreferenceWatcher> watcher) {
diff --git a/content/public/browser/content_browser_client.h b/content/public/browser/content_browser_client.h
index aff5ce9518..c7cfdba248 100644
--- a/content/public/browser/content_browser_client.h
+++ b/content/public/browser/content_browser_client.h
@@ -2567,6 +2567,16 @@ class CONTENT_EXPORT ContentBrowserClient {
virtual media::PictureInPictureEventsInfo::AutoPipInfo GetAutoPipInfo(
const WebContents& web_contents) const;
+ struct WebAppAudioStreamInfo {
+ std::string app_name;
+ std::string xdg_icon_name;
+ };
+
+ // Called on the UI thread to retrieve web-app identity for audio stream
+ // labelling (e.g. PulseAudio PA_PROP_APPLICATION_NAME / ICON_NAME).
+ virtual WebAppAudioStreamInfo GetWebAppInfoForAudioStream(
+ WebContents& web_contents) const;
+
// Registers the watcher to observe updates in RendererPreferences.
virtual void RegisterRendererPreferenceWatcher(
BrowserContext* browser_context,
diff --git a/media/audio/audio_io.h b/media/audio/audio_io.h
index b9708113eb..fd800e579a 100644
--- a/media/audio/audio_io.h
+++ b/media/audio/audio_io.h
@@ -7,6 +7,8 @@
#include <stdint.h>
+#include <string>
+
#include "base/time/time.h"
#include "media/base/audio_glitch_info.h"
#include "media/base/media_export.h"
@@ -134,6 +136,16 @@ class MEDIA_EXPORT AudioOutputStream {
// playing. (i.e. called after Stop or Open)
virtual void Flush() = 0;
+ // Sets a human-readable label for this stream (e.g. tab title).
+ // Used on platforms that support per-stream naming (e.g. PulseAudio).
+ // Default implementation is a no-op.
+ virtual void SetStreamLabel(const std::string& label) {}
+
+ // Sets the application identity for this stream.
+ // |xdg_icon_name| is the freedesktop icon name (Linux only).
+ virtual void SetStreamAppIdentity(const std::string& app_name,
+ const std::string& xdg_icon_name) {}
+
// Constrains a timedelta representing a delay to between 0 and 10 seconds.
// This is used by OS implementations to prevent miscalculated delay values
// from creating large amounts of noise in the delay stats.
diff --git a/media/audio/audio_manager.h b/media/audio/audio_manager.h
index d157982b7e..18640ac0d9 100644
--- a/media/audio/audio_manager.h
+++ b/media/audio/audio_manager.h
@@ -16,6 +16,7 @@
#include "build/build_config.h"
#include "media/audio/audio_device_description.h"
#include "media/audio/audio_logging.h"
+#include "media/audio/audio_stream_metadata.h"
#include "media/audio/audio_thread.h"
#include "media/base/audio_parameters.h"
@@ -120,7 +121,8 @@ class MEDIA_EXPORT AudioManager {
// sound is actually playing.
virtual AudioOutputStream* MakeAudioOutputStreamProxy(
const AudioParameters& params,
- const std::string& device_id) = 0;
+ const std::string& device_id,
+ const AudioStreamMetadata& metadata = AudioStreamMetadata()) = 0;
// Factory to create audio recording streams.
// |channels| can be 1 or 2.
diff --git a/media/audio/audio_manager_base.cc b/media/audio/audio_manager_base.cc
index 9525911791..a24b7696f9 100644
--- a/media/audio/audio_manager_base.cc
+++ b/media/audio/audio_manager_base.cc
@@ -105,10 +105,12 @@ void SendLogMessage(const AudioManagerBase::LogCallback& callback,
struct AudioManagerBase::DispatcherParams {
DispatcherParams(const AudioParameters& input,
const AudioParameters& output,
- const std::string& output_device_id)
+ const std::string& output_device_id,
+ const AudioStreamMetadata& metadata)
: input_params(input),
output_params(output),
- output_device_id(output_device_id) {}
+ output_device_id(output_device_id),
+ metadata(metadata) {}
DispatcherParams(const DispatcherParams&) = delete;
DispatcherParams& operator=(const DispatcherParams&) = delete;
@@ -118,6 +120,7 @@ struct AudioManagerBase::DispatcherParams {
const AudioParameters input_params;
const AudioParameters output_params;
const std::string output_device_id;
+ const AudioStreamMetadata metadata;
std::unique_ptr<AudioOutputDispatcher> dispatcher;
};
@@ -388,7 +391,8 @@ AudioInputStream* AudioManagerBase::MakeAudioInputStream(
AudioOutputStream* AudioManagerBase::MakeAudioOutputStreamProxy(
const AudioParameters& params,
- const std::string& device_id) {
+ const std::string& device_id,
+ const AudioStreamMetadata& metadata) {
CHECK(GetTaskRunner()->BelongsToCurrentThread());
DCHECK(params.IsValid());
std::optional<StreamFormat> uma_stream_format;
@@ -480,7 +484,7 @@ AudioOutputStream* AudioManagerBase::MakeAudioOutputStreamProxy(
}
auto dispatcher_params = std::make_unique<DispatcherParams>(
- params, output_params, output_device_id);
+ params, output_params, output_device_id, metadata);
// Do not reuse the output dispatcher if audio offload is requested.
// Their underlying audio client is configured differently to make
@@ -490,15 +494,14 @@ AudioOutputStream* AudioManagerBase::MakeAudioOutputStreamProxy(
auto it = std::ranges::find_if(
output_dispatchers_,
[&](const std::unique_ptr<DispatcherParams>& dispatcher) {
- // We will reuse the existing dispatcher when:
- // 1) Unified IO is not used, input_params and output_params of the
- // existing dispatcher are the same as the requested dispatcher.
- // 2) Unified IO is used, input_params and output_params of the
- // existing
- // dispatcher are the same as the request dispatcher.
+ // Reuse the existing dispatcher when input_params, output_params,
+ // device ID, and app identity all match. App identity is included
+ // so that PWA streams get their own dispatcher with correctly
+ // tagged physical streams, while regular tabs share one.
return params.Equals(dispatcher->input_params) &&
output_params.Equals(dispatcher->output_params) &&
- output_device_id == dispatcher->output_device_id;
+ output_device_id == dispatcher->output_device_id &&
+ metadata.app_name == dispatcher->metadata.app_name;
});
if (it != output_dispatchers_.end()) {
return (*it)->dispatcher->CreateStreamProxy();
@@ -518,10 +521,11 @@ AudioOutputStream* AudioManagerBase::MakeAudioOutputStreamProxy(
&AudioDebugRecordingManager::RegisterDebugRecordingSource,
base::Unretained(debug_recording_manager_.get()),
AudioDebugRecordingStreamType::kOutput)
- : base::BindRepeating(&GetNullptrAudioDebugRecorder));
+ : base::BindRepeating(&GetNullptrAudioDebugRecorder),
+ metadata);
} else {
dispatcher = std::make_unique<AudioOutputDispatcherImpl>(
- this, output_params, output_device_id, kCloseDelay);
+ this, output_params, output_device_id, kCloseDelay, metadata);
}
dispatcher_params->dispatcher = std::move(dispatcher);
diff --git a/media/audio/audio_manager_base.h b/media/audio/audio_manager_base.h
index 16c920eb24..3ba3179dab 100644
--- a/media/audio/audio_manager_base.h
+++ b/media/audio/audio_manager_base.h
@@ -47,7 +47,8 @@ class MEDIA_EXPORT AudioManagerBase : public AudioManager {
const LogCallback& log_callback) override;
AudioOutputStream* MakeAudioOutputStreamProxy(
const AudioParameters& params,
- const std::string& device_id) override;
+ const std::string& device_id,
+ const AudioStreamMetadata& metadata = AudioStreamMetadata()) override;
// Listeners will be notified on the GetTaskRunner() task runner.
void AddOutputDeviceChangeListener(AudioDeviceListener* listener) override;
diff --git a/media/audio/audio_output_dispatcher.h b/media/audio/audio_output_dispatcher.h
index 50a8b0b916..14a8fef626 100644
--- a/media/audio/audio_output_dispatcher.h
+++ b/media/audio/audio_output_dispatcher.h
@@ -18,6 +18,8 @@
#ifndef MEDIA_AUDIO_AUDIO_OUTPUT_DISPATCHER_H_
#define MEDIA_AUDIO_AUDIO_OUTPUT_DISPATCHER_H_
+#include <string>
+
#include "base/memory/raw_ptr.h"
#include "media/audio/audio_io.h"
@@ -66,6 +68,11 @@ class MEDIA_EXPORT AudioOutputDispatcher {
// called when a stream is stopped.
virtual void FlushStream(AudioOutputProxy* stream_proxy) = 0;
+ // Called by AudioOutputProxy to set a human-readable label on the
+ // underlying physical stream (e.g. tab title for PulseAudio).
+ virtual void SetStreamLabel(AudioOutputProxy* stream_proxy,
+ const std::string& label) {}
+
protected:
AudioManager* audio_manager() const { return audio_manager_; }
diff --git a/media/audio/audio_output_dispatcher_impl.cc b/media/audio/audio_output_dispatcher_impl.cc
index a02fc285d3..670f412012 100644
--- a/media/audio/audio_output_dispatcher_impl.cc
+++ b/media/audio/audio_output_dispatcher_impl.cc
@@ -22,10 +22,12 @@ AudioOutputDispatcherImpl::AudioOutputDispatcherImpl(
AudioManager* audio_manager,
const AudioParameters& params,
const std::string& output_device_id,
- base::TimeDelta close_delay)
+ base::TimeDelta close_delay,
+ const AudioStreamMetadata& metadata)
: AudioOutputDispatcher(audio_manager),
params_(params),
device_id_(output_device_id),
+ metadata_(metadata),
idle_proxies_(0),
close_timer_(FROM_HERE,
close_delay,
@@ -62,7 +64,6 @@ AudioOutputProxy* AudioOutputDispatcherImpl::CreateStreamProxy() {
bool AudioOutputDispatcherImpl::OpenStream() {
DCHECK(audio_manager()->GetTaskRunner()->BelongsToCurrentThread());
- // Ensure that there is at least one open stream.
if (idle_streams_.empty() && !CreateAndOpenStream()) {
return false;
}
@@ -91,6 +92,9 @@ bool AudioOutputDispatcherImpl::StartStream(
double volume = 0;
stream_proxy->GetVolume(&volume);
physical_stream->SetVolume(volume);
+ if (!stream_proxy->stream_label().empty()) {
+ physical_stream->SetStreamLabel(stream_proxy->stream_label());
+ }
DCHECK(audio_logs_.contains(physical_stream));
AudioLog* const audio_log = audio_logs_[physical_stream].get();
audio_log->OnSetVolume(volume);
@@ -138,6 +142,15 @@ void AudioOutputDispatcherImpl::CloseStream(AudioOutputProxy* stream_proxy) {
// StopStream().
void AudioOutputDispatcherImpl::FlushStream(AudioOutputProxy* stream_proxy) {}
+void AudioOutputDispatcherImpl::SetStreamLabel(AudioOutputProxy* stream_proxy,
+ const std::string& label) {
+ DCHECK(audio_manager()->GetTaskRunner()->BelongsToCurrentThread());
+ auto it = proxy_to_physical_map_.find(stream_proxy);
+ if (it != proxy_to_physical_map_.end()) {
+ it->second->SetStreamLabel(label);
+ }
+}
+
void AudioOutputDispatcherImpl::OnDeviceChange() {
DCHECK(audio_manager()->GetTaskRunner()->BelongsToCurrentThread());
@@ -167,6 +180,9 @@ bool AudioOutputDispatcherImpl::CreateAndOpenStream() {
return false;
}
+ if (!metadata_.app_name.empty())
+ stream->SetStreamAppIdentity(metadata_.app_name, metadata_.xdg_icon_name);
+
if (!stream->Open()) {
stream->Close();
return false;
diff --git a/media/audio/audio_output_dispatcher_impl.h b/media/audio/audio_output_dispatcher_impl.h
index a28096cc4d..0908b6876a 100644
--- a/media/audio/audio_output_dispatcher_impl.h
+++ b/media/audio/audio_output_dispatcher_impl.h
@@ -25,6 +25,7 @@
#include "media/audio/audio_io.h"
#include "media/audio/audio_manager.h"
#include "media/audio/audio_output_dispatcher.h"
+#include "media/audio/audio_stream_metadata.h"
#include "media/base/audio_parameters.h"
namespace media {
@@ -39,7 +40,8 @@ class MEDIA_EXPORT AudioOutputDispatcherImpl
AudioOutputDispatcherImpl(AudioManager* audio_manager,
const AudioParameters& params,
const std::string& output_device_id,
- base::TimeDelta close_delay);
+ base::TimeDelta close_delay,
+ const AudioStreamMetadata& metadata = AudioStreamMetadata());
AudioOutputDispatcherImpl(const AudioOutputDispatcherImpl&) = delete;
AudioOutputDispatcherImpl& operator=(const AudioOutputDispatcherImpl&) =
@@ -56,6 +58,8 @@ class MEDIA_EXPORT AudioOutputDispatcherImpl
void StreamVolumeSet(AudioOutputProxy* stream_proxy, double volume) override;
void CloseStream(AudioOutputProxy* stream_proxy) override;
void FlushStream(AudioOutputProxy* stream_proxy) override;
+ void SetStreamLabel(AudioOutputProxy* stream_proxy,
+ const std::string& label) override;
// AudioDeviceListener implementation.
void OnDeviceChange() override;
@@ -83,6 +87,9 @@ class MEDIA_EXPORT AudioOutputDispatcherImpl
// Output device id.
const std::string device_id_;
+ // App identity applied to every physical stream created by this dispatcher.
+ const AudioStreamMetadata metadata_;
+
size_t idle_proxies_;
std::vector<raw_ptr<AudioOutputStream, VectorExperimental>> idle_streams_;
diff --git a/media/audio/audio_output_proxy.cc b/media/audio/audio_output_proxy.cc
index 5f17fe6f80..dfde4670ed 100644
--- a/media/audio/audio_output_proxy.cc
+++ b/media/audio/audio_output_proxy.cc
@@ -99,4 +99,12 @@ void AudioOutputProxy::Flush() {
dispatcher_->FlushStream(this);
}
+void AudioOutputProxy::SetStreamLabel(const std::string& label) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ stream_label_ = label;
+ if (dispatcher_)
+ dispatcher_->SetStreamLabel(this, label);
+}
+
+
} // namespace media
diff --git a/media/audio/audio_output_proxy.h b/media/audio/audio_output_proxy.h
index 118677fa54..6784bebe9c 100644
--- a/media/audio/audio_output_proxy.h
+++ b/media/audio/audio_output_proxy.h
@@ -39,6 +39,8 @@ class MEDIA_EXPORT AudioOutputProxy : public AudioOutputStream {
void GetVolume(double* volume) override;
void Close() override;
void Flush() override;
+ void SetStreamLabel(const std::string& label) override;
+ const std::string& stream_label() const { return stream_label_; }
AudioOutputDispatcher* get_dispatcher_for_testing() const {
return dispatcher_.get();
@@ -63,6 +65,10 @@ class MEDIA_EXPORT AudioOutputProxy : public AudioOutputStream {
// is stopped, and then started again.
double volume_;
+ // Need to save the stream label so it can be re-applied when a new physical
+ // stream is assigned (e.g. after pause/resume).
+ std::string stream_label_;
+
SEQUENCE_CHECKER(sequence_checker_);
};
diff --git a/media/audio/audio_output_proxy_unittest.cc b/media/audio/audio_output_proxy_unittest.cc
index de2afed3b4..7dc58f1f77 100644
--- a/media/audio/audio_output_proxy_unittest.cc
+++ b/media/audio/audio_output_proxy_unittest.cc
@@ -135,9 +135,10 @@ class MockAudioManager : public AudioManagerBase {
AudioOutputStream*(const AudioParameters& params,
const std::string& device_id,
const LogCallback& log_callback));
- MOCK_METHOD2(MakeAudioOutputStreamProxy, AudioOutputStream*(
+ MOCK_METHOD3(MakeAudioOutputStreamProxy, AudioOutputStream*(
const AudioParameters& params,
- const std::string& device_id));
+ const std::string& device_id,
+ const AudioStreamMetadata& metadata));
MOCK_METHOD3(MakeAudioInputStream,
AudioInputStream*(const AudioParameters& params,
const std::string& device_id,
diff --git a/media/audio/audio_output_resampler.cc b/media/audio/audio_output_resampler.cc
index 114ed6ac87..a78365fd5c 100644
--- a/media/audio/audio_output_resampler.cc
+++ b/media/audio/audio_output_resampler.cc
@@ -216,13 +216,15 @@ AudioOutputResampler::AudioOutputResampler(
const std::string& output_device_id,
base::TimeDelta close_delay,
const RegisterDebugRecordingSourceCallback&
- register_debug_recording_source_callback)
+ register_debug_recording_source_callback,
+ const AudioStreamMetadata& metadata)
: AudioOutputDispatcher(audio_manager),
close_delay_(close_delay),
input_params_(input_params),
output_params_(output_params),
original_output_params_(output_params),
device_id_(output_device_id),
+ metadata_(metadata),
reinitialize_timer_(
FROM_HERE,
close_delay_,
@@ -272,7 +274,7 @@ std::unique_ptr<AudioOutputDispatcherImpl> AudioOutputResampler::MakeDispatcher(
DCHECK(audio_manager()->GetTaskRunner()->BelongsToCurrentThread());
DCHECK(callbacks_.empty());
return std::make_unique<AudioOutputDispatcherImpl>(
- audio_manager(), params, output_device_id, close_delay_);
+ audio_manager(), params, output_device_id, close_delay_, metadata_);
}
AudioOutputProxy* AudioOutputResampler::CreateStreamProxy() {
@@ -470,6 +472,13 @@ void AudioOutputResampler::FlushStream(AudioOutputProxy* stream_proxy) {
dispatcher_->FlushStream(stream_proxy);
}
+void AudioOutputResampler::SetStreamLabel(AudioOutputProxy* stream_proxy,
+ const std::string& label) {
+ DCHECK(audio_manager()->GetTaskRunner()->BelongsToCurrentThread());
+ if (dispatcher_)
+ dispatcher_->SetStreamLabel(stream_proxy, label);
+}
+
void AudioOutputResampler::StopStreamInternal(
const CallbackMap::value_type& item) {
DCHECK(audio_manager()->GetTaskRunner()->BelongsToCurrentThread());
diff --git a/media/audio/audio_output_resampler.h b/media/audio/audio_output_resampler.h
index 96aab4a95a..f14e0ba09e 100644
--- a/media/audio/audio_output_resampler.h
+++ b/media/audio/audio_output_resampler.h
@@ -11,6 +11,7 @@
#include "media/audio/audio_debug_recording_helper.h"
#include "media/audio/audio_io.h"
#include "media/audio/audio_output_dispatcher.h"
+#include "media/audio/audio_stream_metadata.h"
#include "media/base/audio_parameters.h"
namespace media {
@@ -42,7 +43,8 @@ class MEDIA_EXPORT AudioOutputResampler : public AudioOutputDispatcher {
const std::string& output_device_id,
base::TimeDelta close_delay,
const RegisterDebugRecordingSourceCallback&
- register_debug_recording_source_callback);
+ register_debug_recording_source_callback,
+ const AudioStreamMetadata& metadata = AudioStreamMetadata());
AudioOutputResampler(const AudioOutputResampler&) = delete;
AudioOutputResampler& operator=(const AudioOutputResampler&) = delete;
@@ -58,6 +60,8 @@ class MEDIA_EXPORT AudioOutputResampler : public AudioOutputDispatcher {
void StreamVolumeSet(AudioOutputProxy* stream_proxy, double volume) override;
void CloseStream(AudioOutputProxy* stream_proxy) override;
void FlushStream(AudioOutputProxy* stream_proxy) override;
+ void SetStreamLabel(AudioOutputProxy* stream_proxy,
+ const std::string& label) override;
private:
using CallbackMap =
@@ -98,6 +102,9 @@ class MEDIA_EXPORT AudioOutputResampler : public AudioOutputDispatcher {
// Output device id.
const std::string device_id_;
+ // App identity applied to every physical stream via the inner dispatcher.
+ const AudioStreamMetadata metadata_;
+
// The reinitialization timer provides a way to recover from temporary failure
// states by clearing the dispatcher if all proxies have been closed and none
// have been created within |close_delay_|. Without this, audio may be lost
diff --git a/media/audio/audio_stream_metadata.h b/media/audio/audio_stream_metadata.h
new file mode 100644
index 0000000000..3a55567527
--- /dev/null
+++ b/media/audio/audio_stream_metadata.h
@@ -0,0 +1,33 @@
+// 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.
+
+#ifndef MEDIA_AUDIO_AUDIO_STREAM_METADATA_H_
+#define MEDIA_AUDIO_AUDIO_STREAM_METADATA_H_
+
+#include <string>
+
+#include "media/base/media_export.h"
+
+namespace media {
+
+struct MEDIA_EXPORT AudioStreamMetadata {
+ // Tab/page title, used as the PulseAudio stream name.
+ std::string title;
+
+ // Application name override (e.g. PWA name).
+ // Sets PA_PROP_APPLICATION_NAME on PulseAudio.
+ std::string app_name;
+
+ // Freedesktop icon name for the application (Linux only).
+ // Sets PA_PROP_APPLICATION_ICON_NAME on PulseAudio.
+ std::string xdg_icon_name;
+
+ bool empty() const {
+ return title.empty() && app_name.empty() && xdg_icon_name.empty();
+ }
+};
+
+} // namespace media
+
+#endif // MEDIA_AUDIO_AUDIO_STREAM_METADATA_H_
diff --git a/media/audio/mock_audio_manager.cc b/media/audio/mock_audio_manager.cc
index 4064041301..7ed4dab779 100644
--- a/media/audio/mock_audio_manager.cc
+++ b/media/audio/mock_audio_manager.cc
@@ -58,7 +58,8 @@ media::AudioOutputStream* MockAudioManager::MakeAudioOutputStream(
media::AudioOutputStream* MockAudioManager::MakeAudioOutputStreamProxy(
const media::AudioParameters& params,
- const std::string& device_id) {
+ const std::string& device_id,
+ const AudioStreamMetadata& metadata) {
return make_output_stream_cb_ ? make_output_stream_cb_.Run(params, device_id)
: nullptr;
}
diff --git a/media/audio/mock_audio_manager.h b/media/audio/mock_audio_manager.h
index 10323a3570..317f87295f 100644
--- a/media/audio/mock_audio_manager.h
+++ b/media/audio/mock_audio_manager.h
@@ -48,7 +48,8 @@ class MockAudioManager : public AudioManager {
AudioOutputStream* MakeAudioOutputStreamProxy(
const media::AudioParameters& params,
- const std::string& device_id) override;
+ const std::string& device_id,
+ const AudioStreamMetadata& metadata = AudioStreamMetadata()) override;
AudioInputStream* MakeAudioInputStream(
const media::AudioParameters& params,
diff --git a/media/audio/pulse/pulse.sigs b/media/audio/pulse/pulse.sigs
index 2edcd1152f..b5a4c2d482 100644
--- a/media/audio/pulse/pulse.sigs
+++ b/media/audio/pulse/pulse.sigs
@@ -55,6 +55,7 @@ int pa_stream_peek(pa_stream* p, const void** data, size_t* nbytes);
void pa_stream_set_read_callback(pa_stream* p, pa_stream_request_cb_t cb, void* userdata);
void pa_stream_set_state_callback(pa_stream* s, pa_stream_notify_cb_t cb, void* userdata);
int pa_stream_write(pa_stream* p, const void* data, size_t nbytes, pa_free_cb_t free_cb, int64_t offset, pa_seek_mode_t seek);
+pa_operation* pa_stream_set_name(pa_stream *s, const char *name, pa_stream_success_cb_t cb, void *userdata);
void pa_stream_set_write_callback(pa_stream *p, pa_stream_request_cb_t cb, void *userdata);
void pa_stream_unref(pa_stream* s);
int pa_context_errno(const_pa_context_ptr c);
diff --git a/media/audio/pulse/pulse_output.cc b/media/audio/pulse/pulse_output.cc
index 6783fd712b..f85d3cd86d 100644
--- a/media/audio/pulse/pulse_output.cc
+++ b/media/audio/pulse/pulse_output.cc
@@ -88,8 +88,7 @@ bool PulseAudioOutputStream::Open() {
SendLogMessage("%s()", __func__);
bool result = pulse::CreateOutputStream(
&pa_mainloop_, &pa_context_, &pa_stream_, params_, device_id_,
- AudioManager::GetGlobalAppName(), &StreamNotifyCallback,
- &StreamRequestCallback, this);
+ pending_metadata_, &StreamNotifyCallback, &StreamRequestCallback, this);
if (!result) {
SendLogMessage("%s => (ERROR: failed to open PA stream)", __func__);
}
@@ -331,4 +330,27 @@ void PulseAudioOutputStream::GetVolume(double* volume) {
*volume = volume_;
}
+void PulseAudioOutputStream::SetStreamLabel(const std::string& label) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ if (!pa_stream_) {
+ pending_metadata_.title = label;
+ return;
+ }
+
+ AutoPulseLock auto_lock(pa_mainloop_);
+ pa_operation* operation = pa_stream_set_name(
+ pa_stream_, label.c_str(), &pulse::StreamSuccessCallback, pa_mainloop_);
+ WaitForOperationCompletion(pa_mainloop_, operation, pa_context_, pa_stream_);
+}
+
+void PulseAudioOutputStream::SetStreamAppIdentity(
+ const std::string& app_name,
+ const std::string& xdg_icon_name) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!pa_stream_) << "App identity must be set before Open()";
+ pending_metadata_.app_name = app_name;
+ pending_metadata_.xdg_icon_name = xdg_icon_name;
+}
+
} // namespace media
diff --git a/media/audio/pulse/pulse_output.h b/media/audio/pulse/pulse_output.h
index 40ab823e38..bb9908c5bd 100644
--- a/media/audio/pulse/pulse_output.h
+++ b/media/audio/pulse/pulse_output.h
@@ -29,6 +29,7 @@
#include "base/threading/thread_checker.h"
#include "media/audio/audio_io.h"
#include "media/audio/audio_manager.h"
+#include "media/audio/audio_stream_metadata.h"
#include "media/base/amplitude_peak_detector.h"
#include "media/base/audio_parameters.h"
@@ -59,6 +60,9 @@ class PulseAudioOutputStream : public AudioOutputStream {
void Stop() override;
void SetVolume(double volume) override;
void GetVolume(double* volume) override;
+ void SetStreamLabel(const std::string& label) override;
+ void SetStreamAppIdentity(const std::string& app_name,
+ const std::string& xdg_icon_name) override;
private:
// Helper method used for sending native logs to the registered client.
@@ -110,6 +114,9 @@ class PulseAudioOutputStream : public AudioOutputStream {
AmplitudePeakDetector peak_detector_;
+ // Metadata set before Open() to be used during stream creation.
+ AudioStreamMetadata pending_metadata_;
+
base::ThreadChecker thread_checker_;
};
diff --git a/media/audio/pulse/pulse_util.cc b/media/audio/pulse/pulse_util.cc
index a08e42a464..e23ab68137 100644
--- a/media/audio/pulse/pulse_util.cc
+++ b/media/audio/pulse/pulse_util.cc
@@ -19,6 +19,7 @@
#include "base/synchronization/waitable_event.h"
#include "build/branding_buildflags.h"
#include "media/audio/audio_device_description.h"
+#include "media/audio/audio_manager.h"
#include "media/base/audio_timestamp_helper.h"
#if defined(DLOPEN_PULSEAUDIO)
@@ -514,7 +515,7 @@ bool CreateOutputStream(raw_ptr<pa_threaded_mainloop>* mainloop,
raw_ptr<pa_stream>* stream,
const AudioParameters& params,
const std::string& device_id,
- const std::string& app_name,
+ const AudioStreamMetadata& metadata,
pa_stream_notify_cb_t stream_callback,
pa_stream_request_cb_t write_callback,
void* user_data) {
@@ -525,8 +526,12 @@ bool CreateOutputStream(raw_ptr<pa_threaded_mainloop>* mainloop,
RETURN_ON_FAILURE(*mainloop, "Failed to create PulseAudio main loop.");
pa_mainloop_api* pa_mainloop_api = pa_threaded_mainloop_get_api(*mainloop);
- *context = pa_context_new(
- pa_mainloop_api, app_name.empty() ? PRODUCT_STRING : app_name.c_str());
+ std::string context_name = metadata.app_name;
+ if (context_name.empty())
+ context_name = AudioManager::GetGlobalAppName();
+ if (context_name.empty())
+ context_name = PRODUCT_STRING;
+ *context = pa_context_new(pa_mainloop_api, context_name.c_str());
RETURN_ON_FAILURE(*context, "Failed to create PulseAudio context.");
// A state callback must be set before calling pa_threaded_mainloop_lock() or
@@ -571,13 +576,21 @@ bool CreateOutputStream(raw_ptr<pa_threaded_mainloop>* mainloop,
map = &source_channel_map;
}
- // Open playback stream and
- // tell PulseAudio what the stream icon should be.
+ // Open playback stream with application identity and icon.
+ // Always set APPLICATION_NAME explicitly — pa_stream_new_with_proplist does
+ // not inherit it from the context, so omitting it results in an empty name.
ScopedPropertyList property_list;
+ pa_proplist_sets(property_list.get(), PA_PROP_APPLICATION_NAME,
+ context_name.c_str());
pa_proplist_sets(property_list.get(), PA_PROP_APPLICATION_ICON_NAME,
- kBrowserDisplayName);
+ metadata.xdg_icon_name.empty()
+ ? kBrowserDisplayName
+ : metadata.xdg_icon_name.c_str());
+ const char* pa_stream_name =
+ metadata.title.empty() ? "Playback" : metadata.title.c_str();
*stream = pa_stream_new_with_proplist(
- *context, "Playback", &sample_specifications, map, property_list.get());
+ *context, pa_stream_name, &sample_specifications, map,
+ property_list.get());
RETURN_ON_FAILURE(*stream, "failed to create PA playback stream");
pa_stream_set_state_callback(*stream, stream_callback, user_data);
diff --git a/media/audio/pulse/pulse_util.h b/media/audio/pulse/pulse_util.h
index b11fba1e30..a04fdc4c11 100644
--- a/media/audio/pulse/pulse_util.h
+++ b/media/audio/pulse/pulse_util.h
@@ -12,6 +12,7 @@
#include "base/memory/raw_ptr.h"
#include "base/time/time.h"
#include "media/audio/audio_device_name.h"
+#include "media/audio/audio_stream_metadata.h"
#include "media/base/audio_parameters.h"
#include "media/base/channel_layout.h"
@@ -93,7 +94,7 @@ bool CreateOutputStream(raw_ptr<pa_threaded_mainloop>* mainloop,
raw_ptr<pa_stream>* stream,
const AudioParameters& params,
const std::string& device_id,
- const std::string& app_name,
+ const AudioStreamMetadata& metadata,
pa_stream_notify_cb_t stream_callback,
pa_stream_request_cb_t write_callback,
void* user_data);
diff --git a/media/mojo/mojom/audio_stream_factory.mojom b/media/mojo/mojom/audio_stream_factory.mojom
index e240fbade7..2bea1c30a4 100644
--- a/media/mojo/mojom/audio_stream_factory.mojom
+++ b/media/mojo/mojom/audio_stream_factory.mojom
@@ -13,6 +13,14 @@ import "media/mojo/mojom/audio_processing.mojom";
import "mojo/public/mojom/base/unguessable_token.mojom";
import "sandbox/policy/mojom/context.mojom";
+// Metadata for an audio output stream, set at creation time so that platform
+// backends (e.g. PulseAudio) can use it during stream setup.
+struct AudioStreamMetadata {
+ string title; // Tab/page title (stream name).
+ string app_name; // Application name override (e.g. PWA name).
+ string xdg_icon_name; // Freedesktop icon name (Linux only).
+};
+
// Mutes a group of AudioOutputStreams while at least one binding to an instance
// exists. Once the last binding is dropped, all streams in the group are
// un-muted.
@@ -73,7 +81,8 @@ interface AudioStreamFactory {
pending_associated_remote<media.mojom.AudioOutputStreamObserver>? observer,
pending_remote<media.mojom.AudioLog>? log,
string device_id, media.mojom.AudioParameters params,
- mojo_base.mojom.UnguessableToken group_id)
+ mojo_base.mojom.UnguessableToken group_id,
+ AudioStreamMetadata metadata)
=> (media.mojom.ReadWriteAudioDataPipe? data_pipe);
// Same as CreateOutputStream(), but the output device of the resulting
@@ -84,7 +93,8 @@ interface AudioStreamFactory {
pending_associated_remote<media.mojom.AudioOutputStreamObserver>? observer,
pending_remote<media.mojom.AudioLog>? log,
string device_id, media.mojom.AudioParameters params,
- mojo_base.mojom.UnguessableToken group_id)
+ mojo_base.mojom.UnguessableToken group_id,
+ AudioStreamMetadata metadata)
=> (media.mojom.ReadWriteAudioDataPipe? data_pipe);
// Binds the request to the LocalMuter associated with the given |group_id|.
@@ -99,6 +109,11 @@ interface AudioStreamFactory {
BindMuter(pending_associated_receiver<LocalMuter> receiver,
mojo_base.mojom.UnguessableToken group_id);
+ // Updates the stream label (e.g. tab title shown in PulseAudio) for all
+ // output streams belonging to the given |group_id|.
+ SetStreamLabelForGroup(mojo_base.mojom.UnguessableToken group_id,
+ string label);
+
// Creates an AudioInputStream that provides the result of looping-back and
// mixing-together all current and future AudioOutputStreams tagged with the
// given |group_id|. The loopback re-mixes audio, if necessary, so that the
diff --git a/services/audio/device_listener_output_stream.cc b/services/audio/device_listener_output_stream.cc
index 784fc172fa..3f7cf0141b 100644
--- a/services/audio/device_listener_output_stream.cc
+++ b/services/audio/device_listener_output_stream.cc
@@ -72,6 +72,11 @@ void DeviceListenerOutputStream::Flush() {
stream_->Flush();
}
+void DeviceListenerOutputStream::SetStreamLabel(const std::string& label) {
+ stream_->SetStreamLabel(label);
+}
+
+
void DeviceListenerOutputStream::OnDeviceChange() {
DCHECK(task_runner_->BelongsToCurrentThread());
std::move(on_device_change_callback_).Run();
diff --git a/services/audio/device_listener_output_stream.h b/services/audio/device_listener_output_stream.h
index e2ca6056b8..75b676ca91 100644
--- a/services/audio/device_listener_output_stream.h
+++ b/services/audio/device_listener_output_stream.h
@@ -43,6 +43,7 @@ class DeviceListenerOutputStream final
void GetVolume(double* volume) final;
void Close() final;
void Flush() final;
+ void SetStreamLabel(const std::string& label) final;
private:
~DeviceListenerOutputStream() final;
diff --git a/services/audio/output_controller.cc b/services/audio/output_controller.cc
index 1acd08a61a..880fa984a8 100644
--- a/services/audio/output_controller.cc
+++ b/services/audio/output_controller.cc
@@ -279,7 +279,8 @@ void OutputController::RecreateStream(OutputController::RecreateReason reason) {
base::Unretained(this)));
} else {
media::AudioOutputStream* stream =
- audio_manager_->MakeAudioOutputStreamProxy(params_, output_device_id_);
+ audio_manager_->MakeAudioOutputStreamProxy(params_, output_device_id_,
+ metadata_);
if (stream) {
// ProcessDeviceChange must close |stream_|.
stream_ = new DeviceListenerOutputStream(
@@ -297,6 +298,12 @@ void OutputController::RecreateStream(OutputController::RecreateReason reason) {
return;
}
+ // Set stream label before Open() so PulseAudio can use it as the stream name.
+ // App identity is handled at the dispatcher level — all physical streams in a
+ // PWA's dispatcher are created with the correct identity.
+ if (!metadata_.title.empty())
+ stream_->SetStreamLabel(metadata_.title);
+
if (!stream_->Open()) {
SendLogMessage("%s => (ERROR: failed to open the created output stream)",
__func__);
@@ -449,6 +456,19 @@ void OutputController::SetVolume(double volume) {
}
}
+void OutputController::SetStreamMetadata(
+ const media::AudioStreamMetadata& metadata) {
+ DCHECK(task_runner_->BelongsToCurrentThread());
+ metadata_ = metadata;
+}
+
+void OutputController::SetStreamLabel(const std::string& label) {
+ DCHECK(task_runner_->BelongsToCurrentThread());
+ metadata_.title = label;
+ if (stream_)
+ stream_->SetStreamLabel(label);
+}
+
int OutputController::OnMoreData(base::TimeDelta delay,
base::TimeTicks delay_timestamp,
const media::AudioGlitchInfo& glitch_info,
diff --git a/services/audio/output_controller.h b/services/audio/output_controller.h
index d43f09fc63..4ffa1a328d 100644
--- a/services/audio/output_controller.h
+++ b/services/audio/output_controller.h
@@ -25,6 +25,7 @@
#include "base/unguessable_token.h"
#include "media/audio/audio_io.h"
#include "media/audio/audio_manager.h"
+#include "media/audio/audio_stream_metadata.h"
#include "media/base/audio_power_monitor.h"
#include "services/audio/loopback_source.h"
@@ -166,6 +167,13 @@ class OutputController : public media::AudioOutputStream::AudioSourceCallback,
// Sets the volume of the audio output stream.
void SetVolume(double volume);
+ // Sets stream metadata (title, app name, icon) that will be applied to the
+ // stream at creation time. Must be called before CreateStream().
+ void SetStreamMetadata(const media::AudioStreamMetadata& metadata);
+
+ // Updates the stream label (e.g. tab title) on the underlying stream.
+ void SetStreamLabel(const std::string& label);
+
// AudioSourceCallback implementation.
int OnMoreData(base::TimeDelta delay,
base::TimeTicks delay_timestamp,
@@ -326,6 +334,10 @@ class OutputController : public media::AudioOutputStream::AudioSourceCallback,
// `SwitchAudioOutputDeviceId()`, which will recreate the stream.
std::string output_device_id_;
+ // Metadata for the audio stream (title, app name, icon), applied to the
+ // platform stream before Open() so that PulseAudio rules see correct values.
+ media::AudioStreamMetadata metadata_;
+
raw_ptr<media::AudioOutputStream, DanglingUntriaged> stream_;
// When true, local audio output should be muted; either by having audio
diff --git a/services/audio/output_controller_unittest.cc b/services/audio/output_controller_unittest.cc
index f2b27a25e0..f452220626 100644
--- a/services/audio/output_controller_unittest.cc
+++ b/services/audio/output_controller_unittest.cc
@@ -302,7 +302,8 @@ class AudioManagerForControllerTest final : public media::FakeAudioManager {
AudioOutputStream* MakeAudioOutputStreamProxy(
const AudioParameters& params,
- const std::string& device_id) final {
+ const std::string& device_id,
+ const AudioStreamMetadata& metadata = AudioStreamMetadata()) final {
last_created_stream_ = new NiceMock<MockAudioOutputStream>(
media::FakeAudioManager::MakeAudioOutputStream(params, device_id,
base::DoNothing()),
diff --git a/services/audio/output_device_mixer_impl.cc b/services/audio/output_device_mixer_impl.cc
index 32d79c8d4f..75a49d8890 100644
--- a/services/audio/output_device_mixer_impl.cc
+++ b/services/audio/output_device_mixer_impl.cc
@@ -307,6 +307,11 @@ class OutputDeviceMixerImpl::MixableOutputStream final
void Flush() final {}
+ // Mixed streams (used for echo cancellation) combine all Chromium output
+ // into one, so a per-stream label would only be correct if there's a single
+ // stream. No-op.
+ void SetStreamLabel(const std::string& label) final {}
+
private:
SEQUENCE_CHECKER(owning_sequence_);
// OutputDeviceMixerImpl will release all the resources and invalidate its
diff --git a/services/audio/output_device_mixer_manager_unittest.cc b/services/audio/output_device_mixer_manager_unittest.cc
index c88082566c..29011035a0 100644
--- a/services/audio/output_device_mixer_manager_unittest.cc
+++ b/services/audio/output_device_mixer_manager_unittest.cc
@@ -108,7 +108,8 @@ class LocalMockAudioManager : public media::MockAudioManager {
(override));
MOCK_METHOD(AudioOutputStream*,
MakeAudioOutputStreamProxy,
- (const media::AudioParameters&, const std::string&),
+ (const media::AudioParameters&, const std::string&,
+ const media::AudioStreamMetadata&),
(override));
};
@@ -572,7 +573,7 @@ TEST_F(OutputDeviceMixerManagerTest, MakeOutputStream_WithBitstreamFormat) {
ExpectNoMixerCreated();
MockAudioOutputStream mock_stream;
- EXPECT_CALL(audio_manager_, MakeAudioOutputStreamProxy(_, _))
+ EXPECT_CALL(audio_manager_, MakeAudioOutputStreamProxy(_, _, _))
.WillOnce(Return(&mock_stream));
AudioParameters bitstream_params{AudioParameters::Format::AUDIO_BITSTREAM_AC3,
@@ -604,7 +605,7 @@ TEST_F(OutputDeviceMixerManagerTest, MakeOutputStream_WithStaleDeviceInfo) {
ExpectNoMixerCreated();
MockAudioOutputStream mock_stream;
- EXPECT_CALL(audio_manager_, MakeAudioOutputStreamProxy(_, _))
+ EXPECT_CALL(audio_manager_, MakeAudioOutputStreamProxy(_, _, _))
.WillOnce(Return(&mock_stream));
AudioOutputStream* out_stream = output_mixer_manager_.MakeOutputStream(
@@ -620,7 +621,7 @@ TEST_F(OutputDeviceMixerManagerTest, MakeOutputStream_WithStaleDeviceInfo) {
TEST_F(OutputDeviceMixerManagerTest, MakeOutputStream_MaxProxies) {
ExpectNoMixerCreated();
- EXPECT_CALL(audio_manager_, MakeAudioOutputStreamProxy(_, _))
+ EXPECT_CALL(audio_manager_, MakeAudioOutputStreamProxy(_, _, _))
.WillOnce(Return(nullptr));
// We use bitstream parameters to simplify hitting a portion of the code that
diff --git a/services/audio/output_stream.cc b/services/audio/output_stream.cc
index 5223fd57da..8b7145db2f 100644
--- a/services/audio/output_stream.cc
+++ b/services/audio/output_stream.cc
@@ -173,7 +173,8 @@ OutputStream::OutputStream(
const std::string& output_device_id,
const media::AudioParameters& params,
LoopbackCoordinator* coordinator,
- const base::UnguessableToken& loopback_group_id)
+ const base::UnguessableToken& loopback_group_id,
+ const media::AudioStreamMetadata& metadata)
: delete_callback_(std::move(delete_callback)),
receiver_(this, std::move(stream_receiver)),
device_switch_receiver_(this, std::move(device_switch_receiver)),
@@ -222,6 +223,7 @@ OutputStream::OutputStream(
log_->OnCreated(params, output_device_id);
coordinator_->AddMember(loopback_group_id_, &controller_);
+ controller_.SetStreamMetadata(metadata);
if (!reader_.IsValid() || !controller_.CreateStream()) {
// Either SyncReader initialization failed or the controller failed to
// create the stream. In the latter case, the controller will have called
@@ -305,6 +307,12 @@ void OutputStream::SetVolume(double volume) {
log_->OnSetVolume(volume);
}
+
+void OutputStream::SetStreamLabel(const std::string& label) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(owning_sequence_);
+ controller_.SetStreamLabel(label);
+}
+
void OutputStream::SwitchAudioOutputDeviceId(
const std::string& output_device_id) {
DCHECK_CALLED_ON_VALID_SEQUENCE(owning_sequence_);
diff --git a/services/audio/output_stream.h b/services/audio/output_stream.h
index 8d2470519f..a391b02e51 100644
--- a/services/audio/output_stream.h
+++ b/services/audio/output_stream.h
@@ -27,6 +27,7 @@
#include "mojo/public/cpp/system/handle.h"
#include "mojo/public/cpp/system/platform_handle.h"
#include "services/audio/loopback_coordinator.h"
+#include "media/audio/audio_stream_metadata.h"
#include "services/audio/output_controller.h"
#include "services/audio/sync_reader.h"
@@ -65,7 +66,8 @@ class OutputStream final : public media::mojom::AudioOutputStream,
const std::string& output_device_id,
const media::AudioParameters& params,
LoopbackCoordinator* coordinator,
- const base::UnguessableToken& loopback_group_id);
+ const base::UnguessableToken& loopback_group_id,
+ const media::AudioStreamMetadata& metadata);
OutputStream(const OutputStream&) = delete;
OutputStream& operator=(const OutputStream&) = delete;
@@ -81,6 +83,12 @@ class OutputStream final : public media::mojom::AudioOutputStream,
// media::mojom::DeviceSwitchInterface implementation.
void SwitchAudioOutputDeviceId(const std::string& output_device_id) final;
+ const base::UnguessableToken& loopback_group_id() const {
+ return loopback_group_id_;
+ }
+
+ void SetStreamLabel(const std::string& label);
+
// OutputController::EventHandler implementation.
void OnControllerPlaying() final;
void OnControllerPaused() final;
diff --git a/services/audio/output_stream_unittest.cc b/services/audio/output_stream_unittest.cc
index b5a9d4a4fc..26d7b08ce2 100644
--- a/services/audio/output_stream_unittest.cc
+++ b/services/audio/output_stream_unittest.cc
@@ -158,7 +158,8 @@ class TestEnvironment {
remote_stream.InitWithNewPipeAndPassReceiver(), observer_.MakeRemote(),
log_.MakeRemote(), "",
media::AudioParameters::UnavailableDeviceParams(),
- base::UnguessableToken::Create(), created_callback_.Get());
+ base::UnguessableToken::Create(),
+ media::mojom::AudioStreamMetadata::New(), created_callback_.Get());
return remote_stream;
}
@@ -169,7 +170,8 @@ class TestEnvironment {
remote_stream.InitWithNewPipeAndPassReceiver(),
mojo::NullAssociatedRemote(), log_.MakeRemote(), "",
media::AudioParameters::UnavailableDeviceParams(),
- base::UnguessableToken::Create(), created_callback_.Get());
+ base::UnguessableToken::Create(),
+ media::mojom::AudioStreamMetadata::New(), created_callback_.Get());
return remote_stream;
}
@@ -180,7 +182,8 @@ class TestEnvironment {
remote_stream.InitWithNewPipeAndPassReceiver(), observer_.MakeRemote(),
mojo::NullRemote(), "",
media::AudioParameters::UnavailableDeviceParams(),
- base::UnguessableToken::Create(), created_callback_.Get());
+ base::UnguessableToken::Create(),
+ media::mojom::AudioStreamMetadata::New(), created_callback_.Get());
return remote_stream;
}
@@ -192,7 +195,8 @@ class TestEnvironment {
device_switch_interface_.BindNewPipeAndPassReceiver(),
observer_.MakeRemote(), log_.MakeRemote(), device_id,
media::AudioParameters::UnavailableDeviceParams(),
- base::UnguessableToken::Create(), created_callback_.Get());
+ base::UnguessableToken::Create(),
+ media::mojom::AudioStreamMetadata::New(), created_callback_.Get());
return remote_stream;
}
diff --git a/services/audio/public/cpp/fake_stream_factory.h b/services/audio/public/cpp/fake_stream_factory.h
index 6ac239329a..c68050a633 100644
--- a/services/audio/public/cpp/fake_stream_factory.h
+++ b/services/audio/public/cpp/fake_stream_factory.h
@@ -70,6 +70,7 @@ class FakeStreamFactory : public media::mojom::AudioStreamFactory {
const std::string& device_id,
const media::AudioParameters& params,
const base::UnguessableToken& group_id,
+ media::mojom::AudioStreamMetadataPtr metadata,
CreateOutputStreamCallback created_callback) override {}
void CreateSwitchableOutputStream(
mojo::PendingReceiver<media::mojom::AudioOutputStream> stream_receiver,
@@ -81,6 +82,7 @@ class FakeStreamFactory : public media::mojom::AudioStreamFactory {
const std::string& output_device_id,
const media::AudioParameters& params,
const base::UnguessableToken& group_id,
+ media::mojom::AudioStreamMetadataPtr metadata,
CreateOutputStreamCallback created_callback) override {}
void BindMuter(
mojo::PendingAssociatedReceiver<media::mojom::LocalMuter> receiver,
diff --git a/services/audio/public/cpp/output_device.cc b/services/audio/public/cpp/output_device.cc
index 8670782afe..61a0c6acc7 100644
--- a/services/audio/public/cpp/output_device.cc
+++ b/services/audio/public/cpp/output_device.cc
@@ -31,6 +31,7 @@ OutputDevice::OutputDevice(
stream_factory_->CreateOutputStream(
stream_.BindNewPipeAndPassReceiver(), mojo::NullAssociatedRemote(),
mojo::NullRemote(), device_id, params, base::UnguessableToken::Create(),
+ media::mojom::AudioStreamMetadata::New(),
base::BindOnce(&OutputDevice::StreamCreated, weak_factory_.GetWeakPtr()));
stream_.set_disconnect_handler(base::BindOnce(
&OutputDevice::OnConnectionError, weak_factory_.GetWeakPtr()));
diff --git a/services/audio/public/cpp/output_device_unittest.cc b/services/audio/public/cpp/output_device_unittest.cc
index f0e668fd4a..3cea21b6a4 100644
--- a/services/audio/public/cpp/output_device_unittest.cc
+++ b/services/audio/public/cpp/output_device_unittest.cc
@@ -110,6 +110,7 @@ class FakeOutputStreamFactory final : public audio::FakeStreamFactory {
const std::string& output_device_id,
const media::AudioParameters& params,
const base::UnguessableToken& group_id,
+ media::mojom::AudioStreamMetadataPtr metadata,
CreateOutputStreamCallback created_callback) final {
EXPECT_FALSE(observer);
EXPECT_FALSE(log);
diff --git a/services/audio/stream_factory.cc b/services/audio/stream_factory.cc
index 940d248ba0..ab9e88eee9 100644
--- a/services/audio/stream_factory.cc
+++ b/services/audio/stream_factory.cc
@@ -16,6 +16,7 @@
#include "base/unguessable_token.h"
#include "build/chromecast_buildflags.h"
#include "media/audio/audio_device_description.h"
+#include "media/audio/audio_stream_metadata.h"
#include "media/base/media_switches.h"
#include "services/audio/input_stream.h"
#include "services/audio/local_muter.h"
@@ -165,6 +166,7 @@ void StreamFactory::CreateOutputStream(
const std::string& output_device_id,
const media::AudioParameters& params,
const base::UnguessableToken& group_id,
+ media::mojom::AudioStreamMetadataPtr metadata,
CreateOutputStreamCallback created_callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(owning_sequence_);
TRACE_EVENT_INSTANT(
@@ -174,7 +176,7 @@ void StreamFactory::CreateOutputStream(
CreateOutputStreamInternal(std::move(stream_receiver), mojo::NullReceiver(),
std::move(observer), std::move(log),
output_device_id, params, group_id,
- std::move(created_callback));
+ std::move(metadata), std::move(created_callback));
}
void StreamFactory::CreateSwitchableOutputStream(
@@ -187,6 +189,7 @@ void StreamFactory::CreateSwitchableOutputStream(
const std::string& output_device_id,
const media::AudioParameters& params,
const base::UnguessableToken& group_id,
+ media::mojom::AudioStreamMetadataPtr metadata,
CreateOutputStreamCallback created_callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(owning_sequence_);
TRACE_EVENT_INSTANT("audio", "CreateSwitchableOutputStream",
@@ -198,7 +201,7 @@ void StreamFactory::CreateSwitchableOutputStream(
CreateOutputStreamInternal(
std::move(stream_receiver), std::move(device_switch_receiver),
std::move(observer), std::move(log), output_device_id, params, group_id,
- std::move(created_callback));
+ std::move(metadata), std::move(created_callback));
}
void StreamFactory::BindMuter(
@@ -343,6 +346,7 @@ void StreamFactory::CreateOutputStreamInternal(
const std::string& output_device_id,
const media::AudioParameters& params,
const base::UnguessableToken& group_id,
+ media::mojom::AudioStreamMetadataPtr metadata,
CreateOutputStreamCallback created_callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(owning_sequence_);
TRACE_EVENT_INSTANT(
@@ -387,12 +391,30 @@ void StreamFactory::CreateOutputStreamInternal(
}
#endif
+ media::AudioStreamMetadata stream_metadata;
+ if (metadata) {
+ stream_metadata.title = std::move(metadata->title);
+ stream_metadata.app_name = std::move(metadata->app_name);
+ stream_metadata.xdg_icon_name = std::move(metadata->xdg_icon_name);
+ }
+
output_streams_.insert(std::make_unique<OutputStream>(
std::move(created_callback), std::move(deleter_callback),
std::move(managed_device_output_stream_create_callback),
std::move(stream_receiver), std::move(device_switch_receiver),
std::move(observer), std::move(log), audio_manager_,
- device_id_or_group_id, params, &coordinator_, group_id));
+ device_id_or_group_id, params, &coordinator_, group_id,
+ stream_metadata));
+}
+
+void StreamFactory::SetStreamLabelForGroup(
+ const base::UnguessableToken& group_id,
+ const std::string& label) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(owning_sequence_);
+ for (const auto& stream : output_streams_) {
+ if (stream->loopback_group_id() == group_id)
+ stream->SetStreamLabel(label);
+ }
}
#if BUILDFLAG(CHROME_WIDE_ECHO_CANCELLATION)
diff --git a/services/audio/stream_factory.h b/services/audio/stream_factory.h
index fb60e68e73..ab50a87d62 100644
--- a/services/audio/stream_factory.h
+++ b/services/audio/stream_factory.h
@@ -94,6 +94,7 @@ class StreamFactory final : public media::mojom::AudioStreamFactory {
const std::string& output_device_id,
const media::AudioParameters& params,
const base::UnguessableToken& group_id,
+ media::mojom::AudioStreamMetadataPtr metadata,
CreateOutputStreamCallback created_callback) final;
void CreateSwitchableOutputStream(
mojo::PendingReceiver<media::mojom::AudioOutputStream> receiver,
@@ -105,7 +106,10 @@ class StreamFactory final : public media::mojom::AudioStreamFactory {
const std::string& output_device_id,
const media::AudioParameters& params,
const base::UnguessableToken& group_id,
+ media::mojom::AudioStreamMetadataPtr metadata,
CreateOutputStreamCallback created_callback) final;
+ void SetStreamLabelForGroup(const base::UnguessableToken& group_id,
+ const std::string& label) final;
void BindMuter(
mojo::PendingAssociatedReceiver<media::mojom::LocalMuter> receiver,
const base::UnguessableToken& group_id) final;
@@ -143,6 +147,7 @@ class StreamFactory final : public media::mojom::AudioStreamFactory {
const std::string& output_device_id,
const media::AudioParameters& params,
const base::UnguessableToken& group_id,
+ media::mojom::AudioStreamMetadataPtr metadata,
CreateOutputStreamCallback created_callback);
SEQUENCE_CHECKER(owning_sequence_);
diff --git a/chrome/browser/ui/browser_navigator.cc b/chrome/browser/ui/browser_navigator.cc
index afd65467c7..d4ee2a6f5c 100644
--- a/chrome/browser/ui/browser_navigator.cc
+++ b/chrome/browser/ui/browser_navigator.cc
@@ -657,6 +657,17 @@ base::WeakPtr<content::NavigationHandle> Navigate(NavigateParams* params) {
params->browser = override_params->browser();
singleton_index = override_params->tab_index().value_or(-1);
} else {
+ // If the navigation originates from a web app window and the URL is outside
+ // the app's scope, open it in the OS default browser via xdg-open (or
+ // platform equivalent) instead of in a Chromium window.
+ if (source_browser && source_browser->is_type_app() &&
+ source_browser->app_controller() &&
+ !source_browser->app_controller()->IsUrlInAppScope(params->url) &&
+ params->url.SchemeIsHTTPOrHTTPS()) {
+ platform_util::OpenExternal(params->url);
+ return nullptr;
+ }
+
std::tuple<BrowserWindowInterface*, int> browser_and_index =
GetBrowserAndTabForDisposition(*params);
params->browser =
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment