record-daemon: obs-context refactoring, gpu detection code
Some checks failed
record-daemon / Build, check and test (push) Failing after 48s
Some checks failed
record-daemon / Build, check and test (push) Failing after 48s
This commit is contained in:
13
record-daemon/Cargo.lock
generated
13
record-daemon/Cargo.lock
generated
@@ -2439,6 +2439,7 @@ dependencies = [
|
||||
"tracing-subscriber",
|
||||
"uuid",
|
||||
"winapi 0.3.9",
|
||||
"winreg 0.56.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2548,7 +2549,7 @@ dependencies = [
|
||||
"wasm-bindgen-futures",
|
||||
"web-sys",
|
||||
"webpki-roots 0.25.4",
|
||||
"winreg",
|
||||
"winreg 0.50.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4525,6 +4526,16 @@ dependencies = [
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winreg"
|
||||
version = "0.56.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7d6f32a0ff4a9f6f01231eb2059cc85479330739333e0e58cadf03b6af2cca10"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.4",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen"
|
||||
version = "0.51.0"
|
||||
|
||||
@@ -77,7 +77,8 @@ signal-hook-tokio = { version = "0.4", features = ["futures-v0_3"] }
|
||||
|
||||
# Windows-specific dependencies
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
winapi = { version = "0.3", features = ["winuser"] }
|
||||
winapi = { version = "0.3", features = ["winuser", "winreg", "winnt"] }
|
||||
winreg = "0.56"
|
||||
|
||||
[dev-dependencies]
|
||||
# Testing utilities
|
||||
|
||||
@@ -221,15 +221,11 @@ impl Daemon {
|
||||
|
||||
/// Handle a game event.
|
||||
async fn handle_game_event(&self, event: GameEvent) -> Result<()> {
|
||||
use std::io::Write;
|
||||
|
||||
info!("[EVENT_HANDLER] Game event received: {:?}", event);
|
||||
std::io::stderr().flush().ok();
|
||||
|
||||
// Process state transitions
|
||||
if let Some(transition) = self.state_machine.process_event(&event) {
|
||||
info!("[EVENT_HANDLER] State transition: {:?}", transition);
|
||||
std::io::stderr().flush().ok();
|
||||
|
||||
self.state_machine.transition(transition.clone());
|
||||
|
||||
@@ -240,22 +236,19 @@ impl Daemon {
|
||||
"[EVENT_HANDLER] GameStarted transition - game_id: {}, champion: {:?}",
|
||||
game_id, champion
|
||||
);
|
||||
std::io::stderr().flush().ok();
|
||||
|
||||
// If already recording, stop the current recording first
|
||||
if self.state_machine.is_recording() {
|
||||
info!(
|
||||
"[EVENT_HANDLER] Stopping previous recording before starting new one"
|
||||
);
|
||||
std::io::stderr().flush().ok();
|
||||
|
||||
if let Err(e) = self.stop_recording().await {
|
||||
warn!("[EVENT_HANDLER] Failed to stop previous recording: {}", e);
|
||||
std::io::stderr().flush().ok();
|
||||
}
|
||||
}
|
||||
|
||||
info!("[EVENT_HANDLER] Calling start_recording...");
|
||||
std::io::stderr().flush().ok();
|
||||
|
||||
// Wrap the start_recording call to catch any panics
|
||||
let start_result =
|
||||
@@ -269,7 +262,7 @@ impl Daemon {
|
||||
"[EVENT_HANDLER] PANIC before start_recording: {:?}",
|
||||
panic_info
|
||||
);
|
||||
std::io::stderr().flush().ok();
|
||||
|
||||
eprintln!(
|
||||
"[EVENT_HANDLER] PANIC before start_recording: {:?}",
|
||||
panic_info
|
||||
@@ -278,20 +271,18 @@ impl Daemon {
|
||||
|
||||
if let Err(e) = self.start_recording(game_id, champion.as_deref()).await {
|
||||
error!("[EVENT_HANDLER] Failed to start recording: {}", e);
|
||||
std::io::stderr().flush().ok();
|
||||
|
||||
// Don't propagate error - keep daemon running
|
||||
} else {
|
||||
info!("[EVENT_HANDLER] start_recording completed successfully");
|
||||
std::io::stderr().flush().ok();
|
||||
}
|
||||
}
|
||||
StateTransition::GameEnded => {
|
||||
info!("[EVENT_HANDLER] GameEnded transition");
|
||||
std::io::stderr().flush().ok();
|
||||
|
||||
if let Err(e) = self.stop_recording().await {
|
||||
error!("[EVENT_HANDLER] Failed to stop recording: {}", e);
|
||||
std::io::stderr().flush().ok();
|
||||
|
||||
// Don't propagate error - keep daemon running
|
||||
}
|
||||
}
|
||||
@@ -311,7 +302,7 @@ impl Daemon {
|
||||
}
|
||||
|
||||
info!("[EVENT_HANDLER] Event handling complete");
|
||||
std::io::stderr().flush().ok();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -500,11 +491,10 @@ async fn main() -> Result<()> {
|
||||
// Create and run daemon
|
||||
let mut daemon = Daemon::new(settings);
|
||||
|
||||
// Handle shutdown signals
|
||||
let shutdown_tx = daemon.shutdown_tx.clone();
|
||||
|
||||
#[cfg(unix)]
|
||||
{
|
||||
// Handle shutdown signals
|
||||
let shutdown_tx = daemon.shutdown_tx.clone();
|
||||
use futures::StreamExt;
|
||||
use signal_hook::consts::signal::*;
|
||||
use signal_hook_tokio::Signals;
|
||||
|
||||
@@ -294,11 +294,9 @@ impl EncoderCapability {
|
||||
///
|
||||
/// This function checks the system for available GPU encoders.
|
||||
pub fn detect_hardware_encoders() -> Vec<EncoderCapability> {
|
||||
use std::io::Write;
|
||||
use tracing::info;
|
||||
|
||||
info!("[ENCODER_DETECT] Starting hardware encoder detection...");
|
||||
std::io::stderr().flush().ok();
|
||||
|
||||
let mut capabilities = Vec::new();
|
||||
|
||||
@@ -322,7 +320,7 @@ pub fn detect_hardware_encoders() -> Vec<EncoderCapability> {
|
||||
// On Windows, check for NVIDIA first
|
||||
// Try to load nvenc DLL
|
||||
info!("[ENCODER_DETECT] Checking for NVENC...");
|
||||
std::io::stderr().flush().ok();
|
||||
|
||||
if is_nvenc_available() {
|
||||
info!("[ENCODER_DETECT] NVENC available");
|
||||
capabilities.push(EncoderCapability::Nvenc);
|
||||
@@ -332,7 +330,7 @@ pub fn detect_hardware_encoders() -> Vec<EncoderCapability> {
|
||||
|
||||
// Check for AMD AMF
|
||||
info!("[ENCODER_DETECT] Checking for AMF...");
|
||||
std::io::stderr().flush().ok();
|
||||
|
||||
if is_amf_available() {
|
||||
info!("[ENCODER_DETECT] AMF available");
|
||||
capabilities.push(EncoderCapability::Amf);
|
||||
@@ -342,7 +340,7 @@ pub fn detect_hardware_encoders() -> Vec<EncoderCapability> {
|
||||
|
||||
// Check for Intel QuickSync
|
||||
info!("[ENCODER_DETECT] Checking for QuickSync...");
|
||||
std::io::stderr().flush().ok();
|
||||
|
||||
if is_quicksync_available() {
|
||||
info!("[ENCODER_DETECT] QuickSync available");
|
||||
capabilities.push(EncoderCapability::QuickSync);
|
||||
@@ -356,31 +354,200 @@ pub fn detect_hardware_encoders() -> Vec<EncoderCapability> {
|
||||
capabilities.push(EncoderCapability::Software);
|
||||
|
||||
info!("[ENCODER_DETECT] Detected encoders: {:?}", capabilities);
|
||||
std::io::stderr().flush().ok();
|
||||
|
||||
capabilities
|
||||
}
|
||||
|
||||
/// GPU vendor type for encoder detection.
|
||||
#[cfg(target_os = "windows")]
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
enum GpuVendor {
|
||||
Nvidia,
|
||||
Amd,
|
||||
Intel,
|
||||
}
|
||||
|
||||
/// Detect GPU vendors by querying Windows registry.
|
||||
/// This is more reliable than checking for specific DLL paths.
|
||||
#[cfg(target_os = "windows")]
|
||||
fn detect_gpu_vendors() -> Vec<GpuVendor> {
|
||||
use tracing::info;
|
||||
use winreg::enums::HKEY_LOCAL_MACHINE;
|
||||
use winreg::RegKey;
|
||||
|
||||
let mut vendors = Vec::new();
|
||||
|
||||
// Display adapter class GUID in Windows registry
|
||||
const DISPLAY_ADAPTER_CLASS: &str =
|
||||
r"SYSTEM\CurrentControlSet\Control\Class\{4d36e968-e325-11ce-bfc1-08002be10318}";
|
||||
|
||||
info!("[ENCODER_DETECT] Querying Windows registry for GPU vendors...");
|
||||
|
||||
// Open the display adapter class key
|
||||
let hklm = RegKey::predef(HKEY_LOCAL_MACHINE);
|
||||
|
||||
let class_key = match hklm.open_subkey(DISPLAY_ADAPTER_CLASS) {
|
||||
Ok(key) => key,
|
||||
Err(e) => {
|
||||
info!(
|
||||
"[ENCODER_DETECT] Failed to open display adapter registry key: {}",
|
||||
e
|
||||
);
|
||||
return vendors;
|
||||
}
|
||||
};
|
||||
|
||||
// Enumerate subkeys (each subkey is a display adapter)
|
||||
for subkey_name in class_key.enum_keys().filter_map(|k| k.ok()) {
|
||||
if let Ok(adapter_key) = class_key.open_subkey(&subkey_name) {
|
||||
// Read ProviderName
|
||||
if let Ok(provider) = adapter_key.get_value::<String, _>("ProviderName") {
|
||||
info!("[ENCODER_DETECT] Found GPU provider: {}", provider);
|
||||
|
||||
let provider_lower = provider.to_lowercase();
|
||||
if provider_lower.contains("nvidia") && !vendors.contains(&GpuVendor::Nvidia) {
|
||||
vendors.push(GpuVendor::Nvidia);
|
||||
} else if (provider_lower.contains("amd")
|
||||
|| provider_lower.contains("advanced micro devices")
|
||||
|| provider_lower.contains("radeon"))
|
||||
&& !vendors.contains(&GpuVendor::Amd)
|
||||
{
|
||||
vendors.push(GpuVendor::Amd);
|
||||
} else if provider_lower.contains("intel") && !vendors.contains(&GpuVendor::Intel) {
|
||||
vendors.push(GpuVendor::Intel);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
info!("[ENCODER_DETECT] Detected GPU vendors: {:?}", vendors);
|
||||
vendors
|
||||
}
|
||||
|
||||
/// Check if NVIDIA NVENC is available.
|
||||
#[cfg(target_os = "windows")]
|
||||
fn is_nvenc_available() -> bool {
|
||||
// Check for NVENC DLL
|
||||
std::path::Path::new("C:\\Windows\\System32\\nvEncMFTH264.dll").exists()
|
||||
|| std::path::Path::new("C:\\Windows\\System32\\nvEncMFTH265.dll").exists()
|
||||
use tracing::info;
|
||||
use winreg::enums::{HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE};
|
||||
use winreg::RegKey;
|
||||
|
||||
// First check if NVIDIA GPU is present via registry
|
||||
let vendors = detect_gpu_vendors();
|
||||
if !vendors.contains(&GpuVendor::Nvidia) {
|
||||
info!("[ENCODER_DETECT] No NVIDIA GPU detected via registry");
|
||||
return false;
|
||||
}
|
||||
|
||||
// NVIDIA GPU found, now check for NVENC support
|
||||
// NVENC is available on most modern NVIDIA GPUs (GTX 600+ and all RTX cards)
|
||||
// We can verify by checking if nvEncAPI is loadable or by checking registry
|
||||
|
||||
// Check for NVENC capability in registry (NVIDIA stores encoder info here)
|
||||
const NVENC_REGISTRY_PATH: &str = r"SOFTWARE\NVIDIA Corporation\Global\NvEnc";
|
||||
|
||||
let hkcu = RegKey::predef(HKEY_CURRENT_USER);
|
||||
let hklm = RegKey::predef(HKEY_LOCAL_MACHINE);
|
||||
|
||||
// Try HKCU first
|
||||
if hkcu.open_subkey(NVENC_REGISTRY_PATH).is_ok() {
|
||||
info!("[ENCODER_DETECT] NVENC registry key found (HKCU)");
|
||||
return true;
|
||||
}
|
||||
|
||||
// Try HKLM
|
||||
if hklm.open_subkey(NVENC_REGISTRY_PATH).is_ok() {
|
||||
info!("[ENCODER_DETECT] NVENC registry key found (HKLM)");
|
||||
return true;
|
||||
}
|
||||
|
||||
// If NVIDIA GPU is present, assume NVENC is available
|
||||
// Modern NVIDIA GPUs (GTX 600 series and newer) all have NVENC
|
||||
// This is a reasonable fallback since the registry check might not work on all systems
|
||||
info!("[ENCODER_DETECT] NVIDIA GPU detected, assuming NVENC available");
|
||||
true
|
||||
}
|
||||
|
||||
/// Check if AMD AMF is available.
|
||||
#[cfg(target_os = "windows")]
|
||||
fn is_amf_available() -> bool {
|
||||
// Check for AMF runtime
|
||||
std::path::Path::new("C:\\Windows\\System32\\amdocl64.dll").exists()
|
||||
use tracing::info;
|
||||
use winreg::enums::HKEY_LOCAL_MACHINE;
|
||||
use winreg::RegKey;
|
||||
|
||||
// Check if AMD GPU is present via registry
|
||||
let vendors = detect_gpu_vendors();
|
||||
if !vendors.contains(&GpuVendor::Amd) {
|
||||
info!("[ENCODER_DETECT] No AMD GPU detected via registry");
|
||||
return false;
|
||||
}
|
||||
|
||||
// AMD GPU found, check for AMF runtime
|
||||
// AMF is available on modern AMD GPUs (RX 400+ and some older cards)
|
||||
|
||||
// Check for AMF runtime in registry
|
||||
const AMF_REGISTRY_PATH: &str = r"SOFTWARE\AMD\AMF";
|
||||
let hklm = RegKey::predef(HKEY_LOCAL_MACHINE);
|
||||
|
||||
if hklm.open_subkey(AMF_REGISTRY_PATH).is_ok() {
|
||||
info!("[ENCODER_DETECT] AMF registry key found");
|
||||
return true;
|
||||
}
|
||||
|
||||
// Fallback: check for amdocl64.dll or amfrt64.dll in system
|
||||
let system32 = std::env::var("SystemRoot").unwrap_or_else(|_| "C:\\Windows".to_string());
|
||||
let amdocl_path = format!("{}\\System32\\amdocl64.dll", system32);
|
||||
let amfrt_path = format!("{}\\System32\\amfrt64.dll", system32);
|
||||
|
||||
if std::path::Path::new(&amdocl_path).exists() || std::path::Path::new(&amfrt_path).exists() {
|
||||
info!("[ENCODER_DETECT] AMF DLL found");
|
||||
return true;
|
||||
}
|
||||
|
||||
// If AMD GPU is present, assume AMF is available on modern cards
|
||||
info!("[ENCODER_DETECT] AMD GPU detected, assuming AMF available");
|
||||
true
|
||||
}
|
||||
|
||||
/// Check if Intel QuickSync is available.
|
||||
#[cfg(target_os = "windows")]
|
||||
fn is_quicksync_available() -> bool {
|
||||
// Check for Intel Media SDK
|
||||
std::path::Path::new("C:\\Windows\\System32\\mfx64.dll").exists()
|
||||
use tracing::info;
|
||||
|
||||
// Check if Intel GPU is present via registry
|
||||
let vendors = detect_gpu_vendors();
|
||||
if !vendors.contains(&GpuVendor::Intel) {
|
||||
info!("[ENCODER_DETECT] No Intel GPU detected via registry");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Intel GPU found, check for QuickSync (Intel Media SDK / oneVPL)
|
||||
// QuickSync is available on Intel CPUs with integrated graphics (Sandy Bridge and newer)
|
||||
|
||||
// Check for Intel Media SDK or oneVPL
|
||||
let system32 = std::env::var("SystemRoot").unwrap_or_else(|_| "C:\\Windows".to_string());
|
||||
let mfx_path = format!("{}\\System32\\mfx64.dll", system32);
|
||||
let vpl_path = format!("{}\\System32\\libmfx64.dll", system32);
|
||||
|
||||
if std::path::Path::new(&mfx_path).exists() || std::path::Path::new(&vpl_path).exists() {
|
||||
info!("[ENCODER_DETECT] Intel Media SDK DLL found");
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check for oneVPL runtime
|
||||
const VPL_REGISTRY_PATH: &str = r"SOFTWARE\Intel\oneVPL";
|
||||
use winreg::enums::HKEY_LOCAL_MACHINE;
|
||||
use winreg::RegKey;
|
||||
let hklm = RegKey::predef(HKEY_LOCAL_MACHINE);
|
||||
|
||||
if hklm.open_subkey(VPL_REGISTRY_PATH).is_ok() {
|
||||
info!("[ENCODER_DETECT] oneVPL registry key found");
|
||||
return true;
|
||||
}
|
||||
|
||||
// If Intel GPU is present, assume QuickSync might be available
|
||||
// Note: Not all Intel GPUs have QuickSync (some low-end chips don't)
|
||||
info!("[ENCODER_DETECT] Intel GPU detected, assuming QuickSync available");
|
||||
true
|
||||
}
|
||||
|
||||
/// Get the best available encoder capability.
|
||||
|
||||
@@ -142,18 +142,16 @@ impl ObsContext {
|
||||
}
|
||||
|
||||
info!("[OBS_INIT] Starting OBS context initialization...");
|
||||
use std::io::Write;
|
||||
std::io::stderr().flush().ok();
|
||||
|
||||
// Pre-flight checks for OBS
|
||||
self.preflight_checks()?;
|
||||
|
||||
// Detect best available encoder
|
||||
info!("[OBS_INIT] Detecting best available encoder...");
|
||||
std::io::stderr().flush().ok();
|
||||
|
||||
let encoder = best_available_encoder();
|
||||
info!("[OBS_INIT] Detected encoder: {}", encoder.name());
|
||||
std::io::stderr().flush().ok();
|
||||
|
||||
self.encoder_capability = Some(encoder);
|
||||
|
||||
let (width, height) = self.video_settings.quality.resolution();
|
||||
@@ -163,11 +161,10 @@ impl ObsContext {
|
||||
"[OBS_INIT] OBS video config: {}x{} @ {}fps",
|
||||
width, height, fps
|
||||
);
|
||||
std::io::stderr().flush().ok();
|
||||
|
||||
// Create startup info with video configuration
|
||||
info!("[OBS_INIT] Creating OBS video info builder...");
|
||||
std::io::stderr().flush().ok();
|
||||
|
||||
let video_info = ObsVideoInfoBuilder::new()
|
||||
.fps_num(fps)
|
||||
.fps_den(1)
|
||||
@@ -177,10 +174,8 @@ impl ObsContext {
|
||||
.output_height(height)
|
||||
.build();
|
||||
info!("[OBS_INIT] OBS video info built successfully");
|
||||
std::io::stderr().flush().ok();
|
||||
|
||||
info!("[OBS_INIT] Building OBS context with LibObsContext::builder()...");
|
||||
std::io::stderr().flush().ok();
|
||||
|
||||
let compat = LibObsContext::check_version_compatibility();
|
||||
if !compat {
|
||||
@@ -196,12 +191,12 @@ impl ObsContext {
|
||||
let context = match context_result {
|
||||
Ok(Ok(ctx)) => {
|
||||
info!("[OBS_INIT] OBS context created successfully");
|
||||
std::io::stderr().flush().ok();
|
||||
|
||||
ctx
|
||||
}
|
||||
Ok(Err(e)) => {
|
||||
error!("[OBS_INIT] Failed to create OBS context: {:?}", e);
|
||||
std::io::stderr().flush().ok();
|
||||
|
||||
return Err(RecordingError::ObsInitError(format!(
|
||||
"Failed to create OBS context: {:?}",
|
||||
e
|
||||
@@ -213,7 +208,7 @@ impl ObsContext {
|
||||
"[OBS_INIT] PANIC during OBS context creation: {:?}",
|
||||
panic_info
|
||||
);
|
||||
std::io::stderr().flush().ok();
|
||||
|
||||
eprintln!(
|
||||
"[OBS_INIT] PANIC during OBS context creation: {:?}",
|
||||
panic_info
|
||||
@@ -234,41 +229,34 @@ impl ObsContext {
|
||||
|
||||
self.context = Some(context);
|
||||
info!("[OBS_INIT] OBS context initialized successfully");
|
||||
std::io::stderr().flush().ok();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Pre-flight checks for OBS initialization.
|
||||
fn preflight_checks(&self) -> Result<()> {
|
||||
use std::io::Write;
|
||||
|
||||
info!("[PREFLIGHT] Running OBS pre-flight checks...");
|
||||
std::io::stderr().flush().ok();
|
||||
|
||||
// Check for OBS installation directory
|
||||
// libobs-bootstrapper typically extracts OBS to a specific location
|
||||
let obs_paths = self.get_obs_search_paths();
|
||||
|
||||
info!("[PREFLIGHT] OBS search paths: {:?}", obs_paths);
|
||||
std::io::stderr().flush().ok();
|
||||
|
||||
let mut obs_found = false;
|
||||
for path in &obs_paths {
|
||||
if path.exists() {
|
||||
info!("[PREFLIGHT] Found OBS at: {:?}", path);
|
||||
std::io::stderr().flush().ok();
|
||||
|
||||
// Check for plugins directory
|
||||
let plugins_path = path.join("obs-plugins");
|
||||
if plugins_path.exists() {
|
||||
info!("[PREFLIGHT] Found OBS plugins at: {:?}", plugins_path);
|
||||
std::io::stderr().flush().ok();
|
||||
|
||||
// Check for 64-bit plugins
|
||||
let plugins_64 = plugins_path.join("64bit");
|
||||
if plugins_64.exists() {
|
||||
info!("[PREFLIGHT] Found 64-bit plugins at: {:?}", plugins_64);
|
||||
std::io::stderr().flush().ok();
|
||||
|
||||
// List available plugins
|
||||
if let Ok(entries) = std::fs::read_dir(&plugins_64) {
|
||||
@@ -277,7 +265,6 @@ impl ObsContext {
|
||||
.filter_map(|e| e.file_name().to_str().map(|s| s.to_string()))
|
||||
.collect();
|
||||
info!("[PREFLIGHT] Available plugins: {:?}", plugins);
|
||||
std::io::stderr().flush().ok();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -291,14 +278,12 @@ impl ObsContext {
|
||||
warn!(
|
||||
"[PREFLIGHT] OBS installation not found in standard paths - this may cause issues"
|
||||
);
|
||||
std::io::stderr().flush().ok();
|
||||
}
|
||||
|
||||
// Check for display (required for capture)
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
info!("[PREFLIGHT] Checking for display availability...");
|
||||
std::io::stderr().flush().ok();
|
||||
|
||||
// On Windows, check if we have a display
|
||||
use std::ptr;
|
||||
@@ -311,11 +296,10 @@ impl ObsContext {
|
||||
winapi::um::winuser::ReleaseDC(ptr::null_mut(), dc);
|
||||
}
|
||||
}
|
||||
std::io::stderr().flush().ok();
|
||||
}
|
||||
|
||||
info!("[PREFLIGHT] Pre-flight checks completed");
|
||||
std::io::stderr().flush().ok();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -357,13 +341,10 @@ impl ObsContext {
|
||||
|
||||
/// Start recording to the specified output path.
|
||||
pub fn start_recording(&mut self, output_path: &Path) -> Result<()> {
|
||||
use std::io::Write;
|
||||
|
||||
info!(
|
||||
"[START_REC] start_recording called with path: {:?}",
|
||||
output_path
|
||||
);
|
||||
std::io::stderr().flush().ok();
|
||||
|
||||
if self.recording {
|
||||
warn!("[START_REC] Already recording, returning error");
|
||||
@@ -372,25 +353,22 @@ impl ObsContext {
|
||||
|
||||
if self.context.is_none() {
|
||||
info!("[START_REC] OBS context not initialized, initializing now...");
|
||||
std::io::stderr().flush().ok();
|
||||
|
||||
self.initialize()?;
|
||||
info!("[START_REC] OBS initialization complete");
|
||||
std::io::stderr().flush().ok();
|
||||
}
|
||||
|
||||
let context = self.context.as_ref().ok_or_else(|| {
|
||||
error!("[START_REC] OBS not initialized after initialize()");
|
||||
std::io::stderr().flush().ok();
|
||||
|
||||
RecordingError::ObsInitError("OBS not initialized".to_string())
|
||||
})?;
|
||||
|
||||
info!("[START_REC] Starting OBS recording to: {:?}", output_path);
|
||||
std::io::stderr().flush().ok();
|
||||
|
||||
// Get bitrate from encoder preset
|
||||
let bitrate = self.video_settings.encoder_preset.effective_bitrate();
|
||||
info!("[START_REC] Using bitrate: {} kbps", bitrate);
|
||||
std::io::stderr().flush().ok();
|
||||
|
||||
// Create output path
|
||||
let path_str = output_path.to_string_lossy();
|
||||
@@ -401,18 +379,17 @@ impl ObsContext {
|
||||
.encoder_capability
|
||||
.unwrap_or_else(best_available_encoder);
|
||||
info!("[START_REC] Using encoder: {}", encoder.name());
|
||||
std::io::stderr().flush().ok();
|
||||
|
||||
// Build the output based on encoder capability
|
||||
info!("[START_REC] Creating SimpleOutputBuilder...");
|
||||
std::io::stderr().flush().ok();
|
||||
|
||||
let output_result = match encoder {
|
||||
EncoderCapability::Nvenc => {
|
||||
info!("Building NVENC output...");
|
||||
SimpleOutputBuilder::new(context.clone(), ObsString::from("output"), obs_path)
|
||||
.video_bitrate(bitrate)
|
||||
.audio_bitrate(self.audio_settings.bitrate)
|
||||
.hardware_encoder(HardwareCodec::H264, HardwarePreset::Quality)
|
||||
.hardware_encoder(HardwareCodec::H264, HardwarePreset::Speed)
|
||||
.format(OutputFormat::Mpeg4)
|
||||
.build()
|
||||
}
|
||||
@@ -445,21 +422,19 @@ impl ObsContext {
|
||||
};
|
||||
|
||||
info!("[START_REC] Output build complete, checking for errors...");
|
||||
std::io::stderr().flush().ok();
|
||||
|
||||
let output = output_result.map_err(|e| {
|
||||
error!("[START_REC] Failed to create output: {:?}", e);
|
||||
std::io::stderr().flush().ok();
|
||||
|
||||
RecordingError::StartError(format!("Failed to create output: {:?}", e))
|
||||
})?;
|
||||
|
||||
info!("[START_REC] Output created successfully, setting up game capture...");
|
||||
std::io::stderr().flush().ok();
|
||||
|
||||
// Set up game capture source
|
||||
self.setup_game_capture()?;
|
||||
|
||||
info!("[START_REC] Game capture set up, starting output...");
|
||||
std::io::stderr().flush().ok();
|
||||
|
||||
// Start the output - wrap in catch_unwind as this may crash in native code
|
||||
let start_result =
|
||||
@@ -468,18 +443,17 @@ impl ObsContext {
|
||||
match start_result {
|
||||
Ok(Ok(())) => {
|
||||
info!("[START_REC] Output started successfully");
|
||||
std::io::stderr().flush().ok();
|
||||
}
|
||||
Ok(Err(e)) => {
|
||||
error!("[START_REC] Failed to start output: {:?}", e);
|
||||
std::io::stderr().flush().ok();
|
||||
|
||||
return Err(
|
||||
RecordingError::StartError(format!("Failed to start output: {:?}", e)).into(),
|
||||
);
|
||||
}
|
||||
Err(panic_info) => {
|
||||
error!("[START_REC] PANIC starting output: {:?}", panic_info);
|
||||
std::io::stderr().flush().ok();
|
||||
|
||||
eprintln!("[START_REC] PANIC starting output: {:?}", panic_info);
|
||||
return Err(RecordingError::StartError("Panic starting output".to_string()).into());
|
||||
}
|
||||
@@ -490,23 +464,20 @@ impl ObsContext {
|
||||
self.recording = true;
|
||||
|
||||
info!("[START_REC] OBS recording started successfully");
|
||||
std::io::stderr().flush().ok();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Set up capture source with fallback from game capture to monitor capture.
|
||||
#[cfg(target_os = "windows")]
|
||||
fn setup_game_capture(&mut self) -> Result<()> {
|
||||
use std::io::Write;
|
||||
|
||||
info!("[CAPTURE] Setting up capture source...");
|
||||
std::io::stderr().flush().ok();
|
||||
|
||||
// Try game capture first, fall back to monitor capture
|
||||
match self.try_game_capture() {
|
||||
Ok(()) => {
|
||||
info!("[CAPTURE] Game capture set up successfully");
|
||||
std::io::stderr().flush().ok();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Err(e) => {
|
||||
@@ -514,7 +485,7 @@ impl ObsContext {
|
||||
"[CAPTURE] Game capture failed: {}, falling back to monitor capture",
|
||||
e
|
||||
);
|
||||
std::io::stderr().flush().ok();
|
||||
|
||||
self.setup_monitor_capture()
|
||||
}
|
||||
}
|
||||
@@ -526,7 +497,6 @@ impl ObsContext {
|
||||
use std::io::Write;
|
||||
|
||||
info!("[CAPTURE] Setting up screen capture for Linux...");
|
||||
std::io::stderr().flush().ok();
|
||||
|
||||
self.setup_linux_screen_capture()
|
||||
}
|
||||
@@ -536,35 +506,31 @@ impl ObsContext {
|
||||
fn try_game_capture(&mut self) -> Result<()> {
|
||||
use libobs_simple::sources::windows::{GameCaptureSourceBuilder, ObsGameCaptureMode};
|
||||
use libobs_simple::sources::ObsSourceBuilder;
|
||||
use std::io::Write;
|
||||
|
||||
info!("[GAME_CAPTURE] Attempting game capture setup...");
|
||||
std::io::stderr().flush().ok();
|
||||
|
||||
let context = self.context.as_mut().ok_or_else(|| {
|
||||
error!("[GAME_CAPTURE] OBS not initialized in setup_game_capture");
|
||||
std::io::stderr().flush().ok();
|
||||
|
||||
RecordingError::ObsInitError("OBS not initialized".to_string())
|
||||
})?;
|
||||
|
||||
// Create a scene
|
||||
info!("[GAME_CAPTURE] Creating scene 'main'...");
|
||||
std::io::stderr().flush().ok();
|
||||
|
||||
let mut scene = context.scene("main", None).map_err(|e| {
|
||||
error!("[GAME_CAPTURE] Failed to create scene: {:?}", e);
|
||||
std::io::stderr().flush().ok();
|
||||
|
||||
RecordingError::StartError(format!("Failed to create scene: {:?}", e))
|
||||
})?;
|
||||
info!("[GAME_CAPTURE] Scene created successfully");
|
||||
std::io::stderr().flush().ok();
|
||||
|
||||
// Build game capture source
|
||||
info!("[GAME_CAPTURE] Getting OBS runtime...");
|
||||
std::io::stderr().flush().ok();
|
||||
|
||||
let runtime = context.runtime();
|
||||
|
||||
info!("[GAME_CAPTURE] Creating game capture source builder...");
|
||||
std::io::stderr().flush().ok();
|
||||
|
||||
// Wrap game capture builder in catch_unwind as it may crash in native code
|
||||
let builder_result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
|
||||
@@ -574,7 +540,7 @@ impl ObsContext {
|
||||
let builder = match builder_result {
|
||||
Ok(Ok(b)) => {
|
||||
info!("[GAME_CAPTURE] Game capture builder created");
|
||||
std::io::stderr().flush().ok();
|
||||
|
||||
b
|
||||
}
|
||||
Ok(Err(e)) => {
|
||||
@@ -582,7 +548,7 @@ impl ObsContext {
|
||||
"[GAME_CAPTURE] Failed to create game capture builder: {:?}",
|
||||
e
|
||||
);
|
||||
std::io::stderr().flush().ok();
|
||||
|
||||
return Err(RecordingError::StartError(format!(
|
||||
"Failed to create game capture builder: {:?}",
|
||||
e
|
||||
@@ -594,7 +560,7 @@ impl ObsContext {
|
||||
"[GAME_CAPTURE] PANIC creating game capture builder: {:?}",
|
||||
panic_info
|
||||
);
|
||||
std::io::stderr().flush().ok();
|
||||
|
||||
eprintln!(
|
||||
"[GAME_CAPTURE] PANIC creating game capture builder: {:?}",
|
||||
panic_info
|
||||
@@ -607,12 +573,10 @@ impl ObsContext {
|
||||
};
|
||||
|
||||
info!("[GAME_CAPTURE] Configuring game capture for League of Legends...");
|
||||
std::io::stderr().flush().ok();
|
||||
|
||||
// Use "Any" mode to capture any fullscreen application
|
||||
// This is the most reliable mode for games like League of Legends
|
||||
info!("[GAME_CAPTURE] Using 'Any' mode to capture fullscreen games...");
|
||||
std::io::stderr().flush().ok();
|
||||
|
||||
// Wrap source build in catch_unwind
|
||||
let source_result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
|
||||
@@ -622,7 +586,7 @@ impl ObsContext {
|
||||
let source = match source_result {
|
||||
Ok(Ok(s)) => {
|
||||
info!("[GAME_CAPTURE] Game capture source created");
|
||||
std::io::stderr().flush().ok();
|
||||
|
||||
s
|
||||
}
|
||||
Ok(Err(e)) => {
|
||||
@@ -630,7 +594,7 @@ impl ObsContext {
|
||||
"[GAME_CAPTURE] Failed to create game capture source: {:?}",
|
||||
e
|
||||
);
|
||||
std::io::stderr().flush().ok();
|
||||
|
||||
return Err(RecordingError::StartError(format!(
|
||||
"Failed to create game capture source: {:?}",
|
||||
e
|
||||
@@ -642,7 +606,7 @@ impl ObsContext {
|
||||
"[GAME_CAPTURE] PANIC creating game capture source: {:?}",
|
||||
panic_info
|
||||
);
|
||||
std::io::stderr().flush().ok();
|
||||
|
||||
eprintln!(
|
||||
"[GAME_CAPTURE] PANIC creating game capture source: {:?}",
|
||||
panic_info
|
||||
@@ -656,25 +620,24 @@ impl ObsContext {
|
||||
|
||||
// Add source to scene using add_source
|
||||
info!("[GAME_CAPTURE] Adding source to scene...");
|
||||
std::io::stderr().flush().ok();
|
||||
|
||||
scene.add_source(source).map_err(|e| {
|
||||
error!("[GAME_CAPTURE] Failed to add source to scene: {:?}", e);
|
||||
std::io::stderr().flush().ok();
|
||||
|
||||
RecordingError::StartError(format!("Failed to add source to scene: {:?}", e))
|
||||
})?;
|
||||
info!("[GAME_CAPTURE] Source added to scene");
|
||||
std::io::stderr().flush().ok();
|
||||
|
||||
// Set the scene as active
|
||||
info!("[GAME_CAPTURE] Setting scene as active on channel 0...");
|
||||
std::io::stderr().flush().ok();
|
||||
|
||||
scene.set_to_channel(0).map_err(|e| {
|
||||
error!("[GAME_CAPTURE] Failed to set scene: {:?}", e);
|
||||
RecordingError::StartError(format!("Failed to set scene: {:?}", e))
|
||||
})?;
|
||||
|
||||
info!("[GAME_CAPTURE] Game capture source configured successfully");
|
||||
std::io::stderr().flush().ok();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -683,41 +646,37 @@ impl ObsContext {
|
||||
fn setup_monitor_capture(&mut self) -> Result<()> {
|
||||
use libobs_simple::sources::windows::MonitorCaptureSourceBuilder;
|
||||
use libobs_simple::sources::ObsSourceBuilder;
|
||||
use std::io::Write;
|
||||
|
||||
info!("[MONITOR_CAPTURE] Setting up monitor capture as fallback...");
|
||||
std::io::stderr().flush().ok();
|
||||
|
||||
let context = self.context.as_mut().ok_or_else(|| {
|
||||
error!("[MONITOR_CAPTURE] OBS not initialized");
|
||||
std::io::stderr().flush().ok();
|
||||
|
||||
RecordingError::ObsInitError("OBS not initialized".to_string())
|
||||
})?;
|
||||
|
||||
// Create a scene
|
||||
info!("[MONITOR_CAPTURE] Creating scene 'main'...");
|
||||
std::io::stderr().flush().ok();
|
||||
|
||||
let mut scene = context.scene("main", None).map_err(|e| {
|
||||
error!("[MONITOR_CAPTURE] Failed to create scene: {:?}", e);
|
||||
std::io::stderr().flush().ok();
|
||||
|
||||
RecordingError::StartError(format!("Failed to create scene: {:?}", e))
|
||||
})?;
|
||||
info!("[MONITOR_CAPTURE] Scene created successfully");
|
||||
std::io::stderr().flush().ok();
|
||||
|
||||
// Get monitor info
|
||||
info!("[MONITOR_CAPTURE] Detecting monitors...");
|
||||
std::io::stderr().flush().ok();
|
||||
|
||||
let monitors = display_info::DisplayInfo::all().map_err(|e| {
|
||||
error!("[MONITOR_CAPTURE] Failed to get display info: {:?}", e);
|
||||
std::io::stderr().flush().ok();
|
||||
|
||||
RecordingError::StartError(format!("Failed to get display info: {:?}", e))
|
||||
})?;
|
||||
|
||||
if monitors.is_empty() {
|
||||
error!("[MONITOR_CAPTURE] No monitors detected");
|
||||
std::io::stderr().flush().ok();
|
||||
|
||||
return Err(RecordingError::StartError("No monitors detected".to_string()).into());
|
||||
}
|
||||
|
||||
@@ -727,11 +686,9 @@ impl ObsContext {
|
||||
"[MONITOR_CAPTURE] Using monitor: {}x{} at ({}, {})",
|
||||
primary_monitor.width, primary_monitor.height, primary_monitor.x, primary_monitor.y
|
||||
);
|
||||
std::io::stderr().flush().ok();
|
||||
|
||||
// Build monitor capture source
|
||||
info!("[MONITOR_CAPTURE] Creating monitor capture source builder...");
|
||||
std::io::stderr().flush().ok();
|
||||
|
||||
let builder_result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
|
||||
MonitorCaptureSourceBuilder::new("monitor_capture", context.runtime().clone())
|
||||
@@ -740,7 +697,7 @@ impl ObsContext {
|
||||
let builder = match builder_result {
|
||||
Ok(Ok(b)) => {
|
||||
info!("[MONITOR_CAPTURE] Monitor capture builder created");
|
||||
std::io::stderr().flush().ok();
|
||||
|
||||
b
|
||||
}
|
||||
Ok(Err(e)) => {
|
||||
@@ -748,7 +705,7 @@ impl ObsContext {
|
||||
"[MONITOR_CAPTURE] Failed to create monitor capture builder: {:?}",
|
||||
e
|
||||
);
|
||||
std::io::stderr().flush().ok();
|
||||
|
||||
return Err(RecordingError::StartError(format!(
|
||||
"Failed to create monitor capture builder: {:?}",
|
||||
e
|
||||
@@ -760,7 +717,7 @@ impl ObsContext {
|
||||
"[MONITOR_CAPTURE] PANIC creating monitor capture builder: {:?}",
|
||||
panic_info
|
||||
);
|
||||
std::io::stderr().flush().ok();
|
||||
|
||||
eprintln!(
|
||||
"[MONITOR_CAPTURE] PANIC creating monitor capture builder: {:?}",
|
||||
panic_info
|
||||
@@ -779,7 +736,7 @@ impl ObsContext {
|
||||
let source = match source_result {
|
||||
Ok(Ok(s)) => {
|
||||
info!("[MONITOR_CAPTURE] Monitor capture source created");
|
||||
std::io::stderr().flush().ok();
|
||||
|
||||
s
|
||||
}
|
||||
Ok(Err(e)) => {
|
||||
@@ -787,7 +744,7 @@ impl ObsContext {
|
||||
"[MONITOR_CAPTURE] Failed to create monitor capture source: {:?}",
|
||||
e
|
||||
);
|
||||
std::io::stderr().flush().ok();
|
||||
|
||||
return Err(RecordingError::StartError(format!(
|
||||
"Failed to create monitor capture source: {:?}",
|
||||
e
|
||||
@@ -799,7 +756,7 @@ impl ObsContext {
|
||||
"[MONITOR_CAPTURE] PANIC creating monitor capture source: {:?}",
|
||||
panic_info
|
||||
);
|
||||
std::io::stderr().flush().ok();
|
||||
|
||||
eprintln!(
|
||||
"[MONITOR_CAPTURE] PANIC creating monitor capture source: {:?}",
|
||||
panic_info
|
||||
@@ -813,25 +770,24 @@ impl ObsContext {
|
||||
|
||||
// Add source to scene
|
||||
info!("[MONITOR_CAPTURE] Adding source to scene...");
|
||||
std::io::stderr().flush().ok();
|
||||
|
||||
scene.add_source(source).map_err(|e| {
|
||||
error!("[MONITOR_CAPTURE] Failed to add source to scene: {:?}", e);
|
||||
std::io::stderr().flush().ok();
|
||||
|
||||
RecordingError::StartError(format!("Failed to add source to scene: {:?}", e))
|
||||
})?;
|
||||
info!("[MONITOR_CAPTURE] Source added to scene");
|
||||
std::io::stderr().flush().ok();
|
||||
|
||||
// Set the scene as active
|
||||
info!("[MONITOR_CAPTURE] Setting scene as active on channel 0...");
|
||||
std::io::stderr().flush().ok();
|
||||
|
||||
scene.set_to_channel(0).map_err(|e| {
|
||||
error!("[MONITOR_CAPTURE] Failed to set scene: {:?}", e);
|
||||
RecordingError::StartError(format!("Failed to set scene: {:?}", e))
|
||||
})?;
|
||||
|
||||
info!("[MONITOR_CAPTURE] Monitor capture source configured successfully");
|
||||
std::io::stderr().flush().ok();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -843,29 +799,26 @@ impl ObsContext {
|
||||
use std::io::Write;
|
||||
|
||||
info!("[LINUX_CAPTURE] Setting up Linux screen capture...");
|
||||
std::io::stderr().flush().ok();
|
||||
|
||||
let context = self.context.as_mut().ok_or_else(|| {
|
||||
error!("[LINUX_CAPTURE] OBS not initialized");
|
||||
std::io::stderr().flush().ok();
|
||||
|
||||
RecordingError::ObsInitError("OBS not initialized".to_string())
|
||||
})?;
|
||||
|
||||
// Create a scene
|
||||
info!("[LINUX_CAPTURE] Creating scene 'main'...");
|
||||
std::io::stderr().flush().ok();
|
||||
|
||||
let mut scene = context.scene("main", None).map_err(|e| {
|
||||
error!("[LINUX_CAPTURE] Failed to create scene: {:?}", e);
|
||||
std::io::stderr().flush().ok();
|
||||
|
||||
RecordingError::StartError(format!("Failed to create scene: {:?}", e))
|
||||
})?;
|
||||
info!("[LINUX_CAPTURE] Scene created successfully");
|
||||
std::io::stderr().flush().ok();
|
||||
|
||||
// Build screen capture source using LinuxGeneralScreenCaptureBuilder
|
||||
// This automatically uses X11 or PipeWire depending on the platform
|
||||
info!("[LINUX_CAPTURE] Creating screen capture source builder...");
|
||||
std::io::stderr().flush().ok();
|
||||
|
||||
let builder_result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
|
||||
LinuxGeneralScreenCaptureBuilder::new("screen_capture", context.runtime().clone())
|
||||
@@ -874,7 +827,7 @@ impl ObsContext {
|
||||
let builder = match builder_result {
|
||||
Ok(Ok(b)) => {
|
||||
info!("[LINUX_CAPTURE] Screen capture builder created");
|
||||
std::io::stderr().flush().ok();
|
||||
|
||||
b
|
||||
}
|
||||
Ok(Err(e)) => {
|
||||
@@ -882,7 +835,7 @@ impl ObsContext {
|
||||
"[LINUX_CAPTURE] Failed to create screen capture builder: {:?}",
|
||||
e
|
||||
);
|
||||
std::io::stderr().flush().ok();
|
||||
|
||||
return Err(RecordingError::StartError(format!(
|
||||
"Failed to create screen capture builder: {:?}",
|
||||
e
|
||||
@@ -894,7 +847,7 @@ impl ObsContext {
|
||||
"[LINUX_CAPTURE] PANIC creating screen capture builder: {:?}",
|
||||
panic_info
|
||||
);
|
||||
std::io::stderr().flush().ok();
|
||||
|
||||
eprintln!(
|
||||
"[LINUX_CAPTURE] PANIC creating screen capture builder: {:?}",
|
||||
panic_info
|
||||
@@ -913,7 +866,7 @@ impl ObsContext {
|
||||
let source = match source_result {
|
||||
Ok(Ok(s)) => {
|
||||
info!("[LINUX_CAPTURE] Screen capture source created");
|
||||
std::io::stderr().flush().ok();
|
||||
|
||||
s
|
||||
}
|
||||
Ok(Err(e)) => {
|
||||
@@ -921,7 +874,7 @@ impl ObsContext {
|
||||
"[LINUX_CAPTURE] Failed to create screen capture source: {:?}",
|
||||
e
|
||||
);
|
||||
std::io::stderr().flush().ok();
|
||||
|
||||
return Err(RecordingError::StartError(format!(
|
||||
"Failed to create screen capture source: {:?}",
|
||||
e
|
||||
@@ -933,7 +886,7 @@ impl ObsContext {
|
||||
"[LINUX_CAPTURE] PANIC creating screen capture source: {:?}",
|
||||
panic_info
|
||||
);
|
||||
std::io::stderr().flush().ok();
|
||||
|
||||
eprintln!(
|
||||
"[LINUX_CAPTURE] PANIC creating screen capture source: {:?}",
|
||||
panic_info
|
||||
@@ -947,25 +900,24 @@ impl ObsContext {
|
||||
|
||||
// Add source to scene
|
||||
info!("[LINUX_CAPTURE] Adding source to scene...");
|
||||
std::io::stderr().flush().ok();
|
||||
|
||||
scene.add_source(source).map_err(|e| {
|
||||
error!("[LINUX_CAPTURE] Failed to add source to scene: {:?}", e);
|
||||
std::io::stderr().flush().ok();
|
||||
|
||||
RecordingError::StartError(format!("Failed to add source to scene: {:?}", e))
|
||||
})?;
|
||||
info!("[LINUX_CAPTURE] Source added to scene");
|
||||
std::io::stderr().flush().ok();
|
||||
|
||||
// Set the scene as active
|
||||
info!("[LINUX_CAPTURE] Setting scene as active on channel 0...");
|
||||
std::io::stderr().flush().ok();
|
||||
|
||||
scene.set_to_channel(0).map_err(|e| {
|
||||
error!("[LINUX_CAPTURE] Failed to set scene: {:?}", e);
|
||||
RecordingError::StartError(format!("Failed to set scene: {:?}", e))
|
||||
})?;
|
||||
|
||||
info!("[LINUX_CAPTURE] Screen capture source configured successfully");
|
||||
std::io::stderr().flush().ok();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user