Affichage Numérique — Écrans + Publicités
Un écran dans un lieu n'est pas une page web. Il fonctionne sans surveillance pendant des mois sur une clé TV bon marché, personne ne regarde la console, et le mode de défaillance est une image figée que personne ne remarque pendant une semaine. Chaque décision ci-dessous existe pour survivre à cela.
Cette compétence intègre un module complet : CRUD administrateur pour les médias et les playlists, un registre des écrans, un appairage par code PIN et URL de provisionnement, un lecteur plein écran, et la couche opérationnelle (santé, aperçu, contrôle à distance) qui la rend gérable.
Quand l'utiliser
Construire ou étendre des écrans qui diffusent en boucle des médias dans un espace physique, sur Next.js App Router avec Firestore ou Supabase/Postgres.
Quand NE PAS l'utiliser
- Diffusion de publicités web (impressions, enchères, pixels de suivi, tags tiers) — un problème différent avec une infrastructure différente.
- Bornes interactives où l'utilisateur appuie pour effectuer une transaction. L'affichage numérique est unidirectionnel.
- Une seule vidéo intégrée sur une page marketing. Utilisez une balise
<video>. - La configuration générique Next.js, CMS ou authentification — considérée comme existante.
Architecture
ADMIN (navigateur) SERVEUR APPAREIL (TV)
───────────────── ────── ───────────
téléchargement média ──────────► stockage objet ◄──── URL publique/signée ─┐
CRUD publicité ─────────┐ │
édition playlist ───────┼──► /api/admin/* ──► publicités + écrans ─┐ │
émettre commande ───────┘ (auth personnel) (restreint) │ │
▼ │
appairer écran ◄──── code PIN ───────────► /api/display/pair │ │
frappe jeton │ │
par écran ▼ │
/api/display/playlist ────interroger──┤
▲ résout adIds → publicités │
└── remonte télémétrie, │
commandes descendantes boucle lecteur
Un seul type de requête soutient l'ensemble du runtime : l'appareil interroge /api/display/playlist, remonte la télémétrie et reçoit le contenu et les commandes. Tout ce qui est opérationnel passe par ce canal.
Faits critiques — à lire avant de concevoir quoi que ce soit
- La playlist est un tableau ordonné sur l'écran, pas une entité distincte.
display.adIds: string[]est la playlist — l'ordre est libre, pas de jointures, une seule lecture. N'introduisez une entité playlist autonome que lorsque le même contenu doit être diffusé sur plusieurs écrans ; voir data-model.md pour le compromis et extensions.md pour la migration. - Interrogez ; ne diffusez pas en continu. Une interrogation toutes les 30 à 60 s est moins coûteuse, survit aux piles réseau en veille et se reconnecte gratuitement. Le temps réel est une mise à niveau facultative, pas la base. Mettez en cache la réponse avec un ETag, sinon vous paierez pour une lecture complète par écran et par interrogation.
- Les téléchargements de médias vont du navigateur au stockage directement, jamais via une route API. Les gestionnaires de route ont des limites de taille de corps et brûlent du calcul en proxyant les octets.
- L'appareil n'est pas fiable et n'est pas surveillé. Il détient un identifiant de longue durée dans le
localStoragesur un matériel auquel n'importe qui peut accéder. Cet identifiant doit être propre à chaque écran et révocable. - Le lecteur ne doit jamais pouvoir s'arrêter. Chaque élément multimédia reçoit un délai d'attente, un gestionnaire d'erreur et un moyen de passer. Un élément défectueux avance ; il ne bloque pas.
Règles strictes
Ne déduisez jamais l'intervalle d'interrogation de l'état de rendu. Interrogez à un intervalle stable et lisez l'état de la diapositive à partir d'une référence. Un minuteur dont l'effet dépend de l'index de la diapositive est détruit et recréé à chaque diapositive, donc un rafraîchissement plus long qu'une diapositive ne se déclencherait jamais ; le contrat de comportement fixe la synchronisation de l'interrogation à l'heure de l'horloge murale.
Ne surchargez jamais un seul booléen pour signifier à la fois « en pause » et « supprimé ». Utilisez
activepour l'intention de l'opérateur et undeletedAtséparé pour le cycle de vie, sinon un élément supprimé réapparaît dès que quelqu'un le réactive.
Ne faites jamais confiance à un champ d'application tenant/lieu fourni par le client. Déduisez-le côté serveur à partir de la session du personnel authentifié ou de la ligne d'écran à laquelle le jeton se résout, et imposez-le sur chaque route
[id]— en renvoyant 404, pas 403, afin que les identifiants ne puissent pas être sondés.
N'émettez jamais un seul jeton partagé pour tous les écrans. Générez un jeton par écran lors de l'appairage, stockez uniquement son hash, et faites de la révocation une simple mise à jour d'une ligne.
Ne démarrez jamais le minuteur de durée d'une image avant
onLoad. Sur une TV lente, la diapositive expirera autrement avant d'être visible.
Ne portez jamais un état d'écran durable dans une commande unique. Le mode vide et la prise de contrôle doivent survivre au rechargement quotidien de l'appareil et à une coupure de courant — transmettez-les comme état à chaque interrogation (
mode), et réservez le canal de commande avec accusé de réception effacé pour les commandes véritablement uniques comme le rechargement. Et toute commande dont l'effet est un rechargement doit persister son accusé de réception avant le rechargement, sinon le serveur la redistribue indéfiniment.
Démarrage rapide
- Remplissez le contrat d'interface pour votre application et confirmez le changement de nom de domaine — adaptation.md.
- Modélisez les entités et choisissez entre tableau et table de jonction — data-model.md.
- Créez les tables/collections, les index, les règles de sécurité et le compartiment de médias — firestore-backend.md ou supabase-backend.md.
- Construisez les points d'accès de l'appareil et de l'administrateur, y compris l'appairage et la vérification des jetons — api-routes.md.
- Appairez un écran — pairing.md — puis intégrez la boucle du lecteur — player-runtime.md.
- Construisez le back-office : médiathèque, liste des écrans, éditeur de playlist — admin-ui.md.
- Ajoutez la santé, l'aperçu et le contrôle à distance avant la mise en production — operations.md.
- Vérifiez par rapport au tableau des contrats de comportement dans player-runtime.md — débranchez le câble réseau, supprimez la publicité en cours en milieu de boucle, émettez un rechargement — et publiez les tests de la machine du lecteur comme couverture de régression.
Répertoire des références
Chargez la référence correspondant aux mots-clés déclencheurs. Pour une conception de novo, lisez data-model.md en premier.
Le code dans api-routes.md et operations.md est écrit avec Firestore comme backend canonique ; supabase-backend.md définit chaque substitution — la table de jonction remplaçant adIds, et les équivalents SQL des fonctions de service.
| Scénario | Mots-clés déclencheurs | Référence |
|---|---|---|
| Intégration dans une application existante | adapter, renommer, jointure, intégrer, tenant, application hôte | adaptation.md |
| Entités, champs, forme de la playlist | schéma, modèle, Publicité, Écran, playlist, jonction, suppression douce | data-model.md |
| Backend Firestore/Firebase | Firestore, firebase-admin, règles de sécurité, index composite, Firebase Storage | firestore-backend.md |
| Backend Supabase/Postgres | Supabase, Postgres, RLS, migration, bucket de stockage, Realtime | supabase-backend.md |
| Points d'accès, appairage, jetons, mise en cache | Route API, appairage, code PIN, jeton, ETag, vérification de portée, zod | api-routes.md |
| Appairer un écran | appairage, code PIN, URL de provisionnement, concentrateur, identifiant, désappairer | pairing.md |
| Boucle du lecteur TV | lecteur, boucle, fondu enchaîné, blocage vidéo, rotation, portrait, verrouillage de veille, hors ligne | player-runtime.md |
| Interface back-office | admin, téléchargement, médiathèque, éditeur de playlist, URL de provisionnement | admin-ui.md |
| Santé, aperçu, contrôle à distance | battement de cœur, dernière apparition, hors ligne, aperçu, rechargement, mode vide, prise de contrôle d'urgence, journal d'audit | operations.md |
| Planification, réutilisation, rapports | diffusion par plages horaires, date de début, campagne, playlist partagée, preuve de diffusion, pré-mise en cache, multi-zone | extensions.md |
| Pourquoi les modèles diffèrent d'un portage naïf | provenance, registre, justification, conservé, ajouté | provenance.md |