كيف تحوّل مكتبة Jellyfin إلى متجر فيديو من التسعينيات؟
حوّل مكتبة Jellyfin إلى متجر فيديو قابل للتجول في المتصفح عبر Halcyon. تعرّف إلى أمر Docker والوكيل العكسي وحدود المشروع وإصدار image الذي ينبغي تثبيته.
ما الذي يفعله Halcyon بمكتبة Jellyfin لديك
يعيد Halcyon Video عرض مكتبة Jellyfin لديك في المتصفح على شكل متجر فيديو من حقبة 1990 يمكن التجول فيه. يتحول كل فيلم تملكه إلى غلاف على رف. تتجول بين الممرات تحت مصابيح الشرائط، وتسحب غلافاً من الرف، وتقلبه لقراءة المواصفات على الجهة الخلفية، ثم تحمله إلى المنضدة لبدء التشغيل. يرسل التشغيل حالات البدء والتقدم والتوقف إلى Jellyfin، لذلك تبقى نقاط الاستئناف وسجل المشاهدة صحيحين.
يقرأ Halcyon خادم Jellyfin موجوداً عبر Jellyfin API، ولا يحتفظ بمكتبة خاصة به. يفترض هذا الدليل أن Jellyfin يعمل بالفعل ويفحص الملفات دون أخطاء. إذا لم يكن كذلك، فأعد أولاً إعداد Jellyfin كخادم وسائط على VPS، ثم عد بعد أن تبدو مكتبتك صحيحة في عميل الويب العادي. هذا النوع من البرامج تثبّته لأن المكتبة موجودة بالفعل، لا لأنك تحتاج إلى خدمة أخرى في قائمة خدمات الاستضافة الذاتية لديك.
المشروع مرخّص بموجب GPL-3.0، ويطوره شخص واحد، ويذكر README بوضوح أنه لا يقبل طلبات السحب. تتسارع وتيرة التطوير، ولا يوجد مشرف ثانٍ لاكتشاف التراجعات، لذلك ثبّت إصدار image قبل أن تعرض المتجر على أي شخص آخر. يشرح القسم الأخير الطريقة.
أين يحدث التصيير؟
يحدث في المتصفح. Halcyon هو تطبيق Vite وTypeScript مبني على three.js، وهي مكتبة JavaScript ترسم الرسومات ثلاثية الأبعاد عبر WebGL (مكتبة رسومات الويب، وهي واجهة المتصفح إلى وحدة GPU). يركّب الجهاز الذي يشغّل الشاشة هندسة المتجر وصور أغلفة الصناديق.
لا يفعل الحاوي الكثير. فهو يشغّل npm run serve، وهو vite preview --port 1420 --strictPort --host، ويقدّم الملفات المبنية بالإضافة إلى بضعة مسارات middleware صغيرة. لا يضيف Halcyon أي تحويل للوسائط، ولا يشغّل أي محرك على الخادم.
لذلك، يتعلق سؤال GPU بالعميل. يمكن لـVPS صغير تقديم هذا التطبيق بسهولة، لأن تقديمه يعني إرسال ملفات ثابتة عبر HTTP. أما الحاسوب المحمول أو الجهاز اللوحي أو التلفاز الذي يشغّل المتصفح فهو الذي يحدد ما إذا كان المتجر سيعمل بسلاسة أو ببطء شديد.
تخرق ميزة واحدة هذه القاعدة. يشغّل Remote Play مثيلات Chromium بلا واجهة على الخادم، ويبث المتجر الذي جرى تصييره إلى هاتف أو جهاز استقبال عبر WebRTC (اتصالات الويب الآنية). يحدث التصيير في الخادم ضمن هذا المسار، ويقتصر افتراضياً على مثيلين، ويمكن تعديل العدد باستخدام REMOTE_PLAY_MAX_INSTANCES. ومن دون جهاز /dev/dri معيّن، تصيّر هذه المثيلات المحتوى باستخدام CPU، لذلك يلاحظ VPS ذو نواتين أثر كل مشاهد إضافي.
ما تقرؤه المتجر من مكتبتك
تأتي الممرات من بنية Jellyfin نفسها. يعرض Halcyon الأقسام من مكتباتك وتصنيفاتك، ويجمع الأجزاء اللاحقة من BoxSets. تأتي المواصفات المطبوعة على ظهر كل علبة من بيانات MediaStreams الوصفية التي يحتفظ بها Jellyfin، ما يعني أن أي بيانات مفقودة في Jellyfin ستكون مفقودة على الرف.
وهذا يجعل المتجر انعكاساً دقيقاً لبياناتك الوصفية. تبدو المكتبة التي يزوّدها مكدس arr في Docker Compose، مع اكتمال الأعمال الفنية والتصنيفات فيها، أفضل بكثير هنا من مجلد يحتوي على ملفات منفصلة ذات أسماء عامة. وتعتمد مكتبات الصور أيضاً على الأداة التي فهرستها، وهو أمر يستحق التذكر عند المفاضلة بين PhotoPrism وImmich للصور الموجودة على الخادم نفسه.
جرّب العرض التجريبي لمتجر الفيديو قبل تثبيت أي شيء
ينشر المشروع المتجر كاملاً وهو يعمل باستخدام مكتبة اصطناعية في العرض التجريبي المستضاف. تؤدي إضافة ?demo=1 إلى أي عنوان URL في Halcyon الوظيفة نفسها في عملية النشر الخاصة بك.
استخدمه لاختبار العتاد. تحتوي المكتبة التجريبية على نحو 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 أن الخادم يرفض الانتقال إلى 1421 عندما يكون 1420 مستخدماً، ولذلك يتوقف بدلاً من ذلك.
يُستخدم --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 يضع الحاوية على كل واجهة لدى الجهاز، بما في ذلك الواجهة العامة. يشرح تشغيل Docker على VPS بقية جوانب هذه المفاضلة. يعيد --restart unless-stopped المتجر بعد إعادة التشغيل، وهي الفكرة نفسها الواردة في خدمات Compose التي تبدأ عند الإقلاع.
يؤدي استنساخ المستودع وتشغيل docker compose up -d إلى إنشاء الصورة محلياً بدلاً من ذلك. ينشئ ملف Compose المضمّن الصورة من المصدر افتراضياً، ويحتوي على سطر image: للصورة المبنية مسبقاً لكنه معلّق. أزل التعليق عن هذا السطر إذا أردت استخدام الصورة المنشورة مع Compose.
هناك حد مهم اعتباراً من August 2026: الصورة المنشورة متاحة لمعمارية linux/amd64 فقط. فشل بناء جزء arm64 من الإصدار متعدد المعماريات تحت المحاكاة، وهو ينتظر runners أصلية لمعمارية arm. على VPS بمعمارية arm64، يفشل السحب مع no matching manifest for linux/arm64/v8 in the manifest list entries، ويكون البناء من النسخة المستنسخة هو الحل.
وجّه التطبيق إلى خادم Jellyfin لديك
افتح http://<host>:1420 وسجّل الدخول باستخدام عنوان خادم Jellyfin واسم المستخدم وكلمة المرور. ملف .env.local.example الموجود في المستودع مخصّص للتطوير المحلي فقط. يعرّض Vite المتغيرات التي تبدأ بالبادئة VITE_ إلى التعليمات البرمجية من جهة العميل، لذلك تُضمَّن كلمة مرور Jellyfin المكتوبة هناك داخل حزمة JavaScript التي ينزّلها كل زائر. على خادم يمكن لأشخاص آخرين الوصول إليه، سجّل الدخول من خلال الواجهة.
يتصل المتصفح بـJellyfin مباشرةً. لا تعمل حاوية Halcyon كـproxy لواجهة Jellyfin البرمجية، وينتج عن ذلك أمران يجب معرفتهما قبل بدء تصحيح الأخطاء.
أولاً، يجب أن يكون Jellyfin قابلاً للوصول من المتصفح، وليس من VPS الذي يقدّم Halcyon فقط. يكون ربط Jellyfin بـ127.0.0.1:8096 مناسباً للاختبار المحلي، لكنه يترك الرفوف فارغة لدى جميع المستخدمين الآخرين.
ثانياً، يتم الاتصال من مصدر مختلف، من عنوان Halcyon إلى عنوان Jellyfin. يجيب Jellyfin عن طلبات API باستخدام Access-Control-Allow-Origin: * افتراضياً، لذلك يعمل دون إعدادات إضافية. إذا قيّدت هذا الإعداد، أو وضعت authentication proxy أمام API الخاصة بـJellyfin، فسيعرض console المتصفح blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource، ويحمّل المتجر رفوف فارغة.
ضعه خلف Reverse Proxy مع مصادقة أمامية
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. داخل الحاوية، يكون الجهاز الذي يعمل عليه هو الحاوية نفسها، ولذلك لا يكون اسم المضيف هو اسم جهازك. يُرفض الطلب الوارد على 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:// أيضاً. يحظر المتصفح طلب API عادي عبر http:// إذا أُجري من صفحة HTTPS، وتعرض وحدة التحكم Mixed Content: The page at 'https://halcyon.example.com/' was loaded over HTTPS, but requested an insecure resource. يفشل تسجيل الدخول ببساطة، من دون تفسير داخل Halcyon. قدّم الخدمتين عبر TLS، أو استخدم HTTP العادي لكلتيهما داخل شبكة خاصة.
ثم تأتي المصادقة. يطلب المتجر بيانات اعتماد Jellyfin، لذلك سيواجه أي شخص يعثر على عنوان URL شاشة تسجيل الدخول. لكن هناك ميزة تغيّر ذلك. يؤدي تفعيل Remote Play من Settings ثم Connection إلى منح جلسة Jellyfin الخاصة بك للخادم، بحيث يحصل زائرو /remote.html على نسخة خاصة بهم من مكتبتك الفعلية. هذا هو الغرض من الميزة، ويعني أن سرية عنوان URL هي الحاجز بين الإنترنت وأفلامك. إذا فعّلت Remote Play، فضع تسجيل الدخول الموحد أمام الموقع بأكمله باستخدام Authentik كبوابة SSO مستضافة ذاتياً، أو أزل اسم المضيف العام وادخل إلى المتجر عبر نفق WireGuard مُدار باستخدام wg-easy.
يرتبط بذلك تفصيلان. يمرر Reverse Proxy المتجر فقط. إذ إن بث Remote Play يستخدم WebRTC عبر UDP، ولا يمر عبر HTTP proxy. لذلك يحتاج إلى مسار مستقل على 3478/udp وعلى 49200 إلى 49260/udp عند استخدام TURN relay المضمّن. كما أن docker run العادي المذكور أعلاه لا يحتفظ بأي volume، ولذلك لا يبقى seed الخاص بـRemote Play بعد docker rm. يربط ملف Compose volume باسم halcyon-data بالمسار /data، ويضبط REMOTE_PLAY_SEED على /data/remote-play-seed.json لهذا السبب تحديداً.
ما الذي تفعله عندما يعمل المتجر بصورة سيئة
يعرض Halcyon المحتوى عند الطلب. لا ينشئ المتجر الخامل أي إطارات، ويؤدي فقدان تركيز النافذة إلى إيقاف حلقة الرسوم المتحركة. لذلك لا يؤدي ترك علامة تبويب مفتوحة إلى استنزاف بطارية الحاسوب المحمول. يساعد ذلك الجهاز الذي يعمل عند الحد الأدنى المقبول. لكنه لا يفيد الجهاز الذي يعجز عن عرض المتجر بالكامل.
يتوفر لهؤلاء العملاء وضع 2.5D، وهو يعتمد على HTML وCSS فقط دون WebGL، وصُمم ليعمل حتى على أجهزة صغيرة مثل Raspberry Pi. يمكنك التبديل بين وضعي 3D و2.5D من الإعدادات أو قائمة الطاقة دون إعادة تحميل الصفحة، ولذلك يستغرق اختبار الوضعين على الجهاز نفسه بضع ثوانٍ. كن واقعياً بشأن النتيجة: يصف المؤلف الوضع المسطح بأنه بدائي ولا يزال قيد التطوير. استخدمه كخيار احتياطي للعملاء ضعيفي الأداء.
عندما يكون العميل أضعف من تشغيل المتجر ثلاثي الأبعاد، يظهر الفشل بوضوح. تعيد علامة التبويب تحميل نفسها، أو يعرض المتصفح رسالة تفيد بفقدان سياق WebGL، وعادةً يحدث ذلك بينما لا تزال الرفوف تمتلئ. بدّل ذلك الجهاز إلى 2.5D بدلاً من تقليص مكتبتك.
ثبّت صورة الحاوية، وتحقق قبل سحبها
تعامل مع هذا الجزء بجدية. وصلت الوسوم v0.1.0 إلى v0.3.1 كلها خلال أيام قليلة من بعضها، وظهر v0.2.1 فقط لأن دفع صورة v0.2.0 فشل. نرحب بتقارير الأخطاء في المشروع الأصلي، لكن التصحيحات غير مقبولة، لذلك يمثل مسار الإصدارات حالة العمل لشخص واحد.
إن تشغيل latest مع الاعتماد على docker pull يعني أن المستودع قد يتغير دون علمك في أي يوم عادي. ثبّت الصورة باستخدام 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كان ذلك الـdigest هو 0.3.1 في 10 August 2026. اقرأ القيمة الحالية بنفسك بدلاً من نسخها، واقرأ ملاحظات الإصدار قبل التحديث، لأن إصداراً تصحيحياً هنا قد يتضمن تغييرات في بنية المستودع إلى جانب الإصلاحات.
FAQ
هل يحتاج Halcyon إلى GPU على VPS الخاص بي؟
ليس للاستخدام العادي. يرسم المتصفح المتجر باستخدام three.js، لذلك ينفّذ جهاز العميل عملية التصيير، بينما يقدّم الـcontainer الملفات الثابتة على المنفذ 1420. الاستثناء هو Remote Play، الذي يشغّل Chromium بوضع headless على الخادم ويبث النتيجة. ينفّذ هذا المسار التصيير باستخدام CPU ما لم تربط /dev/dri بالـcontainer لتفعيل تسريع الأجهزة.
هل يمكنني وضع Halcyon على الإنترنت العام؟
فقط خلف المصادقة. يطلب المتجر بيانات اعتماد Jellyfin، لكن تفعيل Remote Play يسلّم جلسة Jellyfin الخاصة بك إلى الخادم، لذلك يحصل أي شخص يحمّل /remote.html على نسخة من مكتبتك الفعلية دون تسجيل الدخول. ضع reverse proxy مع single sign-on أمامه، أو أبقِ اسم المضيف خارج DNS العام، وادخل إلى المتجر عبر VPN.
لماذا تكون الرفوف فارغة بعد تسجيل الدخول؟
يستدعي المتصفح واجهة Jellyfin API مباشرة، لذلك يجب أن يكون Jellyfin قابلاً للوصول من المتصفح، لا من VPS فقط. افتح وحدة تحكم المتصفح. يعني blocked by CORS policy أن Jellyfin لا يقبل الطلب من عنوان Halcyon. وتعني رسالة Mixed Content أن الصفحة تعمل عبر HTTPS، بينما عنوان Jellyfin الذي أدخلته يستخدم HTTP عادي.
هل أحتاج إلى --network host؟
فقط من أجل Remote Play. يجب أن يعلن WebRTC عن العنوان الحقيقي للجهاز، لكن الـcontainer الموجود خلف Docker bridge لا يمكنه تقديم سوى عنوان 172.x لا يستطيع أي هاتف على شبكتك الوصول إليه. لتصفح المتجر في المتصفح، يعمل -p 1420:1420 ويكشف جزءاً أقل بكثير من المضيف.
ما وسم image الذي ينبغي أن أستخدمه؟
ثبّت digest بدلاً من latest. اقرأ digest لإصدار يتضمن docker buildx imagetools inspect ghcr.io/halcyon-video/halcyon-video:0.3.1، وشغّل ذلك digest، ولا تنتقل إلى إصدار آخر إلا بعد قراءة ملاحظات الإصدار. اعتباراً من August 2026، فإن image المنشورة هي linux/amd64 فقط، لذلك يجب على مضيف arm64 أن يبنيها من النسخة المستنسخة باستخدام docker compose up -d.