Skip to content

Repository files navigation

ImmichKioskPi

A touchscreen photo frame and media browser for your own Immich server, built for a Raspberry Pi with a DSI touch display.

It boots straight into a fullscreen kiosk — no desktop, no mouse, no keyboard. Browse your albums, pinch to zoom photos, play videos with speed control, run a slideshow with transitions, and see the local weather forecast.

It also doubles as a speaker: pair a phone over Bluetooth and the Pi becomes its audio output with a now-playing panel over the slideshow, or connect Spotify directly — either by controlling whatever's already playing on the account, or by picking the Pi itself as a Spotify Connect device named "Kiosk".

Built with Flutter (native Linux), so it stays smooth on a Pi.


Features

Photos & video

  • Browse all your Immich albums with cover thumbnails
  • Full-screen viewer with pinch-zoom, double-tap zoom and swipe
  • Video playback via libmpv with a playback-speed selector (0.25×–2×), scrub, zoom, and a vertical volume slider with mute — the level and mute state carry over to the next video
  • Handles portrait and landscape media without cropping

Slideshow

  • Fade, slide, Ken Burns, or page turn transitions
  • Configurable interval, shuffle, and a blurred backdrop behind letterboxed shots
  • Multi-select albums and play them all as one combined slideshow
  • Images are fully decoded before they animate, so slides never flicker in

Private content

  • Opens Immich's server-side Locked Folder with your PIN, and re-locks when you leave

Indoor sensor

  • Reads a Govee H510x Bluetooth thermometer/hygrometer passively from its BLE broadcasts — no pairing, no account, no cloud
  • Indoor temperature and humidity appear on the weather panel, with a line chart of recent readings in the expanded view, and a low-battery warning

Weather overlay

  • Corner panel showing current conditions, tap to expand into a full-screen 7-day forecast with colour-coded icons
  • Uses Open-Meteono API key needed
  • Choose the location (UK postcode or place name), the screen corner, and °C/°F

Bluetooth speaker + now playing from your phone

  • Pair a phone once and the Pi becomes its A2DP audio sink — music plays through the Pi's own output, not the phone's
  • Shows the track that's playing, with album artwork, in a corner of the slideshow
  • Tap it to expand into a full player with play/pause, next, previous, repeat, shuffle, and a volume slider with mute — the controls drive the phone
  • Works with any app on the phone (Spotify, YouTube Music, podcasts) because it reads Bluetooth AVRCP rather than any one service's API
  • Optionally leave the audio on the phone (headphones) and use the kiosk purely as a remote control
  • Tidies itself away: the panel hides after a minute of nothing playing and reappears the moment playback resumes

Built-in Spotify support

  • Spotify Connect device: the Pi shows up as "Kiosk" in Spotify's own device picker on any phone, desktop or other Spotify app — pick it and audio streams straight to the Pi's speaker, no Bluetooth pairing involved
  • With Spotify Premium, the same now-playing panel can also control whatever's active on the account directly over the Spotify Web API — real seek, like/save the current track, and add it to a playlist — shown in preference to the generic AVRCP source whenever Spotify has something active, falling back to AVRCP the rest of the time

Built for a kiosk

  • On-screen panels drift slowly by a few pixels so nothing sits on the same pixels for long — guards an always-on display against image retention
  • Starts on boot, restarts automatically if it crashes
  • Restart / power-off buttons in Settings
  • Aggressive on-disk caching so it's fast and works well on a slow network
  • An About screen listing every library, licence and credit
  • Optional TV Remote button that switches to a companion remote-control app when one is running (see below)

Screenshots

Albums Album contents
Albums grid Album contents
The home grid. Long-press albums to pick several for one slideshow. An album's photos, with the Slideshow button in the bar.
Photo viewer Slideshow with weather
Photo viewer Slideshow with weather overlay
Full-screen viewer — pinch, double-tap or the +/− buttons to zoom. Photo-frame mode: the whole image fits, edges filled with a blur, and the weather panel in the corner.

