fix: play through the alarm stream so the ring cuts through DND

DND silences the media stream by default, so playback on STREAM_MUSIC was
invisible during DND even though the notification showed. Switch to
STREAM_ALARM + USAGE_ALARM, which DND exempts by default, and restore/force
the alarm stream volume instead of media.
This commit is contained in:
avi 2026-08-07 13:37:29 -05:00
commit 5e5d53ab9b
2 changed files with 20 additions and 16 deletions

View file

@ -49,11 +49,14 @@ refuse to play any sound.** This is enforced at two points:
Only after the user flips that toggle will a trigger actually ring. Only after the user flips that toggle will a trigger actually ring.
When access **is** granted, `RingService`: When access **is** granted, `RingService`:
- saves the current music volume, - saves the current **alarm-stream** volume,
- forces `AudioManager.STREAM_MUSIC` to max, - forces `AudioManager.STREAM_ALARM` to max,
- requests `AUDIOFOCUS_GAIN_TRANSIENT` using `USAGE_NOTIFICATION_RINGTONE` and - requests `AUDIOFOCUS_GAIN_TRANSIENT` using `AudioAttributes.USAGE_ALARM`,
sets `AudioManager.mode = MODE_RINGTONE` so the sound cuts through DND, - plays on the **alarm stream** — the one stream Do Not Disturb does **not**
- plays for 30 s, then restores the volume and abandons focus. silence by default (media/music *is* silenced), which is what lets the ring
cut through DND,
- plays for the configured number of seconds, then restores the volume and
abandons focus.
--- ---

View file

@ -147,19 +147,20 @@ class RingService : Service() {
*/ */
private suspend fun beginPlayback(uri: Uri): Boolean { private suspend fun beginPlayback(uri: Uri): Boolean {
return try { return try {
// Step 1: save current volume. // Step 1: save current alarm-stream volume so we can restore it later.
originalVolume = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC) originalVolume = audioManager.getStreamVolume(AudioManager.STREAM_ALARM)
// Step 2: force max volume. // Step 2: force the alarm stream to max volume.
val maxVolume = audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC) val maxVolume = audioManager.getStreamMaxVolume(AudioManager.STREAM_ALARM)
audioManager.setStreamVolume( audioManager.setStreamVolume(
AudioManager.STREAM_MUSIC, AudioManager.STREAM_ALARM,
maxVolume, maxVolume,
0 // flags = 0 so we do NOT show the system volume UI. 0 // flags = 0 so we do NOT show the system volume UI.
) )
// Step 3: request focus. Use USAGE_NOTIFICATION_RINGTONE so the // Step 3: request focus. Playing on the ALARM stream with
// system lets it play even during DND, and set MODE_RINGTONE. // USAGE_ALARM is what cuts through Do Not Disturb — DND does not
// silence alarms by default (unlike media/music).
previousFocusMode = audioManager.mode previousFocusMode = audioManager.mode
requestAudioFocus() requestAudioFocus()
@ -168,7 +169,7 @@ class RingService : Service() {
mediaPlayer = MediaPlayer() mediaPlayer = MediaPlayer()
mediaPlayer.setAudioAttributes( mediaPlayer.setAudioAttributes(
AudioAttributes.Builder() AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE) .setUsage(AudioAttributes.USAGE_ALARM)
.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
.build() .build()
) )
@ -215,7 +216,7 @@ class RingService : Service() {
} }
val attributes = AudioAttributes.Builder() val attributes = AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE) .setUsage(AudioAttributes.USAGE_ALARM)
.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
.build() .build()
@ -230,7 +231,7 @@ class RingService : Service() {
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
audioManager.requestAudioFocus( audioManager.requestAudioFocus(
listener, listener,
AudioManager.STREAM_MUSIC, AudioManager.STREAM_ALARM,
AudioManager.AUDIOFOCUS_GAIN_TRANSIENT AudioManager.AUDIOFOCUS_GAIN_TRANSIENT
) )
} }
@ -276,7 +277,7 @@ class RingService : Service() {
// Restore the previous volume. // Restore the previous volume.
if (originalVolume >= 0) { if (originalVolume >= 0) {
audioManager.setStreamVolume( audioManager.setStreamVolume(
AudioManager.STREAM_MUSIC, AudioManager.STREAM_ALARM,
originalVolume, originalVolume,
0 0
) )