15 KiB
Record Daemon
A high-performance League of Legends recording daemon built with Rust and libobs.
Features
- Automatic Recording: Detects when League of Legends starts and automatically records matches
- Game Event Capture: Captures game events (kills, deaths, objectives) via the League Client API (LQP)
- Event Timeline: Maps game events to video timestamps for easy navigation
- Hardware Encoding: Supports NVIDIA NVENC, AMD AMF, and software encoding (x264)
- IPC Interface: Unix sockets (Linux) / Named pipes (Windows) for configuration and control from a Tauri app
- Configurable Presets: Quality presets from 720p30 to 1440p60
- Cross-Platform: Supports Windows and Linux
Architecture
record-daemon/
├── src/
│ ├── main.rs # Entry point, daemon setup
│ ├── lib.rs # Library exports
│ ├── config/ # Configuration management
│ │ ├── mod.rs
│ │ ├── settings.rs # User settings
│ │ ├── presets.rs # Encoder presets
│ │ └── persistence.rs # Config file I/O
│ ├── lqp/ # League Client API
│ │ ├── mod.rs
│ │ ├── auth.rs # Lockfile authentication
│ │ ├── client.rs # WebSocket/REST client
│ │ └── events.rs # Game event types
│ ├── recording/ # libobs recording
│ │ ├── mod.rs
│ │ ├── obs_context.rs # OBS initialization
│ │ ├── encoder.rs # Video encoding
│ │ ├── capture.rs # Game capture
│ │ └── output.rs # File output
│ ├── ipc/ # IPC server
│ │ ├── mod.rs
│ │ ├── server.rs # Platform-specific server
│ │ ├── protocol.rs # Message protocol
│ │ └── handlers.rs # Command handlers
│ ├── timeline/ # Event timeline
│ │ ├── mod.rs
│ │ ├── store.rs # Storage backend
│ │ └── mapper.rs # Event-to-video mapping
│ ├── state/ # State machine
│ │ ├── mod.rs
│ │ └── machine.rs # State transitions
│ └── error.rs # Error types
Platform Support
| Feature | Linux | Windows |
|---|---|---|
| Game Detection | ✅ Wine/Proton paths | ✅ Native paths |
| IPC Transport | Unix sockets | Named pipes |
| Lockfile Detection | ✅ | ✅ |
| Hardware Encoding | NVENC | NVENC, AMF |
Building
Linux
cd record-daemon
cargo build --release
Windows
cd record-daemon
cargo build --release
Prerequisites for Windows:
- Visual Studio Build Tools (MSVC)
- OBS Studio installed (for libobs DLLs)
Usage
Running the Daemon
# Run with default settings
./record-daemon
# Run with custom config
./record-daemon --config /path/to/config.toml
# Run with debug logging
./record-daemon --log-level debug
# Run in foreground
./record-daemon --foreground
Command Line Options
| Option | Description |
|---|---|
-c, --config <PATH> |
Path to configuration file |
-l, --log-level <LEVEL> |
Log level (trace, debug, info, warn, error) |
-f, --foreground |
Run in foreground (don't daemonize) |
-s, --socket <PATH> |
Socket path for IPC |
Configuration
The configuration file is stored at:
- Linux:
~/.config/record-daemon/config.toml - Windows:
%APPDATA%\record-daemon\config.toml
Example Configuration
[video]
encoder_preset = { type = "nvenc", bitrate = 8000, cq_level = 20, two_pass = true }
quality = "high"
frame_rate = 60
hardware_acceleration = true
[output]
path = "C:\\Users\\User\\Videos\\LeagueRecordings" # Windows
# path = "/home/user/Videos/LeagueRecordings" # Linux
naming_pattern = "{date}_{time}_{champion}"
container = "mp4"
[audio]
enabled = true
bitrate = 192
sample_rate = 48000
channels = 2
capture_game = true
capture_mic = false
[daemon]
auto_record = true
monitor_client = true
poll_interval_ms = 1000
log_level = "info"
save_timeline = true
Encoder Presets
NVIDIA NVENC
[video.encoder_preset]
type = "nvenc"
bitrate = 8000
cq_level = 20
two_pass = true
AMD AMF (Windows only)
[video.encoder_preset]
type = "amf"
bitrate = 8000
quality = "balanced"
x264 (Software)
[video.encoder_preset]
type = "x264"
preset = "veryfast"
bitrate = 6000
crf = 23
Quality Levels
| Level | Resolution | FPS | Bitrate |
|---|---|---|---|
| Low | 720p | 30 | 4500 kbps |
| Medium | 1080p | 30 | 6000 kbps |
| High | 1080p | 60 | 8000 kbps |
| Ultra | 1440p | 60 | 12000 kbps |
IPC Protocol
The daemon exposes an IPC server for communication with the Tauri app.
Socket Location
- Linux:
$XDG_RUNTIME_DIR/record-daemon.sock(or/tmp/record-daemon.sock) - Windows:
\\.\pipe\record-daemon(named pipe)
Message Format
All messages are JSON-encoded:
{
"type": "request",
"id": "uuid-v4",
"command": "GetSettings",
"payload": null
}
Available Commands
| Command | Description |
|---|---|
GetSettings |
Get current settings |
UpdateSettings |
Update settings |
ResetSettings |
Reset to defaults |
GetStatus |
Get daemon status |
GetEncoders |
Get available encoders |
StartRecording |
Start recording manually |
StopRecording |
Stop recording |
GetRecordings |
List all recordings |
GetTimeline |
Get event timeline for a recording |
Shutdown |
Shutdown the daemon |
Example IPC Session (Linux)
# Connect to socket
nc -U /tmp/record-daemon.sock
# Get status
{"type":"request","id":"00000000-0000-0000-0000-000000000001","command":"GetStatus"}
# Response
{"request_id":"00000000-0000-0000-0000-000000000001","success":true,"data":{"status":"idle","isRecording":false,"clientConnected":false}}
Example IPC Session (Windows)
# Connect to named pipe using PowerShell
$client = New-Object System.IO.Pipes.NamedPipeClientStream(".", "record-daemon", [System.IO.Pipes.PipeDirection]::InOut)
$client.Connect()
$reader = New-Object System.IO.StreamReader($client)
$writer = New-Object System.IO.StreamWriter($client)
# Send command
$writer.WriteLine('{"type":"request","id":"00000000-0000-0000-0000-000000000001","command":"GetStatus"}')
$writer.Flush()
# Read response
$response = $reader.ReadLine()
Write-Host $response
$client.Close()
Event Timeline
Each recording has an associated timeline that maps game events to video timestamps:
{
"recording_id": "uuid",
"start_time": "2024-01-15T10:30:00Z",
"end_time": "2024-01-15T11:15:00Z",
"duration_secs": 2700,
"events": [
{
"video_timestamp": 120,
"game_timestamp": 115,
"event_type": "kill",
"description": "Player1 killed Player2",
"timestamp": "2024-01-15T10:32:00Z"
}
]
}
Windows Test Plan
This section provides a comprehensive test plan for validating the daemon on Windows with League of Legends.
Prerequisites
-
System Requirements
- Windows 10/11 64-bit
- League of Legends installed
- OBS Studio installed (for libobs)
- NVIDIA GPU (for NVENC) or AMD GPU (for AMF) - optional but recommended
-
Build Requirements
- Rust toolchain (stable)
- Visual Studio Build Tools 2022
- Windows SDK
Test Environment Setup
# 1. Install Rust if not already installed
winget install Rustlang.Rustup
# 2. Verify Rust installation
rustc --version
cargo --version
# 3. Build the daemon
cd record-daemon
cargo build --release
Test 1: Daemon Startup
Objective: Verify daemon starts correctly on Windows.
Steps:
# Run daemon in foreground with debug logging
.\target\release\record-daemon.exe --foreground --log-level debug
Expected Results:
- Daemon starts without errors
- Named pipe
\\.\pipe\record-daemonis created - Log shows "IPC server started successfully"
- Config file created at
%APPDATA%\record-daemon\config.toml
Verification:
# Check if named pipe exists
[System.IO.Directory]::GetFiles("\\.\\pipe\\") | Where-Object { $_ -like "*record-daemon*" }
Test 2: League Client Detection
Objective: Verify daemon detects League Client startup.
Steps:
- Start the daemon with debug logging
- Launch League of Legends Client
- Wait for client to fully load (login screen)
Expected Results:
- Log shows "League Client detected (PID: XXXX, Port: XXXX)"
- Lockfile found at
C:\Riot Games\League of Legends\lockfile - Daemon transitions to Monitoring state
Verification:
# Check lockfile exists
Get-Content "C:\Riot Games\League of Legends\lockfile"
# Expected format: LeagueClient:PID:PORT:PASSWORD:PROTOCOL
# Example: LeagueClient:12345:52432:abc123:https
Test 3: Game Start Detection
Objective: Verify daemon detects game start and begins recording.
Steps:
- Start daemon
- Launch League Client
- Start a game (Practice Tool, ARAM, or Summoner's Rift)
- Wait for game to load
Expected Results:
- Log shows "Starting recording for game XXXXX"
- Recording file created in output directory
- Daemon transitions to Recording state
Verification:
# Check for recording files
Get-ChildItem -Path "C:\Users\$env:USERNAME\Videos\LeagueRecordings" -Recurse
Test 4: Event Capture
Objective: Verify game events are captured and mapped to timeline.
Steps:
- Start daemon
- Play a game (Practice Tool recommended for controlled testing)
- Perform actions: kills, deaths, take objectives
- End the game
Expected Results:
- Events logged in debug output
- Timeline JSON file created after game ends
- Events contain correct timestamps
Verification:
# Check timeline files
Get-ChildItem -Path "$env:APPDATA\record-daemon\timelines" -Recurse
# View timeline content
Get-Content "$env:APPDATA\record-daemon\timelines\*.json" | ConvertFrom-Json
Test 5: Game End Detection
Objective: Verify daemon stops recording when game ends.
Steps:
- Start daemon
- Play a game
- End the game (win, lose, or surrender)
Expected Results:
- Log shows "Stopping recording"
- Recording file finalized
- Timeline saved
- Daemon transitions back to Monitoring state
Test 6: IPC Communication
Objective: Verify IPC commands work on Windows named pipes.
Steps:
# Create test script
$pipe = New-Object System.IO.Pipes.NamedPipeClientStream(".", "record-daemon", [System.IO.Pipes.PipeDirection]::InOut)
$pipe.Connect(5000)
$reader = New-Object System.IO.StreamReader($pipe)
$writer = New-Object System.IO.StreamWriter($pipe)
# Test GetStatus
$writer.WriteLine('{"type":"request","id":"00000000-0000-0000-0000-000000000001","command":"GetStatus"}')
$writer.Flush()
$response = $reader.ReadLine()
Write-Host "Status: $response"
# Test GetEncoders
$writer.WriteLine('{"type":"request","id":"00000000-0000-0000-0000-000000000002","command":"GetEncoders"}')
$writer.Flush()
$response = $reader.ReadLine()
Write-Host "Encoders: $response"
$pipe.Close()
Expected Results:
- GetStatus returns current daemon state
- GetEncoders returns available hardware encoders
Test 7: Client Disconnect/Reconnect
Objective: Verify daemon handles client restarts correctly.
Steps:
- Start daemon
- Launch League Client
- Close League Client
- Reopen League Client
Expected Results:
- Log shows "League Client stopped" when closed
- Log shows "League Client detected" when reopened
- Daemon state transitions correctly
Test 8: Multiple Game Sessions
Objective: Verify daemon handles multiple consecutive games.
Steps:
- Start daemon
- Play 3 consecutive games
- Check all recordings
Expected Results:
- Each game creates separate recording
- Each recording has correct timeline
- No memory leaks or performance degradation
Test 9: Hardware Encoding
Objective: Verify hardware encoding works correctly.
Steps:
- Configure NVENC or AMF in config
- Start daemon
- Play a game
- Check recording quality
NVIDIA GPU Verification:
# Check NVENC is being used
nvidia-smi dmon -s u -d pwr,enc
# Should show encoder utilization during recording
Expected Results:
- Recording uses hardware encoder
- GPU encoder utilization shown in monitoring tools
- CPU usage remains low during recording
Test 10: Error Recovery
Objective: Verify daemon recovers from errors gracefully.
Steps:
- Start daemon
- Start recording
- Simulate error scenarios:
- Kill game process
- Delete output directory
- Fill disk space (careful!)
Expected Results:
- Daemon logs error appropriately
- Daemon recovers and continues operation
- No crash or hang
Test 11: Configuration Persistence
Objective: Verify settings are saved and loaded correctly.
Steps:
# Edit config
$configPath = "$env:APPDATA\record-daemon\config.toml"
notepad $configPath
# Restart daemon
.\target\release\record-daemon.exe --foreground
Expected Results:
- Config changes persist across restarts
- Invalid config shows appropriate error
- Default config created if missing
Test 12: Shutdown Handling
Objective: Verify clean shutdown.
Steps:
- Start daemon
- Start recording
- Send Ctrl+C or shutdown signal
Expected Results:
- Recording stops cleanly
- Timeline saved
- Named pipe cleaned up
- No orphaned processes
Development
Running Tests
cargo test
Building Documentation
cargo doc --open
Code Structure
The daemon follows a state machine pattern:
Idle -> Monitoring -> Recording -> Monitoring -> Idle
| | |
v v v
Error <-------- Error -------- Error
|
v
Idle (recovery)
Dependencies
- tokio: Async runtime
- tokio-tungstenite: WebSocket for LQP
- reqwest: HTTP client for LQP REST API
- serde/serde_json: Serialization
- tracing: Logging
- parking_lot: High-performance locks
- chrono: Date/time handling
- uuid: Unique identifiers
Troubleshooting
Windows: Named Pipe Connection Failed
Error: Cannot connect to named pipe
Solution: Ensure daemon is running and pipe exists:
# Check pipe exists
[System.IO.Directory]::GetFiles("\\.\\pipe\\") | Where-Object { $_ -like "*record-daemon*" }
Windows: Lockfile Not Found
Error: League Client lockfile not found
Solution: Check League Client installation path:
# Common paths
Test-Path "C:\Riot Games\League of Legends\lockfile"
Test-Path "D:\Riot Games\League of Legends\lockfile"
Windows: NVENC Not Available
Error: NVENC encoder not available
Solution:
- Ensure NVIDIA GPU is present
- Update NVIDIA drivers
- Check NVENC support:
nvidia-smi
License
MIT