Communitygithub.com

timerise-ai/digital-signage

에이전트 스킬: 디지털 사이니지 모듈 구축 — 미디어 라이브러리, 화면별 재생목록, 디스플레이 등록, PIN 및 프로비저닝 URL 장치 페어링, 전체 화면 TV 플레이어(정지 복구), 화면 상태 및 원격 제어 — Next.js App Router + Firestore 또는 Supabase 기반

digital-signage란 무엇인가요?

이 스킬은 저가형 TV 스틱에서 수개월간 무인 운영되는 디지털 사이니지 시스템을 구축하기 위한 완전한 모듈을 제공합니다. 관리자는 미디어와 재생목록을 CRUD로 관리하고, 디스플레이를 등록하며, PIN이나 프로비저닝 URL을 통해 장치를 페어링할 수 있습니다. 전체 화면 플레이어는 미디어 타임아웃, 오류 처리, 건너뛰기 기능을 통해 영상 정지(freeze frame)와 같은 장애에도 절대 멈추지 않도록 설계되었습니다. 화면 상태 모니터링, 미리보기, 원격 제어(재시작, 블랭크, 긴급 전환) 등 운영 계층도 포함되어 있습니다. 이 시스템은 Next.js App Router와 Firestore 또는 Supabase/Postgres를 백엔드로 사용하며, 핵심 아키텍처는 장치가 30~60초 간격으로 `/api/display/playlist`를 폴링하여 텔레메트리를 전송하고 콘텐츠와 명령을 수신하는 단일 요청 채널에 기반합니다. 특히 무인 환경에서의 안정성을 위해 폴링 기반 통신, 미디어 업로드 시 브라우저→스토리지 직접 전송, 화면별 토큰 발급 및 폐기 가능, ETag 캐싱 등이 필수적으로 적용됩니다. 이 스킬은 웹 광고 서빙, 인터랙티브 키오스크, 단순 임베디드 비디오에는 적합하지 않습니다.

지원 대상~Claude Code~Codex CLI~Cursor
npx skills add timerise-ai/digital-signage

즐겨 사용하는 AI에게 물어보기

이 에이전트 스킬이 미리 로드된 새 채팅을 엽니다.

문서

디지털 사이니지 — 디스플레이 + 광고

장소에 있는 화면은 웹 페이지가 아닙니다. 저렴한 TV 스틱에서 몇 달간 무인으로 실행되며, 아무도 콘솔을 보고 있지 않고, 실패 모드는 일주일 동안 아무도 알아차리지 못하는 정지 프레임입니다. 아래의 모든 결정은 이러한 상황에서도 시스템이 생존하기 위해 존재합니다.

이 스킬은 완전한 모듈을 제공합니다: 미디어 및 재생목록을 위한 관리자 CRUD, 디스플레이 등록, PIN 및 프로비저닝 URL 페어링, 전체 화면 플레이어, 그리고 이를 관리 가능하게 만드는 운영 계층(상태, 미리보기, 원격 제어)입니다.

사용 시기

Next.js App Router와 Firestore 또는 Supabase/Postgres를 사용하여 물리적 공간에서 미디어를 반복 재생하는 화면을 구축하거나 확장할 때.

사용하지 말아야 할 때

  • 웹 광고 서빙(노출, 입찰, 추적 픽셀, 서드파티 태그) — 인프라가 다른 별개의 문제입니다.
  • 인터랙티브 키오스크 — 사용자가 터치하여 거래를 수행하는 경우. 사이니지는 단방향입니다.
  • 마케팅 페이지에 삽입된 단일 비디오 — <video> 태그를 사용하세요.
  • 일반적인 Next.js, CMS, 또는 인증 설정 — 이미 존재한다고 가정합니다.

