跳到主要內容
Flow

套件劃分

Swift Packages 的目錄、命名、分層、依賴方向、邊界規則與 C 函式庫橋接。總覽見 architecture.md。

目錄

Flow/
├── Apps/
│   └── FlowMac/                 # App target;只經 FlowComposition 的 FlowPlayer 與 PlayerAPI 操作播放器
├── Benchmarks/                  # package-benchmark:micro、播放情境、size、compete、baseline(docs/benchmark.md)
├── Packages/
│   ├── Core/                    # 不含任何實作的播放器核心;各 target 位於 Sources/<層>/<target>/,測試位於 Tests/<層>/
│   │   ├── Contract/            # 契約:模組、UI 與自動化編譯時依賴的型別與協定
│   │   │   ├── FlowMedia/       # 媒體詞彙值型別,前端也用:MediaTime、HostTime、FrameRate、TrackInfo(CodecID、VideoFormat、ChromaFormat)、ColorInfo、HDRMetadata、FrameGeometry、SeekMode
│   │   │   ├── FlowDiagnostics/ # Logger、Signpost、計數器
│   │   │   ├── FlowPlayerAPI/   # PlayerAPI、Command/Property/Event、統計值、按鍵綁定與內建名稱;UI 與自動化唯一能碰的介面
│   │   │   └── FlowModuleAPI/   # FlowModule、擴充點協定、資料平面型別(Packet、VideoFrame、幀排列 VideoPixelFormat/ChromaSubsampling、PCMFormat)、DisplayPlan、SPSC ring
│   │   ├── Kernel/              # 只有 Composition、IntegrationTests、Benchmarks 連結(UI、MCP 測試另可連結 FlowController)
│   │   │   ├── FlowRegistry/    # ComponentRegistry:依擁有者記錄與撤銷註冊
│   │   │   ├── FlowGraph/       # Display Graph 編譯、pass 切分
│   │   │   ├── FlowController/  # PlayerController(PlayerAPI 實作)、PropertyStore、預設按鍵綁定(InputConf)
│   │   │   ├── FlowEngine/      # PlayerSession 狀態機、執行緒、FrameQueue/PCM ring、時鐘與 A/V 同步、Seek、元件選擇與 fallback
│   │   │   └── FlowModuleHost/  # 模組啟用/停用
│   │   └── Development/         # 開發用:App 不連結;Core 自身的測試也用,故留在 Core
│   │       ├── FlowSampling/    # 行程/執行緒/記憶體取樣器;測試與 benchmark 用
│   │       └── FlowTestSupport/ # mock 模組、TestModuleContext、fixture 定位、MemorySource;Core、模組、Composition 與整合測試共用
│   ├── Composition/             # FlowComposition:composition root,唯一組裝 Modules/* 之處;App 與 Benchmarks 共用
│   ├── IntegrationTests/        # 真實模組 + mock 輸出的端到端播放測試
│   ├── MCP/                     # FlowMCP,前端:MCP server(Streamable HTTP,僅 loopback)
│   └── UI/                      # FlowUI,前端:SwiftUI 元件(控制列、header、選單)
├── Modules/                     # 實作;模組本身靜態連結(LGPL 函式庫除外)
│   ├── File/                    # FileSource:本機檔案 MediaSource
│   ├── FFmpeg/                  # FFmpegDemuxer、FFmpegAudioDecoder
│   ├── VideoToolbox/            # VTVideoDecoder
│   ├── AVAudio/                 # AVAudioEngine AudioOutput
│   └── Metal4/                  # Metal4DisplayRuntime、MetalLayerPresenter、OffscreenPresenter
│       ├── Plugins/             # MetalLibraryPlugin:建置期把 Shaders/*.metal 編譯為 FlowMetal4.metallib
│       └── Shaders/
│           ├── Include/Flow/    # Shader ABI(見 modules.md#metal-4)
│           └── Display.metal    # 內建 display pass
├── Vendor/                      # 由 scripts/ 產生,不入庫
│   ├── lib{avformat,avcodec,avutil,swresample}.xcframework  # FFmpeg;LGPL,動態;scripts/build-ffmpeg.sh
│   └── Licenses/                # 各函式庫授權全文,隨 App 附於 Resources/Licenses/
├── Tests/(各 package 內)     # 單元、golden、GPU 測試
└── docs/

命名

資料夾與 package 名稱為角色(Core、Composition、UI、FFmpeg),product 與 Swift module 加 Flow 前綴(FlowUI、FlowFFmpeg),以免與 Apple framework(AVAudio、VideoToolbox)或其他 module 撞名。Core 與 IntegrationTests(只有測試 target,無 product)以外皆為單一 product Flow<資料夾>。

分層

Kernel 為核心中契約以外的 target:掌管邏輯、生命週期、執行緒、佇列、時鐘與元件選擇,不含任何模組實作。Core 的層即 Sources/ 下的目錄(Contract、Kernel、Development),check-deps.sh 據此判定各 target 所屬的層。

