Repository navigation
Home Assistant
Nothing to configure in Home Assistant. No YAML, no custom component, no restart. This app talks to Home Assistant rather than waiting to be asked: you give it the address of your HA and one long-lived access token made in HA, and it writes its own sensors into Home Assistant and plays books on Home Assistant's media players.
It has a page of its own — Settings ▾ → Home Assistant, or go straight to
http://your-server:8523/ha.

The address is the one you open Home Assistant at, such as
http://192.168.2.200:8123. No dashboard path after it — if you paste one, the app
says so.
The token is made in Home Assistant, not here:
- Home Assistant → your own profile (bottom left) → Security
- Long-lived access tokens → Create token → name it Audiobooks
- Copy it (Home Assistant shows it once) and paste it into the token field
Press Save, then Test the connection. It answers with which Home Assistant replied and its version — Talking to Home, version 2026.8.1. The token works. — or with what is wrong:
| What it says | What to do |
|---|---|
| Home Assistant did not accept the token | make a new long-lived access token and paste that |
| answered, but not with its API | take the dashboard path off the address |
| Could not reach Home Assistant at … | check the address, and that the container may reach it |
| did not answer within ten seconds | Home Assistant is busy or unreachable; try again |
The token is kept in this app's database, in the folder you mounted at /data. It
is never shown again and never sent back to a browser: the page only knows whether
one is there. Forget the token removes it.
A token that has been in a chat, an email or a screenshot should be deleted in Home Assistant and made again. Deleting it there is enough — this app simply stops being able to write, and says so.
Six sensors, written straight into Home Assistant's state machine. They appear the moment the first send lands:
| Entity | Its value | It also carries |
|---|---|---|
sensor.audiobooks |
how many books | files, which listener, hours total/listened/left, books marked listened, and the new books |
sensor.audiobook_files |
how many files | |
sensor.audiobook_hours |
hours in the collection | |
sensor.audiobook_hours_listened |
hours listened | hours left |
sensor.audiobook_hours_left |
hours left | |
sensor.audiobook_next_up |
the book to carry on with | its id, which track of how many, the seconds in, the percentage, hours left, and the whole queue |
Whose progress to report picks the listener — with one listener in the app, it is that one. Send to Home Assistant sets how often: Home Assistant forgets states written straight into it when it restarts, so a repeat every 15 minutes keeps them there. Show what will be sent lists exactly what would go, without sending it.
Hours listened count a book marked Listened in full, and a book in progress as the tracks already behind you plus the seconds into the one you are on.
A dashboard card needs no templates:
type: entities
title: Audiobooks
entities:
- sensor.audiobooks
- sensor.audiobook_hours
- sensor.audiobook_hours_listened
- sensor.audiobook_next_upThe page lists every media player Home Assistant knows, by the name you gave it. Pick one, then press Play here beside a book in the queue.
What happens: the app hands that player the book as a playlist starting at the track you are on, waits three seconds, and asks it to skip to the second you stopped at. A player that cannot skip still plays — from the start of that track, not from the start of the book.
Two things worth knowing:
-
the player fetches the audio itself. It has to be able to reach this app. If
Home Assistant and your speakers reach the app at a different address than your
browser does — behind a reverse proxy, say — set
BASE_URLon the container to that address, and the URLs handed to players are built from it. - positions do not come back from a player. The app knows what the listening page told it. Play on a speaker and later carry on in the browser, and it starts where the browser last was.
To carry a book on without opening the page, call the app:
rest_command:
continue_audiobook:
url: http://192.168.2.200:8523/api/ha/play?token=YOUR_HA_TOKEN
method: POST
content_type: application/json
payload: '{"player": "media_player.kitchen"}'With no book named, that plays the first unfinished book of the queue from where it
stopped. The token is the container's HA_TOKEN (see below) — leave it out if
you have set no HA_TOKEN, and these addresses answer without one.
The polling side still works, for anyone who prefers it:
| Address | What it is |
|---|---|
GET /api/ha |
the whole state as JSON |
GET /api/ha/book/<id>.m3u?from=<track> |
a book as a playlist, optionally from a track on |
GET /api/ha/continue.m3u |
the book being listened to, from where it stopped, plus X-Audiobook-Id and X-Audiobook-Seek headers |
HA_TOKEN on the container makes those addresses ask for a token (?token=… or an
Authorization: Bearer … header). The audio at /api/stream/… stays open either
way — a speaker sends no token, and could not play otherwise.
![]()