- 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>
5.4 KiB
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 onlocalhost: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 playbackNext: go to next track (clamped at last track)Back to start: select first track, reset position to0: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 (
singleModeflag is set when playing a download).
Track loading behavior
The app loads meditation tracks in this order:
/mp3/playlist.json- Auto-discovery from
/mp3/directory listing (Nginxautoindex on), excluding/mp3/downloads/ - 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:
titleoptional (derived from filename if omitted)artistoptionalsrcrequired; 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.jsononly; the filename is not changed. - Deleting a downloaded track removes the file and its entries from
titles.jsonandskip.json.
Skip intro
- A per-track skip offset (seconds) can be set in the downloader UI.
- Stored in
/mp3/downloads/skip.jsonas{ "filename.mp3": seconds }. GET /api/downloadsreturns askipfield (default0) for each track.- The sleep app seeks to
skipseconds afterloadedmetadatafires when playing a downloaded track. - Setting skip to
0removes the entry fromskip.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