# Podcasts: Strom, Merkliste, Wiedergabeliste The plan agreed on 2026-10-19 (with the user, from their listening history). It replaces the inbox model ("Neu" to work through, "Folgen" beside it, one queue that is both a store and the play order). Server: diora-web (podcasts/). The podcast UI is diora-android's `web-native/static/native-podcast.js`, copied into `static/podcasts/` — built there. ## Why - The subscriptions (about 540) are a self-chosen part of the podcast world: episodes to see go by and dip into now and then — not a list to work through. "Neu" had 3360 unplayed episodes; nobody works through that. - The queue held 74 episodes (100+ hours): a store of "want to hear some day", yet the player played it in order as if it were the play order. - In AntennaPod "download automatically" and "add to queue automatically" were one thing for the user: a regular podcast is one whose new episodes are on the phone. diora split them (177 feeds downloading, 8 queueing). - "Silent" subscriptions came from AntennaPod fetching every feed on the phone, slowly. The server fetches all feeds anyway now (conditional, every 15 minutes). ## The lists **Wiedergabeliste** — short, what plays next. The player goes through it in order and **stops when it is empty** (it does not take from the Merkliste). Every episode in it is downloaded. Filled by hand: ▶ on an episode (plays it, puts it first), or "In die Wiedergabeliste" from the Merkliste or the Strom. **Merkliste** — long, "want to hear". It may grow; it **never expires**. Grouped by podcast, each podcast **collapsible** (newest first inside). New episodes of Stamm subscriptions land here and are downloaded; so is what the user adds with +. An episode played from it moves to the front of the Wiedergabeliste; finished, it leaves both. When an episode leaves both lists (heard, removed), its download is deleted (setting `delete_dequeue`). Downloads follow the lists, not the subscriptions. ## Subscriptions: three levels | level | new episodes | today's mapping | |---|---|---| | **Stamm** | into the Merkliste and downloaded (per feed: or straight into the Wiedergabeliste — an option, used by none yet) | `new_episodes='queue'` (8) plus the regularly heard ones (about 50, list below) | | **Strom** (default) | in the Strom; nothing to work through, nothing downloaded | `'inbox'`, and the former silent `'ignore'` ones | | **Auf Abruf** | not in the Strom; on the podcast's page and in search | — (for exceptions) | The per-feed "download automatically" switch goes: Stamm means it. **Fetch interval** per subscription: *Automatisch* (by publishing rhythm, `interval_days`: daily publishers hourly, rare ones daily), *stündlich*, *täglich*, *wöchentlich*. The former silent subscriptions become Strom with *täglich*. ## The Strom (replaces "Neu" and "Folgen") All episodes of all Strom and Stamm subscriptions, by period (Heute, Gestern, Diese Woche, …). Within a period the podcasts are ordered by **how much the user has listened to them** (episodes heard): every podcast with at least one heard episode before every one without — those count for infinitely more (heard in the last 12 months, `heard_365`); among the never-heard ones, rarely publishing first. Each podcast appears **once per period, always bundled**: its newest episode, and "+n ›" for the others. Episodes new since they were last seen carry a **small mark** (no counts). Per episode: ▶ (Wiedergabeliste, first), + (Merkliste), ★. Filters stay: Favoriten, Verlauf, Angefangen, Gehört. Tabs: **Wiedergabeliste · Merkliste · Strom · Abos**. ## Favourite subscriptions ★ on a subscription (`PodcastFeed.favorite`): a mark and a filter (Abos: only the favourites; episode lists: `filter=fave_feeds`), whatever its level — it puts nothing on the Merkliste by itself. Started with the Stamm subscriptions marked. ## Stöbern (decided 2026-10-19) Within the subscriptions: - **Search** in titles and shownotes: `GET /podcasts/discover/search/?q=…&stream=1` (all words, the last as a prefix; titles weigh more). SQLite FTS5 index, contentless (`podcasts/discover.py`), filled after every refresh; `manage.py index_episodes` fills it. - **Similar episodes** from the other podcasts: `GET /podcasts/discover/similar//`. - **People** (who talks, who gives a talk), from episode titles only (`podcasts/people.py`): `GET /podcasts/discover/people/` → `[{name, episodes, feeds, score}]`, the ones in the most listened podcasts first; `GET /podcasts/discover/person/?name=` → their episodes. - **Topics** of the Strom's last days, chips as for the news: `GET /podcasts/discover/topics/?days=7` → `[{label, ids}]`. - **Forgotten**: `GET /podcasts/discover/forgotten/` → `{loved: [...], rare: [...]}` — unheard episodes older than 60 days of the podcasts listened to most, and of the rare ones; the same all day. One controlled step out (Apple's episode search and fyyd; no key): - **People elsewhere**: `GET /podcasts/discover/elsewhere/?name=` → episodes naming the person in podcasts the user doesn't have (`{source, podcast, feed_url, title, audio_url, guid, pub_date, duration_seconds, artwork_url, link}`). `POST /podcasts/discover/take/` with such an item puts the episode on the Merkliste; its podcast becomes an *Auf Abruf* subscription. - **Recommendations**: `GET /podcasts/discover/recommend/` → podcasts like the most listened ones (fyyd), recommended for several first: `{title, feed_url, artwork_url, description, link, because: [titles], count}`. Subscribing: the usual feeds/add/ (then *Auf Abruf* or Strom). Show search stays on Apple (best coverage). ## Moving over - Today's queue (74) becomes the Merkliste; the Wiedergabeliste starts empty. - Stamm: the 8 automatic subscriptions and the regularly heard ones (≥ 8 episodes a year or ≥ 3 in 90 days, 2026-10-19): Ones and Tooze, Die Neuen Zwanziger – Der Salon, Surplus, The Deprogram, junge Welt – Tagesüberblick, Speakeasy-Bar, jwpod – Forschung, RLS Audiodokumente, The Reaction Pod, Geschichten aus der Geschichte, Märkte Mächte Emissionen, friendlyjordies, Second Thought, Neben der Spur, Forschungsquartett, GegenStandpunkt – Artikel, Hörsaal, Hakim, Marxistische Vorträge, Blätter, Kontextfenster, New Left Review, Kommunisten Kneipe, Jung & Naiv, HealthyGamerGG, Dekarldent, Daily Dose Of Dekarldent, Eine Stunde History, Übertage, Eigentlich, Zwischen Zwei Deckeln, UNFASSBAR, Über Rechts, TitosLeftLung, New Books in History, ARTEde, Irgendwas mit ARTE, Zwei wie Pech & Schwafel, Geschichte der kommenden Welten, Vollbild, Zeitfragen, Zeitfragen. Feature, Die Filmanalyse, Reaction, Wirtschaft am Mittag. - Everything else: Strom; the silent ones with *täglich*. ## Order of work 1. Wiedergabeliste and Merkliste (server: a second list, moving the queue over, the player's order; UI: two tabs, grouped Merkliste). 2. The three levels and the fetch interval; downloads following the lists. 3. The Strom replacing "Neu" and "Folgen". 4. Browsing — after more thought. ## API (server, since 2026-10-19) - `PodcastSettings.lists_v2` turns the two lists on. `POST /podcasts/lists/ {"on": true}` moves the queue into the Merkliste (the Wiedergabeliste starts empty); `{"on": false}` puts the Merkliste back at the end of the queue. Off, everything works as before (apps not updated yet). - **Wiedergabeliste** = the queue as it was: `state.queue`, `/podcasts/queue/add|remove|reorder/`. The player goes through it and stops at its end. - **Merkliste** = `PodcastLater`: `state.later` (rows like the queue's, newest first, plus `added_at`, `auto` — added by a Stamm feed — and `in_queue`), `POST /podcasts/later/add/` and `/podcasts/later/remove/` `{"episode_ids": [...]}`. Heard (progress at the end, mark-played) and "Folge löschen" take an episode off it. Episode lists carry `in_later`; filter `later`. - **Levels**: `PodcastFeed.new_episodes` — `queue` = Stamm into the Merkliste (with lists_v2; without it: the queue), `play` = Stamm into the Wiedergabeliste, `inbox` = Strom, `ignore` = Auf Abruf. `auto_queue` stays true for queue and play. - **Fetch interval**: `PodcastFeed.refresh_interval` — `''` (by rhythm: every `interval_days` hours, within 15 minutes and 24 hours), `hourly`, `daily`, `weekly`; the cron's `refresh_feeds` only fetches feeds that are due (`views.refresh_due`). - **Strom**: `GET /podcasts/episodes/?filter=stream` (all but Auf Abruf; with `folder=`, `sort=`); episodes carry `discovered_at`. Per feed in `state.feeds`: `heard_365` (episodes heard in the last 12 months: played, or listened into for more than a minute) and `interval_days`. `POST /podcasts/stream/seen/` → `{before, now}`; `state.stream_seen_at`. An episode is marked when its `discovered_at` is after the time before the current look. - Web host (`static/js/podcasts-web.js`, `DioraPodcastHost`): `podcastLaterAdd(json episodes)`, `podcastLaterRemove(json ids)`, `podcastListsSwitch(on)`, `podcastStreamSeen()` (keeps the previous time as `state.stream_seen_before` for the marks).