الصورة تتجمّد والصوت يستمر: كشف توقّفٍ ما زال libvlc يسمّيه Playing
الصورة المتجمّدة ليست حالة خطأ. المحرّك يواصل الإبلاغ عن Playing، والشيء الوحيد الذي يتغيّر هو فارق عدّاد يهبط إلى الصفر.
displayed +25 read +50,000 demux +48,000 audio +40 engine state: Playing
displayed +0 read +50,000 demux +48,000 audio +40 engine state: Playing
عيّنتان متجاورتان بفاصل ثانية واحدة. رقم واحد تغيّر. البايتات ما زالت تصل بالمعدّل نفسه، والمُفكِّك (demuxer) ما زال يستهلكها، ومخازن الصوت ما زالت تبلغ بطاقة الصوت، والمحرّك يبلّغ عن Playing في السطرين وسيظل يبلّغ عنه ما دمت تاركًا القناة مفتوحة. وما على الشاشة هو آخر إطار بلغ مخرج الفيديو. هذه القيم ثوابت تدفعها تجهيزةُ الاختبار عندنا إلى الكاشف، لا تسجيلٌ من قناة حقيقية: تدفّق بسرعة 4 ميغابت/ث يقرأ نحو 500,000 بايت في الثانية، أي بمقدار رتبة كاملة فوق هذه الثوابت، ورقما القراءة والتفكيك فيه يتقاربان أكثر بكثير مما تسمح به فجوة 4 بالمئة مستمرة. الشكل هو ما ينتقل. ولا شيء في هذا الزوج خطأ، ولا يتبعه حدث خطأ، وتلك الفوارق الأربعة هي كل ما يحصل عليه الكاشف من أدلة.
كل ما يلي مقيس على libvlc 3.0.21 عبر LibVLCSharp 3.9.7 في تطبيق سطح المكتب عندنا على Windows. وواجهة الإحصاءات تغيّرت بين الإصدارات الكبرى من libvlc، فاقرأ ما يخص العدّادات على أنه كلام عن 3.x.
libvlc يبلّغ عن Playing والصورة متجمّدة
حالة المحرّك تجيب عن سؤال أضيق مما يقرؤه الناس فيها. Playing تعني أن خيط التشغيل لم يُوقَف ولم يُعلَّق ولم ينتهِ. وهي لا تدّعي أن صورة بلغت الشاشة في الثواني الأربع الأخيرة، ولا تدّعي أن الصورة التي بلغتها كان فيها شيء.
مواصفة المراقب (watchdog) عندنا تنص على الفرضية التي يقوم عليها النظام الفرعي كله: libvlc يبقى في Playing بلا حدّ عبر التجمّد وعبر الموجز الأسود معًا، وEncounteredError محجوزة لإخفاقات الفتح القاسية وإخفاقات البروتوكول، فهي لا تكاد تُطلَق أبدًا في نمطَي الإخفاق اللذين يبلّغ عنهما المشاهدون فعلًا. وتلك الجملة قراءتنا نحن للمحرّك، لا سطر منقول من توثيقه، وهي السبب في أن أيًّا من القواعد أدناه لا يسأل المحرّك عن حاله.
والنتيجة المرصودة هي حجم التصنيف الذي اضطررنا إلى بنائه. تسعة أصناف معرَّفة: انعدام البايتات عند طبقة الوصول، ونهاية تدفق صامتة على قناة حية، وخطأ المحرّك نفسه، وبايتات تصل والمُفكِّك لا ينتج شيئًا، وفيديو متوقف مع صوت حي، وتوقّف الاثنين معًا والبيانات تتدفق، وإطارات سوداء على خط معالجة سليم، وساعة إدخال متوقفة، وإعادة تخزين مؤقت متكررة داخل نافذة متدحرجة.
ثلاثة من تلك التسعة تركب أحداث المحرّك. واحد منها خطأ المحرّك. والاثنان الآخران حدثان عاديان ليسا خطأً على الإطلاق: نهاية تدفق أو توقف صامت على قناة لم يوقفها أحد، وتقرير على مستوى المخزن المؤقت نعدّه ضمن حلقة إعادة تخزين. وستة تُستنتَج من العدّادات، وإن كانت الساعة المتوقفة بينها تتكئ أيضًا على حدث الساعة من المحرّك. والتسعة نفسها تبالغ فيما يستطيع النظام قوله، لأن صنف الساعة المتوقفة لا يُعلَن في أي موضع. هو موجود ليعضّد غيره، وليس له رمز تصنيف خاص به.
بطاقة العطل كانت ترمي ذلك كله وتعرض جملة واحدة لكل حالة، فيقرأ المشاهدُ الذي رفض مصدرُه الاتصالَ والمشاهدُ الذي عجز جهازه عن فكّ ترميز الفيديو الكلماتِ نفسها. وكل صنف صار الآن يقابل رمزًا، والصنف الواحد يعني شيئين مختلفين على جانبَي أول إطار، فالمقابلة تحمل الاثنين. انعدام البايتات داخل فترة سماح الاتصال تدفقٌ لم يبدأ قط. والتوقّف نفسه بعد عشر دقائق تدفقٌ توقّف.
كشف تجمّد الفيديو أربعة فوارق عدّادات في ترتيب ثابت
العيّنة أحد عشر حقلًا: طابع زمني رتيب (monotonic) واحد وعشرة عدّادات تراكمية، تُقرأ من المحرّك في مرور واحد. ثمانية من العشرة تُطرح من العيّنة السابقة. والاثنان الباقيان، أي الصور المتأخرة المُسقطة ومخازن الصوت المُسقطة، لا يُطرحان لشيء؛ جُمعا تحسّبًا لقاعدة تريدهما، ولم ترِدهما قاعدة قط. وأربعة فوارق تحمل الأحكام:
- البايتات المقروءة، عند طبقة الوصول. الصفر يعني أن لا شيء يصل من الشبكة.
- البايتات التي يستهلكها المُفكِّك. قراءة غير صفرية مع تفكيك صفري تعني أن بايتات تصل ولا برنامج فيها.
- الصور المعروضة، عند مخرج الفيديو. هذه إشارة الحياة الأساسية للفيديو، والقسم التالي عن سبب امتناع تصديقها بمجرد النظر.
- مخازن الصوت المشغَّلة، عند مخرج الصوت. هذه هي التي تفصل تجمّد الفيديو عن التجمّد الكامل.
الفيديو المفكوك ترميزه هو عدّاد الإسناد الوحيد ذو الحجية: ينوب حين يتبيّن أن عدّاد الصور المعروضة غير صالح للاستخدام. أما الصوت المفكوك ترميزه فلا دور مقابل له. يُقرأ مرة واحدة، ليثبت وجود تدفق صوتي. والوحدات التالفة وانقطاعات الاستمرارية هما الزوج الباقي، وطفرة في أي منهما تُسلّح نوافذ تجمّد أقصر لبعض الوقت.
الترتيب أهم من أي عتبة بمفردها، والشيفرة تقول السبب في موضعه:
// F5 / F6 presentation freezes. Suppressed while demux starvation is suspected: starvation
// stalls presentation too, and F4's longer window owns the classification (its ladder entry
// differs). F1 needs no such guard; it shares the shorter window and is checked first.
الوصول يُفحَص أولًا، ثم المُفكِّك، ثم العرض أخيرًا. فطبقة وصول ميتة تُجوِّع كل ما بعدها، وصورة تتوقف بعد أربع ثوانٍ من توقف البايتات عَرَضٌ لا تشخيص، والإبلاغ عنها كتجمّد يُدخِل سُلَّم التعافي عند الدرجة الخطأ. ونوافذنا الحالية أربع ثوانٍ من انعدام البايتات، وستٌّ لمُفكِّك مُجوَّع، وأربعٌ لعرض متوقف. هذه قيم ضبط ستتغير أول مرة يقيسها أحد من جديد؛ والترتيب هو ما يحمل المعنى.
الاشتباه يُخزَّن لحظةً لا راية. أربعة وسوم يحمل كل منها الطابع الزمني الذي بدأ عنده شرطه، والحكم وسمٌ تجاوز عمرُه نافذته. ونبضة سليمة واحدة تعيد الوسم إلى null فيزول العمر، فالنافذة طرحٌ لا مسكُ دفاتر عند كل نبضة. وطفرة التلف هي الاستثناء الذي يبيّن الشكل: تخزّن لحظة انتهاء لا لحظة بداية، ولا شيء يمسحها مبكرًا، ولا حكم يقرأ عمرها.
ملاحظة نطاق واحدة قبل أن تبدأ العتبات بالظهور وكأنها عامة. كل هذا معايَر على MPEG-TS الخام. ونحمل نوع المصدر بقيمة ts أو hls، لكن للقياس عن بُعد ولعتبات لكل نوع لم يكتبها أحد بعد. وعلى HLS يجري المُفكِّك التكيفي عمليات جلب خاصة به في المصبّ، فانقسام الوصول والتفكيك على المستوى الأعلى لا يعني ما يقوله هذا القسم إنه يعنيه، ولا عتبة واحدة مما نشحنه خاصة بـ HLS.
لماذا يقول عدّاد الصور إن قناة سليمة لا تكفّ عن التجمّد؟
بعض مسارات مخرج الفيديو ومسارات فكّ الترميز العتادي تبخس الإبلاغ عن الصور المعروضة، ولا سبيل إلى أن تسأل أيَّها تسلك. وتعليقنا في الشيفرة يلقي اللوم في ذلك على وحدة الوصول، وهذا لا يصح: طبقة الوصول تقع قبل التفكيك وفكّ الترميز ولا تمسّ العدّاد الذي تزيده نواة مخرج الفيديو. التعليق خطأ، والسلوك الذي يصفه حقيقي.
لذلك تراقب الآلة في الثلاثين ثانية الأولى بعد أن يبدأ التدفق بالعرض بدل أن تحكم. وفي نهاية النافذة تلتزم بواحد من ثلاثة مواقف عبر خمسة فروع: عدّاد الصور المعروضة تقدّم وهو موثوق؛ أو عدّاد الصور المعروضة ميت لكن الفيديو المفكوك ترميزه تقدّم فصار الإشارة البديلة؛ أو كل عدّادات الفيديو ميتة بينما ساعة الإدخال تتقدم ومخرج فيديو موجود، فالمسار يبخس الإبلاغ وتُطفأ قواعد التجمّد لبقية الجلسة. وفرع رابع يلتقط قناة موسومة بالخطأ، حيث لم يتحرك شيء في جانب الفيديو ولم يظهر مخرج فيديو لكن الصوت يعمل، فتتولى حياةُ الصوت الأمر. وخامس يرتد إلى تصديق العدّاد.
وحارس واحد يعلو ذلك كله. لا حكم تجمّد ممكن قبل أن يتقدم عدّاد فيديو مرة واحدة على الأقل في هذه الجلسة، لأن عدّادًا لم يتحرك قط لا يمكن تمييزه عن عدّاد يبخس الإبلاغ، ولأن حالة الموت الحقيقي من البداية تخص مصنِّف بدء التشغيل، وله أدلة أخرى ومدخل آخر إلى السُّلَّم. خمس وأربعون نبضة بفاصل ثانية، والصوت يعمل، والساعة تتقدم، ومخرج الفيديو قائم، وكل عدّادات الفيديو ساكنة: لا شيء يُعلَن.
والحالة الحدّية التي صاغت المعايرة هي اللوحة الثابتة (slate) منخفضة معدّل الإطارات، أي بطاقة هوية محطة أو مرئيات راديو تتجدّد بنحو نصف صورة في الثانية. وعلى آخذ عيّنات يعمل كل ثانية تقضي تلك القناة نبضةً من كل نبضتين عند صفر صورة معروضة، وهي بصمة التجمّد بالضبط. والمعايرة تكتشف المعدّل فتوسّع نافذة التجمّد من أربع ثوانٍ إلى خمس عشرة، والتوسيع هو النصف المخالف للحدس: الغريزة أن تحكم أسرع على قناة تعرض أقل، وذلك ينتج حكمًا كاذبًا على كل لوحة ثابتة عندك. أما التوقف التام للوحة فما زال يُلتقط، على النافذة الأوسع.
ولتسليح اشتباه لا تستطيع أي قاعدة استهلاكه ثمنٌ، وقد دفعناه. توقّف الصوت إلى جانب فيديو سليم كان يرفع الاشتباه على قناة فيديو، حيث لا حكم يستطيع قراءته أبدًا، فتبقى الجلسة في تلك الحالة طول عمرها: تأخذ العيّنات بضعف السرعة ولا تُصدر أبدًا إشارة الاستقرار التي تمسح سجل التعافي. وكل عطل لاحق في تلك الجلسة يرث سُلَّمًا استُنفدت محاولاته ويمضي مباشرة إلى بطاقة الاستسلام. الاشتباه الذي لا يصير حكمًا ليس تحفظًا. إنه تسريب.
الإيقاف المؤقت يبدو تجمّدًا تمامًا
كل عدّاد في العيّنة يتوقف عند إيقاف مؤقت من المستخدم، بالنمط الذي ينتجه التجمّد الكامل بالضبط.
لا سبيل إلى التمييز بينهما من الأرقام، لذلك لا يُوقَّت الإيقاف المؤقت أصلًا: يحجب الأحكام بلا حدّ، وعشر دقائق من عدّادات متجمّدة خلف إيقاف مؤقت لا تُعلن شيئًا. أما القفز (seek) فيحصل على حجب محدود بخمس ثوانٍ، لأن القفز إما أن يُحسَم أو لا. والتخزين المؤقت يعلّق أحكام العرض ويترك قاعدة الوصول مسلّحة، على أساس أن انعدام البايتات أثناء التخزين المؤقت مصدرٌ ميت لا وصلةٌ بطيئة، لأن الوصلة البطيئة تُظهر بايتات تصل رغم بطئها.
وإعادة الفتح هي الموضع الآخر الذي يعضّ فيه الحساب. عدّادات البايتات بـ 32 بت ونطاقها وسائط واحدة، فتعود إلى الصفر حين تُطبَّق وسائط جديدة وتلتفّ في جلسة طويلة بما يكفي. وفي الحالتين يعود الفارق سالبًا، والفارق السالب على أي من عدّادات الحياة الستة يعيد ضبط خط الأساس ويتخطى التقييم في تلك النبضة بدل أن يبلّغ عن توقف خط المعالجة كله.
كشف الإطار الأسود لا بد له أن ينظر إلى البكسلات
الموجز الأسود يُفكّ ترميزه ويُعرَض ويشغّل الصوت ويسحب البايتات بالمعدّل المعتاد. وكل قاعدة حتى الآن ترى تدفقًا سليمًا. هذا هو المسار الوحيد في الكاشف الذي لا بد له أن ينظر إلى البكسلات، وهو يحتاج إطارًا حقيقيًا مُلتقطًا من مخرج الفيديو بمقاس 96 في 54.
عتبة لمعان (luma) واحدة لا تكفي، والسبب هو المحتوى. والحكم يأخذ محورين وتكرارًا:
- نسبة البكسلات السوداء عند 0.98 فأعلى، حيث يُعدّ البكسل أسود عند لمعان 24 فأدنى.
- الانحراف المعياري للّمعان عند 4.0 فأدنى، وهو ما يعفي المشهد المظلم ذا البنية.
- أربع عيّنات سوداء متتالية، وهو ما لا يستطيع التلاشي استيفاءه.
وحالتا فشل المحتوى كلتاهما مثبَّتة بالاختبارات لا بالجدل. المشهد الليلي، وهو في معظمه شبه أسود مع بنية متناثرة يضيئها القمر، يفشل في المحورين معًا. والمحتوى ذو الأشرطة السوداء يخرج أقل من نصف أسود، لأن الأشرطة أقلية من الإطار حتى في نسبة عرض واسعة.
وفي حجة التلاشي فرع لا ينبغي أن أتخطاه. فلعشر ثوانٍ بعد تأكُّد تعافٍ، تهبط السلسلة المطلوبة من أربع عيّنات إلى اثنتين، أي نحو ثلاث ثوانٍ عند الحدّ الأدنى المضبوط وأطول من ذلك عمليًا، وهي مدة يستطيع تلاشٍ بطيء الصمود فيها. هذه مقايضة مقصودة: الدليل الذي أدان المصدر قبل لحظات ما زال قائمًا، فالمصدر الذي يعود مكسورًا يُدان أسرع، وحارس التلاشي هو ما ندفعه ثمنًا لذلك. و1500 مللي ثانية فترةٌ دنيا لا مباعدة، هي الأخرى. فالمِسبار يُعيد التسليح عليها ثم يُطلق عند أول رسالة تصل، فتخرج المباعدة الفعلية أطول: سبع ثوانٍ ثم تسع، في الجولة أدناه.
ومتى يعمل المِسبار قرارٌ له ثمن ظاهر، والوصف الصادق أضيق مما يوحي به الاسم. يعمل مرة واحدة مسحًا مباشرةً بعد أول عرض للتدفق، ومرة أخرى عند الانتقال إلى الاشتباه حين يكون توقّف فيديو هو ما رفعه. وبمجرد إغلاق المسح الافتتاحي لا يعمل مرة أخرى ما دامت الجلسة سليمة. لذلك فالسواد في منتصف التشغيل على تدفق سليم فيما عدا ذلك لا ينتج حكمًا، بصورة قابلة للتكرار، وقد اخترنا ألا نصلحه: التقاطه يكلّف لقطة في كل جلسة من كل تدفق، لكشف ما يراه المشاهد فورًا، والتبديل إلى قناة أخرى ثم العودة يعيد تشغيل المسح على أي حال.
وقيد ثانٍ لا أستطيع إغلاقه بدليل. الالتقاط يمر عبر استدعاء اللقطة الخاص بالمحرّك، وقراءة سطح مفكوك الترميز عتاديًا موضع معروف جدًا يعيد فيه مشغّلٌ إطارًا فارغًا لأسباب لا علاقة لها بما على الشاشة. اللقطة التي لا تصل أبدًا نتعامل معها: هي دليل على مخرج فيديو عالق، يعضّد حكم عدّاد ولا يعلن وحده أبدًا. أما التي تصل وتكون سوداء خطأً فلا نتعامل معها، وليس عندي قياس لكم مرة يحدث ذلك على مسارنا.
وأكثر ما لا أرتاح إليه هو ما يقيسه المِسبار ثم يرميه. كل عيّنة تحسب تجزئة متوسط بـ 64 بت لمستوى اللمعان ومتوسطَ لمعان، وكلاهما يسافر في الرسالة، ومحرّك القواعد لا يقرأ أيًا منهما. والتدفق المتجمّد على إطار ساكن مضيء، على قناة عايرنا عدّاد صورها على أنه غير موثوق، هو ما كانت تجزئتان متساويتان عبر عيّنتين متباعدتين ستلتقطانه. نحن لا نقارنهما، وهل الحالة شائعة بما يكفي لتستحق قاعدة سؤالٌ لا نستطيع الإجابة عنه، لسبب يبلغه القسمان الأخيران. وتفصيل واحد من التجزئة فاجأني بما يكفي لأبقيه: الإطار المسطّح تخرج تجزئته آحادًا كلها، كل بت من الـ 64 مضبوط، لأن متوسط كل خلية يقع عند المتوسط العام بالضبط والمقارنة أكبر من أو يساوي. فالأبيض الصلد والأسود الصلد تجزئتهما واحدة.
أسوأ خلل هنا كان تجمّدًا لا يحدث
قراءة إحصاءات المشغّل ليست استدعاءً آمنًا. كل سطر منها يفكّ الإسناد إلى مقابض أصلية يحرّرها المحرّك حين يبدّل المستخدم القناة، أو يغادر الصفحة، أو حين تُحيل إعادةُ بناء سلسلة التبديل المشغّلَ إلى التقاعد، وآخذ العيّنات يعمل على مهمته الخاصة. وخسارة ذلك السباق لا ترفع شيئًا يستطيع catch امتصاصه: libvlc يعطب بانتهاك وصول وتختفي العملية دون شيء في السجل. لذلك يعمل آخذ العيّنات خلف البوابة نفسها التي يستخدمها مجمّع المشغّلات.
وداخل تلك البوابة، أنتج قرار تخزين مؤقت يبدو صحيحًا بمعزل عن غيره أسوأ خلل شحنه هذا النظام الفرعي. آخذ العيّنات كان يمسك بغلاف الوسائط بدل أن يجلبه في كل نبضة، لأن جلبه مرة في الثانية بدا إهدارًا. وغلاف الوسائط في LibVLCSharp يأخذ مرجعه الأصلي الخاص، فظل الغلاف المخزَّن صالحًا بعد أن بدّل المحرّك الوسائط من تحته على خيط من المجمّع. وظل يجيب. ولم يرمِ استثناءً قط. وأعاد عدّادات الوسائط الميتة، وهي لا تتحرك، وذلك توقف بايتات كامل بكل قاعدة في الآلة.
والعَرَض كان قناة سليمة يُبلَّغ عنها كشبكة ميتة. والثمن كان تبديل المصدر بعيدًا عن تدفق يعمل، مرصودًا مباشرةً أثناء جولة قبول ميداني في 2026-07-24 عند الساعة 23:24. والتعليق الذي يعلو الإصلاح الآن يقول ذلك صراحةً:
// Acquire and dispose the wrapper on every tick. A cached wrapper holds its own native
// retain, so after a pool-thread media swap it would keep returning the DEAD media's
// frozen counters without ever throwing, which reads as a net stall on a healthy stream
// (observed live: [...] 2026-07-24 23:24). One retain/release per second is negligible.
الكاشف الذي يقرأ مدخلات بائتة لا يفشل بهدوء. ينتج الحكم نفسه، بالثقة نفسها، عن قناة تعمل تمامًا، ثم يتصرف بناءً عليه. كل إخفاق آخر في مراقب يكلّفك توقفًا لم تلتقطه. وهذا يكلّفك توقفًا اخترعته أنت، والمشاهد يرى التعافي.
هذا ما تُصدره اثنتا عشرة ثانية من التجمّد فعلًا
أدناه جلسة مكتوبة مسبقًا مُرِّرت على محرّك القواعد الذي نشحنه فوق منصّة الاختبار القائمة، اثنتا عشرة نبضة بفاصل ثانية. نُفِّذت تنفيذًا ولم تُتتبَّع يدويًا، وحُذفت الأداة التي قادتها بعدها. اقرأ الأرقام على أنها تجهيزات اختبار. وكل طلب لقطة أُجيب بعده بـ 300 مللي ثانية، كما يفعل مضيف حقيقي، لأن ميزانية اللقطة ثانية واحدة.
الجلسة تفتح، وتبلّغ عن Playing، وتبلّغ عن مخرج فيديو واحد. ثم:
| t | dRead | dDemux | dDisplayed | dAudio | الحالة بعده | التأثيرات المُصدَرة |
|---|---|---|---|---|---|---|
| 1 ث | +50,000 | +48,000 | +25 | +40 | Starting | لا شيء |
| 2 ث | +50,000 | +48,000 | +25 | +40 | Healthy | StateChanged, SetSampleRate 1 Hz, RequestSnapshot |
| 3 ث | +50,000 | +48,000 | +25 | +40 | Healthy | لا شيء |
| 4 ث | +50,000 | +48,000 | +25 | +40 | Healthy | لا شيء |
| 5 ث | +50,000 | +48,000 | +25 | +40 | Healthy | لا شيء |
| 6 ث | +50,000 | +48,000 | +25 | +40 | Healthy | لا شيء |
| 7 ث | +50,000 | +48,000 | +0 | +40 | Suspect | StateChanged, SetSampleRate 2 Hz, RequestSnapshot |
| 8 ث | +50,000 | +48,000 | +0 | +40 | Suspect | لا شيء |
| 9 ث | +50,000 | +48,000 | +0 | +40 | Suspect | RequestSnapshot |
| 10 ث | +50,000 | +48,000 | +0 | +40 | Suspect | لا شيء |
| 11 ث | +50,000 | +48,000 | +0 | +40 | Recovering | StateChanged, FaultDeclared VideoFreeze, العمر 4000 مللي ثانية |
| 12 ث | +50,000 | +48,000 | +0 | +40 | Recovering | لا شيء |
النبضة 1 لا تنتج شيئًا، لأن عيّنة واحدة ليست فارقًا؛ بل تصير خط الأساس. والنبضة 2 هي أول زوج يُقيَّم، فيثبت تقدّم العرض وتغادر الآلة Starting. وهي تعيد أيضًا ذكر معدّل أخذ عيّنات كانت تستخدمه أصلًا، وذلك تكرار غير ضار. وطلب اللقطة على ذلك السطر هو المسح الافتتاحي، وقد أجابه المضيف بمحتوى حقيقي، 6 بالمئة من البكسلات سوداء بانحراف معياري قدره 41، فأُغلق المسح.
النبضات 3 إلى 6 لا تُصدر شيئًا البتة. وتلك هي الحالة المستقرة المقصودة: التدفق السليم لا ينتج تأثيرات، فالسجل الفارغ هو حالة النجاح.
النبضة 7 هي موضع توقف الصورة. وسم توقّف الفيديو يُختم بـ 7 ث، وتنتقل الآلة إلى Suspect، ويتضاعف آخذ العيّنات إلى 2 هرتز، ويُسلَّح المِسبار فيطلب إطارًا. ولا شيء يُعلَن، لأن وسمًا عمره صفر ليس حكمًا. والنبضتان 8 و10 هما النافذة وهي تجري، والنبضة 9 هي المِسبار يعيد أخذ العيّنة على إيقاعه الخاص، بعد ثانيتين من الطلب الأخير لا بعد 1500 مللي ثانية المضبوطة، لأنه يُطلق عند أول رسالة بعد موعد الاستحقاق. والبايتات والصوت لا يتعثران خلال ذلك كله: هذه الجلسة تبدو مثالية لكل قاعدة إلا واحدة.
النبضة 11 هي الحكم. وسم توقّف الفيديو يبلغ أربع ثوانٍ، والمُفكِّك ليس محل اشتباه، والصوت تقدّم أثناء التوقف وما زال يتقدم، فهو تجمّد فيديو لا تجمّد كامل، وعمر الشذوذ المبلَّغ إلى المنفّذ 4000 مللي ثانية. شغّل السيناريو نفسه مع توقف الصوت عند النبضة 7 أيضًا فيُعلن السطر نفسه تجمّدًا كاملًا. وشغّله مرة أخرى مع انتهاء مهلة اللقطة بدل أن تعيد محتوى، فالحكم لا يتغير، لكن سلسلة سببه تصير video stalled while audio keeps playing (snapshot timeout observed) (أي أن الفيديو توقف والصوت مستمر، مع رصد انتهاء مهلة اللقطة): المخرج العالق عضّد، ولم يقرر.
النبضة 12 لا تُصدر شيئًا لأن الآلة سلّمت الأمر. ومجموعة اختبارات Playback حول هذه الشيفرة 108 اختبارات، كلها ناجحة وقت كتابة هذا الكلام.
الحكم يشتري خمس درجات، وواحدة منها غير مبنية
الحكم مدخل إلى سُلَّم، لا جواب. وخمس درجات موجودة: دفعة إيقاف مؤقت ثم تشغيل، لا ينالها إلا التجمّد الكامل؛ وإعادة تطبيق كاملة للعنوان نفسه؛ وتبديل إلى المرشّح التالي؛ وإعادة بناء المشغّل نفسه؛ ثم الاستسلام. والعطل الواقع خلال خمس عشرة ثانية من ضبط القناة يدخل عند التبديل لا عند إعادة التطبيق، لأن مصدرًا لم يثبت نفسه قط لا يستحق إعادة محاولة العنوان الذي أخفق للتو. أما الجلسات الناضجة فتُبقي إعادة التطبيق أولًا، حتى لا تكلّف عثرةٌ بعد عشر دقائق المشاهدَ تبديلَ مصدر. وهناك محاولة إعادة تشغيل واحدة بالضبط قبل التصعيد، لأن بيانات الميدان أظهرت أن الثانية لا تنفع أبدًا.
ودرجة إعادة البناء خيالٌ صادق في الوقت الحالي. ليس لها عملية أساسية في المضيف، وهي معطّلة في الإعدادات التي نشحنها، والشرط الوحيد الذي يوجّه إليها، أي إيقاف انتهت مهلته وترك الجلسة الأصلية عالقة، ينهي حاليًا السُّلَّم الأمامي ويحطّ على الاستسلام بدلًا منها. وهذا تحفظ صحيح، وهو أيضًا فجوة، والشيفرة تقول ذلك حيث يقع.
وبين المحاولات يكون التراجع أسّيًا مع محاولة أولى مجانية: الأساس مضروبًا في اثنين مرفوعًا إلى رقم المحاولة ناقص واحد، مع سقف، ثم ارتجاف (jitter) بنسبة تصل إلى عشرين بالمئة في الاتجاهين. وعمليًا: أطلق فورًا، ثم بعد ثانية، ثم ثلاث، ثم سبع، وهكذا حتى السقف. والارتجاف يضرب بعد السقف لا قبله، فالحد الأقصى الحقيقي ست وثلاثون ثانية، لا الثلاثون التي يوحي بها السقف. شيء صغير، لكنه من صنف الأشياء الصغيرة التي تجعل رسمًا بيانيًا يبدو خاطئًا عند 3 صباحًا.
البكسلات تبقى على الجهاز الذي التقطها
المِسبار يكتب ملفَّي PNG متناوبين في مجلد البيانات المحلي الخاص بالتطبيق ويكتب فوقهما في مكانهما. لا شيء يرفعهما ولا شيء يرسلهما إلى أي مكان؛ الإطار يعيش ما يكفي لاختزاله إلى رقمين وتجزئة، ثم يكتب فوقه الذي يليه. وأحكام الأعطال تذهب إلى السجل المحلي على الجهاز نفسه، والقناة تُعرَّف هناك بأول ستة عشر محرفًا ست عشريًا من SHA-256، لا بعنوان قط.
وبسبب ذلك الجزء الأخير لا أستطيع أن أخبرك كم هي شائعة حالة الإطار المتجمّد المضيء. أشكال أحداث القياس عن بُعد مكتوبة ومُنمَّطة، وتسمّي رافعًا دُفعيًا في تعليقها التوثيقي، وذلك الرافع غير موجود. ثلاثة أنواع سجلات معلَنة ولا يُنشأ أي منها في أي موضع من الشيفرة. وكل حكم في هذه المقالة على أي أنماط الإخفاق تهم مستمدٌّ من جولة قبول ميداني واحدة، ومنصّة حقن أعطال، ومجموعة اختبارات، لا من أسطول. وذلك حدّ حقيقي على الثقة بكل ما سبق.
وإن كنت تفضّل استخدام الكاشف على القراءة عنه، فهو يُشحَن مفعّلًا افتراضيًا في تطبيق My TV Player على Windows، ويمكنك إنشاء حساب مجاني لتجربته. وحين يؤدي عمله لن تراه: ستتلعثم الصورة أربع ثوانٍ ثم تعود، ولن يكون قد ظهر على الشاشة شيء يخبرك بأن حكمًا صدر أصلًا.
ما قياس هذه المادة37 ادعاءات، كل منها مزود بالأدلة التي تقف خلفه
| المطالبة | الأدلة | عد |
|---|---|---|
| كل ما في هذه المقالة مقيس على libvlc 3.0.21 عبر LibVLCSharp 3.9.7 في نسخة Windows. وواجهة الإحصاءات تختلف في libvlc 4.x، لذا فادعاءات العدّادات محصورة بـ 3.x.clients/windows/docs/PLAYBACK-WATCHDOG-PLAN.md:7 pins the head to LibVLCSharp 3.9.7 / libvlc 3.0.21; docs/research/MTP-PlaybackWatchdog-Spec-v1.0.md:5 scopes the spec to libvlc 3.x with forward-compatible notes for 4.x. | المواصفات | لا ينطبق |
| libvlc يبقى في Playing بلا حدّ عبر التجمّد وعبر الموجز الأسود معًا، وEncounteredError محجوزة لإخفاقات الفتح القاسية وإخفاقات البروتوكول، فهي لا تكاد تُطلَق أبدًا في نمطَي الإخفاق الغالبين في البث الحي.docs/research/MTP-PlaybackWatchdog-Spec-v1.0.md:18 (within the cited 15-19 block), header checked, no confidentiality marking. | المواصفات | لا ينطبق |
| تسعة أصناف أعطال معرَّفة، من F1 إلى F9، وثمانية منها فقط يمكن أن تُعلَن يومًا. أما F8 ClockStall فلا يُعلَن في أي موضع وليس له رمز تصنيف خاص به. | n = 9 | 22/08/2026 |
| ثلاثة من الأصناف التسعة تركب أحداث المحرّك. F3 هو خطأ المحرّك نفسه، وF2 يُعلَن عن EndReached أو عن Stopped صامت، وF9 يُعلَن عن Buffering. والبقية تُستنتَج من العدّادات. | n = 3 | 22/08/2026 |
| بطاقة العطل كانت تعرض جملة واحدة لكل صنف، وكل صنف صار الآن يقابل رمز تصنيف يتوقف أيضًا على وقوع العطل قبل أول إطار أو بعده. | n = 1 | 22/08/2026 |
| عيّنة الإحصاءات أحد عشر حقلًا: طابع زمني رتيب واحد وعشرة عدّادات تراكمية. ثمانية منها تُطرح؛ أما LostPictures وLostAudioBuffers فلا تطرحهما أي قاعدة قط. | n = 11 | 22/08/2026 |
| الفيديو وحده له عدّاد بديل. DecodedVideo ينوب حين تكون DisplayedPictures غير صالحة للاستخدام؛ أما DecodedAudio فلا تُستخدم إلا لإثبات وجود تدفق صوتي، ولا تقرؤها أي قاعدة إشارةَ حياة قط. | n = 1 | 22/08/2026 |
| الأحكام تُفحَص بالوصول أولًا، ثم المُفكِّك، ثم العرض أخيرًا، وقواعد التجمّد تتنحّى ما دام تجويع المُفكِّك محل اشتباه. | n = 1 | 22/08/2026 |
| الضبط الحالي: 4 ث من انعدام البايتات لـ F1، و6 ث لـ F4، و4 ث لـ F5 وF6، و15 ث للوحة ثابتة منخفضة معدّل الإطارات. | n = 1 | 22/08/2026 |
| أربعة وسوم «منذ» تقبل القيمة null تحمل اللحظة التي بدأ عندها كل شرط، ونبضة سليمة واحدة تمسحها. وإشارة خامسة، هي طفرة التلف وانقطاع الاستمرارية، تعمل بالعكس: تخزّن لحظة انتهاء وتقصّر نوافذ التجمّد ما دامت مسلّحة. | n = 4 | 22/08/2026 |
| نموذج العدّادات معايَر على MPEG-TS الخام. ونوع المصدر يُحمَل بقيمة "ts" أو "hls" للقياس عن بُعد ولعتبات مستقبلية لكل نوع، ولا عتبة في الخيارات خاصة بـ HLS اليوم. | n = 1 | 22/08/2026 |
| عدّادات البايتات في libvlc 3.x بـ 32 بت ولكل وسائط على حدة، فتعود إلى الصفر عند إعادة الفتح وتلتفّ في الجلسات الطويلة. وفي الحالتين يصير الفارق سالبًا.WatchdogStateMachine.cs:274-275 states both causes in place ("libvlc zeroes stats on reopen and int counters can wrap"). libvlc 3.x declares i_read_bytes and i_demux_read_bytes as int in libvlc_media_stats_t. | المواصفات | لا ينطبق |
| الفارق السالب على أي من عدّادات الحياة الستة يعيد ضبط خط الأساس ويتخطى التقييم في تلك النبضة. وفوارق الوحدات التالفة وانقطاع الاستمرارية ليست جزءًا من ذلك الحارس. | n = 6 | 22/08/2026 |
| عدّاد الصور يُعايَر على نافذة 30 ث إلى Trusted أو FallbackDecodedVideo أو Untrusted عبر خمسة فروع، وأحكام التجمّد تشترط أيضًا أن يكون عدّاد فيديو قد تقدّم مرة واحدة على الأقل في هذه الجلسة. | n = 5 | 22/08/2026 |
| المسارات التي تبخس الإبلاغ عن الصور المعروضة هي مسارات vout ومسارات فكّ الترميز العتادي، لا وحدات الوصول.WatchdogStateMachine.cs:441 says "this access/vout combo underreports". The access module sits upstream of demux and decode and cannot influence i_displayed_pictures, which the vout core increments. | المواصفات | لا ينطبق |
| 45 نبضة بفاصل ثانية مع صوت يعمل وساعة تتقدم ومخرج فيديو قائم، لكن كل عدّادات الفيديو ميتة، لا تُعلن أي عطل وتحطّ على Untrusted. | n = 45 | 22/08/2026 |
| لوحة ثابتة قيست عند نحو نصف صورة في الثانية توسّع نافذة التجمّد إلى 15 ث بدل أن تقصّرها، والتوقف التام للوحة ما زال يُكتشَف على النافذة الموسَّعة. | n = 1 | 22/08/2026 |
| توقّف الصوت إلى جانب فيديو سليم كان يستطيع رفع الاشتباه ولا يستطيع بلوغ حكم أبدًا، فتبقى الجلسة في SUSPECT طول عمرها عند معدّل أخذ العيّنات الأسرع، ولا تُصدر إشارة الاستقرار قط، وتترك سجل التعافي غير ممسوح، فترث الأعطال اللاحقة سُلَّمًا مستنفَدًا وتمضي مباشرة إلى بطاقة الاستسلام. | n = 1 | 22/08/2026 |
| الإيقاف المؤقت من المستخدم يحجب العدّادات المتجمّدة بلا حدّ؛ والقفز يحجبها 5 ث فقط؛ والتخزين المؤقت يعلّق أحكام العرض بينما تظل قاعدة انعدام البايتات تعمل. | n = 3 | 22/08/2026 |
| ضبط السواد الحالي: البكسل يُعدّ أسود عند لمعان 24 فأدنى، والعيّنة سوداء عند 98 بالمئة من البكسلات سوداء بانحراف معياري للّمعان قدره 4.0 أو أقل، والحكم يحتاج 4 عيّنات متتالية، مُلتقطة بمقاس 96 في 54. | n = 1 | 22/08/2026 |
| لمدة 10 ث بعد تأكّد محاولة تعافٍ، تهبط العيّنات السوداء المتتالية المطلوبة إلى 2. | n = 1 | 22/08/2026 |
| 1500 مللي ثانية حدٌّ أدنى لإعادة التسليح لا مباعدة. المِسبار يُطلق عند أول رسالة تصل عند موعد الاستحقاق أو بعده، فتخرج المباعدة الفعلية أطول: 2 ث حيث تصل الرسائل بمعدّل 1 هرتز، كما في الجولة المسجَّلة هنا. | n = 1 | 22/08/2026 |
| المشهد الليلي يفشل في محورَي السواد كليهما، والمحتوى ذو الأشرطة السوداء أقل من نصف أسود؛ وكلاهما مثبَّت بالاختبارات على أنه ليس أسود. | n = 2 | 22/08/2026 |
| المِسبار يعمل مسحًا وحيدًا مباشرةً بعد أول عرض للتدفق، ومرة أخرى عند الانتقال إلى SUSPECT حين يكون توقّف فيديو هو ما رفع الاشتباه. وبمجرد إغلاق المسح الافتتاحي لا يعمل مرة أخرى أثناء الصحة المستقرة، فالسواد في منتصف التشغيل على تدفق سليم فيما عدا ذلك لا ينتج حكمًا. | n = 2 | 22/08/2026 |
| المِسبار يلتقط عبر استدعاء اللقطة الخاص بالمحرّك، وقراءة لقطة من سطح مفكوك الترميز عتاديًا موضع معروف يعيد فيه مشغّلٌ إطارًا فارغًا لأسباب لا علاقة لها بالمحتوى. نحن نتعامل مع اللقطة الفاشلة كدليل معضِّد؛ وليس عندنا قياس لكم مرة تعود قراءة ناجحة سوداء خطأً.PlaybackEngine.Watchdog.cs:253 calls MediaPlayer.TakeSnapshot (libvlc_video_take_snapshot); the timeout path is handled at WatchdogStateMachine.cs:867-873. | المواصفات | لا ينطبق |
| تجزئة متوسط بـ 64 بت ومتوسط لمعان يُحسبان في كل عيّنة مِسبار ويسافران معًا في الرسالة، ومحرّك القواعد لا يقرأ أيًا منهما. | n = 1 | 22/08/2026 |
| الإطار المسطّح ينتج تجزئة متوسط كل بتاتها مضبوطة، لأن كل خلية تقع عند المتوسط العام والمقارنة أكبر من أو يساوي. لذلك فالأبيض الصلد والأسود الصلد لا تفرّق بينهما هذه التجزئة. | n = 1 | 22/08/2026 |
| الاستخدام بعد التحرير داخل libvlc يظهر انتهاكَ وصول أصليًا، لا استثناء CLR، فلا يعمل أي التقاط مُدار أبدًا.clients/windows/src/MyTvPlayer.Shared/Media/LiveTvPreviewMediaHelper.LibVlc.cs:165-180, reproduced 2026-08-19, 0xC0000005 in libvlc_media_player_release with a concurrent stats read. | المواصفات | لا ينطبق |
| غلاف Media المخزَّن في LibVLCSharp يحتفظ بمرجعه الأصلي الخاص، فبعد تبديل وسائط على خيط من المجمّع يظل يعيد عدّادات الوسائط الميتة المتجمّدة دون أن يرمي استثناءً قط.libvlc 3.x doxygen for libvlc_media_player_get_media: the returned media is reference counted and the caller must release it, which is why a cached LibVLCSharp Media keeps the old media alive and keeps answering. Recorded verbatim at clients/windows/src/MyTvPlayer.Shared/Media/Watchdog/PlaybackWatchdog.cs:322-325. | المواصفات | لا ينطبق |
| غلاف وسائط مخزَّن جعل آخذ العيّنات يبلّغ عن عدّادات وسائط ميتة متجمّدة على تدفق سليم، فقُرئت توقّفَ شبكة. رُصد ذلك مباشرةً أثناء جولة قبول ميداني في 2026-07-24 عند الساعة 23:24. وأُصلح بالحصول على الغلاف وتحريره في كل نبضة. | n = 1 | 24/07/2026 |
| اثنتا عشرة نبضة مكتوبة مسبقًا بفاصل ثانية تبلغ HEALTHY عند 2 ث، وSUSPECT عند 7 ث، وتُعلن F5 VideoFreeze عند 11 ث بعمر شذوذ قدره 4000 مللي ثانية. والسيناريو نفسه مع توقف عدّاد الصوت أيضًا يُعلن F6 TotalFreeze عند النبضة نفسها، وجولة ثالثة تنتهي فيها مهلة لقطات SUSPECT تُعلن F5 مع سلسلة سبب موسَّعة إلى "video stalled while audio keeps playing (snapshot timeout observed)". | n = 12 | 22/08/2026 |
| مجموعة اختبارات Playback تشغّل 108 اختبارات بـ 0 إخفاقات. | n = 108 | 22/08/2026 |
| خمس درجات: دفعة إيقاف مؤقت ثم تشغيل، للتجمّد الكامل وحده، وإعادة تطبيق كاملة، وتبديل إلى المرشّح التالي، وإعادة بناء المشغّل، ثم الاستسلام. ومحاولة إعادة تشغيل واحدة قبل التصعيد، والعطل الواقع خلال 15 ث من ضبط القناة يدخل عند التبديل بدل إعادة التطبيق. | n = 5 | 22/08/2026 |
| درجة إعادة بناء المشغّل ليس لها بعدُ عملية أساسية في المضيف، وهي معطّلة في الإعدادات التي نشحنها، فحادثة انتهاء مهلة الإيقاف تنهي حاليًا السُّلَّم الأمامي وتمضي إلى الاستسلام. | n = 1 | 22/08/2026 |
| المحاولة k تنتظر min(الأساس مضروبًا في (2^k - 1)، السقف) مع ارتجاف متناظر، فتنطلق المحاولة الأولى فورًا ويكون النمو من الثانية 1 ث ثم 3 ث ثم 7 ث حتى السقف. والارتجاف يُطبَّق بعد السقف، فتتوزّع فترات الانتظار عند السقف توزّعًا متناظرًا حول 30 ث، والحد الأقصى الحقيقي 36 ث. | n = 1 | 22/08/2026 |
| المِسبار يكتب ملفَّي PNG متناوبين في مجلد البيانات المحلي الخاص بالتطبيق ويكتب فوقهما. وأحداث الأعطال تذهب إلى السجل المحلي، حيث تُعرَّف القناة بأول 16 محرفًا ست عشريًا من SHA-256 ولا تُعرَّف بعنوان قط. | n = 1 | 22/08/2026 |
| أنواع سجلات القياس عن بُعد معلَنة ولا يُنشأ أي منها قط، والرافع الدُّفعي المذكور في تعليقها التوثيقي غير موجود. | n = 3 | 22/08/2026 |