<feed xmlns='http://www.w3.org/2005/Atom'>
<title>listen.git/gradle/wrapper, branch main</title>
<subtitle>Listen — personal Android podcast app
</subtitle>
<id>https://git.terst.org/listen.git/atom?h=main</id>
<link rel='self' href='https://git.terst.org/listen.git/atom?h=main'/>
<link rel='alternate' type='text/html' href='https://git.terst.org/listen.git/'/>
<updated>2026-09-21T08:38:50+00:00</updated>
<entry>
<title>Embed yt-dlp via Chaquopy instead of shelling out to Termux</title>
<updated>2026-09-21T08:38:50+00:00</updated>
<author>
<name>Claude</name>
<email>noreply@anthropic.com</email>
</author>
<published>2026-09-21T08:38:50+00:00</published>
<link rel='alternate' type='text/html' href='https://git.terst.org/listen.git/commit/?id=433866c912a5dc687c02bf597eff093e3b2615ef'/>
<id>urn:sha1:433866c912a5dc687c02bf597eff093e3b2615ef</id>
<content type='text'>
Verified end to end on an emulator: the probe resolves a real video and the
download lands 54,006,240 bytes of m4a, matching the size the probe predicted.

Two traps found by running it rather than reasoning about it:

- Chaquopy defaults to CPython 3.8, which yt-dlp no longer supports, so pip
  silently resolves 2024.10.22 -- the last release that did, nearly two years
  stale, and YouTube extraction does not work with it. The build succeeds and
  the feature is dead. Pinned to 3.12, which gets 2026.8.19, and
  YouTubeAudioTest now fails on any pre-2025 yt-dlp so it cannot regress
  quietly.
- Chaquopy 16.1.0 calls org.gradle.util.VersionNumber, removed in Gradle 9,
  so Gradle is pinned to 8.14.3 here while FeeDreader stays on 9.5. AGP stays
  at 8.13.2. Chaquopy 17 requires AGP 9.x, so moving off this means bumping
  all three together.

yt-dlp's JSON is parsed through a separate, unit-tested mapping rather than
inline in the Chaquopy call. It is a separate project on its own release
cadence and is the boundary most likely to drift, so a field going missing or
changing type should fail a test rather than crash on the phone. Parsed with
kotlinx.serialization because org.json is an empty stub off device, which
would have made the mapping untestable.

Audio-only by design: separate streams need ffmpeg to merge, the documented
yt-dlp failure mode on Android, and a pre-muxed audio stream has nothing to
merge. No ffmpeg dependency at all.

No proof-of-origin token support, deliberately -- obtaining one means running
Google's attestation script in a WebView, a different act from downloading,
and it is not needed from an ordinary connection.

Known risk recorded in CLAUDE.md: yt-dlp has deprecated YouTube extraction
without a JavaScript runtime, and there is no deno or node on Android.

Co-Authored-By: Claude Opus 5 &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_018AwnALZL6bD4VBtATDVv3B
</content>
</entry>
<entry>
<title>Scaffold Listen, with a podcast-native feed parser</title>
<updated>2026-09-21T08:13:49+00:00</updated>
<author>
<name>Claude</name>
<email>noreply@anthropic.com</email>
</author>
<published>2026-09-21T08:13:49+00:00</published>
<link rel='alternate' type='text/html' href='https://git.terst.org/listen.git/commit/?id=5876025559ba0ad287d3a7a4d9fa0246509e668e'/>
<id>urn:sha1:5876025559ba0ad287d3a7a4d9fa0246509e668e</id>
<content type='text'>
Sibling to FeeDreader, not a shared module. That was a considered call: its
FeedParser only inspects &lt;enclosure&gt; while hunting for a thumbnail, and its
FilterRuleEngine is 46 lines of title substring matching that doesn't
generalise to queue rules, so only Opml.kt would genuinely have transferred.

The parser is the piece everything else sits on, so it goes first, test-first:
18 tests covering audio/video enclosure selection, iTunes durations in both
second and clock form, season/episode numbers, podcast:chapters for the skip
feature, RFC 5005 archive paging for drip subscriptions, and episode identity
falling back to the enclosure URL when a guid is missing.

Two things worth recording:

- Built on XmlPullParserFactory rather than android.util.Xml. The latter
  returns null off-device, which is why FeeDreader's 297-line parser has no
  tests at all. Both resolve to the same KXmlParser on Android.
- SimpleDateFormat with isLenient=false rejects "Tue, 02 Sep 2026 06:00:00
  GMT" -- an entirely ordinary RFC 822 date. Left strict, every pubDate in
  the app would have been null and episode ordering would have been silently
  wrong everywhere.

minSdk is 29, deliberately not FeeDreader's 36, which would restrict the app
to Android 16 and newer.

Permissions: INTERNET and ACCESS_NETWORK_STATE for feeds; FOREGROUND_SERVICE,
FOREGROUND_SERVICE_MEDIA_PLAYBACK, POST_NOTIFICATIONS and WAKE_LOCK because
Android requires a foreground service with a notification to play audio in the
background. That notification is the transport control, not an alert -- Listen
posts no new-episode notifications by design.

Co-Authored-By: Claude Opus 5 &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_018AwnALZL6bD4VBtATDVv3B
</content>
</entry>
</feed>
