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

360 Player

在瀏覽器裡播放 360° 全景影片的本機網頁播放器。 從下拉選單挑選影片資料夾(config.jsonvideoDirNAS 上是 /volume1/photo/Badminton)裡的影片、選擇播放畫質;低畫質版本由 ffmpeg(NVIDIA NVENC)轉出並快取在本機。

啟動

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
# 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 容器只 exposeports,明文那條不會離開容器內網。 代價是憑證綁網域,區網也得用網域連(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/eventsSSE)另外把 proxy_read_timeout 拉到 24 小時,否則轉檔跑到一半進度就不再更新。

NAS 上轉檔速度

  • 沒有 GPU 時走 libx264CPU)。NAS 的 CPU 把 4K 360 影片同時轉成三種畫質大約只有個位數 fps, 82 分鐘的影片可能要跑 5–10 小時(可在背景跑,轉檔面板會顯示剩餘時間)。 CPU 很弱可把 .envX264_PRESET 改成 superfastultrafast
  • 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

{
  "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 Eventsjobs(進度)、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
S
Description
No description provided
Readme
102 KiB
Languages
JavaScript 83.1%
CSS 11.7%
HTML 4%
Dockerfile 1.2%