App 2.45.0: now playing on the Overview

NowPlayingWatcher follows the head unit's media sessions (listening
only, never connecting to a player); NowPlaying picks what plays and
cleans the text. Tiles shows source, title, artist, cover and progress
under the battery; Halloween carves title and artist into the hill.
Settings: "Show what's playing", on by default. Nothing is logged or
sent.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LhzAVaUyqcw5PkGjz4mWW6
This commit is contained in:
2026-11-02 14:03:26 +01:00
co-authored by Claude Opus 5.5
parent bdb3f5e765
commit 91283093f5
15 changed files with 619 additions and 8 deletions
@@ -27,7 +27,7 @@ import java.util.Locale;
* of power, rising with it, in orange or, where it regenerated, green. Unread values show "—" and
* light nothing. With music playing, the pumpkin's glow swells and its carving brightens on the
* beat ({@link MusicTap}); colours and the embers keep their meaning, and with no music it burns as
* before.
* before. What plays is carved into the foot of the hill ({@link NowPlaying}).
*
* The night sky is drawn once into a bitmap per size; only the rest goes over it. Bats, bubbles and
* the ghost move (about 30 frames a second) only while the car moves and the view is on screen;
@@ -99,6 +99,7 @@ final class HalloweenView extends ClusterCanvasView {
drawMoon(c, w, u);
drawGhost(c, w, u);
drawHill(c, w, h, u);
drawSong(c, w, h, u);
drawEmbers(c, w, u);
float pulse = music == null ? 0f : music.pulse();
drawPumpkin(c, w, u, pulse);
@@ -276,6 +277,26 @@ final class HalloweenView extends ClusterCanvasView {
}
}
// ---------- What plays, carved into the hill ----------
private void drawSong(Canvas c, int w, int h, float u) {
if (v.songTitle == null) return;
text.setTypeface(thin);
text.setTextSize(20f * u);
float room = w * 0.44f;
String artist = v.songArtist == null ? "" : " — " + cut(v.songArtist, room / 2f);
// A long title gives way, so the artist stays.
String s = "♪ " + cut(v.songTitle, room - text.measureText("♪ " + artist)) + artist;
label(c, s, w / 2f, h - 12f * u, 20f * u, AccentColor.withAlpha(BONE, 0xC0), thin, Paint.Align.CENTER);
}
/** {@code s} cut with "…" to fit {@code room} in the text paint as set. */
private String cut(String s, float room) {
if (text.measureText(s) <= room) return s;
int fits = text.breakText(s, true, Math.max(0f, room - text.measureText("…")), null);
return s.substring(0, Math.max(0, fits)).trim() + "…";
}
// ---------- The arch of embers: the power ----------
private void drawEmbers(Canvas c, int w, float u) {