Reconnect options (-reconnect, -reconnect_streamed, etc.) only apply to
network inputs and cause 'Option reconnect not found' errors when the
input is pipe:0. Curl already handles connection resilience.
The previous -max_alloc approach does not fix the 'overlong headers' error
because FFmpeg 5.1 (Debian bookworm) uses a compile-time stack-allocated
buffer (MAX_URL_SIZE=4096) for HTTP response headers, which -max_alloc
cannot change.
Instead, when the stream URL is HTTP/HTTPS, launch curl to handle the
HTTP connection and pipe its stdout to FFmpeg via stdin (pipe:0). Curl
does not have the 4096-byte header limit, so it handles oversized CDN
response headers from Douyin without error.
Additional changes:
- RequestStopAsync kills curl first (instead of sending 'q' to FFmpeg),
causing the pipe to close and FFmpeg to exit gracefully on EOF.
- SessionProcessRuntime tracks the curl process for cleanup.
- Add -max_alloc 100000000 to FFmpeg arguments for HTTP inputs to avoid
'overlong headers' error when CDN (e.g. Douyin) returns oversized HTTP
response headers exceeding FFmpeg's default 4096-byte buffer.
- Add exponential backoff for repeated startup failures (30s → 15min cap)
to break the tight fail→retry→re-poll loop that floods notifications.
- Throttle startup failure notifications to at most one per 30 minutes per
room to prevent email/webhook storms during persistent failures.
- Reset backoff counter when a session successfully opens its first segment.
The ternary in GetLowStoragePauseMessageAsync had its branches swapped:
when storage was healthy it returned the pause error, and when storage
was critically low it returned null (allowing finalization to proceed).
This caused MP4 finalization to always pause with a misleading 'storage
is Red' warning even when storage protection was disabled.
The case statement that sets RID based on TARGETARCH runs in a /bin/sh
subshell. When it was in a separate RUN from dotnet restore/publish,
the RID variable was not available to the publish command.
Merge the case dispatch, dotnet restore, and dotnet publish into a
single RUN so the RID shell variable stays in scope. Also restores
the COPY . . directive that was accidentally dropped.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The previous inline case statement was missing proper variable assignment.
Use 'case' to set DOTNET_RID directly instead of trying to capture output
of a command substitution that contained a multi-branch case.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Removing RuntimeIdentifier from the csproj caused dotnet publish to produce
ALL platform runtimes (20+) in the output, making runtime loading ambiguous
and causing FileLoadException at startup.
Instead of hardcoding the RID in the csproj, use Docker's built-in TARGETARCH
ARG (injected automatically by buildx for multi-platform builds):
amd64 -> -r linux-x64
arm64 -> -r linux-arm64
This ensures native deps (SQLitePCLRaw, EF Core) resolve to exactly one
architecture per image, while keeping the csproj clean for local dev.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The csproj had <RuntimeIdentifier>linux-arm64</RuntimeIdentifier> baked in,
which forced dotnet publish to produce linux-arm64 native output on EVERY
platform (including amd64). On amd64 containers the runtime could not load
the entry assembly, causing FileLoadException crash loops.
RID is unnecessary for framework-dependent builds (--p:UseAppHost=false
is already in the Dockerfile). Removing it lets each platform produce
its natural runtime output — the base dotnet/aspnet image already has
the correct architecture's runtime.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
nuget.org is only reachable through the 7890 proxy from the build network
(direct hits return a 302 redirect loop; proxied requests return 200,
verified 8/8). The pipeline passes --build-arg HTTP_PROXY/HTTPS_PROXY, but
Dockerfile did not declare these ARGs in the build stage, so buildkit did
not inject them into the RUN environment and 'dotnet restore' tried nuget.org
directly -> NU1301 'Unable to load the service index'.
Declare HTTP_PROXY/HTTPS_PROXY/NO_PROXY (upper+lower case) as build-stage
ARGs and promote them to ENV so dotnet's HttpClient uses the proxy. Also add
two restore retries (--disable-parallel) to smooth over transient proxy
blips.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
DashboardService was constructing 'today in Beijing' bounds with a +08:00 offset, which Npgsql rejects when binding to a timestamp with time zone column. Added .ToUniversalTime() to normalize to UTC (same instant, offset 0), matching the existing pattern in SessionAnalyticsService.GetUtcWindow.
Under the Red storage tier, MP4 finalization (TS->MP4 remux) was being skipped, so tasks never reached Completed and the segment_completed event script — which uploads the file and deletes the local source to free space — never ran. The disk could never recover, deadlocking all recording and transcoding.
Two reversed checks caused this: (1) FfmpegService gated finalization on the legacy HasEnoughSpace MB threshold (effectively 4GB) instead of the tier system, and (2) the polling loop only resumed paused finalizations when NOT in the Red tier. Now finalization is gated solely on ShouldPauseActive (true Red only) and the polling loop always attempts to resume it every cycle, since finalization is the very mechanism that frees space. Once any segment finalizes, the upload+delete script runs and the disk recovers, letting the rest finish.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Pin the WebApi to RuntimeIdentifier=linux-arm64 (framework-dependent) and rework the Dockerfile for ARM64: copy the full context up front, add QEMU emulation workarounds, and drop debug symbols / cap parallelism during publish.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Introduce a process-wide DatabaseCircuitBreaker that fails fast when Postgres is unavailable (e.g. disk full) instead of letting every request burn doomed EF Core retries. CircuitAwareExecutionStrategy derives from NpgsqlRetryingExecutionStrategy and records success/failure around the public Execute/ExecuteAsync seams; background workers skip work and back off while the circuit is open; the exception middleware maps an open circuit (and other DB outages) to 503. Adds /health (liveness) and /health/ready (readiness, reporting circuit state), plus unit tests for the open/half-open transitions and non-transient SQLSTATE detection.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add a segment-level cleanup alongside the existing session-level one. DeleteMissingFileTasksAsync scans all non-active record tasks, keeps those whose video file no longer exists on disk, and deletes them individually via DeleteTasksAsync (which also drops any session left empty). Exposed as POST /record-tasks/delete-missing-files and a new "清理无文件分片" action in the record tasks view.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- CanStartNewRecording now purely based on Tier==Green (was HasEnoughSpace||Green)
- PollingBackgroundService now uses ShouldPauseActive instead of MB-based CheckShouldPause
- Both pause and start checks consolidated into single guardCheck call
- Old MB pause/resume thresholds still work as secondary safety via hasEnoughSpace
Co-Authored-By: Claude Opus 4.8 noreply@anthropic.com
Add StorageGreenThresholdPercent (default 30%) and StorageRedThresholdPercent
(default 10%) to both SystemSettingsDto and the settings UI.
StorageGuardService now reads thresholds from settings instead of hardcoding.
Co-Authored-By: Claude Opus 4.8 noreply@anthropic.com
DbContext is not thread-safe. Task.WhenAll caused concurrent access
within the same scoped DbContext, throwing 'A second operation was
started on this context instance' errors on slower machines.
Co-Authored-By: Claude Opus 4.8 noreply@anthropic.com
Raspberry Pi PostgreSQL is I/O constrained. 60s timeout was too short
for concurrent writes during heavy recording sessions, causing timeout +
retry causing duplicate key violations.
- CommandTimeout: 60s -> 120s
- maxRetryCount: 5 -> 3
- maxRetryDelay: 10s -> 15s
Co-Authored-By: Claude Opus 4.8 noreply@anthropic.com
PlatformHttpClientFactory was holding a direct ISystemSettingsService reference
(Scoped). When the danmaku connection's request scope ended, retry attempts failed
with ObjectDisposedException on LiveRecorderDbContext.
Changed to use IServiceScopeFactory to create a fresh scope on each CreateAsync call,
so the danmaku retry loop always gets a live DbContext.
Co-Authored-By: Claude Opus 4.8 noreply@anthropic.com
Storage Tier System:
- Add StorageTier enum (Green >30% / Yellow 10-30% / Red <10%)
- Extend StorageGuardResult with Tier, CanStartNewRecording, ShouldPauseActive, UsagePercent
- Yellow tier: deny new recordings but allow existing to finish and upload
- Red tier: deny new recordings and pause active sessions
- Auto-recovery: when disk frees up, polling automatically resumes new recordings
- Update LiveRoomPollingBackgroundService to use tier-based checks
- Expose tier + usage percent in Recovery API
Dashboard Queue Monitor:
- Add pending transcode count, pending upload count, queued data volume to dashboard
- Add storage tier badge (Green/Yellow/Red) with usage percentage
- Add queue monitoring card to dashboard view
Co-Authored-By: Claude Opus 4.8 noreply@anthropic.com
Dashboard:
- Add DashboardDto, DashboardService with SQL-level aggregate queries
- Add GET /api/dashboard endpoint with real-time system status
- Add repository aggregate methods (CountByAvailability, SumDuration, etc.)
- Add DashboardView.vue as new landing page with KPI cards, storage status, recent sessions, top rooms
- Update router to make dashboard the new / route, add nav item in sidebar
File Preview:
- Add IVideoMetadataService + FfmpegVideoMetadataService for video metadata extraction
- Extend MediaBrowserItemDto with Metadata and ThumbnailUrl fields
- Add includeMetadata param to media browser API, add thumbnail endpoint
- Add SessionPlaylistDto and GET /api/record-sessions/{id}/playlist for continuous playback
Bandwidth Statistics:
- Add -progress pipe:1 to live recording ffmpeg args for bitrate output
- Parse total_size/bitrate/speed from ffmpeg progress lines during recording
- Write bandwidth samples as SystemLogEntry (Category=Bandwidth) every 30s
- Add BandwidthStatisticsService, BandwidthController with session timeline + daily summary
- Add bandwidth TypeScript types
Co-Authored-By: Claude Opus 4.8 noreply@anthropic.com
- Add IDanmakuService interface and DanmakuService implementation to parse danmaku XML files
- Add GET /api/record-tasks/{id}/danmaku and GET /api/record-sessions/{id}/danmaku endpoints
- Add DanmakuPlayer Vue component with native video + CSS overlay danmaku rendering
- Add danmakuEngine.ts pure-TypeScript animation loop with binary search, track management, and event notifications
- Add useDanmakuPlayer composable for reusable danmaku data loading
- Integrate danmaku toggle button into RecordTaskDetailView
- Integrate danmaku replay modal dialog into RecordSessionDetailView segment table
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>