Crispy-Tivi: بث IPTV ووسائط متعددة المنصات
تطبيق Flutter لـM3U وXtream Codes وEPG وVOD والتلفزيون المباشر - مع Chromecast وAirPlay ومزامنة سحابية مدمجة.
هذا هو التطبيق الذي يعمل في غرفة معيشتي كل مساء. منزلي يبث IPTV. معظم التطبيقات في هذه المساحة إما برمجيات متروكة بتجربة مستخدم من 2014، أو منتجات مدفوعة خلف جدران اشتراك مثيرة للريبة، أو هشّة لدرجة أن تغيير رابط مزوّد يعطّل كل شيء لأسبوع. زهقت من التلاتة.
Crispy-Tivi هو التطبيق الذي بنيته لأنني أردت شيئاً أستمتع باستخدامه - مش بس أستحمله.
لماذا يوجد
IPTV نظام بيئي فوضوي فعلاً. لا معيار. المزوّدون يقدمون قوائم M3U وواجهات برمجية لـXtream Codes أو كليهما، ويغيّرون نقاط النهاية دون إشعار. بيانات EPG تأتي في صيغ XML متعددة بجودة متباينة. كتالوجات VOD قد تحوي عشرين ألف مدخل أو عشرين فقط. العملاء الذين يبدون جيدين على Android ينهارون على الأجهزة اللوحية، ودعم سطح المكتب عادةً مفكّرة به في آخر اللحظة إن وُجد أصلاً.
التطبيقات الموجودة تتعامل مع هذا التعقيد كتكلفة ثابتة: تبديل قنوات بطيء، لا بحث، لا “متابعة المشاهدة”، لا تجميع منطقي. الطبقة المدفوعة من كبار مشغّلي IPTV تكلّف مالاً ولا تزال تشعر أنها أول مشروع Flutter لشخص ما. أقول هذا كملاحظة تقنية لا كطعن - مشاريع Flutter الأولى هي كيف تتعلم Flutter.
ما أردته هو تجربة مستخدم بجودة Plex لمصدر IPTV أتحكم فيه، مع صفر اعتماد على السحابة. إن كان إنترنتي يعمل ومزوّدي يعمل، يجب أن يعمل التطبيق. نقطة.
كيف يعمل
التطبيق Flutter، مما يمنحني قاعدة كود واحدة تعمل على Android وiOS وmacOS وWindows وLinux. يبدو هذا الاختيار الواضح لتطبيق وسائط في 2025، وهو كذلك - تحسين تصيير Flutter جيد فعلاً الآن وقصة platform channel ناضجة بما يكفي لأستطيع النزول إلى Rust للمسارات الحرجة من حيث الأداء من غير ما دماغي تسيح.
طبقة Rust تتعامل مع تكامل FFmpeg. تحليل HLS وDASH، وتلميحات الترميز، وإدارة المخزن المؤقت - هذه أشياء سيخذلك فيها garbage collector لغة Dart في اللحظة الخطأ، عادةً خلال تبديل قناة مباشرة. Rust عبر FFI لـFlutter يمنحني تخطيطاً متوقعاً للذاكرة ولا توقفات مفاجئة.
دعم البروتوكول يشمل صيغ IPTV الرئيسية: قوائم M3U وM3U-plus (مع تحليل مخصص لـgroup-title)، وواجهة Xtream Codes API (تسجيل دخول، بث، VOD، مسلسلات)، وXMLTV EPG. للبث، طبّقت Chromecast وAirPlay محلياً - المشغّل يسلّم رابط البث وجلسة البث تتعامل مع الباقي. المزامنة السحابية تخزّن المفضلة وتقدم المشاهدة والمجموعات المخصصة؛ طبقة المزامنة تحلّ التعارضات بـlast-write-wins لكل عنصر، وهو خطأ من الناحية النظرية وجيد في الواقع لهذه الحالة.
محرّك EPG كان أعقد قطعة. ملفات XMLTV يمكن أن تكون مئات الميغابايت غير مضغوطة، ولا يمكنك تحليلها على الخيط الرئيسي إلا لو بتحب حوارات ANR. مسار معالجة EPG يعمل في Dart isolate، ويحلّل إلى صيغة ثنائية مدمجة، ويسلّم النتيجة إلى طبقة UI كتدفق من tuples قناة-فترة زمنية. عرض الشبكة يُفعِّل الظاهرية بشكل مفرط - فقط النافذة المرئية وشاشة واحدة من lookahead في الذاكرة في أي وقت.
ما الذي يثير الاهتمام
مجتمع المستضيفين الذاتيين وجد المستودع قبل أن أكتب حتى README صحيحاً، وده يفرح ويوتر في نفس الوقت. الموجة الأولى من المشكلات كانت تقريباً كلياً عن حالات حافة M3U - ترميز الحروف، وهروب group-title غير القياسي، وقوائم بعشرة آلاف قناة حيث من المفترض أن يُصفّي العميل في الوقت الفعلي. تعلمت عن فوضى بيانات M3U الواقعية في أسبوعين من المشكلات أكثر مما تراكم لديّ في سنوات من استخدام هذه الخدمات كمستهلك.
طلب الميزة الذي فاجأني أكثر: دعم المسلسلات. كنت أفكر في IPTV بالأساس كتلفزيون مباشر زائد أفلام VOD، لكن شريحة كبيرة من المستخدمين يحصلون على محتواهم المسلسل بهذه الطريقة أيضاً، وواجهة برمجية لمسلسلات Xtream Codes مختلفة اختلافاً جوهرياً عن واجهة VOD - بنية نقطة نهاية مختلفة وحقول metadata مختلفة ودلالات استمرارية مختلفة. إضافتها بشكل صحيح يعني إعادة التفكير في نموذج المحتوى من “بث أو فيلم” إلى “بث أو فيلم أو مسلسل بحلقات”، وهو ما تتالى عبر المشغّل وفهرس البحث ومتتبع المشاهدة.
ثمانية نجوم على GitHub لا تبدو كثيراً. لكن كل نجمة جاءت مع محادثة حقيقية. الناس الذين يستخدمون هذا التطبيق مستضيفون ذاتيون منغمسون يديرون stacks وسائطهم الخاصة، ولديهم آراء محددة جداً. هذا هو أفضل نوع من تعليقات المستخدمين.
ما الذي كنت سأغيّره
طبقة المزامنة هي أقدم وأسوأ جزء في قاعدة الكود. Last-write-wins افتراضي جيد، لكنه ينهار حين يكون نفس المستخدم يشغّل التطبيق على هاتف وجهاز لوحي ويشاهد أشياء مختلفة على كل منهما. أريد CRDT حقيقية لتقدم المشاهدة - المشكلة محلولة وأنا فقط لم أنقل التطبيق بعد.
حدّ FFI مع Rust أيضاً أكثر يدوية مما أريد. أدير دورة حياة الكائن عبر الحدّ يدوياً، وهو صحيح لكن مملّ للمراجعة. flutter_rust_bridge تحسّن بشكل ملحوظ منذ كتبت التكامل الأولي، وأريد الترحيل إليه بشكل صحيح لكي تتعامل الـbindings المُولَّدة مع ثوابت الأمان التي أُطبّقها الآن بالاتفاق.
وبصراحة؟ الواجهة تحتاج مراجعة تصميم. تجربة المستخدم جيدة - التدفق يعمل، زمن استجابة تبديل القنوات صلب، شبكة EPG مقروءة. لكن التصميم المرئي “مهندس صنع هذا”، وأنا بقول كده وأنا عارف إن الكلام عليّ. أعرف ما يحتاجه. لم أجلس بعد للقيام به.