app · bc9fa71

Dev index Recent changes / versions Changelog About TODO

限制錄影 sidecar 落盤時機並釋放失敗 writer

bc9fa71 · 2026-11-12 09:45 · app
Docs/ChangeHistory.mdliveAPP/Capture/SegmentedRecordingWriter.swift
commit bc9fa71b537e4637edd700e2a301ae85c4b94550Author: Nuclear0709 <agp05432@gmail.com>Date:   Thu Nov 12 09:45:27 2026 +0800     限制錄影 sidecar 落盤時機並釋放失敗 writer diff --git a/Docs/ChangeHistory.md b/Docs/ChangeHistory.mdindex db1dffa..8ca4710 100644--- a/Docs/ChangeHistory.md+++ b/Docs/ChangeHistory.md@@ -2,6 +2,7 @@  ## 2026.11.10 音訊主時鐘與 RTMP 診斷可讀性 +- 錄影 manifest(sidecar)只在開始、完成或中斷等 phase 轉換時持久化 `sourceStartPTS`/`sourceEndPTS`;進度回報不把尾端累積在記憶體等待落盤,避免長錄影產生額外的狀態寫入。失敗收尾會主動釋放 `AVAssetWriter` 與檔案 URL,不讓失敗片段持有 handle。 - 錄影 manifest(sidecar)新增 `sourceStartPTS`/`sourceEndPTS`/`fileStartPTS`,由 `SegmentedRecordingWriter` 在第一個 video sample 與完成時寫入;`filePTS = sourcePTS - sourceStartPTS + fileStartPTS` 可對照 App 擷取時鐘與檔案時間軸。此階段只建立對照,不改檔案時間戳。 - A/V Lag 新增 `sourcePTSDeltaDrift`/`sourcePTSDeltaPerMinute`/`avClockDrift`/`avSyncState`,以第一筆有效來源差為基準區分 stable/drifting/jumped;報告頁與 JSON 都能看到漂移趨勢,不直接用 `publish throughput avOffset` 判斷口型同步。 - 編碼輸入緩衝 UI 改稱「佇列保留」,新增 `received`/`consumed` 累計交接數;文件明確說明直接 handoff 時 `bytes=0`、`peakBytes=0` 是正常,不代表影格沒有進入編碼,也不代表沒有配置容量。diff --git a/liveAPP/Capture/SegmentedRecordingWriter.swift b/liveAPP/Capture/SegmentedRecordingWriter.swiftindex 953221e..dd2255e 100644--- a/liveAPP/Capture/SegmentedRecordingWriter.swift+++ b/liveAPP/Capture/SegmentedRecordingWriter.swift@@ -345,8 +345,14 @@ actor RecordingSegmentWriter {         var sourceEnd: Double?         if lastVideo.isValid { sourceEnd = lastVideo.seconds }         if audioEnd.isValid { sourceEnd = max(sourceEnd ?? audioEnd.seconds, audioEnd.seconds) }-        await library.recordSourceRange(id, sourceStartPTS: sourceStart, sourceEndPTS: sourceEnd,-                                        persist: phase != nil)+        // 進度回報不把 sourceEnd 留在記憶體等落盤;只有 phase 轉換時才一次寫入+        // 起點與目前尾端,避免長時間錄影的反覆狀態更新。+        let persistSourceRange = phase != nil+        await library.recordSourceRange(+            id,+            sourceStartPTS: sourceStart,+            sourceEndPTS: persistSourceRange ? sourceEnd : nil,+            persist: persistSourceRange)         await library.update(id, phase: phase, duration: duration, bytes: bytes,             message: "可控錄製 · 第 \(number) 段 · \(bitrate) · 寫入端丟棄 \(dropped) 個樣本" + sizeWarning)     }@@ -356,6 +362,13 @@ actor RecordingSegmentWriter {     func failed(_ failure: Error) async {         writer?.cancelWriting()         await report(phase: .interrupted)+        // 失敗後仍可能被 router 持有到整場結束;主動釋放 writer 與檔案 URL,+        // 不要讓 AVAssetWriter 的檔案 handle 跟著失敗片段一起留在記憶體。+        writer = nil+        video = nil+        audio = nil+        id = nil+        url = nil         sendlog(message: "[SegmentedRecording] group=\(group) failed=\(failure)")     } }