Ich hätte gerne eine kleine Spiele App für Kids auf dem Tablet. Am bestens als Browser Game. Die App soll nach Möglichkeit eingeschrieben sein, dass die Kinder das Spiel nicht verlassen können. Vermutlich ist das schwer bis unmöglich unter Android. Wenn nicht möglich können wir auch eine API bauen. Die Spiele sollen Reaktion, Gefühl für Bedienung und das kindliche wohl hervorheben. Es können einfache Games wie etwas suchen, einfach Rätsel, etwas ausmalen, nachmalen von zahlen und Buchstaben. Wichtig due Anforderungen sollten den alter entsprechen. Aktuell sind die Kinder 2 und 5. Aber sie werden älter und das Talent steigt. Auch Sprachausgabe und Spracherkennung wären toll. Wir können gerne KI Tools über ollama Einbinden. Magst du mir ein erstes Konzept bauen?
profile
kimi-k2.5:cloud
Nachgedacht für 26 Sekunden
Hier ist ein umfassendes Konzept für "KinderWelt" – eine skalierbare, browserbasierte Spiele-Plattform mit Kiosk-Modus und KI-Integration.
1. Technische Architektur
Kern-Stack
Frontend: PWA (Progressive Web App) mit Vanilla JS/TypeScript + Canvas API/WebGL
UI-Framework: Kein schweres Framework – rein CSS-Animationen für 60fps auf Tablets
Audio: Web Speech API (TTS) + Web Audio API + optional TensorFlow.js (für lokale Spracherkennung)
Backend (optional): Node.js/Express als Proxy zu Ollama (localhost)
Speicher: IndexedDB für Offline-Fortschritte & Cache
Der Kiosk-Modus (Android-Strategie)
Da reiner Browser-Lock schwierig ist, hier eine zweistufige Lösung:
Option A: PWA + Android Screen Pinning (Einfach)
PWA im Fullscreen (display: standalone im Manifest)
Android "Bildschirm fixieren" (Screen Pinning) aktivieren
Keine Hardware-Buttons sichtbar, aber Eltern können es mit Tastenkombination entsperren
Option B: Dedicated Wrapper-App (Empfohlen)
Einfache Android-App (Kotlin) mit WebView
Überschreibt onBackPressed(), onPause(), blockiert Home-Button (mit Systemrechten)
Startet automatisch den Browser im Kiosk-Modus
Vorteil: Kind kann wirklich nicht raus, außer Eltern geben Passwort ein
2. Spielkonzept & Altersstufen
Modul A: "Entdecker" (2-3 Jahre)
Ziel: Grobmotorik, Ursache-Wirkung, Freude am Tippen
Blubber-Blasen: Bunte Kreise erscheinen, platzen bei Berührung mit "Pop"-Sound
Tier-Farm: Großes Tier antippen → realistischer Sound + Animation (TTS: "Das ist eine Kuh!")
Farben-Explosion: Ganzer Bildschirm färbt sich bei Touch, leichte Vibration (haptisches Feedback)
Fang den Stern: Langsam fallende Sterne einsammeln (kein Game Over, nur positive Verstärkung)
Modul B: "Kleine Forscher" (4-5 Jahre)
Ziel: Feinmotorik, Mustererkennung, erste Logik
Zahlen-Folgen: Connect-the-Dots mit Audio ("Tippe auf die 3!")
Memory-Leicht: 3x3 Karten mit Tieren, keine Zeitbegrenzung
Formen-Sortierer: Drag & Drop (Kreis in runde Löcher, Viereck in eckige)
Einfaches Malen: "Zauberstift" (automatisch innerhalb von Linien bleiben)
Modul C: "Smart Kids" (6+ Jahre) [Zukunftssicher]
Ziel: Kognitive Entwicklung, Kreativität
KI-Mitmachgeschichten: Kind sagt ein Wort, Ollama generiert nächsten Satz (lokal, datensicher!)
Buchstaben-Nachzeichnen: Pfadverfolgung mit Präzisions-Messung
Logik-Rätsel: "Welches Tier passt nicht dazu?" mit Sprachausgabe der Fragen
3. KI-Integration (Ollama)
Da Datenschutz bei Kindern kritisch ist, läuft alles lokal auf dem Tablet/Server:
javascript
Einklappen
Speichern
Kopieren
1
2
3
4
5
6
7
8
9
⌄
// Beispiel: Bildbeschreibung für "Was ist das?" Spiel
const ollamaRequest = {
model: "llava-phi3", // Leichtes Vision-Modell
prompt: "Beschreibe dieses Bild für ein 5-jähriges Kind in 3 einfachen Sätzen.",
images: [base64Image]
};
// Spracherkennung via Whisper (lokal)
// TTS via Piper (Ollama Integration) oder Web Speech API
KI-Spiele-Ideen:
"Erzähl mir was": Kind malt etwas, KI errät es (Bild-zu-Text)
Intelligenter Helfer: Wenn Kind bei einem Rätsel 3x falsch tippt, gibt die KI keineswegs die Lösung, sondern einen kindgerechten Hinweis ("Schau mal, die Form hat runde Ecken...")
4. UI/UX Design-Prinzipien
Visuelles Design
Keine Text-Menüs: Nur Icons + Sprache (multilingual)
Große Touch-Targets: Mindestens 80x80px (Kindliche Motorik!)
Hoher Kontrast: Saturierte Farben, aber nicht grell (Material You für Kids)
Keine Werbung/Popups: Vollständig abgeschottete Umgebung
Interaktions-Muster
Immediate Feedback: <100ms Reaktionszeit auf Touch
Positive Verstärkung: Sternchen-Animationen, Applaus-Sounds (keine negativen Sounds bei Fehlern)
Parental Gate: Für Einstellungen: "Halte beide Ohren gleichzeitig gedrückt" (physisches Rätsel statt Mathe-Aufgabe)
5. Implementierungs-Roadmap
Phase 1: MVP (4-6 Wochen)
PWA-Grundgerüst mit Fullscreen-API
Modul A (2 Jahre): Blubber-Blasen + Tier-Farm
Einfacher Kiosk-Mode via Android Screen Pinning
Web Speech API (TTS)
Phase 2: Erweiterung (8-10 Wochen)
Wrapper-App für echten Lock-Mode
Modul B (5 Jahre): Zahlen-Folgen + Memory
Zeichen-Canvas mit simplem "Stay-in-lines" Algorithmus
Offline-Speicherung der Fortschritte
Phase 3: KI-Integration (12+ Wochen)
Ollama-Setup auf lokalem Server/Tablet
Bilderkennungs-Spiele
Adaptive Schwierigkeit (Spiel merkt sich, wenn Kind schnell ist)
6. Schnelle Starter-Datei (Proof of Concept)
Hier ein erster HTML-Prototyp für den Kiosk-Modus + einfaches Spiel:
html
Einklappen
Speichern
Kopieren
Vorschau
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
⌄
⌄
⌄
⌄
⌄
⌄
⌄
⌄
⌄
KinderWelt
7. Empfehlung für Android-Tablet
Für deinen Use Case empfehle ich:
Samsung Kids Mode oder Google Family Link als erste Barriere
Darüber: Die Wrapper-App (APK), die den Browser im Immersive Mode startet
Ollama läuft im Hintergrund auf dem Tablet (wenn leistungsstark genug, z.B. Samsung Tab S9) oder auf einem Raspberry Pi im Netzwerk
Nächste Schritte:
Soll ich den Code für die Android-Wrapper-App (Kotlin) liefern?
Ollama-Setup-Anleitung für lokale Sprachverarbeitung?
Detaillierte Spielmechanik für einen bestimmten Bereich (z.B. das Nachzeichnen)?
Das System wächst mit den Kindern mit – du kannst einfach neue Module als JavaScript-Dateien hinzufügen, ohne die App neu zu installieren!
Lass uns die App als Docker hinter einem recerced Proxy planen. Das ermöglicht die Nutzung von überall, und die ki liegt im gleichen Netzwerk wie das Game selbst. Am besten wir verifizieren die Anmeldung via Authentficator App push. Das spart uns den Umweg Benutzername und Passwort freizugeben. Weitere Idee eine jellyfin Integration. Was hältst du von der Idee. Bestimmte Serien können direkt gestreamt werden.
profile
kimi-k2.5:cloud
Nachgedacht für 3 Minuten
Das ist eine ** Enterprise-Grade-Architektur** für eine Kinder-App! Die Kombination aus öffentlicher Erreichbarkeit, KI-Pipeline und Medien-Streaming erfordert sorgfältige Sicherheitsplanung – besonders bei Minderjährigen.
Hier ist das erweiterte Cloud-native Konzept mit Docker-Stack:
1. Netzwerk-Architektur (Zero-Trust)
Einklappen
Speichern
Kopieren
1
2
3
4
5
6
7
8
Internet → Traefik (SSL/Let's Encrypt) → Authelia (Auth/2FA) → Services
↓
┌──────────────────────┼──────────────────────┐
↓ ↓ ↓
Game-App (Nginx) Ollama (AI) Jellyfin
(Port 3000) (Port 11434) (Port 8096)
↑ ↑ ↑
Shared Docker Network: kids-zone (isoliert, intern)
Warum diese Trennung?
Ollama ist nicht direkt exposed (nur intern über http://ollama:11434 erreichbar)
Jellyfin wird über die Game-UI "verpackt" (kein direkter Zugriff auf Port 8096 von außen)
Alle externen Requests müssen durch Authelia (Auth-Layer)
2. Authentifizierungs-Strategie: "Passwordless + 2FA Push"
Da du "Authenticator App Push" und "kein Passwort freigeben" willst, empfehle ich WebAuthn/Passkeys mit Authentik (nicht Authelia, da Authentik bessere Passwordless-Flows hat):
Ablauf für Eltern:
Registrierung: Scan QR-Code mit Authenticator-App (Authy/Google Authenticator) → TOTP Secret wird gespeichert
Login:
Option A: Passkey/FIDO2 (FaceID/Fingerprint auf Handy → Push-Benachrichtigung zum Bestätigen)
Option B: Magic Link per Email + TOTP-Code aus App
Option C: Session-Token (einmalig QR-Code scannen für dauerhaftes Tablet im Haus)
Wichtig: Kinder sollen gar nicht authentifiziert werden – sie landen direkt im Game nach dem Öffnen der URL. Der Auth-Layer schützt nur vor externen Zugriffen.
3. Jellyfin-Integration (Kinder-Safe)
Technische Umsetzung:
Proxy-Route: /api/media/* → Jellyfin API
UI-Integration: Custom Video-Player im Game-Canvas
Content-Filter: Nur "Kids"-Bibliothek sichtbar (Jellyfin User-Management)
javascript
Einklappen
Speichern
Kopieren
1
2
3
4
5
6
7
8
⌄
⌄
// Beispiel: Serie abrufen aus Jellyfin
const getKidsEpisode = async () => {
const response = await fetch('/api/jellyfin/Items?ParentId=KIDS_LIBRARY_ID', {
headers: { 'X-Emby-Token': '${JELLYFIN_API_KEY}' }
});
// Direkter Stream-URL für HTML5-Player
return `/api/jellyfin/Videos/${itemId}/stream.mp4?Static=true`;
};
⚠️ Rechtliche & Sicherheits-Warnung
Jellyfin öffentlich exposen ist hochriskant:
Urheberrecht: Streaming eigener gerippter DVDs ist legal im Privathaushalt, aber öffentlich erreichbar? Grauzone.
Bandbreite: Unverschlüsselte Streams können abgegriffen werden
Kinder-Inhalte: Öffentliche Verfügbarkeit von Kinder-Medien zieht unerwünschte Aufmerksamkeit an
Empfehlung:
VPN-Tunnel (WireGuard/Tailscale) statt öffentlichem Reverse Proxy für Jellyfin
Oder: Cloudflare Tunnel mit Access-Policies (nur deine IP-Adressen erlauben)
4. Docker-Compose Stack
yaml
Einklappen
Speichern
Kopieren
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
⌄
⌄
⌄
⌄
⌄
⌄
⌄
⌄
⌄
⌄
⌄
⌄
⌄
⌄
⌄
⌄
⌄
⌄
⌄
⌄
⌄
⌄
⌄
⌄
⌄
⌄
⌄
⌄
⌄
version: '3.8'
networks:
kids-zone:
driver: bridge
internal: false # Ollama & Jellyfin nur intern
services:
# Reverse Proxy + SSL
traefik:
image: traefik:v2.10
command:
- "--api.dashboard=true"
- "--providers.docker=true"
- "--entrypoints.websecure.address=:443"
- "--certificatesresolvers.letsencrypt.acme.tlschallenge=true"
- "--certificatesresolvers.letsencrypt.acme.email=admin@deinedomain.de"
- "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json"
ports:
- "443:443"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./letsencrypt:/letsencrypt
networks:
- kids-zone
# Authentik (Identity Provider mit 2FA/Push)
authentik:
image: ghcr.io/goauthentik/server:2023.10
environment:
- AUTHENTIK_SECRET_KEY=${AUTHENTIK_KEY}
- AUTHENTIK_ERROR_REPORTING__ENABLED=false
# PostgreSQL & Redis für Authentik (hier weggelassen für Kürze)
labels:
- "traefik.enable=true"
- "traefik.http.routers.authentik.rule=Host(`auth.deinedomain.de`)"
- "traefik.http.routers.authentik.tls.certresolver=letsencrypt"
networks:
- kids-zone
# Kinder-Spiel-App
kids-game:
build: ./game-app # Node.js/Nginx Image
environment:
- OLLAMA_URL=http://ollama:11434
- JELLYFIN_URL=http://jellyfin:8096
- JELLYFIN_API_KEY=${JELLYFIN_KEY}
labels:
- "traefik.enable=true"
- "traefik.http.routers.kids-game.rule=Host(`spiele.deinedomain.de`)"
- "traefik.http.routers.kids-game.tls.certresolver=letsencrypt"
# Authelia/Authentik Middleware für Schutz
- "traefik.http.routers.kids-game.middlewares=authentik@file"
networks:
- kids-zone
depends_on:
- ollama
- jellyfin
# Ollama (KI) - nur intern erreichbar!
ollama:
image: ollama/ollama:latest
volumes:
- ./ollama-models:/root/.ollama
environment:
- OLLAMA_ORIGINS=*
networks:
- kids-zone
# Keine Ports exposed! Nur über kids-game erreichbar
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: 1
capabilities: [gpu] # Für GPU-Beschleunigung
# Jellyfin (Media) - nur intern!
jellyfin:
image: jellyfin/jellyfin:latest
volumes:
- ./media:/media:ro
- ./jellyfin-config:/config
networks:
- kids-zone
# Kein Port-Mapping! Zugriff nur über kids-game Proxy
5. Sicherheitskonzept für öffentliche Erreichbarkeit
Da es um Kinder geht und öffentlicher Zugriff besteht:
A. Network-Policies
yaml
Einklappen
Speichern
Kopieren
1
2
3
4
5
6
7
⌄
⌄
⌄
⌄
# In der compose zusätzlich:
ollama:
networks:
kids-zone:
aliases:
- ollama-internal
# Firewall: Nur kids-game darf zugreifen
B. Content Security Policy (CSP)
Im Game-Frontend (Nginx-Header):
Einklappen
Speichern
Kopieren
1
2
3
4
Content-SSecurity-Policy: default-src 'self';
connect-src 'self' https://auth.deinedomain.de;
media-src 'self' blob:;
script-src 'self' 'unsafe-inline'; # Für Canvas-Spiele nötig
C. Rate-Limiting (Traefik)
yaml
Einklappen
Speichern
Kopieren
1
2
3
⌄
labels:
- "traefik.http.middlewares.ratelimit.ratelimit.average=100"
- "traefik.http.middlewares.ratelimit.ratelimit.burst=50"
6. Jellyfin-Integration im Game
"Medien-Ecke" für die Kinder
Eltern-UI: /admin → Serien freischalten (Authentik-geschützt)
Kind-UI: Großer Button "Cartoons schauen" → Lädt Thumbnails von Jellyfin API → HTML5-Player im Vollbild
javascript
Einklappen
Speichern
Kopieren
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
⌄
⌄
⌄
// Game-Client Code
class MediaPlayer {
async loadEpisodes() {
// Proxy durch eigenes Backend (sicher, da API-Key nicht im Client)
const res = await fetch('/api/media/kids');
const episodes = await res.json();
// Zeige Cover-Buttons (groß für Touch)
}
play(videoId) {
const player = document.createElement('video');
player.src = `/api/media/stream/${videoId}`;
player.controls = false; // Eigene kindgerechte Controls (große Play-Buttons)
player.requestFullscreen();
}
}
7. Kritische Empfehlungen
❌ Was ich nicht empfehle:
Jellyfin direkt öffentlich: Zu riskant. Nutze stattdessen Cloudflare Access oder Tailscale für "von überall"
Kamera/Mikrofon für 2-Jährige im Browser: DSGVO-Problem. Speichere keine Daten, verarbeite KI lokal (was du ja planst - gut!)
Authenticator-Push ohne Backup: Wenn das Handy kaputt ist, kommt niemand rein → Recovery-Codes drucken
✅ Bessere Alternative zu "öffentlich erreichbar":
Tailscale/Headscale (Mesh VPN):
Tablet ist im VPN-Netzwerk
Keine öffentlichen Ports nötig
Du hast "von überall" Zugriff (vom Handy aus)
Jellyfin bleibt intern
Kein Authentik nötig, einfacher JWT-Auth reicht
Zusammenfassung
Deine Architektur ist machbar, aber over-engineered für eine Kinder-App. Für 2+5 Jahre alte Kinder würde ich empfehlen:
Lokale Instanz im Haus (kein öffentlicher Zugriff)
Tailscale für Eltern-Zugriff von unterwegs
Einfacher JWT statt Authentik (nur für Eltern-Login)
Jellyfin separat im VPN, nicht im Game integriert (Kinder sollen spielen, nicht streamen 😉)
Willst du wirklich den Full-Enterprise-Stack mit öffentlicher Domain? Dann liefere ich dir die vollständige Traefik + Authentik + Compose-Konfiguration. Aber überlege dir die Datenschutz-Implikationen (DSGVO bei öffentlicher Kinder-App).
Nächster Schritt: Soll ich die Tailscale-Alternative (sicherer, einfacher) oder den öffentlichen Reverse-Proxy (komplexer, flexibler) detaillieren?
Ok, npm ist bereits vorhanden. Die App ist nur für eigenen Gebrauch. Als Sicherheitsbedenken zur Kenntnis genommen. Es geht um Urlaub oder besuch bei Freunden.
Freigabe war mir wichtig, das die Kinder die App zwar starten können aber halt nur nach Freigabe durch die Eltern nutzen können. Auch jellyfin ist vorhanden. Und soll einfach mit integriert werden. Im Idealfall über einen Nutzer mit den Rechten des Kindes ( nicht altersgerecht=nicht auswählbar).
VPN ist eine Möglichkeit ist aber mehr Traffic und fordert mehr Bandbreite als eine einfache ssl Verbindung über Reversed Proxy.
Offline Variante ohne KI und ohne Jellyfin klingt super. Vielleicht mit lokalen Medien
profile
kimi-k2.5:cloud
Nachgedacht für eine Minute
Perfekt! Das ist ein pragmatisches Homelab-Setup – deutlich sinnvoller als Enterprise-Overhead. Mit NPM (Nginx Proxy Manager) hast du bereits das beste Tool für SSL-Offloading.
Hier ist das pragmatische Konzept für den Familien-URLaubs-Modus:
1. Architektur: "Freigabe-Schleuse" mit NPM
Einklappen
Speichern
Kopieren
1
2
3
4
5
6
7
8
9
10
11
12
Internet → NPM (443) → [Entscheidung]:
│
┌─────────────────┼─────────────────┐
│ │
Kein Token Token vorhanden
│ │
├─ Static ├─ Game API
├─ "Warte auf ├─ Jellyfin Proxy
│ Freigabe" └─ Ollama (optional)
│
└─ Parental-Gate
(QR-Code / TOTP)
Warum kein VPN doch besser ist (kurz):
Dein Bandbreiten-Argument stimmt für das Streaming, aber für Jellyfin im Urlaub ist ein WireGuard-Tunnel oft stabiler bei Hotel-WLANs (die oft HTTPS filtern/throtteln). Aber wir machen es mit NPM wie gewünscht.
2. Das "Parental Gate" System
Da die Kinder die App starten können, aber erst nach Freigabe spielen:
Option A: TOTP-Daily-Code (Einfachst)
Eltern haben Google Authenticator mit einem Secret (z.B. FAMILIE2024)
Code wechselt alle 30 Minuten (oder täglich für Urlaub)
Kinder sehen nur: "Frage Mama nach dem Code"
Eingabe im Tablet entsperrt Session-Cookie für 2 Stunden
Option B: Push-Freigabe (Eleganter)
Tablet zeigt QR-Code (Session-ID)
Eltern scannen mit Handy → Web-App öffnet sich → Button "Freigeben für 30 Min"
Tablet pollt alle 5 Sekunden /api/status → Status ändert sich zu "grün"
Implementierung (Node.js/Express):
javascript
Einklappen
Speichern
Kopieren
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
⌄
⌄
⌄
⌄
// Backend (ein Teil deiner Game-App)
const sessions = new Map();
// Eltern-Endpunkt (geschützt via NPM Basic Auth oder separate Domain)
app.post('/api/parent/approve', (req, res) => {
const { sessionId, duration } = req.body;
sessions.set(sessionId, {
approved: true,
expires: Date.now() + (duration * 60000),
user: 'kind-max' // Für Jellyfin-User-Auswahl
});
});
// Tablet checkt Status
app.get('/api/status/:sessionId', (req, res) => {
const session = sessions.get(req.params.sessionId);
if (!session) return res.json({ status: 'pending' });
if (session.expires < Date.now()) return res.json({ status: 'expired' });
res.json({
status: 'approved',
jellyfinUser: session.user,
token: generateJWT(session)
});
});
3. NPM (Nginx Proxy Manager) Konfiguration
A. SSL-Zertifikate
kinder-tablet.deinedomain.de (oder DuckDNS/Cloudflare-Tunnel)
Let's Encrypt über NPM GUI
B. Proxy Hosts
1. Haupt-Game (Standard)
Einklappen
Speichern
Kopieren
1
2
3
4
Domain: spiele.zuhause.de
Forward: http://kids-game:3000
SSL: Aktiv
Access List: Öffentlich (für Kinder-Startseite)
2. Admin/Eltern-Freigabe (Geschützt)
Einklappen
Speichern
Kopieren
1
2
3
4
5
Domain: freigabe.zuhause.de
Forward: http://kids-game:3000/admin
SSL: Aktiv
Access List: IP-Whitelist (nur euer Handy/WLAN)
ODER HTTP Basic Auth (User/Pass für Eltern)
3. Jellyfin-Proxy (Intern)
Einklappen
Speichern
Kopieren
1
2
3
4
Domain: media-intern.zuhause.de (nicht exposed!)
Forward: http://jellyfin:8096
Block Common Exploits: An
Access List: Nur localhost/Intern (über Docker-Netzwerk)
Wichtig: Jellyfin bekommt keinen öffentlichen DNS-Eintrag! Nur über die Game-App erreichbar.
4. Jellyfin-Integration: Altersfilter
Setup in Jellyfin:
User kind-2jahre anlegen → Max Parental Rating: "Toddlers"
User kind-5jahre anlegen → Max Parental Rating: "Children"
Bibliotheken taggen mit entsprechenden Ratings
API-Integration:
javascript
Einklappen
Speichern
Kopieren
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
⌄
⌄
⌄
⌄
⌄
// Game-Backend (Proxy zu Jellyfin)
app.get('/api/media/episodes', async (req, res) => {
// Token kommt aus der Freigabe-Session (siehe oben)
const userId = req.session.jellyfinUser; // 'kind-2jahre' oder 'kind-5jahre'
const response = await fetch(`http://jellyfin:8096/Users/${userId}/Items`, {
headers: {
'X-Emby-Token': process.env.JELLYFIN_API_KEY
}
});
const data = await response.json();
// Filtere nur "Continue Watching" oder "Next Up"
const safeItems = data.Items.filter(item =>
item.ParentalRating <= (userId.includes('2') ? 1 : 5)
);
res.json(safeItems);
});
// Video-Stream (kein Direct-Expose)
app.get('/api/media/stream/:id', async (req, res) => {
// Proxy den Stream durch dein Backend (kein CORS, kein direkter Jellyfin-Zugang)
const stream = await fetch(`http://jellyfin:8096/Videos/${req.params.id}/stream?Static=true`, {
headers: { 'X-Emby-Token': process.env.JELLYFIN_API_KEY }
});
stream.body.pipe(res);
});
UI im Game:
Große Kacheln: Nur Serien-Titel-Bilder (keine Beschreibung, die Kinder können nicht lesen)
Auto-Play: Nächste Folge startet automatisch (kein "Was soll ich gucken?"-Stress)
Maximale Zeit: Game-Timer zeigt Sanduhr (wenn Eltern nur 20 Min freigegeben)
5. Offline-Modus: "Urlaubs-Paket"
Da du weniger Bandbreite willst: Pre-Download für Offline
Technik:
Service Worker (PWA) cached Spiele-Logik
IndexedDB speichert 3-4 Folgen Peppa Wutz lokal (als Blob)
Sync API: Wenn wieder online, sync Fortschritt (optional)
Docker-Volume Strategie:
yaml
Einklappen
Speichern
Kopieren
1
2
3
4
5
⌄
⌄
⌄
services:
kids-game:
volumes:
- ./offline-media:/app/public/media:ro # Lokale MP4s für Offline-Modus
- ./game-cache:/app/cache # Service Worker Cache
Offline-Jellyfin (lokal only):
Ein zusätzlicher Jellyfin-Container mit lokalen Dateien (nicht NAS-gemountet)
Nur kleine, transkodierte Dateien (720p, 2MBit) für Tablet-Speicher
Im Urlaub: Switch auf localhost:8096 statt Domain
6. Docker-Compose (Pragmatik-Version)
yaml
Einklappen
Speichern
Kopieren
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
⌄
⌄
⌄
⌄
⌄
⌄
⌄
⌄
⌄
⌄
⌄
⌄
⌄
⌄
⌄
⌄
version: '3.8'
services:
# Die Game-App (dein NPM-Target)
kids-app:
image: node:18-alpine
working_dir: /app
volumes:
- ./game-code:/app
- ./downloads:/app/public/downloads:ro # Offline-Videos
command: sh -c "npm install && npm start"
environment:
- JELLYFIN_URL=http://jellyfin:8096
- JELLYFIN_API_KEY=${JELLYFIN_KEY}
- SESSION_SECRET=${SECRET}
networks:
- kidsnet
# Jellyfin (nur intern!)
jellyfin:
image: jellyfin/jellyfin
volumes:
- ./jellyfin-config:/config
- ./media-kids:/media:ro # Nur Kids-Content mounten!
networks:
- kidsnet
# Keine ports! Nur über kids-app erreichbar
# Optional: Ollama für Zuhause
ollama:
image: ollama/ollama
volumes:
- ./ollama:/root/.ollama
networks:
- kidsnet
deploy:
resources:
limits:
memory: 2G # Für schwaches Urlaubs-Tablet begrenzt
networks:
kidsnet:
driver: bridge
7. Der "Urlaubs-Workflow"
Zuhause vorbereiten:
Tablet mit NPM-URL konfiguriert
5 Folgen Lieblingsserie herunterladen (via Jellyfin Download-Funktion)
Offline-Cache leeren, neu füllen
Im Hotel:
Tablet an: Zeigt "Warte auf Freigabe" mit QR-Code
Papa scannt Code → "Freigeben für 30 Min" → Kinder spielen
Nach 30 Min: Screen lockt wieder (oder wechselt zu "Pause machen" Animation)
Kein Internet im Hotel?:
App erkennt navigator.onLine === false
Zeigt nur Offline-Spiele + heruntergeladene Videos
Keine Freigabe nötig (Eltern sind ja da)
Nächster konkreter Schritt: Soll ich die Service-Worker-Implementierung für den Offline-Modus codieren, oder das Parental-Gate mit QR-Code skripten? Beides ist reiner Vanilla-JS/Node.js Code, passt in dein NPM-Setup.
Wie läuft das ganze denn jetzt ab. Docker + APK? Ja bereite gerne alles vor
profile
kimi-k2.5:cloud
Nachgedacht für 46 Sekunden
Hier ist der komplette Bauplan mit allen Dateien, die du brauchst. Wir haben einen Hybrid-Stack: Der Docker-Server ist das "Gehirn", die APK ist der "Kiosk-Rahmen" mit Offline-Cache.
Architektur-Überblick
Einklappen
Speichern
Kopieren
1
2
3
4
5
6
7
8
9
10
11
12
13
┌─────────────────┐ WiFi/4G ┌──────────────────┐
│ Tablet │ ═══════════════════════►│ Docker-Server │
│ (Android APK) │ │ (Zuhause/VPS) │
│ │ │ │
│ ┌─────────────┐ │ HTTP/HTTPS │ ┌──────────────┐ │
│ │ WebView │ │◄═══════════════════════════│ │ Node.js │ │
│ │ (PWA Mode) │ │ │ │ Game-API │ │
│ │ │ │ (Fallback: Offline) │ │ │ │
│ │ • Spiele │ │ │ │ • Jellyfin │ │
│ │ • Videos │ │ │ │ • Ollama │ │
│ │ • Auth │ │ │ │ • Freigabe │ │
│ └─────────────┘ │ └──────────────────┘
└─────────────────┘
Phase 1: Docker-Server (Das Backend)
Erstelle diese Struktur auf deinem Server:
docker-compose.yml
yaml
Einklappen
Speichern
Kopieren
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
⌄
⌄
⌄
⌄
⌄
⌄
⌄
⌄
⌄
⌄
⌄
⌄
⌄
⌄
⌄
⌄
⌄
⌄
⌄
⌄
⌄
⌄
version: '3.8'
kids-game:
build: ./game-server
container_name: kids-game
restart: unless-stopped
environment:
- NODE_ENV=production
- JELLYFIN_URL=http://jellyfin:8096
- JELLYFIN_API_KEY=${JELLYFIN_API_KEY}
- SESSION_SECRET=${SESSION_SECRET:-random-secret-change-me}
- OFFLINE_MODE=false
volumes:
- ./downloads:/app/public/downloads:ro # Offline-Videos
networks:
- kids-net
# Kein Port nach außen! Nur via NPM erreichbar
# Jellyfin (nur intern)
jellyfin:
image: jellyfin/jellyfin:latest
container_name: jellyfin-kids
restart: unless-stopped
environment:
- PUID=1000
- PGID=1000
volumes:
- ./jellyfin-config:/config
- ./media-kids:/media:ro # Nur Kids-Content!
networks:
- kids-net
# Keine Ports exposed!
# Optional: Ollama für KI
ollama:
image: ollama/ollama:latest
container_name: ollama-kids
restart: unless-stopped
volumes:
- ./ollama-models:/root/.ollama
networks:
- kids-net
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: all
capabilities: [gpu]
# NPM (Reverse Proxy) - Der einzige öffentliche Zugang
npm:
image: jc21/nginx-proxy-manager:latest
container_name: npm
restart: unless-stopped
ports:
- "80:80"
- "443:443"
- "81:81" # Admin-UI
volumes:
- ./npm-data:/data
- ./letsencrypt:/etc/letsencrypt
networks:
- kids-net
- default
networks:
kids-net:
driver: bridge
internal: true # Jellyfin & Ollama nur intern!
game-server/Dockerfile
dockerfile
Einklappen
Speichern
Kopieren
1
2
3
4
5
6
7
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
EXPOSE 3000
CMD ["node", "server.js"]
game-server/server.js (Schnittstelle)
javascript
Einklappen
Speichern
Kopieren
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
⌄
⌄
⌄
⌄
⌄
⌄
⌄
⌄
⌄
⌄
const express = require('express');
const path = require('path');
const app = express();
// Statische Dateien (PWA)
app.use(express.static('public'));
// Health-Check für APK
app.get('/api/health', (req, res) => res.json({ status: 'online', timestamp: Date.now() }));
// Session-Status für Parental Gate
const sessions = new Map();
app.post('/api/session/create', (req, res) => {
const id = Math.random().toString(36).substring(7);
sessions.set(id, { status: 'pending', created: Date.now() });
res.json({ sessionId: id, qrData: `kids://auth/${id}` });
});
app.get('/api/session/:id', (req, res) => {
const session = sessions.get(req.params.id);
if (!session) return res.status(404).json({ error: 'not found' });
res.json(session);
});
// Freigabe durch Eltern
app.post('/api/session/:id/approve', express.json(), (req, res) => {
const { duration = 30, jellyfinUser = 'kind-default' } = req.body;
sessions.set(req.params.id, {
status: 'approved',
approvedAt: Date.now(),
expires: Date.now() + (duration * 60000),
jellyfinUser
});
res.json({ success: true });
});
// Jellyfin Proxy (sicher, ohne direkten Zugriff)
app.get('/api/media/list', async (req, res) => {
try {
const response = await fetch(`${process.env.JELLYFIN_URL}/Users`, {
headers: { 'X-Emby-Token': process.env.JELLYFIN_API_KEY }
});
const users = await response.json();
res.json(users);
} catch (e) {
res.status(503).json({ error: 'jellyfin offline', offline: true });
}
});
// Offline-Modus Detection
app.get('/api/config', (req, res) => {
res.json({
serverUrl: process.env.OFFLINE_MODE === 'true' ? null : 'https://deinedomain.de',
version: '1.0.0'
});
});
app.listen(3000, () => console.log('Kids Game Server running'));
game-server/public/index.html (PWA-Grundgerüst)
html
Einklappen
Speichern
Kopieren
Vorschau
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
⌄
⌄
⌄
⌄
⌄
⌄
⌄
⌄
⌄
⌄
⌄
⌄
⌄
⌄
⌄
// QR Code generieren
new QRious({
element: document.getElementById('qrCanvas'),
value: data.qrData,
size: 200
});
// Polling nach Freigabe
this.checkInterval = setInterval(() => this.checkApproval(), 5000);
} catch (e) {
console.log('Server nicht erreichbar - Offline Modus');
this.showOfflineMode();
}
},
async checkApproval() {
try {
const res = await fetch(`/api/session/${this.sessionId}`);
const data = await res.json();
if (data.status === 'approved') {
clearInterval(this.checkInterval);
this.unlock(data.expires);
}
} catch (e) {
// Server weg -> bleibt gesperrt oder Offline-Modus
}
},
unlock(expires) {
document.getElementById('lockScreen').classList.remove('active');
document.getElementById('gameScreen').classList.add('active');
// Countdown
const updateTimer = () => {
const left = Math.floor((expires - Date.now()) / 1000);
if (left <= 0) location.reload(); // Resperren
const min = Math.floor(left / 60);
const sec = left % 60;
// Timer anzeigen...
};
setInterval(updateTimer, 1000);
},
showOfflineMode() {
// Einfache Offline-Spiele ohne Freigabe (nur für Notfall)
if (confirm('Server nicht erreichbar. Offline-Spiele starten?')) {
document.getElementById('lockScreen').style.display = 'none';
document.getElementById('gameScreen').classList.add('active');
document.body.classList.add('offline');
}
},
startGame() {
// Simples Blubber-Spiel
const canvas = document.getElementById('gameCanvas');
const createBubble = () => {
const b = document.createElement('div');
b.className = 'bubble';
b.style.left = Math.random() * 80 + 10 + '%';
b.style.top = Math.random() * 80 + 10 + '%';
b.style.backgroundColor = ['#FF6B6B', '#4ECDC4', '#45B7D1'][Math.floor(Math.random() * 3)];
b.ontouchstart = () => {
b.remove();
new Audio('data:audio/wav;base64,UklGRnoGAABXQVZFZm10IBAAAAABAAEAQB8AAEAfAAABAAgAZGF0YQoGAACBhYqFbF1fdJivrJBhNjVgodDbq2EcBj+a2/LDciUFLIHO8tiJNwgZaLvt559NEAxQp+PwtmwhBTGH0fPTjjkIHGu8+OGbURE...').play();
};
canvas.appendChild(b);
setTimeout(() => b.remove(), 5000);
};
setInterval(createBubble, 2000);
},
requestClose() {
// Sendet Nachricht an Android-App zum Schließen
if (window.AndroidInterface) {
window.AndroidInterface.exitApp();
}
}
};
app.init();