Files
360_player/README.md
T
JianMiauandClaude Opus 5 ae3221c460 移除 app 容器的對外埠,對外只保留 nginx 的 HTTPS 入口
摘要:
360-player 服務改為只 expose 不 ports,明文 HTTP 不再離開容器內網,
對外入口只剩 360-player-web 的 8443。

根本原因:
先前為了讓區網能用 IP 免憑證警告直連,保留了 app 的 ${PORT}:8360 對外映射。
但該埠在路由器上也有對外轉發,實際使用時是以網域連線
(http://jianmiau.tk:8360),等於在外網留了一條明文路徑。

影響:
外網走 8360 時,影片內容與 API 全程未加密,加上 TLS 的意義被抵消。

修法:
- docker-compose.yml 拿掉 360-player 的 ports,只留 expose: 8360;
  nginx 仍可經容器內網以服務名連到它
- 保留註解說明如何加回來,並註明要綁死區網介面而非 0.0.0.0
- .env / .env.example 移除不再被 compose 參照的 PORT
- README 的 SSL 段落改寫,說明代價是區網也要用網域連

驗證:
docker compose config 展開後只有一個 published port(8443 → 8443),
360-player 服務底下僅剩 expose。

備註:路由器上 8360 的 port forwarding 需另外手動關閉,僅改 compose 不足以關掉外網入口。

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

159 lines
7.9 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
```
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 和到期日。
兩點注意:
- **對外只有 8443 一個入口**:app 容器只 `expose``ports`,明文那條不會離開容器內網。
代價是憑證綁網域,區網也得用網域連(`https://jianmiau.tk:8443`)而不是 IP —— 用 IP 連
會跳憑證警告。真的想要區網免警告的快速通道,把 app 的 `ports` 加回來並綁死區網介面
`"192.168.0.15:8360:8360"`),不要開成 `0.0.0.0`,否則等於在外網開了一條明文路徑。
- 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
```