ADR-0067: Whole-home HiFi in the Music popup — rooms, library and outputs through Music Assistant, pushed by Home Assistant¶
Page en anglais
Cette page n'existe qu'en anglais.
Status: Proposed (2026-10-18, asked for by the author; contract written before the code, not tried on a tablet when written). Extends the music popup of ADR-0050 and the favourite playlists page of ADR-0068 (its own pull request, #515). Date: 2026-10-18
Context¶
The author asked for « une gestion complète HiFi » on the screen, built on Music Assistant: players, sources, outputs, « toute la maison », beautiful, practical, accessible but extremely complete, from the smallest installation to the largest. ADR-0050 gives one player at a time (cover, transport, volume, six chips); ADR-0068 adds favourite YouTube playlists for an Apple TV.
Constraints: push-only and events-only (ADR-0001, ADR-0025) — the firmware names no entity, calls no action and talks to nothing but Home Assistant; nothing of a real home in the public HA files, no !secret (ADR-0024); one popup with pages and one swipe (ADR-0046); the music card stays 940 × 536 on both boards (the author's choice on 2026-10-18, after a mock-up); colours from the palette; every icon in its font's glyphs:; instant transitions; kPayloadMax (16 KiB) per payload.
Verified on the author's Home Assistant on 2026-10-18 (HAOS 18.3, aarch64 VM, Core 2026.10.2):
- The Music Assistant app 2.10.7 (
d5369777_music_assistant, host network) is found by Home Assistant (hassiodiscovery, one confirmation) and its integrationmusic_assistantloads: onemedia_playerper player,mass_player_type(player/group),active_queue,group_memberswhen the player can group, plus one « add the current song to favourites »buttonper player. - Players seen with no setting: AirPlay (Apple TV 4K — unavailable until paired with a PIN shown on the TV —, Freebox Player), DLNA (Samsung TV), Google Cast (Lenovo Smart Clock), and the « Home Assistant MediaPlayers » provider for the ESPHome tablet, the Steam Deck and a voice satellite.
music_assistant.get_library(config_entry_id,media_type,limit,offset,favorite,order_by, response) returned the 12 radios of the library:urilibrary://radio/N,name,image= the image's original URL (a Wikipedia PNG, a favicon…),favorite.music_assistant.play_media(library://radio/2) on the tablet's Music Assistant player: the tablet's own ESPHomemedia_playerwentplayingat once (11:59:51), volume 20 % set before.- The shown track's
entity_pictureon a Music Assistant player is Music Assistant's own image proxy, in plain http on the LAN:http://<ha>:8095/imageproxy/<id>?size=512&fmt=png. The proxy serves sizes 0, 80, 160, 256, 512, 1024 only;<id>issha256("<provider>/<path>")and must have been issued by Music Assistant (otherwise 404). Many radio logos fail upstream even there (Wikimedia refuses the size asked, a favicon is gone): a missing cover is normal, not a fault. music_assistant.get_queuegives the current and the next item and the number of items, not the list; Home Assistant has no action to jump to an item of the queue.
Decision¶
Pages of the Music popup¶
Same card, same chrome, same swipe; the popup always opens on « Lecture ». Pages, in this order, each only when it has something to show:
- Lecture (ADR-0050), plus, with Music Assistant, one line under the album: « À suivre ·
» and « n titres » (the queue), and a chip « + n » after the player's name when it leads a group. - Playlists (ADR-0068, favourite playlists): its grid, its return to « Lecture » 600 ms after a tap and its button in the accent colour for 30 s are unchanged; since both were merged (2026-10-18), its page, its name on the title line and its swipe are those of this popup's single page brick (
tab5_musique.cpp), no longer a second one intab5_lecteur.cpp. - Pièces — every output of the home, eight at most, a grid of 4 × 2 cards: the kind's icon, the name, the title playing or the state, the volume (slider 0-100, sent on release), and up to three buttons each shown only when the player can: link / unlink to the shown player's group (
g), power (o), « send the music here » (t, not on the shown one). A tap shows that player on « Lecture »; a long press opens the wheel (ADR-0036): source (s), mute (m), transfer (t), unlink. On the title line, « Tout ici » (group every available player with the shown one) and « Tout séparer », with Music Assistant only. The shown player's card has the accent border, its group's cards a thinner one. - Bibliothèque — with Music Assistant only. A row of categories (Favoris, Playlists, Albums, Artistes, Radios, Podcasts, Récents), then a grid of 5 × 2 items: cover (or the kind's icon), name, second line. A tap plays a playable item (track, radio, episode, audiobook) on the shown player — replacing the queue — and opens a container (artist → albums, album / playlist → tracks, podcast → episodes) with a back button; a long press opens the wheel: play now, play next, add to the queue, radio mode, open. « Suivants » / « Précédents » when there are more. The sources of a player (from its wheel) show in the same grid.
From the smallest to the largest home, with nothing to set on the screen:
| Home | Pages |
|---|---|
One media_player, no Music Assistant |
Lecture (ADR-0050) |
| Several chosen players, no Music Assistant | Lecture, Pièces: the « Tab5 · lecteurs de musique » list (six), volume, power, mute, source; link only for players that group natively (group_members) |
| Music Assistant | Lecture with the queue, Pièces with every Music Assistant player (eight, sorted by name), groups and transfer, Bibliothèque |
Home Assistant tells which case applies (ma below); the firmware keeps nothing about it.
Contract (minor: two actions, one event)¶
tab5_maj_musique_pieces(pieces, contexte) — pushed on esphome.tab5_connected, on ouvrir, and when a mirror signature of the listed players changes (state, volume, mute, group, title; mode: restart, 1 s wait: a burst is one push). Always sent, even empty.
contexte=ma|montre|file|suivant:ma1when themusic_assistantintegration is loaded, else0;montrethe index inpiecesof the shown player (sensor.tab5_lecteur_montre, ADR-0050),-1if absent;filethe number of items of its queue (0unknown);suivant« title — artist » of the next item, empty if none — last field, takes the rest.pieces= up to eight entries separated by;, eachnom|genre|etat|volume|muet|groupe|fonctions|titre:nom40 characters at most;genreone letter for the icon:tTV,espeaker,ascreen / tablet,ggroup,xother;etatjplaying,ppaused,ron and idle,eoff / standby,iunavailable;volume0-100, empty if unknown;muet0/1;groupethe index of the piece that leads its group (its own index when it leads members),-1alone;fonctionsletters:vvolume,mmute,opower,gcan group,ssource list,tcan receive a transfer (a Music Assistant player);titre« title — artist » when playing or paused, 60 characters at most.
tab5_maj_musique_liste(contexte, elements) — pushed only in answer to an event (parcourir, detail, sources, later chercher). Home Assistant keeps nothing: the tablet sends back the ref it received.
contexte=vue|titre|page|suite|retour:vuefavplalbartradpodrecdet(a container's content)src(a piece's sources)rech(search, later) orerr(titre = the sentence to show);titre48 characters at most;pagefrom 0;suite1if a next page exists;retour1to show the back button.elements= up to ten entries separated by;, eachnom|sous|genre|ref|image:nom,sous40 characters at most;genrettrack,aalbum,rartist,pplaylist,dradio,opodcast or episode,laudiobook,ssource,xother (decides the icon and what a tap does);refa Music Assistant URI (library://album/12,ytmusic://playlist/…), 160 characters at most, or forsthe source's index in the player'ssource_list;imagean http(s) URL, may be empty — last field, takes the rest.- Home Assistant asks small images: a Music Assistant proxy URL gets
size=160; a…googleusercontent.com/…=wN-hNURL gets=w160-h160; other URLs go as they are. In every name, « | » becomes « / » and « ; » becomes « , »; in a URL « ; » becomes%3B.
Event esphome.tab5_musique (action, piece, valeur); piece is an index of the last pieces pushed or -1 (the shown player):
| action | valeur | Home Assistant does |
|---|---|---|
ouvrir |
— | pushes pieces |
parcourir |
vue\|page |
get_library (favourites, playlists, albums, artists, radios, podcasts) or the recently played, ten per page, pushes the list |
detail |
ref\|page |
the content of that artist / album / playlist / podcast |
jouer |
mode\|ref |
music_assistant.play_media on the piece; mode m now (replace), s next, f at the end, r radio mode |
montrer |
— | the piece becomes the shown player (as a chip does, ADR-0050) |
grouper / separer |
— | media_player.join the piece to the shown player / media_player.unjoin it |
tout_grouper / tout_separer |
— | every available piece that can group / every grouped piece |
transferer |
— | music_assistant.transfer_queue from the shown player to the piece, which becomes the shown one |
volume |
0-100 | media_player.volume_set |
muet / marche |
— | toggles mute / power |
sources |
page |
pushes the piece's source_list (vue src) |
source |
index | media_player.select_source with that entry of source_list |
Home Assistant rebuilds the list of pieces the same way to resolve an index (an index out of range does nothing), accepts a ref only if it matches ^[a-z0-9_-]+://[^|;\s]+$, a mode only among the four letters, and never takes an entity or an action from the event. Every action has continue_on_error.
Where the code goes¶
- Home Assistant: one new package,
packages/tab5_musique.yaml(mirror signature, the two pushes,script.tab5_musique_commande), one branch inpackages/tab5_evenements.yaml; it reusessensor.tab5_lecteur_montreandscript.tab5_lecteur_montrerofpackages/tab5_lecteur.yaml. The integration'sconfig_entry_idis found withintegration_entities('music_assistant')/config_entry_id(), never written. - Firmware: a new module
Tab5/ecran/tab5_musique.*(pages Pièces and Bibliothèque, the downloads), new componentsTab5/ui_components/musique_piece.yaml(×8) andmusique_element.yaml(×10), the pages registered on the Music popup'sPagesPopup. Reading in pure C++ tested on a PC (Tab5/socle/tab5_parse.*, a new section:musique_pieces_lire(),musique_liste_lire(); cases intools/test_parse.cpp, a call intools/fuzz/fuzz_parse.cpp). - Covers of the library: ten
online_imageof 160 × 160 (RGB565, about 50 KiB each in PSRAM), downloaded one after the other from the first item; a failed download keeps the kind's icon. The shown track's cover stays ADR-0050's.
Rejected¶
- The tablet talking to Music Assistant itself (its websocket or HTTP API): against ADR-0001 / ADR-0025, and a Music Assistant token on the device.
- The whole queue and « play item n »: Home Assistant's integration has no action for it; Music Assistant's own API needs a token, which would be a secret in public files (ADR-0024). Possible later through the optional Tab5 integration (ADR-0035); until then « À suivre » and the count.
- Resized images through Music Assistant's proxy for every item: the proxy id needs the image's provider, which Home Assistant's response does not carry; only the URLs that are already proxy URLs (local files, now playing) are resized.
media_player.browse_mediainstead ofget_library: no paging (500 items at once) and no favourites filter.- A second popup or a bigger card: the author kept the 940 × 536 card; one popup with pages (ADR-0046).
- Every
media_playerof the house without Music Assistant: ADR-0050's rejection stays (TV apps, the tablet's own speaker…); with Music Assistant, its players are already the author's choice of outputs. - « Don't stop the music » as a toggle: no Home Assistant action sets it; the radio mode of
jouergives the same « keep playing » from an item.
Consequences¶
- API contract 1.3.0 → 1.4.0 (two actions and one event added). Update order: the firmware first; new HA files with an older firmware: the two pushes fail with
continue_on_error, nothing else stops. An older HA with the new firmware: no Pièces / Bibliothèque page (nothing pushed). - The page names on the title line: Lecture, [Playlists], Pièces, [Bibliothèque] — up to four on a 940 px card next to the player's name; to be judged on the 7B.
- New glyphs (speaker, TV, group, link / unlink, power, transfer, album, artist, playlist, radio, podcast, history, back), new screen texts in the six languages, new screens in the off-device render (Pièces with a group, Bibliothèque with and without covers, the sources).
- Search by text needs a keyboard on the screen: a later lot, with a
chercheraction (texte|page) already reserved in this table. - Not measured when written: the ten downloads' time and memory on the 7B, the time of
get_librarywith a large library, a push of eight pieces during a volume drag.