628 lines
15 KiB
Markdown
628 lines
15 KiB
Markdown
# 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
|