# Properties

URL: https://flow.lanstar.dev/docs/properties

> 播放器 Property 的設計規則與行為。型別、預設值與範圍以程式碼為準：`FlowPlayerAPI` 的 `PlayerVocabulary.swift`。Command／Property 系統見 [`player-api.md`](/docs/architecture/player-api#commandproperty-系統)。

## 設計

- **名稱**：`類別.名稱`（借鏡 mpv），Swift 以 `PropertyKey` 的靜態常數存取（如 `.audioVolume`），或以名稱與 `PropertyValue` 存取。
- **目錄**：只有登記的 Property 存在；型別、預設值、唯讀與正規化都由登記的 `PropertyDescriptor` 決定，自建同名的 `PropertyKey` 改變不了（見[目錄](/docs/architecture/player-api#commandproperty-系統)）。
- **可寫與唯讀**：可寫 Property 表達使用者意圖，引擎觀察後套用；唯讀 Property 只由引擎回報狀態。
- **寫入**：先正規化（如超出範圍取最近的值）再存入；未登記、唯讀或無效值拋 `PropertyError`。依名稱寫入時，數字可寫入整數 Property（須為整數）與時間 Property（秒）。未開啟媒體時照常存入，開檔時套用。
- **加減與循環**：`add` 對數值 Property 加減，`cycle` 切換 Bool 或依 `PropertyDescriptor.steps` 移到下一階（見 [Command](/docs/architecture/player-api#commandproperty-系統)）；`playback.rate` 的步階即控制列的速度選單。
- **觀察**：值改變時才通知；SwiftUI 讀 `value(of:)` 會自動更新。
- **保存**：不保存。開啟新檔案時保留，結束 Flow 後回到預設值。

```swift
try player.set(.audioVolume, to: 50)
let token = player.observe(.playbackPosition) { position in … }
```

由控制列、快捷鍵調整見 [`settings.md`](/docs/settings#播放屬性)；MCP 可讀取全部 Property、設定可寫的 Property，見 [`mcp.md`](/docs/mcp#tools)。

## 清單

### 播放

| Property | 寫入 | 行為 |
| --- | --- | --- |
| `playback.state` | 唯讀 | `idle`（未開啟媒體）、`loading`、`ready`（已載入、尚未播放）、`playing`、`paused`、`seeking`、`ended`（播完，保留最後一幀）或 `error`；轉移見[狀態機](/docs/architecture/engine#狀態機與-seek) |
| `playback.path` | 唯讀 | 開啟中的檔案路徑（非檔案時為 URL，同 mpv `path`）；開始載入時設定，`stop` 與失敗後為 `nil`。不為 `nil` 即已開啟媒體 |
| `playback.error` | 唯讀 | 失敗原因（[`PlayerError`](/docs/architecture/player-api#錯誤)），只在 `playback.state` 為 `error` 時不為 `nil` |
| `playback.position` | 唯讀 | 播放中每 0.25 秒更新；暫停、seek、播完時立即更新；開檔、`stop`、開檔失敗後歸零 |
| `playback.duration` | 唯讀 | 未開啟媒體或長度未知時為 `nil` |
| `playback.paused` | 可寫 | 使用者的暫停意圖，而非播放狀態。`load` 開始載入時寫入其 `paused` 參數（預設 `false`，新檔案直接播放）。播完時變為 `true` 並保留最後一幀（mpv `keep-open`），再設為 `false` 從頭播放。`play`、`pause` Command 即寫入此值 |
| `playback.rate` | 可寫 | 變速時音調不變 |

### 音訊

| Property | 寫入 | 行為 |
| --- | --- | --- |
| `audio.volume` | 可寫 | 音量（%） |
| `audio.mute` | 可寫 | 不改變 `audio.volume` |

### 影像

| Property | 寫入 | 行為 |
| --- | --- | --- |
| `video.rotate` | 可寫 | 疊加在影片本身旋轉之上的順時針角度；變更時重新編譯 Display Graph 並更新 `video.display-size`，App 的視窗跟著調整比例 |
| `video.display-size` | 唯讀 | 套用像素長寬比與旋轉（含 `video.rotate`）後的影片尺寸（像素），由影片軌的幾何算出，與視窗無關；開檔時（`fileLoaded` 之前）設定，未開啟媒體或沒有影像時為 `nil`。App 依它調整視窗大小並鎖定比例 |

### 視窗

| Property | 寫入 | 行為 |
| --- | --- | --- |
| `window.fullscreen` | 可寫 | App 套用到視窗，並回寫實際狀態（含系統綠色按鈕） |