Slideshow in motion

Ken Burns pan with a cross-fade between slides:

Slideshow animation

Now playing

When music is playing and no slideshow is running, the player takes over full-screen with a blurred album-art backdrop and large, quick-to-tap controls:

Full-screen player Controls close-up
Now-playing player, full screen Now-playing controls close-up
Tap it to shrink to a corner card and pick an album — it pops back up on its own after a few seconds if nothing came of it. Transport, repeat, shuffle, like and add-to-playlist all sized for a quick tap rather than precision.

Album artwork shown here is © its respective rights holder (in this screenshot, DreamWorks Animation / the "How to Train Your Dragon" soundtrack) — fetched live from Spotify to demonstrate the UI. This project claims no ownership of it.

(Also available as MP4 at higher quality.)

Screenshots use albums without people in them. On the albums grid every thumbnail is deliberately pixelated, since that page shows personal photos.


What you need

  • Raspberry Pi 5 (or Pi 4) with a DSI touch display — developed against a 10" 1200×1920 panel used in landscape
  • Raspberry Pi OS (Debian 13 "trixie") or similar, running the labwc Wayland session
  • An Immich server (v3.x) reachable on your network
  • A computer to build from, or build directly on the Pi

Quick start

1. Install the toolchain on the Pi

SSH into the Pi and run:

bash scripts/pi-setup.sh

This installs Flutter, the Linux build dependencies and libmpv. It needs sudo and downloads a few hundred MB, so give it a few minutes.

nano ~/.config/labwc/rc.xml

Edit that file

<touch deviceName="<your touch device>" mapToOutput="DSI-1" mouseEmulation="no"/>

Find the device name via libinput list-devices (or let Screen Configuration write the entry, then edit it). Reboot after. CNX Software tested exactly this and got all 10 points tracking correctly afterwards

2. Point the helper scripts at your Pi

On the machine you're building from:

cp scripts/local.env.example scripts/local.env

Edit it with your Pi's SSH details:

PI_HOST=pi@immich_kiosk_pi.local
PI_DIR=/home/pi/immich_kiosk_pi

This file is git-ignored, so your username and hostname stay out of the repo.

3. Add your Immich details

Create ~/.config/immich_kiosk_pi/config.json on the Pi (see config.example.json):

{
  "immichUrl": "https://immich.example.com",
  "apiKey": "YOUR_IMMICH_API_KEY"
}

Generate the API key in Immich under Account Settings → API Keys. Keep the file private:

chmod 600 ~/.config/immich_kiosk_pi/config.json

You can also enter these on first run, though a keyboard is easier than the on-screen fields.

4. Build and run

scripts/run.sh

This syncs the source to the Pi, builds a release binary there, and launches it on the display.

5. Start it on boot

On the Pi:

mkdir -p ~/.config/systemd/user ~/.config/labwc
cp deploy/immich_kiosk_pi.service ~/.config/systemd/user/
cp deploy/labwc-autostart ~/.config/labwc/autostart
chmod +x ~/.config/labwc/autostart
systemctl --user daemon-reload
systemctl --user enable --now immich_kiosk_pi

The service file uses systemd's %h and %U specifiers, so it works whatever your username is.


Optional setup

Power-off button

To let the in-app Restart and Power off buttons work without a password prompt, run once on the Pi:

sudo bash deploy/enable-poweroff.sh

Now playing from your phone

The now-playing panel reads Bluetooth AVRCP, so it works with whatever app your phone is using — no accounts or API keys.

Pair the phone with the Pi once:

bluetoothctl
# then, at the prompt:
#   power on
#   agent NoInputNoOutput
#   default-agent
#   pairable on
#   discoverable on
# accept the passkey on both the phone and here, then:
#   trust <PHONE_MAC>

Play something on the phone with media audio routed to the Pi. The panel appears in the slideshow; tap it to expand, tap again to shrink.

Trade-off worth knowing: AVRCP metadata rides along with the Bluetooth audio stream, so the phone's audio plays through the Pi, not the phone. That's inherent to this approach, not a choice of implementation.

