@@ -11,6 +11,8 @@ import android.media.AudioPlaybackConfiguration
1111import android.os.Build
1212import android.os.Handler
1313import android.os.Looper
14+ import android.os.SystemClock
15+ import android.view.KeyEvent
1416import androidx.annotation.RequiresApi
1517import androidx.work.ExistingWorkPolicy
1618import androidx.work.OneTimeWorkRequest
@@ -32,6 +34,7 @@ class SleepAudioWorker(
3234 private val FADE_STEP_MILLIS : Long = TimeUnit .SECONDS .toMillis(1 )
3335 private val RESTORE_VOLUME_MILLIS : Long = TimeUnit .SECONDS .toMillis(2 )
3436 private val WAIT_FOR_PLAYBACK_STOP_MILLIS : Long = TimeUnit .SECONDS .toMillis(4 )
37+ private val MEDIA_KEY_WAIT_MILLIS : Long = TimeUnit .MILLISECONDS .toMillis(500 )
3538 private const val MAX_FADE_STEPS : Int = 20
3639 private const val UNIQUE_WORK_NAME : String = " sleep_audio_work"
3740 const val ACTION_SLEEP_AUDIO : String = " com.d4rk.musicsleeptimer.plus.action.SLEEP_AUDIO"
@@ -105,6 +108,10 @@ class SleepAudioWorker(
105108 sleepFor(RESTORE_VOLUME_MILLIS )
106109 playbackStopped = playbackStopped || ! audioManager.isMusicActive
107110
111+ if (! playbackStopped) {
112+ playbackStopped = audioManager.forceStopPlayback()
113+ }
114+
108115 Result .success()
109116 } catch (interrupted: InterruptedException ) {
110117 Thread .currentThread().interrupt()
@@ -238,6 +245,37 @@ class SleepAudioWorker(
238245 adjustStreamVolume(AudioManager .STREAM_MUSIC , AudioManager .ADJUST_LOWER , 0 )
239246 }
240247
248+ private fun AudioManager.forceStopPlayback (): Boolean {
249+ // Some media apps ignore transient audio focus loss. Use media key events as a
250+ // last resort to request a pause/stop through the same channel as hardware keys.
251+ val keyCodes = listOf (
252+ KeyEvent .KEYCODE_MEDIA_PAUSE ,
253+ KeyEvent .KEYCODE_MEDIA_STOP ,
254+ KeyEvent .KEYCODE_MEDIA_PLAY_PAUSE
255+ )
256+
257+ for (keyCode in keyCodes) {
258+ if (Thread .currentThread().isInterrupted) {
259+ break
260+ }
261+
262+ val eventTime = SystemClock .uptimeMillis()
263+ val downEvent = KeyEvent (eventTime, eventTime, KeyEvent .ACTION_DOWN , keyCode, 0 )
264+ val upEvent = KeyEvent (eventTime, eventTime, KeyEvent .ACTION_UP , keyCode, 0 )
265+
266+ runCatching { dispatchMediaKeyEvent(downEvent) }
267+ runCatching { dispatchMediaKeyEvent(upEvent) }
268+
269+ this @SleepAudioWorker.sleepFor(MEDIA_KEY_WAIT_MILLIS )
270+
271+ if (! isMusicActive) {
272+ return true
273+ }
274+ }
275+
276+ return ! isMusicActive
277+ }
278+
241279 private fun AudioManager.hasControllableOutputDevice (): Boolean {
242280 return if (Build .VERSION .SDK_INT >= 35 ) {
243281 hasSupportedFutureOutputDevice()
0 commit comments