Skip to content

uxdevopstevan/av-feed

Repository files navigation

Circle AV Feed is a Next.js digital signage feed powered by the Circle Admin API (Circle community platform). It renders a full-screen “live feed” display for events: posts (text, image, video) in the main area with a rotating comments ticker.

  • Main area: rotates through posts (image, video, or text-only).
  • Bottom bar: shows comments for the currently displayed post (or a CTA when a promo slide is showing).
  • Config page (/config): per-device settings stored in localStorage (space selection + fetch limits + theming + assets + import/export).

Getting Started

Local development

Run the dev server:

npm run dev
  • Signage screen: http://localhost:3000
  • Config screen: http://localhost:3000/config

Production build

npm run build
npm run start

How it works

Data flow

  • The client polls GET /api/circle/posts every 60 seconds.
  • The server fetches posts + comments from Circle Admin API v2 and returns a SignageSnapshot.
  • The client keeps a stable, append-only queue of post slides (new posts append to the end of the cycle).

Relevant files:

  • Client signage UI: app/components/SignageClient.tsx
  • Snapshot fetch + Circle parsing: app/lib/circle.ts
  • Posts API route: app/api/circle/posts/route.ts
  • Spaces API route: app/api/circle/spaces/route.ts

Main area content rules

  • Only post media is shown in the main area.
  • Media attached to comments is ignored for the main area.
  • Text-only posts render centered on a dark background.

Promo slides

Promo slides are configured per-device on /config (stored in localStorage).

Rotation behavior:

  • If there are 0 posts: promos rotate continuously.
  • If there are 1–2 posts: promos alternate with posts while cycling through all promo images:
    • 1 post: post, promo1, post, promo2, post, promo3, ...
    • 2 posts: post1, promo1, post2, promo2, post1, promo3, ...
  • If there are 3+ posts: promos are inserted after every 3 posts (targeting ~25% promos):
    • post1, post2, post3, promo1, post4, post5, post6, promo2, ...

Video playback

Circle videos are commonly delivered as HLS (.m3u8).

  • Chrome / Windows: we use hls.js to play HLS inside a normal <video>.
  • Safari: uses native HLS when available.
  • Videos autoplay muted (no audio) and do not loop.
  • Video slides stay on-screen for the video duration, clamped by per-device config (see “Display timing” below).

Links

  • Circle Admin API docs: https://api.circle.so/
  • HLS playback (hls.js): https://github.com/video-dev/hls.js

Signage caching + polling (Vercel)

The signage page polls /api/circle/posts every 60 seconds, but the API route is configured to be CDN-cached on Vercel to avoid multiplying Circle upstream calls when multiple screens are open.

  • API route: app/api/circle/posts/route.ts
  • Cache header: Cache-Control: s-maxage=60, stale-while-revalidate=300
  • Daily filtering timezone: Europe/London

Environment variables

Required (server-only)

  • CIRCLE_API_TOKEN: Circle Admin API token. Do not use NEXT_PUBLIC_ — this must remain server-only.

Optional

  • CIRCLE_DEFAULT_SPACE_ID: Default Circle spaceId used when no per-device spaceId is selected.
  • NEXT_PUBLIC_SIGNAGE_CONFIG_PIN: PIN required to unlock /config on a device.

Per-device space selection

The config page (/config) loads a list of Circle spaces from /api/circle/spaces and stores a selected spaceId in localStorage for that browser/device.

When set, the signage client polls /api/circle/posts?spaceId=<id>. This means CDN caching on Vercel will be effectively per-space ID (different query string = different cache key).

Configurable fetch limits (safe defaults + server-side clamping)

The config page also controls how much data we fetch from Circle. These values are stored per-device and are clamped server-side to prevent abuse.

  • daysBack (0–30): include posts/comments from the last N days in London time.
  • maxPosts (1–500): cap post fetch volume.
  • maxCommentsPerPost (0–25): cap comments per post in the bottom bar.
  • maxTotalComments (0–2000): cap total comments returned.

Display timing (per-device)

The config page (/config) also controls display timing stored per-device in localStorage:

  • commentAdvanceMs: how often the bottom-bar comment rotates.
  • postMinMs: minimum time a text/image post stays on-screen.
  • postMaxMs: maximum time a text/image post stays on-screen.
  • promoSlideMs: how long a promo slide stays on-screen (default 8 seconds).
  • videoMinMs: minimum time a video post stays on-screen.
  • videoMaxMs: maximum time a video post stays on-screen.

Text/image post duration can extend to cycle through comments (up to maxCommentsPerPost) at commentAdvanceMs, capped by postMaxMs.

Media loading notes (slow connections)

  • Post images use Next.js image optimization on Vercel (remote domains are allowlisted in next.config.ts).
  • The client prefetches upcoming slide images (posts, promos, and video posters) to reduce the chance of fading to a slide before its media has started downloading.

What to verify on Vercel

  • Response header: In Vercel Function logs / your browser devtools, confirm /api/circle/posts includes the Cache-Control header above.
  • Cache behavior: With multiple clients hitting /api/circle/posts within 60s, Vercel should serve most responses from cache; Circle should not be hit per viewer.
  • Daily snapshot: Refreshing the page should render quickly from localStorage if a snapshot exists for today (keyed by circle:snapshot:YYYY-MM-DD).

About

Circle digital signage feed (Next.js): motion-first UI, HLS video, rotating comments ticker, per-device config

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages