mirror of
https://github.com/kunkundi/crossdesk.git
synced 2026-08-09 11:49:28 +08:00
[fix] harden macOS screen capture lifecycle against late callbacks
This commit is contained in:
@@ -16,7 +16,11 @@ int ScreenCapturerSck::Init(const int fps, cb_desktop_data cb) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
screen_capturer_sck_impl_ = CreateScreenCapturerSck();
|
screen_capturer_sck_impl_ = CreateScreenCapturerSck();
|
||||||
screen_capturer_sck_impl_->Init(fps, on_data_);
|
const int ret = screen_capturer_sck_impl_->Init(fps, on_data_);
|
||||||
|
if (ret != 0) {
|
||||||
|
screen_capturer_sck_impl_.reset();
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -29,8 +33,11 @@ int ScreenCapturerSck::Destroy() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int ScreenCapturerSck::Start(bool show_cursor) {
|
int ScreenCapturerSck::Start(bool show_cursor) {
|
||||||
screen_capturer_sck_impl_->Start(show_cursor);
|
if (!screen_capturer_sck_impl_) {
|
||||||
return 0;
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return screen_capturer_sck_impl_->Start(show_cursor);
|
||||||
}
|
}
|
||||||
|
|
||||||
int ScreenCapturerSck::Stop() {
|
int ScreenCapturerSck::Stop() {
|
||||||
@@ -80,4 +87,4 @@ std::vector<DisplayInfo> ScreenCapturerSck::GetDisplayInfoList() {
|
|||||||
void ScreenCapturerSck::OnFrame() {}
|
void ScreenCapturerSck::OnFrame() {}
|
||||||
|
|
||||||
void ScreenCapturerSck::CleanUp() {}
|
void ScreenCapturerSck::CleanUp() {}
|
||||||
} // namespace crossdesk
|
} // namespace crossdesk
|
||||||
|
|||||||
@@ -16,7 +16,12 @@
|
|||||||
#include <IOKit/graphics/IOGraphicsLib.h>
|
#include <IOKit/graphics/IOGraphicsLib.h>
|
||||||
#include <IOSurface/IOSurface.h>
|
#include <IOSurface/IOSurface.h>
|
||||||
#include <ScreenCaptureKit/ScreenCaptureKit.h>
|
#include <ScreenCaptureKit/ScreenCaptureKit.h>
|
||||||
|
#include <algorithm>
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
|
#include <cctype>
|
||||||
|
#include <cstring>
|
||||||
|
#include <limits>
|
||||||
|
#include <map>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include "display_info.h"
|
#include "display_info.h"
|
||||||
@@ -28,6 +33,15 @@ class ScreenCapturerSckImpl;
|
|||||||
|
|
||||||
static const int kFullDesktopScreenId = -1;
|
static const int kFullDesktopScreenId = -1;
|
||||||
|
|
||||||
|
static std::string NSErrorToString(NSError *error) {
|
||||||
|
if (!error) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *description = [error.localizedDescription UTF8String];
|
||||||
|
return description ? description : "";
|
||||||
|
}
|
||||||
|
|
||||||
// The ScreenCaptureKit API was available in macOS 12.3, but full-screen capture
|
// The ScreenCaptureKit API was available in macOS 12.3, but full-screen capture
|
||||||
// was reported to be broken before macOS 13 - see http://crbug.com/40234870.
|
// was reported to be broken before macOS 13 - see http://crbug.com/40234870.
|
||||||
// Also, the `SCContentFilter` fields `contentRect` and `pointPixelScale` were
|
// Also, the `SCContentFilter` fields `contentRect` and `pointPixelScale` were
|
||||||
@@ -78,6 +92,7 @@ class API_AVAILABLE(macos(14.0)) ScreenCapturerSckImpl : public ScreenCapturer {
|
|||||||
std::map<CGDirectDisplayID, int> display_id_map_reverse_;
|
std::map<CGDirectDisplayID, int> display_id_map_reverse_;
|
||||||
std::map<CGDirectDisplayID, std::string> display_id_name_map_;
|
std::map<CGDirectDisplayID, std::string> display_id_name_map_;
|
||||||
unsigned char *nv12_frame_ = nullptr;
|
unsigned char *nv12_frame_ = nullptr;
|
||||||
|
size_t nv12_frame_size_ = 0;
|
||||||
int width_ = 0;
|
int width_ = 0;
|
||||||
int height_ = 0;
|
int height_ = 0;
|
||||||
int fps_ = 60;
|
int fps_ = 60;
|
||||||
@@ -100,7 +115,7 @@ class API_AVAILABLE(macos(14.0)) ScreenCapturerSckImpl : public ScreenCapturer {
|
|||||||
// Helper object to receive Objective-C callbacks from ScreenCaptureKit and call into this C++
|
// Helper object to receive Objective-C callbacks from ScreenCaptureKit and call into this C++
|
||||||
// object. The helper may outlive this C++ instance, if a completion-handler is passed to
|
// object. The helper may outlive this C++ instance, if a completion-handler is passed to
|
||||||
// ScreenCaptureKit APIs and the C++ object is deleted before the handler executes.
|
// ScreenCaptureKit APIs and the C++ object is deleted before the handler executes.
|
||||||
SckHelper *__strong helper_;
|
SckHelper *__strong helper_ = nil;
|
||||||
// Callback for returning captured frames, or errors, to the caller. Only used on the caller's
|
// Callback for returning captured frames, or errors, to the caller. Only used on the caller's
|
||||||
// thread.
|
// thread.
|
||||||
cb_desktop_data _on_data = nullptr;
|
cb_desktop_data _on_data = nullptr;
|
||||||
@@ -110,7 +125,7 @@ class API_AVAILABLE(macos(14.0)) ScreenCapturerSckImpl : public ScreenCapturer {
|
|||||||
// Guards some variables that may be accessed on different threads.
|
// Guards some variables that may be accessed on different threads.
|
||||||
std::mutex lock_;
|
std::mutex lock_;
|
||||||
// Provides captured desktop frames.
|
// Provides captured desktop frames.
|
||||||
SCStream *__strong stream_;
|
SCStream *__strong stream_ = nil;
|
||||||
// Currently selected display, or 0 if the full desktop is selected. This capturer does not
|
// Currently selected display, or 0 if the full desktop is selected. This capturer does not
|
||||||
// support full-desktop capture, and will fall back to the first display.
|
// support full-desktop capture, and will fall back to the first display.
|
||||||
CGDirectDisplayID current_display_ = 0;
|
CGDirectDisplayID current_display_ = 0;
|
||||||
@@ -182,6 +197,19 @@ ScreenCapturerSckImpl::ScreenCapturerSckImpl() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ScreenCapturerSckImpl::~ScreenCapturerSckImpl() {
|
ScreenCapturerSckImpl::~ScreenCapturerSckImpl() {
|
||||||
|
SckHelper *helper_to_release = nil;
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(lock_);
|
||||||
|
if (stream_) {
|
||||||
|
[stream_ stopCaptureWithCompletionHandler:nil];
|
||||||
|
stream_ = nil;
|
||||||
|
}
|
||||||
|
_on_data = nullptr;
|
||||||
|
helper_to_release = helper_;
|
||||||
|
helper_ = nil;
|
||||||
|
}
|
||||||
|
[helper_to_release releaseCapturer];
|
||||||
|
|
||||||
display_info_list_.clear();
|
display_info_list_.clear();
|
||||||
display_id_map_.clear();
|
display_id_map_.clear();
|
||||||
display_id_map_reverse_.clear();
|
display_id_map_reverse_.clear();
|
||||||
@@ -190,15 +218,22 @@ ScreenCapturerSckImpl::~ScreenCapturerSckImpl() {
|
|||||||
if (nv12_frame_) {
|
if (nv12_frame_) {
|
||||||
delete[] nv12_frame_;
|
delete[] nv12_frame_;
|
||||||
nv12_frame_ = nullptr;
|
nv12_frame_ = nullptr;
|
||||||
|
nv12_frame_size_ = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
[stream_ stopCaptureWithCompletionHandler:nil];
|
|
||||||
[helper_ releaseCapturer];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int ScreenCapturerSckImpl::Init(const int fps, cb_desktop_data cb) {
|
int ScreenCapturerSckImpl::Init(const int fps, cb_desktop_data cb) {
|
||||||
|
if (!cb) {
|
||||||
|
LOG_ERROR("Screen capturer callback is null");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
_on_data = cb;
|
_on_data = cb;
|
||||||
fps_ = fps;
|
fps_ = fps > 0 ? fps : 60;
|
||||||
|
display_info_list_.clear();
|
||||||
|
display_id_map_.clear();
|
||||||
|
display_id_map_reverse_.clear();
|
||||||
|
display_id_name_map_.clear();
|
||||||
|
|
||||||
if (@available(macOS 10.15, *)) {
|
if (@available(macOS 10.15, *)) {
|
||||||
bool has_permission = CGPreflightScreenCaptureAccess();
|
bool has_permission = CGPreflightScreenCaptureAccess();
|
||||||
@@ -216,8 +251,7 @@ int ScreenCapturerSckImpl::Init(const int fps, cb_desktop_data cb) {
|
|||||||
getShareableContentWithCompletionHandler:^(SCShareableContent *result, NSError *error) {
|
getShareableContentWithCompletionHandler:^(SCShareableContent *result, NSError *error) {
|
||||||
if (error) {
|
if (error) {
|
||||||
capture_error = error;
|
capture_error = error;
|
||||||
LOG_ERROR("Failed to get shareable content: {}",
|
LOG_ERROR("Failed to get shareable content: {}", NSErrorToString(error));
|
||||||
std::string([error.localizedDescription UTF8String]));
|
|
||||||
} else {
|
} else {
|
||||||
content = result;
|
content = result;
|
||||||
}
|
}
|
||||||
@@ -227,7 +261,7 @@ int ScreenCapturerSckImpl::Init(const int fps, cb_desktop_data cb) {
|
|||||||
|
|
||||||
if (capture_error || !content || content.displays.count == 0) {
|
if (capture_error || !content || content.displays.count == 0) {
|
||||||
LOG_ERROR("Failed to get display info, error: {}",
|
LOG_ERROR("Failed to get display info, error: {}",
|
||||||
std::string([capture_error.localizedDescription UTF8String]));
|
NSErrorToString(capture_error));
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -284,51 +318,58 @@ int ScreenCapturerSckImpl::Start(bool show_cursor) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int ScreenCapturerSckImpl::SwitchTo(int monitor_index) {
|
int ScreenCapturerSckImpl::SwitchTo(int monitor_index) {
|
||||||
if (stream_) {
|
auto display_it = display_id_map_.find(monitor_index);
|
||||||
[stream_ stopCaptureWithCompletionHandler:^(NSError *error) {
|
if (display_it == display_id_map_.end()) {
|
||||||
std::lock_guard<std::mutex> lock(lock_);
|
LOG_WARN("SwitchTo skipped, invalid monitor_index={}, displays={}",
|
||||||
stream_ = nil;
|
monitor_index, display_id_map_.size());
|
||||||
current_display_ = display_id_map_[monitor_index];
|
return -1;
|
||||||
StartOrReconfigureCapturer();
|
|
||||||
}];
|
|
||||||
} else {
|
|
||||||
current_display_ = display_id_map_[monitor_index];
|
|
||||||
StartOrReconfigureCapturer();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const CGDirectDisplayID target_display = display_it->second;
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(lock_);
|
||||||
|
current_display_ = target_display;
|
||||||
|
}
|
||||||
|
StartOrReconfigureCapturer();
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int ScreenCapturerSckImpl::ResetToInitialMonitor() {
|
int ScreenCapturerSckImpl::ResetToInitialMonitor() {
|
||||||
int target = initial_monitor_index_;
|
int target = initial_monitor_index_;
|
||||||
if (display_info_list_.empty()) return -1;
|
if (display_info_list_.empty()) return -1;
|
||||||
CGDirectDisplayID target_display = display_id_map_[target];
|
auto display_it = display_id_map_.find(target);
|
||||||
if (current_display_ == target_display) return 0;
|
if (display_it == display_id_map_.end()) {
|
||||||
if (stream_) {
|
LOG_WARN("ResetToInitialMonitor skipped, invalid monitor_index={}", target);
|
||||||
[stream_ stopCaptureWithCompletionHandler:^(NSError *error) {
|
return -1;
|
||||||
std::lock_guard<std::mutex> lock(lock_);
|
|
||||||
stream_ = nil;
|
|
||||||
current_display_ = target_display;
|
|
||||||
StartOrReconfigureCapturer();
|
|
||||||
}];
|
|
||||||
} else {
|
|
||||||
current_display_ = target_display;
|
|
||||||
StartOrReconfigureCapturer();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CGDirectDisplayID target_display = display_it->second;
|
||||||
|
if (current_display_ == target_display) return 0;
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(lock_);
|
||||||
|
current_display_ = target_display;
|
||||||
|
}
|
||||||
|
StartOrReconfigureCapturer();
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int ScreenCapturerSckImpl::Destroy() {
|
int ScreenCapturerSckImpl::Destroy() {
|
||||||
std::lock_guard<std::mutex> lock(lock_);
|
SckHelper *helper_to_release = nil;
|
||||||
if (stream_) {
|
{
|
||||||
LOG_INFO("Destroying stream");
|
std::lock_guard<std::mutex> lock(lock_);
|
||||||
[stream_ stopCaptureWithCompletionHandler:nil];
|
if (stream_) {
|
||||||
stream_ = nil;
|
LOG_INFO("Destroying stream");
|
||||||
|
[stream_ stopCaptureWithCompletionHandler:nil];
|
||||||
|
stream_ = nil;
|
||||||
|
}
|
||||||
|
current_display_ = 0;
|
||||||
|
permanent_error_ = false;
|
||||||
|
_on_data = nullptr;
|
||||||
|
helper_to_release = helper_;
|
||||||
|
helper_ = nil;
|
||||||
}
|
}
|
||||||
current_display_ = 0;
|
|
||||||
permanent_error_ = false;
|
[helper_to_release releaseCapturer];
|
||||||
_on_data = nullptr;
|
|
||||||
[helper_ releaseCapturer];
|
|
||||||
helper_ = nil;
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -416,7 +457,7 @@ void ScreenCapturerSckImpl::OnShareableContentCreated(SCShareableContent *conten
|
|||||||
|
|
||||||
// TODO: crbug.com/327458809 - Choose an appropriate sampleHandlerQueue for
|
// TODO: crbug.com/327458809 - Choose an appropriate sampleHandlerQueue for
|
||||||
// best performance.
|
// best performance.
|
||||||
NSError *add_stream_output_error;
|
NSError *add_stream_output_error = nil;
|
||||||
dispatch_queue_t queue = dispatch_queue_create("ScreenCaptureKit.Queue", DISPATCH_QUEUE_SERIAL);
|
dispatch_queue_t queue = dispatch_queue_create("ScreenCaptureKit.Queue", DISPATCH_QUEUE_SERIAL);
|
||||||
bool add_stream_output_result = [stream_ addStreamOutput:helper_
|
bool add_stream_output_result = [stream_ addStreamOutput:helper_
|
||||||
type:SCStreamOutputTypeScreen
|
type:SCStreamOutputTypeScreen
|
||||||
@@ -425,7 +466,7 @@ void ScreenCapturerSckImpl::OnShareableContentCreated(SCShareableContent *conten
|
|||||||
|
|
||||||
if (!add_stream_output_result) {
|
if (!add_stream_output_result) {
|
||||||
stream_ = nil;
|
stream_ = nil;
|
||||||
LOG_ERROR("addStreamOutput failed");
|
LOG_ERROR("addStreamOutput failed: {}", NSErrorToString(add_stream_output_error));
|
||||||
permanent_error_ = true;
|
permanent_error_ = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -436,7 +477,7 @@ void ScreenCapturerSckImpl::OnShareableContentCreated(SCShareableContent *conten
|
|||||||
// calls stopCaptureWithCompletionHandler on the stream, which cancels
|
// calls stopCaptureWithCompletionHandler on the stream, which cancels
|
||||||
// this handler.
|
// this handler.
|
||||||
permanent_error_ = true;
|
permanent_error_ = true;
|
||||||
LOG_ERROR("startCaptureWithCompletionHandler failed");
|
LOG_ERROR("startCaptureWithCompletionHandler failed: {}", NSErrorToString(error));
|
||||||
} else {
|
} else {
|
||||||
LOG_INFO("Capture started");
|
LOG_INFO("Capture started");
|
||||||
}
|
}
|
||||||
@@ -448,8 +489,18 @@ void ScreenCapturerSckImpl::OnShareableContentCreated(SCShareableContent *conten
|
|||||||
|
|
||||||
void ScreenCapturerSckImpl::OnNewCVPixelBuffer(CVPixelBufferRef pixelBuffer,
|
void ScreenCapturerSckImpl::OnNewCVPixelBuffer(CVPixelBufferRef pixelBuffer,
|
||||||
CFDictionaryRef attachment) {
|
CFDictionaryRef attachment) {
|
||||||
|
(void)attachment;
|
||||||
|
if (!pixelBuffer) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
size_t width = CVPixelBufferGetWidth(pixelBuffer);
|
size_t width = CVPixelBufferGetWidth(pixelBuffer);
|
||||||
size_t height = CVPixelBufferGetHeight(pixelBuffer);
|
size_t height = CVPixelBufferGetHeight(pixelBuffer);
|
||||||
|
if (width == 0 || height == 0 || CVPixelBufferGetPlaneCount(pixelBuffer) < 2) {
|
||||||
|
LOG_ERROR("Invalid CVPixelBuffer: width={}, height={}, planes={}", width, height,
|
||||||
|
CVPixelBufferGetPlaneCount(pixelBuffer));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
CVReturn status = CVPixelBufferLockBaseAddress(pixelBuffer, kCVPixelBufferLock_ReadOnly);
|
CVReturn status = CVPixelBufferLockBaseAddress(pixelBuffer, kCVPixelBufferLock_ReadOnly);
|
||||||
if (status != kCVReturnSuccess) {
|
if (status != kCVReturnSuccess) {
|
||||||
@@ -458,18 +509,37 @@ void ScreenCapturerSckImpl::OnNewCVPixelBuffer(CVPixelBufferRef pixelBuffer,
|
|||||||
}
|
}
|
||||||
|
|
||||||
size_t required_size = width * height * 3 / 2;
|
size_t required_size = width * height * 3 / 2;
|
||||||
if (!nv12_frame_ || (width_ * height_ * 3 / 2 < required_size)) {
|
if (required_size > static_cast<size_t>((std::numeric_limits<int>::max)())) {
|
||||||
|
LOG_ERROR("Captured frame is too large: {} bytes", required_size);
|
||||||
|
CVPixelBufferUnlockBaseAddress(pixelBuffer, kCVPixelBufferLock_ReadOnly);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::lock_guard<std::mutex> lock(lock_);
|
||||||
|
if (!_on_data) {
|
||||||
|
CVPixelBufferUnlockBaseAddress(pixelBuffer, kCVPixelBufferLock_ReadOnly);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!nv12_frame_ || nv12_frame_size_ < required_size) {
|
||||||
delete[] nv12_frame_;
|
delete[] nv12_frame_;
|
||||||
nv12_frame_ = new unsigned char[required_size];
|
nv12_frame_ = new unsigned char[required_size];
|
||||||
width_ = width;
|
nv12_frame_size_ = required_size;
|
||||||
height_ = height;
|
|
||||||
}
|
}
|
||||||
|
width_ = static_cast<int>(width);
|
||||||
|
height_ = static_cast<int>(height);
|
||||||
|
|
||||||
void *base_y = CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 0);
|
void *base_y = CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 0);
|
||||||
size_t stride_y = CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, 0);
|
size_t stride_y = CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, 0);
|
||||||
|
|
||||||
void *base_uv = CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 1);
|
void *base_uv = CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 1);
|
||||||
size_t stride_uv = CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, 1);
|
size_t stride_uv = CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, 1);
|
||||||
|
if (!base_y || !base_uv || stride_y < width || stride_uv < width) {
|
||||||
|
LOG_ERROR("Invalid CVPixelBuffer planes: base_y={}, base_uv={}, stride_y={}, stride_uv={}",
|
||||||
|
base_y != nullptr, base_uv != nullptr, stride_y, stride_uv);
|
||||||
|
CVPixelBufferUnlockBaseAddress(pixelBuffer, kCVPixelBufferLock_ReadOnly);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
unsigned char *dst_y = nv12_frame_;
|
unsigned char *dst_y = nv12_frame_;
|
||||||
for (size_t row = 0; row < height; ++row) {
|
for (size_t row = 0; row < height; ++row) {
|
||||||
@@ -481,7 +551,8 @@ void ScreenCapturerSckImpl::OnNewCVPixelBuffer(CVPixelBufferRef pixelBuffer,
|
|||||||
memcpy(dst_uv + row * width, static_cast<unsigned char *>(base_uv) + row * stride_uv, width);
|
memcpy(dst_uv + row * width, static_cast<unsigned char *>(base_uv) + row * stride_uv, width);
|
||||||
}
|
}
|
||||||
|
|
||||||
_on_data(nv12_frame_, width * height * 3 / 2, width, height,
|
_on_data(nv12_frame_, static_cast<int>(required_size), static_cast<int>(width),
|
||||||
|
static_cast<int>(height),
|
||||||
display_id_name_map_[current_display_].c_str());
|
display_id_name_map_[current_display_].c_str());
|
||||||
|
|
||||||
CVPixelBufferUnlockBaseAddress(pixelBuffer, kCVPixelBufferLock_ReadOnly);
|
CVPixelBufferUnlockBaseAddress(pixelBuffer, kCVPixelBufferLock_ReadOnly);
|
||||||
@@ -503,10 +574,14 @@ void ScreenCapturerSckImpl::StartOrReconfigureCapturer() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
SckHelper *local_helper = helper_;
|
SckHelper *local_helper = helper_;
|
||||||
|
if (!local_helper) {
|
||||||
|
LOG_ERROR("Cannot reconfigure capturer: helper is null");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
auto handler = ^(SCShareableContent *content, NSError *error) {
|
auto handler = ^(SCShareableContent *content, NSError *error) {
|
||||||
if (error) {
|
if (error) {
|
||||||
LOG_ERROR("getShareableContent failed: {}",
|
LOG_ERROR("getShareableContent failed: {}", NSErrorToString(error));
|
||||||
std::string([error.localizedDescription UTF8String]));
|
|
||||||
[local_helper onShareableContentCreated:nil];
|
[local_helper onShareableContentCreated:nil];
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -576,4 +651,4 @@ void ScreenCapturerSckImpl::StartOrReconfigureCapturer() {
|
|||||||
|
|
||||||
std::unique_ptr<ScreenCapturer> ScreenCapturerSck::CreateScreenCapturerSck() {
|
std::unique_ptr<ScreenCapturer> ScreenCapturerSck::CreateScreenCapturerSck() {
|
||||||
return std::make_unique<ScreenCapturerSckImpl>();
|
return std::make_unique<ScreenCapturerSckImpl>();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user