Commit Graph
65 Commits
Author SHA1 Message Date
nanxun 19d358b312 feat: improve recording recovery and upload workflow 2026-08-10 11:14:34 +08:00
nanxun e0d3969e46 refactor: move PostgreSQL shared service to dedicated repository 2026-08-03 23:59:25 +08:00
nanxun ecc737f0bd feat: harden recording lifecycle and refresh fnOS UI 2026-08-03 23:45:26 +08:00
nanxun e5b50ea85c feat: add shared PostgreSQL fnOS service and refresh UI 2026-08-02 20:14:39 +08:00
nanxun e8772a39b5 feat: add native fnOS package 2026-08-02 14:43:37 +08:00
nanxun e62dfd2de1 fix: create OpenList target directories before verification 2026-08-01 23:27:02 +08:00
nanxun 9091c1abc7 feat: add reliable OpenList segment uploads 2026-08-01 19:27:56 +08:00
nanxun 12ba62e2a0 fix: skip reconnect options when using curl pipe input
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.
2026-07-09 12:30:11 +08:00
nanxun ad86c080e3 fix: use curl pipe to bypass FFmpeg HTTP header buffer limit
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.
2026-07-09 12:19:34 +08:00
nanxun 8a079b4698 fix: add max_alloc to prevent overlong headers error and throttle startup failure notifications
- 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.
2026-07-09 11:51:52 +08:00
nanxun 48da49ac72 fix: invert reversed storage guard condition in MP4 finalization
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.
2026-07-05 18:10:49 +08:00
nanxunandClaude Opus 4.8 68ab2c773a fix: merge restore+publish+RID into one RUN so shell var persists
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>
2026-07-03 17:22:48 +08:00
nanxunandClaude Opus 4.8 05efe5e125 fix: correct case statement syntax in TARGETARCH -> RID mapping
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>
2026-07-03 17:19:53 +08:00
nanxunandClaude Opus 4.8 7a12b929dd fix: use docker TARGETARCH for runtime-specific dotnet publish
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>
2026-07-03 17:17:38 +08:00
nanxunandClaude Opus 4.8 1c700f67bc fix: remove hardcoded RuntimeIdentifier from csproj causing FileLoadException
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>
2026-07-03 14:29:30 +08:00
nanxunandClaude Opus 4.8 af7a3d1276 fix: declare proxy ARGs in build stage so dotnet restore reaches nuget.org
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>
2026-07-03 13:39:15 +08:00
nanxunandClaude Fable 5 9df174bb0b feat: add OpenList upload support and upload tasks monitoring page
Backend:
- Add OpenListRecordArtifactUploader implementing AList-compatible API upload
- Add Uploading status to RecordArtifactUploadStatus enum
- Add MarkUploadStarted() to RecordResult entity for upload progress tracking
- Add ListUploadStatus API endpoint with pagination and status filtering
- Add UploadTaskItemDto and UploadTaskListResponse models
- Add upload segment count stats (uploaded/failed/uploading) to session DTO
- Add OpenListUploadSettingsDto and upload target type OpenList

Frontend:
- Add UploadTasksView page with route /upload-tasks
- Add upload status labels and UploadTaskItem types
- Refactor MainLayout navigation and clean up main.css
- Polish DashboardView, MetricCard, StatusBadge, RightDrawer components
- Update SettingsView to support OpenList upload configuration

Build:
- Add frontend/Dockerfile.arm64 for ARM64 frontend image
- Update build-arm64-image.sh script

