Skip to content
TIA568BPublic

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

Daily Podcast Queue

A Home Assistant custom integration that prepares a daily "playlist" of the podcasts that published a new episode today — in your chosen listening order, silently skipping any podcast with nothing new — ready for you to play on demand on a Sonos speaker when you have time.

It never auto-plays. Each morning it quietly prepares that day's playlist (fetches feeds, works out today's episodes, records them). You then press a button — or call the service, or trigger it from your own automation — to actually play it. Nothing starts blasting out of a speaker at 6am.

Playback goes through Music Assistant (mass.play_media) when that's available, and otherwise falls back to Home Assistant's built-in media_player.play_media, so it works with a plain Sonos speaker even without Music Assistant.

Because every day's playlist is recorded, you can also replay past days — play catch-up after a holiday, for example — even after those episodes have aged out of the feeds.

No external API, no cloud service — just the podcasts' own RSS feeds. Everything is managed from the Home Assistant UI — no YAML editing. You add, remove, and reorder podcasts from the Daily Podcasts sidebar panel, and set the player and prepare time from the integration's Configure screen.


Install via HACS (one click)

This project is a HACS-installable custom integration. Add it to HACS with this button, which opens HACS on your Home Assistant already pointed at this repository:

Open this repository in HACS on your Home Assistant.

Then:

  1. In the dialog that opens, confirm adding TIA568B/hapodcast as an Integration custom repository.
  2. Select Download, then restart Home Assistant.
  3. Go to Settings → Devices & services → Add integration, search for Daily Podcast Queue, and follow the setup (see Set it up).

The button uses a My Home Assistant link: it opens your own instance (the URL is stored only in your browser) and needs HACS already installed. If the button doesn't open the add-repository dialog, add it manually in HACS: ⋮ → Custom repositories → URL https://github.com/TIA568B/hapodcast, category Integration → Add, then download it.

Manual install (no HACS): copy custom_components/daily_podcasts/ into your HA config/custom_components/ directory and restart. Home Assistant installs the feedparser dependency automatically from the integration's manifest.


How it works

