Ein Abo lässt sich als Hörbuch (oder Hörspiel) markieren (PodcastFeed.kind).
Es verlässt dann die Podcast-Welt: keine „Neu“-Folgen, keine Warteschlangen-
Automatik, nicht in „Folgen“. In der Bibliothek steht es neben den E-Books
(static/js/audiobooks.js, eine Datei für Web, Desktop und Android):
„Weiterhören“, ein Fortschritt pro Buch, die Kapitel in Reihenfolge,
Lesezeichen mit Notiz, Schlummer bis Kapitelende.
Server: /podcasts/audiobooks/ (+<id>/), /podcasts/bookmarks/…, Migration 0019.
Player (podcasts-web.js): podcastPlayBook spielt die Kapitel nacheinander
außerhalb der Warteschlange; ⏭ markiert nicht als gehört. Vertrag:
docs/audiobooks.md. native-podcast.js aus diora-android 7b1d500.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Eine YouTube-Adresse (Kanal, Playlist, Video, Feed) lässt sich wie ein
Podcast abonnieren und in der Suche einfügen. Der Server liest dafür nur
die Kanalseite (einmal) und danach den RSS-Feed des Kanals; Ton oder Video
holt er nie: Die Adresse einer Folge ist die Videoseite (watch?v=…), und
jede App sucht sich den reinen Audio-Stream selbst (docs/youtube.md).
Shorts bleiben draußen, Stille-Analyse und ID3-Kapitel gibt es für solche
Folgen nicht.
Der Player im Browser verweist auf die Apps; die Desktop-App fragt ihren
Hauptprozess (yt-dlp) nach dem Stream. Eine Dauer, die der Feed nicht hat,
meldet der Player mit dem Fortschritt (progress/save: duration_seconds).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SoundCloud leitet die Feed-Adresse auf einen signierten Link um, der nach
zwei Minuten abläuft. Das Audio-Element fragt für jeden weiteren Teil der
Datei diesen Link an, wird abgewiesen (MEDIA_ERR_NETWORK) und blieb danach
kaputt, bis die App neu startete. Jetzt wird die Folge bei einem Fehler an
derselben Stelle neu geladen (bis zu dreimal pro Minute), und nach dem
Aufgeben beim nächsten Abspielen frisch.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Der Server findet die Pausen jetzt nach den Regeln der App (PauseCutter):
10-ms-Fenster, Rauschboden als gleitendes Perzentil, ein Fünftel jeder Pause
bleibt (60–250 ms), halb davor, halb danach. Ältere Ergebnisse werden neu
berechnet (v: 2).
Der Player läuft durch diese Teile höchstens 4× schnell statt 8×: Chromium
schaltet darüber stumm, und weil currentTime in 10–20-ms-Schritten läuft,
landete der Wechsel zurück bei 8× bis zu 200 ms in der Sprache. Pro Pause
wird das Tempo gewählt, das am meisten spart und trotzdem rechtzeitig endet.
Das Tempo des Podcasts setzt die Rate während einer Pause nicht mehr zurück.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Ein Sprung im gestreamten Podcast wartet aufs Netz (hörbares Haken). Jetzt
läuft eine Pause mit 4-facher Geschwindigkeit (höchstens 8x), je 0,12 s
bleiben an beiden Enden; nur Pausen über 3 s werden noch übersprungen.
Geprüft alle 40 ms statt mit timeupdate (alle 250 ms). Browser-Tab im
Hintergrund (gedrosselte Timer): weiter mit Sprüngen, sonst liefe Sprache
sekundenlang vierfach. Gemessen in der Desktop-App: Pausen 52,85–53,34 und
57,03–57,58 s schnell durchlaufen, kein Sprung.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- static/js/home.js rendert #tab-home aus dem, was die Seite schon hat:
zuletzt gelesenes Buch (und drei weitere), laufende oder nächste Folge mit
Restzeit und „N neue Folgen“, Favoriten-Sender (sonst die ersten
gespeicherten) und zuletzt gehörter Sender, Drift mit der zuletzt
gewählten Klangwelt und Sleep-Timer. Läuft im Browser, in diora desktop und
in der Android-App (dort mit static/js/ kopiert).
- app.js: Tab 'home' (TOP_TABS), window.__dioraAmbientStart() lädt Drift bei
Bedarf und startet es; die gewählte Klangwelt wird gemerkt und beim Laden
wiederhergestellt.
- podcasts-web.js: Startseite Home, wenn nichts anderes gewählt ist.
- native-podcast.js aus diora-android 7dcc0c6 (Home in der Startseiten-Wahl,
__dioraPodcastView).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- podcasts/silence.py: ffmpeg (imageio-ffmpeg) findet die Pausen einer Folge einmal
(silencedetect -35 dB, ab 0,4 s), im Hintergrund, höchstens 2 zugleich; gespeichert in
PodcastEpisode.silences (Migration 0018); GET episodes/<id>/silences/
- podcasts-web.js: skip_silence als Einstellung (Gerät, pro Podcast vom Server), springt
beim Abspielen über die Pausen (0,15 s bleiben)
- Drift: Ringpuffer für die Körner als AudioWorklet statt ScriptProcessor im Hauptthread
(gehäufte Aussetzer am Desktop, wenn die Seite kurz stockte)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Der stündliche Abruf der 502 Feeds dauerte 114 s, fast nur Parsen: viele Server
ignorieren ETag/Last-Modified und schicken jedes Mal den ganzen Feed (Lage der Nation
8,4 MB = 5,9 s). ⟳ in der App lief damit in ihr 60-s-Zeitlimit und schlug fehl.
- Fingerabdruck des Feed-Inhalts (ohne lastBuildDate/Kommentare, http_body_hash,
Migration 0017): derselbe Inhalt zählt als unverändert und wird nicht geparst
- _refresh_feeds: ein Pool für alle Feeds (16 parallel), jeder Feed wird geschrieben,
sobald er da ist, optionales Zeitlimit
- refresh-all wartet höchstens 40 s (done/left in der Antwort); der Browser-Host
holt den Stand dann eine Minute später noch einmal, und alle 15 Minuten
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- app.js: __dioraAmbientCmd, meldet Start/Stopp an die App (DioraNative.ambientState,
sie hält Drift bei ausgeschaltetem Bildschirm am Laufen) und an den Browser-Host
- podcasts-web.js: der Sleep-Timer zählt auch, solange Ambient spielt, stoppt es beim
Ablauf; Nachtautomatik auch beim Ambient-Start
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Player-Sync: Aktion 'sleep' (Minuten, -n Folgen, 0 aus); Reports schicken
sleep {left_ms, episodes} mit, der State zeigt ihn (Migration 0016)
- Browser-Host: Nachtfenster sleep_from/sleep_to, Automatik auch beim
Radiostart (auch in diora desktop), Timer des anderen Geräts zählt mit,
ein Timer, der stundenlang nicht lief, verfällt
- Eingabefenster (customPrompt) liegt über Player und Menüs
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- EpisodeProgress.discarded_at (Migration 0015); POST /podcasts/episodes/discard/
{"episode_ids": [...]}: aus der Warteschlange (_dequeue, auch von queue/remove benutzt)
und aus "Neu" (dismissed), discarded_at = jetzt; gehört, Stelle, Stern und updated_at
bleiben; fremde Folgen werden ausgelassen (count)
- state/ liefert "discarded": [{id, at}] der letzten 30 Tage: jedes Gerät löscht seine
ältere Datei; wieder eingereiht (_enqueue) wird discarded_at gelöscht
- Browser-Host: podcastDiscard (pausiert geladene Folge verlässt den Player, laufende spielt
zu Ende), podcastPlayEpisodeAt (Kapitel auf der Folgen-Seite)
- static/podcasts aus diora-android ee1a910 (Folgen-Seite, Folge löschen, Datei löschen,
Wischtexte und Icons, Ziehgriff)
- Tests: DiscardTests
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Setzt eine Seite window.DIORA_PODCAST_HOST, arbeitet podcasts-web.js trotz
vorhandenem DioraNative als ihr Podcast-Host:
- dock: 'existing' nimmt native-radio.js' #nui-dock statt einer Leiste in der
Web-Radioleiste; die Podcast-Seite lädt erst nach DOMContentLoaded
- styles: 'ui-only' nur podcast-ui.css, ohne das Browser-Layout (html.dpw)
- radio: false die Radio-Hooks (__dioraRadioNow …) kommen von native-radio.js;
der Sender legt die Folge weiterhin beiseite, Radio-Stopp meldet der Sync weiter
- deviceName/deviceKind wie der gemeinsame Player das Gerät führt
Ohne die Variable ändert sich im Browser nichts.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- state/ liefert inbox_total (alle neuen Folgen) und inbox_limit neben der
gekürzten Liste; Test dazu.
- Browser: Startseite (nui_start_page) beim Öffnen, „Rückgängig“-Leiste über
der Radioleiste, helle Ansicht für die neuen Teile.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- PodcastSettings (Migration 0014): skip_back / skip_forward (5–60 s),
smart_played, speed_presets (0,5–3,5×), keep_skipped, delete_dequeue,
mobile_downloads, mobile_streaming (allow/ask/never), times_by_speed;
die ersten vier sind anfangs leer (null) und werden mit POST
settings/ {"initial": {...}} nur dort gesetzt, wo noch nichts steht
- GET/POST /podcasts/settings/ und state.settings mit allen Feldern,
geprüft, nichts ändert sich bei einem ungültigen Wert
- PodcastFeed: episode_sort, episode_filter, skip_silence in
feeds/<id>/settings/ und den Feeds des States
- Tempo pro Podcast bis 3,5× (Server und AntennaPod-Import)
- AntennaPod-Import: Sortierung, Filter und Stille pro Podcast, soweit
diora sie zeigen kann; nicht darstellbare werden gezählt
- Browser-Host: Sprungweiten, "gehört" und keep_skipped aus dem Konto,
Tempo bis 3,5×, neue Einstellungen werden gespeichert
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Bisher lud die Seite bei einer neuen Version sofort neu – auch mitten im
Radio oder Podcast. Jetzt erst, wenn nichts spielt, kein Buch offen ist
und kein Textfeld den Fokus hat. Der Browser heißt im Player-Sync kurz
„Chrome · Windows“.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Server: PlayerState und PlayerDevice (Migration 0012), /podcasts/player/
(Stand), report/ (das Gerät, das zu spielen anfängt, wird das aktive und
bleibt es auch pausiert, solange es online ist), command/ (Fernbedienung,
transfer auf ein anderes Gerät) und stream/ (Server-Sent Events, zugleich
die Anwesenheit der Geräte). seq steigt in einem UPDATE und nie zurück.
Browser: podcasts-web.js meldet Abspielen, Pause, Springen, Folgen- und
Senderwechsel (alle 10 s beim Spielen), hört den Stream, hört auf, wenn ein
anderes Gerät startet, führt Befehle aus, zeigt das andere Gerät als remote
und übernimmt, was ein verschwundenes Gerät zuletzt hatte. Eine pausierte
Folge folgt einem neueren Stand von anderswo; im Vordergrund wird nach
15 s neu geholt. Podcast-Seite neu kopiert, sw v58.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Die Player-Leiste sitzt in der Radio-Leiste an der Stelle von „läuft
gerade“: am Desktop eine Zeile (Player, Lautstärke, Fokusmodus, Timer),
am Handy zwei; die Fortschritts- bzw. Live-Linie läuft über die ganze
Leiste. Der Fokusmodus zeigt den Sender weiter groß. sw v56.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Die Podcast-Leiste und der Vollbild-Player zeigen auch den Sender (Logo,
„● Live“, Stopp behält den Sender, speichern, Sleep-Timer, Sender
wechseln); die Radio-Leiste behält nur Lautstärke, Votes, Fokusmodus und
Timer. Podcast-Oberfläche aus diora-android e6761d0, sw v55.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die
Android-App: native-podcast.js aus diora-android, unverändert kopiert nach
static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf),
dazu die Podcast-Teile von native.css.
static/js/podcasts-web.js spielt im Browser die Rolle der App
(window.DioraPodcastHost statt DioraNative):
- Zustand aus /podcasts/state/, in localStorage für einen schnellen Start;
Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht
Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“
- eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist,
wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt
alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“),
automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und
Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit
Ausblenden
- Sperrbildschirm und Medientasten über navigator.mediaSession
- Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs)
- keine Downloads (die Oberfläche blendet sie aus)
- Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender
legt die Folge weg
- Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten
Bildschirmen eine mittige Spalte (760 px)
In der App und der Desktop-App (window.DioraNative vorhanden) tut
podcasts-web.js nichts.
Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die
Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von
Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab).
Service-Worker-Cache auf v52, neue Dateien im App-Shell.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>