record-daemon: obs-context refactoring, gpu detection code
Some checks failed
record-daemon / Build, check and test (push) Failing after 48s

This commit is contained in:
2026-03-21 18:54:01 +01:00
parent 5d4fecc108
commit 8e885ffc64
5 changed files with 262 additions and 141 deletions

View File

@@ -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"

View File

@@ -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

View File

@@ -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;

View File

@@ -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.

View File

@@ -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(())
}