デジタルサイネージ — ディスプレイ + 広告
会場のスクリーンはWebページではありません。安価なTVスティックで無人稼働し、コンソールを見ている人は誰もいません。障害の形は、フリーズしたフレームが1週間誰にも気づかれないことです。以下のすべての設計判断は、その状況を生き延びるためにあります。
このスキルは完全なモジュールを提供します: メディアとプレイリストの管理者CRUD、ディスプレイレジストリ、PINとプロビジョニングURLによるペアリング、フルスクリーンプレイヤー、そしてそれを管理可能にする運用層(ヘルス、プレビュー、リモートコントロール)です。
使用すべき場合
Next.js App Router(FirestoreまたはSupabase/Postgres)上で、物理空間でメディアをループ再生するスクリーンを構築・拡張する場合。
使用すべきでない場合
- Web広告配信(インプレッション、入札、トラッキングピクセル、サードパーティタグ) — 異なるインフラを必要とする別の問題です。
- インタラクティブキオスク(ユーザーがタップして取引を行うもの)。サイネージは一方通行です。
- マーケティングページ上の単一埋め込みビデオ。
<video>タグを使用してください。 - 一般的なNext.js、CMS、または認証設定 — これらは既に存在することを前提としています。
アーキテクチャ
管理者 (ブラウザ) サーバー 端末 (TV)
───────────────── ────── ───────────
メディアアップロード ─────────────► オブジェクトストレージ ◄── 公開/署名付きURL ──┐
広告CRUD ─────────┐ │
プレイリスト編集 ──┼──► /api/admin/* ──► 広告 + ディスプレイ ──┐ │
コマンド発行 ─────┘ (スタッフ認証) (スコープ付き) │ │
▼ │
スクリーンとペアリング ◄── PIN ────────────► /api/display/pair │ │
ディスプレイごとのトークンを │ │
発行 ▼ │
/api/display/playlist ─── ポーリング ───┤
▲ adIds→広告を解決 │
└── テレメトリを上に、 │
コマンドを下に伝達 プレイヤーループ
ただ一つのリクエストタイプがランタイム全体を支えます: 端末が /api/display/playlist をポーリングし、テレメトリを送信し、コンテンツとコマンドを受け取ります。運用上のすべての動作はこのチャネル上で行われます。
重要な事実 — 設計を始める前に必読
- プレイリストはディスプレイ上の順序配列であり、独立したエンティティではありません。
display.adIds: string[]がプレイリストです — 順序は自由、結合不要、1回の読み取りで済みます。同じコンテンツを複数のスクリーンで実行する必要がある場合にのみ、独立したプレイリストエンティティを導入してください。トレードオフについては data-model.md を、移行については extensions.md を参照してください。 - ストリーミングではなくポーリングを使用します。 30~60秒間隔のポーリングはコストが低く、スリープ状態のネットワークスタックでも動作し、再接続が不要です。リアルタイムはオプションのアップグレードであり、基本ではありません。ETagでレスポンスをキャッシュしないと、画面ごと・ポールごとにフルリードのコストが発生します。
- メディアのアップロードはブラウザからストレージに直接行い、APIルートを経由しません。 ルートハンドラには本文サイズ制限があり、プロキシバイトの転送に計算リソースを消費します。
- 端末は信頼できず、無人運用されます。 誰でも近づけるハードウェアの
localStorageに長期有効な資格情報を保持します。その資格情報は画面ごとに発行され、かつ失効可能でなければなりません。 - プレイヤーが停止しては絶対になりません。 すべてのメディア要素にはタイムアウト、エラーハンドラ、スキップ手段を設けます。壊れたアセットは進行させ、動きを止めてはいけません。
厳格なルール
ポーリング間隔をレンダリング状態から導出してはいけません。 安定した間隔でポーリングし、スライドの状態はrefから読み取ります。スライドインデックスに依存するタイマーは、スライドが変わるたびに破棄・再作成されるため、スライドの表示時間より長いリフレッシュ間隔は決して発火しません。動作契約により、ポーリングのタイミングは壁時計時間に固定されます。
1つのブール値を「一時停止」と「削除」の両方に過剰に使用してはいけません。 オペレーターの意図には
activeを、ライフサイクルには別のdeletedAtを使用するか、削除されたアイテムが再度オンに切り替えられたときに再表示されることを防ぎます。
クライアントが指定したテナント/ロケーションスコープを信頼してはいけません。 認証されたスタッフセッション、またはトークンが解決するディスプレイ行からサーバーサイドで導出し、すべての
[id]ルートでそれを強制します — 403ではなく404を返すことで、IDの探索を防ぎます。
すべての画面に1つの共有トークンを発行してはいけません。 ペアリング時に画面ごとのトークンを発行し、そのハッシュのみを保存し、失効は1行の更新で行えるようにします。
画像の表示時間タイマーは
onLoadの前に開始してはいけません。 遅いTVでは、スライドが表示される前に期限切れになります。
永続的な画面状態を一回限りのコマンドで保持してはいけません。 ブランク表示やテイクオーバーは、端末の毎日の再読み込みや電源断にも耐えなければなりません。これらはすべてのポールで状態として配信し(
mode)、確認応答でクリアされるコマンドチャネルは、リロードのような真に一回限りのコマンドのために残します。そして、効果がリロードであるコマンドは、確認応答をリロードより前に永続化しなければなりません。そうしないと、サーバーが永遠に再配信し続けます。
クイックスタート
- アプリケーションとの結合契約を埋め、ドメイン名の変更を確認します — adaptation.md。
- エンティティをモデル化し、配列か結合テーブルかを選択します — data-model.md。
- テーブル/コレクション、インデックス、セキュリティルール、メディアバケットを作成します — firestore-backend.md または supabase-backend.md。
- 端末と管理エンドポイント(ペアリング、トークン検証を含む)を構築します — api-routes.md。
- 画面をペアリングします — pairing.md — その後、プレイヤーループを実装します — player-runtime.md。
- バックオフィスを構築します: メディアライブラリ、ディスプレイ一覧、プレイリストエディター — admin-ui.md。
- 本番稼働前に、ヘルス、プレビュー、リモートコントロールを追加します — operations.md。
- player-runtime.md の動作契約テーブルに対して検証します — ネットワークケーブルを抜き、ループ中の現在の広告を削除し、リロードを発行します — そしてプレイヤーマシンテストを回帰カバレッジとして出荷します。
リファレンス一覧
トリガーキーワードに一致するリファレンスを読み込んでください。新規設計の場合は、最初に data-model.md をお読みください。
api-routes.md および operations.md のコードは、標準バックエンドとしてFirestoreを想定して記述されています。supabase-backend.md はすべての置き換え(adIds を置き換える結合テーブル、サービス関数のSQL相当)を定義しています。
| シナリオ | トリガーキーワード | リファレンス |
|---|---|---|
| 既存アプリへの組み込み | 適応, リネーム, 結合, 統合, テナント, ホストアプリ | adaptation.md |
| エンティティ、フィールド、プレイリストの形状 | スキーマ, モデル, 広告, ディスプレイ, プレイリスト, 結合テーブル, ソフトデリート | data-model.md |
| Firestore/Firebaseバックエンド | Firestore, firebase-admin, セキュリティルール, 複合インデックス, Firebase Storage | firestore-backend.md |
| Supabase/Postgresバックエンド | Supabase, Postgres, RLS, マイグレーション, ストレージバケット, リアルタイム | supabase-backend.md |
| エンドポイント、ペアリング、トークン、キャッシュ | APIルート, ペアリング, PIN, トークン, ETag, スコープチェック, zod | api-routes.md |
| 画面のペアリング | ペアリング, PIN, プロビジョニングURL, ハブ, 資格情報, ペアリング解除 | pairing.md |
| TVプレイヤーループ | プレイヤー, ループ, クロスフェード, ビデオストール, ローテーション, ポートレート, ウェイクロック, オフライン | player-runtime.md |
| バックオフィスUI | 管理, アップロード, メディアライブラリ, プレイリストエディター, プロビジョニングURL | admin-ui.md |
| ヘルス、プレビュー、リモートコントロール | ハートビート, 最終アクセス時刻, オフライン, プレビュー, リロード, ブランク表示, 緊急テイクオーバー, 監査ログ | operations.md |
| スケジューリング、再利用、レポート | デイパーティング, 開始日, キャンペーン, 共有プレイリスト, プルーフ・オブ・プレイ, プリキャッシュ, マルチゾーン | extensions.md |
| テンプレートが単純な移植と異なる理由 | 出典, 台帳, 根拠, 保持, 追加 | provenance.md |