Skip to content

Commit b419a51

Browse files
Add media key fallback to stop stubborn playback
1 parent bfe781e commit b419a51

File tree

1 file changed

+38
-0
lines changed

1 file changed

+38
-0
lines changed

app/src/main/kotlin/com/d4rk/musicsleeptimer/plus/workers/SleepAudioWorker.kt

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import android.media.AudioPlaybackConfiguration
1111
import android.os.Build
1212
import android.os.Handler
1313
import android.os.Looper
14+
import android.os.SystemClock
15+
import android.view.KeyEvent
1416
import androidx.annotation.RequiresApi
1517
import androidx.work.ExistingWorkPolicy
1618
import 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

Comments
 (0)