Browse Source

fix: use generic capturer to list both screens and windows when possible (#39710)

Screensharing with PipeWire via XDG Desktop Portal requires explicit
user permission via permission dialogs. Chromium has separate tabs for
screens and windows and thus its portal implementation requests
permissions separately for each. However, the screencast portal has no
such limitation and supports both screens and windows in a single
request.

WebRTC now supports this type of capture in a new method called
called `CreateGenericCapturer`. The `desktopCapturer` implementation has
been modified to use it. Additionally, Chromium has been patched to use
same generic capturer to ensure that the source IDs remain valid for
`getUserMedia`.
Athul Iddya 1 year ago
parent
commit
66432ed9fc

+ 1 - 0
patches/chromium/.patches

@@ -140,3 +140,4 @@ cherry-pick-35c06406a658.patch
 revert_remove_the_allowaggressivethrottlingwithwebsocket_feature.patch
 cherry-pick-74a2eb9c8cb2.patch
 cherry-pick-26175b0903d8.patch
+fix_use_delegated_generic_capturer_when_available.patch

+ 54 - 0
patches/chromium/fix_use_delegated_generic_capturer_when_available.patch

@@ -0,0 +1,54 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Athul Iddya <[email protected]>
+Date: Fri, 14 Jul 2023 08:03:37 -0700
+Subject: fix: use delegated generic capturer when available
+
+When the generic capturer is used to fetch capture sources, the returned
+ID will be arbitrarily prefixed with "screen" or "window" regardless of
+the source type. If the window capturer is used to stream video when the
+source was a screen or vice-versa, the stream fails to restart in
+delegated capturers like PipeWire.
+
+To fix this, use the generic capturer to fetch the media stream if it's
+delegated and available. This does not cause any issues if the original
+capturer was window or screen-specific, as the IDs remain valid for
+generic capturer as well.
+
+diff --git a/content/browser/media/capture/desktop_capture_device.cc b/content/browser/media/capture/desktop_capture_device.cc
+index 2ba2fd5eebde2c5d6d5cf6ef35fae1593c55cc5e..9e6d1b33835fe1760b564b2581d3530256f48272 100644
+--- a/content/browser/media/capture/desktop_capture_device.cc
++++ b/content/browser/media/capture/desktop_capture_device.cc
+@@ -677,8 +677,14 @@ std::unique_ptr<media::VideoCaptureDevice> DesktopCaptureDevice::Create(
+               DesktopCapturerLacros::CaptureType::kScreen,
+               webrtc::DesktopCaptureOptions());
+ #else
+-      std::unique_ptr<webrtc::DesktopCapturer> screen_capturer(
+-          webrtc::DesktopCapturer::CreateScreenCapturer(options));
++      std::unique_ptr<webrtc::DesktopCapturer> screen_capturer;
++      if (auto generic_capturer =
++              webrtc::DesktopCapturer::CreateGenericCapturer(options);
++          generic_capturer && generic_capturer->GetDelegatedSourceListController()) {
++        screen_capturer = std::move(generic_capturer);
++      } else {
++        screen_capturer = webrtc::DesktopCapturer::CreateScreenCapturer(options);
++      }
+ #endif
+       if (screen_capturer && screen_capturer->SelectSource(source.id)) {
+         capturer = std::make_unique<webrtc::DesktopAndCursorComposer>(
+@@ -697,8 +703,14 @@ std::unique_ptr<media::VideoCaptureDevice> DesktopCaptureDevice::Create(
+           new DesktopCapturerLacros(DesktopCapturerLacros::CaptureType::kWindow,
+                                     webrtc::DesktopCaptureOptions()));
+ #else
+-      std::unique_ptr<webrtc::DesktopCapturer> window_capturer =
+-          webrtc::DesktopCapturer::CreateWindowCapturer(options);
++      std::unique_ptr<webrtc::DesktopCapturer> window_capturer;
++      if (auto generic_capturer =
++              webrtc::DesktopCapturer::CreateGenericCapturer(options);
++          generic_capturer && generic_capturer->GetDelegatedSourceListController()) {
++        window_capturer = std::move(generic_capturer);
++      } else {
++        window_capturer = webrtc::DesktopCapturer::CreateWindowCapturer(options);
++      }
+ #endif
+       if (window_capturer && window_capturer->SelectSource(source.id)) {
+         capturer = std::make_unique<webrtc::DesktopAndCursorComposer>(

+ 1 - 0
patches/webrtc/.patches

@@ -3,3 +3,4 @@ fix_mark_pipewire_capturer_as_failed_after_session_is_closed.patch
 pipewire_capturer_fix_fcntl_call_when_duplicating_a_file_descriptor.patch
 pipewire_capturer_increase_buffer_size_to_avoid_buffer_overflow.patch
 prevent_sdp_munging_of_duplicate_ssrcs.patch
+desktop_capture_introduce_capturer_requesting_both_screen_and.patch

+ 102 - 0
patches/webrtc/desktop_capture_introduce_capturer_requesting_both_screen_and.patch

@@ -0,0 +1,102 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Jan Grulich <[email protected]>
+Date: Wed, 12 Jul 2023 13:15:40 +0200
+Subject: Desktop capture: introduce capturer requesting both screen and
+ windows
+
+When PipeWire and xdg-desktop-portals are used, we can actually combine
+both source types into one request. Make this part of the API for those
+who want to use it this way, e.g. Firefox or Electron, otherwise they
+will end up making two simultaneous requests, resulting into two dialogs
+at the same time asking, while they can be combined into just one.
+
+Bug: webrtc:15363
+Change-Id: Ib6e1e47f66cb01d5c65096aec378b44c3af5f387
+Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/311549
+Reviewed-by: Alexander Cooper <[email protected]>
+Commit-Queue: Jan Grulich <[email protected]>
+Cr-Commit-Position: refs/heads/main@{#40425}
+
+diff --git a/modules/desktop_capture/desktop_capture_types.h b/modules/desktop_capture/desktop_capture_types.h
+index 9627076eea3d1272f87b773ada86be6051f1e224..a4e3e897fde1d3c7bb470449448f30ff6e50caea 100644
+--- a/modules/desktop_capture/desktop_capture_types.h
++++ b/modules/desktop_capture/desktop_capture_types.h
+@@ -15,7 +15,7 @@
+ 
+ namespace webrtc {
+ 
+-enum class CaptureType { kWindow, kScreen };
++enum class CaptureType { kWindow, kScreen, kAnyScreenContent };
+ 
+ // Type used to identify windows on the desktop. Values are platform-specific:
+ //   - On Windows: HWND cast to intptr_t.
+diff --git a/modules/desktop_capture/desktop_capturer.cc b/modules/desktop_capture/desktop_capturer.cc
+index 5211f1acecaba963e1436c1d04aa953853c79eb7..b99f5ecb803ac0ac0e26fbdb3411c77605f33011 100644
+--- a/modules/desktop_capture/desktop_capturer.cc
++++ b/modules/desktop_capture/desktop_capturer.cc
+@@ -26,6 +26,10 @@
+ #include "rtc_base/win/windows_version.h"
+ #endif  // defined(RTC_ENABLE_WIN_WGC)
+ 
++#if defined(WEBRTC_USE_PIPEWIRE)
++#include "modules/desktop_capture/linux/wayland/base_capturer_pipewire.h"
++#endif
++
+ namespace webrtc {
+ 
+ void LogDesktopCapturerFullscreenDetectorUsage() {
+@@ -101,6 +105,25 @@ std::unique_ptr<DesktopCapturer> DesktopCapturer::CreateScreenCapturer(
+   return capturer;
+ }
+ 
++// static
++std::unique_ptr<DesktopCapturer> DesktopCapturer::CreateGenericCapturer(
++    const DesktopCaptureOptions& options) {
++  std::unique_ptr<DesktopCapturer> capturer;
++
++#if defined(WEBRTC_USE_PIPEWIRE)
++  if (options.allow_pipewire() && DesktopCapturer::IsRunningUnderWayland()) {
++    capturer = std::make_unique<BaseCapturerPipeWire>(
++        options, CaptureType::kAnyScreenContent);
++  }
++
++  if (capturer && options.detect_updated_region()) {
++    capturer.reset(new DesktopCapturerDifferWrapper(std::move(capturer)));
++  }
++#endif  // defined(WEBRTC_USE_PIPEWIRE)
++
++  return capturer;
++}
++
+ #if defined(WEBRTC_USE_PIPEWIRE) || defined(WEBRTC_USE_X11)
+ bool DesktopCapturer::IsRunningUnderWayland() {
+   const char* xdg_session_type = getenv("XDG_SESSION_TYPE");
+diff --git a/modules/desktop_capture/desktop_capturer.h b/modules/desktop_capture/desktop_capturer.h
+index fd884f13ff771de2d5ea8b79ba530e9d3b03e913..9c7ecc78f46ea2e2c173416132318f0526714265 100644
+--- a/modules/desktop_capture/desktop_capturer.h
++++ b/modules/desktop_capture/desktop_capturer.h
+@@ -186,6 +186,11 @@ class RTC_EXPORT DesktopCapturer {
+   static std::unique_ptr<DesktopCapturer> CreateScreenCapturer(
+       const DesktopCaptureOptions& options);
+ 
++  // Creates a DesktopCapturer instance which targets to capture windows and
++  // screens.
++  static std::unique_ptr<DesktopCapturer> CreateGenericCapturer(
++      const DesktopCaptureOptions& options);
++
+ #if defined(WEBRTC_USE_PIPEWIRE) || defined(WEBRTC_USE_X11)
+   static bool IsRunningUnderWayland();
+ 
+diff --git a/modules/desktop_capture/linux/wayland/screencast_portal.cc b/modules/desktop_capture/linux/wayland/screencast_portal.cc
+index 3d28d42ba633f5103bb185998d968cd6b85062d9..ebe6779e31be9bbeab5154171ca02fc9d90e8c2a 100644
+--- a/modules/desktop_capture/linux/wayland/screencast_portal.cc
++++ b/modules/desktop_capture/linux/wayland/screencast_portal.cc
+@@ -41,6 +41,8 @@ ScreenCastPortal::CaptureSourceType ScreenCastPortal::ToCaptureSourceType(
+       return ScreenCastPortal::CaptureSourceType::kScreen;
+     case CaptureType::kWindow:
+       return ScreenCastPortal::CaptureSourceType::kWindow;
++    case CaptureType::kAnyScreenContent:
++      return ScreenCastPortal::CaptureSourceType::kAnyScreenContent;
+   }
+ }
+ 

+ 27 - 0
shell/browser/api/electron_api_desktop_capturer.cc

@@ -234,6 +234,33 @@ void DesktopCapturer::StartHandling(bool capture_window,
   // clear any existing captured sources.
   captured_sources_.clear();
 
+  if (capture_window && capture_screen) {
+    // Some capturers like PipeWire suppport a single capturer for both screens
+    // and windows. Use it if possible, treating both as window capture
+    if (auto capturer = webrtc::DesktopCapturer::CreateGenericCapturer(
+            content::desktop_capture::CreateDesktopCaptureOptions());
+        capturer && capturer->GetDelegatedSourceListController()) {
+      capture_screen_ = false;
+      capture_window_ = capture_window;
+      window_capturer_ = std::make_unique<NativeDesktopMediaList>(
+          DesktopMediaList::Type::kWindow, std::move(capturer));
+      window_capturer_->SetThumbnailSize(thumbnail_size);
+
+      OnceCallback update_callback = base::BindOnce(
+          &DesktopCapturer::UpdateSourcesList, weak_ptr_factory_.GetWeakPtr(),
+          window_capturer_.get());
+      OnceCallback failure_callback = base::BindOnce(
+          &DesktopCapturer::HandleFailure, weak_ptr_factory_.GetWeakPtr());
+
+      window_listener_ = std::make_unique<DesktopListListener>(
+          std::move(update_callback), std::move(failure_callback),
+          thumbnail_size.IsEmpty());
+      window_capturer_->StartUpdating(window_listener_.get());
+
+      return;
+    }
+  }
+
   // Start listening for captured sources.
   capture_window_ = capture_window;
   capture_screen_ = capture_screen;