diff --git a/server/install_server.sh b/server/install_server.sh index 22e547c4..9ca24657 100755 --- a/server/install_server.sh +++ b/server/install_server.sh @@ -6,6 +6,7 @@ MEDIAMTX_BASE_URL="https://github.com/bluenviron/mediamtx/releases/download/v${M MEDIAMTX_BIN="/usr/local/bin/mediamtx" MEDIAMTX_CONF_DIR="/etc/mediamtx" MEDIAMTX_CONFIG="$MEDIAMTX_CONF_DIR/mediamtx.yml" +ROVER_SNAPSHOT_WRITER_BIN="/usr/local/bin/rover-snapshot-writer.sh" MEDIAMTX_SERVICE="/etc/systemd/system/mediamtx.service" MULTIROVER_SERVICE="/etc/systemd/system/multirover.service" SNAPSHOT_DIR="/var/lib/rover-snapshots" @@ -26,6 +27,7 @@ SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd) SERVER_DIR="$SCRIPT_DIR" CONFIG_PATH="$SERVER_DIR/config.yaml" MEDIAMTX_TEMPLATE="$SERVER_DIR/mediamtx/mediamtx.yml" +ROVER_SNAPSHOT_WRITER_TEMPLATE="$SERVER_DIR/mediamtx/rover-snapshot-writer.sh" echo "[1/6] Installing dependencies..." dnf install -y nodejs npm curl tar >/dev/null @@ -70,9 +72,15 @@ if [[ ! -f "$MEDIAMTX_TEMPLATE" ]]; then echo "mediaMTX template missing at $MEDIAMTX_TEMPLATE" >&2 exit 1 fi +if [[ ! -f "$ROVER_SNAPSHOT_WRITER_TEMPLATE" ]]; then + echo "Snapshot writer template missing at $ROVER_SNAPSHOT_WRITER_TEMPLATE" >&2 + exit 1 +fi echo " Installing mediaMTX config -> $MEDIAMTX_CONFIG" rm -f "$MEDIAMTX_CONFIG" install -m 0644 "$MEDIAMTX_TEMPLATE" "$MEDIAMTX_CONFIG" +echo " Installing rover snapshot writer -> $ROVER_SNAPSHOT_WRITER_BIN" +install -m 0755 "$ROVER_SNAPSHOT_WRITER_TEMPLATE" "$ROVER_SNAPSHOT_WRITER_BIN" chown -R "$TARGET_USER":"$TARGET_USER" "$MEDIAMTX_CONF_DIR" echo "[4/6] Writing systemd units..." diff --git a/server/mediamtx/mediamtx.yml b/server/mediamtx/mediamtx.yml index 57b89a55..63c1f4d0 100644 --- a/server/mediamtx/mediamtx.yml +++ b/server/mediamtx/mediamtx.yml @@ -41,3 +41,7 @@ paths: all: source: publisher sourceOnDemand: no + # Rover Snapshot Writer + # Keep rover snapshots continuously updated while a rover video path is live. + runOnReady: /usr/local/bin/rover-snapshot-writer.sh + runOnReadyRestart: yes diff --git a/server/mediamtx/rover-snapshot-writer.sh b/server/mediamtx/rover-snapshot-writer.sh new file mode 100644 index 00000000..40107489 --- /dev/null +++ b/server/mediamtx/rover-snapshot-writer.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash +# Rover Snapshot Writer Hook +# Purpose: Runs under mediaMTX runOnReady to keep per-rover JPEG snapshots updated on disk. +# Scope: Writes only rover video path snapshots and ignores audio/forward/room paths. +set -euo pipefail + +PATH_NAME="${MTX_PATH:-}" +SNAP_DIR="${ROVER_SNAPSHOT_DIR:-/var/lib/rover-snapshots}" + +# Ignore non-rover-video paths. +case "$PATH_NAME" in + ""|*-audio|*-fwd|room/*) + exit 0 + ;; +esac + +mkdir -p "$SNAP_DIR" + +exec ffmpeg -hide_banner -loglevel warning \ + -i "srt://127.0.0.1:9000?streamid=read:${PATH_NAME}" \ + -an \ + -vf fps=2 \ + -q:v 6 \ + -update 1 \ + "${SNAP_DIR}/${PATH_NAME}.jpg" diff --git a/server/src/services/roverSnapshotService/poller.js b/server/src/services/roverSnapshotService/poller.js index 6e7ce192..acfa8206 100644 --- a/server/src/services/roverSnapshotService/poller.js +++ b/server/src/services/roverSnapshotService/poller.js @@ -10,6 +10,7 @@ const SNAPSHOT_DIR = process.env.ROVER_SNAPSHOT_DIR || '/var/lib/rover-snapshots const POLL_INTERVAL_MS = 300; const roverState = new Map(); const events = new EventEmitter(); +const readCounts = new Map(); let pollTimer = null; function markState(id, updates = {}) { @@ -35,6 +36,11 @@ function createRoverSnapshotPoller({ roverManager }) { const buffer = await fs.readFile(filePath); const ts = stats.mtimeMs || Date.now(); markState(id, { frame: buffer, ts, error: null, failures: 0, mtimeMs: stats.mtimeMs }); + const count = (readCounts.get(id) || 0) + 1; + readCounts.set(id, count); + if (count === 1 || count % 30 === 0) { + logger.warn('Snapshot read', { id, count, ts, bytes: buffer.length, mtimeMs: stats.mtimeMs }); + } events.emit('frame', { id, buffer, ts }); return { frame: buffer, ts }; } catch (err) { diff --git a/server/src/services/roverSnapshotService/socketGateway.js b/server/src/services/roverSnapshotService/socketGateway.js index 5d18626e..b9ee10cb 100644 --- a/server/src/services/roverSnapshotService/socketGateway.js +++ b/server/src/services/roverSnapshotService/socketGateway.js @@ -25,6 +25,7 @@ function registerRoverSnapshotSocketGateway({ roverManager, roverSnapshotEvents, const socketSubscriptions = new Map(); const subscribeBuckets = new Map(); const lastSentBySocket = new Map(); + const sentCounts = new Map(); function addSubscription(socket, roverId) { if (!roverSubscribers.has(roverId)) roverSubscribers.set(roverId, new Set()); @@ -84,6 +85,12 @@ function registerRoverSnapshotSocketGateway({ roverManager, roverSnapshotEvents, const now = ts || Date.now(); if (now - lastSent < STREAM_INTERVAL_MS) return; lastMap.set(id, now); + const key = `${socketId}:${id}`; + const sentCount = (sentCounts.get(key) || 0) + 1; + sentCounts.set(key, sentCount); + if (sentCount === 1 || sentCount % 30 === 0) { + logger.warn('Snapshot frame sent', { socketId, roverId: id, sentCount, ts, bytes: buffer.length }); + } sendFrame(socket, id, { ts }, buffer); }); });