아키텍처

 관리자(브라우저)                 서버                      장치(TV)
 ─────────────────               ──────                      ───────────
 미디어 업로드 ──────────────► 객체 스토리지 ◄──── 공개/서명된 URL ─┐
 광고 CRUD ─────────┐                                                │
 재생목록 편집 ───┼──► /api/admin/*  ──► 광고 + 디스플레이 ──┐     │
 명령 발행 ───┘       (직원 인증)      (범위 지정)       │     │
                                                              ▼     │
 화면 페어링 ◄──── PIN ────────────► /api/display/pair    │         │
                                       화면별 토큰 발급      ▼       │
                                      /api/display/playlist ── 폴링 ─┤
                                       ▲ adId 해석 → 광고            │
                                       └── 텔레메트리 전송,           │
                                           명령 수신          플레이어 루프

단일 요청 유형이 전체 런타임을 유지합니다: 장치가 /api/display/playlist를 폴링하여 텔레메트리를 전송하고 콘텐츠와 명령을 수신합니다. 모든 운영은 이 채널을 통해 이루어집니다.

중요한 사실 — 설계 전에 반드시 읽으세요

  1. 재생목록은 별도 엔터티가 아니라 디스플레이의 정렬된 배열입니다. display.adIds: string[]가 곧 재생목록입니다 — 순서는 자유롭고, 조인이 없으며, 한 번 읽으면 됩니다. 동일한 콘텐츠를 여러 화면에서 실행해야 하는 경우에만 별도의 재생목록 엔터티를 도입하세요. 트레이드오프는 data-model.md에서, 마이그레이션은 extensions.md에서 확인하세요.
  2. 스트리밍하지 말고 폴링하세요. 30~60초 폴링은 비용이 저렴하고, 절전 중인 네트워크 스택에서도 생존하며, 자동으로 재연결됩니다. 실시간은 선택적 업그레이드이지 기본이 아닙니다. ETag로 응답을 캐시하지 않으면 화면당, 폴링당 전체 읽기 비용을 지불하게 됩니다.
  3. 미디어 업로드는 브라우저에서 스토리지로 직접 전송되어야 하며, API 경로를 거치지 마세요. 라우트 핸들러에는 본문 크기 제한이 있고 바이트를 프록시하는 데 컴퓨팅 리소스를 소모합니다.
  4. 장치는 신뢰할 수 없고 무인 상태입니다. 누구나 접근할 수 있는 하드웨어의 localStorage에 장기 자격 증명을 보관합니다. 이 자격 증명은 화면별로 발급되며 폐기 가능해야 합니다.
  5. 플레이어는 절대 멈출 수 없어야 합니다. 모든 미디어 요소에는 타임아웃, 오류 처리기, 건너뛰기 방법이 있어야 합니다. 손상된 에셋은 시스템을 멈추지 않고 진행합니다.

엄격한 규칙

렌더 상태에서 폴링 간격을 유도하지 마세요. 안정적인 간격으로 폴링하고 슬라이드 상태는 ref에서 읽으세요. 슬라이드 인덱스에 의존하는 타이머는 모든 슬라이드에서 파괴되고 재생성되므로, 슬라이드보다 긴 새로고침은 절대 실행되지 않습니다. 동작 계약은 폴링 타이밍을 벽시계 시간에 고정합니다.

하나의 불리언을 "일시 정지"와 "삭제"로 오버로드하지 마세요. 운영자 의도에는 active를, 수명 주기에는 별도의 deletedAt을 사용하세요. 그렇지 않으면 삭제된 항목이 다시 켜질 때 다시 나타납니다.

클라이언트가 제공한 테넌트/위치 범위를 신뢰하지 마세요. 인증된 직원 세션이나 토큰이 해석되는 디스플레이 행에서 서버 측에서 파생시키고, 모든 [id] 경로에서 이를 강제하세요 — 403이 아닌 404를 반환하여 ID를 프로빙할 수 없도록 합니다.

모든 화면에 공유 토큰 하나를 발급하지 마세요. 페어링 시 화면별 토큰을 발급하고, 해시만 저장하며, 폐기는 단일 행 업데이트로 처리하세요.

이미지의 지속 시간 타이머를 onLoad 전에 시작하지 마세요. 느린 TV에서는 슬라이드가 보이기 전에 만료됩니다.

일회성 명령에 영구적인 화면 상태를 전달하지 마세요. 블랭크와 테이크오버는 장치의 일일 재시작과 전원 차단을 견뎌야 합니다 — 모든 폴링에서 상태(mode)로 전달하고, ack-클리어 명령 채널은 재시작과 같은 진정한 일회성 명령에만 사용하세요. 또한 재시작을 유발하는 명령은 재시작 전에 ack를 유지해야 합니다. 그렇지 않으면 서버가 명령을 계속 재전송합니다.

빠른 시작

  1. 앱의 시임 계약을 채우고 도메인 이름 변경을 확인하세요 — adaptation.md.
  2. 엔터티를 모델링하고 배열 vs. 접합 테이블을 선택하세요 — data-model.md.
  3. 테이블/컬렉션, 인덱스, 보안 규칙 및 미디어 버킷을 생성하세요 — firestore-backend.md 또는 supabase-backend.md.
  4. 페어링 및 토큰 검증을 포함한 장치 및 관리자 엔드포인트를 구축하세요 — api-routes.md.
  5. 화면을 페어링하고 — pairing.md — 플레이어 루프를 적용하세요 — player-runtime.md.
  6. 백오피스를 구축하세요: 미디어 라이브러리, 디스플레이 목록, 재생목록 편집기 — admin-ui.md.
  7. 운영 전에 상태, 미리보기 및 원격 제어를 추가하세요 — operations.md.
  8. player-runtime.md의 동작 계약 테이블과 비교하여 검증하세요 — 네트워크 케이블을 뽑고, 현재 광고를 루프 중간에 삭제하고, 재시작을 발행하세요 — 그리고 플레이어-머신 테스트를 회귀 커버리지로 제공하세요.

참조 디렉터리

트리거 키워드와 일치하는 참조를 로드하세요. 그린필드 설계의 경우 data-model.md를 먼저 읽으세요.

api-routes.md와 operations.md의 코드는 Firestore를 표준 백엔드로 작성되었습니다. supabase-backend.md는 모든 대체 항목(adIds를 대체하는 접합 테이블, 서비스 함수의 SQL 등가물)을 정의합니다.

시나리오트리거 키워드참조
기존 앱에 통합adapt, rename, seam, integrate, tenant, host appadaptation.md
엔터티, 필드, 재생목록 구조schema, model, Ad, Display, playlist, junction, soft deletedata-model.md
Firestore/Firebase 백엔드Firestore, firebase-admin, security rules, composite index, Firebase Storagefirestore-backend.md
Supabase/Postgres 백엔드Supabase, Postgres, RLS, migration, storage bucket, Realtimesupabase-backend.md
엔드포인트, 페어링, 토큰, 캐싱API route, pairing, PIN, token, ETag, scope check, zodapi-routes.md
화면 페어링pairing, PIN, provisioning URL, hub, credential, unpairpairing.md
TV 플레이어 루프player, loop, crossfade, video stall, rotation, portrait, wake lock, offlineplayer-runtime.md
백오피스 UIadmin, upload, media library, playlist editor, provisioning URLadmin-ui.md
상태, 미리보기, 원격 제어heartbeat, last seen, offline, preview, reload, blank, emergency takeover, audit logoperations.md
스케줄링, 재사용, 보고dayparting, start date, campaign, shared playlist, proof of play, precache, multi-zoneextensions.md
템플릿이 단순한 포팅과 다른 이유provenance, ledger, rationale, kept, addedprovenance.md

관련 스킬