feat: add support for system picker in setDisplayMediaRequestHandler (#43680)
* tmp Co-authored-by: Samuel Attard <marshallofsound@electronjs.org> * feat: add support for system picker in setDisplayMediaRequestHandler Co-authored-by: Samuel Attard <marshallofsound@electronjs.org> * oops Co-authored-by: Samuel Attard <marshallofsound@electronjs.org> * Apply suggestions from code review Co-authored-by: Erick Zhao <erick@hotmail.ca> Co-authored-by: Samuel Attard <sam@electronjs.org> * stuff Co-authored-by: Samuel Attard <marshallofsound@electronjs.org> * well... Co-authored-by: Samuel Attard <marshallofsound@electronjs.org> * seems legit Co-authored-by: Samuel Attard <marshallofsound@electronjs.org> * chore: update patch to handle screenCapturer Co-authored-by: Keeley Hammond <khammond@slack-corp.com> * feat: modify API to use useSystemPicker Co-authored-by: Keeley Hammond <khammond@slack-corp.com> * fix: gate ScreenCaptureKitPicker to macos 15 or higher Co-authored-by: Keeley Hammond <khammond@slack-corp.com> * fix: don't use native picker with legacy media selection Co-authored-by: Keeley Hammond <khammond@slack-corp.com> * chore: code review, boolean set & docs update Co-authored-by: Keeley Hammond <khammond@slack-corp.com> * fix: add cancelCallback Co-authored-by: Keeley Hammond <khammond@slack-corp.com> * docs: clarify session & desktopCapturer docs Co-authored-by: Keeley Hammond <khammond@slack-corp.com> * chore: update patches --------- Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com> Co-authored-by: Samuel Attard <marshallofsound@electronjs.org> Co-authored-by: Samuel Attard <sam@electronjs.org> Co-authored-by: Keeley Hammond <khammond@slack-corp.com>
This commit is contained in:
parent
57aeb9dfc6
commit
2aa2611f76
17 changed files with 433 additions and 7 deletions
|
@ -20,7 +20,11 @@ app.whenReady().then(() => {
|
||||||
// Grant access to the first screen found.
|
// Grant access to the first screen found.
|
||||||
callback({ video: sources[0], audio: 'loopback' })
|
callback({ video: sources[0], audio: 'loopback' })
|
||||||
})
|
})
|
||||||
})
|
// If true, use the system picker if available.
|
||||||
|
// Note: this is currently experimental. If the system picker
|
||||||
|
// is available, it will be used and the media request handler
|
||||||
|
// will not be invoked.
|
||||||
|
}, { useSystemPicker: true })
|
||||||
|
|
||||||
mainWindow.loadFile('index.html')
|
mainWindow.loadFile('index.html')
|
||||||
})
|
})
|
||||||
|
|
|
@ -953,7 +953,7 @@ session.fromPartition('some-partition').setPermissionCheckHandler((webContents,
|
||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
#### `ses.setDisplayMediaRequestHandler(handler)`
|
#### `ses.setDisplayMediaRequestHandler(handler[, opts])`
|
||||||
|
|
||||||
* `handler` Function | null
|
* `handler` Function | null
|
||||||
* `request` Object
|
* `request` Object
|
||||||
|
@ -980,12 +980,18 @@ session.fromPartition('some-partition').setPermissionCheckHandler((webContents,
|
||||||
and this is set to `true`, then local playback of audio will not be muted (e.g. using `MediaRecorder`
|
and this is set to `true`, then local playback of audio will not be muted (e.g. using `MediaRecorder`
|
||||||
to record `WebFrameMain` with this flag set to `true` will allow audio to pass through to the speakers
|
to record `WebFrameMain` with this flag set to `true` will allow audio to pass through to the speakers
|
||||||
while recording). Default is `false`.
|
while recording). Default is `false`.
|
||||||
|
* `opts` Object (optional) _macOS_ _Experimental_
|
||||||
|
* `useSystemPicker` Boolean - true if the available native system picker should be used. Default is `false`. _macOS_ _Experimental_
|
||||||
|
|
||||||
This handler will be called when web content requests access to display media
|
This handler will be called when web content requests access to display media
|
||||||
via the `navigator.mediaDevices.getDisplayMedia` API. Use the
|
via the `navigator.mediaDevices.getDisplayMedia` API. Use the
|
||||||
[desktopCapturer](desktop-capturer.md) API to choose which stream(s) to grant
|
[desktopCapturer](desktop-capturer.md) API to choose which stream(s) to grant
|
||||||
access to.
|
access to.
|
||||||
|
|
||||||
|
`useSystemPicker` allows an application to use the system picker instead of providing a specific video source from `getSources`.
|
||||||
|
This option is experimental, and currently available for MacOS 15+ only. If the system picker is available and `useSystemPicker`
|
||||||
|
is set to `true`, the handler will not be invoked.
|
||||||
|
|
||||||
```js
|
```js
|
||||||
const { session, desktopCapturer } = require('electron')
|
const { session, desktopCapturer } = require('electron')
|
||||||
|
|
||||||
|
@ -994,7 +1000,11 @@ session.defaultSession.setDisplayMediaRequestHandler((request, callback) => {
|
||||||
// Grant access to the first screen found.
|
// Grant access to the first screen found.
|
||||||
callback({ video: sources[0] })
|
callback({ video: sources[0] })
|
||||||
})
|
})
|
||||||
})
|
// Use the system picker if available.
|
||||||
|
// Note: this is currently experimental. If the system picker
|
||||||
|
// is available, it will be used and the media request handler
|
||||||
|
// will not be invoked.
|
||||||
|
}, { useSystemPicker: true })
|
||||||
```
|
```
|
||||||
|
|
||||||
Passing a [WebFrameMain](web-frame-main.md) object as a video or audio stream
|
Passing a [WebFrameMain](web-frame-main.md) object as a video or audio stream
|
||||||
|
|
|
@ -270,6 +270,7 @@ filenames = {
|
||||||
"shell/browser/api/electron_api_debugger.h",
|
"shell/browser/api/electron_api_debugger.h",
|
||||||
"shell/browser/api/electron_api_desktop_capturer.cc",
|
"shell/browser/api/electron_api_desktop_capturer.cc",
|
||||||
"shell/browser/api/electron_api_desktop_capturer.h",
|
"shell/browser/api/electron_api_desktop_capturer.h",
|
||||||
|
"shell/browser/api/electron_api_desktop_capturer_mac.mm",
|
||||||
"shell/browser/api/electron_api_dialog.cc",
|
"shell/browser/api/electron_api_dialog.cc",
|
||||||
"shell/browser/api/electron_api_download_item.cc",
|
"shell/browser/api/electron_api_download_item.cc",
|
||||||
"shell/browser/api/electron_api_download_item.h",
|
"shell/browser/api/electron_api_download_item.h",
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { BrowserWindow } from 'electron/main';
|
import { BrowserWindow } from 'electron/main';
|
||||||
const { createDesktopCapturer } = process._linkedBinding('electron_browser_desktop_capturer');
|
const { createDesktopCapturer, isDisplayMediaSystemPickerAvailable } = process._linkedBinding('electron_browser_desktop_capturer');
|
||||||
|
|
||||||
const deepEqual = (a: ElectronInternal.GetSourcesOptions, b: ElectronInternal.GetSourcesOptions) => JSON.stringify(a) === JSON.stringify(b);
|
const deepEqual = (a: ElectronInternal.GetSourcesOptions, b: ElectronInternal.GetSourcesOptions) => JSON.stringify(a) === JSON.stringify(b);
|
||||||
|
|
||||||
|
@ -13,6 +13,8 @@ function isValid (options: Electron.SourcesOptions) {
|
||||||
return Array.isArray(options?.types);
|
return Array.isArray(options?.types);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export { isDisplayMediaSystemPickerAvailable };
|
||||||
|
|
||||||
export async function getSources (args: Electron.SourcesOptions) {
|
export async function getSources (args: Electron.SourcesOptions) {
|
||||||
if (!isValid(args)) throw new Error('Invalid options');
|
if (!isValid(args)) throw new Error('Invalid options');
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,37 @@
|
||||||
import { fetchWithSession } from '@electron/internal/browser/api/net-fetch';
|
import { fetchWithSession } from '@electron/internal/browser/api/net-fetch';
|
||||||
import { net } from 'electron/main';
|
import { net } from 'electron/main';
|
||||||
const { fromPartition, fromPath, Session } = process._linkedBinding('electron_browser_session');
|
const { fromPartition, fromPath, Session } = process._linkedBinding('electron_browser_session');
|
||||||
|
const { isDisplayMediaSystemPickerAvailable } = process._linkedBinding('electron_browser_desktop_capturer');
|
||||||
|
|
||||||
|
// Fake video source that activates the native system picker
|
||||||
|
// This is used to get around the need for a screen/window
|
||||||
|
// id in Chrome's desktopCapturer.
|
||||||
|
let fakeVideoSourceId = -1;
|
||||||
|
const systemPickerVideoSource = Object.create(null);
|
||||||
|
Object.defineProperty(systemPickerVideoSource, 'id', {
|
||||||
|
get () {
|
||||||
|
return `window:${fakeVideoSourceId--}:0`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
systemPickerVideoSource.name = '';
|
||||||
|
Object.freeze(systemPickerVideoSource);
|
||||||
|
|
||||||
Session.prototype.fetch = function (input: RequestInfo, init?: RequestInit) {
|
Session.prototype.fetch = function (input: RequestInfo, init?: RequestInit) {
|
||||||
return fetchWithSession(input, init, this, net.request);
|
return fetchWithSession(input, init, this, net.request);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Session.prototype.setDisplayMediaRequestHandler = function (handler, opts) {
|
||||||
|
if (!handler) return this._setDisplayMediaRequestHandler(handler, opts);
|
||||||
|
|
||||||
|
this._setDisplayMediaRequestHandler(async (req, callback) => {
|
||||||
|
if (opts && opts.useSystemPicker && isDisplayMediaSystemPickerAvailable()) {
|
||||||
|
return callback({ video: systemPickerVideoSource });
|
||||||
|
}
|
||||||
|
|
||||||
|
return handler(req, callback);
|
||||||
|
}, opts);
|
||||||
|
};
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
fromPartition,
|
fromPartition,
|
||||||
fromPath,
|
fromPath,
|
||||||
|
|
|
@ -129,3 +129,4 @@ feat_enable_passing_exit_code_on_service_process_crash.patch
|
||||||
chore_remove_reference_to_chrome_browser_themes.patch
|
chore_remove_reference_to_chrome_browser_themes.patch
|
||||||
feat_enable_customizing_symbol_color_in_framecaptionbutton.patch
|
feat_enable_customizing_symbol_color_in_framecaptionbutton.patch
|
||||||
build_expose_webplugininfo_interface_to_electron.patch
|
build_expose_webplugininfo_interface_to_electron.patch
|
||||||
|
feat_allow_usage_of_sccontentsharingpicker_on_supported_platforms.patch
|
||||||
|
|
|
@ -199,7 +199,7 @@ index 58985ce62dc569256bad5e94de9c0d125fc470d0..33436784b691c860d58f8b4dfcc6718e
|
||||||
&SelectFileDialogLinuxKde::OnSelectSingleFolderDialogResponse, this,
|
&SelectFileDialogLinuxKde::OnSelectSingleFolderDialogResponse, this,
|
||||||
parent));
|
parent));
|
||||||
diff --git a/ui/shell_dialogs/select_file_dialog_linux_portal.cc b/ui/shell_dialogs/select_file_dialog_linux_portal.cc
|
diff --git a/ui/shell_dialogs/select_file_dialog_linux_portal.cc b/ui/shell_dialogs/select_file_dialog_linux_portal.cc
|
||||||
index 61ddcbf7bf57e423099c7d392a19b3ec79b5d03f..b2d2e11f72dcca5b3791a6dd3e9e5ae930a1f701 100644
|
index 61ddcbf7bf57e423099c7d392a19b3ec79b5d03f..920d0610943091f850e44e3e0481abd7fe08f881 100644
|
||||||
--- a/ui/shell_dialogs/select_file_dialog_linux_portal.cc
|
--- a/ui/shell_dialogs/select_file_dialog_linux_portal.cc
|
||||||
+++ b/ui/shell_dialogs/select_file_dialog_linux_portal.cc
|
+++ b/ui/shell_dialogs/select_file_dialog_linux_portal.cc
|
||||||
@@ -44,7 +44,9 @@ constexpr char kMethodStartServiceByName[] = "StartServiceByName";
|
@@ -44,7 +44,9 @@ constexpr char kMethodStartServiceByName[] = "StartServiceByName";
|
||||||
|
|
|
@ -0,0 +1,331 @@
|
||||||
|
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||||
|
From: Samuel Attard <marshallofsound@electronjs.org>
|
||||||
|
Date: Thu, 8 Aug 2024 08:39:10 -0700
|
||||||
|
Subject: feat: allow usage of SCContentSharingPicker on supported platforms
|
||||||
|
|
||||||
|
This is implemented as a magic "window id" that instead of pulling an SCStream manually
|
||||||
|
instead farms out to the screen picker.
|
||||||
|
|
||||||
|
diff --git a/content/browser/media/capture/desktop_capture_device_mac.cc b/content/browser/media/capture/desktop_capture_device_mac.cc
|
||||||
|
index 88c56f4dfcc1f8517ef1e8b6f1d37f5ba4d0b2c7..a75493a6d4d8ce8340a2d820eff5eed4e6a95109 100644
|
||||||
|
--- a/content/browser/media/capture/desktop_capture_device_mac.cc
|
||||||
|
+++ b/content/browser/media/capture/desktop_capture_device_mac.cc
|
||||||
|
@@ -28,7 +28,7 @@ class DesktopCaptureDeviceMac : public IOSurfaceCaptureDeviceBase {
|
||||||
|
~DesktopCaptureDeviceMac() override = default;
|
||||||
|
|
||||||
|
// IOSurfaceCaptureDeviceBase:
|
||||||
|
- void OnStart() override {
|
||||||
|
+ void OnStart(std::optional<bool> use_native_picker) override {
|
||||||
|
requested_format_ = capture_params().requested_format;
|
||||||
|
requested_format_.pixel_format = media::PIXEL_FORMAT_NV12;
|
||||||
|
DCHECK_GT(requested_format_.frame_size.GetArea(), 0);
|
||||||
|
diff --git a/content/browser/media/capture/io_surface_capture_device_base_mac.cc b/content/browser/media/capture/io_surface_capture_device_base_mac.cc
|
||||||
|
index 8a774911ce0f610b2c993976d108f840696c1d02..5ead7287e2d765d043f8b9c0229a2ee825d9f544 100644
|
||||||
|
--- a/content/browser/media/capture/io_surface_capture_device_base_mac.cc
|
||||||
|
+++ b/content/browser/media/capture/io_surface_capture_device_base_mac.cc
|
||||||
|
@@ -20,7 +20,7 @@ void IOSurfaceCaptureDeviceBase::AllocateAndStart(
|
||||||
|
client_ = std::move(client);
|
||||||
|
capture_params_ = params;
|
||||||
|
|
||||||
|
- OnStart();
|
||||||
|
+ OnStart(params.use_native_picker);
|
||||||
|
}
|
||||||
|
|
||||||
|
void IOSurfaceCaptureDeviceBase::StopAndDeAllocate() {
|
||||||
|
diff --git a/content/browser/media/capture/io_surface_capture_device_base_mac.h b/content/browser/media/capture/io_surface_capture_device_base_mac.h
|
||||||
|
index 8ac12480f663a74dfbdcf7128a582a81b4474d25..db6802a2603e1d3c3039e49737438124bf2ee1f1 100644
|
||||||
|
--- a/content/browser/media/capture/io_surface_capture_device_base_mac.h
|
||||||
|
+++ b/content/browser/media/capture/io_surface_capture_device_base_mac.h
|
||||||
|
@@ -25,7 +25,7 @@ class CONTENT_EXPORT IOSurfaceCaptureDeviceBase
|
||||||
|
~IOSurfaceCaptureDeviceBase() override;
|
||||||
|
|
||||||
|
// OnStart is called by AllocateAndStart.
|
||||||
|
- virtual void OnStart() = 0;
|
||||||
|
+ virtual void OnStart(std::optional<bool> use_native_picker) = 0;
|
||||||
|
|
||||||
|
// OnStop is called by StopAndDeAllocate.
|
||||||
|
virtual void OnStop() = 0;
|
||||||
|
diff --git a/content/browser/media/capture/screen_capture_kit_device_mac.mm b/content/browser/media/capture/screen_capture_kit_device_mac.mm
|
||||||
|
index b6129282c6807702cf88e0a3e2ba233e41a20960..1c2d0c6dd4101fe0bac69e3018bbbedadce224cc 100644
|
||||||
|
--- a/content/browser/media/capture/screen_capture_kit_device_mac.mm
|
||||||
|
+++ b/content/browser/media/capture/screen_capture_kit_device_mac.mm
|
||||||
|
@@ -24,24 +24,83 @@
|
||||||
|
std::optional<gfx::Size>,
|
||||||
|
std::optional<gfx::Rect>)>;
|
||||||
|
using ErrorCallback = base::RepeatingClosure;
|
||||||
|
+using CancelCallback = base::RepeatingClosure;
|
||||||
|
+
|
||||||
|
+API_AVAILABLE(macos(15.0))
|
||||||
|
+@interface ScreenCaptureKitPickerHelper
|
||||||
|
+ : NSObject <SCContentSharingPickerObserver>
|
||||||
|
+
|
||||||
|
+- (void)contentSharingPicker:(SCContentSharingPicker *)picker
|
||||||
|
+ didCancelForStream:(SCStream *)stream;
|
||||||
|
+
|
||||||
|
+- (void)contentSharingPicker:(SCContentSharingPicker *)picker
|
||||||
|
+ didUpdateWithFilter:(SCContentFilter *)filter
|
||||||
|
+ forStream:(SCStream *)stream;
|
||||||
|
+
|
||||||
|
+- (void)contentSharingPickerStartDidFailWithError:(NSError *)error;
|
||||||
|
+
|
||||||
|
+@end
|
||||||
|
+
|
||||||
|
+@implementation ScreenCaptureKitPickerHelper {
|
||||||
|
+ base::RepeatingCallback<void(SCContentFilter *)> _pickerCallback;
|
||||||
|
+ ErrorCallback _errorCallback;
|
||||||
|
+ CancelCallback _cancelCallback;
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+- (void)contentSharingPicker:(SCContentSharingPicker *)picker
|
||||||
|
+ didCancelForStream:(SCStream *)stream {
|
||||||
|
+ // TODO: This doesn't appear to be called on Apple's side;
|
||||||
|
+ // implement this logic
|
||||||
|
+ _cancelCallback.Run();
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+- (void)contentSharingPicker:(SCContentSharingPicker *)picker
|
||||||
|
+ didUpdateWithFilter:(SCContentFilter *)filter
|
||||||
|
+ forStream:(SCStream *)stream {
|
||||||
|
+ if (stream == nil) {
|
||||||
|
+ _pickerCallback.Run(filter);
|
||||||
|
+ [picker removeObserver:self];
|
||||||
|
+ }
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+- (void)contentSharingPickerStartDidFailWithError:(NSError *)error {
|
||||||
|
+ _errorCallback.Run();
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+- (instancetype)initWithStreamPickCallback:(base::RepeatingCallback<void(SCContentFilter *)>)pickerCallback
|
||||||
|
+ cancelCallback:(CancelCallback)cancelCallback
|
||||||
|
+ errorCallback:(ErrorCallback)errorCallback {
|
||||||
|
+ if (self = [super init]) {
|
||||||
|
+ _pickerCallback = pickerCallback;
|
||||||
|
+ _cancelCallback = cancelCallback;
|
||||||
|
+ _errorCallback = errorCallback;
|
||||||
|
+ }
|
||||||
|
+ return self;
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+@end
|
||||||
|
|
||||||
|
API_AVAILABLE(macos(12.3))
|
||||||
|
@interface ScreenCaptureKitDeviceHelper
|
||||||
|
: NSObject <SCStreamDelegate, SCStreamOutput>
|
||||||
|
|
||||||
|
- (instancetype)initWithSampleCallback:(SampleCallback)sampleCallback
|
||||||
|
+ cancelCallback:(CancelCallback)cancelCallback
|
||||||
|
errorCallback:(ErrorCallback)errorCallback;
|
||||||
|
@end
|
||||||
|
|
||||||
|
@implementation ScreenCaptureKitDeviceHelper {
|
||||||
|
SampleCallback _sampleCallback;
|
||||||
|
+ CancelCallback _cancelCallback;
|
||||||
|
ErrorCallback _errorCallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (instancetype)initWithSampleCallback:(SampleCallback)sampleCallback
|
||||||
|
+ cancelCallback:(CancelCallback)cancelCallback
|
||||||
|
errorCallback:(ErrorCallback)errorCallback {
|
||||||
|
if (self = [super init]) {
|
||||||
|
_sampleCallback = sampleCallback;
|
||||||
|
+ _cancelCallback = cancelCallback;
|
||||||
|
_errorCallback = errorCallback;
|
||||||
|
}
|
||||||
|
return self;
|
||||||
|
@@ -141,7 +200,8 @@ + (SCStreamConfiguration*)streamConfigurationWithFrameSize:(gfx::Size)frameSize
|
||||||
|
|
||||||
|
class API_AVAILABLE(macos(12.3)) ScreenCaptureKitDeviceMac
|
||||||
|
: public IOSurfaceCaptureDeviceBase,
|
||||||
|
- public ScreenCaptureKitResetStreamInterface {
|
||||||
|
+ public ScreenCaptureKitResetStreamInterface
|
||||||
|
+ {
|
||||||
|
public:
|
||||||
|
explicit ScreenCaptureKitDeviceMac(const DesktopMediaID& source,
|
||||||
|
SCContentFilter* filter)
|
||||||
|
@@ -152,18 +212,41 @@ explicit ScreenCaptureKitDeviceMac(const DesktopMediaID& source,
|
||||||
|
device_task_runner_,
|
||||||
|
base::BindRepeating(&ScreenCaptureKitDeviceMac::OnStreamSample,
|
||||||
|
weak_factory_.GetWeakPtr()));
|
||||||
|
+ CancelCallback cancel_callback = base::BindPostTask(
|
||||||
|
+ device_task_runner_,
|
||||||
|
+ base::BindRepeating(&ScreenCaptureKitDeviceMac::OnStreamError,
|
||||||
|
+ weak_factory_.GetWeakPtr()));
|
||||||
|
ErrorCallback error_callback = base::BindPostTask(
|
||||||
|
device_task_runner_,
|
||||||
|
base::BindRepeating(&ScreenCaptureKitDeviceMac::OnStreamError,
|
||||||
|
weak_factory_.GetWeakPtr()));
|
||||||
|
helper_ = [[ScreenCaptureKitDeviceHelper alloc]
|
||||||
|
initWithSampleCallback:sample_callback
|
||||||
|
+ cancelCallback:cancel_callback
|
||||||
|
errorCallback:error_callback];
|
||||||
|
+
|
||||||
|
+ if (@available(macOS 15.0, *)) {
|
||||||
|
+ auto picker_callback = base::BindPostTask(
|
||||||
|
+ device_task_runner_,
|
||||||
|
+ base::BindRepeating(&ScreenCaptureKitDeviceMac::OnContentFilterReady, weak_factory_.GetWeakPtr())
|
||||||
|
+ );
|
||||||
|
+ auto* picker_observer = [[ScreenCaptureKitPickerHelper alloc] initWithStreamPickCallback:picker_callback cancelCallback:cancel_callback errorCallback:error_callback];
|
||||||
|
+ [[SCContentSharingPicker sharedPicker] addObserver:picker_observer];
|
||||||
|
+ }
|
||||||
|
}
|
||||||
|
ScreenCaptureKitDeviceMac(const ScreenCaptureKitDeviceMac&) = delete;
|
||||||
|
ScreenCaptureKitDeviceMac& operator=(const ScreenCaptureKitDeviceMac&) =
|
||||||
|
delete;
|
||||||
|
- ~ScreenCaptureKitDeviceMac() override = default;
|
||||||
|
+ ~ScreenCaptureKitDeviceMac() override {
|
||||||
|
+ if (@available(macOS 15.0, *)) {
|
||||||
|
+ auto* picker = [SCContentSharingPicker sharedPicker];
|
||||||
|
+ ScreenCaptureKitDeviceMac::active_streams_--;
|
||||||
|
+ picker.maximumStreamCount = @(ScreenCaptureKitDeviceMac::active_streams_);
|
||||||
|
+ if (ScreenCaptureKitDeviceMac::active_streams_ == 0 && picker.active) {
|
||||||
|
+ picker.active = false;
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
|
||||||
|
void OnShareableContentCreated(SCShareableContent* content) {
|
||||||
|
DCHECK(device_task_runner_->RunsTasksInCurrentSequence());
|
||||||
|
@@ -232,7 +315,7 @@ void CreateStream(SCContentFilter* filter) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
- if (@available(macOS 14.0, *)) {
|
||||||
|
+ if (@available(macOS 15.0, *)) {
|
||||||
|
// Update the content size. This step is neccessary when used together
|
||||||
|
// with SCContentSharingPicker. If the Chrome picker is used, it will
|
||||||
|
// change to retina resolution if applicable.
|
||||||
|
@@ -241,6 +324,9 @@ void CreateStream(SCContentFilter* filter) {
|
||||||
|
filter.contentRect.size.height * filter.pointPixelScale);
|
||||||
|
}
|
||||||
|
|
||||||
|
+ OnContentFilterReady(filter);
|
||||||
|
+ }
|
||||||
|
+ void OnContentFilterReady(SCContentFilter* filter) {
|
||||||
|
gfx::RectF dest_rect_in_frame;
|
||||||
|
actual_capture_format_ = capture_params().requested_format;
|
||||||
|
actual_capture_format_.pixel_format = media::PIXEL_FORMAT_NV12;
|
||||||
|
@@ -254,6 +340,7 @@ void CreateStream(SCContentFilter* filter) {
|
||||||
|
stream_ = [[SCStream alloc] initWithFilter:filter
|
||||||
|
configuration:config
|
||||||
|
delegate:helper_];
|
||||||
|
+
|
||||||
|
{
|
||||||
|
NSError* error = nil;
|
||||||
|
bool add_stream_output_result =
|
||||||
|
@@ -395,7 +482,7 @@ void OnStreamError() {
|
||||||
|
if (fullscreen_module_) {
|
||||||
|
fullscreen_module_->Reset();
|
||||||
|
}
|
||||||
|
- OnStart();
|
||||||
|
+ OnStart(std::nullopt);
|
||||||
|
} else {
|
||||||
|
client()->OnError(media::VideoCaptureError::kScreenCaptureKitStreamError,
|
||||||
|
FROM_HERE, "Stream delegate called didStopWithError");
|
||||||
|
@@ -418,23 +505,39 @@ void OnUpdateConfigurationError() {
|
||||||
|
}
|
||||||
|
|
||||||
|
// IOSurfaceCaptureDeviceBase:
|
||||||
|
- void OnStart() override {
|
||||||
|
+ void OnStart(std::optional<bool> use_native_picker) override {
|
||||||
|
DCHECK(device_task_runner_->RunsTasksInCurrentSequence());
|
||||||
|
- if (filter_) {
|
||||||
|
- // SCContentSharingPicker is used where filter_ is set on creation.
|
||||||
|
- CreateStream(filter_);
|
||||||
|
- } else {
|
||||||
|
- // Chrome picker is used.
|
||||||
|
- auto content_callback = base::BindPostTask(
|
||||||
|
- device_task_runner_,
|
||||||
|
- base::BindRepeating(
|
||||||
|
- &ScreenCaptureKitDeviceMac::OnShareableContentCreated,
|
||||||
|
- weak_factory_.GetWeakPtr()));
|
||||||
|
- auto handler = ^(SCShareableContent* content, NSError* error) {
|
||||||
|
- content_callback.Run(content);
|
||||||
|
- };
|
||||||
|
- [SCShareableContent getShareableContentWithCompletionHandler:handler];
|
||||||
|
+
|
||||||
|
+ if (@available(macOS 15.0, *)) {
|
||||||
|
+ constexpr bool DefaultUseNativePicker = true;
|
||||||
|
+ if (use_native_picker.value_or(DefaultUseNativePicker) && source_.id < 0 && source_.window_id == 0) {
|
||||||
|
+ auto* picker = [SCContentSharingPicker sharedPicker];
|
||||||
|
+ ScreenCaptureKitDeviceMac::active_streams_++;
|
||||||
|
+ picker.maximumStreamCount = @(ScreenCaptureKitDeviceMac::active_streams_);
|
||||||
|
+ if (!picker.active) {
|
||||||
|
+ picker.active = true;
|
||||||
|
+ }
|
||||||
|
+ NSMutableArray<NSNumber*>* exclude_ns_windows = [NSMutableArray array];
|
||||||
|
+ [[[[NSApplication sharedApplication] windows] filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(NSWindow* win, NSDictionary *bindings) {
|
||||||
|
+ return [win sharingType] == NSWindowSharingNone;
|
||||||
|
+ }]] enumerateObjectsUsingBlock:^(NSWindow* win, NSUInteger idx, BOOL *stop) {
|
||||||
|
+ [exclude_ns_windows addObject:@([win windowNumber])];
|
||||||
|
+ }];
|
||||||
|
+ picker.defaultConfiguration.excludedWindowIDs = exclude_ns_windows;
|
||||||
|
+ [picker present];
|
||||||
|
+ return;
|
||||||
|
+ }
|
||||||
|
}
|
||||||
|
+
|
||||||
|
+ auto content_callback = base::BindPostTask(
|
||||||
|
+ device_task_runner_,
|
||||||
|
+ base::BindRepeating(
|
||||||
|
+ &ScreenCaptureKitDeviceMac::OnShareableContentCreated,
|
||||||
|
+ weak_factory_.GetWeakPtr()));
|
||||||
|
+ auto handler = ^(SCShareableContent* content, NSError* error) {
|
||||||
|
+ content_callback.Run(content);
|
||||||
|
+ };
|
||||||
|
+ [SCShareableContent getShareableContentWithCompletionHandler:handler];
|
||||||
|
}
|
||||||
|
void OnStop() override {
|
||||||
|
DCHECK(device_task_runner_->RunsTasksInCurrentSequence());
|
||||||
|
@@ -492,6 +595,8 @@ void ResetStreamTo(SCWindow* window) override {
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
+ static int active_streams_;
|
||||||
|
+
|
||||||
|
const DesktopMediaID source_;
|
||||||
|
SCContentFilter* const filter_;
|
||||||
|
const scoped_refptr<base::SingleThreadTaskRunner> device_task_runner_;
|
||||||
|
@@ -521,6 +626,8 @@ void ResetStreamTo(SCWindow* window) override {
|
||||||
|
base::WeakPtrFactory<ScreenCaptureKitDeviceMac> weak_factory_{this};
|
||||||
|
};
|
||||||
|
|
||||||
|
+int ScreenCaptureKitDeviceMac::active_streams_ = 0;
|
||||||
|
+
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
// Although ScreenCaptureKit is available in 12.3 there were some bugs that
|
||||||
|
diff --git a/content/browser/renderer_host/media/in_process_video_capture_device_launcher.cc b/content/browser/renderer_host/media/in_process_video_capture_device_launcher.cc
|
||||||
|
index 7adf8264cfa9980c4a8414bf0f8bfa9ad70ec0b3..d162612dc70a2b57190aaf558aca8f46cbdedcad 100644
|
||||||
|
--- a/content/browser/renderer_host/media/in_process_video_capture_device_launcher.cc
|
||||||
|
+++ b/content/browser/renderer_host/media/in_process_video_capture_device_launcher.cc
|
||||||
|
@@ -360,13 +360,15 @@ void InProcessVideoCaptureDeviceLauncher::LaunchDeviceAsync(
|
||||||
|
std::move(after_start_capture_callback));
|
||||||
|
break;
|
||||||
|
#else
|
||||||
|
+ media::VideoCaptureParams updated_params = params;
|
||||||
|
+ updated_params.use_native_picker = stream_type != blink::mojom::MediaStreamType::GUM_DESKTOP_VIDEO_CAPTURE;
|
||||||
|
// All cases other than tab capture or Aura desktop/window capture.
|
||||||
|
TRACE_EVENT_INSTANT0(TRACE_DISABLED_BY_DEFAULT("video_and_image_capture"),
|
||||||
|
"UsingDesktopCapturer", TRACE_EVENT_SCOPE_THREAD);
|
||||||
|
start_capture_closure = base::BindOnce(
|
||||||
|
&InProcessVideoCaptureDeviceLauncher::
|
||||||
|
DoStartDesktopCaptureOnDeviceThread,
|
||||||
|
- base::Unretained(this), desktop_id, params,
|
||||||
|
+ base::Unretained(this), desktop_id, updated_params,
|
||||||
|
CreateDeviceClient(media::VideoCaptureBufferType::kSharedMemory,
|
||||||
|
kMaxNumberOfBuffers, std::move(receiver),
|
||||||
|
std::move(receiver_on_io_thread)),
|
||||||
|
diff --git a/media/capture/video_capture_types.h b/media/capture/video_capture_types.h
|
||||||
|
index f2b75f5b2f547ad135c1288bf3639b26dedc8053..ef18724d9f2ea68a47b66fc3981f58a73ac1b51d 100644
|
||||||
|
--- a/media/capture/video_capture_types.h
|
||||||
|
+++ b/media/capture/video_capture_types.h
|
||||||
|
@@ -355,6 +355,8 @@ struct CAPTURE_EXPORT VideoCaptureParams {
|
||||||
|
// Flag indicating whether HiDPI mode should be enabled for tab capture
|
||||||
|
// sessions.
|
||||||
|
bool is_high_dpi_enabled = true;
|
||||||
|
+
|
||||||
|
+ std::optional<bool> use_native_picker;
|
||||||
|
};
|
||||||
|
|
||||||
|
CAPTURE_EXPORT std::ostream& operator<<(
|
|
@ -503,6 +503,13 @@ gin::Handle<DesktopCapturer> DesktopCapturer::Create(v8::Isolate* isolate) {
|
||||||
return handle;
|
return handle;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// static
|
||||||
|
#if !BUILDFLAG(IS_MAC)
|
||||||
|
bool DesktopCapturer::IsDisplayMediaSystemPickerAvailable() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
gin::ObjectTemplateBuilder DesktopCapturer::GetObjectTemplateBuilder(
|
gin::ObjectTemplateBuilder DesktopCapturer::GetObjectTemplateBuilder(
|
||||||
v8::Isolate* isolate) {
|
v8::Isolate* isolate) {
|
||||||
return gin::Wrappable<DesktopCapturer>::GetObjectTemplateBuilder(isolate)
|
return gin::Wrappable<DesktopCapturer>::GetObjectTemplateBuilder(isolate)
|
||||||
|
@ -524,6 +531,9 @@ void Initialize(v8::Local<v8::Object> exports,
|
||||||
gin_helper::Dictionary dict(context->GetIsolate(), exports);
|
gin_helper::Dictionary dict(context->GetIsolate(), exports);
|
||||||
dict.SetMethod("createDesktopCapturer",
|
dict.SetMethod("createDesktopCapturer",
|
||||||
&electron::api::DesktopCapturer::Create);
|
&electron::api::DesktopCapturer::Create);
|
||||||
|
dict.SetMethod(
|
||||||
|
"isDisplayMediaSystemPickerAvailable",
|
||||||
|
&electron::api::DesktopCapturer::IsDisplayMediaSystemPickerAvailable);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
|
@ -36,6 +36,8 @@ class DesktopCapturer final : public gin::Wrappable<DesktopCapturer>,
|
||||||
|
|
||||||
static gin::Handle<DesktopCapturer> Create(v8::Isolate* isolate);
|
static gin::Handle<DesktopCapturer> Create(v8::Isolate* isolate);
|
||||||
|
|
||||||
|
static bool IsDisplayMediaSystemPickerAvailable();
|
||||||
|
|
||||||
void StartHandling(bool capture_window,
|
void StartHandling(bool capture_window,
|
||||||
bool capture_screen,
|
bool capture_screen,
|
||||||
const gfx::Size& thumbnail_size,
|
const gfx::Size& thumbnail_size,
|
||||||
|
|
17
shell/browser/api/electron_api_desktop_capturer_mac.mm
Normal file
17
shell/browser/api/electron_api_desktop_capturer_mac.mm
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
// Copyright (c) 2024 Salesforce, Inc.
|
||||||
|
// Use of this source code is governed by the MIT license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
#include "shell/browser/api/electron_api_desktop_capturer.h"
|
||||||
|
|
||||||
|
namespace electron::api {
|
||||||
|
|
||||||
|
// static
|
||||||
|
bool DesktopCapturer::IsDisplayMediaSystemPickerAvailable() {
|
||||||
|
if (@available(macOS 15.0, *)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace electron::api
|
|
@ -1607,7 +1607,7 @@ void Session::FillObjectTemplate(v8::Isolate* isolate,
|
||||||
&Session::SetPermissionRequestHandler)
|
&Session::SetPermissionRequestHandler)
|
||||||
.SetMethod("setPermissionCheckHandler",
|
.SetMethod("setPermissionCheckHandler",
|
||||||
&Session::SetPermissionCheckHandler)
|
&Session::SetPermissionCheckHandler)
|
||||||
.SetMethod("setDisplayMediaRequestHandler",
|
.SetMethod("_setDisplayMediaRequestHandler",
|
||||||
&Session::SetDisplayMediaRequestHandler)
|
&Session::SetDisplayMediaRequestHandler)
|
||||||
.SetMethod("setDevicePermissionHandler",
|
.SetMethod("setDevicePermissionHandler",
|
||||||
&Session::SetDevicePermissionHandler)
|
&Session::SetDevicePermissionHandler)
|
||||||
|
|
|
@ -58,6 +58,11 @@ void InitializeFeatureList() {
|
||||||
if (platform_specific_enable_features.size() > 0) {
|
if (platform_specific_enable_features.size() > 0) {
|
||||||
enable_features += std::string(",") + platform_specific_enable_features;
|
enable_features += std::string(",") + platform_specific_enable_features;
|
||||||
}
|
}
|
||||||
|
std::string platform_specific_disable_features =
|
||||||
|
DisablePlatformSpecificFeatures();
|
||||||
|
if (platform_specific_disable_features.size() > 0) {
|
||||||
|
disable_features += std::string(",") + platform_specific_disable_features;
|
||||||
|
}
|
||||||
base::FeatureList::InitInstance(enable_features, disable_features);
|
base::FeatureList::InitInstance(enable_features, disable_features);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,6 +78,9 @@ void InitializeFieldTrials() {
|
||||||
std::string EnablePlatformSpecificFeatures() {
|
std::string EnablePlatformSpecificFeatures() {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
std::string DisablePlatformSpecificFeatures() {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
} // namespace electron
|
} // namespace electron
|
||||||
|
|
|
@ -11,6 +11,7 @@ namespace electron {
|
||||||
void InitializeFeatureList();
|
void InitializeFeatureList();
|
||||||
void InitializeFieldTrials();
|
void InitializeFieldTrials();
|
||||||
std::string EnablePlatformSpecificFeatures();
|
std::string EnablePlatformSpecificFeatures();
|
||||||
|
std::string DisablePlatformSpecificFeatures();
|
||||||
} // namespace electron
|
} // namespace electron
|
||||||
|
|
||||||
#endif // ELECTRON_SHELL_BROWSER_FEATURE_LIST_H_
|
#endif // ELECTRON_SHELL_BROWSER_FEATURE_LIST_H_
|
||||||
|
|
|
@ -31,4 +31,13 @@ std::string EnablePlatformSpecificFeatures() {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string DisablePlatformSpecificFeatures() {
|
||||||
|
if (@available(macOS 14.4, *)) {
|
||||||
|
// Required to stop timing out getDisplayMedia while waiting for
|
||||||
|
// the user to select a window with the picker
|
||||||
|
return "TimeoutHangingVideoCaptureStarts";
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace electron
|
} // namespace electron
|
||||||
|
|
2
typings/internal-ambient.d.ts
vendored
2
typings/internal-ambient.d.ts
vendored
|
@ -213,7 +213,7 @@ declare namespace NodeJS {
|
||||||
_linkedBinding(name: 'electron_browser_app'): { app: Electron.App, App: Function };
|
_linkedBinding(name: 'electron_browser_app'): { app: Electron.App, App: Function };
|
||||||
_linkedBinding(name: 'electron_browser_auto_updater'): { autoUpdater: Electron.AutoUpdater };
|
_linkedBinding(name: 'electron_browser_auto_updater'): { autoUpdater: Electron.AutoUpdater };
|
||||||
_linkedBinding(name: 'electron_browser_crash_reporter'): CrashReporterBinding;
|
_linkedBinding(name: 'electron_browser_crash_reporter'): CrashReporterBinding;
|
||||||
_linkedBinding(name: 'electron_browser_desktop_capturer'): { createDesktopCapturer(): ElectronInternal.DesktopCapturer; };
|
_linkedBinding(name: 'electron_browser_desktop_capturer'): { createDesktopCapturer(): ElectronInternal.DesktopCapturer; isDisplayMediaSystemPickerAvailable(): boolean; };
|
||||||
_linkedBinding(name: 'electron_browser_event_emitter'): { setEventEmitterPrototype(prototype: Object): void; };
|
_linkedBinding(name: 'electron_browser_event_emitter'): { setEventEmitterPrototype(prototype: Object): void; };
|
||||||
_linkedBinding(name: 'electron_browser_global_shortcut'): { globalShortcut: Electron.GlobalShortcut };
|
_linkedBinding(name: 'electron_browser_global_shortcut'): { globalShortcut: Electron.GlobalShortcut };
|
||||||
_linkedBinding(name: 'electron_browser_image_view'): { ImageView: any };
|
_linkedBinding(name: 'electron_browser_image_view'): { ImageView: any };
|
||||||
|
|
4
typings/internal-electron.d.ts
vendored
4
typings/internal-electron.d.ts
vendored
|
@ -127,6 +127,10 @@ declare namespace Electron {
|
||||||
type?: 'backgroundPage' | 'window' | 'browserView' | 'remote' | 'webview' | 'offscreen';
|
type?: 'backgroundPage' | 'window' | 'browserView' | 'remote' | 'webview' | 'offscreen';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface Session {
|
||||||
|
_setDisplayMediaRequestHandler: Electron.Session['setDisplayMediaRequestHandler'];
|
||||||
|
}
|
||||||
|
|
||||||
type CreateWindowFunction = (options: BrowserWindowConstructorOptions) => WebContents;
|
type CreateWindowFunction = (options: BrowserWindowConstructorOptions) => WebContents;
|
||||||
|
|
||||||
interface Menu {
|
interface Menu {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue