Halcyon:把 Jellyfin 變成 90 年代錄影帶店
Halcyon 在瀏覽器中把 Jellyfin 媒體庫重建為可自由走動的 1990 年代出租店。本文提供 Docker 指令與反向代理設定,也說明 GPL-3.0、單人維護及 Remote Play 的 2 個執行個體限制。
Halcyon 如何處理你的 Jellyfin 媒體庫
Halcyon Video 會在瀏覽器中,將你的 Jellyfin 媒體庫重新呈現為一間可自由走動的 1990 年代錄影帶出租店。你擁有的每部影片都會成為架上的一個影片盒。你可以在日光燈下走過各個走道,取下一個盒子,翻到背面查看規格,再把它帶到櫃台開始播放。播放開始、進度與停止狀態都會回報給 Jellyfin,因此繼續播放位置與觀看記錄能維持正確。
Halcyon 會透過 Jellyfin API 讀取現有的 Jellyfin 伺服器,不會建立自己的媒體庫。本指南假設 Jellyfin 已在執行,且媒體掃描正常。如果尚未完成,請先設定 將 Jellyfin 設為 VPS 上的媒體伺服器,等到媒體庫在一般網頁用戶端中顯示正確後再回來。安裝這項服務的前提是媒體庫已經存在,而不是因為你需要在 自架服務清單中再增加一項服務。
此專案採用 GPL-3.0 授權,由一人撰寫;README 也明確表示不接受 pull request。開發進度很快,且沒有第二位維護者負責攔截回歸問題,因此在向其他人展示這間商店前,請先固定映像版本。最後一節會說明操作方式。
在哪裡進行算繪?
在瀏覽器中。Halcyon 是以 three.js 為基礎建立的 Vite 與 TypeScript 應用程式。three.js 是透過 WebGL(web graphics library,瀏覽器與 GPU 之間的介面)繪製 3D 圖形的 JavaScript 程式庫。商店的幾何模型與盒裝美術圖會由顯示畫面的裝置合成。
容器幾乎不需執行任何工作。它會執行 npm run serve,也就是 vite preview --port 1420 --strictPort --host,並提供建置後的檔案及幾個小型 middleware 路由。Halcyon 不會進行轉碼,也不會在伺服器上執行遊戲引擎。
因此,GPU 的問題取決於用戶端。小型 VPS 也能順利提供這項服務,因為其工作只是透過 HTTP 提供靜態檔案。真正決定商店能否流暢運作的,是執行瀏覽器的筆電、平板或電視。
有一項功能不適用上述規則。Remote Play 會在伺服器上啟動 headless Chromium 執行個體,並透過 WebRTC(web real time communication)將算繪後的商店串流至手機或機上盒。這條路徑會在伺服器上進行算繪,預設最多使用 2 個執行個體,也可透過 REMOTE_PLAY_MAX_INSTANCES 調整。若未映射 /dev/dri 裝置,這些執行個體會改用 CPU 算繪,因此 2 核心 VPS 會明顯受到每位額外觀眾的影響。
商店從媒體庫讀取的內容
貨架結構來自 Jellyfin 本身。Halcyon 會根據媒體庫與類型排列區段,也會依據 BoxSets 將續集分組。每個盒裝背面印出的規格,則取自 Jellyfin 已保存的 MediaStreams 中繼資料。因此,Jellyfin 中缺少的資訊,在貨架上也不會顯示。
因此,商店會如實反映您的中繼資料。使用 Docker Compose 中的 arr stack 建立的媒體庫,如果已填妥圖片與類型,呈現效果會比只有檔案夾、檔名又是通用名稱的檔案集合好得多。
安裝任何元件前,先試用影音商店示範
此專案會在託管示範中,使用合成媒體庫執行完整商店。對任何 Halcyon URL 加上 ?demo=1,即可在您自己的部署中達到相同效果。
請將此示範用於硬體測試。示範媒體庫包含約 2,000 個標題,約需 2 GB 瀏覽器記憶體,負載高於大多數個人媒體庫。若示範在您計畫用來瀏覽的裝置上出現卡頓,您自己的媒體庫也會卡頓。解決方式是下方所述的 2.5D 模式,而不是改用更大的 VPS。
使用 Docker 執行
這是上游文件提供的指令。
docker run -d --name halcyon --network host --restart unless-stopped \
ghcr.io/halcyon-video/halcyon-video接著確認服務已啟動。
docker logs halcyon
curl -I http://127.0.0.1:1420日誌應顯示預覽伺服器正在監聽 1420 埠,而 curl 應能回應 HTTP/1.1 200 OK。容器若在幾秒內結束,幾乎總是連接埠造成的問題。--strictPort 表示伺服器不會在 1420 已被占用時改用 1421,因此會直接停止。
--network host 是供 Remote Play 使用,不是供商店使用。WebRTC 必須向要求串流的裝置公布機器的實際位址。在預設的 Docker bridge 後方,容器只知道自己的 172.x 位址;網路上的手機無法連線到該位址,因此串流永遠無法建立連線。如果只想在瀏覽器中使用商店,請改為發布連接埠。
docker run -d --name halcyon -p 1420:1420 --restart unless-stopped \
ghcr.io/halcyon-video/halcyon-video在 VPS 上,這是較好的預設設定,因為 host networking 會讓容器使用機器上的所有介面,包括公開介面。在 VPS 上執行 Docker 說明了這項取捨的其他部分。--restart unless-stopped 會在重新開機後重新啟動商店,概念上與 開機時啟動的 Compose 服務 相同。
複製 repository 並執行 docker compose up -d,則會改為在本機建置映像檔。提交的 Compose 檔案預設會從原始碼建置,並將預先建置的 image: 行註解掉;若要在 Compose 中使用已發布的映像檔,請取消該行的註解。
截至 2026 年 8 月,有一項限制:已發布的映像檔只有 linux/amd64。多架構映像檔推送中的 arm64 部分在模擬環境下建置失敗,目前正等待原生 arm runner。在 arm64 VPS 上,pull 會因 no matching manifest for linux/arm64/v8 in the manifest list entries 而失敗;此時應從複製的 repository 建置。
設定 Jellyfin 伺服器位址
開啟 http://<host>:1420,並使用 Jellyfin 伺服器位址、使用者名稱與密碼登入。儲存庫中的 .env.local.example 檔案僅供本機開發使用。Vite 會將前綴為 VITE_ 的變數暴露給用戶端程式碼,因此寫入其中的 Jellyfin 密碼會被編譯到每位訪客都會下載的 JavaScript bundle 中。在其他人可連線的伺服器上,請透過介面登入。
瀏覽器會直接與 Jellyfin 通訊。Halcyon 的 container 不會代理 Jellyfin API,因此在開始除錯前,請先了解以下兩項影響。
首先,瀏覽器必須能連線到 Jellyfin,不能只讓提供 Halcyon 的 VPS 連線。Jellyfin 若繫結至 127.0.0.1:8096,本機測試沒有問題,但其他使用者會看到空白的內容架。
其次,這是跨來源請求,來源為 Halcyon 的位址,目標為 Jellyfin 的位址。Jellyfin 預設會以 Access-Control-Allow-Origin: * 回應 API 請求,因此不需要額外設定即可運作。如果你縮限了該設定,或在 Jellyfin API 前方放置驗證 proxy,瀏覽器主控台會回報 blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource,而 store 載入後會顯示空白的內容架。
將其置於反向代理之後,並在前方加入驗證
vite preview 是預覽伺服器。它不會終止 TLS(傳輸層安全性),也沒有內建存取控制,因此任何公開服務都應將它置於 nginx 或 Caddy 之後。
server {
listen 443 ssl;
server_name halcyon.example.com;
location / {
proxy_pass http://127.0.0.1:1420;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}容器前方使用網域名稱時,還需要多設定一項。Halcyon 會接受 localhost、原始 IP 位址,以及執行所在機器的名稱,以防 DNS rebinding。容器內的執行所在機器就是該容器,因此其主機名稱不是你的主機名稱。以 halcyon.example.com 傳入的請求會遭拒絕,回應中會列出遭拒絕的主機名稱。請加入該名稱。
docker run -d --name halcyon -p 127.0.0.1:1420:1420 --restart unless-stopped \
-e HALCYON_ALLOWED_HOSTS=halcyon.example.com \
ghcr.io/halcyon-video/halcyon-video值以逗號分隔;開頭的句點(例如 .example.com)會比對子網域,而 all 會停用檢查。只有在外部無法連入的機器上,才使用 all。
商店經由 https:// 提供服務後,你在登入時輸入的 Jellyfin 位址也必須是 https://。瀏覽器會封鎖從 HTTPS 頁面發出的純 http:// API 呼叫,而主控台會顯示 Mixed Content: The page at 'https://halcyon.example.com/' was loaded over HTTPS, but requested an insecure resource。登入只會失敗,Halcyon 內不會提供任何說明。請讓兩者都經由 TLS 提供服務,或在私有網路內讓兩者都使用純 HTTP。
接著是驗證。商店會要求 Jellyfin 認證,因此找到 URL 的陌生訪客會看到登入畫面。但有一項功能會改變這點。在 Settings 中進入 Connection 並啟用 Remote Play 後,伺服器會使用你的 Jellyfin 工作階段,因此造訪 /remote.html 的訪客會取得你實際媒體庫的獨立執行個體。這就是該功能的用途,也表示 URL 的機密性成了網際網路與你的影片之間的唯一屏障。若啟用 Remote Play,請在整個網站前方加入單一登入,例如使用 Authentik 作為自架 SSO 閘道;或者移除公開主機名稱,改用 由 wg-easy 管理的 WireGuard 通道 存取商店。
還有兩項細節。反向代理只承載商店:Remote Play 串流使用 UDP 傳輸的 WebRTC,不會經過 HTTP 代理。因此,在使用內建 TURN relay 時,必須為它在 3478/udp 及 49200 至 49260/udp 上提供獨立路徑。此外,上述純 docker run 不會保留任何 volume,因此 Remote Play seed 不會在 docker rm 後保留。Compose 檔案會在 /data 掛載 halcyon-data volume,並將 REMOTE_PLAY_SEED 設為 /data/remote-play-seed.json,正是為了這個原因。
商店執行不穩定時的處理方式
Halcyon 會依需求進行算繪。閒置中的商店不會合成任何畫格,視窗失去焦點後也會停止動畫迴圈,因此分頁即使持續開啟,也不會耗盡筆記型電腦的電池電力。這能改善僅略低於需求的裝置效能,但無法處理完全無法算繪商店的裝置。
這類用戶端可使用 2.5D 模式。此模式只使用一般 HTML 和 CSS,不使用 WebGL,適用於 Raspberry Pi 等低規格硬體。您可以從設定或電源選單切換 3D 與 2.5D,且不需要重新載入頁面,因此在同一台裝置上測試兩種模式只需幾秒。請對實際效果保持合理預期:作者表示扁平模式仍較粗糙,且尚在開發中。請將其視為低效能用戶端的備援模式。
用戶端效能不足以執行 3D 商店時,故障通常很明顯。分頁會自行重新載入,或瀏覽器會回報 WebGL context 遺失,通常發生在貨架仍持續填入內容時。請將該裝置切換至 2.5D,而不要刪減您的資料庫。
固定映像版本,拉取前先檢查
請務必重視這一點。標籤 v0.1.0 到 v0.3.1 都是在幾天內發布的,而 v0.2.1 存在的唯一原因,是映像推送 v0.2.0 失敗。上游歡迎錯誤回報,但不接受修補程式,因此這個發布序列其實是某個人的工作狀態。
如果習慣搭配 docker pull 執行 latest,映像儲存庫可能在任何普通的星期二悄悄變更。請使用 digest 固定版本;這是唯一不會變動的參照。
docker buildx imagetools inspect ghcr.io/halcyon-video/halcyon-video:0.3.1這會輸出該標籤背後的 digest。請以它取代標籤。
docker run -d --name halcyon -p 1420:1420 --restart unless-stopped \
ghcr.io/halcyon-video/halcyon-video@sha256:747dcc821a3d2fa318b50e76024783c1835609047e84f502e23d021bc1898b20截至 10 August 2026,該 digest 是 0.3.1。請自行讀取目前的 digest,不要直接複製這個值。切換版本前也請先閱讀 release notes,因為這裡的修補版本除了修正問題,也可能變更儲存庫配置。
FAQ
Halcyon 在 VPS 上需要 GPU 嗎?
一般使用不需要。商店由瀏覽器中的 three.js 繪製,因此由用戶端機器負責算圖,而容器只會在 1420 埠提供靜態檔案。例外是 Remote Play。它會在伺服器上執行 headless Chromium 並串流結果。除非將 /dev/dri 對映到容器中以啟用硬體加速,否則這個流程會使用 CPU 算圖。
我可以將 Halcyon 放到公開網際網路上嗎?
只能放在驗證機制後方。商店會要求 Jellyfin 認證,但啟用 Remote Play 後,會將你的 Jellyfin 工作階段交給伺服器。因此,任何載入 /remote.html 的人都能在不登入的情況下取得你實際媒體庫的執行個體。請在前方設定具備單一登入功能的反向代理,或不要將該主機名稱加入公開 DNS,改用 VPN 存取商店。
為什麼登入後書架是空的?
瀏覽器會直接呼叫 Jellyfin API,因此瀏覽器必須能連線到 Jellyfin,不能只有 VPS 能連線。請開啟瀏覽器主控台。blocked by CORS policy 表示 Jellyfin 不接受來自 Halcyon 位址的請求。出現 Mixed Content 訊息表示頁面使用 HTTPS,而你輸入的 Jellyfin 位址是純 HTTP。
我需要 --network host 嗎?
只有 Remote Play 需要。WebRTC 必須公布機器的實際位址,而在 Docker bridge 後方,容器只能提供 172.x 位址,網路上的手機無法連線到該位址。若只是在瀏覽器中瀏覽商店,使用 -p 1420:1420 即可,且暴露的主機資訊少得多。
我應該使用哪個映像標籤?
請固定使用 digest,而不要使用 latest。讀取具有 docker buildx imagetools inspect ghcr.io/halcyon-video/halcyon-video:0.3.1 的版本之 digest,執行該 digest,並在閱讀 release notes 後才更新。截至 2026 年 8 月,已發布的映像檔只有 linux/amd64,因此 arm64 主機必須從 clone 使用 docker compose up -d 建置。