Files
plexamp-lite/README.md
T
XoséandClaude Opus 5.5 4834c6af08 Plexamp Lite 1.14.2: stream originals as downloads too
The cut is per account, not per device: a file request from any device signed in to the same account (another
phone, an iPod, the Mac) cut this phone's stream. The player's own stream now also asks for the file as a
download (?download=1), which Plex leaves out of that rule; the plain address follows for servers that refuse
downloads. Transcodes aren't affected.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 21:54:18 +01:00

176 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Plexamp Lite
A small Plexamp-style music player for Plex, built for the Samsung Galaxy Mini (GT-S5570:
Android 2.2/2.3, 240×320 ldpi screen, 600 MHz ARMv6, 384 MB RAM). Runs on any Android 2.2+ phone, and comes
in a second build for Android 4.1 and later (Xperia J, Galaxy S II…) that uses what those phones can do: see
*Android 4.1 build*. Formerly Minamp (the package name, `com.minamp`, stays so updates install over it).
## Language
The interface is in **Galician** by default, whatever language the phone is set to. English is
available under *Axustes → Idioma*. All text lives in `tools/strings.py` (key → English, Galician),
which generates `res/values/strings.xml` (Galician) and `res/values-en/strings.xml`; the connection
log stays in English since it's meant for troubleshooting.
## Install
1. On the phone: **Settings → Applications → Unknown sources** (tick it).
2. Copy `PlexampLite.apk` (Android 4.1+: `PlexampLite-api16.apk`; Tenda picks the right one by itself) to the SD card (USB cable, or download it in the browser) and open it
with a file manager, or from a computer: `adb install PlexampLite.apk`.
3. Plexamp Lite installs to internal storage (about 1 MB): an app on the SD card is killed whenever the
phone shares its SD card over USB, which stopped the music.
## Sign in
Open Plexamp Lite → **Sign in with Plex**. It shows a 4-character code; on any other device go to
**plex.tv/link** and type it in. Plexamp Lite then lists your servers (owned and shared) and music libraries.
*Connect to a server address* is the manual fallback: an IP/hostname plus an optional X-Plex-Token.
## Streaming from anywhere
After sign-in Plexamp Lite knows every address Plex advertises for your server and picks the best
working one, re-checking whenever the phone switches network:
| Order | Connection | When it's used |
|---|---|---|
| 1 | Whatever worked last time | Tried alone first, so a normal start costs one request (if that was a remote address, the home addresses get 2 s first) |
| 2 | Home network, plain HTTP | On your Wi-Fi (fast: no encryption work for the phone) |
| 3 | Home network, HTTPS (`*.plex.direct`) | On your Wi-Fi if plain HTTP is refused, or if *Settings → Encrypt at home too* is on |
| 4 | Remote, HTTPS | Away, if Remote Access is set up on the server |
| 5 | Plex Relay | Away, when the server isn't reachable directly (bandwidth-capped; Plexamp Lite caps itself at 192 kbps) |
Secure addresses are tried one at a time: on a 600 MHz phone a TLS handshake takes seconds, and
several at once make each of them time out. Plexamp Lite uses TLS 1.2 with session resumption, so only
the first connection to each server pays the full cost; later ones resume in a fraction of it.
For the best remote experience enable **Settings → Remote Access** on your Plex Media Server.
Without it, Relay still works.
Android 2.x can't do modern TLS, so Plexamp Lite bundles its own TLS 1.2/1.3 stack (Bouncy Castle,
renamed to `com.minamp.bc` so it doesn't clash with the old copy inside Android) and verifies
certificates against Mozilla's root store (from `certifi`). A tiny loopback proxy feeds the
phone's media player, so HTTPS streams and relay work the same as local ones. If the phone's
clock has been reset to before 2024 the certificate date check is skipped rather than failing.
## Playback and quality
* MP3, AAC (M4A) and Ogg Vorbis play directly. FLAC, ALAC etc. are transcoded to MP3 by the
server (Android 2.2/2.3 can't decode FLAC).
* **Menu → Settings**: separate quality for home Wi-Fi (default *Original*) and for away/mobile
data (default *192 kbps MP3*); 320/256/192/128/96 kbps available. *Quality over Bluetooth*
(default 192 kbps) caps both while the sound goes to a Bluetooth speaker: on the Mini, Wi-Fi and
Bluetooth share one radio, and a lighter stream keeps the speaker from stuttering.
* Seeking in a transcoded track restarts the transcode at the new position.
* **Read-ahead:** while a track plays, the next two in the queue are fetched whole in the background
(one at a time) and then played from the file; without an SD card only the next one is kept. Android 2.x's player keeps only a few seconds of stream buffer, so on weak
Wi-Fi shared with a Bluetooth speaker it used to go silent for seconds at a time.
* **One stream per account:** Plex cuts an account's file stream when any device signed in to it starts another,
so every request for a track's file (playing, read-ahead, downloads) asks for it as a download
(`?download=1`), which Plex leaves out of that rule; otherwise the read-ahead and the player, or two phones
playing at once, cut each other off. A server that refuses downloads gets the plain address.
* If a stream fails, Plexamp Lite re-picks the connection and resumes; if it fails again it downloads
the whole track first and plays from the SD card. "Download tracks before playing" forces that
mode (useful on flaky mobile data). Playback that stalls ("playing" but the time doesn't move
for 10 s) is recovered the same way.
* A Bluetooth speaker that drops its link for a moment pauses Plexamp Lite, which resumes by itself if the
speaker is back within 20 s. Why playback paused or stopped is written to the connection log,
which also goes to logcat (tag `PlexampLite`, readable over USB with `adb logcat`).
* Plays are reported to Plex (Now Playing in the Plex dashboard, play counts, resume position). For testing
against a real server, an empty file named `PlexampLite-noreport` at the top of the SD card turns that off
(`adb shell touch /sdcard/PlexampLite-noreport`; delete it afterwards).
## Features
* Home: Library radio, Loved radio (rated 4★+ / loved), Time-travel radio (pick a decade),
Random album, Recently added and Recently played shelves.
* Artists / Albums (5 sort orders) / Playlists tabs with paging and fast scroll; Search.
* Artist page with *Play all* and *Artist radio*; album and playlist pages with *Play*/*Shuffle*.
* Long-press anything: Play next, Add to queue, Go to album/artist, Love.
* Now Playing: big art on a backdrop coloured from the cover, seek bar, shuffle, repeat
(queue/track), love, sleep timer (15–90 min or end of track), where it's streaming from.
* **Lyrics**: the microphone button in Now Playing shows the track's lyrics from Plex in place of the art (as in
the Mac app). It's a lasting setting: it stays on for the following tracks and after restarts. Timed (LRC)
lyrics follow the song with the current line lit (tap a line to jump there; scrolling by hand pauses the
follow for a few seconds); plain-text lyrics just scroll. With several versions, timed ones win, else the
longest text (a local `.txt` can be a broken copy).
* **Home-screen widget** (4×1): art, title, artist, previous / play-pause / next; tap the art for Now Playing.
* Up Next queue: jump, play next, remove, clear; the queue and position survive restarts.
* Downloads for offline listening: long-press a track, album, artist or playlist -> *Download*;
*Menu -> Downloads* shows progress, space used and free, play/shuffle, retry and remove. Files go to
the SD card. MP3 originals are kept as they are (unless above a lower *Download quality* in
Settings); everything else is transcoded to MP3, 320 kbps by default. Downloaded tracks play from
the SD card, offline too, and plays made offline are reported to Plex later.
* *Menu -> Close Plexamp Lite* stops the music and closes every screen.
* Wired headset button: 1 click play/pause, 2 next, 3 previous; Bluetooth media keys;
pauses for calls and when headphones are unplugged.
## Android 4.1 build
One build per **Android level**, never per phone (the same pattern as Tenda): `build/PlexampLite.apk` for
Android 2.2+ and `build/PlexampLite-api16.apk` for 4.1+, from the same source, package and key.
`versionCode` is the manifest's × 100 + the level (15 → 1508 and 1516), so a phone that moves up a level
gets the newer build as an ordinary update, and Tenda gives each phone the build for its Android. Everything is
shared in `src/`; what only one level can do is in `platform/api8/…/Platform.java` and
`platform/api16/…/Platform.java` (the same methods; keep them in step), plus `platform/api16/res`, which
overrides a few resources. The Android 4.1 build adds:
* **ExoPlayer 2.18** (`platform/api16/…/ExoEngine.java`) instead of Android's MediaPlayer, behind the small
`Engine` interface the player service uses (`MediaPlayerEngine` on Android 2.x):
* **gapless playback**: when the next track's file is already here (read ahead or downloaded), it's queued
in the player and starts with no gap;
* a buffer of up to 3 minutes instead of seconds, so Wi-Fi gaps don't silence it;
* errors that say what failed. A track ExoPlayer can't decode is retried with Android's MediaPlayer.
Read-ahead, the stream proxy, the transcodes and the recovery steps are the same in both builds.
* A **notification** with the art and ⏮ ⏯ ⏭ × (pull it down for a larger one); swiping it away while paused stops.
* **Lock-screen controls** with the art (RemoteControlClient).
* The **widget** can be resized; two rows or taller shows a larger layout.
* Android 4's **Holo** dialogs and text fields; a ⋮ button on phones without a Menu key.
* **Metered networks**: a phone's Wi-Fi hotspot counts as away, like mobile data.
The Galaxy Mini's build is unchanged in how it plays, and also got the widget and the lyrics.
## Building (no Android SDK needed)
```
tools/setup_toolchain.sh # once: fetches aapt2, the API 8 android.jar, builds dx, renames Bouncy Castle
tools/fetch_exoplayer.sh # once: ExoPlayer 2.18.7 + Guava (checksums pinned) into ~/android-toolchain
./build.sh # -> build/PlexampLite.apk (Android 2.2+) and build/PlexampLite-api16.apk (4.1+)
./build.sh 16 # one level only
```
Also needed for the Android 4.1 build: the API 16 `android.jar` in `~/android-toolchain/platforms/android-16/`,
R8 (`d8.jar` from build-tools 34, in `~/android-toolchain/calendar-tools/`) and a JDK 11+ (`build.sh` finds
Homebrew's openjdk@17). ExoPlayer 2.18.7 is the newest that needs only Guava; R8 (`tools/r8-rules.pro`) keeps
the app whole and shrinks ExoPlayer and Guava to what it uses, in one dex.
Per level `build.sh` runs `aapt2` (resources), `javac` (Java 8 bytecode checked against that level's
`android.jar`), then for Android 2.x the Dalvik 2.x link check and `dx` (dex 035), for 4.1 R8; then `tools/signapk.py`, which writes the v1 signature in the
classic Android SignApk format and aligns the APK. (Modern `jarsigner` output does *not* install on
Android 4.2 and older: it names digests `SHA-1-Digest` and labels the PKCS#7 signer algorithm in a
way old `JarUtils` can't read, so the phone treats the APK as unsigned.) The debug key in `signing/` (password `minamp123`) is what this APK is
signed with, so rebuilt APKs install over it.
Tests (desktop JVM, no device):
* `test/PlexTest.java` + `test/mock_plex.py`: PIN sign-in, server discovery, connection
picking/fallback, TLS with a wildcard plex.direct-style certificate (ECDSA leaf, RSA root),
untrusted-root rejection, browsing, search, artwork, and the stream proxy (Range requests over
TLS, de-chunking transcodes, aborting).
* `test/QueueTest.java`: shuffle/unshuffle, repeat modes, play next, remove, move, clear.
* `test/LyricsTest.java`: LRC (several stamps a line, offsets, word stamps) and plain-text lyrics
(`javac -d /tmp/lt src/com/minamp/util/Lrc.java test/LyricsTest.java && java -cp /tmp/lt LyricsTest`).
* `test/PoolAbortTest.java` (`test/run_pool_abort.sh`): aborting a response that's already finished leaves
its pooled connection, now carrying another request, alone.
* `test/PreferHomeTest.java` + `test/identity_server.py`: after being away, the home address wins
again as soon as it answers.
* `test/NetTest.java`: the bundled TLS + HTTP client against real servers; `test/HandshakeProfile.java`
samples where handshake time goes (run with `-Xint`).
## Limits
* Android 2.x build: no gapless playback (no API for it), and the notification has no playback buttons
(pre-Android 3 can't): tap it to open Now Playing, or use the widget.
* Android 4.1 build: gapless only into a track whose file is already on the phone; FLAC is still transcoded
(ExoPlayer has no FLAC decoder on Android 4.1).