Private
Public Access
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:
1 parent
9b88520ec3
commit
a610b090f0
3 files changed
+45
-4
No files matched your search
+3
-3
@@ -1,9 +1,9 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
package="com.minamp"
|
package="com.minamp"
|
||||||
android:versionCode="9"
|
android:versionCode="10"
|
||||||
android:versionName="1.8"
|
android:versionName="1.9"
|
||||||
android:installLocation="auto">
|
android:installLocation="internalOnly">
|
||||||
|
|
||||||
<uses-sdk android:minSdkVersion="8" android:targetSdkVersion="10" />
|
<uses-sdk android:minSdkVersion="8" android:targetSdkVersion="10" />
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ public class MediaButtonReceiver extends BroadcastReceiver {
|
|||||||
if (ev == null) return;
|
if (ev == null) return;
|
||||||
if (isOrderedBroadcast()) abortBroadcast();
|
if (isOrderedBroadcast()) abortBroadcast();
|
||||||
if (ev.getAction() != KeyEvent.ACTION_DOWN || ev.getRepeatCount() > 0) return;
|
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();
|
final Context app = context.getApplicationContext();
|
||||||
switch (ev.getKeyCode()) {
|
switch (ev.getKeyCode()) {
|
||||||
case KeyEvent.KEYCODE_HEADSETHOOK:
|
case KeyEvent.KEYCODE_HEADSETHOOK:
|
||||||
|
|||||||
@@ -144,6 +144,9 @@ public class PlayerService extends Service implements MediaPlayer.OnPreparedList
|
|||||||
private boolean hasFocus;
|
private boolean hasFocus;
|
||||||
private boolean pausedByFocus;
|
private boolean pausedByFocus;
|
||||||
private boolean pausedByCall;
|
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 WifiManager.WifiLock wifiLock;
|
||||||
private boolean noisyRegistered;
|
private boolean noisyRegistered;
|
||||||
private boolean foreground;
|
private boolean foreground;
|
||||||
@@ -486,6 +489,7 @@ public class PlayerService extends Service implements MediaPlayer.OnPreparedList
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void trackFailed(String msg) {
|
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);
|
long pos = Math.max(currentPosition(), lastPos);
|
||||||
if (refusal != null) msg = refusal; // the server's own reason beats "Playback error (1/-1004)"
|
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;
|
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() {
|
private void pause() {
|
||||||
pausedByFocus = false;
|
pausedByFocus = false;
|
||||||
|
pausedByRoute = false;
|
||||||
playWhenReady = false;
|
playWhenReady = false;
|
||||||
if (mp != null && prepared) {
|
if (mp != null && prepared) {
|
||||||
try { mp.pause(); } catch (Exception ignored) {}
|
try { mp.pause(); } catch (Exception ignored) {}
|
||||||
@@ -611,6 +616,7 @@ public class PlayerService extends Service implements MediaPlayer.OnPreparedList
|
|||||||
|
|
||||||
private final Runnable ticker = new Runnable() {
|
private final Runnable ticker = new Runnable() {
|
||||||
public void run() {
|
public void run() {
|
||||||
|
if (state == PLAYING) btOn = audio.isBluetoothA2dpOn();
|
||||||
App.get().handler.postDelayed(this, 1000);
|
App.get().handler.postDelayed(this, 1000);
|
||||||
long now = System.currentTimeMillis();
|
long now = System.currentTimeMillis();
|
||||||
if (sleepAt > 0 && now >= sleepAt) {
|
if (sleepAt > 0 && now >= sleepAt) {
|
||||||
@@ -692,10 +698,12 @@ public class PlayerService extends Service implements MediaPlayer.OnPreparedList
|
|||||||
if (pausedByFocus) { pausedByFocus = false; play(); }
|
if (pausedByFocus) { pausedByFocus = false; play(); }
|
||||||
break;
|
break;
|
||||||
case AudioManager.AUDIOFOCUS_LOSS:
|
case AudioManager.AUDIOFOCUS_LOSS:
|
||||||
|
com.minamp.net.DiagLog.log("paused: another app took the audio (focus lost)");
|
||||||
hasFocus = false;
|
hasFocus = false;
|
||||||
pause();
|
pause();
|
||||||
break;
|
break;
|
||||||
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
|
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; }
|
if (state == PLAYING) { pause(); pausedByFocus = true; }
|
||||||
break;
|
break;
|
||||||
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
|
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() {
|
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() {
|
private void registerNoisy() {
|
||||||
|
|||||||
Reference in new issue
Block a user