Skip to content

Latest commit

 

History

7 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

concerto-player

A lightweight native player agent for Concerto digital signage, designed to run on Raspberry Pi alongside a Chromium kiosk browser.


What it does

Concerto's built-in player runs entirely in the browser — on every poll cycle the browser fetches the full player page, re-downloads assets, and re-renders content. On a Raspberry Pi over a typical network this is slow, CPU-heavy, and fragile.

concerto-player is a sidecar daemon that sits between the Pi and your Concerto server. It handles all network I/O, caches assets locally, and serves a thin local player page to Chromium over loopback. The browser never talks to Concerto directly.

[Chromium kiosk]
      ↕  localhost:8080
[concerto-player]  ←──── polls every 5s ────→  [Concerto server]
      ↓
[Local asset cache]

Result: Chromium loads content from disk, not the network. Polls are lightweight JSON requests. The screen keeps displaying the last known good content through network outages. Memory and CPU usage on the Pi drops significantly.


Features

  • Local asset cache — images downloaded once, served from disk; LRU eviction with configurable size cap (default 2 GB)
  • SSE push to browser — content updates and layout changes are pushed instantly; no polling from the browser
  • Exponential backoff — graceful handling of server outages; last good state served until connectivity recovers
  • Prometheus metrics — poll latency, cache hit rate, SSE client count, and more at /metrics
  • Health endpoint/healthz for uptime monitoring
  • Bearer token auth — optional Authorization: Bearer header on all Concerto API requests, for use with an Nginx auth proxy in front of Concerto
  • Single static binary — no runtime dependencies, no interpreter, no virtualenv; copy one file and run
  • VPN-transparent — works over Tailscale, WireGuard, or any network topology

Hardware

Tested on:

Hardware Architecture Notes
Raspberry Pi 4 (2GB+) linux/arm64 Recommended
Raspberry Pi 3B+ linux/arm Supported

Memory footprint at idle: under 30 MB RSS. CPU during steady-state polling: under 5% of one core.


Quick start

1. Build

# For Pi 4 (ARM64)
make build-arm64

# For Pi 3 (ARMv7)
make build-arm

Requires Go 1.22+. Produces a single static binary with no external dependencies.

2. Configure

Create /etc/concerto-player/config.toml on the Pi:

[server]
base_url   = "https://your-concerto-server"
screen_id  = "1"       # numeric screen ID from Concerto
api_secret = ""        # Bearer token if using Nginx auth proxy; leave empty for open installs

[polling]
interval_seconds = 5   # ±10% jitter applied automatically

[cache]
dir       = "/var/cache/concerto-player"
max_bytes = 2147483648  # 2 GB

[http]
listen = "127.0.0.1:8080"

[log]
level  = "info"   # debug | info | warn | error
format = "json"   # json | text

All values can be overridden with environment variables — see Configuration below.

3. Deploy

# Copy binary and config
scp concerto-player-arm64       pi@your-pi:/usr/local/bin/concerto-player
scp concerto-player.toml        pi@your-pi:/etc/concerto-player/config.toml

# Run
ssh pi@your-pi "concerto-player"

4. Point Chromium at it

Launch Chromium in kiosk mode pointing at the local player:

chromium-browser \
  --kiosk \
  --noerrdialogs \
  --disable-infobars \
  --no-first-run \
  http://127.0.0.1:8080

Configuration

Config file search order: ./concerto-player.toml, then /etc/concerto-player/config.toml.

Environment variables take precedence over the config file.

Environment variable Config key Description
CONCERTO_BASE_URL server.base_url Concerto server URL
CONCERTO_SCREEN_ID server.screen_id Numeric screen ID
CONCERTO_API_SECRET server.api_secret Bearer token (optional)
CONCERTO_POLL_INTERVAL polling.interval_seconds Poll interval in seconds
CONCERTO_CACHE_DIR cache.dir Asset cache directory
CONCERTO_CACHE_MAX_BYTES cache.max_bytes Cache size cap in bytes
CONCERTO_HTTP_LISTEN http.listen Local HTTP listen address
CONCERTO_LOG_LEVEL log.level Log level
CONCERTO_LOG_FORMAT log.format Log format (json or text)

Bearer token auth (optional)

Concerto v3 has no built-in API authentication. If you want per-screen token authentication without modifying Concerto, put Nginx in front of it:

[Pi]  →  Bearer token  →  [Nginx]  →  validates token, injects screen ID  →  [Concerto]

Nginx validates the token using a map directive and proxies the request to Concerto with the correct screen ID. Concerto is unaware of the auth layer.

Set api_secret in the Pi config to the token for that screen. If api_secret is empty, no Authorization header is sent — suitable for open/development installs.

A reference Nginx config for this pattern is in docs/nginx-auth.md (coming soon).


Endpoints

The agent serves the following on http.listen (default 127.0.0.1:8080):

Endpoint Description
GET / Player page — open this in Chromium
GET /state Current screen state as JSON
GET /events SSE stream — browser subscribes here automatically
GET /asset/{sha256} Cached asset served from disk
GET /metrics Prometheus metrics
GET /healthz Health check — always 200 if the agent is running

Metrics

Namespace: concerto_player_

Metric Type Description
poll_total Counter Total poll attempts
poll_errors_total Counter Failed polls
poll_duration_seconds Histogram Full poll cycle latency
cache_size_bytes Gauge Current cache disk usage
cache_items_total Gauge Cached asset count
cache_hits_total Counter Cache hits
cache_misses_total Counter Cache misses
cache_evictions_total Counter Assets evicted
sse_clients_total Gauge Connected SSE clients
build_info Gauge Version and build metadata

Compatible with any Prometheus + Grafana setup.


Content types

Supports all Concerto v3 content types:

Concerto type Rendered as
Graphic <img> — asset downloaded and cached locally
RichText (html) sandboxed <iframe srcdoc>
RichText (plaintext) <div> with innerText
Video (YouTube) sandboxed <iframe> with youtube-nocookie embed
Video (Vimeo) sandboxed <iframe> with Vimeo embed
Video (TikTok) sandboxed <iframe> with TikTok embed
Clock <div> updated every second using Concerto date-fns format strings

Note on video duration: Concerto's duration field controls how long a video item displays before advancing to the next item. Set this to match your actual video length. If left blank it defaults to 10 seconds.


Known limitations

  • No self-hosted video — video content supports YouTube, Vimeo, and TikTok embeds only, matching Concerto v3's supported video sources.
  • Single screen per agent — one agent process per screen. Running multiple screens on one Pi requires multiple agent instances on different ports.
  • No systemd unit file — included in a future release. For now, manage the process with your preferred supervisor (systemd, supervisord, etc.).

Building from source

git clone https://github.com/scopey/concerto-player
cd concerto-player

# Run tests
make test

# Build for current platform
make build

# Build for Pi 4
make build-arm64

# Build for Pi 3
make build-arm

Requires Go 1.22 or later. No CGO. No external tooling beyond the Go toolchain.


Contributing

Bug reports and pull requests welcome. Please open an issue before submitting a large PR so we can discuss the approach first.


Acknowledgements

concerto-player was built against the Concerto v3 API. The Concerto project provided the foundation that made rapid development of this agent possible. If you're looking for a full-featured open source digital signage platform, check them out.


License

Apache 2.0

About

Player for Concerto v3 API

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages