record-daemon: fix obs recording
This commit is contained in:
@@ -1,11 +1,13 @@
|
||||
//! Game capture source configuration.
|
||||
//! Game capture source configuration using libobs-simple.
|
||||
//!
|
||||
//! This module provides capture sources for recording game footage,
|
||||
//! including game capture, window capture, and monitor capture.
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tracing::{debug, info};
|
||||
|
||||
use crate::error::Result;
|
||||
|
||||
/// Game capture source for recording game footage.
|
||||
///
|
||||
/// Uses OBS game capture for efficient GPU-based capture.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct GameCapture {
|
||||
/// Source name.
|
||||
@@ -18,6 +20,8 @@ pub struct GameCapture {
|
||||
pub mode: CaptureMode,
|
||||
/// Whether to capture cursor.
|
||||
pub capture_cursor: bool,
|
||||
/// Window class (Windows only).
|
||||
pub window_class: Option<String>,
|
||||
}
|
||||
|
||||
impl Default for GameCapture {
|
||||
@@ -26,8 +30,9 @@ impl Default for GameCapture {
|
||||
name: "Game Capture".to_string(),
|
||||
window: None,
|
||||
process_name: Some("League of Legends.exe".to_string()),
|
||||
mode: CaptureMode::Any,
|
||||
mode: CaptureMode::Process,
|
||||
capture_cursor: false,
|
||||
window_class: Some("RiotWindowClass".to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -41,6 +46,18 @@ impl GameCapture {
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a game capture configured for League of Legends.
|
||||
pub fn for_league_of_legends() -> Self {
|
||||
Self {
|
||||
name: "League of Legends Capture".to_string(),
|
||||
window: Some("League of Legends (TM) Client".to_string()),
|
||||
process_name: Some("League of Legends.exe".to_string()),
|
||||
mode: CaptureMode::Window,
|
||||
capture_cursor: false,
|
||||
window_class: Some("RiotWindowClass".to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the window to capture.
|
||||
pub fn with_window(mut self, window: &str) -> Self {
|
||||
self.window = Some(window.to_string());
|
||||
@@ -55,105 +72,56 @@ impl GameCapture {
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the window class (Windows only).
|
||||
pub fn with_window_class(mut self, class: &str) -> Self {
|
||||
self.window_class = Some(class.to_string());
|
||||
self
|
||||
}
|
||||
|
||||
/// Set whether to capture the cursor.
|
||||
pub fn with_cursor(mut self, capture: bool) -> Self {
|
||||
self.capture_cursor = capture;
|
||||
self
|
||||
}
|
||||
|
||||
/// Create the OBS source.
|
||||
///
|
||||
/// Note: This would create the actual obs_source_t in libobs.
|
||||
pub fn create_source(&self) -> Result<CaptureSource> {
|
||||
info!("Creating game capture source: {}", self.name);
|
||||
|
||||
// Note: Actual libobs source creation would happen here
|
||||
// obs_source_create("game_capture", name, settings, nullptr)
|
||||
|
||||
let source = CaptureSource {
|
||||
name: self.name.clone(),
|
||||
source_type: SourceType::GameCapture,
|
||||
active: false,
|
||||
};
|
||||
|
||||
debug!("Game capture source created");
|
||||
Ok(source)
|
||||
/// Get the window string for OBS in the format "Title:Class:Executable".
|
||||
pub fn window_string(&self) -> Option<String> {
|
||||
match (&self.window, &self.window_class, &self.process_name) {
|
||||
(Some(window), Some(class), Some(process)) => {
|
||||
Some(format!("{}:{}:{}", window, class, process))
|
||||
}
|
||||
(Some(window), None, Some(process)) => Some(format!("{}::{}", window, process)),
|
||||
(Some(window), Some(class), None) => Some(format!("{}:{}:", window, class)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Capture mode.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
#[derive(Default)]
|
||||
pub enum CaptureMode {
|
||||
/// Capture any fullscreen application.
|
||||
Any,
|
||||
/// Capture a specific window.
|
||||
Window,
|
||||
/// Capture a specific process.
|
||||
#[default]
|
||||
Process,
|
||||
}
|
||||
|
||||
/// Capture source abstraction.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CaptureSource {
|
||||
/// Source name.
|
||||
pub name: String,
|
||||
/// Source type.
|
||||
pub source_type: SourceType,
|
||||
/// Whether the source is active.
|
||||
pub active: bool,
|
||||
}
|
||||
|
||||
impl CaptureSource {
|
||||
/// Check if the source is active.
|
||||
pub fn is_active(&self) -> bool {
|
||||
self.active
|
||||
}
|
||||
|
||||
/// Activate the source.
|
||||
pub fn activate(&mut self) -> Result<()> {
|
||||
if self.active {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
debug!("Activating capture source: {}", self.name);
|
||||
// Note: Actual activation would involve obs_source_set_enabled
|
||||
self.active = true;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Deactivate the source.
|
||||
pub fn deactivate(&mut self) -> Result<()> {
|
||||
if !self.active {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
debug!("Deactivating capture source: {}", self.name);
|
||||
// Note: Actual deactivation would involve obs_source_set_enabled
|
||||
self.active = false;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Source type.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum SourceType {
|
||||
/// Game capture source.
|
||||
GameCapture,
|
||||
/// Window capture source.
|
||||
WindowCapture,
|
||||
/// Monitor capture source.
|
||||
MonitorCapture,
|
||||
}
|
||||
|
||||
/// Window capture source (alternative to game capture).
|
||||
///
|
||||
/// Uses OBS window capture which works with more applications
|
||||
/// but may have slightly higher overhead than game capture.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct WindowCapture {
|
||||
/// Source name.
|
||||
pub name: String,
|
||||
/// Window title.
|
||||
pub window_title: String,
|
||||
/// Window class (X11).
|
||||
/// Window class (X11/Windows).
|
||||
pub window_class: Option<String>,
|
||||
/// Whether to capture cursor.
|
||||
pub capture_cursor: bool,
|
||||
@@ -170,35 +138,28 @@ impl WindowCapture {
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the window class (for X11).
|
||||
/// Set the window class (for X11/Windows).
|
||||
pub fn with_class(mut self, class: &str) -> Self {
|
||||
self.window_class = Some(class.to_string());
|
||||
self
|
||||
}
|
||||
|
||||
/// Create the OBS source.
|
||||
pub fn create_source(&self) -> Result<CaptureSource> {
|
||||
info!(
|
||||
"Creating window capture source: {} ({})",
|
||||
self.name, self.window_title
|
||||
);
|
||||
|
||||
let source = CaptureSource {
|
||||
name: self.name.clone(),
|
||||
source_type: SourceType::WindowCapture,
|
||||
active: false,
|
||||
};
|
||||
|
||||
Ok(source)
|
||||
/// Set whether to capture the cursor.
|
||||
pub fn with_cursor(mut self, capture: bool) -> Self {
|
||||
self.capture_cursor = capture;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Monitor capture source (fallback).
|
||||
///
|
||||
/// Captures an entire monitor. Useful as a fallback when
|
||||
/// game capture or window capture don't work.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MonitorCapture {
|
||||
/// Source name.
|
||||
pub name: String,
|
||||
/// Monitor index.
|
||||
/// Monitor index (0-based).
|
||||
pub monitor: u32,
|
||||
/// Whether to capture cursor.
|
||||
pub capture_cursor: bool,
|
||||
@@ -214,39 +175,39 @@ impl MonitorCapture {
|
||||
}
|
||||
}
|
||||
|
||||
/// Create the OBS source.
|
||||
pub fn create_source(&self) -> Result<CaptureSource> {
|
||||
info!(
|
||||
"Creating monitor capture source: {} (monitor {})",
|
||||
self.name, self.monitor
|
||||
);
|
||||
|
||||
let source = CaptureSource {
|
||||
name: self.name.clone(),
|
||||
source_type: SourceType::MonitorCapture,
|
||||
active: false,
|
||||
};
|
||||
|
||||
Ok(source)
|
||||
/// Set whether to capture the cursor.
|
||||
pub fn with_cursor(mut self, capture: bool) -> Self {
|
||||
self.capture_cursor = capture;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Find the League of Legends game window.
|
||||
pub fn find_league_window() -> Option<String> {
|
||||
// Note: Actual window finding would use platform-specific APIs
|
||||
// On Linux: X11/Wayland
|
||||
// On Windows: Win32 API
|
||||
/// Capture source type.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum SourceType {
|
||||
/// Game capture source.
|
||||
GameCapture,
|
||||
/// Window capture source.
|
||||
WindowCapture,
|
||||
/// Monitor capture source.
|
||||
MonitorCapture,
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
/// Find the League of Legends game window.
|
||||
///
|
||||
/// Returns the window title if found, or a default if not found.
|
||||
pub fn find_league_window() -> Option<String> {
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
// Would use x11rb or similar to find window
|
||||
// On Windows, we can use the Win32 API to find the window
|
||||
// For now, return the expected window title
|
||||
Some("League of Legends (TM) Client".to_string())
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
// Would use FindWindowW
|
||||
// On Linux, we would use X11 or Wayland APIs
|
||||
// For now, return the expected window title
|
||||
Some("League of Legends (TM) Client".to_string())
|
||||
}
|
||||
|
||||
@@ -254,6 +215,11 @@ pub fn find_league_window() -> Option<String> {
|
||||
None
|
||||
}
|
||||
|
||||
/// Get the default capture configuration for League of Legends.
|
||||
pub fn league_capture_config() -> GameCapture {
|
||||
GameCapture::for_league_of_legends()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -262,7 +228,7 @@ mod tests {
|
||||
fn test_game_capture_creation() {
|
||||
let capture = GameCapture::new("Test Capture");
|
||||
assert_eq!(capture.name, "Test Capture");
|
||||
assert_eq!(capture.mode, CaptureMode::Any);
|
||||
assert_eq!(capture.mode, CaptureMode::Process);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -277,17 +243,23 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_capture_source_activation() {
|
||||
let mut source = CaptureSource {
|
||||
name: "Test".to_string(),
|
||||
source_type: SourceType::GameCapture,
|
||||
active: false,
|
||||
};
|
||||
fn test_league_capture_config() {
|
||||
let capture = league_capture_config();
|
||||
assert_eq!(capture.name, "League of Legends Capture");
|
||||
assert_eq!(
|
||||
capture.process_name,
|
||||
Some("League of Legends.exe".to_string())
|
||||
);
|
||||
assert_eq!(capture.window_class, Some("RiotWindowClass".to_string()));
|
||||
}
|
||||
|
||||
assert!(!source.is_active());
|
||||
source.activate().unwrap();
|
||||
assert!(source.is_active());
|
||||
source.deactivate().unwrap();
|
||||
assert!(!source.is_active());
|
||||
#[test]
|
||||
fn test_window_string() {
|
||||
let capture = GameCapture::for_league_of_legends();
|
||||
let window_str = capture.window_string();
|
||||
assert!(window_str.is_some());
|
||||
let s = window_str.unwrap();
|
||||
assert!(s.contains("League of Legends"));
|
||||
assert!(s.contains("RiotWindowClass"));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user