diff --git a/AndroidManifest.xml b/AndroidManifest.xml index d3c498c..3a2d578 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -1,9 +1,9 @@ + android:versionCode="10" + android:versionName="1.9" + android:installLocation="internalOnly"> diff --git a/src/com/minamp/MediaButtonReceiver.java b/src/com/minamp/MediaButtonReceiver.java index 7de3773..4cb57e7 100644 --- a/src/com/minamp/MediaButtonReceiver.java +++ b/src/com/minamp/MediaButtonReceiver.java @@ -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: diff --git a/src/com/minamp/PlayerService.java b/src/com/minamp/PlayerService.java index f4c7ac5..70e9991 100644 --- a/src/com/minamp/PlayerService.java +++ b/src/com/minamp/PlayerService.java @@ -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() {