Minamp 1.9: install internally, resume after Bluetooth dropouts, log why playback stops

Installed on the SD card (installLocation auto), Minamp was killed whenever USB
storage was turned on. A2DP dropouts send AUDIO_BECOMING_NOISY like an unplug:
after a Bluetooth loss, resume if the speaker is back within 20 s. Focus loss,
media buttons and stream failures now go to the connection log.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
XoséandClaude Opus 5.5 committed 2026-10-18 14:16:56 +02:00
1 parent 9b88520ec3
commit a610b090f0
3 files changed
+45 -4

No files matched your search

+3 -3
View File
@@ -1,9 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.minamp"
android:versionCode="9"
android:versionName="1.8"
android:installLocation="auto">
android:versionCode="10"
android:versionName="1.9"
android:installLocation="internalOnly">
<uses-sdk android:minSdkVersion="8" android:targetSdkVersion="10" />
+1
View File
@@ -19,6 +19,7 @@ public class MediaButtonReceiver extends BroadcastReceiver {
if (ev == null) return;
if (isOrderedBroadcast()) abortBroadcast();
if (ev.getAction() != KeyEvent.ACTION_DOWN || ev.getRepeatCount() > 0) return;
com.minamp.net.DiagLog.log("media button " + ev.getKeyCode() + " (headset / Bluetooth)");
final Context app = context.getApplicationContext();
switch (ev.getKeyCode()) {
case KeyEvent.KEYCODE_HEADSETHOOK:
+41 -1
View File
@@ -144,6 +144,9 @@ public class PlayerService extends Service implements MediaPlayer.OnPreparedList
private boolean hasFocus;
private boolean pausedByFocus;
private boolean pausedByCall;
private boolean pausedByRoute; // the Bluetooth speaker dropped: resume if it's back soon
private long routeLostAt;
private boolean btOn; // audio was going to Bluetooth (A2DP), as last seen while playing
private WifiManager.WifiLock wifiLock;
private boolean noisyRegistered;
private boolean foreground;
@@ -486,6 +489,7 @@ public class PlayerService extends Service implements MediaPlayer.OnPreparedList
}
private void trackFailed(String msg) {
com.minamp.net.DiagLog.log("track failed: " + msg + (refusal != null ? " / " + refusal : "") + " (retry " + (retries + 1) + ")");
long pos = Math.max(currentPosition(), lastPos);
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;
@@ -536,6 +540,7 @@ public class PlayerService extends Service implements MediaPlayer.OnPreparedList
private void pause() {
pausedByFocus = false;
pausedByRoute = false;
playWhenReady = false;
if (mp != null && prepared) {
try { mp.pause(); } catch (Exception ignored) {}
@@ -611,6 +616,7 @@ public class PlayerService extends Service implements MediaPlayer.OnPreparedList
private final Runnable ticker = new Runnable() {
public void run() {
if (state == PLAYING) btOn = audio.isBluetoothA2dpOn();
App.get().handler.postDelayed(this, 1000);
long now = System.currentTimeMillis();
if (sleepAt > 0 && now >= sleepAt) {
@@ -692,10 +698,12 @@ public class PlayerService extends Service implements MediaPlayer.OnPreparedList
if (pausedByFocus) { pausedByFocus = false; play(); }
break;
case AudioManager.AUDIOFOCUS_LOSS:
com.minamp.net.DiagLog.log("paused: another app took the audio (focus lost)");
hasFocus = false;
pause();
break;
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
com.minamp.net.DiagLog.log("paused for a moment: another app needs the audio");
if (state == PLAYING) { pause(); pausedByFocus = true; }
break;
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
@@ -716,8 +724,40 @@ public class PlayerService extends Service implements MediaPlayer.OnPreparedList
}
};
/**
* Headphones unplugged, or the Bluetooth speaker went away. Android 2.x sends this for any A2DP dropout too
* (Wi-Fi and Bluetooth share one radio on these phones, and cheap speakers drop the link now and then):
* after a Bluetooth loss, keep watching for the speaker and resume if it's back within 20 s.
*/
private final BroadcastReceiver noisy = new BroadcastReceiver() {
@Override public void onReceive(Context c, Intent i) { pause(); }
@Override public void onReceive(Context c, Intent i) {
boolean bt = btOn;
com.minamp.net.DiagLog.log("paused: audio output went away (" + (bt ? "Bluetooth speaker dropped" : "headphones unplugged") + ")");
pause();
if (bt && track != null) {
pausedByRoute = true;
routeLostAt = System.currentTimeMillis();
App.get().handler.removeCallbacks(routeWatch);
App.get().handler.postDelayed(routeWatch, 1000);
}
}
};
private final Runnable routeWatch = new Runnable() {
public void run() {
if (!pausedByRoute) return;
long gone = System.currentTimeMillis() - routeLostAt;
if (audio.isBluetoothA2dpOn()) {
com.minamp.net.DiagLog.log("Bluetooth speaker back after " + gone / 1000 + "s: resuming");
pausedByRoute = false;
play();
} else if (gone < 20000) {
App.get().handler.postDelayed(this, 1000);
} else {
com.minamp.net.DiagLog.log("Bluetooth speaker didn't come back: staying paused");
pausedByRoute = false;
}
}
};
private void registerNoisy() {