diff --git a/index.html b/index.html index c03ec6d..d354e6a 100644 --- a/index.html +++ b/index.html @@ -435,6 +435,18 @@ + +
+
πŸ“š
+
+
β€”
+
+
+ +
+
β™ͺ
@@ -586,7 +598,9 @@ HΓΆrbuch -
+
diff --git a/src/abs.js b/src/abs.js index 771a870..4cf8550 100644 --- a/src/abs.js +++ b/src/abs.js @@ -185,6 +185,10 @@ 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); + } } } @@ -203,6 +207,7 @@ export async function absPlayItem(item) { const ap = S.absPlayer; const audio = _getAudio(); + if (!S.audio.paused) S.audio.pause(); // stop music if playing if (ap._sessionId) { absCloseSession(ap._sessionId); ap._sessionId = null; } _stopSync(); audio.pause(); @@ -255,6 +260,11 @@ export function absTogglePlay() { S.absPlayer.playing ? audio.pause() : audio.play().catch(() => {}); } +export function absInterrupt() { + if (!S.absPlayer.item || !S.absPlayer.playing) return; + _getAudio().pause(); // triggers the pause listener which calls _doSync() +} + export function absSeek(globalTime) { const ap = S.absPlayer; globalTime = Math.max(0, Math.min(globalTime, ap.duration || Infinity)); @@ -334,6 +344,7 @@ export function absClosePlayer() { } export function absToggleChapters() { + if (_playlistMode) return; document.getElementById('abs-chapter-list')?.classList.toggle('open'); _renderChapterList(); } @@ -353,6 +364,30 @@ export function absSeekToChapter(idx) { if (ch) absSeek(ch.start); } +// ─── PLAYLIST MODE ─── +let _playlistMode = localStorage.getItem('ns_abs_pl_mode') === '1'; + +export function absTogglePlaylistMode() { + _playlistMode = !_playlistMode; + localStorage.setItem('ns_abs_pl_mode', _playlistMode ? '1' : '0'); + _applyPlaylistMode(); +} + +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'); + } +} + // ─── PROGRESS SCRUB ─── let _scrubbing = false; export function absProgDown(e) { _scrubbing = true; _scrub(e); } @@ -367,6 +402,33 @@ function _scrub(e) { } // ─── RENDER HELPERS ─── +function _renderMiniBar() { + const ap = S.absPlayer; + const bar = document.getElementById('abs-mini-bar'); + if (!bar) return; + if (!ap.item) { bar.classList.remove('visible'); return; } + bar.classList.add('visible'); + const meta = ap.item?.media?.metadata || {}; + const cover = absCoverUrl(ap.item?.id); + const coverEl = document.getElementById('abs-mini-cover'); + if (coverEl) { + coverEl.innerHTML = cover + ? `` + : 'πŸ“š'; + } + const titleEl = document.getElementById('abs-mini-title'); + if (titleEl) titleEl.textContent = meta.title || 'Unbekannt'; + const ch = ap.chapters[ap.chapterIdx]; + const chEl = document.getElementById('abs-mini-chapter'); + if (chEl) chEl.textContent = ch?.title || meta.authorName || ''; + const playBtn = document.getElementById('abs-mini-play'); + if (playBtn) { + playBtn.innerHTML = ap.playing + ? `` + : ``; + } +} + function _fmt(secs) { if (!isFinite(secs) || secs < 0) secs = 0; const h = Math.floor(secs / 3600); @@ -393,6 +455,8 @@ function _renderPlayer() { _renderControls(); _renderChapterName(); _renderSleepBtn(); + _renderMiniBar(); + _applyPlaylistMode(); } function _renderProgress() { @@ -417,12 +481,14 @@ function _renderControls() { : ``; const sv = document.getElementById('abs-speed-val'); if (sv) sv.textContent = `${ap.speed}Γ—`; + _renderMiniBar(); } function _renderChapterName() { const ch = S.absPlayer.chapters[S.absPlayer.chapterIdx]; const el = document.getElementById('abs-chapter-name'); if (el) el.textContent = ch?.title || ''; + _renderMiniBar(); } function _renderChapterList() { diff --git a/src/main.js b/src/main.js index 72f22a5..8b4db3a 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, + absLoadSettingsLibraries, absTogglePlaylistMode, absInterrupt, } from './abs.js'; import { openNowPlaying, closeNowPlaying, switchNpTab, npGoArtist, npGoAlbum, @@ -126,6 +126,7 @@ window.addEventListener('load', () => { S.audio.addEventListener('timeupdate', updateProgress); S.audio.addEventListener('ended', onEnded); S.audio.addEventListener('play', () => { + absInterrupt(); setPlayIcon(true); document.getElementById('player-art').classList.add('spinning'); if(navigator.mediaSession) navigator.mediaSession.playbackState = 'playing'; @@ -270,7 +271,7 @@ Object.assign(window, { absSetSpeed, absSleepIn, absOpenPlayer, absClosePlayer, absToggleChapters, absToggleSpeedPicker, absToggleSleepPicker, absSeekToChapter, absProgDown, absProgMove, absProgUp, - absOpenBook, absPlayChapter, absConnectSettings, absSetLibrary, absDisconnect, + absOpenBook, absPlayChapter, absConnectSettings, absSetLibrary, absDisconnect, absTogglePlaylistMode, absOnSearch: (val) => { clearTimeout(window._absSearchTimer); window._absSearchTimer = setTimeout(() => { diff --git a/styles.css b/styles.css index 7093d1f..99fe3f6 100644 --- a/styles.css +++ b/styles.css @@ -951,3 +951,19 @@ nav{flex:1;overflow-y:auto;padding:4px 10px} .abs-cover-area{padding:8px 24px} .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.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-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-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}