diff --git a/record-daemon/Cargo.lock b/record-daemon/Cargo.lock index 9dcd723..c2647e3 100644 --- a/record-daemon/Cargo.lock +++ b/record-daemon/Cargo.lock @@ -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" diff --git a/record-daemon/Cargo.toml b/record-daemon/Cargo.toml index 3d3f7a2..268d102 100644 --- a/record-daemon/Cargo.toml +++ b/record-daemon/Cargo.toml @@ -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 diff --git a/record-daemon/src/main.rs b/record-daemon/src/main.rs index 1265cc8..6fad9c7 100644 --- a/record-daemon/src/main.rs +++ b/record-daemon/src/main.rs @@ -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; diff --git a/record-daemon/src/recording/encoder.rs b/record-daemon/src/recording/encoder.rs index d6a878c..24052c6 100644 --- a/record-daemon/src/recording/encoder.rs +++ b/record-daemon/src/recording/encoder.rs @@ -294,11 +294,9 @@ impl EncoderCapability { /// /// This function checks the system for available GPU encoders. pub fn detect_hardware_encoders() -> Vec { - 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 { // 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 { // 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 { // 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 { 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 { + 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::("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. diff --git a/record-daemon/src/recording/obs_context.rs b/record-daemon/src/recording/obs_context.rs index f67853d..5c80d08 100644 --- a/record-daemon/src/recording/obs_context.rs +++ b/record-daemon/src/recording/obs_context.rs @@ -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(()) }