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

627
record-daemon/README.md Normal file
View File

@@ -0,0 +1,627 @@
# 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
```bash
cd record-daemon
cargo build --release
```
### Windows
```powershell
cd record-daemon
cargo build --release
```
**Prerequisites for Windows:**
- Visual Studio Build Tools (MSVC)
- OBS Studio installed (for libobs DLLs)
## Usage
### Running the Daemon
```bash
# 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
```toml
[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
```toml
[video.encoder_preset]
type = "nvenc"
bitrate = 8000
cq_level = 20
two_pass = true
```
#### AMD AMF (Windows only)
```toml
[video.encoder_preset]
type = "amf"
bitrate = 8000
quality = "balanced"
```
#### x264 (Software)
```toml
[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:
```json
{
"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)
```bash
# 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)
```powershell
# 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:
```json
{
"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
1. **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
2. **Build Requirements**
- Rust toolchain (stable)
- Visual Studio Build Tools 2022
- Windows SDK
### Test Environment Setup
```powershell
# 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:**
```powershell
# 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-daemon` is created
- Log shows "IPC server started successfully"
- Config file created at `%APPDATA%\record-daemon\config.toml`
**Verification:**
```powershell
# 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:**
1. Start the daemon with debug logging
2. Launch League of Legends Client
3. 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:**
```powershell
# 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:**
1. Start daemon
2. Launch League Client
3. Start a game (Practice Tool, ARAM, or Summoner's Rift)
4. 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:**
```powershell
# 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:**
1. Start daemon
2. Play a game (Practice Tool recommended for controlled testing)
3. Perform actions: kills, deaths, take objectives
4. End the game
**Expected Results:**
- Events logged in debug output
- Timeline JSON file created after game ends
- Events contain correct timestamps
**Verification:**
```powershell
# 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:**
1. Start daemon
2. Play a game
3. 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:**
```powershell
# 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:**
1. Start daemon
2. Launch League Client
3. Close League Client
4. 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:**
1. Start daemon
2. Play 3 consecutive games
3. 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:**
1. Configure NVENC or AMF in config
2. Start daemon
3. Play a game
4. Check recording quality
**NVIDIA GPU Verification:**
```powershell
# 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:**
1. Start daemon
2. Start recording
3. 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:**
```powershell
# 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:**
1. Start daemon
2. Start recording
3. Send Ctrl+C or shutdown signal
**Expected Results:**
- Recording stops cleanly
- Timeline saved
- Named pipe cleaned up
- No orphaned processes
---
## Development
### Running Tests
```bash
cargo test
```
### Building Documentation
```bash
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:
```powershell
# 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:
```powershell
# 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:**
1. Ensure NVIDIA GPU is present
2. Update NVIDIA drivers
3. Check NVENC support: `nvidia-smi`
## License
MIT