Using headphones on the phone

If you'd rather the music kept playing on the phone — Bluetooth headphones, its own speaker — turn Settings → Now playing → "Play the audio on this device" off. The Pi then stops acting as an audio sink, but the panel keeps showing the track and the controls still work: this display becomes a pure remote.

That works because AVRCP's control channel is independent of the A2DP audio stream, so org.bluez.MediaPlayer1 survives with the audio profile switched off. The setting is re-applied whenever the phone reconnects, since PipeWire turns the audio profile back on by itself.

With the setting on, PipeWire routes the incoming stream to whatever output is active — a USB speaker, for example. Check the link with:

pw-link -l | grep bluez

(Note pactl may not be installed on Raspberry Pi OS; pw-link and wpctl are the PipeWire tools that are.)

Two things that look like faults but aren't: those links only exist while audio is actively streaming, so a paused track shows none; and wpctl inspect can report a stale bluez5.profile. Neither is a reliable way to tell whether audio routing is enabled — the setting itself is the source of truth.

The volume slider sets AVRCP absolute volume, i.e. the level the phone is sending — the same control as the phone's own volume buttons. It is not a local mixer level for the Pi's output.

Album artwork isn't part of AVRCP, so it's looked up from the free iTunes Search API using the artist and track name. Searching by track is markedly more reliable than by album, because AVRCP album strings often carry suffixes like (Deluxe Version) [Explicit].

Spotify

There are two independent Spotify features — set up either or both.

