This commit is contained in:
@@ -9,7 +9,9 @@ Everything it does fits on one screen:
|
||||
- **Volume** up/down for the Home 400 and the Living Room pair. A tap lands
|
||||
on the next multiple of `VOLUME_STEP` — from 23 it goes to 25, not 28 —
|
||||
so the levels stay round. Hold to keep moving.
|
||||
- **Play or pause** either room. A room that is grouped shares the AVR's
|
||||
- **Play or pause** either room, or skip a track, while it is playing
|
||||
Spotify — the buttons only show up then, since an AVR input has nothing
|
||||
to pause or skip. A room that is grouped shares the AVR's
|
||||
transport, so pausing it pauses the group — HEOS's doing, not the panel's:
|
||||
a group has one thing playing, by definition
|
||||
- **Group** either room with the AVR — the AVR is always the host, so its
|
||||
@@ -18,6 +20,9 @@ Everything it does fits on one screen:
|
||||
- **Ungroup** either room again, or all of them at once
|
||||
- **Change the AVR's input**, listed under the names you gave them, minus
|
||||
the sources you deleted in the AVR's setup menu
|
||||
- **Resume Spotify** on a room from Fifou's or Clarita's account, one
|
||||
button each — the reverse of connecting to it from the Spotify app.
|
||||
Optional; see [Spotify](#spotify) below
|
||||
|
||||
## The kit it assumes
|
||||
|
||||
@@ -78,6 +83,62 @@ by their HEOS input id (`GET /api/avr/inputs` shows the exact strings, e.g.
|
||||
`inputs/aux_in_1`), and sets their order; leave it empty to list everything
|
||||
HEOS reports for it.
|
||||
|
||||
## Spotify
|
||||
|
||||
Spotify dropped native, browsable HEOS integration years ago — it is
|
||||
Connect-only on HEOS now, and Connect only works phone → speaker: the
|
||||
Spotify app pushes playback to a room, and there is nothing in HEOS that
|
||||
asks for the reverse. So "Resume Spotify" doesn't go through HEOS at all;
|
||||
it calls Spotify's own Web API to transfer the account's current playback
|
||||
onto the room's Spotify Connect receiver, which the receiver already
|
||||
advertises on the LAN whether or not anything is playing. This needs
|
||||
Spotify Premium and a one-time login, since Spotify has no way to grant
|
||||
that without a human approving it once.
|
||||
|
||||
1. Create an app at the
|
||||
[Spotify Developer dashboard](https://developer.spotify.com/dashboard)
|
||||
(any name), and add this Redirect URI in its settings:
|
||||
`http://127.0.0.1:8899/callback`. Spotify allows plain `http` for a
|
||||
`127.0.0.1` redirect specifically, which is why the login below needs no
|
||||
HTTPS setup.
|
||||
2. While the app is in development mode, Spotify only lets accounts you
|
||||
have listed log in to it: add both accounts' email addresses under the
|
||||
app's **User Management**.
|
||||
3. Run the one-time login once per account, from a machine with a browser
|
||||
(your laptop is fine — it doesn't have to be the Pi):
|
||||
```bash
|
||||
python3 tools/spotify_auth.py --client-id <id> --client-secret <secret> --account fifou
|
||||
python3 tools/spotify_auth.py --client-id <id> --client-secret <secret> --account clarita
|
||||
```
|
||||
Log in as that account each time — use a private window for the second
|
||||
run, or Spotify just approves whichever account the browser is already
|
||||
logged in as. Each run prints `SPOTIFY_CLIENT_ID` and
|
||||
`SPOTIFY_CLIENT_SECRET` (the same both times) plus that account's own
|
||||
`SPOTIFY_FIFOU_REFRESH_TOKEN` or `SPOTIFY_CLARITA_REFRESH_TOKEN`.
|
||||
4. Put those four lines in a `.env` file in this directory (already
|
||||
git-ignored, and excluded from the deploy rsync the same way
|
||||
`members.json` is — see "What survives a deploy" below). `config.py`
|
||||
reads it directly, so no shell-sourcing step or restart-in-the-right-
|
||||
terminal gotcha — just `python3 app.py` as normal. A real exported
|
||||
environment variable (or systemd's `EnvironmentFile`, in the service)
|
||||
still overrides the file rather than the other way round.
|
||||
|
||||
`config.py` never sees the credentials themselves — it only reads them out
|
||||
of the environment. Each speaker's card then gets one Spotify button per
|
||||
account in `SPOTIFY_ACCOUNTS` that has a refresh token; an account without
|
||||
one simply gets no button. The AVR's card has none. Whichever account is
|
||||
playing on a room right now gets a border around its button — the panel
|
||||
asks Spotify, but only while HEOS says a room is on Spotify. Each room resumes on the Spotify Connect device named
|
||||
`spotify_name` in its `TARGETS` entry, falling back to `heos_name` if that
|
||||
key is missing (they usually match). `GET /api/spotify/devices?account=fifou`
|
||||
lists what Spotify actually calls each device, if a room's button ever says
|
||||
one isn't visible.
|
||||
|
||||
A room's play/pause and next buttons only appear while HEOS reports it is
|
||||
playing (or paused on) Spotify. To see what HEOS reports for a room, use
|
||||
`GET /raw/player/get_now_playing_media?pid=<pid>` with a pid from
|
||||
`/api/targets`: Spotify shows up as `"sid": 4`.
|
||||
|
||||
## Add it to the iOS home screen
|
||||
|
||||
Open the page in Safari → Share → **Add to Home Screen**. It then launches
|
||||
@@ -159,11 +220,11 @@ else or runs as someone else. Re-run the workflow and it goes green.
|
||||
|
||||
### What survives a deploy
|
||||
|
||||
The rsync excludes `.venv` and `members.json`, so the runtime and the stereo
|
||||
pair's learned membership are left alone by `--delete`. Everything else in
|
||||
the deploy path is made to match the repo, `config.py` included: your device
|
||||
names live in git, so change them there and push rather than editing the
|
||||
deployed copy.
|
||||
The rsync excludes `.venv`, `members.json` and `.env`, so the runtime, the
|
||||
stereo pair's learned membership, and Spotify's credentials are left alone by
|
||||
`--delete`. Everything else in the deploy path is made to match the repo,
|
||||
`config.py` included: your device names live in git, so change them there and
|
||||
push rather than editing the deployed copy.
|
||||
|
||||
## Behind a reverse proxy, at /heos
|
||||
|
||||
@@ -277,6 +338,8 @@ Used by the interface:
|
||||
| `POST /api/group/none` | every room back on its own |
|
||||
| `GET /api/avr/inputs` | your renamed sources, over HEOS |
|
||||
| `POST /api/avr/input` | `{"code": "inputs/aux_in_1"}` |
|
||||
| `GET /api/spotify/devices?account=fifou` | every Spotify Connect receiver that account currently sees (needs [Spotify](#spotify) configured) |
|
||||
| `POST /api/spotify/resume` | `{"target": "home400", "account": "fifou"}` — transfers that account's current playback there and resumes it |
|
||||
|
||||
`POST /volume/up` and `/volume/down` take one snapped tap by default; pass
|
||||
`?step=3` and they move that many raw points instead, as they always did.
|
||||
@@ -306,8 +369,10 @@ python3 tools/make_icons.py # re-render the icons from static/lo
|
||||
app.py Flask: the UI, the API, and the old bridge's routes
|
||||
controller.py what a room is, what grouping means, volume, the AVR's inputs
|
||||
heos.py HEOS CLI client (persistent socket, reconnects itself)
|
||||
spotify.py Spotify Web API client, for the "Resume Spotify" button
|
||||
config.py your devices and preferences
|
||||
demo.py fake speakers for --demo
|
||||
templates/ static/ the interface
|
||||
tests/ a fake HEOS server, and tests against it
|
||||
tests/ fake HEOS and Spotify servers, and tests against them
|
||||
tools/spotify_auth.py one-time Spotify login, prints the refresh token
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user