summaryrefslogtreecommitdiffstats
path: root/dom/media/systemservices/video_engine
diff options
context:
space:
mode:
Diffstat (limited to 'dom/media/systemservices/video_engine')
-rw-r--r--dom/media/systemservices/video_engine/desktop_capture_impl.cc782
-rw-r--r--dom/media/systemservices/video_engine/desktop_capture_impl.h249
-rw-r--r--dom/media/systemservices/video_engine/desktop_device_info.cc488
-rw-r--r--dom/media/systemservices/video_engine/desktop_device_info.h84
-rw-r--r--dom/media/systemservices/video_engine/placeholder_device_info.cc60
-rw-r--r--dom/media/systemservices/video_engine/placeholder_device_info.h45
-rw-r--r--dom/media/systemservices/video_engine/platform_uithread.cc198
-rw-r--r--dom/media/systemservices/video_engine/platform_uithread.h96
-rw-r--r--dom/media/systemservices/video_engine/tab_capturer.cc331
-rw-r--r--dom/media/systemservices/video_engine/tab_capturer.h88
-rw-r--r--dom/media/systemservices/video_engine/video_capture_factory.cc230
-rw-r--r--dom/media/systemservices/video_engine/video_capture_factory.h89
12 files changed, 2740 insertions, 0 deletions
diff --git a/dom/media/systemservices/video_engine/desktop_capture_impl.cc b/dom/media/systemservices/video_engine/desktop_capture_impl.cc
new file mode 100644
index 0000000000..a966ff06d4
--- /dev/null
+++ b/dom/media/systemservices/video_engine/desktop_capture_impl.cc
@@ -0,0 +1,782 @@
+/*
+ * Copyright (c) 2014 The WebRTC project authors. All Rights Reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "video_engine/desktop_capture_impl.h"
+
+#include <cstdlib>
+#include <memory>
+#include <string>
+
+#include "CamerasTypes.h"
+#include "VideoEngine.h"
+#include "VideoUtils.h"
+#include "api/video/i420_buffer.h"
+#include "common_video/libyuv/include/webrtc_libyuv.h"
+#include "libyuv.h" // NOLINT
+#include "modules/include/module_common_types.h"
+#include "modules/video_capture/video_capture_config.h"
+#include "modules/video_capture/video_capture_impl.h"
+#include "system_wrappers/include/clock.h"
+#include "rtc_base/logging.h"
+#include "rtc_base/ref_counted_object.h"
+#include "rtc_base/time_utils.h"
+#include "rtc_base/trace_event.h"
+#include "modules/desktop_capture/desktop_and_cursor_composer.h"
+#include "modules/desktop_capture/desktop_frame.h"
+#include "modules/desktop_capture/desktop_capture_options.h"
+#include "modules/desktop_capture/desktop_capturer_differ_wrapper.h"
+#include "modules/video_capture/video_capture.h"
+#include "mozilla/StaticPrefs_media.h"
+#include "mozilla/SyncRunnable.h"
+#include "mozilla/TaskQueue.h"
+#include "mozilla/TimeStamp.h"
+#include "nsThreadUtils.h"
+#include "tab_capturer.h"
+
+using mozilla::NewRunnableMethod;
+using mozilla::TabCapturerWebrtc;
+using mozilla::TimeDuration;
+using mozilla::camera::CaptureDeviceType;
+using mozilla::camera::CaptureEngine;
+
+static void CaptureFrameOnThread(nsITimer* aTimer, void* aClosure) {
+ static_cast<webrtc::DesktopCaptureImpl*>(aClosure)->CaptureFrameOnThread();
+}
+
+namespace webrtc {
+
+int32_t ScreenDeviceInfoImpl::Init() {
+ mDesktopDeviceInfo =
+ std::unique_ptr<DesktopDeviceInfo>(DesktopDeviceInfo::Create());
+ return 0;
+}
+
+int32_t ScreenDeviceInfoImpl::Refresh() {
+ mDesktopDeviceInfo->Refresh();
+ return 0;
+}
+
+uint32_t ScreenDeviceInfoImpl::NumberOfDevices() {
+ return mDesktopDeviceInfo->getDisplayDeviceCount();
+}
+
+int32_t ScreenDeviceInfoImpl::GetDeviceName(
+ uint32_t aDeviceNumber, char* aDeviceNameUTF8, uint32_t aDeviceNameUTF8Size,
+ char* aDeviceUniqueIdUTF8, uint32_t aDeviceUniqueIdUTF8Size,
+ char* aProductUniqueIdUTF8, uint32_t aProductUniqueIdUTF8Size, pid_t* aPid,
+ bool* aDeviceIsPlaceholder) {
+ DesktopDisplayDevice desktopDisplayDevice;
+
+ // always initialize output
+ if (aDeviceNameUTF8 && aDeviceNameUTF8Size > 0) {
+ memset(aDeviceNameUTF8, 0, aDeviceNameUTF8Size);
+ }
+
+ if (aDeviceUniqueIdUTF8 && aDeviceUniqueIdUTF8Size > 0) {
+ memset(aDeviceUniqueIdUTF8, 0, aDeviceUniqueIdUTF8Size);
+ }
+ if (aProductUniqueIdUTF8 && aProductUniqueIdUTF8Size > 0) {
+ memset(aProductUniqueIdUTF8, 0, aProductUniqueIdUTF8Size);
+ }
+
+ if (mDesktopDeviceInfo->getDesktopDisplayDeviceInfo(
+ aDeviceNumber, desktopDisplayDevice) == 0) {
+ size_t len;
+
+ const char* deviceName = desktopDisplayDevice.getDeviceName();
+ len = deviceName ? strlen(deviceName) : 0;
+ if (len && aDeviceNameUTF8 && len < aDeviceNameUTF8Size) {
+ memcpy(aDeviceNameUTF8, deviceName, len);
+ }
+
+ const char* deviceUniqueId = desktopDisplayDevice.getUniqueIdName();
+ len = deviceUniqueId ? strlen(deviceUniqueId) : 0;
+ if (len && aDeviceUniqueIdUTF8 && len < aDeviceUniqueIdUTF8Size) {
+ memcpy(aDeviceUniqueIdUTF8, deviceUniqueId, len);
+ }
+ }
+
+ return 0;
+}
+
+int32_t ScreenDeviceInfoImpl::DisplayCaptureSettingsDialogBox(
+ const char* aDeviceUniqueIdUTF8, const char* aDialogTitleUTF8,
+ void* aParentWindow, uint32_t aPositionX, uint32_t aPositionY) {
+ // no device properties to change
+ return 0;
+}
+
+int32_t ScreenDeviceInfoImpl::NumberOfCapabilities(
+ const char* aDeviceUniqueIdUTF8) {
+ return 0;
+}
+
+int32_t ScreenDeviceInfoImpl::GetCapability(
+ const char* aDeviceUniqueIdUTF8, uint32_t aDeviceCapabilityNumber,
+ VideoCaptureCapability& aCapability) {
+ return 0;
+}
+
+int32_t ScreenDeviceInfoImpl::GetBestMatchedCapability(
+ const char* aDeviceUniqueIdUTF8, const VideoCaptureCapability& aRequested,
+ VideoCaptureCapability& aResulting) {
+ return 0;
+}
+
+int32_t ScreenDeviceInfoImpl::GetOrientation(const char* aDeviceUniqueIdUTF8,
+ VideoRotation& aOrientation) {
+ return 0;
+}
+
+VideoCaptureModule* DesktopCaptureImpl::Create(const int32_t aModuleId,
+ const char* aUniqueId,
+ const CaptureDeviceType aType) {
+ return new rtc::RefCountedObject<DesktopCaptureImpl>(aModuleId, aUniqueId,
+ aType);
+}
+
+int32_t WindowDeviceInfoImpl::Init() {
+ mDesktopDeviceInfo =
+ std::unique_ptr<DesktopDeviceInfo>(DesktopDeviceInfo::Create());
+ return 0;
+}
+
+int32_t WindowDeviceInfoImpl::Refresh() {
+ mDesktopDeviceInfo->Refresh();
+ return 0;
+}
+
+uint32_t WindowDeviceInfoImpl::NumberOfDevices() {
+ return mDesktopDeviceInfo->getWindowCount();
+}
+
+int32_t WindowDeviceInfoImpl::GetDeviceName(
+ uint32_t aDeviceNumber, char* aDeviceNameUTF8, uint32_t aDeviceNameUTF8Size,
+ char* aDeviceUniqueIdUTF8, uint32_t aDeviceUniqueIdUTF8Size,
+ char* aProductUniqueIdUTF8, uint32_t aProductUniqueIdUTF8Size, pid_t* aPid,
+ bool* aDeviceIsPlaceholder) {
+ DesktopDisplayDevice desktopDisplayDevice;
+
+ // always initialize output
+ if (aDeviceNameUTF8 && aDeviceNameUTF8Size > 0) {
+ memset(aDeviceNameUTF8, 0, aDeviceNameUTF8Size);
+ }
+ if (aDeviceUniqueIdUTF8 && aDeviceUniqueIdUTF8Size > 0) {
+ memset(aDeviceUniqueIdUTF8, 0, aDeviceUniqueIdUTF8Size);
+ }
+ if (aProductUniqueIdUTF8 && aProductUniqueIdUTF8Size > 0) {
+ memset(aProductUniqueIdUTF8, 0, aProductUniqueIdUTF8Size);
+ }
+
+ if (mDesktopDeviceInfo->getWindowInfo(aDeviceNumber, desktopDisplayDevice) ==
+ 0) {
+ size_t len;
+
+ const char* deviceName = desktopDisplayDevice.getDeviceName();
+ len = deviceName ? strlen(deviceName) : 0;
+ if (len && aDeviceNameUTF8 && len < aDeviceNameUTF8Size) {
+ memcpy(aDeviceNameUTF8, deviceName, len);
+ }
+
+ const char* deviceUniqueId = desktopDisplayDevice.getUniqueIdName();
+ len = deviceUniqueId ? strlen(deviceUniqueId) : 0;
+ if (len && aDeviceUniqueIdUTF8 && len < aDeviceUniqueIdUTF8Size) {
+ memcpy(aDeviceUniqueIdUTF8, deviceUniqueId, len);
+ }
+ if (aPid) {
+ *aPid = desktopDisplayDevice.getPid();
+ }
+ }
+
+ return 0;
+}
+
+int32_t WindowDeviceInfoImpl::DisplayCaptureSettingsDialogBox(
+ const char* aDeviceUniqueIdUTF8, const char* aDialogTitleUTF8,
+ void* aParentWindow, uint32_t aPositionX, uint32_t aPositionY) {
+ // no device properties to change
+ return 0;
+}
+
+int32_t WindowDeviceInfoImpl::NumberOfCapabilities(
+ const char* aDeviceUniqueIdUTF8) {
+ return 0;
+}
+
+int32_t WindowDeviceInfoImpl::GetCapability(
+ const char* aDeviceUniqueIdUTF8, uint32_t aDeviceCapabilityNumber,
+ VideoCaptureCapability& aCapability) {
+ return 0;
+}
+
+int32_t WindowDeviceInfoImpl::GetBestMatchedCapability(
+ const char* aDeviceUniqueIdUTF8, const VideoCaptureCapability& aRequested,
+ VideoCaptureCapability& aResulting) {
+ return 0;
+}
+
+int32_t WindowDeviceInfoImpl::GetOrientation(const char* aDeviceUniqueIdUTF8,
+ VideoRotation& aOrientation) {
+ return 0;
+}
+
+int32_t BrowserDeviceInfoImpl::Init() {
+ mDesktopDeviceInfo =
+ std::unique_ptr<DesktopDeviceInfo>(DesktopDeviceInfo::Create());
+ return 0;
+}
+
+int32_t BrowserDeviceInfoImpl::Refresh() {
+ mDesktopDeviceInfo->Refresh();
+ return 0;
+}
+
+uint32_t BrowserDeviceInfoImpl::NumberOfDevices() {
+ return mDesktopDeviceInfo->getTabCount();
+}
+
+int32_t BrowserDeviceInfoImpl::GetDeviceName(
+ uint32_t aDeviceNumber, char* aDeviceNameUTF8, uint32_t aDeviceNameUTF8Size,
+ char* aDeviceUniqueIdUTF8, uint32_t aDeviceUniqueIdUTF8Size,
+ char* aProductUniqueIdUTF8, uint32_t aProductUniqueIdUTF8Size, pid_t* aPid,
+ bool* aDeviceIsPlaceholder) {
+ DesktopTab desktopTab;
+
+ // always initialize output
+ if (aDeviceNameUTF8 && aDeviceNameUTF8Size > 0) {
+ memset(aDeviceNameUTF8, 0, aDeviceNameUTF8Size);
+ }
+ if (aDeviceUniqueIdUTF8 && aDeviceUniqueIdUTF8Size > 0) {
+ memset(aDeviceUniqueIdUTF8, 0, aDeviceUniqueIdUTF8Size);
+ }
+ if (aProductUniqueIdUTF8 && aProductUniqueIdUTF8Size > 0) {
+ memset(aProductUniqueIdUTF8, 0, aProductUniqueIdUTF8Size);
+ }
+
+ if (mDesktopDeviceInfo->getTabInfo(aDeviceNumber, desktopTab) == 0) {
+ size_t len;
+
+ const char* deviceName = desktopTab.getTabName();
+ len = deviceName ? strlen(deviceName) : 0;
+ if (len && aDeviceNameUTF8 && len < aDeviceNameUTF8Size) {
+ memcpy(aDeviceNameUTF8, deviceName, len);
+ }
+
+ const char* deviceUniqueId = desktopTab.getUniqueIdName();
+ len = deviceUniqueId ? strlen(deviceUniqueId) : 0;
+ if (len && aDeviceUniqueIdUTF8 && len < aDeviceUniqueIdUTF8Size) {
+ memcpy(aDeviceUniqueIdUTF8, deviceUniqueId, len);
+ }
+ }
+
+ return 0;
+}
+
+int32_t BrowserDeviceInfoImpl::DisplayCaptureSettingsDialogBox(
+ const char* aDeviceUniqueIdUTF8, const char* aDialogTitleUTF8,
+ void* aParentWindow, uint32_t aPositionX, uint32_t aPositionY) {
+ // no device properties to change
+ return 0;
+}
+
+int32_t BrowserDeviceInfoImpl::NumberOfCapabilities(
+ const char* aDeviceUniqueIdUTF8) {
+ return 0;
+}
+
+int32_t BrowserDeviceInfoImpl::GetCapability(
+ const char* aDeviceUniqueIdUTF8, uint32_t aDeviceCapabilityNumber,
+ VideoCaptureCapability& aCapability) {
+ return 0;
+}
+
+int32_t BrowserDeviceInfoImpl::GetBestMatchedCapability(
+ const char* aDeviceUniqueIdUTF8, const VideoCaptureCapability& aRequested,
+ VideoCaptureCapability& aResulting) {
+ return 0;
+}
+
+int32_t BrowserDeviceInfoImpl::GetOrientation(const char* aDeviceUniqueIdUTF8,
+ VideoRotation& aOrientation) {
+ return 0;
+}
+
+std::shared_ptr<VideoCaptureModule::DeviceInfo>
+DesktopCaptureImpl::CreateDeviceInfo(const int32_t aId,
+ const CaptureDeviceType aType) {
+ if (aType == CaptureDeviceType::Screen) {
+ auto screenInfo = std::make_shared<ScreenDeviceInfoImpl>(aId);
+ if (!screenInfo || screenInfo->Init() != 0) {
+ return nullptr;
+ }
+ return screenInfo;
+ }
+ if (aType == CaptureDeviceType::Window) {
+ auto windowInfo = std::make_shared<WindowDeviceInfoImpl>(aId);
+ if (!windowInfo || windowInfo->Init() != 0) {
+ return nullptr;
+ }
+ return windowInfo;
+ }
+ if (aType == CaptureDeviceType::Browser) {
+ auto browserInfo = std::make_shared<BrowserDeviceInfoImpl>(aId);
+ if (!browserInfo || browserInfo->Init() != 0) {
+ return nullptr;
+ }
+ return browserInfo;
+ }
+ return nullptr;
+}
+
+const char* DesktopCaptureImpl::CurrentDeviceName() const {
+ return mDeviceUniqueId.c_str();
+}
+
+static DesktopCaptureOptions CreateDesktopCaptureOptions() {
+ DesktopCaptureOptions options;
+// Help avoid an X11 deadlock, see bug 1456101.
+#ifdef MOZ_X11
+ MOZ_ALWAYS_SUCCEEDS(mozilla::SyncRunnable::DispatchToThread(
+ mozilla::GetMainThreadSerialEventTarget(),
+ NS_NewRunnableFunction(__func__, [&] {
+ options = DesktopCaptureOptions::CreateDefault();
+ })));
+#else
+ options = DesktopCaptureOptions::CreateDefault();
+#endif
+
+ // Leave desktop effects enabled during WebRTC captures.
+ options.set_disable_effects(false);
+
+#if defined(WEBRTC_WIN)
+ if (mozilla::StaticPrefs::media_webrtc_capture_allow_directx()) {
+ options.set_allow_directx_capturer(true);
+ }
+ options.set_allow_cropping_window_capturer(true);
+# if defined(RTC_ENABLE_WIN_WGC)
+ if (mozilla::StaticPrefs::media_webrtc_capture_screen_allow_wgc()) {
+ options.set_allow_wgc_screen_capturer(true);
+ options.set_allow_wgc_zero_hertz(
+ mozilla::StaticPrefs::media_webrtc_capture_wgc_allow_zero_hertz());
+ }
+ if (mozilla::StaticPrefs::media_webrtc_capture_window_allow_wgc()) {
+ options.set_allow_wgc_window_capturer(true);
+ options.set_allow_wgc_zero_hertz(
+ mozilla::StaticPrefs::media_webrtc_capture_wgc_allow_zero_hertz());
+ }
+# endif
+#endif
+
+#if defined(WEBRTC_MAC)
+ if (mozilla::StaticPrefs::media_webrtc_capture_allow_iosurface()) {
+ options.set_allow_iosurface(true);
+ }
+#endif
+
+#if defined(WEBRTC_USE_PIPEWIRE)
+ if (mozilla::StaticPrefs::media_webrtc_capture_allow_pipewire()) {
+ options.set_allow_pipewire(true);
+ }
+#endif
+
+ return options;
+}
+
+static std::unique_ptr<DesktopCapturer> CreateTabCapturer(
+ const DesktopCaptureOptions& options, DesktopCapturer::SourceId aSourceId,
+ nsCOMPtr<nsISerialEventTarget> aCaptureThread) {
+ std::unique_ptr<DesktopCapturer> capturer =
+ TabCapturerWebrtc::Create(aSourceId, std::move(aCaptureThread));
+ if (capturer && options.detect_updated_region()) {
+ capturer.reset(new DesktopCapturerDifferWrapper(std::move(capturer)));
+ }
+
+ return capturer;
+}
+
+static bool UsePipewire() {
+#if defined(WEBRTC_USE_PIPEWIRE)
+ return mozilla::StaticPrefs::media_webrtc_capture_allow_pipewire() &&
+ webrtc::DesktopCapturer::IsRunningUnderWayland();
+#else
+ return false;
+#endif
+}
+
+static std::unique_ptr<DesktopCapturer> CreateDesktopCapturerAndThread(
+ CaptureDeviceType aDeviceType, DesktopCapturer::SourceId aSourceId,
+ nsIThread** aOutThread) {
+ DesktopCaptureOptions options = CreateDesktopCaptureOptions();
+ std::unique_ptr<DesktopCapturer> capturer;
+
+ auto ensureThread = [&]() {
+ if (*aOutThread) {
+ return *aOutThread;
+ }
+
+ nsIThreadManager::ThreadCreationOptions threadOptions;
+#if defined(XP_WIN) || defined(XP_MACOSX)
+ // Windows desktop capture needs a UI thread.
+ // Mac screen capture needs a thread with a CFRunLoop.
+ threadOptions.isUiThread = true;
+#endif
+ NS_NewNamedThread("DesktopCapture", aOutThread, nullptr, threadOptions);
+ return *aOutThread;
+ };
+
+ if ((aDeviceType == CaptureDeviceType::Screen ||
+ aDeviceType == CaptureDeviceType::Window) &&
+ UsePipewire()) {
+ capturer = DesktopCapturer::CreateGenericCapturer(options);
+ if (!capturer) {
+ return capturer;
+ }
+
+ capturer = std::make_unique<DesktopAndCursorComposer>(std::move(capturer),
+ options);
+ } else if (aDeviceType == CaptureDeviceType::Screen) {
+ capturer = DesktopCapturer::CreateScreenCapturer(options);
+ if (!capturer) {
+ return capturer;
+ }
+
+ capturer->SelectSource(aSourceId);
+
+ capturer = std::make_unique<DesktopAndCursorComposer>(std::move(capturer),
+ options);
+ } else if (aDeviceType == CaptureDeviceType::Window) {
+#if defined(RTC_ENABLE_WIN_WGC)
+ options.set_allow_wgc_capturer_fallback(true);
+#endif
+ capturer = DesktopCapturer::CreateWindowCapturer(options);
+ if (!capturer) {
+ return capturer;
+ }
+
+ capturer->SelectSource(aSourceId);
+
+ capturer = std::make_unique<DesktopAndCursorComposer>(std::move(capturer),
+ options);
+ } else if (aDeviceType == CaptureDeviceType::Browser) {
+ // XXX We don't capture cursors, so avoid the extra indirection layer. We
+ // could also pass null for the pMouseCursorMonitor.
+ capturer = CreateTabCapturer(options, aSourceId, ensureThread());
+ } else {
+ MOZ_ASSERT(!capturer);
+ return capturer;
+ }
+
+ MOZ_ASSERT(capturer);
+ ensureThread();
+
+ return capturer;
+}
+
+DesktopCaptureImpl::DesktopCaptureImpl(const int32_t aId, const char* aUniqueId,
+ const CaptureDeviceType aType)
+ : mModuleId(aId),
+ mTrackingId(mozilla::TrackingId(CaptureEngineToTrackingSourceStr([&] {
+ switch (aType) {
+ case CaptureDeviceType::Screen:
+ return CaptureEngine::ScreenEngine;
+ case CaptureDeviceType::Window:
+ return CaptureEngine::WinEngine;
+ case CaptureDeviceType::Browser:
+ return CaptureEngine::BrowserEngine;
+ default:
+ return CaptureEngine::InvalidEngine;
+ }
+ }()),
+ aId)),
+ mDeviceUniqueId(aUniqueId),
+ mDeviceType(aType),
+ mControlThread(mozilla::GetCurrentSerialEventTarget()),
+ mNextFrameMinimumTime(Timestamp::Zero()),
+ mCallbacks("DesktopCaptureImpl::mCallbacks") {}
+
+DesktopCaptureImpl::~DesktopCaptureImpl() {
+ MOZ_ASSERT(!mCaptureThread);
+ MOZ_ASSERT(!mRequestedCapability);
+}
+
+void DesktopCaptureImpl::RegisterCaptureDataCallback(
+ rtc::VideoSinkInterface<VideoFrame>* aDataCallback) {
+ auto callbacks = mCallbacks.Lock();
+ callbacks->insert(aDataCallback);
+}
+
+void DesktopCaptureImpl::DeRegisterCaptureDataCallback(
+ rtc::VideoSinkInterface<VideoFrame>* aDataCallback) {
+ auto callbacks = mCallbacks.Lock();
+ auto it = callbacks->find(aDataCallback);
+ if (it != callbacks->end()) {
+ callbacks->erase(it);
+ }
+}
+
+int32_t DesktopCaptureImpl::StopCaptureIfAllClientsClose() {
+ {
+ auto callbacks = mCallbacks.Lock();
+ if (!callbacks->empty()) {
+ return 0;
+ }
+ }
+ return StopCapture();
+}
+
+int32_t DesktopCaptureImpl::SetCaptureRotation(VideoRotation aRotation) {
+ MOZ_ASSERT_UNREACHABLE("Unused");
+ return -1;
+}
+
+bool DesktopCaptureImpl::SetApplyRotation(bool aEnable) { return true; }
+
+int32_t DesktopCaptureImpl::StartCapture(
+ const VideoCaptureCapability& aCapability) {
+ RTC_DCHECK_RUN_ON(&mControlThreadChecker);
+
+ if (mRequestedCapability) {
+ // Already initialized
+ MOZ_ASSERT(*mRequestedCapability == aCapability);
+
+ return 0;
+ }
+
+ MOZ_ASSERT(!mCaptureThread);
+
+ DesktopCapturer::SourceId sourceId = std::stoi(mDeviceUniqueId);
+ std::unique_ptr capturer = CreateDesktopCapturerAndThread(
+ mDeviceType, sourceId, getter_AddRefs(mCaptureThread));
+
+ MOZ_ASSERT(!capturer == !mCaptureThread);
+ if (!capturer) {
+ return -1;
+ }
+
+ mRequestedCapability = mozilla::Some(aCapability);
+ mCaptureThreadChecker.Detach();
+
+ MOZ_ALWAYS_SUCCEEDS(mCaptureThread->Dispatch(NS_NewRunnableFunction(
+ "DesktopCaptureImpl::InitOnThread",
+ [this, self = RefPtr(this), capturer = std::move(capturer),
+ maxFps = std::max(aCapability.maxFPS, 1)]() mutable {
+ InitOnThread(std::move(capturer), maxFps);
+ })));
+
+ return 0;
+}
+
+bool DesktopCaptureImpl::FocusOnSelectedSource() {
+ RTC_DCHECK_RUN_ON(&mControlThreadChecker);
+ if (!mCaptureThread) {
+ MOZ_ASSERT_UNREACHABLE(
+ "FocusOnSelectedSource must be called after StartCapture");
+ return false;
+ }
+
+ bool success = false;
+ MOZ_ALWAYS_SUCCEEDS(mozilla::SyncRunnable::DispatchToThread(
+ mCaptureThread, NS_NewRunnableFunction(__func__, [&] {
+ RTC_DCHECK_RUN_ON(&mCaptureThreadChecker);
+ MOZ_ASSERT(mCapturer);
+ success = mCapturer && mCapturer->FocusOnSelectedSource();
+ })));
+ return success;
+}
+
+int32_t DesktopCaptureImpl::StopCapture() {
+ RTC_DCHECK_RUN_ON(&mControlThreadChecker);
+ if (mRequestedCapability) {
+ // Sync-cancel the capture timer so no CaptureFrame calls will come in after
+ // we return.
+ MOZ_ALWAYS_SUCCEEDS(mozilla::SyncRunnable::DispatchToThread(
+ mCaptureThread,
+ NewRunnableMethod(__func__, this,
+ &DesktopCaptureImpl::ShutdownOnThread)));
+
+ mRequestedCapability = mozilla::Nothing();
+ }
+
+ if (mCaptureThread) {
+ // CaptureThread shutdown.
+ mCaptureThread->AsyncShutdown();
+ mCaptureThread = nullptr;
+ }
+
+ return 0;
+}
+
+bool DesktopCaptureImpl::CaptureStarted() {
+ MOZ_ASSERT_UNREACHABLE("Unused");
+ return true;
+}
+
+int32_t DesktopCaptureImpl::CaptureSettings(VideoCaptureCapability& aSettings) {
+ MOZ_ASSERT_UNREACHABLE("Unused");
+ return -1;
+}
+
+void DesktopCaptureImpl::OnCaptureResult(DesktopCapturer::Result aResult,
+ std::unique_ptr<DesktopFrame> aFrame) {
+ RTC_DCHECK_RUN_ON(&mCaptureThreadChecker);
+ if (!aFrame) {
+ return;
+ }
+
+ const auto startProcessTime = Timestamp::Micros(rtc::TimeMicros());
+ auto frameTime = startProcessTime;
+ if (auto diff = startProcessTime - mNextFrameMinimumTime;
+ diff < TimeDelta::Zero()) {
+ if (diff > TimeDelta::Millis(-1)) {
+ // Two consecutive frames within a millisecond is OK. It could happen due
+ // to timing.
+ frameTime = mNextFrameMinimumTime;
+ } else {
+ // Three consecutive frames within two milliseconds seems too much, drop
+ // one.
+ MOZ_ASSERT(diff >= TimeDelta::Millis(-2));
+ RTC_LOG(LS_WARNING) << "DesktopCapture render time is getting too far "
+ "ahead. Framerate is unexpectedly high.";
+ return;
+ }
+ }
+
+ uint8_t* videoFrame = aFrame->data();
+ VideoCaptureCapability frameInfo;
+ frameInfo.width = aFrame->size().width();
+ frameInfo.height = aFrame->size().height();
+ frameInfo.videoType = VideoType::kARGB;
+
+ size_t videoFrameLength =
+ frameInfo.width * frameInfo.height * DesktopFrame::kBytesPerPixel;
+
+ const int32_t width = frameInfo.width;
+ const int32_t height = frameInfo.height;
+
+ // Not encoded, convert to I420.
+ if (frameInfo.videoType != VideoType::kMJPEG &&
+ CalcBufferSize(frameInfo.videoType, width, abs(height)) !=
+ videoFrameLength) {
+ RTC_LOG(LS_ERROR) << "Wrong incoming frame length.";
+ return;
+ }
+
+ int stride_y = width;
+ int stride_uv = (width + 1) / 2;
+
+ // Setting absolute height (in case it was negative).
+ // In Windows, the image starts bottom left, instead of top left.
+ // Setting a negative source height, inverts the image (within LibYuv).
+
+ mozilla::PerformanceRecorder<mozilla::CopyVideoStage> rec(
+ "DesktopCaptureImpl::ConvertToI420"_ns, mTrackingId, width, abs(height));
+ // TODO(nisse): Use a pool?
+ rtc::scoped_refptr<I420Buffer> buffer =
+ I420Buffer::Create(width, abs(height), stride_y, stride_uv, stride_uv);
+
+ const int conversionResult = libyuv::ConvertToI420(
+ videoFrame, videoFrameLength, buffer->MutableDataY(), buffer->StrideY(),
+ buffer->MutableDataU(), buffer->StrideU(), buffer->MutableDataV(),
+ buffer->StrideV(), 0, 0, // No Cropping
+ aFrame->stride() / DesktopFrame::kBytesPerPixel, height, width, height,
+ libyuv::kRotate0, ConvertVideoType(frameInfo.videoType));
+ if (conversionResult != 0) {
+ RTC_LOG(LS_ERROR) << "Failed to convert capture frame from type "
+ << static_cast<int>(frameInfo.videoType) << "to I420.";
+ return;
+ }
+ rec.Record();
+
+ NotifyOnFrame(VideoFrame::Builder()
+ .set_video_frame_buffer(buffer)
+ .set_timestamp_us(frameTime.us())
+ .build());
+
+ const TimeDelta processTime =
+ Timestamp::Micros(rtc::TimeMicros()) - startProcessTime;
+
+ if (processTime > TimeDelta::Millis(10)) {
+ RTC_LOG(LS_WARNING)
+ << "Too long processing time of incoming frame with dimensions "
+ << width << "x" << height << ": " << processTime.ms() << " ms";
+ }
+}
+
+void DesktopCaptureImpl::NotifyOnFrame(const VideoFrame& aFrame) {
+ RTC_DCHECK_RUN_ON(&mCaptureThreadChecker);
+ // Set the next frame's minimum time to ensure two consecutive frames don't
+ // have an identical render time (which is in milliseconds).
+ Timestamp nextFrameMinimumTime =
+ Timestamp::Millis(aFrame.render_time_ms()) + TimeDelta::Millis(1);
+
+ MOZ_ASSERT(nextFrameMinimumTime >= mNextFrameMinimumTime);
+
+ mNextFrameMinimumTime = nextFrameMinimumTime;
+ auto callbacks = mCallbacks.Lock();
+ for (auto* cb : *callbacks) {
+ cb->OnFrame(aFrame);
+ }
+}
+
+void DesktopCaptureImpl::InitOnThread(
+ std::unique_ptr<DesktopCapturer> aCapturer, int aFramerate) {
+ RTC_DCHECK_RUN_ON(&mCaptureThreadChecker);
+
+ mCapturer = std::move(aCapturer);
+
+ // We need to call Start on the same thread we call CaptureFrame on.
+ mCapturer->Start(this);
+
+ mCaptureTimer = NS_NewTimer();
+ mRequestedCaptureInterval = mozilla::Some(
+ TimeDuration::FromSeconds(1. / static_cast<double>(aFramerate)));
+
+ CaptureFrameOnThread();
+}
+
+void DesktopCaptureImpl::ShutdownOnThread() {
+ RTC_DCHECK_RUN_ON(&mCaptureThreadChecker);
+ if (mCaptureTimer) {
+ mCaptureTimer->Cancel();
+ mCaptureTimer = nullptr;
+ }
+
+ // DesktopCapturer dtor blocks until fully shut down. TabCapturerWebrtc needs
+ // the capture thread to be alive.
+ mCapturer = nullptr;
+
+ mRequestedCaptureInterval = mozilla::Nothing();
+}
+
+void DesktopCaptureImpl::CaptureFrameOnThread() {
+ RTC_DCHECK_RUN_ON(&mCaptureThreadChecker);
+
+ auto start = mozilla::TimeStamp::Now();
+ mCapturer->CaptureFrame();
+ auto end = mozilla::TimeStamp::Now();
+
+ // Calculate next capture time.
+ const auto duration = end - start;
+ const auto timeUntilRequestedCapture = *mRequestedCaptureInterval - duration;
+
+ // Use at most x% CPU or limit framerate
+ constexpr float sleepTimeFactor =
+ (100.0f / kMaxDesktopCaptureCpuUsage) - 1.0f;
+ static_assert(sleepTimeFactor >= 0.0);
+ static_assert(sleepTimeFactor < 100.0);
+ const auto sleepTime = duration.MultDouble(sleepTimeFactor);
+
+ mCaptureTimer->InitHighResolutionWithNamedFuncCallback(
+ &::CaptureFrameOnThread, this,
+ std::max(timeUntilRequestedCapture, sleepTime), nsITimer::TYPE_ONE_SHOT,
+ "DesktopCaptureImpl::mCaptureTimer");
+}
+
+} // namespace webrtc
diff --git a/dom/media/systemservices/video_engine/desktop_capture_impl.h b/dom/media/systemservices/video_engine/desktop_capture_impl.h
new file mode 100644
index 0000000000..7292f6c8a7
--- /dev/null
+++ b/dom/media/systemservices/video_engine/desktop_capture_impl.h
@@ -0,0 +1,249 @@
+/*
+ * Copyright (c) 2014 The WebRTC project authors. All Rights Reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef WEBRTC_MODULES_DESKTOP_CAPTURE_MAIN_SOURCE_DESKTOP_CAPTURE_IMPL_H_
+#define WEBRTC_MODULES_DESKTOP_CAPTURE_MAIN_SOURCE_DESKTOP_CAPTURE_IMPL_H_
+
+/*
+ * video_capture_impl.h
+ */
+
+#include <memory>
+#include <set>
+#include <string>
+
+#include "api/sequence_checker.h"
+#include "api/video/video_frame.h"
+#include "api/video/video_sink_interface.h"
+#include "modules/desktop_capture/desktop_capturer.h"
+#include "modules/video_capture/video_capture.h"
+
+#include "desktop_device_info.h"
+#include "mozilla/DataMutex.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/TimeStamp.h"
+#include "nsCOMPtr.h"
+#include "PerformanceRecorder.h"
+
+class nsIThread;
+class nsITimer;
+
+namespace mozilla::camera {
+enum class CaptureDeviceType;
+}
+
+namespace webrtc {
+
+class VideoCaptureEncodeInterface;
+
+// simulate deviceInfo interface for video engine, bridge screen/application and
+// real screen/application device info
+
+class ScreenDeviceInfoImpl : public VideoCaptureModule::DeviceInfo {
+ public:
+ ScreenDeviceInfoImpl(int32_t aId) : mId(aId) {}
+ virtual ~ScreenDeviceInfoImpl() = default;
+
+ int32_t Init();
+ int32_t Refresh();
+
+ virtual uint32_t NumberOfDevices();
+ virtual int32_t GetDeviceName(uint32_t aDeviceNumber, char* aDeviceNameUTF8,
+ uint32_t aDeviceNameUTF8Size,
+ char* aDeviceUniqueIdUTF8,
+ uint32_t aDeviceUniqueIdUTF8Size,
+ char* aProductUniqueIdUTF8,
+ uint32_t aProductUniqueIdUTF8Size, pid_t* aPid,
+ bool* aDeviceIsPlaceholder = nullptr);
+
+ virtual int32_t DisplayCaptureSettingsDialogBox(
+ const char* aDeviceUniqueIdUTF8, const char* aDialogTitleUTF8,
+ void* aParentWindow, uint32_t aPositionX, uint32_t aPositionY);
+ virtual int32_t NumberOfCapabilities(const char* aDeviceUniqueIdUTF8);
+ virtual int32_t GetCapability(const char* aDeviceUniqueIdUTF8,
+ uint32_t aDeviceCapabilityNumber,
+ VideoCaptureCapability& aCapability);
+
+ virtual int32_t GetBestMatchedCapability(
+ const char* aDeviceUniqueIdUTF8, const VideoCaptureCapability& aRequested,
+ VideoCaptureCapability& aResulting);
+ virtual int32_t GetOrientation(const char* aDeviceUniqueIdUTF8,
+ VideoRotation& aOrientation);
+
+ protected:
+ int32_t mId;
+ std::unique_ptr<DesktopDeviceInfo> mDesktopDeviceInfo;
+};
+
+class WindowDeviceInfoImpl : public VideoCaptureModule::DeviceInfo {
+ public:
+ WindowDeviceInfoImpl(int32_t aId) : mId(aId){};
+ virtual ~WindowDeviceInfoImpl() = default;
+
+ int32_t Init();
+ int32_t Refresh();
+
+ virtual uint32_t NumberOfDevices();
+ virtual int32_t GetDeviceName(uint32_t aDeviceNumber, char* aDeviceNameUTF8,
+ uint32_t aDeviceNameUTF8Size,
+ char* aDeviceUniqueIdUTF8,
+ uint32_t aDeviceUniqueIdUTF8Size,
+ char* aProductUniqueIdUTF8,
+ uint32_t aProductUniqueIdUTF8Size, pid_t* aPid,
+ bool* aDeviceIsPlaceholder = nullptr);
+
+ virtual int32_t DisplayCaptureSettingsDialogBox(
+ const char* aDeviceUniqueIdUTF8, const char* aDialogTitleUTF8,
+ void* aParentWindow, uint32_t aPositionX, uint32_t aPositionY);
+ virtual int32_t NumberOfCapabilities(const char* aDeviceUniqueIdUTF8);
+ virtual int32_t GetCapability(const char* aDeviceUniqueIdUTF8,
+ uint32_t aDeviceCapabilityNumber,
+ VideoCaptureCapability& aCapability);
+
+ virtual int32_t GetBestMatchedCapability(
+ const char* aDeviceUniqueIdUTF8, const VideoCaptureCapability& aRequested,
+ VideoCaptureCapability& aResulting);
+ virtual int32_t GetOrientation(const char* aDeviceUniqueIdUTF8,
+ VideoRotation& aOrientation);
+
+ protected:
+ int32_t mId;
+ std::unique_ptr<DesktopDeviceInfo> mDesktopDeviceInfo;
+};
+
+class BrowserDeviceInfoImpl : public VideoCaptureModule::DeviceInfo {
+ public:
+ BrowserDeviceInfoImpl(int32_t aId) : mId(aId){};
+ virtual ~BrowserDeviceInfoImpl() = default;
+
+ int32_t Init();
+ int32_t Refresh();
+
+ virtual uint32_t NumberOfDevices();
+ virtual int32_t GetDeviceName(uint32_t aDeviceNumber, char* aDeviceNameUTF8,
+ uint32_t aDeviceNameUTF8Size,
+ char* aDeviceUniqueIdUTF8,
+ uint32_t aDeviceUniqueIdUTF8Size,
+ char* aProductUniqueIdUTF8,
+ uint32_t aProductUniqueIdUTF8Size, pid_t* aPid,
+ bool* aDeviceIsPlaceholder = nullptr);
+
+ virtual int32_t DisplayCaptureSettingsDialogBox(
+ const char* aDeviceUniqueIdUTF8, const char* aDialogTitleUTF8,
+ void* aParentWindow, uint32_t aPositionX, uint32_t aPositionY);
+ virtual int32_t NumberOfCapabilities(const char* aDeviceUniqueIdUTF8);
+ virtual int32_t GetCapability(const char* aDeviceUniqueIdUTF8,
+ uint32_t aDeviceCapabilityNumber,
+ VideoCaptureCapability& aCapability);
+
+ virtual int32_t GetBestMatchedCapability(
+ const char* aDeviceUniqueIdUTF8, const VideoCaptureCapability& aRequested,
+ VideoCaptureCapability& aResulting);
+ virtual int32_t GetOrientation(const char* aDeviceUniqueIdUTF8,
+ VideoRotation& aOrientation);
+
+ protected:
+ int32_t mId;
+ std::unique_ptr<DesktopDeviceInfo> mDesktopDeviceInfo;
+};
+
+// Reuses the video engine pipeline for screen sharing.
+// As with video, DesktopCaptureImpl is a proxy for screen sharing
+// and follows the video pipeline design
+class DesktopCaptureImpl : public DesktopCapturer::Callback,
+ public VideoCaptureModule {
+ public:
+ /* Create a screen capture modules object
+ */
+ static VideoCaptureModule* Create(
+ const int32_t aModuleId, const char* aUniqueId,
+ const mozilla::camera::CaptureDeviceType aType);
+
+ [[nodiscard]] static std::shared_ptr<VideoCaptureModule::DeviceInfo>
+ CreateDeviceInfo(const int32_t aId,
+ const mozilla::camera::CaptureDeviceType aType);
+
+ // mControlThread only.
+ void RegisterCaptureDataCallback(
+ rtc::VideoSinkInterface<VideoFrame>* aCallback) override;
+ void RegisterCaptureDataCallback(
+ RawVideoSinkInterface* dataCallback) override {}
+ void DeRegisterCaptureDataCallback(
+ rtc::VideoSinkInterface<VideoFrame>* aCallback) override;
+ int32_t StopCaptureIfAllClientsClose() override;
+
+ int32_t SetCaptureRotation(VideoRotation aRotation) override;
+ bool SetApplyRotation(bool aEnable) override;
+ bool GetApplyRotation() override { return true; }
+
+ const char* CurrentDeviceName() const override;
+
+ int32_t StartCapture(const VideoCaptureCapability& aCapability) override;
+ virtual bool FocusOnSelectedSource() override;
+ int32_t StopCapture() override;
+ bool CaptureStarted() override;
+ int32_t CaptureSettings(VideoCaptureCapability& aSettings) override;
+
+ void CaptureFrameOnThread();
+
+ const int32_t mModuleId;
+ const mozilla::TrackingId mTrackingId;
+ const std::string mDeviceUniqueId;
+ const mozilla::camera::CaptureDeviceType mDeviceType;
+
+ protected:
+ DesktopCaptureImpl(const int32_t aId, const char* aUniqueId,
+ const mozilla::camera::CaptureDeviceType aType);
+ virtual ~DesktopCaptureImpl();
+
+ private:
+ // Maximum CPU usage in %.
+ static constexpr uint32_t kMaxDesktopCaptureCpuUsage = 50;
+ void InitOnThread(std::unique_ptr<DesktopCapturer> aCapturer, int aFramerate);
+ void ShutdownOnThread();
+ // DesktopCapturer::Callback interface.
+ void OnCaptureResult(DesktopCapturer::Result aResult,
+ std::unique_ptr<DesktopFrame> aFrame) override;
+
+ // Notifies all mCallbacks of OnFrame(). mCaptureThread only.
+ void NotifyOnFrame(const VideoFrame& aFrame);
+
+ // Control thread on which the public API is called.
+ const nsCOMPtr<nsISerialEventTarget> mControlThread;
+ // Set in StartCapture.
+ mozilla::Maybe<VideoCaptureCapability> mRequestedCapability
+ RTC_GUARDED_BY(mControlThreadChecker);
+ // The DesktopCapturer is created on mControlThread but assigned and accessed
+ // only on mCaptureThread.
+ std::unique_ptr<DesktopCapturer> mCapturer
+ RTC_GUARDED_BY(mCaptureThreadChecker);
+ // Dedicated thread that does the capturing.
+ nsCOMPtr<nsIThread> mCaptureThread RTC_GUARDED_BY(mControlThreadChecker);
+ // Checks that API methods are called on mControlThread.
+ webrtc::SequenceChecker mControlThreadChecker;
+ // Checks that frame delivery only happens on mCaptureThread.
+ webrtc::SequenceChecker mCaptureThreadChecker;
+ // Timer that triggers frame captures. Only used on mCaptureThread.
+ // TODO(Bug 1806646): Drive capture with vsync instead.
+ nsCOMPtr<nsITimer> mCaptureTimer RTC_GUARDED_BY(mCaptureThreadChecker);
+ // Interval between captured frames, based on the framerate in
+ // mRequestedCapability. mCaptureThread only.
+ mozilla::Maybe<mozilla::TimeDuration> mRequestedCaptureInterval
+ RTC_GUARDED_BY(mCaptureThreadChecker);
+ // Used to make sure incoming timestamp is increasing for every frame.
+ webrtc::Timestamp mNextFrameMinimumTime RTC_GUARDED_BY(mCaptureThreadChecker);
+ // Callbacks for captured frames. Mutated on mControlThread, callbacks happen
+ // on mCaptureThread.
+ mozilla::DataMutex<std::set<rtc::VideoSinkInterface<VideoFrame>*>> mCallbacks;
+};
+
+} // namespace webrtc
+
+#endif // WEBRTC_MODULES_DESKTOP_CAPTURE_MAIN_SOURCE_DESKTOP_CAPTURE_IMPL_H_
diff --git a/dom/media/systemservices/video_engine/desktop_device_info.cc b/dom/media/systemservices/video_engine/desktop_device_info.cc
new file mode 100644
index 0000000000..185bfe6254
--- /dev/null
+++ b/dom/media/systemservices/video_engine/desktop_device_info.cc
@@ -0,0 +1,488 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "desktop_device_info.h"
+#include "modules/desktop_capture/desktop_capture_options.h"
+#include "modules/desktop_capture/desktop_capturer.h"
+#include "mozilla/Sprintf.h"
+#include "mozilla/StaticPrefs_media.h"
+#include "mozilla/SyncRunnable.h"
+#include "mozilla/UniquePtr.h"
+#include "nsIBrowserWindowTracker.h"
+#include "nsImportModule.h"
+
+#include <cstddef>
+#include <cstdlib>
+#include <cstdio>
+#include <cstring>
+#include <memory>
+
+namespace webrtc {
+
+static inline void SetStringMember(char** aMember, const char* aValue) {
+ if (!aValue) {
+ return;
+ }
+
+ if (*aMember) {
+ delete[] *aMember;
+ *aMember = nullptr;
+ }
+
+ size_t nBufLen = strlen(aValue) + 1;
+ char* buffer = new char[nBufLen];
+ memcpy(buffer, aValue, nBufLen - 1);
+ buffer[nBufLen - 1] = '\0';
+ *aMember = buffer;
+}
+
+DesktopDisplayDevice::DesktopDisplayDevice() {
+ mScreenId = kInvalidScreenId;
+ mDeviceUniqueIdUTF8 = nullptr;
+ mDeviceNameUTF8 = nullptr;
+ mPid = 0;
+}
+
+DesktopDisplayDevice::~DesktopDisplayDevice() {
+ mScreenId = kInvalidScreenId;
+
+ delete[] mDeviceUniqueIdUTF8;
+ delete[] mDeviceNameUTF8;
+
+ mDeviceUniqueIdUTF8 = nullptr;
+ mDeviceNameUTF8 = nullptr;
+}
+
+void DesktopDisplayDevice::setScreenId(const ScreenId aScreenId) {
+ mScreenId = aScreenId;
+}
+
+void DesktopDisplayDevice::setDeviceName(const char* aDeviceNameUTF8) {
+ SetStringMember(&mDeviceNameUTF8, aDeviceNameUTF8);
+}
+
+void DesktopDisplayDevice::setUniqueIdName(const char* aDeviceUniqueIdUTF8) {
+ SetStringMember(&mDeviceUniqueIdUTF8, aDeviceUniqueIdUTF8);
+}
+
+void DesktopDisplayDevice::setPid(const int aPid) { mPid = aPid; }
+
+ScreenId DesktopDisplayDevice::getScreenId() { return mScreenId; }
+
+const char* DesktopDisplayDevice::getDeviceName() { return mDeviceNameUTF8; }
+
+const char* DesktopDisplayDevice::getUniqueIdName() {
+ return mDeviceUniqueIdUTF8;
+}
+
+pid_t DesktopDisplayDevice::getPid() { return mPid; }
+
+DesktopDisplayDevice& DesktopDisplayDevice::operator=(
+ DesktopDisplayDevice& aOther) {
+ if (&aOther == this) {
+ return *this;
+ }
+ mScreenId = aOther.getScreenId();
+ setUniqueIdName(aOther.getUniqueIdName());
+ setDeviceName(aOther.getDeviceName());
+ mPid = aOther.getPid();
+
+ return *this;
+}
+
+DesktopTab::DesktopTab() {
+ mTabBrowserId = 0;
+ mTabNameUTF8 = nullptr;
+ mTabUniqueIdUTF8 = nullptr;
+ mTabCount = 0;
+}
+
+DesktopTab::~DesktopTab() {
+ delete[] mTabNameUTF8;
+ delete[] mTabUniqueIdUTF8;
+
+ mTabNameUTF8 = nullptr;
+ mTabUniqueIdUTF8 = nullptr;
+}
+
+void DesktopTab::setTabBrowserId(uint64_t aTabBrowserId) {
+ mTabBrowserId = aTabBrowserId;
+}
+
+void DesktopTab::setUniqueIdName(const char* aTabUniqueIdUTF8) {
+ SetStringMember(&mTabUniqueIdUTF8, aTabUniqueIdUTF8);
+}
+
+void DesktopTab::setTabName(const char* aTabNameUTF8) {
+ SetStringMember(&mTabNameUTF8, aTabNameUTF8);
+}
+
+void DesktopTab::setTabCount(const uint32_t aCount) { mTabCount = aCount; }
+
+uint64_t DesktopTab::getTabBrowserId() { return mTabBrowserId; }
+
+const char* DesktopTab::getUniqueIdName() { return mTabUniqueIdUTF8; }
+
+const char* DesktopTab::getTabName() { return mTabNameUTF8; }
+
+uint32_t DesktopTab::getTabCount() { return mTabCount; }
+
+DesktopTab& DesktopTab::operator=(DesktopTab& aOther) {
+ mTabBrowserId = aOther.getTabBrowserId();
+ setUniqueIdName(aOther.getUniqueIdName());
+ setTabName(aOther.getTabName());
+
+ return *this;
+}
+
+class DesktopDeviceInfoImpl : public DesktopDeviceInfo {
+ public:
+ DesktopDeviceInfoImpl();
+ ~DesktopDeviceInfoImpl();
+
+ int32_t Init() override;
+ int32_t Refresh() override;
+ int32_t getDisplayDeviceCount() override;
+ int32_t getDesktopDisplayDeviceInfo(
+ uint32_t aIndex, DesktopDisplayDevice& aDesktopDisplayDevice) override;
+ int32_t getWindowCount() override;
+ int32_t getWindowInfo(uint32_t aIndex,
+ DesktopDisplayDevice& aWindowDevice) override;
+ uint32_t getTabCount() override;
+ int32_t getTabInfo(uint32_t aIndex, DesktopTab& aDesktopTab) override;
+
+ protected:
+ DesktopDisplayDeviceList mDesktopDisplayList;
+ DesktopDisplayDeviceList mDesktopWindowList;
+ DesktopTabList mDesktopTabList;
+
+ void CleanUp();
+ void CleanUpWindowList();
+ void CleanUpTabList();
+ void CleanUpScreenList();
+
+ void InitializeWindowList();
+ virtual void InitializeTabList();
+ void InitializeScreenList();
+
+ void RefreshWindowList();
+ void RefreshTabList();
+ void RefreshScreenList();
+
+ void DummyTabList(DesktopTabList& aList);
+};
+
+DesktopDeviceInfoImpl::DesktopDeviceInfoImpl() = default;
+
+DesktopDeviceInfoImpl::~DesktopDeviceInfoImpl() { CleanUp(); }
+
+int32_t DesktopDeviceInfoImpl::getDisplayDeviceCount() {
+ return static_cast<int32_t>(mDesktopDisplayList.size());
+}
+
+int32_t DesktopDeviceInfoImpl::getDesktopDisplayDeviceInfo(
+ uint32_t aIndex, DesktopDisplayDevice& aDesktopDisplayDevice) {
+ if (aIndex >= mDesktopDisplayList.size()) {
+ return -1;
+ }
+
+ std::map<intptr_t, DesktopDisplayDevice*>::iterator iter =
+ mDesktopDisplayList.begin();
+ std::advance(iter, aIndex);
+ DesktopDisplayDevice* desktopDisplayDevice = iter->second;
+ if (desktopDisplayDevice) {
+ aDesktopDisplayDevice = (*desktopDisplayDevice);
+ }
+
+ return 0;
+}
+
+int32_t DesktopDeviceInfoImpl::getWindowCount() {
+ return static_cast<int32_t>(mDesktopWindowList.size());
+}
+
+int32_t DesktopDeviceInfoImpl::getWindowInfo(
+ uint32_t aIndex, DesktopDisplayDevice& aWindowDevice) {
+ if (aIndex >= mDesktopWindowList.size()) {
+ return -1;
+ }
+
+ std::map<intptr_t, DesktopDisplayDevice*>::iterator itr =
+ mDesktopWindowList.begin();
+ std::advance(itr, aIndex);
+ DesktopDisplayDevice* window = itr->second;
+ if (!window) {
+ return -1;
+ }
+
+ aWindowDevice = (*window);
+ return 0;
+}
+
+uint32_t DesktopDeviceInfoImpl::getTabCount() { return mDesktopTabList.size(); }
+
+int32_t DesktopDeviceInfoImpl::getTabInfo(uint32_t aIndex,
+ DesktopTab& aDesktopTab) {
+ if (aIndex >= mDesktopTabList.size()) {
+ return -1;
+ }
+
+ std::map<intptr_t, DesktopTab*>::iterator iter = mDesktopTabList.begin();
+ std::advance(iter, aIndex);
+ DesktopTab* desktopTab = iter->second;
+ if (desktopTab) {
+ aDesktopTab = (*desktopTab);
+ }
+
+ return 0;
+}
+
+void DesktopDeviceInfoImpl::CleanUp() {
+ CleanUpScreenList();
+ CleanUpWindowList();
+ CleanUpTabList();
+}
+int32_t DesktopDeviceInfoImpl::Init() {
+ InitializeScreenList();
+ InitializeWindowList();
+ InitializeTabList();
+
+ return 0;
+}
+int32_t DesktopDeviceInfoImpl::Refresh() {
+ RefreshScreenList();
+ RefreshWindowList();
+ RefreshTabList();
+
+ return 0;
+}
+
+void DesktopDeviceInfoImpl::CleanUpWindowList() {
+ std::map<intptr_t, DesktopDisplayDevice*>::iterator iterWindow;
+ for (iterWindow = mDesktopWindowList.begin();
+ iterWindow != mDesktopWindowList.end(); iterWindow++) {
+ DesktopDisplayDevice* aWindow = iterWindow->second;
+ delete aWindow;
+ iterWindow->second = nullptr;
+ }
+ mDesktopWindowList.clear();
+}
+
+void DesktopDeviceInfoImpl::InitializeWindowList() {
+ DesktopCaptureOptions options;
+
+// Wayland is special and we will not get any information about windows
+// without going through xdg-desktop-portal. We will already have
+// a screen placeholder so there is no reason to build windows list.
+#if defined(WEBRTC_USE_PIPEWIRE)
+ if (mozilla::StaticPrefs::media_webrtc_capture_allow_pipewire() &&
+ webrtc::DesktopCapturer::IsRunningUnderWayland()) {
+ return;
+ }
+#endif
+
+// Help avoid an X11 deadlock, see bug 1456101.
+#ifdef MOZ_X11
+ MOZ_ALWAYS_SUCCEEDS(mozilla::SyncRunnable::DispatchToThread(
+ mozilla::GetMainThreadSerialEventTarget(),
+ NS_NewRunnableFunction(__func__, [&] {
+ options = DesktopCaptureOptions::CreateDefault();
+ })));
+#else
+ options = DesktopCaptureOptions::CreateDefault();
+#endif
+ std::unique_ptr<DesktopCapturer> winCap =
+ DesktopCapturer::CreateWindowCapturer(options);
+ DesktopCapturer::SourceList list;
+ if (winCap && winCap->GetSourceList(&list)) {
+ DesktopCapturer::SourceList::iterator itr;
+ for (itr = list.begin(); itr != list.end(); itr++) {
+ DesktopDisplayDevice* winDevice = new DesktopDisplayDevice;
+ if (!winDevice) {
+ continue;
+ }
+
+ winDevice->setScreenId(itr->id);
+ winDevice->setDeviceName(itr->title.c_str());
+ winDevice->setPid(itr->pid);
+
+ char idStr[BUFSIZ];
+#if WEBRTC_WIN
+ _snprintf_s(idStr, sizeof(idStr), sizeof(idStr) - 1, "%ld",
+ static_cast<long>(winDevice->getScreenId()));
+#else
+ SprintfLiteral(idStr, "%ld", static_cast<long>(winDevice->getScreenId()));
+#endif
+ winDevice->setUniqueIdName(idStr);
+ mDesktopWindowList[winDevice->getScreenId()] = winDevice;
+ }
+ }
+}
+
+void DesktopDeviceInfoImpl::RefreshWindowList() {
+ CleanUpWindowList();
+ InitializeWindowList();
+}
+
+void DesktopDeviceInfoImpl::CleanUpTabList() {
+ for (auto& iterTab : mDesktopTabList) {
+ DesktopTab* desktopTab = iterTab.second;
+ delete desktopTab;
+ iterTab.second = nullptr;
+ }
+ mDesktopTabList.clear();
+}
+
+void webrtc::DesktopDeviceInfoImpl::InitializeTabList() {
+ if (!mozilla::StaticPrefs::media_getusermedia_browser_enabled()) {
+ return;
+ }
+
+ // This is a sync dispatch to main thread, which is unfortunate. To
+ // call JavaScript we have to be on main thread, but the remaining
+ // DesktopCapturer very much wants to be off main thread. This might
+ // be solvable by calling this method earlier on while we're still on
+ // main thread and plumbing the information down to here.
+ nsCOMPtr<nsIRunnable> runnable = NS_NewRunnableFunction(__func__, [&] {
+ nsresult rv;
+ nsCOMPtr<nsIBrowserWindowTracker> bwt =
+ do_ImportESModule("resource:///modules/BrowserWindowTracker.sys.mjs",
+ "BrowserWindowTracker", &rv);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ nsTArray<RefPtr<nsIVisibleTab>> tabArray;
+ rv = bwt->GetAllVisibleTabs(tabArray);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ for (const auto& browserTab : tabArray) {
+ nsString contentTitle;
+ browserTab->GetContentTitle(contentTitle);
+ int64_t browserId;
+ browserTab->GetBrowserId(&browserId);
+
+ DesktopTab* desktopTab = new DesktopTab;
+ if (desktopTab) {
+ char* contentTitleUTF8 = ToNewUTF8String(contentTitle);
+ desktopTab->setTabBrowserId(browserId);
+ desktopTab->setTabName(contentTitleUTF8);
+ std::ostringstream uniqueId;
+ uniqueId << browserId;
+ desktopTab->setUniqueIdName(uniqueId.str().c_str());
+ mDesktopTabList[static_cast<intptr_t>(desktopTab->getTabBrowserId())] =
+ desktopTab;
+ free(contentTitleUTF8);
+ }
+ }
+ });
+ mozilla::SyncRunnable::DispatchToThread(
+ mozilla::GetMainThreadSerialEventTarget(), runnable);
+}
+
+void DesktopDeviceInfoImpl::RefreshTabList() {
+ CleanUpTabList();
+ InitializeTabList();
+}
+
+void DesktopDeviceInfoImpl::CleanUpScreenList() {
+ std::map<intptr_t, DesktopDisplayDevice*>::iterator iterDevice;
+ for (iterDevice = mDesktopDisplayList.begin();
+ iterDevice != mDesktopDisplayList.end(); iterDevice++) {
+ DesktopDisplayDevice* desktopDisplayDevice = iterDevice->second;
+ delete desktopDisplayDevice;
+ iterDevice->second = nullptr;
+ }
+ mDesktopDisplayList.clear();
+}
+
+// With PipeWire we can't select which system resource is shared so
+// we don't create a window/screen list. Instead we place these constants
+// as window name/id so frontend code can identify PipeWire backend
+// and does not try to create screen/window preview.
+
+#define PIPEWIRE_ID 0xaffffff
+#define PIPEWIRE_NAME "####_PIPEWIRE_PORTAL_####"
+
+void DesktopDeviceInfoImpl::InitializeScreenList() {
+ DesktopCaptureOptions options;
+
+// Wayland is special and we will not get any information about screens
+// without going through xdg-desktop-portal so we just need a screen
+// placeholder.
+#if defined(WEBRTC_USE_PIPEWIRE)
+ if (mozilla::StaticPrefs::media_webrtc_capture_allow_pipewire() &&
+ webrtc::DesktopCapturer::IsRunningUnderWayland()) {
+ DesktopDisplayDevice* screenDevice = new DesktopDisplayDevice;
+ if (!screenDevice) {
+ return;
+ }
+
+ screenDevice->setScreenId(PIPEWIRE_ID);
+ screenDevice->setDeviceName(PIPEWIRE_NAME);
+
+ char idStr[BUFSIZ];
+ SprintfLiteral(idStr, "%ld",
+ static_cast<long>(screenDevice->getScreenId()));
+ screenDevice->setUniqueIdName(idStr);
+ mDesktopDisplayList[screenDevice->getScreenId()] = screenDevice;
+ return;
+ }
+#endif
+
+// Help avoid an X11 deadlock, see bug 1456101.
+#ifdef MOZ_X11
+ MOZ_ALWAYS_SUCCEEDS(mozilla::SyncRunnable::DispatchToThread(
+ mozilla::GetMainThreadSerialEventTarget(),
+ NS_NewRunnableFunction(__func__, [&] {
+ options = DesktopCaptureOptions::CreateDefault();
+ })));
+#else
+ options = DesktopCaptureOptions::CreateDefault();
+#endif
+ std::unique_ptr<DesktopCapturer> screenCapturer =
+ DesktopCapturer::CreateScreenCapturer(options);
+ DesktopCapturer::SourceList list;
+ if (screenCapturer && screenCapturer->GetSourceList(&list)) {
+ DesktopCapturer::SourceList::iterator itr;
+ for (itr = list.begin(); itr != list.end(); itr++) {
+ DesktopDisplayDevice* screenDevice = new DesktopDisplayDevice;
+ screenDevice->setScreenId(itr->id);
+ if (list.size() == 1) {
+ screenDevice->setDeviceName("Primary Monitor");
+ } else {
+ screenDevice->setDeviceName(itr->title.c_str());
+ }
+ screenDevice->setPid(itr->pid);
+
+ char idStr[BUFSIZ];
+#if WEBRTC_WIN
+ _snprintf_s(idStr, sizeof(idStr), sizeof(idStr) - 1, "%ld",
+ static_cast<long>(screenDevice->getScreenId()));
+#else
+ SprintfLiteral(idStr, "%ld",
+ static_cast<long>(screenDevice->getScreenId()));
+#endif
+ screenDevice->setUniqueIdName(idStr);
+ mDesktopDisplayList[screenDevice->getScreenId()] = screenDevice;
+ }
+ }
+}
+
+void DesktopDeviceInfoImpl::RefreshScreenList() {
+ CleanUpScreenList();
+ InitializeScreenList();
+}
+
+/* static */
+DesktopDeviceInfo* DesktopDeviceInfo::Create() {
+ auto info = mozilla::MakeUnique<DesktopDeviceInfoImpl>();
+ if (info->Init() != 0) {
+ return nullptr;
+ }
+ return info.release();
+}
+} // namespace webrtc
diff --git a/dom/media/systemservices/video_engine/desktop_device_info.h b/dom/media/systemservices/video_engine/desktop_device_info.h
new file mode 100644
index 0000000000..824792b3c0
--- /dev/null
+++ b/dom/media/systemservices/video_engine/desktop_device_info.h
@@ -0,0 +1,84 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef WEBRTC_MODULES_DESKTOP_CAPTURE_DEVICE_INFO_H_
+#define WEBRTC_MODULES_DESKTOP_CAPTURE_DEVICE_INFO_H_
+
+#include <map>
+#include "modules/desktop_capture/desktop_capture_types.h"
+
+namespace webrtc {
+
+class DesktopDisplayDevice {
+ public:
+ DesktopDisplayDevice();
+ ~DesktopDisplayDevice();
+
+ void setScreenId(const ScreenId aScreenId);
+ void setDeviceName(const char* aDeviceNameUTF8);
+ void setUniqueIdName(const char* aDeviceUniqueIdUTF8);
+ void setPid(pid_t aPid);
+
+ ScreenId getScreenId();
+ const char* getDeviceName();
+ const char* getUniqueIdName();
+ pid_t getPid();
+
+ DesktopDisplayDevice& operator=(DesktopDisplayDevice& aOther);
+
+ protected:
+ ScreenId mScreenId;
+ char* mDeviceNameUTF8;
+ char* mDeviceUniqueIdUTF8;
+ pid_t mPid;
+};
+
+using DesktopDisplayDeviceList = std::map<intptr_t, DesktopDisplayDevice*>;
+
+class DesktopTab {
+ public:
+ DesktopTab();
+ ~DesktopTab();
+
+ void setTabBrowserId(uint64_t aTabBrowserId);
+ void setUniqueIdName(const char* aTabUniqueIdUTF8);
+ void setTabName(const char* aTabNameUTF8);
+ void setTabCount(const uint32_t aCount);
+
+ uint64_t getTabBrowserId();
+ const char* getUniqueIdName();
+ const char* getTabName();
+ uint32_t getTabCount();
+
+ DesktopTab& operator=(DesktopTab& aOther);
+
+ protected:
+ uint64_t mTabBrowserId;
+ char* mTabNameUTF8;
+ char* mTabUniqueIdUTF8;
+ uint32_t mTabCount;
+};
+
+using DesktopTabList = std::map<intptr_t, DesktopTab*>;
+
+class DesktopDeviceInfo {
+ public:
+ virtual ~DesktopDeviceInfo() = default;
+
+ virtual int32_t Init() = 0;
+ virtual int32_t Refresh() = 0;
+ virtual int32_t getDisplayDeviceCount() = 0;
+ virtual int32_t getDesktopDisplayDeviceInfo(
+ uint32_t aIndex, DesktopDisplayDevice& aDesktopDisplayDevice) = 0;
+ virtual int32_t getWindowCount() = 0;
+ virtual int32_t getWindowInfo(uint32_t aIndex,
+ DesktopDisplayDevice& aWindowDevice) = 0;
+ virtual uint32_t getTabCount() = 0;
+ virtual int32_t getTabInfo(uint32_t aIndex, DesktopTab& aDesktopTab) = 0;
+
+ static DesktopDeviceInfo* Create();
+};
+}; // namespace webrtc
+
+#endif
diff --git a/dom/media/systemservices/video_engine/placeholder_device_info.cc b/dom/media/systemservices/video_engine/placeholder_device_info.cc
new file mode 100644
index 0000000000..62496b1b93
--- /dev/null
+++ b/dom/media/systemservices/video_engine/placeholder_device_info.cc
@@ -0,0 +1,60 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et ft=cpp : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "placeholder_device_info.h"
+#include "modules/video_capture/video_capture_factory.h"
+
+namespace mozilla {
+
+PlaceholderDeviceInfo::PlaceholderDeviceInfo(bool aCameraPresent)
+ : mCameraPresent(aCameraPresent) {}
+
+PlaceholderDeviceInfo::~PlaceholderDeviceInfo() = default;
+
+uint32_t PlaceholderDeviceInfo::NumberOfDevices() { return mCameraPresent; }
+
+int32_t PlaceholderDeviceInfo::Init() { return 0; }
+
+int32_t PlaceholderDeviceInfo::GetDeviceName(
+ uint32_t aDeviceNumber, char* aDeviceNameUTF8, uint32_t aDeviceNameLength,
+ char* aDeviceUniqueIdUTF8, uint32_t aDeviceUniqueIdUTF8Length,
+ char* aProductUniqueIdUTF8, uint32_t aProductUniqueIdUTF8Length,
+ pid_t* aPid, bool* aDeviceIsPlaceholder) {
+ // Check whether there is camera device reported by the Camera portal
+ // When the promise is resolved, it means there is a camera available
+ // but we have to use a placeholder device.
+ if (!mCameraPresent) {
+ return -1;
+ }
+
+ // Making these empty to follow the specs for non-legacy enumeration:
+ // https://w3c.github.io/mediacapture-main/#access-control-model
+ memset(aDeviceNameUTF8, 0, aDeviceNameLength);
+ memset(aDeviceUniqueIdUTF8, 0, aDeviceUniqueIdUTF8Length);
+
+ if (aProductUniqueIdUTF8) {
+ memset(aProductUniqueIdUTF8, 0, aProductUniqueIdUTF8Length);
+ }
+
+ if (aDeviceIsPlaceholder) {
+ *aDeviceIsPlaceholder = true;
+ }
+
+ return 0;
+}
+
+int32_t PlaceholderDeviceInfo::CreateCapabilityMap(
+ const char* /*aDeviceUniqueIdUTF8*/) {
+ return -1;
+}
+
+int32_t PlaceholderDeviceInfo::DisplayCaptureSettingsDialogBox(
+ const char* /*deviceUniqueIdUTF8*/, const char* /*dialogTitleUTF8*/,
+ void* /*parentWindow*/, uint32_t /*positionX*/, uint32_t /*positionY*/) {
+ return -1;
+}
+
+} // namespace mozilla
diff --git a/dom/media/systemservices/video_engine/placeholder_device_info.h b/dom/media/systemservices/video_engine/placeholder_device_info.h
new file mode 100644
index 0000000000..5632c1f0cd
--- /dev/null
+++ b/dom/media/systemservices/video_engine/placeholder_device_info.h
@@ -0,0 +1,45 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#ifndef DOM_MEDIA_SYSTEMSERVICES_VIDEO_ENGINE_PLACEHOLDER_DEVICE_INFO_H_
+#define DOM_MEDIA_SYSTEMSERVICES_VIDEO_ENGINE_PLACEHOLDER_DEVICE_INFO_H_
+
+#include "modules/video_capture/device_info_impl.h"
+#include "modules/video_capture/video_capture.h"
+#include "modules/video_capture/video_capture_impl.h"
+
+namespace mozilla {
+
+class PlaceholderDeviceInfo
+ : public webrtc::videocapturemodule::DeviceInfoImpl {
+ public:
+ explicit PlaceholderDeviceInfo(bool aCameraPresent);
+ ~PlaceholderDeviceInfo() override;
+
+ uint32_t NumberOfDevices() override;
+ int32_t GetDeviceName(uint32_t aDeviceNumber, char* aDeviceNameUTF8,
+ uint32_t aDeviceNameLength, char* aDeviceUniqueIdUTF8,
+ uint32_t aDeviceUniqueIdUTF8Length,
+ char* aProductUniqueIdUTF8 = nullptr,
+ uint32_t aProductUniqueIdUTF8Length = 0,
+ pid_t* aPid = nullptr,
+ bool* aDeviceIsPlaceholder = nullptr) override;
+
+ int32_t CreateCapabilityMap(const char* aDeviceUniqueIdUTF8) override;
+ int32_t DisplayCaptureSettingsDialogBox(const char* aDeviceUniqueIdUTF8,
+ const char* aDialogTitleUTF8,
+ void* aParentWindow,
+ uint32_t aPositionX,
+ uint32_t aPositionY) override;
+ int32_t Init() override;
+
+ private:
+ const bool mCameraPresent;
+};
+
+} // namespace mozilla
+
+#endif // DOM_MEDIA_SYSTEMSERVICES_VIDEO_ENGINE_PLACEHOLDER_DEVICE_INFO_H_
diff --git a/dom/media/systemservices/video_engine/platform_uithread.cc b/dom/media/systemservices/video_engine/platform_uithread.cc
new file mode 100644
index 0000000000..701a989a18
--- /dev/null
+++ b/dom/media/systemservices/video_engine/platform_uithread.cc
@@ -0,0 +1,198 @@
+/*
+ * Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#if defined(WEBRTC_WIN)
+
+# include "platform_uithread.h"
+
+namespace rtc {
+
+// timer id used in delayed callbacks
+static const UINT_PTR kTimerId = 1;
+static const wchar_t kThisProperty[] = L"ThreadWindowsUIPtr";
+static const wchar_t kThreadWindow[] = L"WebrtcWindowsUIThread";
+
+PlatformUIThread::~PlatformUIThread() {
+ CritScope scoped_lock(&cs_);
+ switch (state_) {
+ case State::STARTED: {
+ MOZ_DIAGNOSTIC_ASSERT(
+ false, "PlatformUIThread must be stopped before destruction");
+ break;
+ }
+ case State::STOPPED:
+ break;
+ case State::UNSTARTED:
+ break;
+ }
+}
+
+bool PlatformUIThread::InternalInit() {
+ // Create an event window for use in generating callbacks to capture
+ // objects.
+ CritScope scoped_lock(&cs_);
+ switch (state_) {
+ // We have already started there is nothing todo. Should this be assert?
+ case State::STARTED:
+ break;
+ // Stop() has already been called so there is likewise nothing to do.
+ case State::STOPPED:
+ break;
+ // Stop() has not been called yet, setup the UI thread, and set our
+ // state to STARTED.
+ case State::UNSTARTED: {
+ WNDCLASSW wc;
+ HMODULE hModule = GetModuleHandle(NULL);
+ if (!GetClassInfoW(hModule, kThreadWindow, &wc)) {
+ ZeroMemory(&wc, sizeof(WNDCLASSW));
+ wc.hInstance = hModule;
+ wc.lpfnWndProc = EventWindowProc;
+ wc.lpszClassName = kThreadWindow;
+ RegisterClassW(&wc);
+ }
+ hwnd_ = CreateWindowW(kThreadWindow, L"", 0, 0, 0, 0, 0, NULL, NULL,
+ hModule, NULL);
+ // Added in review of bug 1760843, follow up to remove 1767861
+ MOZ_RELEASE_ASSERT(hwnd_);
+ // Expected to always work but if it doesn't we should still fulfill the
+ // contract of always running the process loop at least a single
+ // iteration.
+ // This could be rexamined in the future.
+ if (hwnd_) {
+ SetPropW(hwnd_, kThisProperty, this);
+ // state_ needs to be STARTED before we request the initial timer
+ state_ = State::STARTED;
+ if (timeout_) {
+ // if someone set the timer before we started
+ RequestCallbackTimer(timeout_);
+ }
+ }
+ break;
+ }
+ };
+ return state_ == State::STARTED;
+}
+
+bool PlatformUIThread::RequestCallbackTimer(unsigned int milliseconds) {
+ CritScope scoped_lock(&cs_);
+
+ switch (state_) {
+ // InternalInit() has yet to run so we do not have a UI thread to use as a
+ // target of the timer. We should just remember what timer interval was
+ // requested and let InternalInit() call this function again when it is
+ // ready.
+ case State::UNSTARTED: {
+ timeout_ = milliseconds;
+ return false;
+ }
+ // We have already stopped, do not schedule a new timer.
+ case State::STOPPED:
+ return false;
+ case State::STARTED: {
+ if (timerid_) {
+ KillTimer(hwnd_, timerid_);
+ }
+ timeout_ = milliseconds;
+ timerid_ = SetTimer(hwnd_, kTimerId, milliseconds, NULL);
+ return !!timerid_;
+ }
+ }
+ // UNREACHABLE
+}
+
+void PlatformUIThread::Stop() {
+ {
+ RTC_DCHECK_RUN_ON(&thread_checker_);
+ CritScope scoped_lock(&cs_);
+ // Shut down the dispatch loop and let the background thread exit.
+ if (timerid_) {
+ MOZ_ASSERT(hwnd_);
+ KillTimer(hwnd_, timerid_);
+ timerid_ = 0;
+ }
+ switch (state_) {
+ // If we haven't started yet there is nothing to do, we will go into
+ // the STOPPED state at the end of the function and InternalInit()
+ // will not move us to STARTED.
+ case State::UNSTARTED:
+ break;
+ // If we have started, that means that InternalInit() has run and the
+ // message wait loop has or will run. We need to signal it to stop. wich
+ // will allow PlatformThread::Stop to join that thread.
+ case State::STARTED: {
+ MOZ_ASSERT(hwnd_);
+ PostMessage(hwnd_, WM_CLOSE, 0, 0);
+ break;
+ }
+ // We have already stopped. There is nothing to do.
+ case State::STOPPED:
+ break;
+ }
+ // Always set our state to STOPPED
+ state_ = State::STOPPED;
+ }
+ monitor_thread_.Finalize();
+}
+
+void PlatformUIThread::Run() {
+ // InternalInit() will return false when the thread is already in shutdown.
+ // otherwise we must run until we get a Windows WM_QUIT msg.
+ const bool runUntilQuitMsg = InternalInit();
+ // The interface contract of Start/Stop is that for a successful call to
+ // Start, there should be at least one call to the run function.
+ NativeEventCallback();
+ while (runUntilQuitMsg) {
+ // Alertable sleep to receive WM_QUIT (following a WM_CLOSE triggering a
+ // WM_DESTROY)
+ if (MsgWaitForMultipleObjectsEx(0, nullptr, INFINITE, QS_ALLINPUT,
+ MWMO_ALERTABLE | MWMO_INPUTAVAILABLE) ==
+ WAIT_OBJECT_0) {
+ MSG msg;
+ if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
+ if (msg.message == WM_QUIT) {
+ // THE ONLY WAY to exit the thread loop
+ break;
+ }
+ TranslateMessage(&msg);
+ DispatchMessage(&msg);
+ }
+ }
+ }
+}
+
+void PlatformUIThread::NativeEventCallback() { native_event_callback_(); }
+
+/* static */
+LRESULT CALLBACK PlatformUIThread::EventWindowProc(HWND hwnd, UINT uMsg,
+ WPARAM wParam,
+ LPARAM lParam) {
+ if (uMsg == WM_DESTROY) {
+ RemovePropW(hwnd, kThisProperty);
+ PostQuitMessage(0);
+ return 0;
+ }
+
+ PlatformUIThread* twui =
+ static_cast<PlatformUIThread*>(GetPropW(hwnd, kThisProperty));
+ if (!twui) {
+ return DefWindowProc(hwnd, uMsg, wParam, lParam);
+ }
+
+ if (uMsg == WM_TIMER && wParam == kTimerId) {
+ twui->NativeEventCallback();
+ return 0;
+ }
+
+ return DefWindowProc(hwnd, uMsg, wParam, lParam);
+}
+
+} // namespace rtc
+
+#endif
diff --git a/dom/media/systemservices/video_engine/platform_uithread.h b/dom/media/systemservices/video_engine/platform_uithread.h
new file mode 100644
index 0000000000..9c213ca933
--- /dev/null
+++ b/dom/media/systemservices/video_engine/platform_uithread.h
@@ -0,0 +1,96 @@
+/*
+ * Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef RTC_BASE_PLATFORM_UITHREAD_H_
+#define RTC_BASE_PLATFORM_UITHREAD_H_
+
+#if defined(WEBRTC_WIN)
+# include "Assertions.h"
+# include "rtc_base/deprecated/recursive_critical_section.h"
+# include "rtc_base/platform_thread.h"
+# include "api/sequence_checker.h"
+# include "ThreadSafety.h"
+
+namespace rtc {
+/*
+ * Windows UI thread for screen capture
+ * Launches a thread which enters a message wait loop after calling the
+ * provided ThreadRunFunction once. A repeating timer event might be registered
+ * with a callback through the Win32 API. If so, that timer will cause WM_TIMER
+ * messages to appear in the threads message queue. This will wake the thread
+ * which will then first look to see if it received the WM_QUIT message, then
+ * it will pass any non WM_QUIT messages on to the registered message handlers
+ * (synchronously on the current thread). In the case oF WM_TIMER the
+ * registered handler calls the NativeEventCallback which is simply the
+ * ThreadRunFunction which was passed to the constructor.
+ *
+ * Shutdown of the message wait loop is triggered by sending a WM_CLOSE which
+ * will start tearing down the "window" which hosts the UI thread. This will
+ * cause a WM_DESTROY message to be received. Upon reception a WM_QUIT message
+ * is enqueued. When the message wait loop receives a WM_QUIT message it stops,
+ * thus allowing the thread to be joined.
+ *
+ * Note: that the only source of a WM_CLOSE should be PlatformUIThread::Stop.
+ * Note: because PlatformUIThread::Stop is called from a different thread than
+ * PlatformUIThread::Run, it is possible that Stop can race Run.
+ *
+ * After being stopped PlatformUIThread can not be started again.
+ *
+ */
+
+class PlatformUIThread {
+ public:
+ PlatformUIThread(std::function<void()> func, const char* name,
+ ThreadAttributes attributes)
+ : name_(name),
+ native_event_callback_(std::move(func)),
+ monitor_thread_(PlatformThread::SpawnJoinable([this]() { Run(); }, name,
+ attributes)) {}
+
+ virtual ~PlatformUIThread();
+
+ void Stop();
+
+ /**
+ * Request a recurring callback.
+ */
+ bool RequestCallbackTimer(unsigned int milliseconds);
+
+ protected:
+ void Run();
+
+ private:
+ static LRESULT CALLBACK EventWindowProc(HWND, UINT, WPARAM, LPARAM);
+ void NativeEventCallback();
+ // Initialize the UI thread that is servicing the timer events
+ bool InternalInit();
+
+ // Needs to be initialized before monitor_thread_ as it takes a string view to
+ // name_
+ std::string name_;
+ RecursiveCriticalSection cs_;
+ std::function<void()> native_event_callback_;
+ webrtc::SequenceChecker thread_checker_;
+ PlatformThread monitor_thread_;
+ HWND hwnd_ MOZ_GUARDED_BY(cs_) = nullptr;
+ UINT_PTR timerid_ MOZ_GUARDED_BY(cs_) = 0;
+ unsigned int timeout_ MOZ_GUARDED_BY(cs_) = 0;
+ enum class State {
+ UNSTARTED,
+ STARTED,
+ STOPPED,
+ };
+ State state_ MOZ_GUARDED_BY(cs_) = State::UNSTARTED;
+};
+
+} // namespace rtc
+
+#endif
+#endif // RTC_BASE_PLATFORM_UITHREAD_H_
diff --git a/dom/media/systemservices/video_engine/tab_capturer.cc b/dom/media/systemservices/video_engine/tab_capturer.cc
new file mode 100644
index 0000000000..793d965028
--- /dev/null
+++ b/dom/media/systemservices/video_engine/tab_capturer.cc
@@ -0,0 +1,331 @@
+/*
+ * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "tab_capturer.h"
+
+#include "desktop_device_info.h"
+#include "modules/desktop_capture/desktop_capture_options.h"
+#include "modules/desktop_capture/desktop_frame.h"
+#include "mozilla/Logging.h"
+#include "mozilla/dom/BrowsingContext.h"
+#include "mozilla/dom/ImageBitmap.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/PromiseNativeHandler.h"
+#include "mozilla/dom/WindowGlobalParent.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/SpinEventLoopUntil.h"
+#include "mozilla/TaskQueue.h"
+#include "nsThreadUtils.h"
+#include "rtc_base/checks.h"
+#include "rtc_base/logging.h"
+
+mozilla::LazyLogModule gTabShareLog("TabShare");
+#define LOG_FUNC_IMPL(level) \
+ MOZ_LOG( \
+ gTabShareLog, level, \
+ ("TabCapturerWebrtc %p: %s id=%" PRIu64, this, __func__, mBrowserId))
+#define LOG_FUNC() LOG_FUNC_IMPL(LogLevel::Debug)
+#define LOG_FUNCV() LOG_FUNC_IMPL(LogLevel::Verbose)
+
+using namespace mozilla::dom;
+
+namespace mozilla {
+
+class CaptureFrameRequest {
+ using CapturePromise = TabCapturerWebrtc::CapturePromise;
+
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(CaptureFrameRequest)
+
+ CaptureFrameRequest() : mCaptureTime(TimeStamp::Now()) {}
+
+ operator MozPromiseRequestHolder<CapturePromise>&() { return mRequest; }
+
+ void Complete() { mRequest.Complete(); }
+ void Disconnect() { mRequest.Disconnect(); }
+ bool Exists() { return mRequest.Exists(); }
+
+ protected:
+ virtual ~CaptureFrameRequest() { MOZ_RELEASE_ASSERT(!Exists()); }
+
+ public:
+ const TimeStamp mCaptureTime;
+
+ private:
+ MozPromiseRequestHolder<CapturePromise> mRequest;
+};
+
+TabCapturerWebrtc::TabCapturerWebrtc(
+ SourceId aSourceId, nsCOMPtr<nsISerialEventTarget> aCaptureThread)
+ : mBrowserId(aSourceId),
+ mMainThreadWorker(
+ TaskQueue::Create(do_AddRef(GetMainThreadSerialEventTarget()),
+ "TabCapturerWebrtc::mMainThreadWorker")),
+ mCallbackWorker(TaskQueue::Create(aCaptureThread.forget(),
+ "TabCapturerWebrtc::mCallbackWorker")) {
+ RTC_DCHECK_RUN_ON(&mControlChecker);
+ MOZ_ASSERT(aSourceId != 0);
+ mCallbackChecker.Detach();
+
+ LOG_FUNC();
+}
+
+// static
+std::unique_ptr<webrtc::DesktopCapturer> TabCapturerWebrtc::Create(
+ SourceId aSourceId, nsCOMPtr<nsISerialEventTarget> aCaptureThread) {
+ return std::unique_ptr<webrtc::DesktopCapturer>(
+ new TabCapturerWebrtc(aSourceId, std::move(aCaptureThread)));
+}
+
+TabCapturerWebrtc::~TabCapturerWebrtc() {
+ RTC_DCHECK_RUN_ON(&mCallbackChecker);
+ LOG_FUNC();
+
+ // mMainThreadWorker handles frame capture requests async. Since we're in the
+ // dtor, no more frame capture requests can be made through CaptureFrame(). It
+ // can be shut down now.
+ mMainThreadWorker->BeginShutdown();
+
+ // There may still be async frame capture requests in flight, waiting to be
+ // reported to mCallback on mCallbackWorker. Disconnect them (must be done on
+ // mCallbackWorker) and shut down mCallbackWorker to ensure nothing more can
+ // get queued to it.
+ MOZ_ALWAYS_SUCCEEDS(
+ mCallbackWorker->Dispatch(NS_NewRunnableFunction(__func__, [this] {
+ RTC_DCHECK_RUN_ON(&mCallbackChecker);
+ for (const auto& req : mRequests) {
+ DisconnectRequest(req);
+ }
+ mCallbackWorker->BeginShutdown();
+ })));
+
+ // Block until the workers have run all pending tasks. We must do this for two
+ // reasons:
+ // - All runnables dispatched to mMainThreadWorker and mCallbackWorker capture
+ // the raw pointer `this` as they rely on `this` outliving the worker
+ // TaskQueues.
+ // - mCallback is only guaranteed to outlive `this`. No calls can be made to
+ // it after the dtor is finished.
+
+ // Spin the underlying thread of mCallbackWorker, which we are currently on,
+ // until it is empty. We have no other way of waiting for mCallbackWorker to
+ // become empty while blocking the current call.
+ SpinEventLoopUntil<ProcessFailureBehavior::IgnoreAndContinue>(
+ "~TabCapturerWebrtc"_ns, [&] { return mCallbackWorker->IsEmpty(); });
+
+ // No need to await shutdown since it was shut down synchronously above.
+ mMainThreadWorker->AwaitIdle();
+}
+
+bool TabCapturerWebrtc::GetSourceList(
+ webrtc::DesktopCapturer::SourceList* aSources) {
+ MOZ_LOG(gTabShareLog, LogLevel::Debug,
+ ("TabShare: GetSourceList, result %zu", aSources->size()));
+ // XXX UI
+ return true;
+}
+
+bool TabCapturerWebrtc::SelectSource(webrtc::DesktopCapturer::SourceId) {
+ MOZ_ASSERT_UNREACHABLE("Source is passed through ctor for constness");
+ return true;
+}
+
+bool TabCapturerWebrtc::FocusOnSelectedSource() { return true; }
+
+void TabCapturerWebrtc::Start(webrtc::DesktopCapturer::Callback* aCallback) {
+ RTC_DCHECK_RUN_ON(&mCallbackChecker);
+ RTC_DCHECK(!mCallback);
+ RTC_DCHECK(aCallback);
+
+ LOG_FUNC();
+
+ mCallback = aCallback;
+}
+
+void TabCapturerWebrtc::CaptureFrame() {
+ RTC_DCHECK_RUN_ON(&mCallbackChecker);
+ LOG_FUNCV();
+ if (mRequests.GetSize() > 2) {
+ // Allow two async capture requests in flight
+ OnCaptureFrameFailure();
+ return;
+ }
+
+ auto request = MakeRefPtr<CaptureFrameRequest>();
+ InvokeAsync(mMainThreadWorker, __func__, [this] { return CaptureFrameNow(); })
+ ->Then(mCallbackWorker, __func__,
+ [this, request](CapturePromise::ResolveOrRejectValue&& aValue) {
+ if (!CompleteRequest(request)) {
+ // Request was disconnected or overrun. Failure has already
+ // been reported to the callback elsewhere.
+ return;
+ }
+
+ if (aValue.IsReject()) {
+ OnCaptureFrameFailure();
+ return;
+ }
+
+ OnCaptureFrameSuccess(std::move(aValue.ResolveValue()));
+ })
+ ->Track(*request);
+ mRequests.PushFront(request.forget());
+}
+
+void TabCapturerWebrtc::OnCaptureFrameSuccess(
+ UniquePtr<dom::ImageBitmapCloneData> aData) {
+ RTC_DCHECK_RUN_ON(&mCallbackChecker);
+ MOZ_DIAGNOSTIC_ASSERT(aData);
+ LOG_FUNCV();
+ webrtc::DesktopSize size(aData->mPictureRect.Width(),
+ aData->mPictureRect.Height());
+ webrtc::DesktopRect rect = webrtc::DesktopRect::MakeSize(size);
+ std::unique_ptr<webrtc::DesktopFrame> frame(
+ new webrtc::BasicDesktopFrame(size));
+
+ gfx::DataSourceSurface::ScopedMap map(aData->mSurface,
+ gfx::DataSourceSurface::READ);
+ if (!map.IsMapped()) {
+ OnCaptureFrameFailure();
+ return;
+ }
+ frame->CopyPixelsFrom(map.GetData(), map.GetStride(), rect);
+
+ mCallback->OnCaptureResult(webrtc::DesktopCapturer::Result::SUCCESS,
+ std::move(frame));
+}
+
+void TabCapturerWebrtc::OnCaptureFrameFailure() {
+ RTC_DCHECK_RUN_ON(&mCallbackChecker);
+ LOG_FUNC();
+ mCallback->OnCaptureResult(webrtc::DesktopCapturer::Result::ERROR_TEMPORARY,
+ nullptr);
+}
+
+bool TabCapturerWebrtc::IsOccluded(const webrtc::DesktopVector& aPos) {
+ return false;
+}
+
+class TabCapturedHandler final : public PromiseNativeHandler {
+ public:
+ NS_DECL_ISUPPORTS
+
+ using CapturePromise = TabCapturerWebrtc::CapturePromise;
+
+ static void Create(Promise* aPromise,
+ MozPromiseHolder<CapturePromise> aHolder) {
+ MOZ_ASSERT(aPromise);
+ MOZ_ASSERT(NS_IsMainThread());
+
+ RefPtr<TabCapturedHandler> handler =
+ new TabCapturedHandler(std::move(aHolder));
+ aPromise->AppendNativeHandler(handler);
+ }
+
+ void ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue,
+ ErrorResult& aRv) override {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (NS_WARN_IF(!aValue.isObject())) {
+ mHolder.Reject(NS_ERROR_UNEXPECTED, __func__);
+ return;
+ }
+
+ RefPtr<ImageBitmap> bitmap;
+ if (NS_WARN_IF(NS_FAILED(
+ UNWRAP_OBJECT(ImageBitmap, &aValue.toObject(), bitmap)))) {
+ mHolder.Reject(NS_ERROR_UNEXPECTED, __func__);
+ return;
+ }
+
+ UniquePtr<ImageBitmapCloneData> data = bitmap->ToCloneData();
+ if (!data) {
+ mHolder.Reject(NS_ERROR_UNEXPECTED, __func__);
+ return;
+ }
+
+ mHolder.Resolve(std::move(data), __func__);
+ }
+
+ void RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue,
+ ErrorResult& aRv) override {
+ MOZ_ASSERT(NS_IsMainThread());
+ mHolder.Reject(aRv.StealNSResult(), __func__);
+ }
+
+ private:
+ explicit TabCapturedHandler(MozPromiseHolder<CapturePromise> aHolder)
+ : mHolder(std::move(aHolder)) {}
+
+ ~TabCapturedHandler() = default;
+
+ MozPromiseHolder<CapturePromise> mHolder;
+};
+
+NS_IMPL_ISUPPORTS0(TabCapturedHandler)
+
+bool TabCapturerWebrtc::CompleteRequest(CaptureFrameRequest* aRequest) {
+ RTC_DCHECK_RUN_ON(&mCallbackChecker);
+ if (!aRequest->Exists()) {
+ // Request was disconnected or overrun. mCallback has already been notified.
+ return false;
+ }
+ while (CaptureFrameRequest* req = mRequests.Peek()) {
+ if (req->mCaptureTime > aRequest->mCaptureTime) {
+ break;
+ }
+ // Pop the request before calling the callback, in case it could mutate
+ // mRequests, now or in the future.
+ RefPtr<CaptureFrameRequest> dropMe = mRequests.Pop();
+ req->Complete();
+ if (req->mCaptureTime < aRequest->mCaptureTime) {
+ OnCaptureFrameFailure();
+ }
+ }
+ MOZ_DIAGNOSTIC_ASSERT(!aRequest->Exists());
+ return true;
+}
+
+void TabCapturerWebrtc::DisconnectRequest(CaptureFrameRequest* aRequest) {
+ RTC_DCHECK_RUN_ON(&mCallbackChecker);
+ LOG_FUNCV();
+ aRequest->Disconnect();
+ OnCaptureFrameFailure();
+}
+
+auto TabCapturerWebrtc::CaptureFrameNow() -> RefPtr<CapturePromise> {
+ MOZ_ASSERT(mMainThreadWorker->IsOnCurrentThread());
+ LOG_FUNCV();
+
+ WindowGlobalParent* wgp = nullptr;
+ RefPtr<BrowsingContext> context =
+ BrowsingContext::GetCurrentTopByBrowserId(mBrowserId);
+ if (context) {
+ wgp = context->Canonical()->GetCurrentWindowGlobal();
+ }
+ if (!wgp) {
+ // If we can't access the window, we just won't capture anything
+ return CapturePromise::CreateAndReject(NS_ERROR_NOT_AVAILABLE, __func__);
+ }
+
+ // XXX This would be more efficient if we used CrossProcessPaint directly and
+ // returned a surface.
+ RefPtr<Promise> promise =
+ wgp->DrawSnapshot(nullptr, 1.0, "white"_ns, false, IgnoreErrors());
+ if (!promise) {
+ return CapturePromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
+ }
+
+ MozPromiseHolder<CapturePromise> holder;
+ RefPtr<CapturePromise> p = holder.Ensure(__func__);
+ TabCapturedHandler::Create(promise, std::move(holder));
+ return p;
+}
+
+} // namespace mozilla
diff --git a/dom/media/systemservices/video_engine/tab_capturer.h b/dom/media/systemservices/video_engine/tab_capturer.h
new file mode 100644
index 0000000000..92c4fa2ad1
--- /dev/null
+++ b/dom/media/systemservices/video_engine/tab_capturer.h
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2018 The WebRTC project authors. All Rights Reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef MODULES_DESKTOP_CAPTURE_TAB_CAPTURER_H_
+#define MODULES_DESKTOP_CAPTURE_TAB_CAPTURER_H_
+
+#include "api/sequence_checker.h"
+#include "modules/desktop_capture/desktop_capturer.h"
+#include "mozilla/MozPromise.h"
+#include "nsDeque.h"
+#include "nsThreadUtils.h"
+
+namespace mozilla {
+namespace dom {
+struct ImageBitmapCloneData;
+} // namespace dom
+
+class CaptureFrameRequest;
+class TabCapturedHandler;
+class TaskQueue;
+
+class TabCapturerWebrtc : public webrtc::DesktopCapturer {
+ protected:
+ TabCapturerWebrtc(SourceId aSourceId,
+ nsCOMPtr<nsISerialEventTarget> aCaptureThread);
+ ~TabCapturerWebrtc();
+
+ public:
+ friend class CaptureFrameRequest;
+ friend class TabCapturedHandler;
+
+ static std::unique_ptr<webrtc::DesktopCapturer> Create(
+ SourceId aSourceId, nsCOMPtr<nsISerialEventTarget> aCaptureThread);
+
+ TabCapturerWebrtc(const TabCapturerWebrtc&) = delete;
+ TabCapturerWebrtc& operator=(const TabCapturerWebrtc&) = delete;
+
+ // DesktopCapturer interface.
+ void Start(Callback* aCallback) override;
+ void CaptureFrame() override;
+ bool GetSourceList(SourceList* aSources) override;
+ bool SelectSource(SourceId) override;
+ bool FocusOnSelectedSource() override;
+ bool IsOccluded(const webrtc::DesktopVector& aPos) override;
+
+ private:
+ // Capture code
+ using CapturePromise =
+ MozPromise<UniquePtr<dom::ImageBitmapCloneData>, nsresult, true>;
+ RefPtr<CapturePromise> CaptureFrameNow();
+
+ // Helper that checks for overrun requests. Returns true if aRequest had not
+ // been dropped due to disconnection or overrun.
+ // Note that if this returns true, the caller takes the responsibility to call
+ // mCallback with a capture result for aRequest.
+ bool CompleteRequest(CaptureFrameRequest* aRequest);
+
+ // Helper that disconnects the request, and notifies mCallback of a temporary
+ // failure.
+ void DisconnectRequest(CaptureFrameRequest* aRequest);
+
+ // Handle the result from the async callback from CaptureFrameNow.
+ void OnCaptureFrameSuccess(UniquePtr<dom::ImageBitmapCloneData> aData);
+ void OnCaptureFrameFailure();
+
+ const uint64_t mBrowserId;
+ const RefPtr<TaskQueue> mMainThreadWorker;
+ const RefPtr<TaskQueue> mCallbackWorker;
+ webrtc::SequenceChecker mControlChecker;
+ webrtc::SequenceChecker mCallbackChecker;
+ // Set in Start() and guaranteed by the owner of this class to outlive us.
+ webrtc::DesktopCapturer::Callback* mCallback
+ RTC_GUARDED_BY(mCallbackChecker) = nullptr;
+
+ // mCallbackWorker only
+ nsRefPtrDeque<CaptureFrameRequest> mRequests RTC_GUARDED_BY(mCallbackChecker);
+};
+
+} // namespace mozilla
+
+#endif // MODULES_DESKTOP_CAPTURE_TAB_CAPTURER_H_
diff --git a/dom/media/systemservices/video_engine/video_capture_factory.cc b/dom/media/systemservices/video_engine/video_capture_factory.cc
new file mode 100644
index 0000000000..e4ca505fa6
--- /dev/null
+++ b/dom/media/systemservices/video_engine/video_capture_factory.cc
@@ -0,0 +1,230 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et ft=cpp : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "video_capture_factory.h"
+
+#include "mozilla/StaticPrefs_media.h"
+#include "desktop_capture_impl.h"
+#include "VideoEngine.h"
+
+#if defined(WEBRTC_USE_PIPEWIRE)
+# include "video_engine/placeholder_device_info.h"
+#endif
+
+#if defined(WEBRTC_USE_PIPEWIRE) && defined(MOZ_ENABLE_DBUS)
+# include "mozilla/widget/AsyncDBus.h"
+#endif
+
+#include <memory>
+
+namespace mozilla {
+
+VideoCaptureFactory::VideoCaptureFactory() {
+#if (defined(WEBRTC_LINUX) || defined(WEBRTC_BSD)) && !defined(WEBRTC_ANDROID)
+ mVideoCaptureOptions = std::make_unique<webrtc::VideoCaptureOptions>();
+ // In case pipewire is enabled, this acts as a fallback and can be always
+ // enabled.
+ mVideoCaptureOptions->set_allow_v4l2(true);
+ bool allowPipeWire = false;
+# if defined(WEBRTC_USE_PIPEWIRE)
+ allowPipeWire =
+ mozilla::StaticPrefs::media_webrtc_camera_allow_pipewire_AtStartup();
+ mVideoCaptureOptions->set_allow_pipewire(allowPipeWire);
+# endif
+ if (!allowPipeWire) {
+ // V4L2 backend can and should be initialized right away since there are no
+ // permissions involved
+ InitCameraBackend();
+ }
+#endif
+}
+
+std::shared_ptr<webrtc::VideoCaptureModule::DeviceInfo>
+VideoCaptureFactory::CreateDeviceInfo(
+ int32_t aId, mozilla::camera::CaptureDeviceType aType) {
+ if (aType == mozilla::camera::CaptureDeviceType::Camera) {
+ std::shared_ptr<webrtc::VideoCaptureModule::DeviceInfo> deviceInfo;
+#if (defined(WEBRTC_LINUX) || defined(WEBRTC_BSD)) && !defined(WEBRTC_ANDROID)
+# if defined(WEBRTC_USE_PIPEWIRE)
+ // Special case when PipeWire is not initialized yet and we need to insert
+ // a camera device placeholder based on camera device availability we get
+ // from the camera portal
+ if (!mCameraBackendInitialized && mVideoCaptureOptions->allow_pipewire()) {
+ MOZ_ASSERT(mCameraAvailability != Unknown);
+ deviceInfo.reset(
+ new PlaceholderDeviceInfo(mCameraAvailability == Available));
+ return deviceInfo;
+ }
+# endif
+
+ deviceInfo.reset(webrtc::VideoCaptureFactory::CreateDeviceInfo(
+ mVideoCaptureOptions.get()));
+#else
+ deviceInfo.reset(webrtc::VideoCaptureFactory::CreateDeviceInfo());
+#endif
+ return deviceInfo;
+ }
+
+#if defined(WEBRTC_ANDROID) || defined(WEBRTC_IOS)
+ MOZ_ASSERT("CreateDeviceInfo NO DESKTOP CAPTURE IMPL ON ANDROID" == nullptr);
+ return nullptr;
+#else
+ return webrtc::DesktopCaptureImpl::CreateDeviceInfo(aId, aType);
+#endif
+}
+
+rtc::scoped_refptr<webrtc::VideoCaptureModule>
+VideoCaptureFactory::CreateVideoCapture(
+ int32_t aModuleId, const char* aUniqueId,
+ mozilla::camera::CaptureDeviceType aType) {
+ if (aType == mozilla::camera::CaptureDeviceType::Camera) {
+#if (defined(WEBRTC_LINUX) || defined(WEBRTC_BSD)) && !defined(WEBRTC_ANDROID)
+ return webrtc::VideoCaptureFactory::Create(mVideoCaptureOptions.get(),
+ aUniqueId);
+#else
+ return webrtc::VideoCaptureFactory::Create(aUniqueId);
+#endif
+ }
+
+#if defined(WEBRTC_ANDROID) || defined(WEBRTC_IOS)
+ MOZ_ASSERT("CreateVideoCapture NO DESKTOP CAPTURE IMPL ON ANDROID" ==
+ nullptr);
+ return nullptr;
+#else
+ return rtc::scoped_refptr<webrtc::VideoCaptureModule>(
+ webrtc::DesktopCaptureImpl::Create(aModuleId, aUniqueId, aType));
+#endif
+}
+
+auto VideoCaptureFactory::InitCameraBackend()
+ -> RefPtr<CameraBackendInitPromise> {
+ if (!mPromise) {
+ mPromise = mPromiseHolder.Ensure(__func__);
+#if (defined(WEBRTC_LINUX) || defined(WEBRTC_BSD)) && !defined(WEBRTC_ANDROID)
+ MOZ_ASSERT(mVideoCaptureOptions);
+ mVideoCaptureOptions->Init(this);
+# if defined(WEBRTC_USE_PIPEWIRE)
+ mPromise = mPromise->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [this, self = RefPtr(this)](
+ const CameraBackendInitPromise::ResolveOrRejectValue& aValue) {
+ if (aValue.IsReject() &&
+ aValue.RejectValue() != NS_ERROR_DOM_MEDIA_NOT_ALLOWED_ERR) {
+ // Fallback to V4L2 in case of PipeWire or camera portal failure.
+ // There is nothing we need to do in order to initialize V4L2 so
+ // consider the backend initialized and ready to be used.
+ mVideoCaptureOptions->set_allow_pipewire(false);
+ mCameraBackendInitialized = true;
+
+ return CameraBackendInitPromise::CreateAndResolve(
+ NS_OK,
+ "VideoCaptureFactory::InitCameraBackend Resolve with "
+ "fallback to V4L2");
+ }
+
+ return CameraBackendInitPromise::CreateAndResolveOrReject(
+ aValue,
+ "VideoCaptureFactory::InitCameraBackend Resolve or Reject");
+ });
+# endif
+#else
+ mCameraBackendInitialized = true;
+ mPromiseHolder.Resolve(NS_OK,
+ "VideoCaptureFactory::InitCameraBackend Resolve");
+#endif
+ }
+
+ return mPromise;
+}
+
+auto VideoCaptureFactory::HasCameraDevice()
+ -> RefPtr<VideoCaptureFactory::HasCameraDevicePromise> {
+#if defined(WEBRTC_USE_PIPEWIRE) && defined(MOZ_ENABLE_DBUS)
+ if (mVideoCaptureOptions && mVideoCaptureOptions->allow_pipewire()) {
+ return widget::CreateDBusProxyForBus(
+ G_BUS_TYPE_SESSION, G_DBUS_PROXY_FLAGS_NONE,
+ /* aInterfaceInfo = */ nullptr, "org.freedesktop.portal.Desktop",
+ "/org/freedesktop/portal/desktop",
+ "org.freedesktop.portal.Camera")
+ ->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [](RefPtr<GDBusProxy>&& aProxy) {
+ GVariant* variant =
+ g_dbus_proxy_get_cached_property(aProxy, "IsCameraPresent");
+ if (!variant) {
+ return HasCameraDevicePromise::CreateAndReject(
+ NS_ERROR_NO_INTERFACE,
+ "VideoCaptureFactory::HasCameraDevice Reject");
+ }
+
+ if (!g_variant_is_of_type(variant, G_VARIANT_TYPE_BOOLEAN)) {
+ return HasCameraDevicePromise::CreateAndReject(
+ NS_ERROR_UNEXPECTED,
+ "VideoCaptureFactory::HasCameraDevice Reject");
+ }
+
+ const bool hasCamera = g_variant_get_boolean(variant);
+ g_variant_unref(variant);
+ return HasCameraDevicePromise::CreateAndResolve(
+ hasCamera ? Available : NotAvailable,
+ "VideoCaptureFactory::HasCameraDevice Resolve");
+ },
+ [](GUniquePtr<GError>&& aError) {
+ return HasCameraDevicePromise::CreateAndReject(
+ NS_ERROR_NO_INTERFACE,
+ "VideoCaptureFactory::HasCameraDevice Reject");
+ });
+ }
+#endif
+ return HasCameraDevicePromise::CreateAndReject(
+ NS_ERROR_NOT_IMPLEMENTED, "VideoCaptureFactory::HasCameraDevice Reject");
+}
+
+auto VideoCaptureFactory::UpdateCameraAvailability()
+ -> RefPtr<UpdateCameraAvailabilityPromise> {
+ return VideoCaptureFactory::HasCameraDevice()->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [this, self = RefPtr(this)](
+ const HasCameraDevicePromise::ResolveOrRejectValue& aValue) {
+ if (aValue.IsResolve()) {
+ mCameraAvailability = aValue.ResolveValue();
+
+ return HasCameraDevicePromise::CreateAndResolve(
+ mCameraAvailability,
+ "VideoCaptureFactory::UpdateCameraAvailability Resolve");
+ }
+
+ // We want to fallback to V4L2 at this point, therefore make sure a
+ // camera device is announced so we can proceed with a gUM request,
+ // where we can fallback to V4L2 backend.
+ mCameraAvailability = Available;
+
+ return HasCameraDevicePromise::CreateAndReject(
+ aValue.RejectValue(),
+ "VideoCaptureFactory::UpdateCameraAvailability Reject");
+ });
+}
+
+void VideoCaptureFactory::OnInitialized(
+ webrtc::VideoCaptureOptions::Status status) {
+ switch (status) {
+ case webrtc::VideoCaptureOptions::Status::SUCCESS:
+ mCameraBackendInitialized = true;
+ mPromiseHolder.Resolve(NS_OK, __func__);
+ return;
+ case webrtc::VideoCaptureOptions::Status::UNAVAILABLE:
+ mPromiseHolder.Reject(NS_ERROR_NOT_AVAILABLE, __func__);
+ return;
+ case webrtc::VideoCaptureOptions::Status::DENIED:
+ mPromiseHolder.Reject(NS_ERROR_DOM_MEDIA_NOT_ALLOWED_ERR, __func__);
+ return;
+ default:
+ mPromiseHolder.Reject(NS_ERROR_FAILURE, __func__);
+ return;
+ }
+}
+
+} // namespace mozilla
diff --git a/dom/media/systemservices/video_engine/video_capture_factory.h b/dom/media/systemservices/video_engine/video_capture_factory.h
new file mode 100644
index 0000000000..70505e572a
--- /dev/null
+++ b/dom/media/systemservices/video_engine/video_capture_factory.h
@@ -0,0 +1,89 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et ft=cpp : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef MOZILLA_VIDEO_CAPTURE_FACTORY_H_
+#define MOZILLA_VIDEO_CAPTURE_FACTORY_H_
+
+#include "modules/video_capture/video_capture_factory.h"
+#include "modules/video_capture/video_capture_options.h"
+#include "modules/video_capture/video_capture.h"
+
+#include "mozilla/MozPromise.h"
+
+namespace mozilla::camera {
+enum class CaptureDeviceType;
+}
+
+namespace mozilla {
+/**
+ * NOTE: This class must be accessed only on a single SerialEventTarget
+ */
+class VideoCaptureFactory : webrtc::VideoCaptureOptions::Callback {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(VideoCaptureFactory);
+
+ enum CameraAvailability { Unknown, Available, NotAvailable };
+
+ VideoCaptureFactory();
+
+ std::shared_ptr<webrtc::VideoCaptureModule::DeviceInfo> CreateDeviceInfo(
+ int32_t aId, mozilla::camera::CaptureDeviceType aType);
+
+ rtc::scoped_refptr<webrtc::VideoCaptureModule> CreateVideoCapture(
+ int32_t aModuleId, const char* aUniqueId,
+ mozilla::camera::CaptureDeviceType aType);
+
+ using CameraBackendInitPromise = MozPromise<nsresult, nsresult, false>;
+ /**
+ * Request to initialize webrtc::VideoCaptureOptions
+ *
+ * Resolves with NS_OK when VideoCaptureOptions has been properly initialized
+ * or rejects with one of the possible errors. Since this is only now
+ * supported by PipeWire, all the errors are PipeWire specific:
+ * 1) NS_ERROR_NOT_AVAILABLE - PipeWire libraries are not available on
+ * the system
+ * 2) NS_ERROR_DOM_MEDIA_NOT_ALLOWED_ERR - camera access has been rejected
+ * 3) NS_ERROR_FAILURE - generic error, usually a PipeWire failure
+ */
+ RefPtr<CameraBackendInitPromise> InitCameraBackend();
+
+ /**
+ * Updates information about camera availability
+ */
+ using UpdateCameraAvailabilityPromise =
+ MozPromise<CameraAvailability, nsresult, true>;
+ RefPtr<UpdateCameraAvailabilityPromise> UpdateCameraAvailability();
+
+ private:
+ ~VideoCaptureFactory() = default;
+ // aka OnCameraBackendInitialized
+ // this method override has to follow webrtc::VideoCaptureOptions::Callback
+ void OnInitialized(webrtc::VideoCaptureOptions::Status status) override;
+
+ /**
+ * Resolves with true or false depending on whether there is a camera device
+ * advertised by the xdg-desktop-portal (Camera portal). Rejects with one
+ * of the following errors:
+ * 1) NS_ERROR_NOT_IMPLEMENTED - support for the Camera portal is not
+ * implemented or enabled
+ * 2) NS_ERROR_NO_INTERFACE - the camera portal is not available
+ * 3) NS_ERROR_UNEXPECTED - the camera portal returned wrong value
+ */
+ using HasCameraDevicePromise = MozPromise<CameraAvailability, nsresult, true>;
+ RefPtr<HasCameraDevicePromise> HasCameraDevice();
+
+ std::atomic<bool> mCameraBackendInitialized = false;
+ CameraAvailability mCameraAvailability = Unknown;
+#if (defined(WEBRTC_LINUX) || defined(WEBRTC_BSD)) && !defined(WEBRTC_ANDROID)
+ std::unique_ptr<webrtc::VideoCaptureOptions> mVideoCaptureOptions;
+#endif
+ MozPromiseHolder<CameraBackendInitPromise> mPromiseHolder;
+ RefPtr<CameraBackendInitPromise> mPromise;
+};
+
+} // namespace mozilla
+
+#endif // MOZILLA_VIDEO_CAPTURE_FACTORY_H_