Web API control (like, playlists, and controlling whatever's already playing)

If you have Spotify Premium, the now-playing panel can control Spotify directly over the Spotify Web API instead of the generic AVRCP path — proper seek, shuffle, repeat, volume, like/unlike the current track, and add it to a playlist, on whichever Spotify Connect device is actually active (the phone, a speaker, the Pi itself if you've also set up the Connect device below) — not just what BlueZ happens to expose. It's shown in preference to the AVRCP source whenever Spotify has something active, and falls back to AVRCP the rest of the time — for a podcast app, YouTube Music, anything Spotify's API doesn't know about.

This part is a control layer only — it doesn't make the Pi an audio output by itself. Audio comes from wherever Spotify is already playing, unless that's also the Pi (see the Connect device below).

Setup, in Settings → Spotify:

  1. Create a free app at the Spotify Developer Dashboard.
  2. On that app, add http://127.0.0.1:8909/callback as a Redirect URI.
  3. In the kiosk, paste the app's Client ID and tap Connect. A browser window opens on the Pi's own screen (Chromium and Firefox are both present) for the one-time Spotify login; a small local server catches the redirect and finishes the connection automatically.

No client secret is needed or stored — the login uses OAuth Authorization Code with PKCE, so only the Client ID (not confidential) and a refresh token are kept, in the same config file as everything else. If you connected before liking/playlists existed, tap Reconnect once to re-authorise with the extra permissions (user-library-read, user-library-modify, playlist-read-private, playlist-modify-public, playlist-modify-private) — an existing refresh token doesn't gain scopes it wasn't originally granted.

Spotify replaced several library/playlist endpoints in February 2026 (the old per-type /me/tracks and /playlists/{id}/tracks now 403 silently); this integration uses the current /me/library and /playlists/{id}/items endpoints.

Spotify Connect device (play audio directly on the Pi)

Separately from the Web API control layer, the Pi can show up as its own selectable device — named "Kiosk" — in Spotify's Connect picker on any phone, desktop app, or other Spotify client. Picking it streams audio straight to the Pi over the network; no Bluetooth pairing, no OAuth login for this part.

This is powered by librespot, an open-source Spotify Connect client, run as a background service. The easiest way to get the binary is the raspotify package, but its own systemd service runs as root against ALSA directly — which fights with PipeWire and doesn't share your audio output. Use it only for the binary, then run librespot yourself as a user service so it talks to the same PipeWire sink as everything else:

# Install librespot's binary via the raspotify package...
sudo apt-get -y install curl
curl -sL https://dtcooper.github.io/raspotify/install.sh | sudo sh

# ...then disable raspotify's own root-level service — we run librespot
# ourselves, below.
sudo systemctl disable --now raspotify

# Install and start the user-level unit instead.
mkdir -p ~/.config/systemd/user ~/.cache/librespot
cp ~/immich_kiosk_pi/deploy/librespot.service ~/.config/systemd/user/
systemctl --user daemon-reload
systemctl --user enable --now librespot.service

"Kiosk" should now appear in Spotify's Connect device list within a few seconds. Requires Spotify Premium, same as librespot itself does.

On audio quality: the unit passes --bitrate 320, the highest Ogg Vorbis quality Spotify Connect carries ("Very High" in Spotify's own app) — the default is 160 if that flag is left off. True lossless (24-bit/44.1kHz FLAC) isn't reachable here or on any other Connect device: Spotify's Lossless tier only streams inside its own official apps, over a different pipeline than Connect entirely, so this is a Spotify-side limitation rather than something librespot or this setup can work around.

Indoor temperature sensor

The indoor reading comes from a Govee H510x (H5101/H5102/H5104/H5177), which broadcasts temperature and humidity in its Bluetooth LE advertisements.

The kiosk doesn't scan for it. Home Assistant already watches the same sensor full-time via its govee_ble integration, so the kiosk reads the value from Home Assistant's REST API instead. Two things scanning the same air gained nothing, and BLE scanning on the Pi's built-in radio makes Bluetooth audio stutter, because that radio shares one antenna with A2DP.

Configure it under Settings → Home Assistant: server URL, a long-lived access token and the entity IDs. Without a token the indoor reading is simply hidden.

Tokens are long and awkward to type on a touchscreen, so there's a helper that takes one on the Pi and checks it works before saving:

bash ~/immich_kiosk_pi/scripts/set-ha-token.sh

Create the token in Home Assistant under your user name → Security → Long-lived access tokens.

The 24-hour chart in the expanded weather panel comes from Home Assistant's history API, thinned to roughly one point per ten minutes so the chart doesn't try to draw thousands of segments.

If you want Bluetooth audio and BLE sensing at once, use two radios. A USB BLE dongle costs very little and removes the contention entirely: leave the built-in hci0 for audio and give Home Assistant the dongle. In Home Assistant, disable the Bluetooth config entry for the built-in adapter — it auto-discovers every adapter it finds, so otherwise it will scan on both. BlueZ only powers extra controllers at boot when AutoEnable=true is set in /etc/bluetooth/main.conf.

Turning the screen off with Alexa

The panel can be switched off by voice, with no cloud account, no Amazon developer account and nothing exposed to the internet. Three pieces:

  1. deploy/screen_control.py — a small HTTP service on the host. Home Assistant runs in a container and can't reach the host's Wayland session, so it can't call wlopm itself. This runs as the user who owns that session and binds to localhost only:

    /screen                 state as JSON
    /screen/on              /screen/off      /screen/toggle
    /screen/brightness?value=0-100
    

    Install it alongside the kiosk unit:

    cp deploy/screen-control.service ~/.config/systemd/user/
    systemctl --user enable --now screen-control.service
  2. A command_line switch in Home Assistant that curls it. Home Assistant uses host networking, so 127.0.0.1:8765 reaches the host service.

  3. emulated_hue, which presents that one switch to Alexa as a Philips Hue V1 bridge. Alexa has native Hue support, so nothing else is needed. Say "Alexa, discover devices", then "Alexa, turn off the kiosk screen".

Both YAML blocks are in deploy/homeassistant.yaml, ready to append to Home Assistant's configuration.yaml.

Name the device in the Alexa app carefully: if the name collides with an Echo, a speaker or a group, Alexa matches that instead and the light is never reached — the app button keeps working, so it looks like a voice problem when it isn't. "Kiosk" alone collided here; "Kiosk screen" was fine.

Two constraints worth knowing, both from Alexa rather than this project:

  • It must listen on port 80. Amazon stopped talking to other ports in the August 2019 Echo firmware.
  • The Pi needs a fixed address. Alexa caches the bridge by IP, so a DHCP lease change silently breaks it. A reservation on the router is the tidiest way; emulated_hue's host_ip must match.

Touch wakes it again — which is why "off" dims rather than powers down. Cutting the DSI output also cuts power to the touch controller, so the panel stops reporting touches altogether and nothing in software can wake it. Measured on this display: 13 touch events with the output on, none at all with it off.

So /screen/off sets the backlight to zero and leaves the output powered, and screen_control.py watches the touchscreen to turn it back up. /screen/off?deep=1 still powers the output right down, for when the extra saving matters more than being able to wake it by hand.

Devices to watch come from /proc/bus/input/devices — anything with a mouse handler, which is the touch panel and any real mouse. Keyboard-style devices are deliberately excluded: the paired phone's AVRCP media keys appear as one, and a track change shouldn't wake the screen. Reading the touchscreen needs membership of the input group.

Power changes and wakes are recorded in ~/.cache/immich_kiosk_pi/screen_control.log, which is the quickest way to tell whether a touch was seen at all.

TV Remote button

If you run a companion remote-control app on the same Pi, a TV Remote icon appears in the home screen's toolbar and switches to it with one tap. The button only appears while that app is actually running, so it stays out of the way otherwise.

It shells out to wlrctl to spot the window and focus it:

sudo apt-get install -y wlrctl

The app is matched by Wayland app_id, set by _remoteAppId in lib/screens/home_screen.dart (currently com.vwillcox.vidaa_remote) — change it there to point at your own app. Without wlrctl, or without that app running, the button simply never appears and nothing else is affected.

Locked Folder

Immich's Locked Folder is protected by a login session, which an API key can't unlock. To use it, the app needs your Immich account login. Run on the Pi:

bash deploy/set-immich-login.sh

It prompts for your email and password (hidden) and stores them in ~/.config/immich_kiosk_pi/config.json. A padlock icon then appears in the home screen's toolbar; tap it and enter your PIN.

Your password is stored in plain text in that file, readable only by your user. If you'd rather not do that, simply skip this step — everything else works without it.


Gestures

Where Gesture Action
Albums long-press start multi-select
Albums tap (while selecting) add / remove album
Photo pinch or double-tap zoom in and out
Photo drag (zoomed) pan
Photo swipe left / right previous / next
Photo swipe down close
Photo tap hide / show controls
Slideshow swipe left / right previous / next (pauses)
Slideshow swipe down exit
Slideshow tap hide / show controls
Video double-tap left / right skip back / forward 10s
Video double-tap centre play / pause
Video drag horizontally scrub
Video drag vertically volume
Video tap the speaker icon mute / unmute
Video drag the right-edge slider volume
Video swipe down close
Weather panel tap expand / collapse the forecast
Now playing panel tap expand / collapse the player

On-screen +/− zoom buttons are also provided as a fallback.


Settings

Tap the gear icon on the home screen:

  • Connection — Immich server URL and API key
  • Locked Folder — whether the account login is configured
  • Weather — on/off, location, screen corner, °C/°F
  • Now playing — show what the paired phone is playing, and which corner (the expanded player also carries volume and mute)
  • Slideshow — seconds per photo, transition style, shuffle
  • Storage — how much the photo cache is using, with a Clear button
  • Device — Restart and Power off
  • About — version, the open-source libraries used with their licences and links, and credits for code adapted from elsewhere

Development

The project is built on the Pi (it's an arm64 Linux target), but you can keep the source on another machine and sync it over:

scripts/sync.sh        # copy source to the Pi
scripts/run.sh         # sync, build release, restart the kiosk
scripts/run.sh debug   # sync, then flutter run with hot reload
scripts/shot.sh        # screenshot the Pi's display to a PNG

Everything is configured through scripts/local.env (see step 2), or you can override PI_HOST / PI_DIR as environment variables.

Handy service commands on the Pi:

systemctl --user restart immich_kiosk_pi
journalctl --user -u immich_kiosk_pi -f

The window is fullscreen and borderless by default. Set IMMICH_KIOSK_WINDOWED=1 to run it in a normal window while debugging.

Debug launch hooks

Environment variables that boot straight into one screen — handy for capturing screenshots or testing a screen in isolation. They're inert unless set.

Variable Opens
IMMICH_KIOSK_TEST_ALBUMGRID=<albumId> an album's asset grid (IMMICH_KIOSK_TEST_ALBUMNAME sets its title)
IMMICH_KIOSK_TEST_GALLERY=<albumId> the photo viewer (IMMICH_KIOSK_TEST_GALLERY_INDEX picks the photo)
IMMICH_KIOSK_TEST_SLIDESHOW=<albumId> the slideshow
IMMICH_KIOSK_TEST_VIDEO=<assetId> the video player
IMMICH_KIOSK_TEST_LOCKED=<pin> the Locked Folder, unlocked
IMMICH_KIOSK_TEST_LOCKED_VIDEO=<pin> the first locked video
IMMICH_KIOSK_TEST_ABOUT=1 the About screen
IMMICH_KIOSK_TEST_NOWPLAYING=1 the now-playing panel on a blank background

Screen burn-in

The weather and now-playing panels are the only things that stay put on an always-on display, so they drift continuously within a 24px radius, tracing a slow Lissajous path (17- and 23-minute periods on the two axes, recomputed every 20 seconds — about two pixels a step, below the threshold of notice).

The panels' margin is deliberately larger than the drift amplitude: if a panel clamped against a screen edge it would sit still there, which is the problem this is meant to solve. See lib/widgets/burn_in_drift.dart.

Project layout

lib/
  main.dart                    # app entry, providers, root routing
  theme.dart                   # dark, touch-first theme
  config/app_config.dart       # settings model
  models/immich_models.dart    # Album, Asset
  services/
    config_service.dart        # reads/writes config.json
    immich_service.dart        # Immich REST client + response caching
    locked_folder_service.dart # session login, PIN unlock, re-lock
    media_source.dart          # API-key vs session-token media access
    media_cache.dart           # on-disk image cache + memory tuning
    api_cache.dart             # cached API responses
    weather_service.dart       # Open-Meteo forecast and geocoding
    now_playing_service.dart   # BlueZ AVRCP over D-Bus + artwork lookup
  config/credits.dart          # attribution data shown on the About screen
  screens/                     # home, album, gallery, video, slideshow,
                               # locked folder, settings, setup, PIN pad,
                               # about
  widgets/                     # cached image, back button, weather overlay

How it talks to Immich

Immich v3 REST API, authenticated with the x-api-key header:

Purpose Endpoint
Album list GET /api/albums
Album contents POST /api/search/metadata with albumIds
Thumbnail / preview GET /api/assets/{id}/thumbnail?size=thumbnail|preview
Full image GET /api/assets/{id}/original
Video stream GET /api/assets/{id}/video/playback

The Locked Folder additionally uses POST /api/auth/login, POST /api/auth/session/unlock, then POST /api/search/metadata with visibility: locked, and POST /api/auth/session/lock on exit — these need a session token rather than an API key.

Caching

Images and API responses are cached under ~/.cache/immich_kiosk_pi — deliberately not in /tmp, which on Raspberry Pi OS is a RAM-backed tmpfs. The cache holds up to 20,000 files for a year, so restarts are near-instant. Clear it any time from Settings → Storage.


Troubleshooting

Nothing appears on screen Check the service is running: systemctl --user status immich_kiosk_pi. It needs the labwc Wayland session to already be up — that's why it's started from ~/.config/labwc/autostart.

Some thumbnails don't load The app retries with backoff and falls back to the full-size image. If it persists, the asset may not have a thumbnail generated in Immich yet.

Videos play as a blank blue frame Hardware video decoding can produce an unsampleable surface on this GL path, so the player forces software decoding. If you've changed that, change it back in lib/screens/video_player_screen.dart.

Text fields are hard to fill in There's no on-screen keyboard, so the server URL, API key and login are easiest to set by editing ~/.config/immich_kiosk_pi/config.json over SSH. The numeric PIN pad is custom-built and works fine by touch.

Harmless log noise Failed to create AudioController: Unable to find mixer control: Master is an ALSA probe from media_kit; audio still works.


Privacy

ImmichKioskPi talks only to your own Immich server and to Open-Meteo for the weather. There's no analytics and no third-party service. Your credentials live in ~/.config/immich_kiosk_pi/config.json on the device and are never committed — that file is git-ignored.


Third-party libraries

The same list is available on the device under Settings → About, so attribution travels with the app rather than living only here.

Built with Flutter (BSD-3-Clause, source).

Dart packages

Package Used for Licence
provider · src State management / dependency injection MIT
dio · src HTTP client for the Immich and weather APIs MIT
cached_network_image · src Images with auth headers and caching MIT
flutter_cache_manager · src On-disk cache, relocated to the NVMe MIT
file · src Filesystem abstraction for the custom cache MIT
media_kit · src Video playback and speed control MIT
media_kit_video Video render surface MIT
media_kit_libs_video Native video dependencies MIT
dbus · src Talks to BlueZ for phone media metadata MPL-2.0
crypto · src SHA-256 for the Spotify OAuth PKCE code challenge BSD-3-Clause
path · src Path joining for cache locations BSD-3-Clause
flutter_lints (dev) Lint rules BSD-3-Clause

System libraries

Library Used for Licence
mpv / libmpv · source Video decoding behind media_kit LGPL-2.1+ (details)
BlueZ · source Bluetooth stack — AVRCP metadata and control GPL-2.0+ / LGPL-2.1+
librespot Spotify Connect device ("Kiosk") — plays audio directly, run as its own systemd service MIT
GTK 3 Flutter's Linux embedder window LGPL-2.1+

Services

Service Used for Terms
Immich · src Your own photo server (the whole point) AGPL-3.0
Open-Meteo Weather forecast — no API key required Free for non-commercial use, CC BY 4.0
iTunes Search API Album artwork lookup Free, no key · Apple terms
Spotify Web API Playback control, liked songs and playlists when Spotify is connected Requires your own free Spotify Developer app + Premium account
postcodes.io · src UK postcode → coordinates MIT, data under OGL

Credits and sources

Almost all of the code here was written for this project, but these parts come from, or are adapted from, elsewhere:

  • Flutter project scaffoldinglinux/runner/*, .metadata, analysis_options.yaml and the initial main.dart were generated by flutter create and then modified (notably my_application.cc, changed to start fullscreen and borderless). Flutter SDK, BSD-3-Clause.
  • _NvmeFileSystem in lib/services/media_cache.dart is modelled on IOFileSystem from flutter_cache_manager (MIT), changed to store files under a fixed directory instead of the system temp directory.
  • Weather code descriptions and icon mapping in lib/services/weather_service.dart and lib/widgets/weather_overlay.dart follow the WMO weather-code table as published in the Open-Meteo API docs.
  • Immich API usage was derived from the Immich API documentation together with probing a live v3 server — in particular that album contents come from POST /api/search/metadata, and that the Locked Folder needs a session token rather than an API key.
  • Bluetooth now-playing uses BlueZ's AVRCP support over D-Bus (org.bluez.MediaPlayer1) for metadata and transport control. Album art is not part of AVRCP, so it is resolved separately from the iTunes Search API by artist + track title.
  • Material Design icons ship with Flutter (Apache-2.0).

No code was copied from Stack Overflow, blog posts or other projects.


Licence

MIT — do what you like with it, no warranty.

About

Touchscreen Immich photo/video browser for a Raspberry Pi 5 with a 10" DSI display (Flutter, Linux kiosk)

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages