# Audiobooks (Hörbücher & Hörspiele) A podcast subscription can be marked as an audiobook. It then leaves the podcast world (no "Neu", no queue automation, not in the podcast lists) and shows up in the library ("Bücher") next to the e-books, where it is listened to like a book: its episodes are chapters in order, "Weiterhören" goes on where the last chapter stopped, the whole book has one progress, places can be kept as bookmarks with a note, and the sleep timer can stop at the end of a chapter. ## Server (diora-web) ### The mark `PodcastFeed.kind`: `"podcast"` (default) or `"audiobook"`. A feed setting like the others: in `state.feeds[]` (`/podcasts/state/`), set with `POST /podcasts/feeds//settings/ {"kind": "audiobook"}`. Marked as an audiobook: - its episodes leave "Neu" (dismissed, not played); new ones never get there and are never queued, whatever `new_episodes` says; - it is left out of "Folgen" (`/podcasts/episodes/` without `feed_id`); - nothing else changes: places, played marks, downloads, settings stay. Episodes of it already in the queue stay there until played or removed. ### Chapters and progress Chapter order: `episode_number` (if set), then `pub_date`, then the title in natural order ("Kapitel 2" before "Kapitel 10"), then id. `GET /podcasts/audiobooks/` → `{"books": [book, …]}`, the last listened first: ``` book = { "feed_id", "title", "author", "artwork_url", "chapters": 24, "done": 6, // played chapters "total_seconds": 32400, "heard_seconds": 11400, "next": {"episode_id", "index", "position_seconds"} | null, // null: finished "last_played_at": ISO | null } ``` `heard_seconds` = played chapters' durations + the places in the others. Durations the feed doesn't have count as 0 until a player reports them (`progress/save` with `duration_seconds`, see youtube.md). `GET /podcasts/audiobooks//` → `{"book": book, "chapters": [ch, …], "bookmarks": [bm, …]}` with ``` ch = { // the queue entry's shape, so it can be handed to the player as it is "episode__id", "episode__title", "episode__audio_url", "episode__duration_seconds", "episode__artwork_url", "episode__feed__id", "episode__feed__title", "episode__feed__artwork_url", "episode__pub_date", "position_seconds", "played", "index" } bm = {"id", "episode_id", "position_ms", "note", "created_at"} // in chapter order, then place ``` ### Bookmarks - `POST /podcasts/bookmarks/ {"episode_id", "position_ms", "note"}` → `{"ok", "bookmark": bm}` - `POST /podcasts/bookmarks// {"note"}` → `{"ok", "bookmark": bm}` - `POST /podcasts/bookmarks//delete/` → `{"ok"}` Any episode can have bookmarks; the library shows those of audiobooks. ## The player (both hosts: podcasts-web.js and Android) One new host call, next to `podcastPlayEpisode`: `podcastPlayBook(json)` with `json = {"feed_id", "chapters": [ch, …], "index", "position_ms"}` (`position_ms` optional: else the chapter's own place) - plays `chapters[index]` **without putting anything in the queue**; - the book is what plays next: when a chapter ends it counts as played and the next chapter follows from its own place; after the last one playing stops; "next" (⏭, media keys) goes to the next chapter without marking the one left as played; - playing anything else (a queue episode, the radio's own handover aside) ends the book context; the book context survives a restart of the page/app; - the sleep timer needs nothing new: "end of chapter" is `podcastSleep(-1)` (end of the episode) or, for an episode with chapters of its own, the minutes until that chapter's end (`podcastSleep(ms / 60000)`, fractions are fine). `playerState()` (unchanged) tells the library which chapter plays where. ## The library (diora-web `static/js/audiobooks.js`, everywhere) One file for the web page, the desktop app and the Android app (like feeds.js; full flavour only, it needs the podcast host). It draws into `#tab-books`, above `#book-list`, in its own container `#audiobook-shelf`: - "Weiterhören": the audiobook listened to last (unless finished); - "Hörbücher": a card per audiobook — picture, title, author, "3 h 10 von 9 h · Kapitel 7/24" and a progress bar; - "+ Hörbuch": pick one of the podcast subscriptions to mark as an audiobook; - the book view: picture, progress, ▶ Weiterhören / Von vorn, the chapter list (tap: play it from its place; played ones ✓), bookmarks (tap: play from there; note editable; ✕), "+ Lesezeichen" (at the place where this book plays now), "Schlummer: Kapitelende", and ⋮ → "Wieder als Podcast", "Alles als ungehört". It talks to the server with fetch and to the player through the host (`window.DioraNative` on Android, `window.DioraPodcastHost` elsewhere): `podcastState`, `podcastFeedSettings`, `podcastPlayBook`, `playerState`, `podcastSleep`, `podcastMarkPlayedAll`. ## The podcast UI (native-podcast.js, diora-android) - feed settings sheet: "Als Hörbuch" (sets `kind`); - audiobook feeds left out of Abos, Neu, Folgen, search results of the subscriptions; a hint "In Bücher → Hörbücher" where one was moved; - the player shows a book chapter as "Kapitel 7/24 · " when it plays one.