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:
1 parent
757c7045ba
commit
cf88d87791
3 files changed
+57
-23
No files matched your search
@@ -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
@@ -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
@@ -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}
|
||||
|
||||
Reference in new issue
Block a user