12 KiB
12 KiB
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
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:
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
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
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.
// 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.
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.
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.
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.
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
crossbeamchannels for inter-thread communication - Implement backpressure on event channels
CPU Efficiency
- Run libobs on dedicated thread
- Use async/await with
tokiofor I/O operations - Minimize allocations in hot paths
- Use
parking_lotlocks 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
[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:
{
"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
#[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
- Set up
Cargo.tomlwith all required dependencies - Implement configuration module first (needed by all other modules)
- Implement LQP client for game detection and event capture
- Implement libobs recording engine
- Implement IPC server
- Implement timeline storage
- Wire everything together in the main daemon loop
- Add comprehensive logging and error handling
- Write tests and documentation