Without a SetReadDeadline, a silently dead WebSocket (e.g. Cloudflare
dropping the connection without a close frame) would block readLoop
forever. The daemon would appear connected but never receive tasks,
and never fall back to HTTP polling.
- Send RFC 6455 pings every 30s (resets Cloudflare's idle timer)
- SetReadDeadline of 45s, refreshed on every pong and text message
- SetWriteDeadline of 10s on all writes to prevent blocked sends
- On timeout, readLoop emits "disconnected" → HybridTransport falls
back to HTTP and starts WS reconnection loop
- Register WatchReporter cancel funcs in streamRegistry so they get
cancelled when switching to a different stream (prevents goroutine leak)
- Re-notify streamReady when the server is already serving the requested
task (handles duplicate stream requests from the web UI)
- Rewrite tests for byte-based tracking semantics, remove dead
parseRangeStart tests
EstimatedProgress now returns video duration in seconds (from ffprobe).
WatchReporter sends Position and Duration fields when available, giving
the server precise playback time instead of just a percentage.
Replace Range-header-based progress tracking with a trackingReader that
measures actual bytes read per connection. This gives accurate playback
position even for local/NAS files where VLC buffers aggressively.
- Token bucket rate limiter at 2x video bitrate (from ffprobe)
- CAS loops for lock-free atomic progress updates without regression
- probeMediaInfo extracts bitrate + duration via ffprobe (3s timeout)
- Defense-in-depth: only probe regular files, reject FIFOs/devices
- Remove dead parseRangeStart function
- Consistent [stream] log prefix
Include StreamPort, LanIP, and TailscaleIP in RegisterRequest so the
server knows the agent's stream endpoints from the moment it registers,
not just after the first heartbeat. Align HeartbeatRequest field order
with RegisterRequest for consistency.
Replace anacrolix/upnp with huin/goupnp + custom NAT-PMP (RFC 6886)
implementation. NAT-PMP is tried first (faster, more compatible with
TP-Link routers), with UPnP-IGD SOAP as fallback. Gateway detection
reads /proc/net/route for accuracy. Includes unit tests with mock
NAT-PMP server and permanent e2e tests (build tag manual).
Track the highest byte offset served by the stream server to estimate
playback progress (0-100%). A WatchReporter goroutine sends progress
to POST /api/internal/agent/watch-progress every 10s during streaming.
- Add maxByteOffset + totalFileSize to StreamServer for Range tracking
- Add FileSize() to fileProvider interface (all 3 providers)
- New WatchReporter: periodic progress reporter tied to daemon context
- New WatchProgressUpdate type with optional progress/position/duration
- Wire reporter into all 3 stream paths (task stream, disk stream, active download stream)
- Replace `upgrade` stub with real command (alias for `self-update`)
- Also register `update` as alias: `unarr update` works too
- Rewrite `status` to show full config, disk usage, daemon state, and
update availability with colored sections
- Add version check cache (1h TTL) so `status` is instant on repeat runs
- Guard against division by zero on empty filesystems
- Guard against negative durations from clock skew
- Guard against stale PID via heartbeat recency check (2 min)
- Add comprehensive test coverage across agent, engine, upgrade, usenet,
arr, library, mediaserver, and UI packages
- Improve Makefile coverage target to exclude cmd/ glue code
- Fix stream handler resource cleanup and ffprobe error handling