Files
360_player/README.md
T
JianMiauandClaude Opus 5 fa220b00eb 新增 nginx 容器終結 TLS,讓播放器可用 HTTPS 連線
摘要:
比照 badminton-scoreboard 的做法,加一個 nginx 容器負責 SSL,
反向代理到原本的 Node 應用;Node 端維持純 HTTP,不碰憑證。

根本原因:
播放器原本只提供 HTTP。專案內原先想直接 symlink www/certificate 進來,
但那份憑證是 2026-04-16 從 DSM 複製出來的靜態副本,已於 2026-07-01 過期,
且 DSM 續期後不會自動更新副本。共用目錄 /volume1/docker/certs 才是
其他專案在用、且持續被續期的來源(目前效期至 2026-09-04)。

影響:
- 外網連線全程明文
- 若沿用 www/certificate,瀏覽器會直接因憑證過期而擋下連線

修法:
- 新增 docker/nginx/(Dockerfile + entrypoint.sh):啟動時把 cert.pem 與
  chain.pem 正規化並串成 fullchain,依環境變數產生 nginx 設定,
  再用 inotifywait 監看憑證目錄,續期後自動 reload,不需重啟容器
- docker-compose.yml 拆成 360-player(app)與 360-player-web(nginx)兩個服務,
  憑證以唯讀掛載 ${SSL_CERT_DIR:-/volume1/docker/certs}
- 保留 app 的 HTTP 埠:憑證綁網域,區網用 IP 連 HTTPS 必定跳警告,
  維持 區網走 http / 外網走 https 兩條路
- .env.example 補上 SSL_* 與 HTTPS_PORT 設定項

針對本專案調整(與 badminton-scoreboard 不同之處):
- proxy_buffering off + proxy_max_temp_file_size 0:影片走 HTTP Range 串流,
  開著緩衝 nginx 會把整段回應先寫成暫存檔,數 GB 來源會塞爆容器磁碟
- proxy_read_timeout/send_timeout 24h:/api/events 是 SSE,轉檔動輒數小時,
  預設 60 秒會被切斷導致進度停止更新
- 移除 websocket 的 Upgrade 標頭:本專案用 SSE,沒有 websocket

