Halcyon দিয়ে Jellyfin-কে 90-এর দশকের ভিডিও স্টোর বানান
Halcyon ব্রাউজারে আপনার Jellyfin লাইব্রেরিকে হাঁটা যায় এমন 1990-এর দশকের rental store বানায়। Docker command, reverse proxy ও বাস্তব সীমাবদ্ধতা জানুন।
Halcyon আপনার Jellyfin লাইব্রেরিতে কী করে
Halcyon Video ব্রাউজারে আপনার Jellyfin লাইব্রেরিকে হাঁটা যায় এমন 1990-এর দশকের একটি ভিডিও স্টোর হিসেবে দেখায়। আপনার মালিকানাধীন প্রতিটি চলচ্চিত্র shelf-এর একটি case হয়ে যায়। আপনি strip light-এর নিচে aisle ধরে হাঁটেন, shelf থেকে একটি box নামান, পেছনের অংশে লেখা specification পড়তে সেটি উল্টে দেখেন, তারপর playback শুরু করতে counter-এ নিয়ে যান। Playback-এর start, progress এবং stop তথ্য Jellyfin-এ ফেরত পাঠানো হয়। ফলে resume point এবং watch history সঠিক থাকে।
Halcyon Jellyfin API ব্যবহার করে একটি বিদ্যমান Jellyfin server থেকে তথ্য পড়ে এবং নিজস্ব কোনো library সংরক্ষণ করে না। এই নির্দেশিকায় ধরে নেওয়া হয়েছে যে Jellyfin ইতিমধ্যে চলছে এবং library সঠিকভাবে scan করছে। তা না হলে আগে VPS-এ Jellyfin-কে media server হিসেবে সেট আপ করুন এবং সাধারণ web client-এ library সঠিক দেখানোর পর এখানে ফিরে আসুন। এটি এমন একটি service, যা library আগে থেকেই থাকার কারণে install করবেন; আপনার self-hosting তালিকায় আরেকটি service দরকার বলে নয়।
প্রকল্পটি GPL-3.0-এর অধীনে প্রকাশিত এবং একজন ব্যক্তি এটি লিখেছেন। README-তে স্পষ্টভাবে বলা আছে যে এটি pull request গ্রহণ করে না। Development দ্রুত এগোয় এবং regression শনাক্ত করার মতো দ্বিতীয় কোনো maintainer নেই। তাই অন্য কাউকে store দেখানোর আগে image version pin করুন। শেষ section-এ এর পদ্ধতি দেখানো হয়েছে।
রেন্ডারিং কোথায় হয়?
ব্রাউজারে। Halcyon হলো Vite এবং TypeScript-ভিত্তিক একটি অ্যাপ, যা three.js-এর ওপর তৈরি। three.js একটি JavaScript library, যা WebGL (web graphics library, GPU-এর সঙ্গে ব্রাউজারের interface)-এর মাধ্যমে 3D graphics আঁকে। Store geometry এবং box art যে machine-এ screen চলছে, সেটিই সেগুলো একত্র করে।
Container খুব কম কাজ করে। এটি npm run serve চালায়, যা হলো vite preview --port 1420 --strictPort --host, এবং built files-এর পাশাপাশি কয়েকটি ছোট middleware route serve করে। Halcyon কোনো transcoding যোগ করে না এবং server-এ কোনো engine চালায় না।
তাই GPU-সংক্রান্ত প্রশ্নটি client-এর ক্ষেত্রে প্রযোজ্য। একটি ছোট VPS এটি সহজেই serve করতে পারে, কারণ এখানে serve করার অর্থ হলো HTTP-এর মাধ্যমে static files পাঠানো। Browser চালানো laptop, tablet বা television-ই নির্ধারণ করে store মসৃণভাবে চলবে, নাকি ধীর হয়ে যাবে।
একটি feature এই নিয়মের ব্যতিক্রম। Remote Play server-এ headless Chromium instance চালু করে এবং rendered store WebRTC (web real time communication)-এর মাধ্যমে phone বা set top box-এ stream করে। এই পথে rendering server-এ হয়। ডিফল্টভাবে সর্বোচ্চ দুইটি instance চলে, এবং REMOTE_PLAY_MAX_INSTANCES দিয়ে এই সংখ্যা পরিবর্তন করা যায়। mapped /dev/dri device না থাকলে instance-গুলো CPU-তে render করে। তাই দুই core-এর VPS-এ অতিরিক্ত প্রতিটি viewer-এর প্রভাব স্পষ্ট হয়।
আপনার লাইব্রেরি থেকে স্টোর কী তথ্য পড়ে
Aisle-গুলো Jellyfin-এর নিজস্ব structure থেকে আসে। Halcyon আপনার library ও genre অনুযায়ী section সাজায় এবং আপনার BoxSets থেকে sequel-গুলো group করে। প্রতিটি case-এর পেছনে মুদ্রিত spec-গুলো Jellyfin-এ আগে থেকেই থাকা MediaStreams metadata থেকে আসে। তাই Jellyfin-এ যা missing, shelf-এও তা missing।
এতে store আপনার metadata-এর একটি নির্ভরযোগ্য প্রতিচ্ছবি হিসেবে কাজ করে। artwork ও genre আগে থেকেই পূরণ করা Docker Compose-এ একটি arr stack দিয়ে তৈরি library এখানে generic নামের loose file-এর folder-এর তুলনায় অনেক ভালো দেখায়। Photo library-র ক্ষেত্রেও একইভাবে সেটি index করা software-এর ওপর নির্ভরতা থাকে। একই server-এ থাকা still image-এর জন্য PhotoPrism এবং Immich-এর তুলনা করার সময় এই বিষয়টি মনে রাখা দরকার।
কিছু ইনস্টল করার আগে video store demo পরীক্ষা করুন
প্রকল্পটি hosted demo-তে synthetic library ব্যবহার করে সম্পূর্ণ store চালু রাখে। নিজের deployment-এ যেকোনো Halcyon URL-এর শেষে ?demo=1 যোগ করলেও একই ফল পাবেন।
এটিকে hardware test হিসেবে ব্যবহার করুন। Demo library-তে প্রায় 2,000টি title আছে এবং browser memory হিসেবে প্রায় 2 GB প্রয়োজন হয়, যা অধিকাংশ personal library-এর চেয়ে বেশি। যে device থেকে browse করার পরিকল্পনা করছেন, সেখানে demo যদি আটকে আটকে চলে, আপনার নিজের library-ও একইভাবে চলবে। এর সমাধান বড় VPS নয়; নিচে বর্ণিত 2.5D mode ব্যবহার করুন।
Docker দিয়ে চালান
এটি upstream-এর documentation-এ দেওয়া command।
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লগে preview server-কে port 1420-এ listening অবস্থায় দেখানোর কথা, এবং curl-এর মাধ্যমে HTTP/1.1 200 OK-এর উত্তর পাওয়ার কথা। কোনো container কয়েক সেকেন্ডের মধ্যে exit করলে প্রায় সব ক্ষেত্রেই কারণ port। --strictPort নির্ধারণ করে যে 1420 দখল করা থাকলে server 1421-এ সরে যাবে না; বরং বন্ধ হয়ে যাবে।
--network host Remote Play-এর জন্য, store-এর জন্য নয়। WebRTC-কে stream-এর অনুরোধ করা device-এর কাছে মেশিনের প্রকৃত address জানাতে হয়। Default Docker bridge-এর পেছনে container শুধু নিজের 172.x address জানে। আপনার network-এর কোনো phone এই address-এ পৌঁছাতে পারে না। তাই stream কখনো connect হয় না। আপনি যদি শুধু browser-এ store ব্যবহার করতে চান, port publish করুন।
docker run -d --name halcyon -p 1420:1420 --restart unless-stopped \
ghcr.io/halcyon-video/halcyon-videoVPS-এ এটি ভালো default। কারণ host networking container-কে মেশিনের সব interface-এ যুক্ত করে, public interface-সহ। VPS-এ Docker চালানো-এ এই trade-off-এর বাকি বিষয়গুলো দেখানো হয়েছে। --restart unless-stopped reboot-এর পরে store আবার চালু করে। এটি boot-এর সময় চালু হওয়া Compose service-এর মতোই একই পদ্ধতি।
Repository clone করে docker compose up -d চালালে image স্থানীয়ভাবে build হয়। Commit করা Compose file-এ default হিসেবে source থেকে build করা হয়। এতে prebuilt image: line-টি comment করা থাকে। Compose-এর অধীনে published image ব্যবহার করতে চাইলে ওই line-এর comment তুলে দিন।
August 2026 অনুযায়ী একটি গুরুত্বপূর্ণ সীমাবদ্ধতা আছে: published image শুধু linux/amd64। Emulation-এর অধীনে multi architecture push-এর arm64 অংশ ব্যর্থ হয়েছে। Native arm runner পাওয়ার অপেক্ষা চলছে। arm64 VPS-এ pull করলে no matching manifest for linux/arm64/v8 in the manifest list entries-সহ ব্যর্থ হবে। এই ক্ষেত্রে clone থেকে build করাই সমাধান।
Jellyfin সার্ভারের ঠিকানা নির্ধারণ করুন
http://<host>:1420 খুলে Jellyfin সার্ভারের address, username এবং password দিয়ে লগ ইন করুন। Repository-র .env.local.example file শুধু local development-এর জন্য। Vite client-side code-এ VITE_ দিয়ে শুরু হওয়া variable প্রকাশ করে। তাই সেখানে লেখা Jellyfin password প্রতিটি visitor যে JavaScript bundle download করে, তার মধ্যে compile হয়ে যায়। অন্যরা reach করতে পারে এমন server-এ interface ব্যবহার করে লগ ইন করুন।
Browser সরাসরি Jellyfin-এর সঙ্গে যোগাযোগ করে। Halcyon-এর container Jellyfin API-র জন্য proxy হিসেবে কাজ করে না। Debugging শুরু করার আগে এর দুটি গুরুত্বপূর্ণ ফলাফল জানা দরকার।
প্রথমত, Jellyfin-কে শুধু Halcyon serve করা VPS থেকে নয়, browser থেকেও reach করতে হবে। 127.0.0.1:8096-এ bound থাকা Jellyfin local test-এর জন্য ঠিক আছে, কিন্তু অন্য সবার জন্য shelves খালি থাকবে।
দ্বিতীয়ত, এই call cross-origin। এটি Halcyon-এর address থেকে Jellyfin-এর address-এ যায়। Jellyfin defaultভাবে API request-এর উত্তর Access-Control-Allow-Origin: * দিয়ে দেয়, তাই অতিরিক্ত configuration ছাড়াই এটি কাজ করে। আপনি যদি ওই setting সীমিত করে থাকেন, অথবা Jellyfin API-র সামনে authentication proxy বসিয়ে থাকেন, browser console-এ blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource দেখাবে এবং store-এ খালি shelves লোড হবে।
Reverse proxy-এর পেছনে রাখুন এবং সামনে authentication দিন
vite preview একটি preview server। এটি TLS (transport layer security) termination করে না এবং নিজস্ব access control নেই। তাই public ব্যবহারের ক্ষেত্রে এটি 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;
}
}Container-এর সামনে domain name ব্যবহার করতে আরও একটি setting প্রয়োজন। DNS rebinding প্রতিরোধ করতে Halcyon localhost, raw IP address এবং যে machine-এ এটি চলে তার নামের অনুরোধ গ্রহণ করে। Container-এর ভিতরে যে machine-এ এটি চলে সেটি হলো container নিজেই। তাই সেই hostname আপনার hostname নয়। halcyon.example.com হিসেবে আসা অনুরোধ প্রত্যাখ্যাত হয় এবং response-এ প্রত্যাখ্যাত host-এর নাম দেখানো হয়। সেই নামটি যোগ করুন।
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-videoValue-টি comma-separated। .example.com-এর মতো শুরুতে থাকা dot subdomain-গুলোর সঙ্গেও match করে। all check-টি বন্ধ করে। all কেবল এমন machine-এ ব্যবহার করুন, যেটিতে বাইরের কোনো উৎস থেকে পৌঁছানো যায় না।
Store-টি https://-এর মাধ্যমে serve করার পর login-এর সময় দেওয়া Jellyfin address-ও https:// হতে হবে। HTTPS page থেকে করা plain http:// API call browser block করে এবং console-এ Mixed Content: The page at 'https://halcyon.example.com/' was loaded over HTTPS, but requested an insecure resource দেখা যায়। Halcyon-এর ভিতরে কোনো ব্যাখ্যা ছাড়াই login ব্যর্থ হয়। উভয়টিই TLS-এর মাধ্যমে serve করুন, অথবা private network-এর ভিতরে উভয়টিই plain HTTP-তে রাখুন।
এরপর authentication। Store Jellyfin credential চায়। তাই URL খুঁজে পাওয়া কোনো অপরিচিত ব্যক্তি login screen দেখতে পাবে। একটি feature এই আচরণ বদলে দেয়। Settings এবং তারপর Connection-এর অধীনে Remote Play চালু করলে আপনার Jellyfin session server-কে দেওয়া হয়। ফলে /remote.html-এ আসা visitor আপনার আসল library-এর নিজস্ব instance পায়। Feature-টির উদ্দেশ্যই এটি। এর অর্থ হলো URL-এর গোপনীয়তাই internet এবং আপনার film library-এর মাঝের একমাত্র বাধা। Remote Play চালু করলে পুরো site-এর সামনে single sign-on রাখুন; এ জন্য Authentik-কে self-hosted SSO gateway হিসেবে ব্যবহার করুন, অথবা public hostname বাদ দিয়ে wg-easy দিয়ে পরিচালিত WireGuard tunnel-এর মাধ্যমে store-এ পৌঁছান।
এটির সঙ্গে আরও দুটি বিষয় মনে রাখতে হবে। Reverse proxy কেবল store বহন করে। Remote Play stream WebRTC over UDP এবং HTTP proxy-এর মধ্য দিয়ে যায় না। তাই 3478/udp-এ এবং bundled TURN relay ব্যবহৃত হলে 49200 থেকে 49260/udp-এ এর নিজস্ব path প্রয়োজন। এছাড়া উপরের plain docker run কোনো volume সংরক্ষণ করে না। তাই Remote Play seed docker rm-এর পর টিকে থাকে না। এই কারণেই Compose file একটি halcyon-data volume-কে /data-এ mount করে এবং REMOTE_PLAY_SEED-এর মান /data/remote-play-seed.json নির্ধারণ করে।
স্টোর ধীরগতিতে চললে কী করবেন
Halcyon প্রয়োজন অনুযায়ী render করে। কোনো store idle অবস্থায় থাকলে এটি কোনো frame composite করে না। Window focus হারালে animation loop বন্ধ হয়ে যায়। তাই একটি tab খোলা থাকলেও laptop battery অস্বাভাবিকভাবে দ্রুত শেষ হয় না। শুধু সীমার কাছাকাছি সক্ষমতার কোনো machine-এর ক্ষেত্রে এটি উপকারী। কিন্তু কোনো machine যদি store একেবারেই render করতে না পারে, তাহলে এতে কোনো সমাধান হয় না।
এই client-গুলোর জন্য 2.5D mode রয়েছে। এতে WebGL ব্যবহার না করে সাধারণ HTML এবং CSS ব্যবহৃত হয়। Raspberry Pi-এর মতো কম সক্ষম hardware-এর জন্যও এটি তৈরি করা হয়েছে। Page reload ছাড়াই settings অথবা power menu থেকে 3D এবং 2.5D-এর মধ্যে পরিবর্তন করা যায়। তাই একই device-এ দুটো mode পরীক্ষা করতে কয়েক সেকেন্ডই লাগে। তবে ফলাফল সম্পর্কে বাস্তবসম্মত থাকুন। লেখক flat mode-কে অপরিশীলিত এবং এখনও উন্নয়নাধীন হিসেবে বর্ণনা করেছেন। দুর্বল client-এর জন্য এটিকে fallback হিসেবে ব্যবহার করুন।
কোনো client 3D store চালানোর জন্য যথেষ্ট সক্ষম না হলে ব্যর্থতা স্পষ্টভাবে দেখা যায়। Tab নিজে থেকে reload হতে পারে। অথবা browser lost WebGL context-এর বার্তা দেখাতে পারে। সাধারণত shelves এখনও পূরণ হওয়ার সময় এটি ঘটে। Library ছোট করার বদলে ওই device-টিকে 2.5D mode-এ সরিয়ে দিন।
ইমেজ pin করুন এবং pull করার আগে পরীক্ষা করুন
এই অংশটি গুরুত্ব দিয়ে নিন। v0.1.0 থেকে v0.3.1 পর্যন্ত tag-গুলো কয়েক দিনের ব্যবধানে প্রকাশিত হয়েছে, আর v0.2.1 তৈরি হয়েছে শুধু v0.2.0-এর image push ব্যর্থ হওয়ার কারণে। Upstream-এ bug report দেওয়া যায়, কিন্তু patch গ্রহণ করা হয় না। তাই এই release stream একজনের কার্যরত অবস্থার ওপর নির্ভরশীল।
docker pull করার অভ্যাসসহ latest চালালে সাধারণ যেকোনো মঙ্গলবার store আপনার অজান্তেই পরিবর্তিত হতে পারে। Digest দিয়ে pin করুন। এটিই একমাত্র reference যা পরিবর্তন করা যায় না।
docker buildx imagetools inspect ghcr.io/halcyon-video/halcyon-video:0.3.1এটি tag-এর পেছনে থাকা digest দেখায়। Tag-এর পরিবর্তে এটি ব্যবহার করুন।
docker run -d --name halcyon -p 1420:1420 --restart unless-stopped \
ghcr.io/halcyon-video/halcyon-video@sha256:747dcc821a3d2fa318b50e76024783c1835609047e84f502e23d021bc1898b2010 August 2026 তারিখে সেই digest ছিল 0.3.1। এটি copy না করে নিজে বর্তমান digest পড়ুন। Version পরিবর্তনের আগে release notes-ও পড়ুন, কারণ এখানে একটি patch release-এ fix-এর পাশাপাশি store layout পরিবর্তনও থাকতে পারে।
FAQ
Halcyon চালাতে কি আমার VPS-এ GPU দরকার?
সাধারণ ব্যবহারের জন্য নয়। স্টোরটি browser-এ three.js দিয়ে render হয়, তাই rendering client machine-এ হয় এবং container শুধু port 1420-এ static file পরিবেশন করে। ব্যতিক্রম হলো Remote Play, যা server-এ headless Chromium চালিয়ে ফলাফল stream করে। hardware acceleration-এর জন্য /dev/dri container-এ map না করলে এই প্রক্রিয়ায় CPU ব্যবহার করে rendering হয়।
আমি কি Halcyon-কে public Internet-এ রাখতে পারি?
শুধু authentication-এর পেছনে রাখলে। স্টোরটি Jellyfin credentials চায়, কিন্তু Remote Play চালু করলে আপনার Jellyfin session server-কে দেওয়া হয়। ফলে /remote.html load করা যে কেউ login না করেই আপনার প্রকৃত library-র একটি instance পেয়ে যায়। এর সামনে single sign-on-সহ একটি reverse proxy রাখুন, অথবা hostname-টি public DNS-এর বাইরে রেখে VPN-এর মাধ্যমে স্টোরে প্রবেশ করুন।
Login করার পর shelves খালি কেন?
browser সরাসরি Jellyfin API-তে call করে। তাই Jellyfin-কে শুধু VPS থেকে নয়, browser থেকেও reachable হতে হবে। browser console খুলুন। blocked by CORS policy এর অর্থ হলো Halcyon's address থেকে Jellyfin request গ্রহণ করছে না। Mixed Content message-এর অর্থ হলো page-টি HTTPS-এ চলছে, কিন্তু আপনি যে Jellyfin address দিয়েছেন সেটি plain HTTP।
আমার কি --network host দরকার?
শুধু Remote Play-এর জন্য। WebRTC-কে machine-এর প্রকৃত address advertise করতে হয়। Docker bridge-এর পেছনে container কেবল এমন একটি 172.x address দিতে পারে, যেখানে আপনার network-এর কোনো phone পৌঁছাতে পারে না। browser-এ স্টোর browse করার জন্য -p 1420:1420 কাজ করে এবং host-এর অনেক কম অংশ expose করে।
কোন image tag ব্যবহার করা উচিত?
latest-এর পরিবর্তে একটি digest pin করুন। docker buildx imagetools inspect ghcr.io/halcyon-video/halcyon-video:0.3.1 দিয়ে কোনো version-এর digest পড়ুন, সেই digest চালান, এবং release notes পড়ার পরেই পরিবর্তন করুন। August 2026 অনুযায়ী published image শুধু linux/amd64, তাই arm64 host-কে clone থেকে docker compose up -d দিয়ে build করতে হবে।