Private
Public Access
Minamp 1.1
- Run network/TLS work on 256 KB-stack threads (net/Threads.java): Android 2.x's ~16 KB default stack overflowed during the bundled TLS handshake - Close pooled TLS connections off the caller thread - Try several Plex music-transcoder URL variants until the server accepts one, and remember the one that worked - Accept pasted server URLs with paths; default to 443 for https - Catch fatal errors and save a crash report (shown on next launch and written to Minamp-error.txt on the SD card) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
948d1198ad
commit
0a2ac901bd
16 files changed
+417
-80
No files matched your search
@@ -7,7 +7,6 @@ import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationManager;
|
||||
@@ -109,8 +108,8 @@ public class PlayerService extends Service implements MediaPlayer.OnPreparedList
|
||||
}
|
||||
|
||||
// ---- instance ----
|
||||
private final ExecutorService worker = Executors.newSingleThreadExecutor();
|
||||
private final ExecutorService reporter = Executors.newSingleThreadExecutor();
|
||||
private final ExecutorService worker = com.minamp.net.Threads.pool("player", 1);
|
||||
private final ExecutorService reporter = com.minamp.net.Threads.pool("report", 1);
|
||||
private StreamProxy proxy;
|
||||
private MediaPlayer mp;
|
||||
private boolean prepared;
|
||||
@@ -125,6 +124,9 @@ public class PlayerService extends Service implements MediaPlayer.OnPreparedList
|
||||
private String session;
|
||||
private String routeId;
|
||||
private int retries;
|
||||
private int[] variantOrder; // transcode variant behind each URL of the current route
|
||||
private String refusal; // what the server said when it refused the current stream
|
||||
private int refusalCode;
|
||||
private int failuresInRow;
|
||||
private boolean forceDownload;
|
||||
private boolean scrobbled;
|
||||
@@ -150,6 +152,20 @@ public class PlayerService extends Service implements MediaPlayer.OnPreparedList
|
||||
wifiLock = wm.createWifiLock(WifiManager.WIFI_MODE_FULL, "minamp");
|
||||
wifiLock.setReferenceCounted(false);
|
||||
proxy = new StreamProxy();
|
||||
proxy.listener = new StreamProxy.Listener() {
|
||||
public void onError(final String id, final int code, final String message) {
|
||||
post(new Runnable() {
|
||||
public void run() {
|
||||
if (id.equals(routeId)) { refusal = message; refusalCode = code; }
|
||||
}
|
||||
});
|
||||
}
|
||||
public void onAccepted(final String id, final int index) {
|
||||
post(new Runnable() {
|
||||
public void run() { if (id.equals(routeId)) rememberVariant(index); }
|
||||
});
|
||||
}
|
||||
};
|
||||
try { proxy.start(); } catch (IOException e) { /* reported when playback starts */ }
|
||||
try {
|
||||
TelephonyManager tm = (TelephonyManager) getSystemService(TELEPHONY_SERVICE);
|
||||
@@ -254,23 +270,29 @@ public class PlayerService extends Service implements MediaPlayer.OnPreparedList
|
||||
final String sess = direct ? null : "minamp-" + Long.toString(System.currentTimeMillis(), 36);
|
||||
// Streamed transcodes start at the offset; downloads always fetch the whole track and seek in the file.
|
||||
final long startAt = (direct || download) ? 0 : (posMs / 1000) * 1000;
|
||||
final String url = direct ? lib.directUrl(it) : lib.transcodeUrl(it, max, startAt, sess);
|
||||
final java.util.List<String> urls;
|
||||
final int[] order = direct ? null : Library.variantOrder();
|
||||
if (direct) { urls = new java.util.ArrayList<String>(); urls.add(lib.directUrl(it)); }
|
||||
else urls = lib.transcodeUrls(it, max, startAt, sess);
|
||||
final String ext = direct ? extFor(it) : "mp3";
|
||||
File file = null;
|
||||
if (download) file = downloadTo(url, g, it);
|
||||
if (download) file = downloadTo(urls, order, g, it);
|
||||
final File ff = file;
|
||||
post(new Runnable() {
|
||||
public void run() {
|
||||
if (g != gen) return;
|
||||
transcoding = !direct;
|
||||
session = sess;
|
||||
variantOrder = order;
|
||||
kbps = direct ? 0 : (max <= 0 ? 320 : max);
|
||||
begin(url, ext, ff, startAt, (direct || download) ? posMs : 0);
|
||||
begin(urls, ext, ff, startAt, (direct || download) ? posMs : 0);
|
||||
}
|
||||
});
|
||||
} catch (final Exception e) {
|
||||
} catch (final Throwable e) {
|
||||
if (!(e instanceof Exception)) App.logError("player", e);
|
||||
final String m = com.minamp.net.Threads.describe(e);
|
||||
post(new Runnable() {
|
||||
public void run() { if (g == gen) trackFailed(message(e)); }
|
||||
public void run() { if (g == gen) trackFailed(m); }
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -284,13 +306,28 @@ public class PlayerService extends Service implements MediaPlayer.OnPreparedList
|
||||
return "mp3";
|
||||
}
|
||||
|
||||
private File downloadTo(String url, int g, Item it) throws IOException {
|
||||
private File downloadTo(java.util.List<String> urls, final int[] order, int g, Item it) throws IOException {
|
||||
File base = null;
|
||||
if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) base = getExternalCacheDir();
|
||||
if (base == null) base = getCacheDir();
|
||||
File f = new File(base, "play-" + (g % 2) + ".tmp");
|
||||
Http.Response r = Http.get(url, Plex.headers(null), 30000);
|
||||
if (r.code != 200) { r.close(); throw new IOException("Server said " + r.code); }
|
||||
Http.Response r = null;
|
||||
String why = null;
|
||||
for (int i = 0; i < urls.size(); i++) {
|
||||
r = Http.get(urls.get(i), Plex.headers(null), 30000);
|
||||
if (r.code == 200) {
|
||||
if (order != null) {
|
||||
final int idx = i;
|
||||
post(new Runnable() { public void run() { if (variantOrder == order) rememberVariant(idx); else rememberVariantOf(order, idx); } });
|
||||
}
|
||||
break;
|
||||
}
|
||||
why = "Server refused the stream (" + r.code + ")";
|
||||
String body = new String(r.readAll(), "UTF-8");
|
||||
if (body.trim().length() > 0) why += ": " + Plex.snippet(body);
|
||||
r = null;
|
||||
}
|
||||
if (r == null) throw new IOException(why);
|
||||
InputStream in = r.body;
|
||||
FileOutputStream out = new FileOutputStream(f);
|
||||
try {
|
||||
@@ -316,7 +353,20 @@ public class PlayerService extends Service implements MediaPlayer.OnPreparedList
|
||||
return f;
|
||||
}
|
||||
|
||||
private void begin(String url, String ext, File file, long offset, long seekTo) {
|
||||
private void rememberVariant(int index) {
|
||||
rememberVariantOf(variantOrder, index);
|
||||
}
|
||||
|
||||
private void rememberVariantOf(int[] order, int index) {
|
||||
if (order == null || index < 0 || index >= order.length) return;
|
||||
int v = order[index];
|
||||
if (v != Library.preferredVariant) {
|
||||
Library.preferredVariant = v;
|
||||
App.get().prefs.edit().putInt("tcVariant", v).commit();
|
||||
}
|
||||
}
|
||||
|
||||
private void begin(java.util.List<String> urls, String ext, File file, long offset, long seekTo) {
|
||||
try {
|
||||
mp = new MediaPlayer();
|
||||
mp.setAudioStreamType(AudioManager.STREAM_MUSIC);
|
||||
@@ -335,7 +385,9 @@ public class PlayerService extends Service implements MediaPlayer.OnPreparedList
|
||||
} else {
|
||||
if (proxy.port() < 0) proxy.start();
|
||||
routeId = proxy.newId();
|
||||
mp.setDataSource(proxy.register(routeId, url, ext));
|
||||
refusal = null;
|
||||
refusalCode = 0;
|
||||
mp.setDataSource(proxy.register(routeId, urls, ext));
|
||||
}
|
||||
setState(BUFFERING, "Buffering…");
|
||||
mp.prepareAsync();
|
||||
@@ -400,7 +452,12 @@ public class PlayerService extends Service implements MediaPlayer.OnPreparedList
|
||||
|
||||
private void trackFailed(String msg) {
|
||||
long pos = Math.max(currentPosition(), lastPos);
|
||||
retries++;
|
||||
if (refusal != null) msg = refusal; // the server's own reason beats "Playback error (1/-1004)"
|
||||
boolean permanent = refusalCode >= 400 && refusalCode < 500 && refusalCode != 408 && refusalCode != 429
|
||||
|| (msg != null && msg.startsWith("Server refused the stream (4"));
|
||||
refusal = null;
|
||||
refusalCode = 0;
|
||||
retries = permanent ? 99 : retries + 1;
|
||||
App app = App.get();
|
||||
if (retries == 1) {
|
||||
// Most failures on a phone are network changes: re-pick the connection and resume.
|
||||
@@ -658,7 +715,7 @@ public class PlayerService extends Service implements MediaPlayer.OnPreparedList
|
||||
private void post(Runnable r) { App.get().handler.post(r); }
|
||||
|
||||
private void toast(String m) {
|
||||
if (m != null && m.length() > 0) Toast.makeText(this, m, Toast.LENGTH_SHORT).show();
|
||||
if (m != null && m.length() > 0) Toast.makeText(this, m, m.length() > 40 ? Toast.LENGTH_LONG : Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
|
||||
static String message(Exception e) {
|
||||
|
||||
Reference in new issue
Block a user