sleep-meditation/docs/TECHNICAL.md
Ivo Oskamp 0d9f20690f Release v0.1.6
- Switch to shared build-and-push.sh; version read from docs/changelog.md
- Add docs/changelog.md; remove version.txt, .last-branch, .gitignore
- Stack: image tag via SLEEP_MEDITATION_IMAGE_TAG
- Downloader: YouTube support (yt-dlp + ffmpeg), best audio to mp3
- Downloader: Content-Type validation for direct URLs
- Downloader: auto-fetch YouTube title; title field optional
- Downloader: progress bar with phase (downloading/converting)
- Downloader: store source URL per file; show Source link in manage list

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 15:13:10 +02:00

5.4 KiB

Sleep Meditation — Technical Documentation

Scope

This project provides a web app in a container that plays MP3 audio on desktop and mobile, including iPhone lock-screen controls. A separate internal container handles downloading and managing audio tracks.

Architecture

Two containers share a host volume containing all MP3 files and metadata.

sleep-meditation (public)

  • Runtime: nginx:1.27-alpine + Python 3 (managed by supervisord)
  • Nginx: serves static files and proxies /api/ to the local Python API on localhost:8001
  • Python API: read-only Flask app — lists downloaded tracks only (GET /api/downloads)
  • App: frontend (index.html, styles.css, app.js)
  • MP3 source: mounted host volume at /usr/share/nginx/html/mp3
  • Externally accessible; no write operations exposed

sleep-meditation-downloader (internal)

  • Runtime: python:3.12-alpine + Flask
  • UI: management page at http://<host>:${SLEEP_MEDITATION_DOWNLOADER_PORT}/
  • API: all write operations — download, delete, rename downloads and playlist tracks
  • MP3 volume mounted at /mp3
  • Accessible on ${SLEEP_MEDITATION_DOWNLOADER_PORT} within the local network (not publicly exposed)
  • After downloading, use the refresh button in the sleep app to pick up new downloads

File overview

containers/sleep-meditation/
  Dockerfile
  nginx.conf
  supervisord.conf
  api.py                  ← read-only: GET /api/downloads only
  site/
    index.html
    styles.css
    app.js
    manifest.webmanifest
    service-worker.js
    mp3/playlist.json
containers/sleep-meditation-downloader/
  Dockerfile
  api.py                  ← all write operations + UI file serving
  site/
    index.html
    styles.css
    app.js
stack/
  stack.yml
  sleep-meditation.env

iPhone background playback

  • Autoplay without user interaction is blocked.
  • The user must start playback manually at least once.
  • After playback starts, audio usually continues when the screen is locked.
  • Media Session API enables lock-screen controls for play/pause/track skip.

UI

Single page — no routing. Contains:

  • Playlist selector and audio player with controls
  • Downloads list (read-only, populated via GET /api/downloads)
  • Refresh button (↺) in the header: reloads the full page; useful when the app is saved as a home screen bookmark on iPhone (no browser refresh available)

Player controls

  • Previous: go to previous track (clamped at first track)
  • Play/Pause: toggle playback
  • Next: go to next track (clamped at last track)
  • Back to start: select first track, reset position to 0:00, no autoplay

When a playlist track ends:

  • If there is a next track, it starts automatically.
  • If the last track ends, playback stops (no loop).

When a downloaded track ends:

  • Playback stops. No auto-advance (singleMode flag is set when playing a download).

Track loading behavior

The app loads meditation tracks in this order:

  1. /mp3/playlist.json
  2. Auto-discovery from /mp3/ directory listing (Nginx autoindex on), excluding /mp3/downloads/
  3. Empty list (no fallback track)

Downloaded tracks are loaded separately via GET /api/downloads.

Playlist contract

File: /mp3/playlist.json

[
  {
    "title": "Track name",
    "artist": "Artist",
    "src": "/mp3/track.mp3"
  }
]

Rules:

  • title optional (derived from filename if omitted)
  • artist optional
  • src required; absolute path (/mp3/...) or bare filename

When a track title is renamed via the downloader UI, the API writes the updated title back to playlist.json. If playlist.json does not yet exist in the volume, it is created from the current directory listing.

Downloaded tracks

  • Stored in /mp3/downloads/ inside the mp3 volume.
  • Filenames are URL- and filesystem-safe (letters, digits, spaces, hyphens only).
  • Display titles (preserving special characters like : and |) are stored in /mp3/downloads/titles.json.
  • Renaming a downloaded track updates titles.json only; the filename is not changed.
  • Deleting a downloaded track removes the file and its entries from titles.json and skip.json.

Skip intro

  • A per-track skip offset (seconds) can be set in the downloader UI.
  • Stored in /mp3/downloads/skip.json as { "filename.mp3": seconds }.
  • GET /api/downloads returns a skip field (default 0) for each track.
  • The sleep app seeks to skip seconds after loadedmetadata fires when playing a downloaded track.
  • Setting skip to 0 removes the entry from skip.json.

API endpoints

sleep-meditation (public, read-only)

Method Path Description
GET /api/downloads List downloaded tracks

sleep-meditation-downloader (internal only)

Method Path Description
GET / Management UI
GET /api/downloads List downloaded tracks
GET /api/tracks List playlist tracks
POST /api/download Start server-side download {url, title}
GET /api/download/status/{track_id} Poll download status
DELETE /api/downloads/{filename} Delete a downloaded track
POST /api/downloads/{filename}/rename Rename display title {title}
POST /api/downloads/{filename}/skip Set skip-intro offset {seconds}
POST /api/tracks/rename Update playlist track title {src, title}

Container healthcheck

  • Endpoint: GET /
  • Expected: HTTP 200
  • Configured in Dockerfile with wget