record-daemon: add live client events, fix video_file
All checks were successful
record-daemon / Build, check and test (push) Successful in 2m12s
All checks were successful
record-daemon / Build, check and test (push) Successful in 2m12s
This commit is contained in:
@@ -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
|
||||
// =========================================================================
|
||||
|
||||
@@ -32,9 +32,14 @@ pub const LP_CHANGE_NOTIFICATION: &str = "/lol-ranked/v1/current-lp-change-notif
|
||||
pub const LIVE_CLIENT_DATA_ACTIVE_PLAYER: &str = "/liveclientdata/activeplayer";
|
||||
/// Live client player list endpoint.
|
||||
pub const LIVE_CLIENT_DATA_PLAYER_LIST: &str = "/liveclientdata/playerlist";
|
||||
/// Live client event data endpoint (kills, deaths, objectives).
|
||||
pub const LIVE_CLIENT_DATA_EVENTS: &str = "/liveclientdata/eventdata";
|
||||
/// Local player selection in champion select.
|
||||
pub const CHAMPION_SELECT_LOCAL_PLAYER: &str = "/lol-champ-select/v1/session/my-selection";
|
||||
|
||||
/// Live Client Data server base URL (runs on port 2999).
|
||||
pub const LIVE_CLIENT_DATA_BASE_URL: &str = "https://127.0.0.1:2999";
|
||||
|
||||
/// LQP WebSocket endpoints to subscribe to.
|
||||
pub const SUBSCRIBE_ENDPOINTS: &[&str] = &[
|
||||
GAMEFLOW_PHASE,
|
||||
|
||||
@@ -16,8 +16,8 @@ pub use client::LqpClient;
|
||||
pub use endpoints::{
|
||||
ALL_RUNE_PAGES, CHAMPION_SELECT, CHAMPION_SELECT_LOCAL_PLAYER, CHAMPION_SUMMARY,
|
||||
GAMEFLOW_PHASE, GAME_STATS, LIVE_CLIENT_DATA, LIVE_CLIENT_DATA_ACTIVE_PLAYER,
|
||||
LIVE_CLIENT_DATA_PLAYER_LIST, MATCH_HISTORY, RUNE_PAGES, SESSION, SUBSCRIBE_ENDPOINTS,
|
||||
SUMMONER,
|
||||
LIVE_CLIENT_DATA_BASE_URL, LIVE_CLIENT_DATA_EVENTS, LIVE_CLIENT_DATA_PLAYER_LIST,
|
||||
MATCH_HISTORY, RUNE_PAGES, SESSION, SUBSCRIBE_ENDPOINTS, SUMMONER,
|
||||
};
|
||||
pub use events::{
|
||||
ChampSelectStartInfo, ChampionPickInfo, DeathEvent, EventData, GameEndInfo, GameEvent,
|
||||
@@ -26,4 +26,6 @@ pub use events::{
|
||||
QueueInfo, RunePage, RuneSlot, SummonerSpells, TeamMember,
|
||||
};
|
||||
pub use state::{ClientState, GameflowPhase};
|
||||
pub use websocket::{parse_event_from_uri, parse_websocket_message, ParsedEvent};
|
||||
pub use websocket::{
|
||||
parse_event_from_uri, parse_live_client_event, parse_websocket_message, ParsedEvent,
|
||||
};
|
||||
|
||||
@@ -116,7 +116,7 @@ pub fn parse_event_from_uri(
|
||||
// Handle game events (kills, deaths, objectives)
|
||||
if uri == "/lol-game-events/v1/game-events" {
|
||||
info!("Game event received: {:?}", data);
|
||||
return GameEvent::from_json(data);
|
||||
return parse_game_event(data);
|
||||
}
|
||||
|
||||
// Handle ready check
|
||||
@@ -664,6 +664,418 @@ fn parse_ranked_stats_event(data: &serde_json::Value) -> Option<GameEvent> {
|
||||
None
|
||||
}
|
||||
|
||||
/// Parse game events from the /lol-game-events/v1/game-events endpoint.
|
||||
///
|
||||
/// This endpoint receives events like kills, deaths, and objectives.
|
||||
/// The format varies but typically includes an EventName field.
|
||||
fn parse_game_event(data: &serde_json::Value) -> Option<GameEvent> {
|
||||
// The game events API can send various event types
|
||||
// Common event names: ChampionKill, ChampionDeath, DragonKill, BaronKill, etc.
|
||||
let event_name = data.get("EventName").and_then(|n| n.as_str()).unwrap_or("");
|
||||
|
||||
info!("Parsing game event: {} -> {:?}", event_name, data);
|
||||
|
||||
match event_name {
|
||||
"ChampionKill" => {
|
||||
// Extract kill information
|
||||
let killer = data
|
||||
.get("KillerName")
|
||||
.and_then(|n| n.as_str())
|
||||
.unwrap_or("Unknown")
|
||||
.to_string();
|
||||
|
||||
let victim = data
|
||||
.get("VictimName")
|
||||
.and_then(|n| n.as_str())
|
||||
.unwrap_or("Unknown")
|
||||
.to_string();
|
||||
|
||||
let killer_champion = data
|
||||
.get("KillerChampionName")
|
||||
.and_then(|n| n.as_str())
|
||||
.map(|s| s.to_string());
|
||||
|
||||
let victim_champion = data
|
||||
.get("VictimChampionName")
|
||||
.and_then(|n| n.as_str())
|
||||
.map(|s| s.to_string());
|
||||
|
||||
// Check if it was a solo kill (no assisters)
|
||||
let assisters = data
|
||||
.get("Assisters")
|
||||
.and_then(|a| a.as_array())
|
||||
.map(|arr| arr.len() as u32)
|
||||
.unwrap_or(0);
|
||||
|
||||
let solo_kill = assisters == 0;
|
||||
|
||||
// Extract position if available
|
||||
let position = data.get("Position").map(|p| super::events::Position {
|
||||
x: p.get("x").and_then(|x| x.as_f64()).unwrap_or(0.0) as f32,
|
||||
y: p.get("y").and_then(|y| y.as_f64()).unwrap_or(0.0) as f32,
|
||||
});
|
||||
|
||||
// Get game time
|
||||
let game_time = data.get("GameTime").and_then(|t| t.as_f64());
|
||||
|
||||
let event_json = serde_json::json!({
|
||||
"eventType": "lcu-kill",
|
||||
"killer": killer,
|
||||
"killerChampion": killer_champion,
|
||||
"victim": victim,
|
||||
"victimChampion": victim_champion,
|
||||
"soloKill": solo_kill,
|
||||
"assists": assisters,
|
||||
"position": position,
|
||||
"gameTime": game_time
|
||||
});
|
||||
|
||||
GameEvent::from_json(&event_json)
|
||||
}
|
||||
"ChampionDeath" => {
|
||||
// Extract death information
|
||||
let killer = data
|
||||
.get("KillerName")
|
||||
.and_then(|n| n.as_str())
|
||||
.map(|s| s.to_string());
|
||||
|
||||
let killer_champion = data
|
||||
.get("KillerChampionName")
|
||||
.and_then(|n| n.as_str())
|
||||
.map(|s| s.to_string());
|
||||
|
||||
// Death cause - could be champion, minion, tower, etc.
|
||||
let cause = killer
|
||||
.clone()
|
||||
.or_else(|| {
|
||||
data.get("DeathCause")
|
||||
.and_then(|c| c.as_str())
|
||||
.map(|s| s.to_string())
|
||||
})
|
||||
.unwrap_or_else(|| "Unknown".to_string());
|
||||
|
||||
// Extract position if available
|
||||
let position = data.get("Position").map(|p| super::events::Position {
|
||||
x: p.get("x").and_then(|x| x.as_f64()).unwrap_or(0.0) as f32,
|
||||
y: p.get("y").and_then(|y| y.as_f64()).unwrap_or(0.0) as f32,
|
||||
});
|
||||
|
||||
// Get game time
|
||||
let game_time = data.get("GameTime").and_then(|t| t.as_f64());
|
||||
|
||||
let event_json = serde_json::json!({
|
||||
"eventType": "lcu-death",
|
||||
"killer": killer,
|
||||
"killerChampion": killer_champion,
|
||||
"cause": cause,
|
||||
"position": position,
|
||||
"gameTime": game_time
|
||||
});
|
||||
|
||||
GameEvent::from_json(&event_json)
|
||||
}
|
||||
"DragonKill" | "BaronKill" | "HeraldKill" | "RiftHeraldKill" | "ElderDragonKill" => {
|
||||
let objective_type = match event_name {
|
||||
"DragonKill" => "dragon",
|
||||
"BaronKill" => "baron",
|
||||
"HeraldKill" | "RiftHeraldKill" => "herald",
|
||||
"ElderDragonKill" => "elderdragon",
|
||||
_ => "unknown",
|
||||
};
|
||||
|
||||
let team = data.get("Team").and_then(|t| t.as_u64()).unwrap_or(0) as u32;
|
||||
|
||||
let game_time = data.get("GameTime").and_then(|t| t.as_f64());
|
||||
|
||||
let event_json = serde_json::json!({
|
||||
"eventType": "lcu-objective",
|
||||
"objectiveType": objective_type,
|
||||
"team": team,
|
||||
"participated": false, // Will be determined by the caller
|
||||
"gameTime": game_time
|
||||
});
|
||||
|
||||
GameEvent::from_json(&event_json)
|
||||
}
|
||||
"TurretKill" | "InhibitorKill" | "NexusKill" => {
|
||||
let objective_type = match event_name {
|
||||
"TurretKill" => "tower",
|
||||
"InhibitorKill" => "inhibitor",
|
||||
"NexusKill" => "nexus",
|
||||
_ => "unknown",
|
||||
};
|
||||
|
||||
let team = data.get("Team").and_then(|t| t.as_u64()).unwrap_or(0) as u32;
|
||||
|
||||
let game_time = data.get("GameTime").and_then(|t| t.as_f64());
|
||||
|
||||
let event_json = serde_json::json!({
|
||||
"eventType": "lcu-objective",
|
||||
"objectiveType": objective_type,
|
||||
"team": team,
|
||||
"participated": false,
|
||||
"gameTime": game_time
|
||||
});
|
||||
|
||||
GameEvent::from_json(&event_json)
|
||||
}
|
||||
_ => {
|
||||
// Try to parse as a generic event with eventType field
|
||||
debug!(
|
||||
"Unknown game event type: {}, attempting generic parse",
|
||||
event_name
|
||||
);
|
||||
GameEvent::from_json(data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse events from the Live Client Data API (port 2999).
|
||||
///
|
||||
/// The Live Client Data API provides real-time game events including:
|
||||
/// - Champion kills and deaths
|
||||
/// - Objective kills (Dragon, Baron, Herald, etc.)
|
||||
/// - Building destruction (Turrets, Inhibitors)
|
||||
///
|
||||
/// Event format from /liveclientdata/eventdata:
|
||||
/// ```json
|
||||
/// {
|
||||
/// "EventID": 1,
|
||||
/// "EventName": "ChampionKill",
|
||||
/// "EventTime": 123.456,
|
||||
/// "KillerName": "Player1",
|
||||
/// "VictimName": "Player2",
|
||||
/// "Assisters": ["Player3"],
|
||||
/// ...
|
||||
/// }
|
||||
/// ```
|
||||
pub fn parse_live_client_event(data: &serde_json::Value) -> Option<ParsedEvent> {
|
||||
let event_name = data.get("EventName").and_then(|n| n.as_str()).unwrap_or("");
|
||||
|
||||
info!("Parsing live client event: {} -> {:?}", event_name, data);
|
||||
|
||||
let event = match event_name {
|
||||
"ChampionKill" => {
|
||||
// Extract kill information
|
||||
let killer = data
|
||||
.get("KillerName")
|
||||
.and_then(|n| n.as_str())
|
||||
.unwrap_or("Unknown")
|
||||
.to_string();
|
||||
|
||||
let victim = data
|
||||
.get("VictimName")
|
||||
.and_then(|n| n.as_str())
|
||||
.unwrap_or("Unknown")
|
||||
.to_string();
|
||||
|
||||
// Get champion names if available
|
||||
let killer_champion = data
|
||||
.get("KillerChampionName")
|
||||
.and_then(|n| n.as_str())
|
||||
.map(|s| s.to_string());
|
||||
|
||||
let victim_champion = data
|
||||
.get("VictimChampionName")
|
||||
.and_then(|n| n.as_str())
|
||||
.map(|s| s.to_string());
|
||||
|
||||
// Check if it was a solo kill (no assisters)
|
||||
let assisters = data
|
||||
.get("Assisters")
|
||||
.and_then(|a| a.as_array())
|
||||
.map(|arr| arr.len() as u32)
|
||||
.unwrap_or(0);
|
||||
|
||||
let solo_kill = assisters == 0;
|
||||
|
||||
// Get game time
|
||||
let game_time = data.get("EventTime").and_then(|t| t.as_f64());
|
||||
|
||||
// Extract position if available
|
||||
let position = data.get("Position").map(|p| super::events::Position {
|
||||
x: p.get("x").and_then(|x| x.as_f64()).unwrap_or(0.0) as f32,
|
||||
y: p.get("y").and_then(|y| y.as_f64()).unwrap_or(0.0) as f32,
|
||||
});
|
||||
|
||||
let event_json = serde_json::json!({
|
||||
"eventType": "lcu-kill",
|
||||
"killer": killer,
|
||||
"killerChampion": killer_champion,
|
||||
"victim": victim,
|
||||
"victimChampion": victim_champion,
|
||||
"soloKill": solo_kill,
|
||||
"assists": assisters,
|
||||
"position": position,
|
||||
"gameTime": game_time
|
||||
});
|
||||
|
||||
GameEvent::from_json(&event_json)
|
||||
}
|
||||
"ChampionDeath" => {
|
||||
// Extract death information
|
||||
let killer = data
|
||||
.get("KillerName")
|
||||
.and_then(|n| n.as_str())
|
||||
.map(|s| s.to_string());
|
||||
|
||||
let killer_champion = data
|
||||
.get("KillerChampionName")
|
||||
.and_then(|n| n.as_str())
|
||||
.map(|s| s.to_string());
|
||||
|
||||
// Death cause
|
||||
let cause = killer
|
||||
.clone()
|
||||
.or_else(|| {
|
||||
data.get("DeathCause")
|
||||
.and_then(|c| c.as_str())
|
||||
.map(|s| s.to_string())
|
||||
})
|
||||
.unwrap_or_else(|| "Unknown".to_string());
|
||||
|
||||
// Get game time
|
||||
let game_time = data.get("EventTime").and_then(|t| t.as_f64());
|
||||
|
||||
// Extract position if available
|
||||
let position = data.get("Position").map(|p| super::events::Position {
|
||||
x: p.get("x").and_then(|x| x.as_f64()).unwrap_or(0.0) as f32,
|
||||
y: p.get("y").and_then(|y| y.as_f64()).unwrap_or(0.0) as f32,
|
||||
});
|
||||
|
||||
let event_json = serde_json::json!({
|
||||
"eventType": "lcu-death",
|
||||
"killer": killer,
|
||||
"killerChampion": killer_champion,
|
||||
"cause": cause,
|
||||
"position": position,
|
||||
"gameTime": game_time
|
||||
});
|
||||
|
||||
GameEvent::from_json(&event_json)
|
||||
}
|
||||
"DragonKill" | "BaronKill" | "HeraldKill" | "RiftHeraldKill" | "ElderDragonKill" => {
|
||||
let objective_type = match event_name {
|
||||
"DragonKill" => "dragon",
|
||||
"BaronKill" => "baron",
|
||||
"HeraldKill" | "RiftHeraldKill" => "herald",
|
||||
"ElderDragonKill" => "elderdragon",
|
||||
_ => "unknown",
|
||||
};
|
||||
|
||||
let _killer = data
|
||||
.get("KillerName")
|
||||
.and_then(|n| n.as_str())
|
||||
.unwrap_or("Unknown");
|
||||
|
||||
// Determine team based on killer (would need player list to determine team)
|
||||
let team = data.get("KillerTeam").and_then(|t| t.as_u64()).unwrap_or(0) as u32;
|
||||
|
||||
let game_time = data.get("EventTime").and_then(|t| t.as_f64());
|
||||
|
||||
let event_json = serde_json::json!({
|
||||
"eventType": "lcu-objective",
|
||||
"objectiveType": objective_type,
|
||||
"team": team,
|
||||
"participated": false,
|
||||
"gameTime": game_time
|
||||
});
|
||||
|
||||
GameEvent::from_json(&event_json)
|
||||
}
|
||||
"TurretKill" | "InhibitorKill" | "NexusKill" => {
|
||||
let objective_type = match event_name {
|
||||
"TurretKill" => "tower",
|
||||
"InhibitorKill" => "inhibitor",
|
||||
"NexusKill" => "nexus",
|
||||
_ => "unknown",
|
||||
};
|
||||
|
||||
let team = data.get("KillerTeam").and_then(|t| t.as_u64()).unwrap_or(0) as u32;
|
||||
|
||||
let game_time = data.get("EventTime").and_then(|t| t.as_f64());
|
||||
|
||||
let event_json = serde_json::json!({
|
||||
"eventType": "lcu-objective",
|
||||
"objectiveType": objective_type,
|
||||
"team": team,
|
||||
"participated": false,
|
||||
"gameTime": game_time
|
||||
});
|
||||
|
||||
GameEvent::from_json(&event_json)
|
||||
}
|
||||
"Multikill" => {
|
||||
// Multikill events (double, triple, quadra, penta kills)
|
||||
let killer = data
|
||||
.get("KillerName")
|
||||
.and_then(|n| n.as_str())
|
||||
.unwrap_or("Unknown");
|
||||
|
||||
let kill_count = data.get("KillCount").and_then(|k| k.as_u64()).unwrap_or(2) as u32;
|
||||
|
||||
let game_time = data.get("EventTime").and_then(|t| t.as_f64());
|
||||
|
||||
info!(
|
||||
"Multikill event: {} got a {}-kill at {:?}",
|
||||
killer, kill_count, game_time
|
||||
);
|
||||
|
||||
// Don't emit a separate event for multikills, they're derived from kills
|
||||
None
|
||||
}
|
||||
"FirstBlood" => {
|
||||
let killer = data
|
||||
.get("KillerName")
|
||||
.and_then(|n| n.as_str())
|
||||
.unwrap_or("Unknown");
|
||||
|
||||
let victim = data
|
||||
.get("VictimName")
|
||||
.and_then(|n| n.as_str())
|
||||
.unwrap_or("Unknown");
|
||||
|
||||
let game_time = data.get("EventTime").and_then(|t| t.as_f64());
|
||||
|
||||
info!(
|
||||
"First Blood: {} killed {} at {:?}",
|
||||
killer, victim, game_time
|
||||
);
|
||||
|
||||
// First blood is just a special kill, the kill event will be emitted separately
|
||||
None
|
||||
}
|
||||
"GameStart" => {
|
||||
let game_time = data
|
||||
.get("EventTime")
|
||||
.and_then(|t| t.as_f64())
|
||||
.unwrap_or(0.0);
|
||||
|
||||
info!("Game started at {:?}", game_time);
|
||||
None
|
||||
}
|
||||
"GameEnd" => {
|
||||
let game_time = data
|
||||
.get("EventTime")
|
||||
.and_then(|t| t.as_f64())
|
||||
.unwrap_or(0.0);
|
||||
|
||||
info!("Game ended at {:?}", game_time);
|
||||
None
|
||||
}
|
||||
_ => {
|
||||
debug!("Unknown live client event type: {}", event_name);
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
event.map(|e| ParsedEvent {
|
||||
event: e,
|
||||
raw_data: data.clone(),
|
||||
uri: "/liveclientdata/eventdata".to_string(),
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
@@ -206,6 +206,8 @@ impl Daemon {
|
||||
if let Some(creds) = watcher.credentials() {
|
||||
self.lqp_client.connect(creds.clone()).await?;
|
||||
self.lqp_client.start_event_listener().await?;
|
||||
// Start polling for live client events (kills, deaths, objectives)
|
||||
self.lqp_client.start_live_client_event_poller().await;
|
||||
}
|
||||
}
|
||||
Some(false) => {
|
||||
|
||||
@@ -197,6 +197,11 @@ impl TimelineStore {
|
||||
metadata.end_time = Some(result.end_time);
|
||||
metadata.duration = result.duration;
|
||||
metadata.file_path = Some(result.path.clone());
|
||||
metadata.video_file = result
|
||||
.path
|
||||
.file_name()
|
||||
.and_then(|n| n.to_str())
|
||||
.map(String::from);
|
||||
metadata.file_size = result.file_size();
|
||||
metadata.finalized = true;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user