summaryrefslogtreecommitdiff
path: root/CLAUDE.md
diff options
context:
space:
mode:
authorClaude <noreply@anthropic.com>2026-09-21 20:25:01 +0000
committerClaude <noreply@anthropic.com>2026-09-21 20:25:01 +0000
commit8d980bb234b8303f17908621501620cb898d17ef (patch)
tree0a6e09addc5f1d4ad70beb1f50461cc2fdf7e5bd /CLAUDE.md
parentf7e7719bd1ed8a40765ce8b31ef8fa2c600bc7d0 (diff)
Android Auto support, and give the car its skip-forward button
Two things from the car. Skip-forward was missing: a head unit shows about three transport controls, and leaving previous/next track available meant they took the slots. Previous does nothing useful for a single episode anyway, so those commands are now removed in onConnect and the seek buttons get the room. Auto did not recognise Listen as driving-friendly, so it could only be controlled from the phone. That needs a browsable library, which means a MediaLibraryService rather than a MediaSessionService, plus a tree: Up Next and Podcasts at the root, shows beneath, episodes beneath those, capped at 100 because one real feed here holds 2753. Three things were each individually fatal and none failed loudly: - The automotive_app_desc meta-data has to sit at application level. A search-and-replace put a copy inside each activity as well, where it is simply ignored. - The service must be exported, because the controller is another process. - onConnect must grant DEFAULT_SESSION_AND_LIBRARY_COMMANDS. With plain DEFAULT_SESSION_COMMANDS a browser connects successfully and then gets PERMISSION_DENIED on the library root -- the connection looks healthy and browsing is dead. Found by logging the result code rather than assuming. Voice search too, since in a car it is the only interaction that keeps your eyes on the road. Shows rank before episodes: asking for a show by name is the common case, and a show is browsable, so the driver still picks the episode. MediaBrowseTest drives all of this over the real session protocol from a MediaBrowser. It must run on the main thread and await rather than block -- MediaController rejects calls from another thread, and blocking the main thread on a future that completes there deadlocks. Also: XML comments cannot contain "--", which made the manifest unparseable with a merge error that never named the line; and the reachability guard was reporting a method reference (LibraryTree::subscription) as dead because it only matched call syntax with parens. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018AwnALZL6bD4VBtATDVv3B
Diffstat (limited to 'CLAUDE.md')
-rw-r--r--CLAUDE.md15
1 files changed, 15 insertions, 0 deletions
diff --git a/CLAUDE.md b/CLAUDE.md
index 67de4bd..130d3a8 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -143,6 +143,21 @@ intro/outro skipping. Schema fields for manual skip offsets exist but nothing re
- **Feeds are big.** A real subscription in testing produced 2753 episodes in one refresh. Any
per-episode work — a query in a loop, an O(n²) match — needs to be considered at that scale.
+- **The service is a `MediaLibraryService`, not a `MediaSessionService`.** Android Auto only
+ treats an app as driving-friendly if it can browse a media library. Three things are each
+ individually fatal and none of them fail loudly: the `automotive_app_desc.xml` meta-data must
+ sit at *application* level, the service must be `exported` (the controller is another
+ process), and `onConnect` must grant `DEFAULT_SESSION_AND_LIBRARY_COMMANDS` — with plain
+ `DEFAULT_SESSION_COMMANDS` a car connects successfully and then gets PERMISSION_DENIED on the
+ library root. `MediaBrowseTest` walks the tree over the real protocol to catch all three.
+
+- **Car head units show about three transport controls.** Leave `COMMAND_SEEK_TO_PREVIOUS`/`NEXT`
+ available and they take the slots, pushing skip-forward off the screen. They are removed in
+ `onConnect` so the seek buttons get the room.
+
+- **XML comments cannot contain `--`.** The house prose style uses it constantly and it makes
+ the manifest unparseable, with a merge error that doesn't name the line.
+
- **The playing episode is always the head of the queue.** `advance()` drops the head, so if
something played from a show list isn't there, finishing it deletes a queued episode unplayed.
`play()` calls `playNow()` first to hold that invariant. Don't add a playback entry point that