Other:
- Add segment_completed_openlist.sh trigger script
- Add prototype/ directory with UI mockups
- Add frontend .dockerignore refinements

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-01 21:14:50 +08:00
nanxun f8abae0a7a fix: normalize DateTimeOffset to UTC in DashboardService to fix Npgsql offset rejection
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.
2026-06-21 00:01:03 +08:00
nanxunandClaude Opus 4.8 f9c7ec5d43 fix: resolve low-storage deadlock by always resuming MP4 finalization
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>
2026-06-16 15:13:07 +08:00
nanxunandClaude Opus 4.8 df70b64956 build: target linux-arm64 and streamline the Docker image build
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>
2026-06-11 17:52:18 +08:00
nanxunandClaude Opus 4.8 5196ffa0f0 feat: add database circuit breaker and health-readiness endpoint
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>
2026-06-11 17:52:18 +08:00
nanxunandClaude Opus 4.8 6da0690fd3 feat: batch-delete recording segments whose files are missing
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>
2026-06-11 17:52:18 +08:00
nanxunandClaude Opus 4.8 noreply@anthropic.com e640d5b5cc fix: consolidate storage tier with old MB thresholds into single tier system
- 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
2026-06-05 11:57:20 +08:00
nanxunandClaude Opus 4.8 noreply@anthropic.com 0831b33ea0 feat: make storage tier thresholds configurable in system settings
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
2026-06-05 11:55:08 +08:00
nanxunandClaude Opus 4.8 noreply@anthropic.com f5ad1dec00 fix: run dashboard queries sequentially to avoid DbContext concurrency
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
2026-06-05 11:49:48 +08:00
nanxunandClaude Opus 4.8 noreply@anthropic.com 46da2d78f0 perf: increase DB timeout to 120s, reduce retries to 3 with longer delay
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
2026-06-05 01:01:59 +08:00
nanxunandClaude Opus 4.8 noreply@anthropic.com 524a053263 fix: PlatformHttpClientFactory uses IServiceScopeFactory to avoid disposed DbContext in danmaku retry
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
2026-06-05 00:02:42 +08:00
nanxunandClaude Opus 4.8 noreply@anthropic.com 24e5cf2a06 feat: add tiered storage guard (Green/Yellow/Red) and dashboard queue monitor
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
2026-06-04 23:13:26 +08:00
nanxunandClaude Opus 4.8 noreply@anthropic.com cbee29bef9 feat: add dashboard, file preview metadata, and bandwidth statistics
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
2026-06-04 00:30:02 +08:00
nanxunandClaude Opus 4.8 a5c2cc3202 feat: add danmaku replay player integration
- 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>
2026-06-03 18:09:43 +08:00
nanxun b82110461a feat: add storage and script failure notifications 2026-05-31 19:35:06 +08:00
nanxun fd5be2cc8e ci: stabilize buildx heartbeat and cache 2026-05-31 16:48:35 +08:00
nanxun 7a286fd619 feat: expand platform adapters and preview tooling 2026-05-13 19:40:43 +08:00
nanxun c5346940e7 feat: add manual segment completed trigger 2026-05-08 23:33:09 +08:00
nanxun ea41d3e7b4 fix: retry segmented sessions and restore settings text 2026-05-08 23:27:01 +08:00
nanxun 93e39b2c1c fix: allow retention cleanup filter inheritance 2026-05-08 21:05:22 +08:00
nanxun cc37d90d92 feat: async session cleanup and fix live room scroll 2026-05-08 18:18:36 +08:00
nanxun 8f5e63cffd fix: include ffmpeg exit diagnostics 2026-05-06 19:40:59 +08:00
nanxun 8626af6d80 fix: guard polling hangs with per-room timeout 2026-05-06 09:50:08 +08:00
nanxun 56432a9ece feat: add transcode workspace and media browser 2026-05-05 02:14:52 +08:00
nanxun f0f9ed3456 feat: localize quality labels and add quality template token 2026-05-01 07:16:03 +08:00
nanxun 0b8f2c9775 feat: add import detection toggles 2026-04-30 20:24:23 +08:00
nanxun 9b99cd7e98 fix: retry transient postgres polling saves 2026-04-30 16:05:59 +08:00
nanxun 39404560cc fix: backfill postgres upload result defaults 2026-04-30 12:29:49 +08:00
nanxun 24b901dddd fix: widen postgres text columns for migration 2026-04-29 23:54:50 +08:00
nanxun b72a4fd6b5 fix: initialize sqlite provider for migration 2026-04-29 21:59:26 +08:00
nanxun 8132466c5d feat: migrate runtime to postgresql 2026-04-29 18:47:12 +08:00
nanxun 49d893f29b fix: 修复直播间状态检测的多项bug - 状态值扩展为1或2,API异常时用页面HTML兜底,页面解析增加转义清理,前端移除选中阻断自动刷新 2026-04-28 19:26:41 +08:00
nanxun e0f91716ab fix: 修复DueAt取MAX导致紧急轮询被忽略,停播后直播间状态不更新 2026-04-28 11:38:44 +08:00
nanxun fcfa94dee3 feat: 日志内容搜索、设置分类Tabs、日报推送、删除合并/条件清理/无分片清理 2026-04-28 10:56:16 +08:00