diff --git a/app/src/main/java/com/hyperling/apps/infinitetimer/MainActivity.java b/app/src/main/java/com/hyperling/apps/infinitetimer/MainActivity.java index d2065f3..09dbbd2 100755 --- a/app/src/main/java/com/hyperling/apps/infinitetimer/MainActivity.java +++ b/app/src/main/java/com/hyperling/apps/infinitetimer/MainActivity.java @@ -13,6 +13,7 @@ import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.Handler; +import android.os.Looper; import android.os.Message; import android.provider.Settings; import android.support.v7.app.AlertDialog; @@ -55,6 +56,12 @@ public class MainActivity extends AppCompatActivity { TextView tvDot; Uri ringtoneUri; + + // RingtoneManager reference for consistent notification sound playback across loops + private android.media.Ringtone notificationRingtone; + + // For proper sound lifecycle management across timer loops + private Ringtone ringtonePlayer; MediaPlayer mediaPlayer; SeekBar seekBar; @@ -482,7 +489,7 @@ public class MainActivity extends AppCompatActivity { if (DEBUG) Log.d(tag, "Finished"); } - private final Handler incrementTime = new Handler(){ + private final android.os.Handler incrementTime = new android.os.Handler(android.os.Looper.getMainLooper()){ @Override public void handleMessage(Message msg) { String tag = TAG + "handleMessage"; @@ -605,17 +612,34 @@ public class MainActivity extends AppCompatActivity { if (DEBUG) Log.d(tag, "Starting"); try { - // Safely instantiate MediaPlayer locally to prevent NPE - Uri uri = android.media.RingtoneManager.getDefaultUri(android.media.RingtoneManager.TYPE_NOTIFICATION); - MediaPlayer localPlayer = MediaPlayer.create(this, uri); + // Stop any existing notification ringtone first to prevent overlap + if (notificationRingtone != null && notificationRingtone.isPlaying()) { + notificationRingtone.stop(); + } - if (localPlayer != null) { - localPlayer.start(); - localPlayer.release(); // Clean up resources immediately + // Use RingtoneManager which handles async playback lifecycle properly + Uri uri = android.media.RingtoneManager.getDefaultUri(android.media.RingtoneManager.TYPE_NOTIFICATION); + notificationRingtone = android.media.RingtoneManager.getRingtone(this, uri); + + if (notificationRingtone != null) { + // Ringtone.play() is asynchronous and handles its own lifecycle, + // avoiding the MediaPlayer.create() + immediate release bug that + // caused silence on subsequent loops + notificationRingtone.play(); } else { - // Fallback for edge cases where create() fails - android.media.Ringtone ringtone = android.media.RingtoneManager.getRingtone(this, uri); - if (ringtone != null) ringtone.play(); + // Fallback: if getRingtone fails, try direct MediaPlayer with proper lifecycle + MediaPlayer localPlayer = MediaPlayer.create(this, uri); + if (localPlayer != null) { + localPlayer.start(); + final MediaPlayer fp = localPlayer; + // Release after playback completes (use delay as a safety net) + new android.os.Handler(android.os.Looper.getMainLooper()).postDelayed( + new Runnable() { @Override public void run() { + if (fp.isPlaying()) fp.stop(); + fp.release(); + }}, 2000 + ); + } } } catch (Exception e) { sharedPreferences.edit().putBoolean(keyStartStop, false).apply(); @@ -744,6 +768,11 @@ public class MainActivity extends AppCompatActivity { Alert.stop(this); if (mediaPlayer != null) { mediaPlayer.release(); mediaPlayer = null; } + // Clean up ringtone player to prevent resource leak across config changes + if (notificationRingtone != null && notificationRingtone.isPlaying()) { + notificationRingtone.stop(); + } + if (DEBUG) Log.d(tag, "Done with local code, calling super.."); super.onDestroy();