A calm audio player for kids. No algorithm, no rabbit holes.
lauschi wraps audio providers behind a curated, card-based interface for children. Parents build visual content cards; kid taps a card, audio starts. No recommendations, no autoplay, no screen time spiral.
Currently supports Spotify and ARD Audiothek (free, no account needed). Built with Flutter. DACH-market focus (Hörspiele, Kindermusik), internationalizable. All data stays on-device.
MVP functional on Android and iOS. Grouping, catalog matching, NFC tag support, parent/kid modes working. Catalog curation pipeline covers ~120 DACH Hörspiel and music series.
Requires mise for tool management.
git clone git@github.com:EnTeQuAk/lauschi.git
cd lauschi
mise install # Flutter, Java 17, gh, uv
cp .env.example .env # developer keys for scripts
cp .env.app.example .env.app # app build config
mise run setup # flutter pub get + codegen
mise run dev # run on connected deviceTwo env files (both gitignored):
.env— Developer keys for tooling. Loaded by mise. Not passed to Flutter..env.app— App build config. Passed to Flutter via--dart-define-from-file.
.env.app:
ENABLE_SPOTIFY=false # feature flag
SPOTIFY_CLIENT_ID=... # required when Spotify enabled
SENTRY_DSN=... # optional, error tracking
SENTRY_ENVIRONMENT=development
ARD Audiothek works out of the box with no credentials, no account. Free
public broadcaster content. The app uses the ARD Audiothek GraphQL API
(api.ardaudiothek.de) to browse and stream children's audio content directly.
Spotify requires a client ID (PKCE flow, no client secret). Create a
Spotify app at https://developer.spotify.com/dashboard, add
lauschi://callback as a redirect URI, and copy the client ID.
For catalog curation tools (optional, not needed to build/run the app):
OPENCODE_API_KEY=... # for AI curation scripts
BRAVE_API_KEY=... # for catalog review web search
- Flutter + Dart: iOS and Android from one codebase
- Riverpod 3: state management
- Drift: local SQLite with schema migrations
- Spotify Web Playback SDK: Spotify audio via hidden WebView (EME/DRM).
The SDK's
player.htmlis hosted externally (requires HTTPS origin for Widevine). Seelib/core/spotify/spotify_config.dartfor the URL. - ARD Audiothek GraphQL API + just_audio: free streaming of public broadcaster content (no account required)
- go_router: navigation with parent PIN gate
- very_good_analysis: strict linting
- Sentry: crash reporting + session replay (EU region)
- AI-assisted catalog curation: pydantic-ai scripts using kimi-k2.5 for discovery and review, minimax-m2.5 for independent verification. Final approval is always human via a TUI review tool.
The curation pipeline uses AI models to discover, classify, and cross-check album data from Spotify. This is a developer-side build tool. No AI runs in the app itself, no AI-generated content is shown to kids, and neither ever will be. The models help sort through thousands of albums; a human reviews and approves the final result.
The catalog ships as curated YAML in assets/catalog/series.yaml, backed by per-series curation data in assets/catalog/curation/*.json.
series.yaml ← defines series (keywords, patterns, artist IDs)
│
▼
catalog-curate ← AI discovers + classifies albums from Spotify
│
▼
curation/*.json ← per-series: albums, include/exclude decisions, age notes
│
▼
catalog-review-ai ← AI reviews: excludes junk, proposes splits, fills gaps
│
▼
catalog-apply-splits ← executes split proposals into new curation JSONs
│
▼
catalog-verify ← second model verifies; auto-approves on agreement
│
▼
catalog-review (TUI) ← human reviews disagreements → writes to series.yaml
-
Add the series entry to
assets/catalog/series.yaml:- id: paw_patrol title: PAW Patrol keywords: ["PAW Patrol"] aliases: ["Paw Patrol - Das Hörspiel"] spotify_artist_ids: ["..."] episode_pattern: "Folge (\\d+)"
Series can have multiple
keywordsandaliasesfor fuzzy matching.episode_patternis a regex with one capture group for the episode number. -
Run AI curation to discover albums:
mise run catalog-curate -- --series "PAW Patrol"Creates
assets/catalog/curation/paw_patrol.jsonwith all albums classified as include/exclude. -
Run AI review:
mise run catalog-review-ai -- paw_patrol
Reviews the curation: excludes duplicates/compilations, proposes splits for sub-series, fills episode gaps. Uses Wikipedia + Spotify album details for verification.
-
Apply any splits:
mise run catalog-apply-splits # dry-run mise run catalog-apply-splits -- --apply # create new JSONs
-
Verify with a second model:
mise run catalog-verify -- paw_patrol
A second model (minimax-m2.5) independently verifies the review decisions. When both models agree, the series is auto-approved and written to
series.yaml. Disagreements are flagged for human review. -
Human review (for disagreements or manual inspection):
mise run catalog-review # all series mise run catalog-review -- paw_patrol # specific series
- Browse series, see AI decisions with overrides applied
t: toggle an album's include/excluden: add reviewer notesa: approve, writes toseries.yamlr: rejectTab: next unreviewed series
AI review is incremental and skips already-reviewed series:
mise run catalog-review-ai -- --all # only reviews new/unreviewed
mise run catalog-review-ai -- --all --force # re-review everything
mise run catalog-review-ai -- asterix --force # re-review one seriesOn --force, previous review decisions are fed to the AI so it builds on prior work.
When the AI review finds albums that belong in a separate series (different era, sub-series, spinoff), it proposes a split:
- Sub-series: Wieso? Weshalb? Warum? → JUNIOR, PROFIWISSEN, ERSTLESER, Vorlesegeschichten
- Production eras: Löwenzahn → CLASSICS (Peter Lustig) vs modern (Fritz Fuchs)
- Spinoffs: Die wilden Hühner → Die Wilden Küken
- Format variants: Sternenschweif Hörspiel → Sternenschweif Klassik (audiobook)
Splits are proposals in the JSON until apply-splits executes them. Each split creates a new curation JSON with the albums moved over, and adds exclude overrides to the parent.
All manual and AI review edits go to review.overrides in the curation JSON. The original AI curation in series.albums is never mutated. This means:
- Re-running curation produces a fresh base; overrides are preserved
- Every decision has an audit trail
- The JSON is git-diffable
CLI for quick edits:
mise run catalog-edit -- show asterix
mise run catalog-edit -- exclude asterix ALBUM_ID "standalone special"
mise run catalog-edit -- toggle asterix ALBUM_ID
mise run catalog-edit -- add asterix ALBUM_ID
mise run catalog-edit -- search "Asterix Folge 35"mise run catalog-check # cached validation against series.yaml
mise run catalog-check-fresh # live Spotify API validation
mise run catalog-audit # full L5 discography audit| Task | Description |
|---|---|
catalog-curate |
AI-curate a series (pydantic-ai + kimi-k2.5) |
catalog-review-ai |
AI review with three-way decisions |
catalog-apply-splits |
Execute split proposals |
catalog-review |
Human review TUI |
catalog-edit |
CLI for quick edits |
catalog-report |
Report gaps/dupes across all series |
catalog-check |
Validate series.yaml (cached) |
catalog-check-fresh |
Validate series.yaml (live API) |
catalog-audit |
Full discography audit |
catalog-discover |
Search for new artist candidates |
catalog-titles |
Discover new series titles |
mise run dev # flutter run with .env
mise run test # flutter test
mise run analyze # flutter analyze
mise run setup # pub get + codegen
mise run check # format + analyze + test (CI equivalent)Prerequisites:
- Android SDK (install via
mise installor Android Studio) - Java 17 (installed by mise)
- A connected Android device or emulator with API 24+
# Debug APK
mise run build # builds APK with codegen + .env
# Install on connected device
flutter install --debug
# Or run directly
mise run devThe debug APK lands in build/app/outputs/flutter-apk/app-debug.apk.
For release builds, you'll need a signing key configured in
android/app/build.gradle. See the
Flutter Android deployment docs.
# Run all tests
mise run test
# Run a single test file
flutter test test/core/catalog/catalog_service_test.dart --dart-define-from-file=.env.app
# Run with coverage
flutter test --coverage --dart-define-from-file=.env.appTests don't require a device and run on the Dart VM. Integration tests (Patrol) require a connected device or emulator.
iOS builds require macOS with Xcode. The project uses Codemagic for CI/CD on
iOS. Local iOS development works with flutter run on a Mac with Xcode
installed and an iOS 14.0+ device/simulator.
- Fork the repo and create a branch from
main mise install && mise run setup- Make your changes, run
mise run checkto verify - Open a PR. CI runs format, analyze, test, and debug build.
Keep commits focused and messages in imperative mood ("Fix bug" not "Fixed
bug"). See the project's analysis_options.yaml for lint rules.
Copyright © 2025-2026 Christopher Grebs. Licensed under GPL-3.0.
