diff --git a/backend/novasonic-server.js b/backend/novasonic-server.js index 675021c..2a20fc2 100644 --- a/backend/novasonic-server.js +++ b/backend/novasonic-server.js @@ -23,7 +23,7 @@ const { downloadTrack, downloadAlbumTracks } = require('./lib/downloader'); const app = express(); app.use((req, res, next) => { res.setHeader('Access-Control-Allow-Origin', '*'); - res.setHeader('Access-Control-Allow-Methods', 'GET,POST,OPTIONS'); + res.setHeader('Access-Control-Allow-Methods', 'GET,POST,PATCH,DELETE,OPTIONS'); res.setHeader('Access-Control-Allow-Headers', 'Content-Type,Authorization,X-ABS-URL,X-ABS-Token'); res.setHeader('Access-Control-Allow-Private-Network', 'true'); if (req.method === 'OPTIONS') { res.sendStatus(204); return; } diff --git a/index.html b/index.html index d354e6a..4fc6eec 100644 --- a/index.html +++ b/index.html @@ -435,16 +435,20 @@ - -
-
πŸ“š
-
+ +
+
+
πŸ“š
+
β€”
- +
@@ -598,9 +602,7 @@ HΓΆrbuch - +
@@ -702,6 +704,12 @@ Kapitel + + +
diff --git a/src/abs.js b/src/abs.js index 4cf8550..b7a555d 100644 --- a/src/abs.js +++ b/src/abs.js @@ -185,10 +185,6 @@ function _updateChapter() { ap.chapterIdx = idx; _renderChapterName(); _renderChapterList(); - if (_playlistMode) { - const cl = document.getElementById('abs-chapter-list'); - setTimeout(() => cl?.querySelector('.abs-ch-item.active')?.scrollIntoView({ block: 'nearest', behavior: 'smooth' }), 30); - } } } @@ -344,7 +340,6 @@ export function absClosePlayer() { } export function absToggleChapters() { - if (_playlistMode) return; document.getElementById('abs-chapter-list')?.classList.toggle('open'); _renderChapterList(); } @@ -364,28 +359,22 @@ export function absSeekToChapter(idx) { if (ch) absSeek(ch.start); } -// ─── PLAYLIST MODE ─── -let _playlistMode = localStorage.getItem('ns_abs_pl_mode') === '1'; +// ─── CHAPTER TRACK MODE ─── +let _chapterTrackMode = localStorage.getItem('ns_abs_chtrack') === '1'; -export function absTogglePlaylistMode() { - _playlistMode = !_playlistMode; - localStorage.setItem('ns_abs_pl_mode', _playlistMode ? '1' : '0'); - _applyPlaylistMode(); +export function absToggleChapterTrack() { + _chapterTrackMode = !_chapterTrackMode; + localStorage.setItem('ns_abs_chtrack', _chapterTrackMode ? '1' : '0'); + document.getElementById('abs-chtrack-btn')?.classList.toggle('active', _chapterTrackMode); + _renderProgress(); } -function _applyPlaylistMode() { - const overlay = document.getElementById('abs-player'); - overlay?.classList.toggle('playlist-mode', _playlistMode); - document.getElementById('abs-playlist-btn')?.classList.toggle('active', _playlistMode); - const cl = document.getElementById('abs-chapter-list'); - if (!cl) return; - if (_playlistMode) { - cl.classList.add('open'); - _renderChapterList(); - setTimeout(() => cl.querySelector('.abs-ch-item.active')?.scrollIntoView({ block: 'nearest', behavior: 'smooth' }), 50); - } else { - cl.classList.remove('open'); - } +function _chapterWindow() { + const ap = S.absPlayer; + if (!_chapterTrackMode || ap.chapterIdx < 0 || !ap.chapters.length) return null; + const ch = ap.chapters[ap.chapterIdx]; + const nextCh = ap.chapters[ap.chapterIdx + 1]; + return { start: ch.start ?? 0, end: nextCh?.start ?? ap.duration }; } // ─── PROGRESS SCRUB ─── @@ -397,8 +386,9 @@ function _scrub(e) { const bar = document.getElementById('abs-prog-bar'); if (!bar) return; const rect = bar.getBoundingClientRect(); - const x = (e.touches?.[0]?.clientX ?? e.clientX) - rect.left; - absSeek((Math.max(0, Math.min(1, x / rect.width))) * S.absPlayer.duration); + const pct = Math.max(0, Math.min(1, ((e.touches?.[0]?.clientX ?? e.clientX) - rect.left) / rect.width)); + const win = _chapterWindow(); + absSeek(win ? win.start + pct * (win.end - win.start) : pct * S.absPlayer.duration); } // ─── RENDER HELPERS ─── @@ -456,20 +446,29 @@ function _renderPlayer() { _renderChapterName(); _renderSleepBtn(); _renderMiniBar(); - _applyPlaylistMode(); + document.getElementById('abs-chtrack-btn')?.classList.toggle('active', _chapterTrackMode); } function _renderProgress() { const ap = S.absPlayer; - const pct = ap.duration > 0 ? (ap.currentTime / ap.duration) * 100 : 0; + const win = _chapterWindow(); + const dispCur = win ? ap.currentTime - win.start : ap.currentTime; + const dispDur = win ? win.end - win.start : ap.duration; + const pct = dispDur > 0 ? Math.min(100, (dispCur / dispDur) * 100) : 0; + const fill = document.getElementById('abs-prog-fill'); const thumb = document.getElementById('abs-prog-thumb'); - if (fill) fill.style.width = `${Math.min(100, pct)}%`; - if (thumb) thumb.style.left = `${Math.min(100, pct)}%`; + if (fill) fill.style.width = `${pct}%`; + if (thumb) thumb.style.left = `${pct}%`; const cur = document.getElementById('abs-time-cur'); const rem = document.getElementById('abs-time-rem'); - if (cur) cur.textContent = _fmt(ap.currentTime); - if (rem) rem.textContent = `-${_fmt(ap.duration - ap.currentTime)}`; + if (cur) cur.textContent = _fmt(dispCur); + if (rem) rem.textContent = `-${_fmt(dispDur - dispCur)}`; + + // mini bar strip always shows full-book progress + const miniPct = ap.duration > 0 ? Math.min(100, (ap.currentTime / ap.duration) * 100) : 0; + const miniFill = document.getElementById('abs-mini-prog-fill'); + if (miniFill) miniFill.style.width = `${miniPct}%`; } function _renderControls() { diff --git a/src/main.js b/src/main.js index 8b4db3a..c98edf0 100644 --- a/src/main.js +++ b/src/main.js @@ -24,7 +24,7 @@ import { absSetSpeed, absSleepIn, absOpenPlayer, absClosePlayer, absToggleChapters, absToggleSpeedPicker, absToggleSleepPicker, absSeekToChapter, absProgDown, absProgMove, absProgUp, loadAbsBooks, absOpenBook, loadAbsBookDetail, absPlayChapter, - absLoadSettingsLibraries, absTogglePlaylistMode, absInterrupt, + absLoadSettingsLibraries, absToggleChapterTrack, absInterrupt, } from './abs.js'; import { openNowPlaying, closeNowPlaying, switchNpTab, npGoArtist, npGoAlbum, @@ -271,7 +271,7 @@ Object.assign(window, { absSetSpeed, absSleepIn, absOpenPlayer, absClosePlayer, absToggleChapters, absToggleSpeedPicker, absToggleSleepPicker, absSeekToChapter, absProgDown, absProgMove, absProgUp, - absOpenBook, absPlayChapter, absConnectSettings, absSetLibrary, absDisconnect, absTogglePlaylistMode, + absOpenBook, absPlayChapter, absConnectSettings, absSetLibrary, absDisconnect, absToggleChapterTrack, absOnSearch: (val) => { clearTimeout(window._absSearchTimer); window._absSearchTimer = setTimeout(() => { diff --git a/styles.css b/styles.css index 99fe3f6..2addf3d 100644 --- a/styles.css +++ b/styles.css @@ -952,18 +952,17 @@ nav{flex:1;overflow-y:auto;padding:4px 10px} .abs-cover-wrap{width:min(240px,calc(100vw - 48px))} } -/* ── ABS MINI BAR ── */ -.abs-mini-bar{position:fixed;bottom:var(--player-h);left:0;right:0;background:rgba(10,10,28,.96);backdrop-filter:blur(16px);border-top:1px solid rgba(130,80,255,.25);padding:9px 16px;display:flex;align-items:center;gap:12px;z-index:99;transform:translateY(100%);transition:transform .3s;cursor:pointer} +/* ── ABS MINI BAR (overlays the bottom player bar) ── */ +.abs-mini-bar{position:fixed;bottom:0;left:0;right:0;height:var(--player-h);background:rgba(8,8,20,.96);backdrop-filter:blur(28px);border-top:1px solid var(--border2);padding:0 16px;display:flex;align-items:center;gap:12px;z-index:101;transform:translateY(100%);transition:transform .3s} .abs-mini-bar.visible{transform:translateY(0)} -.abs-mini-cover{width:40px;height:40px;border-radius:7px;background:var(--surface2);display:flex;align-items:center;justify-content:center;font-size:18px;flex-shrink:0;overflow:hidden} -.abs-mini-info{flex:1;min-width:0} +.abs-mini-prog-strip{position:absolute;top:0;left:0;right:0;height:3px;background:var(--border2)} +.abs-mini-prog-fill{height:100%;background:var(--accent);transition:width .5s linear;width:0} +.abs-mini-cover{width:46px;height:46px;border-radius:8px;background:var(--surface2);display:flex;align-items:center;justify-content:center;font-size:20px;flex-shrink:0;overflow:hidden;cursor:pointer} +.abs-mini-info{flex:1;min-width:0;cursor:pointer} .abs-mini-title{font-size:13px;font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis} -.abs-mini-chapter{font-size:11px;color:var(--text2);margin-top:1px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis} -.abs-mini-play{background:none;border:none;color:var(--text);cursor:pointer;padding:6px;display:flex;align-items:center;justify-content:center;flex-shrink:0} -@media(max-width:768px){.abs-mini-bar{bottom:calc(var(--player-h) + 80px + env(safe-area-inset-bottom))}} - -/* ── ABS PLAYLIST MODE ── */ +.abs-mini-chapter{font-size:11px;color:var(--text2);margin-top:2px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis} +.abs-mini-play{background:none;border:none;color:var(--text);cursor:pointer;padding:8px;display:flex;align-items:center;justify-content:center;flex-shrink:0} +.abs-mini-open{background:none;border:none;color:var(--text2);cursor:pointer;padding:8px;display:flex;align-items:center;justify-content:center;flex-shrink:0} +.abs-mini-open:hover{color:var(--text)} .abs-hbtn.active{color:var(--accent)} -.abs-player-overlay.playlist-mode .abs-cover-area{flex:0 0 auto;padding:4px 40px} -.abs-player-overlay.playlist-mode .abs-cover-wrap{width:min(140px,calc(100vw - 80px))} -.abs-player-overlay.playlist-mode .abs-chapter-list.open{flex:1;max-height:none;overflow-y:auto} +@media(max-width:768px){.abs-mini-bar{bottom:80px;height:var(--player-h)}}