fix: simplify audiobook sleep timer custom input; update README

- Remove OK button and expand-on-click from sleep timer custom row
- Always show label + plain number input (no spinners, Enter to confirm)
- Update README with all current features (Spotify importer, ABS, lyrics, filters, etc.)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
HoneyandClaude Sonnet 4.6 committed 2026-05-14 19:04:12 +02:00
1 parent 757c7045ba
commit cf88d87791
3 files changed
+57 -23

No files matched your search

+48 -10
View File
@@ -7,16 +7,45 @@ Built with Vite + vanilla ES modules, served via nginx in Docker.
## Features
### Music Library
- Stream music from your Navidrome / Subsonic library
- Browse artists, albums, playlists, starred tracks and random picks
- Queue management with shuffle, repeat (all / once) and radio mode (endless random)
- Now Playing overlay with swipe-to-dismiss and lock screen media controls (Android/iOS)
- iTunes / Last.fm integration — charts, global search, 30s previews, recommendations
- Download tracks from YouTube/SoundCloud via yt-dlp directly into your music library
- Duplicate detection — skips tracks that already exist on the server
- Edit playlists (name, description, cover art, public toggle)
- PWA — installable, offline-capable via service worker
- Browse artists, albums, playlists, starred tracks, and random picks
- Sort and filter views (albums, favorites) with chip filters
- Search/filter bar inside playlist detail and favorites (instant, client-side)
- Synced and plain lyrics via LRCLIB (auto-fetched per track)
### Playback
- Queue management with shuffle, repeat (all / once), and radio mode (endless random)
- Now Playing overlay with swipe-to-dismiss
- Lock screen media controls (Android / iOS via Media Session API)
- Gapless-style queue with prev/next, skip forward/back
### Discovery
- iTunes / Last.fm integration — charts, global search, 30-second previews, similar artists, recommendations
- Spotify playlist importer — paste a Spotify playlist URL to preview and download all tracks, enriched with iTunes metadata and cover art
### Downloads (yt-dlp backend)
- Download individual tracks, full albums, or entire artists from YouTube / SoundCloud
- Spotify playlist importer downloads with iTunes-enriched metadata (album, year, track number, genre, cover art)
- Duplicate detection — skips tracks already present on the server
- Lyrics embedded into downloaded files (LRCLIB)
### Playlists
- Create, edit, and delete playlists
- Edit playlist name, description, cover art, and public/private toggle
- Add / remove tracks; reorder via drag and drop
### Audiobookshelf Integration
- Connect to your Audiobookshelf server (URL + API key)
- Browse and stream audiobooks from your ABS library
- Full player: chapters list, playback speed, sleep timer (preset or custom minutes), progress sync
### App
- PWA — installable on Android / iOS / desktop, offline-capable via service worker
- Android APK (WebView wrapper with native feel)
- Multiple color themes
- Dark theme, mobile-first responsive layout
- German UI
---
@@ -31,15 +60,23 @@ Music_App/
│ ├── player.js # Playback, transport, queue, media session, preview
│ ├── views.js # Navigation, page loaders, card makers, search
│ ├── ui.js # Modals, overlays, sidebar, settings, context menu
│ ├── nowplaying.js # Now Playing overlay
│ ├── abs.js # Audiobookshelf integration
│ ├── spotify.js # Spotify playlist importer
│ ├── components.js # Shared UI components
│ ├── themes.js # Theme switching
│ ├── pwa.js # Service worker registration + update banner
│ └── main.js # App init, audio wiring, window globals
├── backend/
│ ├── novasonic-server.js # Node.js download backend (yt-dlp + ffmpeg)
│ └── lib/
│ └── metadata.js # iTunes lookup, lyrics fetch, artwork download
├── public/ # Static assets (copied as-is to dist/)
│ ├── sw.js # Service worker
│ ├── manifest.json # PWA manifest
│ ├── icon.svg # App icon
│ └── config.js # Runtime config placeholder (generated by Docker)
├── backend/
│ └── novasonic-server.js # Node.js download backend (yt-dlp + ffmpeg)
├── android/ # Android WebView APK wrapper
├── index.html # App shell + Vite entry point
├── styles.css # All styles
├── vite.config.js
@@ -104,3 +141,4 @@ npm run preview # Preview the production build locally
2. Enter your Navidrome server URL, username and password
3. Optionally add a Last.fm API key (Settings) for recommendations and similar artists
4. Optionally add the backend URL (Settings) to enable yt-dlp downloads
5. Optionally connect Audiobookshelf (Settings) to stream audiobooks
+3 -4
View File
@@ -719,10 +719,9 @@
<button onclick="absSleepIn(30)">30 Min</button>
<button onclick="absSleepIn(45)">45 Min</button>
<button onclick="absSleepIn(60)">60 Min</button>
<div id="abs-sleep-custom-row" class="abs-sleep-custom-row" onclick="if(!this.classList.contains('active')){this.classList.add('active');var inp=document.getElementById('abs-sleep-custom-input');inp.value='';inp.focus();}">
<span class="abs-sleep-custom-label">Eigene Zeit…</span>
<input id="abs-sleep-custom-input" type="number" min="1" max="999" placeholder="Min" onclick="event.stopPropagation()" onkeydown="if(event.key==='Enter'){var v=parseInt(this.value);if(v>0)absSleepIn(v);}">
<button onclick="event.stopPropagation();var v=parseInt(document.getElementById('abs-sleep-custom-input').value);if(v>0)absSleepIn(v);">OK</button>
<div class="abs-sleep-custom-row">
<span class="abs-sleep-custom-label">Eigene Zeit</span>
<input id="abs-sleep-custom-input" type="number" min="1" max="999" placeholder="Min" onkeydown="if(event.key==='Enter'){var v=parseInt(this.value);if(v>0)absSleepIn(v);}">
</div>
</div>
</div>
+6 -9
View File
@@ -950,15 +950,12 @@ nav{flex:1;overflow-y:auto;padding:4px 10px}
.abs-picker button{display:block;width:100%;padding:9px 16px;background:none;border:none;color:var(--text);font-family:var(--font);font-size:13px;cursor:pointer;text-align:left;transition:background .15s;white-space:nowrap}
.abs-picker button:hover{background:var(--surface2)}
.abs-picker button.active{color:var(--accent);font-weight:700}
.abs-sleep-custom-row{display:flex;align-items:center;gap:6px;width:100%;padding:9px 16px;background:none;border:none;border-top:1px solid var(--border2);color:var(--text);font-family:var(--font);font-size:13px;cursor:pointer;text-align:left;box-sizing:border-box;transition:background .15s}
.abs-sleep-custom-row:hover{background:var(--surface2)}
.abs-sleep-custom-row.active{background:var(--surface2);cursor:default;padding:8px 10px}
.abs-sleep-custom-label{flex:1;pointer-events:none}
.abs-sleep-custom-row.active .abs-sleep-custom-label{display:none}
.abs-sleep-custom-row input{display:none;flex:1;min-width:0;padding:5px 8px;background:var(--surface);border:1.5px solid var(--accent);border-radius:6px;color:var(--text);font-family:var(--font);font-size:13px;outline:none}
.abs-sleep-custom-row.active input{display:block}
.abs-sleep-custom-row button{display:none;padding:5px 12px;background:var(--accent);border:none;border-radius:6px;color:#fff;font-family:var(--font);font-size:13px;font-weight:600;cursor:pointer;white-space:nowrap;flex-shrink:0}
.abs-sleep-custom-row.active button{display:block}
.abs-sleep-custom-row{display:flex;align-items:center;gap:8px;width:100%;padding:7px 16px 9px;border-top:1px solid var(--border2);box-sizing:border-box}
.abs-sleep-custom-label{font-size:11px;color:var(--text3);white-space:nowrap;flex-shrink:0}
.abs-sleep-custom-row input{flex:1;min-width:0;padding:5px 8px;background:var(--surface);border:1.5px solid var(--border2);border-radius:6px;color:var(--text);font-family:var(--font);font-size:13px;outline:none;transition:border-color .15s}
.abs-sleep-custom-row input:focus{border-color:var(--accent)}
.abs-sleep-custom-row input[type=number]::-webkit-inner-spin-button,.abs-sleep-custom-row input[type=number]::-webkit-outer-spin-button{-webkit-appearance:none;margin:0}
.abs-sleep-custom-row input[type=number]{-moz-appearance:textfield}
.abs-chapter-list{flex-shrink:0;overflow-y:auto;max-height:0;transition:max-height .3s ease;border-top:1px solid transparent}
.abs-chapter-list.open{max-height:220px;border-color:var(--border)}
.abs-ch-item{display:flex;align-items:center;gap:10px;padding:8px 20px;cursor:pointer;transition:background .15s}