record-daemon: initial commit
This commit is contained in:
627
record-daemon/README.md
Normal file
627
record-daemon/README.md
Normal 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
|
||||
Reference in New Issue
Block a user