feat: Audiobookshelf integration — browse, stream, chapters, sleep timer
- src/abs.js: full ABS API client + player engine - Login/token auth, library browsing, search - Playback session API (multi-file book support via startOffset) - Progress sync to ABS every 30s and on pause - Chapter navigation (prev/next/seek-to), ±10s / ±30s skip - Speed control (0.75–2×), sleep timer with countdown - index.html: sidebar nav, audiobooks/detail pages, full-screen player overlay, ABS settings section (URL + credentials + library picker) - state.js: abs + absPlayer state objects - views.js: navigate() handles 'audiobooks' and 'abs-book-detail' - styles.css: book grid, detail page, player overlay, pickers Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
719d4d9afd
commit
c17b7ea11a
7 files changed
+948
No files matched your search
+165
@@ -91,6 +91,12 @@
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><path d="M8 11.973c2.5-1.473 5.5-.973 7.5.527M9.5 14.473c1.5-.973 3.5-.973 5 .027M7 9c3-1.5 6.5-1 9 1"/></svg>Spotify Importer
|
||||
</div>
|
||||
</div>
|
||||
<div class="nav-section" id="nav-abs-section" style="display:none">
|
||||
<div class="nav-section-title">Hörbücher</div>
|
||||
<div class="nav-item" onclick="navigate('audiobooks')" data-page="audiobooks">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z"/><path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z"/></svg>Bibliothek
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</aside>
|
||||
|
||||
@@ -301,6 +307,28 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- AUDIOBOOKS -->
|
||||
<div class="page" id="page-audiobooks">
|
||||
<div class="page-header">
|
||||
<div class="page-title">Hörbücher <em>📚</em></div>
|
||||
</div>
|
||||
<div class="search-wrap" style="padding:0 0 16px">
|
||||
<div class="search-box" style="background:var(--surface);border-color:var(--border2)">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.35-4.35"/></svg>
|
||||
<input id="abs-search-input" type="text" placeholder="Titel oder Autor suchen…" oninput="absOnSearch(this.value)">
|
||||
</div>
|
||||
</div>
|
||||
<div id="abs-books-grid" class="abs-books-grid"></div>
|
||||
</div>
|
||||
|
||||
<!-- AUDIOBOOK DETAIL -->
|
||||
<div class="page" id="page-abs-book-detail">
|
||||
<button class="back-btn" onclick="navigate('audiobooks')">
|
||||
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="15 18 9 12 15 6"/></svg>Hörbücher
|
||||
</button>
|
||||
<div id="abs-book-detail-content"></div>
|
||||
</div>
|
||||
|
||||
<!-- SPOTIFY IMPORTER -->
|
||||
<div class="page" id="page-spotify-importer">
|
||||
<div class="page-header">
|
||||
@@ -498,6 +526,26 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="settings-section">
|
||||
<h3 style="display:flex;align-items:center;gap:8px;margin-bottom:12px">
|
||||
<span style="width:24px;height:24px;border-radius:6px;background:var(--green-dim);display:inline-flex;align-items:center;justify-content:center;font-size:12px">📚</span>
|
||||
Audiobookshelf
|
||||
</h3>
|
||||
<div class="field"><label>Server URL</label><input id="abs-s-url" type="url" placeholder="https://books.example.com"></div>
|
||||
<div class="field"><label>Benutzername</label><input id="abs-s-user" type="text" placeholder="admin"></div>
|
||||
<div class="field"><label>Passwort</label><input id="abs-s-pass" type="password" placeholder="••••••••"></div>
|
||||
<div id="abs-lib-picker" style="display:none" class="field">
|
||||
<label>Bibliothek</label>
|
||||
<select id="abs-lib-select" onchange="absSetLibrary(this.value)" style="width:100%;padding:10px 12px;background:var(--bg2);border:1px solid var(--border2);border-radius:var(--radius-sm);color:var(--text);font-family:var(--font);font-size:14px;outline:none"></select>
|
||||
</div>
|
||||
<div class="settings-row" style="margin-top:8px;border-radius:9px">
|
||||
<span id="abs-s-status" style="font-size:13px;color:var(--green)"></span>
|
||||
<div style="display:flex;gap:8px">
|
||||
<button class="btn-outline" style="padding:6px 12px;font-size:12px;color:var(--danger);border-color:rgba(224,85,85,0.3)" onclick="absDisconnect()">Trennen</button>
|
||||
<button class="btn-outline" id="abs-s-connect-btn" style="padding:6px 12px;font-size:12px" onclick="absConnectSettings()">Verbinden</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="settings-section">
|
||||
<h3 style="display:flex;align-items:center;gap:8px;margin-bottom:12px">
|
||||
<span style="width:24px;height:24px;border-radius:6px;background:var(--surface2);display:inline-flex;align-items:center;justify-content:center;font-size:12px">🎨</span>
|
||||
@@ -523,6 +571,123 @@
|
||||
<div class="toast-container" id="toast-container"></div>
|
||||
<audio id="audio-element"></audio>
|
||||
<audio id="preview-audio"></audio>
|
||||
<audio id="abs-audio"></audio>
|
||||
|
||||
<!-- AUDIOBOOKSHELF PLAYER OVERLAY -->
|
||||
<div id="abs-player" class="abs-player-overlay">
|
||||
<!-- Header -->
|
||||
<div class="abs-player-header">
|
||||
<button class="abs-hbtn" onclick="absClosePlayer()">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="6 9 12 15 18 9"/></svg>
|
||||
</button>
|
||||
<span class="abs-player-label">Hörbuch</span>
|
||||
<div style="width:36px"></div>
|
||||
</div>
|
||||
|
||||
<!-- Cover -->
|
||||
<div class="abs-cover-area">
|
||||
<div class="abs-cover-wrap">
|
||||
<img id="abs-cover" src="" alt="" class="abs-cover-img" style="display:none">
|
||||
<div id="abs-cover-placeholder" class="abs-cover-ph">📚</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Info -->
|
||||
<div class="abs-player-info">
|
||||
<div id="abs-title" class="abs-player-title">—</div>
|
||||
<div id="abs-author" class="abs-player-author"></div>
|
||||
<div id="abs-chapter-name" class="abs-player-chapter"></div>
|
||||
</div>
|
||||
|
||||
<!-- Progress bar -->
|
||||
<div class="abs-progress-area">
|
||||
<div class="abs-prog-bar" id="abs-prog-bar"
|
||||
onmousedown="absProgDown(event)" onmousemove="absProgMove(event)" onmouseup="absProgUp(event)"
|
||||
ontouchstart="absProgDown(event)" ontouchmove="absProgMove(event)" ontouchend="absProgUp(event)">
|
||||
<div class="abs-prog-track">
|
||||
<div class="abs-prog-fill" id="abs-prog-fill"></div>
|
||||
<div class="abs-prog-thumb" id="abs-prog-thumb"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="abs-prog-times">
|
||||
<span id="abs-time-cur">0:00</span>
|
||||
<span id="abs-time-rem">-0:00</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Main controls -->
|
||||
<div class="abs-controls">
|
||||
<button class="abs-ctrl-btn" onclick="absPrevChapter()" title="Voriges Kapitel">
|
||||
<svg width="22" height="22" viewBox="0 0 24 24" fill="currentColor"><polygon points="19 20 9 12 19 4 19 20"/><line x1="5" y1="19" x2="5" y2="5" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"/></svg>
|
||||
</button>
|
||||
<button class="abs-ctrl-btn abs-skip-btn" onclick="absSkipSecs(-10)" title="-10s">
|
||||
<svg width="26" height="26" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8">
|
||||
<path d="M12 22C6.477 22 2 17.523 2 12S6.477 2 12 2s10 4.477 10 10"/>
|
||||
<polyline points="15 9 12 6 9 9"/>
|
||||
<path d="M12 6v4"/>
|
||||
</svg>
|
||||
<span class="abs-skip-label">10</span>
|
||||
</button>
|
||||
<button class="abs-play-pause" id="abs-playpause-btn" onclick="absTogglePlay()">
|
||||
<svg width="28" height="28" viewBox="0 0 24 24" fill="currentColor"><polygon points="5 3 19 12 5 21 5 3"/></svg>
|
||||
</button>
|
||||
<button class="abs-ctrl-btn abs-skip-btn" onclick="absSkipSecs(30)" title="+30s">
|
||||
<svg width="26" height="26" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8">
|
||||
<path d="M12 22C17.523 22 22 17.523 22 12S17.523 2 12 2 2 6.477 2 12"/>
|
||||
<polyline points="9 9 12 6 15 9"/>
|
||||
<path d="M12 6v4"/>
|
||||
</svg>
|
||||
<span class="abs-skip-label">30</span>
|
||||
</button>
|
||||
<button class="abs-ctrl-btn" onclick="absNextChapter()" title="Nächstes Kapitel">
|
||||
<svg width="22" height="22" viewBox="0 0 24 24" fill="currentColor"><polygon points="5 4 15 12 5 20 5 4"/><line x1="19" y1="5" x2="19" y2="19" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Extra controls -->
|
||||
<div class="abs-extra-controls">
|
||||
<!-- Speed -->
|
||||
<div class="abs-picker-wrap">
|
||||
<button class="abs-extra-btn" id="abs-speed-btn" onclick="absToggleSpeedPicker()">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>
|
||||
<span id="abs-speed-val">1×</span>
|
||||
</button>
|
||||
<div id="abs-speed-picker" class="abs-picker">
|
||||
<button data-speed="0.75" onclick="absSetSpeed(0.75)">0.75×</button>
|
||||
<button data-speed="1" onclick="absSetSpeed(1)">1×</button>
|
||||
<button data-speed="1.25" onclick="absSetSpeed(1.25)">1.25×</button>
|
||||
<button data-speed="1.5" onclick="absSetSpeed(1.5)">1.5×</button>
|
||||
<button data-speed="1.75" onclick="absSetSpeed(1.75)">1.75×</button>
|
||||
<button data-speed="2" onclick="absSetSpeed(2)">2×</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Sleep timer -->
|
||||
<div class="abs-picker-wrap">
|
||||
<button class="abs-extra-btn" id="abs-sleep-btn" onclick="absToggleSleepPicker()">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/></svg>
|
||||
</button>
|
||||
<div id="abs-sleep-picker" class="abs-picker abs-picker-right">
|
||||
<button onclick="absSleepIn(0)">Aus</button>
|
||||
<button onclick="absSleepIn(5)">5 Min</button>
|
||||
<button onclick="absSleepIn(10)">10 Min</button>
|
||||
<button onclick="absSleepIn(15)">15 Min</button>
|
||||
<button onclick="absSleepIn(30)">30 Min</button>
|
||||
<button onclick="absSleepIn(45)">45 Min</button>
|
||||
<button onclick="absSleepIn(60)">60 Min</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Chapter list toggle -->
|
||||
<button class="abs-extra-btn" onclick="absToggleChapters()">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="8" y1="6" x2="21" y2="6"/><line x1="8" y1="12" x2="21" y2="12"/><line x1="8" y1="18" x2="21" y2="18"/><line x1="3" y1="6" x2="3.01" y2="6"/><line x1="3" y1="12" x2="3.01" y2="12"/><line x1="3" y1="18" x2="3.01" y2="18"/></svg>
|
||||
Kapitel
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Chapter list -->
|
||||
<div id="abs-chapter-list" class="abs-chapter-list"></div>
|
||||
</div>
|
||||
|
||||
<!-- CONTEXT MENU -->
|
||||
<div class="ctx-menu" id="ctx-menu" style="display:none"></div>
|
||||
|
||||
Reference in new issue
Block a user