Backport from the iOS version: offline downloads, home-first reconnect, stall warning, Close

- Downloads for offline listening: long-press a track, album, artist or
  playlist -> Download; Menu -> Downloads lists them (progress, space used/free,
  play/shuffle, retry, remove). Saved on the SD card (Android/data/com.minamp/
  files/downloads). MP3 originals kept as they are unless above a lower
  download quality; the rest transcoded to MP3 (320 kbps by default). One at a
  time, Range resume for originals, retried when the server is back, Wi-Fi lock.
- Downloaded tracks play from the SD card, offline too; plays made offline
  are reported to Plex when a connection works again.
- Server.pick: when last time's connection was remote, give the plain home
  addresses 2 s first (a hairpinned remote address kept us encrypted and
  capped at home). test/PreferHomeTest.java.
- Playback "playing" but not moving for 12 s: say so instead of staying silent.
- Menu -> Close Minamp: stop the music and close every screen.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
XoséandClaude Opus 5.5 committed 2026-10-17 16:03:46 +02:00
1 parent f7f49ecfbb
commit f9050bbac0
15 files changed
+933 -43

No files matched your search

+36 -1
View File
@@ -89,6 +89,7 @@ public class PlayerService extends Service implements MediaPlayer.OnPreparedList
public static String sourceLabel() {
PlayerService s = instance;
if (s == null || s.track == null) return "";
if (s.localFile) return s.getString(R.string.downloaded);
App app = App.get();
String where = app.server == null || app.server.active() == null ? "" : com.minamp.plex.Server.kindLabel(app.server.active().kind);
String q = s.transcoding ? s.kbps + " kbps MP3" : (s.track.codec.toUpperCase() + (s.track.bitrate > 0 ? " " + s.track.bitrate + " kbps" : ""));
@@ -134,6 +135,10 @@ public class PlayerService extends Service implements MediaPlayer.OnPreparedList
private long lastTimeline;
private long lastSave;
private long lastPos; // last position seen while playing (getCurrentPosition is unreliable after completion)
private boolean localFile; // playing a download from the SD card
private long stallPos = -1; // position last seen moving
private long stallSince;
private boolean stallWarned;
private AudioManager audio;
private boolean hasFocus;
@@ -265,6 +270,17 @@ public class PlayerService extends Service implements MediaPlayer.OnPreparedList
setState(BUFFERING, getString(R.string.connecting));
goForeground();
if (play) requestFocus();
// Downloaded: play the file on the SD card, no network needed (offline too)
final File saved = Downloads.get().fileFor(it);
localFile = saved != null;
if (saved != null) {
transcoding = false;
session = null;
variantOrder = null;
kbps = 0;
begin(new java.util.ArrayList<String>(), "mp3", saved, 0, posMs);
return;
}
final boolean download = forceDownload || app.downloadFirst();
worker.execute(new Runnable() {
public void run() {
@@ -605,6 +621,16 @@ public class PlayerService extends Service implements MediaPlayer.OnPreparedList
if (state == PLAYING && track != null) {
long pos = currentPosition();
lastPos = pos;
// "playing" but not moving for 12 s: say so instead of staying silent
if (pos != stallPos) {
stallPos = pos;
stallSince = now;
stallWarned = false;
} else if (!stallWarned && now - stallSince > 12000) {
stallWarned = true;
com.minamp.net.DiagLog.log("playback stalled at " + pos / 1000 + "s");
toast(getString(R.string.stalled));
}
if (!scrobbled && track.duration > 0 && pos > track.duration * 9 / 10) scrobble(track);
if (now - lastTimeline > 10000) report(false);
if (now - lastSave > 15000) { lastSave = now; PlayQueue.get().resumePositionMs = pos; }
@@ -631,7 +657,16 @@ public class PlayerService extends Service implements MediaPlayer.OnPreparedList
scrobbled = true;
final App app = App.get();
reporter.execute(new Runnable() {
public void run() { if (app.library != null) app.library.scrobble(it); }
public void run() {
if (app.library == null) return;
// offline (a download): keep the play and report it when the server is back
boolean online = app.library.server.active() != null;
if (!online) {
try { app.library.server.base(); online = true; } catch (Exception ignored) {}
}
if (online) app.library.scrobble(it);
else app.addPendingScrobble(it.ratingKey);
}
});
}