Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
131 commits
Select commit Hold shift + click to select a range
c6b015c
add framerecorder
samfreund Nov 14, 2025
9c79dd2
use match data for recording
samfreund Nov 14, 2025
236db2e
add library calls
samfreund Nov 14, 2025
e5d2b05
different strategies for frame capture
samfreund Nov 11, 2025
3a703e8
add available disk space to cmd base
samfreund Nov 12, 2025
19cab3c
add available memory check
samfreund Nov 12, 2025
33e5fc7
add function to delete old recordings when more space is requested
samfreund Nov 13, 2025
4ee9672
add function to get recordings path
samfreund Nov 13, 2025
d01d4a1
send list of recordings to frontend
samfreund Nov 13, 2025
f0389db
change recording save path
samfreund Nov 14, 2025
b9d77cf
recordings frontend interface
samfreund Nov 14, 2025
5b62e36
stub out recording export
samfreund Nov 14, 2025
f94b20b
implement settable strategy
samfreund Nov 14, 2025
5610f27
export/delete recordings backend
samfreund Nov 14, 2025
6b9cb9b
optimize copying
samfreund Nov 16, 2025
fa2aab5
use delete confirmation modal
samfreund Nov 19, 2025
63c712c
snapshot export method
samfreund Nov 24, 2025
94fa00e
use dynamically created links for downloading
samfreund Nov 24, 2025
4539fa6
lint
samfreund Nov 29, 2025
ade6dab
determine space needed using cameras
samfreund Nov 29, 2025
7383200
lint
samfreund Nov 30, 2025
a0980c6
reserve space client side
samfreund Nov 30, 2025
3cca2c3
start addressing comments
samfreund Dec 1, 2025
2518552
Merge branch 'main' into record
samfreund Dec 6, 2025
f652774
Merge branch 'main' into record
samfreund Dec 14, 2025
ee5fc03
Merge branch 'main' into record
samfreund Dec 26, 2025
09bae42
fix typo
samfreund Jan 22, 2026
a219e30
Merge branch 'main' into record
samfreund Jan 22, 2026
f16b283
WIP
samfreund Jan 23, 2026
2806ea0
WIP
samfreund Jan 24, 2026
7c1dfa4
Merge branch 'main' into record
JosephTLockwood May 12, 2026
8d2be29
fix(recorder): correct disk-space threshold from 4 KB to 4 GB
JosephTLockwood May 12, 2026
7d91b4b
fix(recorder): clone Mat before queueing to fix use-after-free
JosephTLockwood May 12, 2026
d0048e6
fix(recorder): exportAll moves file into directory, not over it
JosephTLockwood May 12, 2026
cac7a1b
fix(recorder): close Files.list stream in exportAll
JosephTLockwood May 12, 2026
e057f92
fix(server): reject path traversal in recording endpoints
JosephTLockwood May 12, 2026
a0805ca
feat(recorder): thread capture timestamp + sequence id through record…
JosephTLockwood May 12, 2026
5e21401
feat(recorder): per-frame metadata sidecar JSONL
JosephTLockwood May 12, 2026
13d9228
feat(recorder): live H.264 encoder via FFmpeg subprocess
JosephTLockwood May 12, 2026
4d8e376
fix(recorder): fail construction if metadata sidecar can't open
JosephTLockwood May 12, 2026
60f872c
fix(recorder): write metadata before frame to keep replay invariant
JosephTLockwood May 12, 2026
0130537
docs(recorder): document capture_ns epoch and replay timing contract
JosephTLockwood May 12, 2026
334a434
fix(recorder): skip redundant disk check on first frame
JosephTLockwood May 12, 2026
e86e492
test(recorder): synthetic smoke for encode + metadata pipeline
JosephTLockwood May 12, 2026
a4da7df
feat(recorder): switch from H.264 mp4 to MJPEG-AVI
JosephTLockwood May 12, 2026
e67f3cb
docs(recorder): document NT-driven recording trigger path
JosephTLockwood May 12, 2026
2ced1aa
feat(recorder): switch from MJPEG-AVI to JPEG image sequence
JosephTLockwood May 12, 2026
e0a875f
fix(recorder): close frameRecorder NPE race + zip download extension
JosephTLockwood May 12, 2026
4a1c5de
fix(recorder): sanitize NT match data through PathSafety before resol…
JosephTLockwood May 12, 2026
9600701
fix(recorder): snapshot frameRecorder before use in release/setRecord…
JosephTLockwood May 12, 2026
54f4f40
fix(recorder): drop redundant mkdirs in setRecording
JosephTLockwood May 12, 2026
f198981
fix(client): URL-encode recording and camera names in download links
JosephTLockwood May 12, 2026
8d21e24
chore(client): remove dead exportRecordings anchor element
JosephTLockwood May 12, 2026
355adbc
fix(recorder): serialize setRecording and release to prevent orphan r…
JosephTLockwood May 12, 2026
dd95b78
fix(client): drop undefined from selectedRecordings value type
JosephTLockwood May 12, 2026
a644a85
fix(recorder): make setRecording a no-op on Libcamera + File providers
JosephTLockwood May 12, 2026
9b5bb40
chore(recorder): drop redundant snapshot pattern inside synchronized …
JosephTLockwood May 12, 2026
d29654c
fix(recorder): treat whitespace-only matchData as missing in setRecor…
JosephTLockwood May 12, 2026
cdab76d
chore(recorder): direct field check instead of getRecording inside sy…
JosephTLockwood May 12, 2026
bc555b7
feat(replay): add FileLogCamera type for recorded-frame replay
JosephTLockwood May 12, 2026
7d2bc1c
feat(replay): metadata.jsonl sidecar reader
JosephTLockwood May 12, 2026
df8f909
feat(replay): FileLogFrameProvider decodes mp4 + sidecar into Frames
JosephTLockwood May 12, 2026
4118eab
feat(replay): inline real-time pacing in FileLogFrameProvider
JosephTLockwood May 12, 2026
db7041c
feat(replay): FileLogVisionSource wires provider into VisionModule
JosephTLockwood May 12, 2026
e61e98c
feat(replay): enumerate recording dirs in getConnectedCameras
JosephTLockwood May 12, 2026
b5fa58b
feat(replay): UI surface for unmatched file-log cameras
JosephTLockwood May 12, 2026
f0b55bd
docs(replay): document FileLogFrameProvider timestamp + calibration c…
JosephTLockwood May 12, 2026
c4539af
feat(replay): adapt to MJPEG-AVI recording format
JosephTLockwood May 12, 2026
8e01037
feat(replay): adapt to JPEG image-sequence recording format
JosephTLockwood May 12, 2026
509ae64
chore(replay): post-audit cleanup of recorder + replay provider
JosephTLockwood May 12, 2026
84df3ef
feat(replay): tighten enumerator filter to require frames/000000.jpg
JosephTLockwood May 12, 2026
b3ab59a
chore(replay): drop hollow FileLogSourceSettables, fix recording file…
JosephTLockwood May 12, 2026
a9af5a7
fix(replay): make FileLogFrameProvider.setRecording a no-op instead o…
JosephTLockwood May 12, 2026
57e8073
feat(replay): badge file-log cameras with a Replay chip in dropdowns
JosephTLockwood May 12, 2026
d4df14e
fix(replay): park vision thread at EOF instead of marking it disconne…
JosephTLockwood May 12, 2026
26e8fde
fix(replay): drop pv-select #item slot override to keep dropdown clic…
JosephTLockwood May 12, 2026
720c809
docs(replay): tighten FileLogFrameProvider comments + document EOF ca…
JosephTLockwood May 12, 2026
f03ecf4
style(replay): apply spotlessApply reformat across record/replay files
JosephTLockwood May 12, 2026
0f87d12
fix(recordings): silence ESLint errors in RecordingsCard
JosephTLockwood May 12, 2026
a5e62de
fix(replay): keep generic <T> cast out of pv-select template
JosephTLockwood May 12, 2026
4dc76eb
feat(replay): introduce JsonResultExporter + writer-side tests
JosephTLockwood May 12, 2026
884d243
fix(replay): close JsonResultExporter writer on header-write failure
JosephTLockwood May 12, 2026
0697974
feat(replay): tee File Log Camera pipeline results to JsonResultExporter
JosephTLockwood May 13, 2026
2f2d6fd
fix(replay): harden VisionModule JSON-export tee against stop/open races
JosephTLockwood May 13, 2026
84ad51e
feat(record,replay): write tss.json at recording-start and read it du…
JosephTLockwood May 13, 2026
ccbdcf4
fix(replay): route tss snapshot read errors through Logger; warn on i…
JosephTLockwood May 13, 2026
b0c9546
test(replay): record→export end-to-end determinism + tuning-sensitivi…
JosephTLockwood May 13, 2026
710d858
docs(replay): record→replay→export→AKit JSON consumption reference
JosephTLockwood May 13, 2026
e5b236e
refactor(replay): unify JsonResultExporter on a single static Logger
JosephTLockwood May 13, 2026
6a06617
refactor(replay): drop dead tssSnapshotWarned flag and FQN'd IOException
JosephTLockwood May 13, 2026
9925770
refactor(replay): swap OffsetSnapshot's Boolean/Long for an Optional<>
JosephTLockwood May 13, 2026
1b2dd3e
refactor(replay): expose getFrameProvider/setFrameProvider on VisionS…
JosephTLockwood May 13, 2026
de647a6
fix(replay): keep VisionSource provider non-null on release; reject n…
JosephTLockwood May 13, 2026
3151b4f
feat(replay): VisionModule.startReplay/cancelReplay + isReplaying NT
JosephTLockwood May 13, 2026
69cff6d
fix(replay): leak-on-error, NT state on rename, executor race in star…
JosephTLockwood May 13, 2026
010eddb
feat(replay): gate JsonResultExporter tee on runtime FrameProvider type
JosephTLockwood May 13, 2026
e194995
feat(server): POST /api/recordings/replay + /cancel
JosephTLockwood May 13, 2026
9daeae1
feat(client): per-row Replay button on RecordingsCard
JosephTLockwood May 13, 2026
dd8ffdb
feat(client): ReplayPanel on the Cameras tab
JosephTLockwood May 13, 2026
08788c4
chore(replay): remove obsolete File Log Camera enumeration path
JosephTLockwood May 13, 2026
96477dd
docs(replay): rewrite replay-side section for the in-place swap workflow
JosephTLockwood May 13, 2026
6d64421
chore(replay): revert dead-refactor noise in shared Vue files
JosephTLockwood May 13, 2026
40be5ab
refactor(replay): keep VisionSource provider getter/setter non-final
JosephTLockwood May 13, 2026
b4e0481
chore(recorder): drop the unused strat-file marker
JosephTLockwood May 13, 2026
24e6c08
chore(recorder): rename RecordingStrategy.VIDEO back to SNAPSHOTS
JosephTLockwood May 13, 2026
2ca35b0
refactor(recorder): drop VisionSourceManager lookup from exportCamera
JosephTLockwood May 13, 2026
9eb58da
feat(replay): drop ReplayPanel; keep the per-row Replay button only
JosephTLockwood May 13, 2026
c549e2a
feat(server): drop unused /api/recordings/replay/cancel endpoint
JosephTLockwood May 13, 2026
646f47c
chore(replay): collapse FrameLogFormat into a FrameRecorder static
JosephTLockwood May 13, 2026
cdf74f3
docs(replay): trim FrameRecorder / FileLogFrameProvider / MetadataSid…
JosephTLockwood May 13, 2026
897362b
docs(replay): drop unreferenced docs/replay.md
JosephTLockwood May 13, 2026
8fc02d0
test(replay): tighten FileLogFrameProvider negative-ctor tests
JosephTLockwood May 13, 2026
3dd9836
test(replay): consolidate MetadataSidecarReader negative tests
JosephTLockwood May 13, 2026
6d0341d
chore(recorder): collapse the 3-arg FrameRecorder ctor
JosephTLockwood May 13, 2026
8b8f916
test(replay): drop unused TestSettables from frame-provider-swap test
JosephTLockwood May 13, 2026
df7a0fd
chore(recorder): drop unread sampledAtWpiNtNowNs field from TssSample
JosephTLockwood May 13, 2026
d1898ac
chore(replay): trim VisionModule.teeToJsonResultExporter
JosephTLockwood May 13, 2026
11db0b8
chore(replay): fold parkUntilInterrupted into emptyOrPark
JosephTLockwood May 13, 2026
d00b999
chore(replay): trim FrameProvider.frameRecorder comment
JosephTLockwood May 13, 2026
09de187
refactor(replay): extract USBFrameProvider.offerToRecorder helper
JosephTLockwood May 13, 2026
df6da43
fix(server): make exportIndividual stream cleanly + name zip by nickname
JosephTLockwood May 13, 2026
4217b11
feat(server): re-add POST /api/recordings/replay/cancel
JosephTLockwood May 13, 2026
807ace8
feat(server): GET /api/recordings/results + /result for per-tuning JS…
JosephTLockwood May 13, 2026
775fc94
feat(server): GET /api/recordings/replay/status for polling UIs
JosephTLockwood May 13, 2026
9a1654b
feat(client): useReplayStatus composable for /replay/status polling
JosephTLockwood May 13, 2026
bf198ab
feat(client): ReplayBanner mounted in App.vue
JosephTLockwood May 13, 2026
4be1259
style(client): prettier reflow ReplayBanner
JosephTLockwood May 13, 2026
751ba8b
feat(client): inline processed-stream preview in RecordingsCard
JosephTLockwood May 13, 2026
52aa7c1
feat(client): results dropdown + auto-download on replay end
JosephTLockwood May 13, 2026
4bf9e7a
fix(replay): gate json export to recording's capture_ns window
JosephTLockwood May 13, 2026
864ce47
style: apply spotless reformat across record/replay files
JosephTLockwood May 15, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions photon-client/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { inject, onBeforeMount } from "vue";
import PhotonSidebar from "@/components/app/photon-sidebar.vue";
import PhotonLogView from "@/components/app/photon-log-view.vue";
import PhotonErrorSnackbar from "@/components/app/photon-error-snackbar.vue";
import ReplayBanner from "@/components/common/ReplayBanner.vue";
import { useTheme } from "vuetify";
import { restoreThemeConfig } from "@/lib/ThemeManager";

