From b93b3f47beeda72ce6bf63fad3c21cd2fbd9b57d Mon Sep 17 00:00:00 2001 From: "Danilo M." Date: Sat, 7 Nov 2026 11:33:21 +0100 Subject: docs: Sonix player design A HiBy R1 running Sonix Player gets a boot logo editor in the settings window, a drawer tile and page (device, playlists, folders), and an R1 icon in the island's status pill opening its playback controls. All of it over SonixLink, plus Wi-Fi transfer for the boot logo upload. Co-Authored-By: Claude Opus 5.5 --- .../specs/2026-11-07-sonix-player-design.md | 194 +++++++++++++++++++++ 1 file changed, 194 insertions(+) create mode 100644 docs/superpowers/specs/2026-11-07-sonix-player-design.md (limited to 'docs/superpowers') diff --git a/docs/superpowers/specs/2026-11-07-sonix-player-design.md b/docs/superpowers/specs/2026-11-07-sonix-player-design.md new file mode 100644 index 0000000..5ab7794 --- /dev/null +++ b/docs/superpowers/specs/2026-11-07-sonix-player-design.md @@ -0,0 +1,194 @@ +# Sonix player + +A HiBy R1 running Sonix Player, reached over the LAN, gets three places in +the desktop shell: a boot logo editor in the settings window, a drawer tile +and page for its state and library, and an icon in the island's status pill +that opens its playback controls. + +Everything goes through two servers the player runs: + +- **SonixLink**, HTTP on port 7800, no authentication. JSON state, commands, + covers, the library. Switched on from the player's control centre and + left on. +- **Wi-Fi transfer**, HTTP on port 80, the card's file server. It runs only + while its page is open on the player: leaving the page by any route stops + it (Sonix `gui/wireless/wifitransfer.c`, on purpose: an unauthenticated + root file server). Used only for the boot logo upload. + +## Layout + +``` +desktop/ + Sonix.qml singleton service: presence, state, commands, upload + island/SonixPanel.qml the controls panel; the status pill gains the R1 icon + modules/sonix/ + Sonix.js pure functions, tested by test_sonix.js + test_sonix.js + sonix-beacon.py stdlib UDP listener for the player's beacon + test_sonix_beacon.py + SonixModule.qml drawer tile + page + SonixPage.qml tabs: Device, Playlists, Folders + BootlogoModule.qml settings window page, group Appearance + BootlogoTab.qml + fonts/ Bungee, Bungee Inline, Major Mono Display, + Michroma, Audiowide, and their OFL.txt + README.md +``` + +The fonts are SIL OFL 1.1, from google/fonts, bundled with their licence. + +## Sonix service + +One instance in the `desktop` process; the island, the drawer and the +settings page all read it. + +**Presence.** SonixLink broadcasts `SONIXLINK1 ` to UDP +7801 every 2s. QML has no UDP socket, so `sonix-beacon.py` binds 7801 and +prints each beacon as one line; the service reads it with a `Process` and a +`SplitParser`. `present` is true while beacons arrive and false 6s after the +last. `host`, `port` and `name` come from the beacon, so a DHCP change of +address is followed without configuration. If the helper exits it is +restarted with a backoff; while it is down `present` stays false. + +**State.** `/api/state` is polled every 1s while at least one view holds the +service (`hold()` / `release()`, counted): the island panel and the drawer +page. Nothing is polled otherwise. When the count reaches zero the service +sends `/api/bye`, so the player stops counting a client. Fields used: +`state` (0 stopped, 1 playing, 2 paused), `position` and `duration` +(seconds), `title`, `artist`, `album`, `path`, `sample_rate`, `bits`, +`bitrate`, `lossless`, `volume`, `mode`, `battery`, `charging`, `favourite`, +`accent`, `scanning`, `scan_count`, `tracks`, `playlists_revision`. + +**Commands.** `command(do, params, body)` issues `/api/command?do=...` with +`XMLHttpRequest`, 3s timeout. Track lists go in the POST body, one path per +line. A failed request reports its error to the caller and never changes +`present`: only the beacon does. + +**Upload.** `upload(localFile, cardDir)` runs `curl`: `POST /create` with +`path` for the folder, then `POST /upload` with `path` and `files[]`, the +same calls the transfer page makes. Card paths are under `/data/mnt/sd_0/`. + +## Island + +The status pill shows an R1 icon, left of the headphones icon, while +`present`. It takes the accent while playing. A click opens `SonixPanel`, +built like `HeadphonesPanel`: + +- the cover from `/api/cover`, reloaded when `path` changes +- title, artist, album, and a **heart**: filled when `favourite`, a click + sends `favourites_add` or `favourites_remove` with the current path; the + heart flips at once and the next poll confirms or reverts it +- the quality line, e.g. `FLAC · 24/96 · 2304 kbps`, and a lossless badge +- a seek bar: position advances locally between polls, a click sends `seek` + in seconds +- previous, play/pause, next +- volume 0-100, slider and wheel (5 per notch), sent once it settles +- the mode button: normal, repeat_all, repeat_one, shuffle, shuffle_repeat, + in that order +- battery percentage and a charging mark + +The panel holds the service while open. + +## Drawer + +`SonixModule` has a tile while `present` (`tiled` bound to it). Label: the +beacon's name. State line: `95% · playing`, or `idle`. Active (accent) while +playing. The page holds the service while it shows, and has three tabs. + +**Device.** Name, `host:port`, battery and charging, the player's accent as a +swatch, the track count; during a scan `scanning… N found`, otherwise a +Rescan button (`scan`). One line of what is playing. Links: Open Wi-Fi +transfer (`http:///` in the browser) and Boot logo (opens the +settings page). + +**Playlists.** `/api/playlists`: name and track count. A click opens the +playlist (`/api/playlist?name=`), back arrow returns. In a playlist: +play all (`play_all` with `playlist`), and per track play (`play_path`) and +remove (`playlist_remove`). New playlist takes a name and sends the user to +Folders for its first track: SonixLink creates a playlist by adding to a +name that does not exist, and an empty one cannot be made, so until then the +new name lives only in this page. + +**Folders.** `/api/browse?path=/`, folders first, breadcrumbs, back arrow up. +A folder row has play (`play_all` with `folder`) and add; a file row has +play (`play_path`) and add. Add opens a menu: the playlists, and New…. + +Both lists reload when their tab opens and when `playlists_revision` +changes, so edits made on the player appear. + +**Limits.** SonixLink has no rename, delete, or reorder for playlists. The +player has the functions internally (`playlists_rename`, +`playlists_delete`) but does not expose them, and the `.m3u` files on the +card are backups, not the playlists. These are done on the player. Queue, +history and a favourites list are not part of this. + +## Boot logo page + +In the settings window, group Appearance, keywords: boot, logo, sonix, hiby. +Left, the canvas; right, the controls. + +**Canvas.** An exact 480x800 scene, shown scaled to fit, framed as the +panel. The output is this scene, so what the player shows is what is seen. + +**Picture.** Choose picture opens a file dialog in `~/Pictures`, JPEG or +PNG. It opens centred at minimum zoom, the crop the player itself would +make. Drag pans, the wheel zooms from just covering 480x800 up to 4x. Pan is +clamped so the picture always covers the frame. Reset recentres. A picture +that fails to load shows its error and disables Save and Upload. + +**Text.** + +- text, default `$USER` +- font: four buttons, each label in its own face: Bungee + Inline, Major + Mono Display, Michroma, Audiowide +- position: a switch decides whether a drag on the canvas moves the picture + or the text; snaps for top, centre, bottom, centred horizontally +- size: a slider +- duotone: a fill colour and a second colour, each picked from swatches: + white, black, the six player accents, and the udt accent (`Theme.accent`, + live). Swap exchanges them. The second colour is an offset copy behind the + fill, with an offset slider and one of four diagonal directions; for + Bungee it is the Bungee Inline layer drawn over the fill, with no offset. + +Player accents: blue `#3584e4`, orange `#ff7800`, yellow `#e5a50a`, green +`#2ec27e`, purple `#9141ac`, pink `#d56199`. + +Text, font, size, position, colours and offset are kept in `Prefs`. The +picture and its pan and zoom are not. + +**Finalize.** In the page's header bar (`Page.actions`): + +- Output folder: a folder picker, kept in `Prefs`, default + `~/Pictures/sonix-bootlogo/`, created if missing. +- Save: `grabToImage` writes `bootlogo-YYYYMMDD-HHMMSS.png`, 480x800, and + `bootlogo-YYYYMMDD-HHMMSS-mask.png`, the text alone on transparency. PNG, + which the player accepts for a boot logo. A folder that cannot be written + shows the error and nothing is uploaded. +- Upload, enabled while `present`: saves, then checks `http:///`. If + Wi-Fi transfer does not answer, the button reads `Waiting for Wi-Fi + transfer…` with the hint "Open Wi-Fi transfer on the player (Control + centre) and leave it on screen", and rechecks every 2s for 60s. Then + uploads into `Bootlogo/` on the card. Success: "Uploaded. Pick it on the + player: Settings > Appearance > Boot logo. You can leave Wi-Fi transfer + now." Failure: the HTTP status or curl's error; the local copy stays. + +No draft and no dirty mark: the page edits no file. + +## Errors + +- Player gone (no beacon for 6s): icon and tile hide; an open panel or page + says the player is not on the network and disables its controls. +- A failed command shows one line in the panel or page; a 400 shows the + player's reason text. +- Wi-Fi transfer off: handled by Upload's wait, above. + +## Testing + +- `node desktop/modules/sonix/test_sonix.js`, over `Sonix.js`: beacon line + parsing, the quality line, the mode cycle, the pan and zoom clamp, the + output file name, breadcrumbs from a path. +- `/usr/bin/python3 desktop/modules/sonix/test_sonix_beacon.py`: sends one + datagram to the helper on a test port and checks the printed line. +- API calls are checked against a live player. The canvas, the panel and the + page are checked by eye, by the user; the agent confirms loading from the + `qs` log. -- cgit v1.2.3