Files
leaguerecorder/record-daemon
Valentin Haudiquet fcfa55d0aa
Some checks are pending
record-daemon / Build, check and test (push) Waiting to run
record raw events everywhere
2026-05-06 23:53:01 +02:00
..
2026-05-06 23:53:01 +02:00
2026-03-19 17:48:07 +01:00
2026-05-06 23:53:01 +02:00
2026-03-19 17:48:07 +01:00

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

  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

# 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-daemon is 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:

  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:

# 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:

# 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:

# 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:

# 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:

# 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:

# 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

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:

  1. Ensure NVIDIA GPU is present
  2. Update NVIDIA drivers
  3. Check NVENC support: nvidia-smi

License

MIT