Expand Down Expand Up @@ -63,6 +64,7 @@ onBeforeMount(() => {
<template>
<v-app>
<photon-sidebar />
<replay-banner />
<v-main>
<v-container class="main-container" fluid fill-height>
<v-layout>
Expand Down
113 changes: 113 additions & 0 deletions photon-client/src/components/common/ReplayBanner.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
<script setup lang="ts">
import { computed } from "vue";
import { axiosPost } from "@/lib/PhotonUtils";
import { useCameraSettingsStore } from "@/stores/settings/CameraSettingsStore";
import { useReplayStatus } from "@/composables/useReplayStatus";

const { active } = useReplayStatus();
const cameraStore = useCameraSettingsStore();

interface BannerRow {
cameraUniqueName: string;
nickname: string;
recordingName: string;
currentFrame: number;
totalFrames: number;
progress: number;
}

const ROW_HEIGHT_PX = 64;

const rows = computed<BannerRow[]>(() =>
active.value.map((entry) => {
const camera = cameraStore.cameras[entry.cameraUniqueName];
const total = entry.totalFrames > 0 ? entry.totalFrames : 1;
return {
cameraUniqueName: entry.cameraUniqueName,
nickname: camera?.nickname ?? entry.cameraUniqueName,
recordingName: entry.recordingName,
currentFrame: entry.currentFrame,
totalFrames: entry.totalFrames,
progress: Math.min(100, Math.round((entry.currentFrame / total) * 100))
};
})
);

const cancelReplay = (cameraUniqueName: string) => {
void axiosPost("/recordings/replay/cancel", "cancel replay on " + cameraUniqueName, { cameraUniqueName });
};
</script>

<template>
<v-app-bar
v-if="rows.length > 0"
location="top"
:height="rows.length * ROW_HEIGHT_PX"
color="info"
flat
role="status"
aria-live="polite"
>
<div class="replay-banner-stack">
<div v-for="row in rows" :key="row.cameraUniqueName" class="replay-banner-row">
<div class="replay-banner-content">
<div class="replay-banner-text">
<strong>Replaying</strong>
<span class="replay-banner-recording">{{ row.recordingName }}</span>
<span>on</span>
<strong>{{ row.nickname }}</strong>
<span class="replay-banner-counter">— frame {{ row.currentFrame }} / {{ row.totalFrames }}</span>
</div>
<v-btn size="small" color="error" variant="flat" @click="cancelReplay(row.cameraUniqueName)">
<v-icon start>mdi-stop</v-icon>
Cancel
</v-btn>
</div>
<v-progress-linear :model-value="row.progress" color="white" bg-color="rgba(255, 255, 255, 0.2)" height="3" />
</div>
</div>
</v-app-bar>
</template>

<style scoped>
.replay-banner-stack {
display: flex;
flex-direction: column;
width: 100%;
}

.replay-banner-row {
display: flex;
flex-direction: column;
justify-content: center;
gap: 0.25rem;
padding: 0.5rem 1rem;
border-bottom: 1px solid rgba(255, 255, 255, 0.12);
}

.replay-banner-row:last-child {
border-bottom: none;
}

.replay-banner-content {
display: flex;
align-items: center;
justify-content: space-between;
gap: 1rem;
}

.replay-banner-text {
display: flex;
flex-wrap: wrap;
gap: 0.4rem;
align-items: baseline;
}

.replay-banner-recording {
font-family: monospace;
}

.replay-banner-counter {
opacity: 0.85;
}
</style>
Loading
Loading