feat(train): signing in to Jellyfin and marking playlists (ADR 0030, D47)

Music's decisions as D47, and its first part: the music screen from the
account sheet, on Android, signs in to the person's Jellyfin with Quick
Connect, else a password, and lists their playlists of music to mark
for running. The token is in the keyring, the server and the marks in
the person's music.json, nothing on Train's server.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
Tizian 「ティツィアーン」 2026-10-29 12:36:32 +01:00
commit 3336fe4eca
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64

View file

@ -986,6 +986,59 @@ Strava. What was found, 27 Oct:
read Train's database; a role of its own for it is left for later. restic now forgets
snapshots grouped by host alone, so that the snapshots before Train's dump joined
them aren't kept for good as a group of their own.
- **D47 Music from Jellyfin** (recommended, the developer's; 29 Oct; D4's how):
- **Signing in**: with Jellyfin's Quick Connect (Train shows a code, the person
approves it in another Jellyfin client, Train gets a token of its own), else with
the user name and password where the server has Quick Connect off (over the
password alone, which always passes through Train, and over an API key, which only
an administrator makes and which carries an administrator's rights).
- **Which tracks**: those of the Jellyfin playlists the person marks for running in
Train's settings (over the whole library, hours of downloads and analysis on a large
one, and over genres, only as good as the library's tags).
- **Kept on this phone only**: the token in secure storage, the server's address, the
marked playlists and each track's tempo in a file of the person's on the device,
nothing on Train's server (over sealed, synced rows, a synced model and its
migration so that a second phone skips the analysis; music plays on phones only,
D5). A second phone signs in and analyses again.
- **Offline**: the marked playlists' tracks are downloaded on unmetered networks only
and kept up with the playlists; the settings show their size and delete them (over
fetching what a run needs as it starts, which makes the start wait on the network
and a free run guess its length, and over streaming, which D4 ruled out).
- **Tempo by Train's own analysis, in Kotlin** (over aubio, GPL-3 C, which needs a
native build per ABI; TarsosDSP, GPL-3 Java, unmaintained since 2021; Essentia,
AGPL-3 C++, tens of MB and a heavy build): Android's decoder reads a minute of the
downloaded track, an onset envelope and its autocorrelation give the beats per
minute, tested on the JVM with beats of known tempo. A BPM tag the file carries is
taken first.
- **Played by Media3's ExoPlayer in the recording service** (Apache-2.0, AndroidX;
over just_audio and audio_service in Flutter, whose engine may stop with the
activity and whose player would talk to the service's cues and plan runner across
the channel): beside the cues, which duck it, and the plan runner, it plays on with
the screen off and picks the next track there.
- **Cadence followed**: the one the step detector measures, smoothed over 30 s,
without a plan and for a step aiming at heart rate or the like; for a step aiming
at a pace, the cadence the person's own past runs show at that pace (steps against
speed), the measured one until three runs show it (over the measured cadence alone,
which never pulls the person towards the step's pace, and over a cadence set by
hand). A track fits at its tempo, its half or its double, sped up or slowed down by
at most **6 %** with the pitch kept (over 10 %, where voices start to sound off, and
over none, which needs a large playlist). A metronome at the cadence plays where no
library is set (D4).
- In parts: 10a signing in to Jellyfin and marking playlists in the settings; 10b
downloading the marked tracks and working out their tempo; 10c playing them in a
recording, by cadence, and the metronome.
Built: 10a. *Music* in the account sheet, on Android: the server's address (https
where none is typed; a path kept, as for a server under `/jellyfin`), then Quick
Connect's code, asked about every 3 s and given up after 9 minutes (Jellyfin forgets
a request after ten), or *Use a password*, which a server with Quick Connect off
asks for at once; Quick Connect as Jellyfin 10.9 and later have it. Train signs in
as the client "Halkyon Train" with a device ID of its own, made once and kept, so
that the server lists it as a device of the person's. The token is in the keyring
under the person's ID, and the tokens of people the device no longer keeps are
forgotten as the library opens; a token Jellyfin takes back signs the person out
on the phone too. Signing out ends the token on the server where it answers. The
person's playlists of music are listed (a playlist naming no type counts as one,
one of videos doesn't) and marked, the marked ones by ID and name in `music.json`.
## Consequences