2026-08-21 10:57:17 +08:00
|
|
|
|
# 360 Player
|
|
|
|
|
|
|
|
|
|
|
|
在瀏覽器裡播放 360° 全景影片的本機網頁播放器。
|
2026-08-21 11:07:29 +08:00
|
|
|
|
從下拉選單挑選影片資料夾(`config.json` 的 `videoDir`,NAS 上是 `/volume1/photo/Badminton`)裡的影片、選擇播放畫質;低畫質版本由 ffmpeg(NVIDIA NVENC)轉出並快取在本機。
|
2026-08-21 10:57:17 +08:00
|
|
|
|
|
|
|
|
|
|
## 啟動
|
|
|
|
|
|
|
|
|
|
|
|
```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,沒有就退回 CPU(libx264)。
|
|
|
|
|
|
|
|
|
|
|
|
## 操作
|
|
|
|
|
|
|
|
|
|
|
|
| 動作 | 滑鼠 / 觸控 | 鍵盤 |
|
|
|
|
|
|
|---|---|---|
|
|
|
|
|
|
| 環視 | 拖曳 | ← → ↑ ↓ |
|
|
|
|
|
|
| 縮放(視角) | 滾輪 / 雙指 | `+` `-` |
|
|
|
|
|
|
| 播放 / 暫停 | 點一下畫面 | `空白鍵` / `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`;來源檔案變動(大小或修改時間改變)時會自動視為過期並重轉。
|
|
|
|
|
|
|
|
|
|
|
|
## 部署到 NAS(Docker)
|
|
|
|
|
|
|
2026-08-21 11:07:29 +08:00
|
|
|
|
兩個容器:`360-player` 是應用本體(`node:22-alpine`,內含 ffmpeg),
|
|
|
|
|
|
`360-player-web` 是 nginx,負責終結 TLS 再轉給前者。資料夾都用 volume 掛進容器:
|
2026-08-21 10:57:17 +08:00
|
|
|
|
|
2026-08-21 11:07:29 +08:00
|
|
|
|
| 容器 | 容器內路徑 | 用途 | 對應環境變數 |
|
|
|
|
|
|
|---|---|---|---|
|
|
|
|
|
|
| `360-player` | `/videos` | 影片資料夾(唯讀) | `VIDEO_DIR` |
|
|
|
|
|
|
| `360-player` | `/cache` | 轉檔輸出與 probe 快取(需可寫) | `CACHE_DIR` |
|
|
|
|
|
|
| `360-player-web` | `/etc/nginx/certs` | SSL 憑證(唯讀) | `SSL_CERT_DIR` |
|
2026-08-21 10:57:17 +08:00
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
# 1. 把整個專案資料夾複製到 NAS,例如 /volume1/docker/360_player
|
|
|
|
|
|
# 2. 建立 .env,填 NAS 上的實際路徑
|
|
|
|
|
|
cp .env.example .env && vi .env
|
|
|
|
|
|
# 3. 建置並啟動
|
|
|
|
|
|
docker compose up -d --build
|
2026-08-21 15:01:56 +08:00
|
|
|
|
# 4. 瀏覽器開 https://你的網域:8443
|
2026-08-21 10:57:17 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
Synology Container Manager:「專案」→「新增」→ 選這個資料夾,它會讀取 `docker-compose.yml`,
|
|
|
|
|
|
環境變數可在「環境」分頁填(等同 `.env`)。
|
|
|
|
|
|
|
2026-08-21 11:07:29 +08:00
|
|
|
|
### 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 和到期日。
|
|
|
|
|
|
|
|
|
|
|
|
兩點注意:
|
|
|
|
|
|
|
2026-08-21 15:01:56 +08:00
|
|
|
|
- **對外只有 8443 一個入口**:app 容器只 `expose` 不 `ports`,明文那條不會離開容器內網。
|
|
|
|
|
|
代價是憑證綁網域,區網也得用網域連(`https://jianmiau.tk:8443`)而不是 IP —— 用 IP 連
|
|
|
|
|
|
會跳憑證警告。真的想要區網免警告的快速通道,把 app 的 `ports` 加回來並綁死區網介面
|
|
|
|
|
|
(`"192.168.0.15:8360:8360"`),不要開成 `0.0.0.0`,否則等於在外網開了一條明文路徑。
|
2026-08-21 11:07:29 +08:00
|
|
|
|
- nginx 這邊關掉了 `proxy_buffering` 並把 `proxy_max_temp_file_size` 設為 0。
|
|
|
|
|
|
影片是靠 HTTP Range 串流的,開著緩衝會讓 nginx 先把整段回應寫成暫存檔再吐出去,
|
|
|
|
|
|
幾 GB 的來源會直接塞爆容器磁碟。`/api/events`(SSE)另外把 `proxy_read_timeout`
|
|
|
|
|
|
拉到 24 小時,否則轉檔跑到一半進度就不再更新。
|
|
|
|
|
|
|
2026-08-21 10:57:17 +08:00
|
|
|
|
**NAS 上轉檔速度**
|
|
|
|
|
|
- 沒有 GPU 時走 `libx264`(CPU)。NAS 的 CPU 把 4K 360 影片同時轉成三種畫質大約只有個位數 fps,
|
|
|
|
|
|
82 分鐘的影片可能要跑 5–10 小時(可在背景跑,轉檔面板會顯示剩餘時間)。
|
|
|
|
|
|
CPU 很弱可把 `.env` 的 `X264_PRESET` 改成 `superfast` 或 `ultrafast`。
|
2026-08-21 11:07:29 +08:00
|
|
|
|
- NAS 有 Intel 內顯時,`docker-compose.yml` 裡的 `devices: /dev/dri` 要打開,
|
|
|
|
|
|
啟動時會自動偵測並改用 VAAPI 硬體編碼,偵測失敗則自動退回 CPU。
|
|
|
|
|
|
已在 Celeron J4025(UHD Graphics 600)+ DSM 實機驗證:容器啟動 log 顯示
|
|
|
|
|
|
`轉檔引擎:VAAPI 硬體編碼`,`/api/config` 回傳 `modes: ["vaapi","cpu"]`。
|
|
|
|
|
|
注意 DSM 內建的 ffmpeg 拿掉了 VAAPI 編碼器,**直接在 NAS 上 `npm start` 只會走 CPU**,
|
|
|
|
|
|
硬體轉檔只有在容器裡才有。
|
2026-08-21 10:57:17 +08:00
|
|
|
|
- **最快的做法**:在有 NVIDIA 顯卡的電腦上先跑 `npm start` 轉完,再把 `cache/` 裡的
|
|
|
|
|
|
`*.mp4` 與 `*.mp4.json` 複製到 NAS 的 `CACHE_DIR`。快取檔只認來源檔的大小與修改時間(容許 2 秒誤差),
|
|
|
|
|
|
所以兩邊看到同一個檔案就能直接共用。
|
|
|
|
|
|
|
|
|
|
|
|
## 設定(`config.json`)
|
|
|
|
|
|
|
|
|
|
|
|
```json
|
|
|
|
|
|
{
|
|
|
|
|
|
"port": 8360,
|
|
|
|
|
|
"host": "0.0.0.0",
|
2026-08-21 11:07:29 +08:00
|
|
|
|
"videoDir": "/volume1/photo/Badminton",
|
2026-08-21 10:57:17 +08:00
|
|
|
|
"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 球體貼圖播放器)
|
2026-08-21 11:07:29 +08:00
|
|
|
|
docker/nginx/ nginx 映像檔:終結 TLS,反向代理到 app(憑證變更自動 reload)
|
2026-08-21 10:57:17 +08:00
|
|
|
|
cache/ 轉檔輸出與 probe 快取(已 gitignore)
|
|
|
|
|
|
```
|