Plexamp Lite 1.14: Android 4.1 build with ExoPlayer, lyrics, home-screen widget

- One build per Android level, as in Tenda: build/PlexampLite.apk (Android 2.2+, versionCode 1508) and
  build/PlexampLite-api16.apk (Android 4.1+, 1516), from shared src/ plus platform/api8 or platform/api16.
- Android 4.1 build: ExoPlayer 2.18.7 behind a small Engine interface (gapless hand-over to read-ahead and
  downloaded files, a longer buffer, MediaPlayer fallback for tracks it can't decode), a notification and
  lock screen with art and buttons, a resizable widget, Holo, a menu button without a Menu key, metered networks.
  R8 shrinks ExoPlayer and Guava (tools/fetch_exoplayer.sh, tools/r8-rules.pro).
- Both builds: lyrics in Now Playing as in the Mac app (timed lyrics follow and seek), a home-screen widget,
  media play/pause keys, the moon icon drawn without clipPath.
- PlexampLite-noreport on the SD card stops play reports, for tests against a real server.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
XoséandClaude Opus 5.5 committed 2026-11-01 18:41:26 +01:00
1 parent 2e7476669e
commit 13f29e26c4
39 files changed
+1685 -119

No files matched your search

+36
View File
@@ -6,6 +6,7 @@ import java.util.Collections;
import java.util.List;
import java.util.Random;
import com.minamp.net.Http;
import com.minamp.util.Json;
/** Paths into one music library section, plus the playback-URL decisions. */
@@ -150,6 +151,35 @@ public final class Library {
if (t.partKey.length() == 0) throw new IOException(com.minamp.util.Tr.t("no_media", "Track has no playable media"));
}
/** The track's lyrics text, "" when it has none: timed (LRC) if there are, else the longest plain text (a
* local .txt can be a broken copy of LyricFind's). Blocking. */
public String lyrics(Item t) throws IOException {
List<Item> l = server.items("/library/metadata/" + t.ratingKey); // lists leave the streams out
if (l.isEmpty()) return "";
Item full = l.get(0);
String best = "";
for (int i = 0; i < full.lyricsKeys.size(); i++) {
String text = lyricsStream(full.lyricsKeys.get(i));
if (i < full.lyricsTimed && text.trim().length() > 0) return text;
if (text.trim().length() > best.trim().length()) best = text;
}
return best;
}
private String lyricsStream(String key) throws IOException {
Http.Response r = Http.get(server.url(key), server.mediaHeaders(), 20000);
try {
if (r.code == 404) return "";
if (r.code != 200) throw new IOException("HTTP " + r.code);
byte[] d = r.readAll();
String text = new String(d, "UTF-8");
if (text.indexOf('\uFFFD') >= 0) text = new String(d, "ISO-8859-1"); // not UTF-8 after all
return text;
} finally {
r.close();
}
}
public static void shuffle(List<Item> l) {
Collections.shuffle(l, new Random());
}
@@ -295,7 +325,12 @@ public final class Library {
// ------------------------------------------------------------------ reporting
/** For testing against a real server: report no plays or positions (App sets it when the SD card has a file
* named PlexampLite-noreport), so the account's play counts and history stay as they were. */
public static boolean noReport;
public void timeline(Item t, String state, long timeMs) {
if (noReport) return;
try {
server.fetchQuiet("/:/timeline?ratingKey=" + t.ratingKey
+ "&key=" + Plex.enc("/library/metadata/" + t.ratingKey)
@@ -305,6 +340,7 @@ public final class Library {
}
public void scrobble(Item t) {
if (noReport) return;
try {
server.fetchQuiet("/:/scrobble?identifier=com.plexapp.plugins.library&key=" + Plex.enc(t.ratingKey));
} catch (Exception ignored) {}