record-daemon: add live client events, fix video_file
record-daemon / Build, check and test (push) Successful in 2m12s

This commit is contained in:
2026-03-28 11:08:19 +01:00
parent 16d9ddaafa
commit 3223ba74fc
6 changed files with 570 additions and 5 deletions
+140 -1
View File
@@ -14,7 +14,7 @@ use super::endpoints;
use super::events::GameEvent;
use super::state::{ClientState, GameflowPhase};
use super::tls::create_insecure_tls_config;
use super::websocket::{parse_websocket_message, ParsedEvent};
use super::websocket::{parse_live_client_event, parse_websocket_message, ParsedEvent};
use crate::error::{LqpError, Result};
/// LQP Client for League Client communication.
@@ -409,12 +409,151 @@ impl LqpClient {
.await
}
/// Get live client event data (kills, deaths, objectives) from port 2999.
/// This endpoint is available during games and provides real-time events.
pub async fn get_live_client_events(&self) -> Result<serde_json::Value> {
// Live client data runs on port 2999, separate from the LQP client port
let url = format!(
"{}{}",
endpoints::LIVE_CLIENT_DATA_BASE_URL,
endpoints::LIVE_CLIENT_DATA_EVENTS
);
let response = self
.http_client
.get(&url)
.send()
.await
.map_err(|e| LqpError::ConnectionFailed(e.to_string()))?;
if !response.status().is_success() {
return Err(LqpError::ConnectionFailed(format!(
"Live client events request failed: {}",
response.status()
))
.into());
}
let json = response
.json()
.await
.map_err(|e| LqpError::EventParseError(e.to_string()))?;
Ok(json)
}
/// Get local player selection from champion select.
pub async fn get_local_player_selection(&self) -> Result<serde_json::Value> {
self.request("GET", endpoints::CHAMPION_SELECT_LOCAL_PLAYER)
.await
}
/// Start polling for live client events during a game.
/// This polls the /liveclientdata/eventdata endpoint and broadcasts events.
pub async fn start_live_client_event_poller(&self) {
let event_sender = self.event_sender.clone();
let state = self.state.clone();
let shutdown = self.shutdown.clone();
let http_client = self.http_client.clone();
info!("Starting live client event poller");
tokio::spawn(async move {
let mut last_event_id: Option<u64> = None;
let mut poll_count = 0u32;
loop {
if *shutdown.read().await {
info!("Live client event poller shutting down");
break;
}
// Only poll when in game
let current_phase = state.read().await.phase;
if current_phase != GameflowPhase::InProgress {
// Reset event tracking when not in game
last_event_id = None;
poll_count = 0;
tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
continue;
}
poll_count += 1;
if poll_count % 20 == 1 {
// Log every 10 seconds (20 * 500ms)
info!("Live client event poller active, polling for events...");
}
// Poll for events
let url = format!(
"{}{}",
endpoints::LIVE_CLIENT_DATA_BASE_URL,
endpoints::LIVE_CLIENT_DATA_EVENTS
);
match http_client.get(&url).send().await {
Ok(response) if response.status().is_success() => {
match response.json::<serde_json::Value>().await {
Ok(events) => {
// The response has an "Events" key containing the array
let events_array = events.get("Events").and_then(|e| e.as_array());
if let Some(events_array) = events_array {
let event_count = events_array.len();
if event_count > 0 {
info!(
"Received {} events from live client API",
event_count
);
}
for event in events_array {
// Check if this is a new event
let event_id =
event.get("EventID").and_then(|id| id.as_u64());
// Skip events we've already processed
if let Some(id) = event_id {
if let Some(last_id) = last_event_id {
if id <= last_id {
continue;
}
}
last_event_id = Some(id);
}
// Parse and broadcast the event
if let Some(parsed) = parse_live_client_event(event) {
info!("Parsed live client event: {:?}", parsed.event);
// Update state based on event
Self::update_state_from_event(&state, &parsed.event)
.await;
// Broadcast event
if event_sender.send(parsed).is_err() {
trace!("No event subscribers");
}
}
}
}
}
Err(e) => {
debug!("Failed to parse live client events: {}", e);
}
}
}
Ok(response) => {
info!("Live client events request failed: {}", response.status());
}
Err(e) => {
info!("Failed to fetch live client events: {}", e);
}
}
// Poll every 500ms during games
tokio::time::sleep(tokio::time::Duration::from_millis(500)).await;
}
});
}
// =========================================================================
// Metadata Fetching Methods
// =========================================================================