Canal Rodéo
Note technique aux actionnaires

Diagnostic des coupures de diffusion

Où se situe réellement le problème, preuves à l'appui — et ce que ce n'est pas.

Date : 17 juin 2026 Plateforme : Cloudflare Stream + lecteur HLS.js Vérifications : en direct sur l'API Cloudflare + lecture du code + base Neon

1Résumé exécutif

Conclusion

Les coupures ne viennent pas du lecteur vidéo (le code de la plateforme). Elles se produisent en amont du lecteur, au moment où le signal monte d'OBS vers Cloudflare (l'« ingest »). Deux causes réelles et vérifiables :

  1. Le stockage Cloudflare Stream qui se remplit en silence jusqu'au plafond de 1000 minutes. Une fois plein, Cloudflare ferme la connexion d'OBS après ~8 secondes et bloque tout nouveau direct. C'est un blocage côté serveur qui imite à s'y méprendre un bug de réseau ou de lecteur.
  2. Le lien Internet Starlink du site du rodéo (l'« uplink »). Quand le lien physique faiblit, le signal coupe — et cela couperait sur n'importe quelle plateforme (Facebook, YouTube inclus).

Le lecteur, lui, est non seulement sain mais plus résilient que la moyenne : il se reconnecte automatiquement dès que la source revient. Il subit et affiche la coupure ; il ne cause pas les blackouts récurrents.

Nuance honnête : un audit contradictoire a tout de même trouvé un vrai bug client — sur le lecteur de la page d'accueil, une reconnexion toutes les ~30 s lors d'une instabilité du flux (le symptôme rapporté à un live passé). Il a été corrigé et déployé le 17 juin. Ce bug est distinct des coupures totales récurrentes décrites ici ; détaillé en §5.

Ce document n'est pas défensif : chaque affirmation importante est rattachée à une preuve qu'un tiers peut revérifier (appel à l'API Cloudflare, ligne de code, requête à la base de données). Là où il reste une nuance ou un point honnête à corriger, il est écrit noir sur blanc (section 5).

2Le symptôme observé

Du point de vue d'un spectateur ou d'un actionnaire qui regarde : l'image fige dans le lecteur. Comme la seule chose visible à l'écran est le lecteur de Canal Rodéo, la conclusion intuitive — « c'est le lecteur qui bogue » — est compréhensible. Mais le gel à l'écran est le dernier maillon d'une chaîne, pas le maillon brisé.

Le « piège du décalage » — le point le plus important à comprendre

Pour offrir une lecture fluide sur le réseau rural / Starlink des spectateurs, le lecteur garde volontairement jusqu'à ~3 minutes de vidéo d'avance en mémoire tampon (maxBufferLength 90 / maxMaxBufferLength 180). Conséquence : quand la source coupe réellement (stockage plein ou lien Starlink qui tombe), les spectateurs continuent de voir l'image encore quelques minutes, puis l'écran fige.

Le gel apparaît donc dans le lecteur, mais en retard de plusieurs minutes par rapport à la vraie cause survenue en amont. C'est exactement ce qui fait porter le blâme au lecteur. Ce réglage profond de tampon n'est pas un défaut : c'est le bon choix pour cette audience rurale. Mais il faut le nommer pour ne pas confondre le messager avec la cause.

site/js/watch.js — configuration HLS.js, maxBufferLength 90 / maxMaxBufferLength 180

Au niveau de l'ingest (la montée du signal), le symptôme technique documenté est : OBS se connecte en RTMPS, puis Cloudflare ferme la connexion après ~8 secondes (recv -30848 / close_notify). Ça ressemble à un problème d'OBS, de clé, de réseau — alors que c'est un refus côté serveur de Cloudflare quand le compte est plein.

3La vraie cause racine

Cause #1 — Le stockage Cloudflare Stream atteint le plafond de 1000 min → l'ingest est bloqué

Le compte Cloudflare Stream a un plafond de 1000 minutes de vidéo stockée. Le hic structurel : le live input est configuré pour enregistrer automatiquement chaque connexion. À chaque reconnexion d'OBS (typique sur un lien Starlink instable), Cloudflare crée un nouvel enregistrement. Sur un live agité, cela génère des centaines de mini-fragments qui remplissent le plafond en silence. Une fois plein → Cloudflare refuse le prochain direct.

Vérifié en direct aujourd'hui (17 juin 2026)
Configuration du live input lue à l'instant : recording.mode = automatic et deleteRecordingAfterDays = null.
Aucune purge automatique : chaque reconnexion s'accumule et le plafond remonte tout seul. C'est la cause racine structurelle qui faisait re-remplir le compte après chaque nettoyage. GET /accounts/{acct}/stream/live_inputs/{LI} — champ recording
Incidents documentés (mémoire d'opérations)
6 juin 2026 : stockage à 1000.04 / 1000 → ingest bloqué en plein test pré-live de Princeville. Environ 1 h perdue sur de fausses pistes (IPv6, clé RTMPS, keyframe) avant d'identifier le vrai coupable.
17 juin 2026 (matin) : stockage retrouvé à 1089.6 / 1000dépassé. Libéré à ~225 / 1000 en supprimant 201 vieux enregistrements (gros lives + ~189 mini-fragments de reconnexion), tout en conservant le dernier vrai live (9 juin). ~/.claude/.../memory/canalrodeo-streaming-ops.md
228,21 / 1000 min
Stockage actuel — sous le plafond, OK pour l'instant
Vérifié en direct : GET /stream/storage-usage · success:true · 50 vidéos
disconnected
État du live input — déconnexion propre (client_disconnect)
Côté OBS/uplink, PAS un rejet serveur — cohérent avec un test terminé

À noter en toute honnêteté : le lien de cause à effet « plafond plein → coupure » n'est pas reproductible à l'instant, justement parce que le stockage a été libéré (228/1000). Ce qu'on peut prouver en direct, c'est l'inverse positif : sous le plafond, l'ingest fonctionne (un test du jour a produit une archive propre de 171,55 s). Le mécanisme « plein → blocage à ~8 s » repose sur les deux incidents documentés ci-dessus. C'est plausible, daté et répété — pas une supposition — mais c'est une preuve historique, pas une capture refaite aujourd'hui.

Cause #2 — L'uplink Starlink du site du rodéo

Le second risque, indépendant du stockage, est le lien Internet physique par lequel le signal monte vers Cloudflare. Le Starlink du site a un IPv6 défaillant qui « avale » le RTMPS (black-hole), et une bande passante variable. Quand le lien faiblit ou coupe, la session OBS tombe — peu importe la plateforme. Facebook ou YouTube subiraient exactement le même problème, car la cause est le réseau de la source, pas la plateforme de diffusion.

Correctif déjà documenté
Forcer IPv4 via le fichier hosts + activer DynamicBitrate + reconnexion automatique dans OBS. Le PC Windows de la tour de stream est actuellement hors ligne — c'est le maillon de production réel à remettre en route. ~/.claude/.../memory/canalrodeo-streaming-ops.md

Quelle cause explique quoi

CauseType de coupure expliquée
Stockage plein (cap 1000 min)Les échecs de démarrage / blocages totaux d'un direct (OBS refusé)
Uplink Starlink instableLes micro-coupures intermittentes pendant un direct déjà parti
Badge « EN DIRECT » résiduel (~2 min)De la perception de coupure — pas une vraie coupure du flux (voir §5)

Aucune de ces trois causes n'est le code du lecteur.

4Pourquoi ce n'est pas le lecteur

Au-delà du raisonnement, il y a une impossibilité structurelle : le lecteur d'un spectateur ne peut pas couper la montée du signal d'OBS, parce que les deux ne se croisent jamais.

Deux chemins découplés — preuve par l'architecture
OBS POUSSE le signal en RTMP vers Cloudflare (un chemin, côté source). Le spectateur TIRE le flux HLS depuis le CDN mondial de Cloudflare (un autre chemin, côté lecture). Une recherche dans tout le code client (site/js/*.js) sur rtmp / publish / live_input / whip / webrtc donne zéro résultat : le lecteur ne fait que télécharger un manifeste .m3u8. Il n'a aucun moyen technique de fermer la connexion d'OBS. grep site/js/*.js (ingest/rtmp/publish) = 0 résultat · watch.js:410 hls.loadSource(url)

Le lecteur est sur-équipé pour absorber les coupures

La lecture complète du code (watch.js, ~837 lignes, et live-mode.js) montre un arsenal de récupération plus complet que la moyenne :

Sources
site/js/watch.js:425-452 (gestion Hls.Events.ERROR) · :496-520 (watchdog buffering) · :533-562 (superviseur _healTick 10 s) · :383-409 (config retries) · live-mode.js:136-142 (reconnexion hero)

Le bilan d'exploitation (base Neon, chiffres revérifiés)

Le même code a déjà diffusé à l'échelle, en conditions réelles, sans être le point de défaillance. Les chiffres ci-dessous ont été recoupés directement dans la base de données et corrigés par rapport aux estimations qui circulaient.

~502
Pic de spectateurs simultanés (live du 6 juin)
Neon broadcasts · 463 spectateurs uniques · le chiffre le plus défendable
29
Directs au compteur (28 « reported » + 1 test resté ouvert)
Neon : table broadcasts
17 987
Sessions de visionnement historiques
Neon : table stream_sessions
85
Commits d'évolution du code de la plateforme
git log — maturité, itérations, durcissement sécurité

Honnêteté sur les chiffres : on avait évoqué « 22 lives, 587 simultanés, ~15 700 sessions ». La vérification en base donne plutôt 29 directs, 17 987 sessions, et un pic crédible de ~502 simultanés le 6 juin (et non 587). Une valeur de 1418 existe en base le 25 avril mais c'est un artéfact (sessions = pic pour seulement 183 spectateurs uniques : dédoublement de sessions au premier live) — à ne pas citer comme record. On préfère communiquer le chiffre solide (~502) plutôt qu'un chiffre flatteur invérifiable.

Le test du 17 juin 2026 — la chaîne fonctionne bout en bout

Depuis OBS sur le Mac, un test a produit :

Vérifié en direct
Archive ready confirmée côté serveur (state=ready, duration=171.55s) ; manifeste livrable (content-type application/vnd.apple.mpegurl) ; détection du site visible dans le watchdog du jour. GET .../live_inputs/{LI}/videos · GET customer-3nr1c2xhydewpgk0.cloudflarestream.com/{uid}/manifest/video.m3u8 · ~/canalrodeo-stream-watch.log

Limite honnête du test : il a été réalisé depuis OBS sur le Mac local, pas depuis le PC Windows de la tour via le Starlink du site. Le maillon le plus fragile (l'uplink Starlink) n'a donc pas été testé aujourd'hui. Le test prouve que la chaîne ingest → archivage → détection → livraison HLS fonctionne quand il y a de la place et un bon lien ; il ne dit rien sur le comportement quand le stockage est plein — ce qui, soit dit en passant, renforce la thèse que la cause des coupures est ailleurs que dans le code.

5Les objections, traitées honnêtement

Un examen contradictoire a été mené : on a activement cherché à prouver que le lecteur était fautif. Voici ce qui a été trouvé, sans filtre. Aucun de ces points ne cause les coupures — mais les nommer rend ce document crédible plutôt que défensif.

Pas une cause   Le tampon profond (~3 min) retarde le gel.

C'est le réglage optimal pour le réseau rural, pas un défaut. Mais c'est le piège de perception nº1 : une coupure côté serveur apparaît comme un gel du lecteur, en retard de quelques minutes. Déjà expliqué en §2. À garder tel quel.

Vrai point mineur   Le badge « EN DIRECT » reste affiché ~2 min après un test, puis retombe seul.

Ce n'est pas un bug : c'est un « filet de fraîcheur » intentionnel et documenté dans le code. La détection considère un direct « vivant » si la dernière vidéo a été modifiée il y a moins de 2 minutes — pour éviter que le site clignote « hors ligne » entre deux fragments pendant un vrai live à micro-coupures. La retombée à false est garantie mathématiquement passé 2 minutes. Le watchdog du jour le montre noir sur blanc : bouffées live:true de ~2 min puis retour à false. Risque réel : ce clignotement peut, à lui seul, générer une plainte « ça coupe » alors qu'aucun flux n'a réellement coupé. À considérer comme un facteur de perception distinct.

workers/src/stream.ts:208-222 · seuil age < 2*60*1000 ms · ~/canalrodeo-stream-watch.log

Cas limite mineur   L'état error est traité comme « pas en direct ».

Un Starlink qui clignote peut produire plusieurs courts fragments error/ready. Pendant qu'OBS se reconnecte sous une nouvelle vidéo, le site pourrait brièvement afficher « hors ligne » avant que le filet de fraîcheur 2 min ne rattrape. Cela n'affecte que le badge / la bascule automatique, pas la livraison aux spectateurs déjà en lecture. Couvert par le filet 2 min dans la grande majorité des cas.

À surveiller   L'auto-guérison crée une nouvelle session à chaque reprise.

Quand le superviseur relance la lecture, il ouvre une nouvelle session côté base. Pendant une vraie panne d'uplink, chaque spectateur boucle et gonfle un peu le compteur de sessions. C'est un amplificateur de symptôme (inflation du compteur, écritures DB), pas une cause de coupure. Optimisable (réutiliser la session) — cosmétique.

Bug trouvé · corrigé   Un vrai bug client a été trouvé sur le lecteur de la page d'accueil — et corrigé.

Un audit contradictoire dédié (8 agents, chaque vecteur de déconnexion passé au crible) a trouvé un défaut réel : sur la page d'accueil, le lecteur du bandeau (« hero ») se détruisait puis se recréait chaque fois que la détection de direct oscillait true/false — ce qui pouvait reconnecter l'image toutes les ~30 secondes quand le flux était instable. C'est exactement le symptôme « ça déconnecte aux 30 s » observé à un live passé. Corrigé le 17 juin (hystérésis : le lecteur ne se détruit plus sur un faux négatif isolé — commit c469940, déployé en production). Deux précisions importantes : la page de visionnement /watch n'a jamais eu ce défaut (vérifié), et ce bug d'accueil est distinct des blackouts totaux récurrents — ceux-là restent causés par l'ingest (§3). Les 5 autres vecteurs audités (heartbeat, seek, watchdogs, chat, retry) sont bénins.

Verdict de l'examen contradictoire

En toute transparence : l'audit a trouvé un seul bug du lecteur — le flap du hero d'accueil ci-dessus, déjà corrigé et déployé. Aucun défaut dans le chemin de livraison /watch ni ailleurs ne peut causer les blackouts totaux récurrents : ceux-là viennent de l'ingest (§3). Les autres écarts relevés sont des chiffres d'historique (corrigés en §4) et des points de perception (badge, latence, tampon). Sur la question « le lecteur est-il la cause des coupures récurrentes ? », la réponse reste non — mais un bug client réel existait bel et bien, et il a été trouvé puis éliminé.

6La solution appliquée et la prévention

FAIT — 17 juin 2026
Stockage libéré. 201 vieux enregistrements supprimés (gros lives + ~189 mini-fragments de reconnexion). Le compte est passé de 1089,6/1000 (dépassé) à ~225/1000. Le dernier vrai live (9 juin), les boucles vidéo du site et l'après-match sont conservés. Vérifié en direct : 228,21/1000.
À FAIRE — correctif de fond (le vrai levier)
Empêcher le re-remplissage automatique du plafond. Poser deleteRecordingAfterDays sur le live input (purge automatique des enregistrements) et/ou passer recording.mode à off pour le live. Sans ça, « réglé » est transitoire : le plafond se redépasse en silence au prochain live agité. C'est le seul correctif qui supprime la cause racine pour de bon.
RECOMMANDÉ — marge de sécurité
Ajouter +1000 min de stockage (~5 $/mois) comme coussin, en complément de la purge automatique. Le coût est négligeable face à une heure de direct perdue.
PROCÉDURE — avant chaque événement
Vérifier le stockage avant chaque live (un seul appel API : GET /stream/storage-usage). 30 secondes de contrôle évitent l'incident du 6 juin.
EN ROUTE — durcir l'uplink
Forcer IPv4 via hosts + DynamicBitrate + reconnexion auto dans OBS sur le PC de la tour. Procédure déjà documentée dans la mémoire d'opérations.
Point clé

Les quatre correctifs touchent l'infrastructure et la montée du signal — pas le code du lecteur. Le lecteur n'a rien à corriger pour faire disparaître les coupures : il faut arrêter de couper la source.

7Le vrai chantier restant — en toute transparence

Le problème n'est pas entièrement clos, et il serait malhonnête de le prétendre. Deux choses restent ouvertes :

  1. Le correctif de fond du stockage n'est pas encore posé. Aujourd'hui le compte est libéré (228/1000) mais la configuration recording.mode=automatic + deleteRecordingAfterDays=null est toujours active. Tant que la purge automatique n'est pas en place, le risque de re-remplissage existe au prochain live agité.
  2. L'uplink Starlink reste le maillon le plus fragile, et le PC Windows de la tour de stream est actuellement hors ligne. C'est le maillon de production réel à remettre en route et à durcir (IPv4 + DynamicBitrate). Ce risque est indépendant de la plateforme : il existerait à l'identique sur Facebook Live ou YouTube.

En clair : si une coupure survenait maintenant, ce ne serait pas le stockage (il est OK) ni le lecteur — ce serait l'uplink ou une simple déconnexion d'OBS. L'état actuel du live input le confirme : disconnected / client_disconnect (côté source, pas côté serveur).


En une phrase : le lecteur de Canal Rodéo est sain, mature et résilient ; les coupures viennent de la montée du signal (stockage Cloudflare plein + uplink Starlink), et la prévention durable passe par la purge automatique du stockage et le durcissement de l'uplink — deux chantiers d'infrastructure, pas une réécriture du code.