Preparing (automatic, each morning — no sound):

  1. At the time you set, the integration's built-in daily schedule runs — in "prepare only" mode (build_queue with play: false). It never plays.

  2. For each podcast in your list (in order), it fetches the RSS feed fresh over HTTP (cache-busting headers, so what's "new" is decided on current data) and parses every episode's audio enclosure URL and publish date.

  3. It keeps all episodes published since that podcast was last prepared, in your Home Assistant timezone — so a feed that drops more than one episode in a day contributes all of them (ordered oldest-first within that feed). Feeds keep your configured list order; feeds with nothing new are dropped.

    Catch-up window (per podcast, default on): each podcast tracks a high-water mark — the publish time of the newest episode it has already offered you. A run includes everything published after that mark, through now. This is gap-proof: whether you missed a day, a weekend, a holiday, or Home Assistant was down, the next run picks up exactly what you haven't been given yet — no weekday special-casing.

    • First run (no mark yet, e.g. a newly-added podcast): offers only that podcast's newest episode within the look-back window, so adding a podcast immediately gives you its latest episode without a backlog. After that, normal "everything since" catch-up takes over.
    • Max look-back (default 18 days, Settings): caps how far back catch-up reaches, so a very old mark after a long outage can't queue a huge backlog.
    • De-duplication: episodes are tracked by their feed GUID, so an episode you've already been offered is never queued twice, even if its feed's timestamps are fuzzy.
    • Catch-up off (per podcast): that podcast uses today only instead.
    • The mark only advances when a prepare/play actually records episodes; replaying history and dry-runs never move it, and a feed that fails to fetch keeps its mark and retries next run.
  4. It records that ordered playlist to a per-day file (config/daily_podcasts_history/YYYY-MM-DD.json).

Playing (on demand — when you ask):

  1. When you press the button / call daily_podcasts.build_queue (which plays by default) / fire it from your own automation, it builds today's playlist the same way and queues it on your player, clearing the old queue first and playing the episodes back-to-back:

    • With Music Assistant: one mass.play_media call with the whole list, enqueue: replace.
    • Without Music Assistant (native Sonos): clear the queue (media_player.clear_playlist), queue the first episode with enqueue: play (which builds a real Sonos queue and starts it), then append the rest with enqueue: add, so playback advances episode to episode.

    To replay a previous day instead, use daily_podcasts.play_history (see Catch-up).

    If there's nothing new to build when you press play — typically because the scheduled 06:00 prepare already built today's playlist and advanced the per-podcast high-water marks — the button plays today's already-prepared playlist from history instead of doing nothing. So pressing play always plays today's episodes, whether or not a prepare already ran.

    Instant play (prefer_prepared: true). A normal build_queue fetches and parses every feed before it queues, which can take ~15-25s (6 feeds ≈ 20s in testing) — a noticeable delay when triggering from a physical button. Pass prefer_prepared: true and, if today's playlist is already prepared (which it will be once the daily or intraday prepare has run), the service plays it immediately without fetching feeds — playback starts in about a second. It only falls back to a full fetch-and-build if nothing is prepared for today yet. This is the recommended flag for button / voice triggers.

    prefer_prepared is also resume-aware, so pressing the button again mid-listen doesn't drag you back to episode 1:

    • If the player is already playing today's playlist (any episode in it), it's left exactly as-is.
    • If the player is paused on today's playlist (e.g. you paused to take a call), it resumes from where you left off.
    • Only when the player is idle/stopped, finished the queue, or playing something else does it (re)queue today's playlist from the start.

Failures are isolated: a single unreachable or malformed feed is logged and the rest of the list still plays. In prepare-only mode (play: false), if nothing is new since the last run it logs that and writes no history file.

To replay stored days later, call daily_podcasts.play_history — see Catch-up: replay past days.


Files

custom_components/
  daily_podcasts/
    __init__.py        # the integration logic + services + daily scheduler
    config_flow.py     # the UI setup + options (manage) screens
    const.py           # constants
    manifest.json      # domain, version, config_flow, feedparser requirement
    services.yaml      # service definitions for the HA UI
    strings.json       # UI strings
    translations/
      en.json          # English UI translations
    frontend.py         # serves/registers the sidebar panel
    www/
      daily-podcasts-panel.js  # full-screen podcast manager
hacs.json              # HACS metadata (repo root)
automations.yaml       # OPTIONAL example (catch-up button); not required
configuration.yaml.snippet  # OPTIONAL YAML import path; not required
README.md

At runtime the integration creates config/daily_podcasts_history/ for the recorded playlists. That's runtime data, so it's git-ignored.


Prerequisites

  • Home Assistant with a Sonos (or other) media_player entity to queue onto.
  • Music Assistant is optional. If you have it set up (podcasts added as RSS Feed providers, speakers showing as MA player entities), the integration uses mass.play_media. If not, it falls back to the native media_player.play_media on whatever media_player you select (e.g. the Sonos integration's own entity).
  • HACS (for the one-click install). Not required for the manual install.
  • feedparser — installed automatically; it's declared in the integration's manifest.json and Home Assistant installs it on startup.

Set it up

Everything is done in the UI — no YAML.

  1. Settings → Devices & services → Add integration → search Daily Podcast Queue.
  2. In the setup dialog, choose your Player (the Sonos / Music Assistant media_player), the time to prepare the daily playlist (default 06:00), and whether to prepare a playlist automatically each day. (Preparing never plays — it just gets today's playlist ready.)
  3. Finish. Then open the Daily Podcasts panel in the sidebar to add your podcasts.

Check Settings → System → Logs for lines beginning [daily_podcasts].

Coming from an older YAML setup? If you still have a daily_podcasts: block in configuration.yaml, the integration imports it into a UI entry once on startup, then ignores the YAML. You can delete the block afterwards and manage everything from the UI. New installs don't need any YAML at all.


Manage it (all in the UI)

The Daily Podcasts sidebar panel

The integration adds a dedicated Daily Podcasts item to the Home Assistant sidebar — similar to the Shopping List panel. After updating and restarting Home Assistant, look for Daily Podcasts in the left sidebar with the podcast icon. If it is not visible immediately, refresh the browser once. No Lovelace dashboard card or manual dashboard resource is required.

The panel has two tabs:

  • Overview — shows what's playing now (with a progress bar), the current play queue, and at-a-glance info: whether automatic daily prepare is on, the next scheduled prepare time, the prepare time of day, and your podcast count. Each queued item shows its podcast and episode title, with the current item highlighted. Each item has a Skip to Podcast button that jumps playback straight to it, and a ✕ that removes just that item from the queue. (Both use the Sonos integration — sonos.play_queue and sonos.remove_from_queue — so on other players these controls are disabled.)
  • Management — add, remove, edit, and drag-reorder podcasts (and toggle each one's catch-up) on a single screen, then save the whole list atomically.

The panel uses the same backend services that are available for automations: daily_podcasts.list_podcasts, daily_podcasts.set_podcasts, daily_podcasts.get_queue, daily_podcasts.skip_to, and daily_podcasts.remove_from_queue.

Use the native Sonos media_player entity. The live queue, Skip, and Remove controls use the Sonos integration's own queue actions, which only work on the native Sonos entity for a speaker (e.g. media_player.office). If the configured player is a different entity for the same speaker — for example a Music Assistant player — the Overview tab can't read or control the queue and will say so. Set the player to the Sonos entity in Configure.

Settings — the Configure screen

Settings → Devices & services → Daily Podcast Queue → Configure holds the integration-wide settings (not the podcast list, which lives in the sidebar panel above):

  • The player to queue onto.
  • The time to prepare the daily playlist.
  • The "prepare automatically each day" on/off switch.
  • The max catch-up look-back (days).
  • Daytime refresh (off by default): when on, the integration runs extra prepare-only passes during a daytime window, so today's recorded playlist picks up episodes that publish after the daily run. Configure the interval (hours) and the start/end hour of the window (default hourly, 07:00–17:00). Like the daily run, these never auto-play. Requires the daily automatic prepare to be on.
  • An optional timezone override.

Changes take effect immediately (the integration reloads itself and re-arms the daily schedule). No restart, no YAML.

Turn off the automatic daily prepare: uncheck "Prepare a playlist automatically each day" in Settings. You can still prepare/play on demand any time via the button or service.


Play it (on demand)

The daily schedule only prepares the playlist; you decide when to play. Any of these plays today's episodes on your player:

  • A dashboard button — the recommended setup. See Buttons on a dashboard below.
  • Developer Tools → Actions: pick Daily Podcast Queue: Build and play today's podcast queue (daily_podcasts.build_queue) and Perform action — it plays by default.
  • Your own automation calling daily_podcasts.build_queue (e.g. tied to arriving home, a voice command, etc.).

Useful options on build_queue:

action: daily_podcasts.build_queue
data:
  player: media_player.office   # override the configured player
  play: false                   # prepare/record only, don't play
  dry_run: true                 # log the plan, touch nothing
  • play: false — prepare today's playlist without playing (same as the daily schedule).
  • dry_run: true — logs which podcasts are included/skipped and in what order, without playing or writing history. Handy for verifying your list.

Re-running the same day is safe: it rebuilds the identical queue, replacing the old one, so you never get duplicates.

Buttons on a dashboard

A ready-made set of helpers + automations is included (created on your instance):

  • Play Daily Podcasts (input_button.play_daily_podcasts) → plays today's playlist on the office Sonos.
  • Podcast Catch Up Range (input_select.podcast_catch_up_range) → pick "Last N days" / "Everything recorded".
  • Play Podcast Catch Up (input_button.play_podcast_catch_up) → plays the selected range.

Add those three to a dashboard card. Press Play Daily Podcasts when you're ready to listen; set a range and press Play Podcast Catch Up to catch up.


Catch-up: replay past days

Every daily run saves the playlist it built to config/daily_podcasts_history/YYYY-MM-DD.json. Because the daily prepare runs every morning regardless of whether you're home, those files pile up while you're away with nothing for you to switch on. When you're back, replay them with daily_podcasts.play_history.

Days play in chronological order, and within each day the original podcast order is preserved. The whole span is queued the same way as a normal play (mass.play_media if Music Assistant is available, otherwise media_player.play_media replace-then-add), clearing the previous queue first — so it's a single continuous queue, and re-running it rebuilds the same queue rather than duplicating.

Call it from Developer Tools → Actions (pick Daily Podcast Queue: Play recorded podcast history) or in YAML. Pick episodes with one of these selectors (first match wins):

# Catch up on everything since the day you left:
action: daily_podcasts.play_history
data:
  since: "2026-09-20"        # from this date through today
# A specific date range:
action: daily_podcasts.play_history
data:
  start: "2026-09-20"        # either side is optional
  end: "2026-09-24"
# Just one day:
action: daily_podcasts.play_history
data:
  date: "2026-09-22"
# The last N recorded days:
action: daily_podcasts.play_history
data:
  days: 5
# Everything recorded (omit all selectors):
action: daily_podcasts.play_history

Optional extras: player: to target a different speaker, and dry_run: true to log exactly what would play without touching the speaker.

Selector precedence: date → start/end → since → days → (nothing = all recorded days). Only days that actually have a recorded file are included; requesting a day with no file is silently skipped.

The preset Podcast Catch Up Range picker (Last N days / Everything) covers most catch-up needs. If you'd rather catch up from an exact date, create a Date input_datetime helper and point a button at daily_podcasts.play_history with since: set from it — the optional automations.yaml in this repo has a ready-to-adapt example.


Behavior guarantees (and how they're met)

Requirement How
Never auto-plays Daily schedule runs build_queue with play: false — it only prepares/records; playback happens only on an explicit call
Play on demand Button / service / your automation calls build_queue (plays by default)
Manage without YAML Config flow + options menu (add/remove/reorder/settings) in the UI
Edit the list, nothing else The ordered podcast list is the one thing you edit, from Configure
Daily prepare without automations.yaml Built-in schedule via async_track_time_change, re-armed on options change
Preserve order while skipping Included items keep original list order; skipped/failed simply omitted
All of a day's episodes Every episode in the catch-up window is included per feed (not just the newest), oldest-first within the feed
Local-tz dates Each episode's UTC pubDate is converted to hass.config.time_zone before comparing
Never miss anything Per-podcast high-water mark: each run includes everything published since the last successful prepare (gap-proof across missed days/outages)
No duplicates Mark advances only on recorded prepares; GUID de-dupe against recent history; replays/dry-runs don't advance it
Bounded backlog Max look-back (default 18 days) caps how far catch-up reaches
Newly-added podcast is playable First run (no mark) offers that podcast's newest recent episode, not a backlog; then catches up normally
Per-podcast catch-up Each podcast has a catch-up on/off flag (default on; off = today only), set from Add/Edit a podcast
Resilient to feed outages A feed that fails to fetch keeps its mark and retries next run
Fresh data Feeds are fetched over HTTP with no-cache headers at trigger time
Queue on Sonos in order mass.play_media (one ordered list) when MA is present; else clear queue + first enqueue: play + rest enqueue: add so a real Sonos queue is built and advances
Works without Music Assistant Falls back to native media_player services on the chosen player
Feed failure isolation Per-feed try/except logs the error and continues
Nothing today No episodes → no service call, speaker untouched, logged
Idempotent reruns enqueue: replace rebuilds the same queue, no duplicates
Catch-up after time away Each day's playlist is recorded to JSON; play_history replays any day/range in chronological, per-day order
History survives feed aging Replay reads the recorded files, not the live feeds, so aged-out episodes still play
Non-blocking Feed fetch/parse and file I/O run in the executor, off the event loop

Troubleshooting

  • Integration not found after install: make sure you restarted HA after downloading in HACS, and that custom_components/daily_podcasts/manifest.json exists. Check the logs for load errors.
  • No daily_podcasts.* services: make sure you added the integration from Settings → Devices & services → Add integration. The services register once the config entry is set up. Check the logs for load errors.
  • "It didn't play this morning": that's by design — the daily schedule only prepares the playlist (it never auto-plays). Press the Play Daily Podcasts button, or call daily_podcasts.build_queue, to play. The log shows Daily run scheduled for HH:MM:SS when the prepare is armed, and Prepared today's playlist ... not playing now (play=False) after it prepares.
  • The button records but doesn't play: check the log line — if you see play=False, the call passed play: false. The button should call build_queue with no play (defaults to true) or play: true.
  • First run only offers today (new install or just upgraded): that's by design. Each podcast's catch-up high-water mark starts empty, so the first prepare offers today only and sets the mark from there; every run after that is gap-proof. If episodes you hadn't listened to yet (e.g. a weekend's worth) are sitting unplayed when you first run it, grab them with the catch-up button (daily_podcasts.play_history) — only days the integration has already recorded are available there.
  • A single podcast keeps re-queuing or never catches up: catch-up relies on stable feed publish dates (and GUIDs for de-dupe). The per-podcast high-water mark advances to the newest episode seen in the catch-up window on every recorded run — including episodes de-duped away — so an already-offered episode is not resurfaced even if it rotates out of recent history. If a feed rewrites publish dates or GUIDs so the mark can't track it, turn that podcast's catch-up off (today only) via Configure → Edit a podcast. The marks live in .storage/daily_podcasts_hwm; deleting that file resets catch-up, and the next run backfills marks from recorded history (so it won't re-offer episodes already in daily_podcasts_history/).
  • feedparser errors on startup: HA installs it from the manifest; watch the startup logs. A restart usually resolves a transient install.
  • Timezone looks wrong: this uses hass.config.time_zone. Set the timezone: option explicitly if you need to override it.
  • Episode not picked up: some feeds date-stamp episodes so they land on a different local calendar day. Run build_queue with dry_run: true and read the INCLUDE/SKIP log lines — they show each episode's local publish date.
  • A URL won't play: with the native fallback, episodes are sent as media_content_type: music; with Music Assistant, type is auto-detected. If a specific feed misbehaves, check that its <item> has a proper <enclosure> audio URL.
  • Which player to pick: if Music Assistant is set up, choose its MA player entity for the speaker. If not, choose the speaker's native entity (e.g. the Sonos integration's media_player.*). The log line QUEUE #n -> <player> via <method> shows which service was used.
  • Catch-up plays nothing: check that files exist in config/daily_podcasts_history/ (there's no file for days nothing published, and none written before this was installed). Run with dry_run: true to see which days/episodes it selected. Replayed audio URLs come from the recorded file — if a podcast host expires old media URLs, a very old entry may 404.

Notes for publishing this repo

If you later want this in the HACS default store (not just as a custom repository), HACS also requires: a GitHub repo description and topics, a GitHub release/tag, and brand assets (an icon.png) submitted to the home-assistant/brands repo. None of that is needed for the custom-repository install above.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages