Sleewave Backend is a local media service for a Flutter client. It aggregates multiple music sources, deduplicates matching tracks, prepares cache-backed playback, and serves downloads for the mobile app.
- Source discovery endpoint for the client UI.
- Search across one source, several sources, or all available sources.
- Track deduplication across providers.
- Search prioritization for tracks already cached on the server or already saved on the device.
- Temporary MP3 cache with size-based eviction and least-recently-used cleanup.
- Direct stream and download endpoints backed by server cache.
- Device library sync so the backend can avoid offering duplicate tracks again.
- Structured JSON error responses.
ytm- availableyt- availablesc- availablespotify- listed but disabled until integration is implementedvk- available
Returns the list of sources with availability flags so the Flutter app can render a source picker.
Streams search results as server-sent events so the client can render tracks as they are discovered instead of waiting for the whole search response.
Query parameters:
q- search textsources- comma-separated source ids such asytm,yt,scsource- optional single-source alias for compatibilitylimit- result count, default10offset- pagination offset, default0device_id- optional device identifier used to prioritize already saved tracks
Examples:
GET /search?q=daft%20punk&sources=all&device_id=phone-01
GET /search?q=daft%20punk&sources=ytm,sc
GET /search?q=daft%20punk&source=ytEvents are sent in this shape:
event: start
data: {"event":"start","query":"daft punk","sources":["ytm","yt","sc"],"emitted":0}
event: track
data: {"event":"track","source":"ytm","track":{"title":"One More Time","artist":"Daft Punk","duration":320,"cover_url":"https://...","album":"Discovery","result_id":"stable-exact-key","availability":{"in_server_cache":true,"preferred_origin":"server"}},"emitted":1}
event: warning
data: {"event":"warning","source":"sc","warning":{...},"emitted":4}
event: done
data: {"event":"done","emitted":10}
Use result_id for stream, download, delete, and device-library actions. It is stable.
Downloads the selected result into the server temp cache if needed, then returns the cached MP3 inline for playback.
GET /stream/stable-exact-keyDownloads the selected result into the server temp cache if needed, then returns the cached MP3 as an attachment for the device to save.
GET /download/stable-exact-key?device_id=phone-01When device_id is provided, the backend checks that phone's library first. If the track is already on that phone, it returns a structured 409 track_already_on_device error and does not send the file again.
Returns every song currently available in the backend download cache, newest recently accessed first.
Each saved song is returned with its permanent result_id, which works with the normal /stream/{result_id} and /download/{result_id} endpoints.
GET /saved-songs?limit=50&offset=0Response:
{
"songs": [
{
"title": "One More Time",
"artist": "Daft Punk",
"duration": 320,
"cover_url": "https://...",
"album": "Discovery",
"result_id": "stable-exact-key",
"availability": {
"in_server_cache": true,
"cache_key": "stable-exact-key",
"preferred_origin": "server"
}
}
],
"count": 1,
"total": 1,
"limit": 50,
"offset": 0,
"has_more": false
}Use the returned result_id exactly like a search result:
GET /stream/stable-exact-key
GET /download/stable-exact-keyCached matches are also emitted first from GET /search before provider searches complete. If the cached matches fill the requested limit, the backend returns them without searching remote providers.
Delete one cached/cataloged track:
DELETE /tracks/stable-exact-keyClear cached MP3 files while keeping the track catalog and device library:
DELETE /cacheClear all backend temp state, including cached MP3s, track catalog, and device library:
DELETE /server-tempReplaces the known track list for a device. Server cache is retained so other phones can reuse already downloaded files.
Request body:
{
"device_id": "phone-01",
"tracks": [
{
"result_id": "stable-exact-key"
}
]
}Call this after the Flutter app has finished saving a downloaded track. The backend adds the track to the device library and keeps the server cache available for reuse.
{
"device_id": "phone-01",
"result_id": "stable-exact-key"
}All handled errors return JSON in this shape:
{
"error": {
"code": "provider_unavailable",
"message": "Spotify integration has not been implemented yet.",
"details": {
"source": "spotify"
}
}
}- Cache location defaults to the OS temp directory inside
sleewave-media-cache. - Files are stored as MP3.
- When the cache grows over the configured limit, the oldest unused tracks are evicted first.
- When a track is confirmed as saved on a device, the server cache copy is retained for other devices until normal cache eviction removes it.
Environment variables:
SLEEWAVE_CACHE_DIR- optional custom cache directorySLEEWAVE_CACHE_MAX_MB- maximum cache size in megabytes, default1024SLEEWAVE_YTDLP_COOKIES- optional raw Netscape cookies.txt content foryt-dlpSLEEWAVE_YTDLP_COOKIES_BASE64- optional base64-encoded Netscape cookies.txt content foryt-dlpSLEEWAVE_YTDLP_COOKIES_FILE- optional path to a Netscape cookies.txt file foryt-dlpSLEEWAVE_YTDLP_COOKIES_FROM_BROWSER- optional browser cookie source such aschrome,firefox:default, orchrome:Profile 1
Cookie env precedence is SLEEWAVE_YTDLP_COOKIES_FILE, then SLEEWAVE_YTDLP_COOKIES_BASE64, then SLEEWAVE_YTDLP_COOKIES, then SLEEWAVE_YTDLP_COOKIES_FROM_BROWSER. The backend loads app/.env on startup.
- Install dependencies:
pip install -r requirements.txt-
Make sure
ffmpegis installed and available inPATH.yt-dlpuses it to convert audio into MP3 files. -
Start the API:
uvicorn app.main:app --reloadThe backend will be available at http://127.0.0.1:8000.
- This repository is ready for Flutter integration, but the Flutter client still needs to call
/device-library/syncor/device-library/confirm-downloadso the backend can suppress duplicates correctly. - The intended flow is
search -> user picks a result_id -> stream/download -> confirm device download when saved locally. SpotifyandVKare exposed to the client as disabled sources so the UI can show future integrations without pretending they work today.