層位置可依賴
契約FlowMedia、FlowDiagnostics、FlowPlayerAPI、FlowModuleAPI契約
KernelFlowRegistry、FlowGraph、FlowController、FlowEngine、FlowModuleHost契約、Kernel
模組Modules/*契約、runtime 模組
組裝Composition契約、Kernel、模組;測試另可用開發用
前端UI、MCPFlowMedia、FlowDiagnostics、FlowPlayerAPI
AppApps/FlowMac前端、FlowComposition、FlowMedia、FlowDiagnostics、FlowPlayerAPI
開發用FlowSampling、FlowTestSupport契約;App 不連結
工具IntegrationTests、Benchmarks不限;App 不連結

check-deps.sh 不區分模組間的依賴:模組可依賴任何模組,是否為 runtime 模組由 review 把關。

依賴方向

只能向下:

Apps → {FlowUI, FlowMCP, FlowComposition}
FlowComposition → {FlowModuleHost, FlowEngine, Modules/*}
{FlowUI, FlowMCP} → FlowPlayerAPI
FlowModuleHost → FlowRegistry
FlowEngine → {FlowGraph, FlowRegistry, FlowController}
{FlowGraph, FlowRegistry} → FlowModuleAPI → FlowPlayerAPI → FlowMedia
{FlowController, FlowModuleHost} → FlowDiagnostics
FlowController → FlowPlayerAPI
Modules/* → {FlowModuleAPI, FlowDiagnostics}

邊界規則

  • Core/ 只定義契約並掌管邏輯、生命週期與控制平面;不得匯入 SwiftUI、AppKit、Network、Metal、VideoToolbox、AVFAudio 與任何 vendored C 函式庫(CoreVideo 僅作為 CVPixelBuffer/IOSurface 的不透明載體,契約中只出現在 FlowModuleAPI;前端依賴的 FlowMedia、FlowDiagnostics、FlowPlayerAPI 不匯入 CoreVideo、CoreMedia、IOSurface)。SwiftLint custom rule(core_banned_import、core_banned_link、frontend_contract_banned_import)檢查 Packages/Core 的 import 與連結。
  • Modules/ 只能依賴契約、自身的 vendored 函式庫,以及所需的 runtime 模組;不得依賴 Kernel。模組測試另可用 FlowTestSupport(TestModuleContext 記錄註冊)。
  • FlowUI 與 FlowMCP 只依賴 FlowMedia、FlowDiagnostics、FlowPlayerAPI;App 另依賴前端與 FlowComposition,只經 FlowPlayer 與 PlayerAPI 操作播放器;兩者都不依賴 FlowModuleAPI、FlowEngine。例外是 FlowUI、FlowMCP 的測試 target 可依賴 FlowController,以真實 PlayerController 搭配 stub handler;FlowPlayer 只以 @_spi(Benchmark) 開放指定模組的 init、額外 presenter 與 PresentationSurface 給 Benchmarks 與自身的測試,不暴露 registry、display runtime 與 PlayerSession;統計經 PlayerAPI.statistics,與前端同一條路。
  • 依賴由 Package.swift 宣告強制,scripts/check-deps.sh(prek)依上表檢查各 manifest(含 Core 各層 target)、project.yml 各 target 連結的 product 與 App 的 import(App 不得以 @_spi、@testable import),並檢查命名;每次先以自我測試確認各規則擋得下違規;package 的 import 由 explicit module build 限制在依賴閉包內;所有 package 與 App target 啟用 MemberImportVisibility(只能使用本檔 import 的模組成員)、ExistentialAny、InternalImportsByDefault(import 預設 internal,公開 API 用到的型別才 public import,vendored C 型別因此無法出現在公開 API),警告視為錯誤;scripts/check-settings.sh 確認各 Package.swift 與 project.yml 設定一致。
  • 模組只公開 FlowModule 型別;其餘實作為 internal,測試以 @testable import 存取。Core 與模組中只有 Benchmarks 需要的型別(如 FrameQueue、PlaybackClock)以 @_spi(Benchmark) 開放,不設為 public。
  • 是否「必要、不可停用」由 App 組裝時標記(見模組模型),不由資料夾決定;核心不內建任何實作,沒有模組時無法播放。
  • 官方功能用 FlowModuleAPI 寫不出來即視為介面缺陷。

C 函式庫

vendored C 函式庫(Modules/*)的橋接規則:

  • C 型別與指標只留在各自的 Modules/<Lib>,跨界只用 FlowModuleAPI 型別。
  • 同一模組的元件可辨識自己的 Packet.owner 型別、直接共用 C 物件(如 FlowFFmpeg 的音訊解碼器借出 demuxer 的 AVBufferRef),但只能省配置或拷貝:輸出只依契約欄位決定,與其他 owner 的 packet 相同。
  • AVPacket、AVFrame 等結構在 Factory.make 或 init 配置一次,處理時以 av_packet_unref/av_frame_unref 重用,於 deinit 釋放。FFmpeg 每次呼叫仍會配置資料 buffer,故只在吞吐級執行緒處理(見執行緒分級)。
  • withUnsafe* 取得的指標不得逃出 closure;長期使用的記憶體自行 allocate,並明確指定擁有者。
  • C callback 用 @convention(c),以 Unmanaged.passUnretained(self).toOpaque() 傳 context;無法保證擁有者比 C 端活得久時用 passRetained,拆除時 release()。
  • C callback 跑在函式庫自己的執行緒:依該執行緒分級,不得阻塞,不碰 actor 狀態。