- Python 63.1%
- JavaScript 15.1%
- kvlang 7%
- CSS 7%
- HTML 3.8%
- Other 4%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
- FastAPI server + browser UI in server/, run via docker-compose.yml (port 8080, library in ./data/library). Reuses the player's own library scanner so both sides share one library format. - Web UI: browse albums/tracks, edit tags (album/track title, artist, colour, emoji icon, cover with live player-tile preview), upload music into new or existing albums, preview audio. - Tag edits write both metadata.json (artist + per-track overrides) and real audio tags via mutagen (ID3/FLAC/MP4/Vorbis), covers are embedded too; the player displays the new fields. - Pull-based sync for the Pi: scripts/sync_client.py (stdlib only) fetches /api/manifest, downloads new/changed files and removes deleted ones; install.sh can set up a systemd timer, and the player app rescans its library periodically so changes appear live. |
||
| library | ||
| scripts | ||
| server | ||
| simon_player | ||
| tests | ||
| .dockerignore | ||
| .gitignore | ||
| conftest.py | ||
| docker-compose.yml | ||
| README.md | ||
| requirements-pi.txt | ||
| requirements.txt | ||
Simon Player
A kid-friendly, full-screen touch audio player for a Raspberry Pi 5 with a 4.3" touchscreen. Big colourful buttons, no reading required, no menus to get lost in - just tap a story to play it.
- Touch home screen: a shelf of big cover-art tiles for each story.
- Player screen: huge play/pause, skip, a chunky drag-to-seek bar, volume, and an optional sleep timer (15/30/45 min).
- Optional Toniebox-style tags: place an RFID/NFC figurine on a reader to start a story, in addition to touch browsing.
- Remembers where you left off: pausing/leaving a story resumes from the same spot next time.
- Self-contained UI - all icons/buttons are hand-drawn vector shapes, no external icon fonts or image assets required.
Hardware assumptions
- Raspberry Pi Zero 2 W, Raspberry Pi OS (Bookworm), preferably booted straight to the console (see Headless kiosk below).
- A Waveshare 2.8" DPI LCD (480x640 IPS, capacitive touch) mounted on the
40-pin header - setup in
Waveshare 2.8" DPI LCD below. Simon Player
reads its resolution from
~/.simon_player/config.json(screen_width/screen_height, defaults match this panel). - Audio: the Zero 2 W has no audio jack - use a USB sound card or a speaker with a built-in DAC; VLC plays through ALSA automatically.
- Optional: an MFRC522 RFID reader wired over SPI, for the Toniebox-style
tag feature. Pi 5 note: the classic
RPi.GPIOpackage does not work on the Pi 5's RP1 I/O chip - installrpi-lgpioinstead (already handled byrequirements-pi.txt). Panel note: the 2.8" DPI LCD uses every SPI0 pin, so an SPI-wired reader cannot coexist with it (see RFID section).
Install
git clone <this repo> ~/simon_player
cd ~/simon_player
./scripts/install.sh
The script installs system packages (vlc for audio playback, plus the
SDL2 display libraries needed for headless rendering), adds your user to
the video/render/input groups, creates a venv, installs Python
dependencies, optionally installs the RFID extras, and optionally enables
a boot-time systemd kiosk service so the player starts full-screen on
the panel at every boot - with or without a desktop session.
Then copy story folders into ~/SimonPlayerLibrary - or manage the
library from any browser with the web admin UI below.
Web admin & library sync (Docker)
A web server with a browser-based admin UI ships in this repo. Run it anywhere on your network (a NAS, another Linux box, or the Pi itself with more RAM-heavy Docker workloads in mind):
docker compose up -d --build
# -> http://<host>:8080
The web UI lets you:
- browse everything that is (or will be) on the player - albums, tracks, covers - styled like the player's home screen;
- edit tags: album title, artist, per-track title/artist (written into
real ID3/FLAC/MP4 tags and the
metadata.jsonthe player reads); - upload covers - saved as
cover.*and embedded into the audio files; - add music by uploading files into new or existing albums;
- customise the player UI per album: tile colour, emoji icon, cover and titles, with a live preview of the resulting player tile.
The library lives in ./data/library next to docker-compose.yml
(bind-mounted to /library in the container). Set API_TOKEN=... in
the compose environment if others can reach the server; the UI asks for
the token once.
Syncing to the player
The player pulls from the server with a small stdlib-only client:
-
Run
./scripts/install.shand answer y to the sync question, or set it up by hand:.venv/bin/python scripts/sync_client.py \ --server http://<server>:8080 [--token <token>] # one-off test -
The systemd timer (
scripts/simon-player-sync.timer) runs the client every 5 minutes: new/changed files are downloaded into~/SimonPlayerLibrary, files deleted on the server are removed. State is kept in~/.simon_player/sync_state.json; use--dry-runor--no-deleteto preview/soften the behaviour. -
The player app rescans its library every 30 seconds, so changes show up without a restart. Sync is one-way (server → player): treat the web UI as the source of truth.
Headless kiosk (no desktop session)
The Pi does not need a desktop session at all. Kivy draws through SDL2,
and SDL2's kmsdrm video driver renders straight to the display hardware
- no X server and no Wayland compositor involved:
- Run
./scripts/install.sh- it adds your user to thevideo/render/inputgroups and can enable thesimon-playerkiosk service. - Answer "y" to booting to the console (equivalent to
sudo systemctl set-default multi-user.targetor, viasudo raspi-config, System Options -> Boot / Auto Login -> Console). - Reboot. The player fills the panel and is restarted automatically if it crashes.
Useful commands:
sudo systemctl start simon-player # also stop / restart
journalctl -u simon-player -f # follow the logs
If you keep booting into the desktop instead, disable the kiosk service
(sudo systemctl disable --now simon-player) - a desktop session owns the
display, and starting the kiosk unit takes the display away from it.
Running manually from the console works too: python -m simon_player.main
auto-selects SDL_VIDEODRIVER=kmsdrm when no desktop session is running.
Fallback if the Kivy build's bundled SDL2 ever refuses kmsdrm on your
image: run the app inside a minimal Wayland kiosk compositor -
sudo apt install cage, then start cage -- .venv/bin/python -m simon_player.main from a console (or use it as the service's ExecStart).
Waveshare 2.8" DPI LCD
The player targets a Pi Zero 2 W with this panel: a 480x640 IPS
capacitive touchscreen that hangs off the 40-pin header and appears as
the DPI-1 KMS display - no desktop session needed.
-
Follow the Waveshare wiki: download its DTBO bundle and copy the
.dtbofiles into/boot/overlays/, then add to/boot/firmware/config.txt:dtoverlay=vc4-kms-v3d dtoverlay=waveshare-28dpi-3b-4b dtoverlay=waveshare-28dpi-3b dtoverlay=waveshare-28dpi-4b dtoverlay=waveshare-touch-28dpi dtoverlay=vc4-kms-dpi-2inch8 -
Reboot - the console shows on the panel at 480x640 (native portrait), which is what Simon Player defaults to. For landscape instead: add
video=DPI-1:480x640M@60,rotate=90at the start of/boot/firmware/cmdline.txt, rotate touch via a/etc/udev/rules.d/99-waveshare-touch.ruleslibinput calibration matrix (wiki: Touch Rotation), and setscreen_width/screen_heightto 640/480 in~/.simon_player/config.json. -
Backlight is wired to GPIO18; the wiki's "Adjust Backlight" section covers dimming and on/off.
-
Zero 2 W quirk: if some grey pixels render greenish, the wiki FAQ has a replacement overlay file for the Zero 2 W.
Audio: the Zero 2 W has no audio jack - plug in a USB sound card (or a speaker with a built-in DAC); VLC picks it up through ALSA.
Running manually
source .venv/bin/activate
python -m simon_player.main
Press Escape (if you have a keyboard attached) or Alt+F4 to quit during
development. On the real kiosk deployment there's no window chrome or
keyboard, so quitting is done via sudo systemctl stop simon-player or
by power-cycling the Pi.
Configuration
Settings live in ~/.simon_player/config.json (created with defaults on
first run):
| Key | Default | Meaning |
|---|---|---|
library_path |
~/SimonPlayerLibrary |
Where story folders live |
screen_width / screen_height |
480 / 640 |
Match your touchscreen (panel native: 480x640 portrait) |
fullscreen |
true |
Full-screen kiosk mode |
show_cursor |
false |
Hide the mouse cursor (touch-only) |
default_volume |
80 |
Starting volume (0-100) |
rfid_enabled |
true |
Poll for RFID tags (no-ops without a reader) |
Playback resume position, current volume, and RFID tag links are stored
separately in ~/.simon_player/state.json and ~/.simon_player/tags.json.
Setting up RFID figurines/cards
Heads-up: the Waveshare 2.8" DPI LCD uses all of the SPI0 pins, so an SPI-wired MFRC522 cannot coexist with that panel. To use RFID with it, re-wire the reader for I2C mode or use an I2C reader (e.g. PN532). Without a working reader, the app silently stays in touch-only mode.
- Wire an MFRC522 reader to the Pi's SPI pins and enable SPI via
sudo raspi-config-> Interface Options -> SPI. pip install -r requirements-pi.txt(inside the venv).- Link a tag to a story folder:
Hold the figurine/card on the reader when prompted.python scripts/rfid_enroll.py add "Winnie the Pooh" python scripts/rfid_enroll.py listshows all linked tags;remove <uid>unlinks one.
If no reader is wired up (or you're developing on a laptop), RFID support silently disables itself - touch browsing always works.
Project layout
simon_player/
config.py Settings (~/.simon_player/config.json)
state.py Resume-position / volume / RFID-tag persistence
library.py Scans story folders into Album/Track objects
player.py python-vlc playback wrapper
rfid.py Optional background RFID polling thread
app.py The Kivy App - all playback/UI control logic
ui/
screens.py Hand-drawn touch widgets (buttons, seek bar, icons) + screens
simon_player.kv Layout/styling for the home & player screens
server/ Web admin server + browser UI (runs in Docker)
app/main.py FastAPI routes: browse, edit tags, upload, sync manifest
app/tags.py mutagen-based tag/cover writing (mp3/flac/m4a/ogg)
app/static/ The web frontend (vanilla HTML/CSS/JS)
tests/ API tests (pytest)
scripts/
install.sh Pi setup script
simon-player.service systemd kiosk unit template
simon-player-sync.service/.timer systemd library-sync units
sync_client.py Pulls the library from the web server (stdlib only)
rfid_enroll.py Grown-up CLI to link tags to stories
docker-compose.yml Runs the web server on port 8080 with ./data/library
library/README.md Story folder format
tests/ Unit tests for the (hardware-free) library/state logic
Running the tests
source .venv/bin/activate
pip install pytest
pytest
(Only library.py and state.py are covered by unit tests - they have no
hardware/Kivy dependency. player.py, rfid.py, and the UI need real
audio/touch/RFID hardware or a display and are exercised manually.)