add replay session
This commit is contained in:
143
src/session_replay.js
Normal file
143
src/session_replay.js
Normal file
@@ -0,0 +1,143 @@
|
||||
const PRACTICE_TYPES = new Set(["open_practice", "free_practice", "practice", "qualification"]);
|
||||
|
||||
export function normalizeSessionReplayPassings(passings) {
|
||||
return (Array.isArray(passings) ? passings : [])
|
||||
.filter((passing) => Number.isFinite(Number(passing?.timestamp)))
|
||||
.map((passing, index) => ({
|
||||
...passing,
|
||||
replayIndex: index,
|
||||
timestamp: Number(passing.timestamp),
|
||||
lapMs: Math.max(0, Number(passing.lapMs || 0) || 0),
|
||||
transponder: String(passing.transponder || ""),
|
||||
competitorKey: String(passing.competitorKey || passing.driverId || passing.teamId || passing.transponder || `passing_${index}`),
|
||||
driverName: String(passing.displayName || passing.teamName || passing.driverName || passing.transponder || "Unknown"),
|
||||
carName: String(passing.subLabel || passing.carName || ""),
|
||||
}))
|
||||
.sort((left, right) => left.timestamp - right.timestamp || left.replayIndex - right.replayIndex);
|
||||
}
|
||||
|
||||
export function createSessionReplayRuntime() {
|
||||
return {
|
||||
sessionId: "",
|
||||
sessionType: "",
|
||||
sessionName: "",
|
||||
status: "idle",
|
||||
speed: 1,
|
||||
elapsedMs: 0,
|
||||
durationMs: 0,
|
||||
startedWallAt: 0,
|
||||
startedElapsedMs: 0,
|
||||
passings: [],
|
||||
timer: null,
|
||||
};
|
||||
}
|
||||
|
||||
export function loadSessionReplay(runtime, session, result) {
|
||||
const passings = normalizeSessionReplayPassings(result?.passings);
|
||||
runtime.sessionId = String(session?.id || "");
|
||||
runtime.sessionType = String(session?.type || "");
|
||||
runtime.sessionName = String(session?.name || "");
|
||||
runtime.status = passings.length ? "paused" : "empty";
|
||||
runtime.elapsedMs = 0;
|
||||
runtime.durationMs = passings.length > 1 ? Math.max(0, passings[passings.length - 1].timestamp - passings[0].timestamp) : 0;
|
||||
runtime.startedWallAt = 0;
|
||||
runtime.startedElapsedMs = 0;
|
||||
runtime.passings = passings;
|
||||
return runtime;
|
||||
}
|
||||
|
||||
export function playSessionReplay(runtime, nowTs = Date.now()) {
|
||||
if (!runtime.passings.length) return false;
|
||||
if (runtime.elapsedMs >= runtime.durationMs && runtime.durationMs > 0) runtime.elapsedMs = 0;
|
||||
runtime.status = "running";
|
||||
runtime.startedWallAt = nowTs;
|
||||
runtime.startedElapsedMs = runtime.elapsedMs;
|
||||
return true;
|
||||
}
|
||||
|
||||
export function pauseSessionReplay(runtime, nowTs = Date.now()) {
|
||||
if (runtime.status !== "running") return false;
|
||||
advanceSessionReplay(runtime, nowTs);
|
||||
runtime.status = "paused";
|
||||
return true;
|
||||
}
|
||||
|
||||
export function resetSessionReplay(runtime) {
|
||||
runtime.status = runtime.passings.length ? "paused" : "idle";
|
||||
runtime.elapsedMs = 0;
|
||||
runtime.startedWallAt = 0;
|
||||
runtime.startedElapsedMs = 0;
|
||||
return runtime;
|
||||
}
|
||||
|
||||
export function setSessionReplaySpeed(runtime, speed, nowTs = Date.now()) {
|
||||
if (runtime.status === "running") {
|
||||
advanceSessionReplay(runtime, nowTs);
|
||||
runtime.startedWallAt = nowTs;
|
||||
runtime.startedElapsedMs = runtime.elapsedMs;
|
||||
}
|
||||
runtime.speed = [1, 2, 5, 10].includes(Number(speed)) ? Number(speed) : 1;
|
||||
return runtime.speed;
|
||||
}
|
||||
|
||||
export function advanceSessionReplay(runtime, nowTs = Date.now()) {
|
||||
if (runtime.status !== "running") return runtime.elapsedMs;
|
||||
const advanced = runtime.startedElapsedMs + Math.max(0, nowTs - runtime.startedWallAt) * runtime.speed;
|
||||
runtime.elapsedMs = Math.min(runtime.durationMs, advanced);
|
||||
if (runtime.elapsedMs >= runtime.durationMs) runtime.status = "finished";
|
||||
return runtime.elapsedMs;
|
||||
}
|
||||
|
||||
export function buildSessionReplaySnapshot(runtime) {
|
||||
const firstTimestamp = runtime.passings[0]?.timestamp || 0;
|
||||
const cutoff = firstTimestamp + Math.max(0, runtime.elapsedMs);
|
||||
const visiblePassings = runtime.passings.filter((passing, index) => index === 0 || passing.timestamp <= cutoff);
|
||||
const competitors = new Map();
|
||||
|
||||
visiblePassings.forEach((passing) => {
|
||||
const key = passing.competitorKey;
|
||||
const row = competitors.get(key) || {
|
||||
key,
|
||||
driverName: passing.driverName,
|
||||
carName: passing.carName,
|
||||
transponder: passing.transponder,
|
||||
laps: 0,
|
||||
bestLapMs: 0,
|
||||
lastLapMs: 0,
|
||||
firstTimestamp: passing.timestamp,
|
||||
lastTimestamp: passing.timestamp,
|
||||
};
|
||||
row.driverName = passing.driverName || row.driverName;
|
||||
row.carName = passing.carName || row.carName;
|
||||
row.transponder = passing.transponder || row.transponder;
|
||||
row.lastTimestamp = passing.timestamp;
|
||||
if (passing.validLap !== false && passing.lapMs > 0) {
|
||||
row.laps += 1;
|
||||
row.lastLapMs = passing.lapMs;
|
||||
row.bestLapMs = row.bestLapMs ? Math.min(row.bestLapMs, passing.lapMs) : passing.lapMs;
|
||||
}
|
||||
competitors.set(key, row);
|
||||
});
|
||||
|
||||
const practiceRanking = PRACTICE_TYPES.has(runtime.sessionType);
|
||||
const leaderboard = [...competitors.values()]
|
||||
.map((row) => ({ ...row, totalMs: Math.max(0, row.lastTimestamp - row.firstTimestamp) }))
|
||||
.sort((left, right) => {
|
||||
if (practiceRanking) {
|
||||
return (left.bestLapMs || Number.MAX_SAFE_INTEGER) - (right.bestLapMs || Number.MAX_SAFE_INTEGER)
|
||||
|| right.laps - left.laps
|
||||
|| left.driverName.localeCompare(right.driverName);
|
||||
}
|
||||
return right.laps - left.laps || left.totalMs - right.totalMs || left.driverName.localeCompare(right.driverName);
|
||||
});
|
||||
|
||||
return {
|
||||
status: runtime.status,
|
||||
elapsedMs: runtime.elapsedMs,
|
||||
durationMs: runtime.durationMs,
|
||||
progress: runtime.durationMs ? Math.min(1, runtime.elapsedMs / runtime.durationMs) : visiblePassings.length ? 1 : 0,
|
||||
visiblePassings,
|
||||
latestPassing: visiblePassings[visiblePassings.length - 1] || null,
|
||||
leaderboard,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user