驗證:
以 entrypoint 相同的 awk 邏輯在本機組出 fullchain,openssl 解析得到完整三層
憑證鏈(jianmiau.tk → Let's Encrypt YR2 → ISRG Root YR),且 cert 與 privkey
的 modulus 相符。docker compose config 展開後的路徑與埠號皆正確。
註:無 docker daemon 權限,未實際 build 與啟動容器。

順帶修正 README:videoDir 範例改為 NAS 路徑、VAAPI 一節更新為已實機驗證。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-21 11:07:29 +08:00

157 lines
7.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 360 Player
在瀏覽器裡播放 360° 全景影片的本機網頁播放器。
從下拉選單挑選影片資料夾(`config.json``videoDir`NAS 上是 `/volume1/photo/Badminton`)裡的影片、選擇播放畫質;低畫質版本由 ffmpeg(NVIDIA NVENC)轉出並快取在本機。
## 啟動
```bash
npm install # 第一次
npm start # 或 node server.js
```
啟動後終端機會列出網址,例如 `http://localhost:8360`;同一區網的其他裝置(手機、平板)可用列出的 `http://192.168.x.x:8360` 連線。
需求:Node.js 18+、`ffmpeg` / `ffprobe` 在 PATH 裡(轉檔用)。有 NVIDIA 顯卡時自動使用 NVDEC + NVENC,沒有就退回 CPUlibx264)。
## 操作
| 動作 | 滑鼠 / 觸控 | 鍵盤 |
|---|---|---|
| 環視 | 拖曳 | ← → ↑ ↓ |
| 縮放(視角) | 滾輪 / 雙指 | `+` `-` |
| 播放 / 暫停 | 點一下畫面 | `空白鍵` / `K` |
| 快退 / 快進 10 秒 | — | `J` / `L` |
| 逐格 | — | `,` / `.` |
| 全螢幕 | 雙擊畫面 | `F` |
| 靜音 | — | `M` |
| 重置視角 | ⌖ 按鈕 | `0` |
- 上方「360° / 平面」按鈕可手動切換投影方式(影片沒有 360 metadata 但是 2:1 比例時會自動判斷為 360)。
- 每部影片的播放位置、偏好畫質、音量都會記住。
## 畫質與轉檔
「畫質」下拉選單列出原始檔與 `config.json` 裡定義的每一級畫質:
- 已轉檔的畫質會顯示 ✓ 與檔案大小,選取後立即切換(保留目前播放進度)。
- 尚未轉檔的畫質選取後會**加入轉檔佇列**,完成時畫面會跳出通知與「切換」按鈕。
- 「轉檔」面板可以看進度(fps、速度、剩餘時間)、取消、刪除已轉檔檔案,
以及**「一次轉出全部畫質」**:來源只讀一次就同時輸出全部畫質。
> 來源放在網路磁碟(例如 RaiDrive)時,讀取速度通常才是瓶頸,
> 而不是 GPU。這種情況下請用「一次轉出全部畫質」,時間和只轉一種幾乎相同。
轉檔輸出:H.264 + AAC、`faststart`(moov 在檔頭,可立即開播、順暢拖動),
並重新寫入 Spherical Video V1 metadata,所以用 VLC / YouTube 開也會被認成 360 影片。
轉出的檔案放在 `cache/`,檔名 `<原檔名>__<畫質id>.mp4`;來源檔案變動(大小或修改時間改變)時會自動視為過期並重轉。
## 部署到 NASDocker
兩個容器:`360-player` 是應用本體(`node:22-alpine`,內含 ffmpeg),
`360-player-web` 是 nginx,負責終結 TLS 再轉給前者。資料夾都用 volume 掛進容器:
| 容器 | 容器內路徑 | 用途 | 對應環境變數 |
|---|---|---|---|
| `360-player` | `/videos` | 影片資料夾(唯讀) | `VIDEO_DIR` |
| `360-player` | `/cache` | 轉檔輸出與 probe 快取(需可寫) | `CACHE_DIR` |
| `360-player-web` | `/etc/nginx/certs` | SSL 憑證(唯讀) | `SSL_CERT_DIR` |
```bash
# 1. 把整個專案資料夾複製到 NAS,例如 /volume1/docker/360_player
# 2. 建立 .env,填 NAS 上的實際路徑
cp .env.example .env && vi .env
# 3. 建置並啟動
docker compose up -d --build
# 4. 瀏覽器開 https://你的網域:8443(或區網 http://NAS-IP:8360
```
Synology Container Manager:「專案」→「新增」→ 選這個資料夾,它會讀取 `docker-compose.yml`
環境變數可在「環境」分頁填(等同 `.env`)。
### SSL
TLS 由 `360-player-web`(nginx)終結,Node 那端維持純 HTTP,所以應用程式碼裡沒有任何憑證邏輯。
憑證放在 `SSL_CERT_DIR`(預設 `/volume1/docker/certs`,和其他專案共用同一份),
唯讀掛進 nginx 容器:
```
cert.pem 伺服器憑證
chain.pem 中介憑證
privkey.pem 私鑰
```
檔名可用 `SSL_CERT_FILE_NAME` / `SSL_CHAIN_FILE_NAME` / `SSL_KEY_FILE_NAME` 改。
容器啟動時 entrypoint 會把 `cert.pem` + `chain.pem` 接成 fullchain(順便去掉 CRLF),
並用 `inotifywait` 盯著這個資料夾 —— **DSM 續期後直接覆蓋檔案就好,nginx 會自己 reload,不用重開容器**
啟動 log 會印出憑證的 subject 和到期日。
兩點注意:
- 憑證是綁網域的,**用區網 IP 連 HTTPS 一定會跳警告**。所以 compose 同時保留了
HTTP 的 `${PORT}:8360`:區網走 http、外網走 https。想只留 HTTPS 就把那段 `ports` 刪掉。
- nginx 這邊關掉了 `proxy_buffering` 並把 `proxy_max_temp_file_size` 設為 0。
影片是靠 HTTP Range 串流的,開著緩衝會讓 nginx 先把整段回應寫成暫存檔再吐出去,
幾 GB 的來源會直接塞爆容器磁碟。`/api/events`SSE)另外把 `proxy_read_timeout`
拉到 24 小時,否則轉檔跑到一半進度就不再更新。
**NAS 上轉檔速度**
- 沒有 GPU 時走 `libx264`CPU)。NAS 的 CPU 把 4K 360 影片同時轉成三種畫質大約只有個位數 fps,
82 分鐘的影片可能要跑 5–10 小時(可在背景跑,轉檔面板會顯示剩餘時間)。
CPU 很弱可把 `.env``X264_PRESET` 改成 `superfast``ultrafast`
- NAS 有 Intel 內顯時,`docker-compose.yml` 裡的 `devices: /dev/dri` 要打開,
啟動時會自動偵測並改用 VAAPI 硬體編碼,偵測失敗則自動退回 CPU。
已在 Celeron J4025UHD Graphics 600+ DSM 實機驗證:容器啟動 log 顯示
`轉檔引擎:VAAPI 硬體編碼``/api/config` 回傳 `modes: ["vaapi","cpu"]`
注意 DSM 內建的 ffmpeg 拿掉了 VAAPI 編碼器,**直接在 NAS 上 `npm start` 只會走 CPU**
硬體轉檔只有在容器裡才有。
- **最快的做法**:在有 NVIDIA 顯卡的電腦上先跑 `npm start` 轉完,再把 `cache/` 裡的
`*.mp4``*.mp4.json` 複製到 NAS 的 `CACHE_DIR`。快取檔只認來源檔的大小與修改時間(容許 2 秒誤差),
所以兩邊看到同一個檔案就能直接共用。
## 設定(`config.json`
```json
{
"port": 8360,
"host": "0.0.0.0",
"videoDir": "/volume1/photo/Badminton",
"cacheDir": "./cache",
"extensions": [".mp4", ".mov", ".m4v", ".webm"],
"qualities": [
{ "id": "2560", "label": "高", "width": 2560, "bitrate": "10M", "maxrate": "13M" },
{ "id": "1920", "label": "中", "width": 1920, "bitrate": "5M", "maxrate": "7M" },
{ "id": "1280", "label": "低", "width": 1280, "bitrate": "2.5M", "maxrate": "3.5M" }
]
}
```
- `qualities`:每一級只要給寬度,高度依原始比例計算;比原始影片寬的級別會自動略過。
可加 `"nvencPreset": "p6"` 之類的欄位調整 NVENC 預設(預設 `p4`)。
- 環境變數:`PORT=9000` 覆寫埠號;`CONFIG=other.json` 使用另一份設定;`LOG_STREAM=0` 關閉串流請求 log。
## API(給進階使用)
| 方法 | 路徑 | 說明 |
|---|---|---|
| GET | `/api/videos` | 影片清單(含 ffprobe 資訊、各畫質狀態) |
| GET | `/stream/:name?q=original\|<畫質id>` | 影片串流(支援 Range) |
| POST | `/api/transcode` | `{ "name", "quality" }` / `{ "name", "qualities": [] }` / `{ "name", "all": true }` |
| GET | `/api/jobs` | 轉檔工作 |
| DELETE | `/api/jobs/:id` | 取消工作 |
| DELETE | `/api/jobs/finished` | 清除已完成 |
| DELETE | `/api/variant?name=&quality=` | 刪除轉檔檔案 |
| GET | `/api/events` | Server-Sent Events`jobs`(進度)、`finished` |
## 專案結構
```
server.js Express:清單、Range 串流、轉檔 API、SSE
lib/probe.js ffprobe 包裝 + 永續快取(含 360 metadata 判斷)
lib/transcode.js ffmpeg 工作佇列(一次解碼多輸出;CUDA → NVENC → CPU 備援)
lib/spherical.js 把 Spherical Video V1 metadata 注回 MP4
public/ 前端(Three.js 球體貼圖播放器)
docker/nginx/ nginx 映像檔:終結 TLS,反向代理到 app(憑證變更自動 reload)
cache/ 轉檔輸出與 probe 快取(已 gitignore
```