record-daemon: initial commit

This commit is contained in:
2026-03-19 17:48:07 +01:00
commit d6c0334369
30 changed files with 9486 additions and 0 deletions

View File

@@ -0,0 +1,446 @@
# Record Daemon Architecture
## Overview
The record-daemon is a high-performance Rust daemon that automatically records League of Legends matches using libobs, captures game events via the League Client API (LQP), and exposes configuration via a Unix socket IPC for the Tauri app.
## High-Level Architecture
```mermaid
flowchart TB
subgraph Daemon[Record Daemon]
direction TB
Main[Main Event Loop]
State[State Machine]
subgraph Core[Core Modules]
Config[Configuration Manager]
LQP[LQP Client]
OBS[libobs Recording Engine]
IPC[IPC Server]
Timeline[Event Timeline Store]
end
end
subgraph External[External Systems]
LC[League Client]
Game[League of Legends Game]
Tauri[Tauri App]
FS[File System]
end
LQP -->|WebSocket| LC
OBS -->|Game Capture| Game
IPC -->|Unix Socket| Tauri
Config -->|JSON/TOML| FS
Timeline -->|SQLite/JSON| FS
OBS -->|Video Files| FS
Main --> State
State --> Config
State --> LQP
State --> OBS
State --> IPC
State --> Timeline
```
## Module Structure
```
record-daemon/
├── Cargo.toml
├── src/
│ ├── main.rs # Entry point, daemon setup
│ ├── lib.rs # Library exports
│ ├── config/
│ │ ├── mod.rs # Config module exports
│ │ ├── settings.rs # User-configurable settings
│ │ ├── presets.rs # Encoding presets (quality/performance)
│ │ └── persistence.rs # Config file I/O
│ ├── lqp/
│ │ ├── mod.rs # LQP module exports
│ │ ├── client.rs # LQP WebSocket client
│ │ ├── events.rs # Game event types and parsing
│ │ └── auth.rs # LQP authentication (port/password from lockfile)
│ ├── recording/
│ │ ├── mod.rs # Recording module exports
│ │ ├── obs_context.rs # libobs initialization and context
│ │ ├── capture.rs # Game capture source setup
│ │ ├── encoder.rs # Video encoder configuration
│ │ └── output.rs # File output configuration
│ ├── ipc/
│ │ ├── mod.rs # IPC module exports
│ │ ├── server.rs # Unix socket server
│ │ ├── protocol.rs # Request/response protocol
│ │ └── handlers.rs # Command handlers
│ ├── timeline/
│ │ ├── mod.rs # Timeline module exports
│ │ ├── store.rs # Event storage backend
│ │ └── mapper.rs # Event-to-video timestamp mapping
│ ├── state/
│ │ ├── mod.rs # State module exports
│ │ ├── machine.rs # Daemon state machine
│ │ └── transitions.rs # State transition logic
│ └── error.rs # Error types and handling
└── tests/
└── ...
```
## State Machine
The daemon operates as a finite state machine with the following states:
```mermaid
stateDiagram-v2
[*] --> Idle: Daemon Start
Idle --> Monitoring: League Client Detected
Monitoring --> Idle: League Client Closed
Monitoring --> Recording: Match Started
Recording --> Monitoring: Match Ended
Recording --> Recording: Event Received
Idle --> Error: Fatal Error
Monitoring --> Error: Fatal Error
Recording --> Error: Fatal Error
Error --> Idle: Recovery
```
### State Descriptions
| State | Description |
|-------|-------------|
| **Idle** | Daemon is running but League Client is not detected |
| **Monitoring** | League Client is running, waiting for match to start |
| **Recording** | Active recording in progress, capturing events |
| **Error** | Recoverable error state, attempting recovery |
## Data Flow
### Match Recording Flow
```mermaid
sequenceDiagram
participant D as Daemon
participant LC as League Client
participant OBS as libobs
participant TL as Timeline
participant FS as FileSystem
Note over D: State: Monitoring
LC->>D: MatchFound event via LQP
D->>OBS: Initialize recording context
D->>FS: Create output file
Note over D: State: Recording
LC->>D: GameStart event
D->>OBS: Start recording
D->>TL: Record start timestamp
loop Game Events
LC->>D: Kill/Death/Objective events
D->>TL: Store event with timestamp
end
LC->>D: GameEnd event
D->>OBS: Stop recording
D->>TL: Finalize timeline
D->>FS: Save timeline metadata
Note over D: State: Monitoring
```
### IPC Communication Flow
```mermaid
sequenceDiagram
participant T as Tauri App
participant IPC as IPC Server
participant D as Daemon Core
participant C as Config Manager
T->>IPC: Connect via Unix Socket
T->>IPC: GetSettings request
IPC->>D: Handle request
D->>C: Read current settings
C-->>D: Settings data
D-->>IPC: Settings response
IPC-->>T: JSON response
T->>IPC: UpdateSettings request
IPC->>D: Handle request
D->>C: Validate and save
C-->>D: Success
D-->>IPC: Confirmation
IPC-->>T: Success response
```
## Key Components
### 1. Configuration Module
Handles user-configurable settings with hot-reload support.
```rust
// Key configuration structures
struct Settings {
output_path: PathBuf,
encoder_preset: EncoderPreset,
frame_rate: u32,
quality: QualityLevel,
audio_capture: AudioSettings,
}
enum EncoderPreset {
Nvenc { bitrate: u32, cq_level: u32 },
Amf { bitrate: u32 },
X264 { preset: String, bitrate: u32 },
}
enum QualityLevel {
Low, // 720p30, lower bitrate
Medium, // 1080p30, moderate bitrate
High, // 1080p60, high bitrate
Ultra, // 1440p60, max quality
}
```
### 2. LQP Client
Connects to the League Client via WebSocket using credentials from the lockfile.
```rust
struct LqpClient {
port: u16,
password: String,
websocket: Option<WebSocket>,
}
// Key events to capture
enum GameEvent {
MatchFound(MatchInfo),
GameStart(GameStartInfo),
Kill(KillEvent),
Death(DeathEvent),
Objective(ObjectiveEvent),
GameEnd(GameEndInfo),
}
```
### 3. libobs Recording Engine
Manages OBS context, capture sources, and encoding.
```rust
struct ObsRecordingEngine {
context: ObsContext,
video_encoder: Box<dyn VideoEncoder>,
audio_encoder: Box<dyn AudioEncoder>,
output: Box<dyn Output>,
active: bool,
}
impl ObsRecordingEngine {
fn initialize(config: &Settings) -> Result<Self>;
fn start_recording(&mut self, output_path: &Path) -> Result<()>;
fn stop_recording(&mut self) -> Result<RecordingResult>;
fn get_current_timestamp(&self) -> Duration;
}
```
### 4. IPC Server
Unix socket server for communication with Tauri app.
```rust
struct IpcServer {
socket_path: PathBuf,
listener: UnixListener,
handlers: HashMap<IpcCommand, CommandHandler>,
}
enum IpcCommand {
GetSettings,
UpdateSettings,
GetStatus,
GetRecordings,
GetTimeline { recording_id: Uuid },
StartRecording,
StopRecording,
}
```
### 5. Event Timeline Store
Stores game events mapped to video timestamps.
```rust
struct TimelineStore {
backend: StorageBackend,
}
struct RecordingMetadata {
id: Uuid,
game_id: u64,
start_time: DateTime<Utc>,
end_time: Option<DateTime<Utc>>,
output_file: PathBuf,
events: Vec<TimestampedEvent>,
}
struct TimestampedEvent {
video_timestamp: Duration, // Offset from recording start
game_timestamp: Duration, // Game time
event: GameEvent,
}
```
## Performance Considerations
### Memory Management
- Use `Arc<RwLock<T>>` for shared state between modules
- Pre-allocate buffers for video encoding
- Use `crossbeam` channels for inter-thread communication
- Implement backpressure on event channels
### CPU Efficiency
- Run libobs on dedicated thread
- Use async/await with `tokio` for I/O operations
- Minimize allocations in hot paths
- Use `parking_lot` locks for better performance
### GPU Acceleration
- Prioritize NVENC/AMF hardware encoding
- Fall back to software encoding only if unavailable
- Configure GPU-based video capture
### Disk I/O
- Use buffered writes for video output
- Store timeline data in memory during recording
- Flush to disk only on recording end
- Consider SSD for output directory
## Dependencies
```toml
[dependencies]
# Async runtime
tokio = { version = "1", features = ["full"] }
# Serialization
serde = { version = "1", features = ["derive"] }
serde_json = "1"
toml = "0.8"
# libobs bindings
libobs-rs = "0.1" # or appropriate version
# WebSocket for LQP
tokio-tungstenite = "0.21"
# HTTP client for LQP REST API
reqwest = { version = "0.11", features = ["json", "rustls-tls"] }
# Async utilities
futures = "0.3"
async-trait = "0.1"
# Error handling
thiserror = "1"
anyhow = "1"
# Logging
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
# UUID for recording IDs
uuid = { version = "1", features = ["v4", "serde"] }
# Date/time
chrono = { version = "0.4", features = ["serde"] }
# Locking primitives
parking_lot = "0.12"
# Crossbeam channels
crossbeam = "0.8"
# Unix socket IPC
tokio-util = { version = "0.7", features = ["codec"] }
# Configuration directories
directories = "5"
# CLI (optional, for debugging)
clap = { version = "4", features = ["derive"] }
```
## IPC Protocol Specification
### Message Format
All messages are JSON-encoded with the following structure:
```json
{
"type": "request|response|notification",
"id": "uuid-v4",
"command": "CommandName",
"payload": { ... }
}
```
### Commands
| Command | Direction | Payload | Description |
|---------|-----------|---------|-------------|
| `GetSettings` | Request → | `{}` | Get current settings |
| `GetSettings` | ← Response | `Settings` | Current settings object |
| `UpdateSettings` | Request → | `Settings` | Update settings |
| `UpdateSettings` | ← Response | `{ "success": true }` | Confirmation |
| `GetStatus` | Request → | `{}` | Get daemon status |
| `GetStatus` | ← Response | `DaemonStatus` | Current state and info |
| `GetRecordings` | Request → | `{}` | List all recordings |
| `GetRecordings` | ← Response | `[RecordingMetadata]` | Recording list |
| `GetTimeline` | Request → | `{ "recording_id": "uuid" }` | Get specific timeline |
| `GetTimeline` | ← Response | `Timeline` | Event timeline |
| `RecordingStarted` | ← Notification | `RecordingInfo` | Recording started |
| `RecordingStopped` | ← Notification | `RecordingResult` | Recording ended |
| `GameEvent` | ← Notification | `GameEvent` | Live game event |
## Error Handling Strategy
```rust
#[derive(Debug, thiserror::Error)]
pub enum DaemonError {
#[error("LQP connection failed: {0}")]
LqpConnection(#[from] LqpError),
#[error("Recording error: {0}")]
Recording(#[from] RecordingError),
#[error("Configuration error: {0}")]
Config(#[from] ConfigError),
#[error("IPC error: {0}")]
Ipc(#[from] IpcError),
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
}
```
## Next Steps
1. Set up `Cargo.toml` with all required dependencies
2. Implement configuration module first (needed by all other modules)
3. Implement LQP client for game detection and event capture
4. Implement libobs recording engine
5. Implement IPC server
6. Implement timeline storage
7. Wire everything together in the main daemon loop
8. Add comprehensive logging and error handling
9. Write tests and documentation