@@ -8,14 +8,18 @@ import android.content.ComponentName
88import android.content.Context
99import android.content.Intent
1010import android.content.pm.ServiceInfo
11+ import android.database.ContentObserver
1112import android.graphics.Bitmap
1213import android.media.AudioDeviceCallback
1314import android.media.AudioDeviceInfo
1415import android.media.AudioManager
1516import android.net.Uri
1617import android.os.Build
1718import android.os.Bundle
19+ import android.os.Handler
20+ import android.os.Looper
1821import android.os.SystemClock
22+ import android.provider.Settings
1923import androidx.core.app.NotificationCompat
2024import androidx.core.app.ServiceCompat
2125import androidx.core.graphics.drawable.toBitmap
@@ -230,8 +234,27 @@ class MusicService : MediaLibraryService() {
230234 private var shouldResumeAfterHeadsetReconnect = false
231235 private var lastNoisyPauseRealtimeMs = 0L
232236 private var resumeOnHeadsetReconnectEnabled = false
237+ private var pauseOnVolumeZeroEnabled = false
233238 private var temporaryForegroundStartedInOnCreate = false
234239
240+ // Observes the device's media stream volume and pauses playback when it
241+ // reaches 0, if the user has enabled the "pause on volume zero" preference.
242+ private val systemVolumeObserver by lazy {
243+ object : ContentObserver (Handler (Looper .getMainLooper())) {
244+ override fun onChange (selfChange : Boolean ) {
245+ if (! pauseOnVolumeZeroEnabled) return
246+ val streamVolume = audioManager.getStreamVolume(AudioManager .STREAM_MUSIC )
247+ if (streamVolume == 0 ) {
248+ val player = mediaSession?.player ? : engine.masterPlayer
249+ if (player.isPlaying) {
250+ player.pause()
251+ Timber .tag(TAG ).d(" pauseOnVolumeZero: paused because system media volume reached 0" )
252+ }
253+ }
254+ }
255+ }
256+ }
257+
235258 companion object {
236259 private const val TAG = " MusicService_PixelPlay"
237260 const val NOTIFICATION_ID = 101
@@ -411,6 +434,7 @@ class MusicService : MediaLibraryService() {
411434 syncLocalListeningStatsFromPlayer(engine.masterPlayer)
412435
413436 engine.masterPlayer.addListener(playerListener)
437+ registerSystemVolumeObserver()
414438
415439 // Handle player swaps (crossfade) to keep MediaSession in sync
416440 engine.setOnPlayerAboutToBeReleasedListener { oldPlayer ->
@@ -492,6 +516,12 @@ class MusicService : MediaLibraryService() {
492516 }
493517 }
494518
519+ serviceScope.launch {
520+ userPreferencesRepository.pauseOnVolumeZeroFlow.collect { enabled ->
521+ pauseOnVolumeZeroEnabled = enabled
522+ }
523+ }
524+
495525 serviceScope.launch {
496526 userPreferencesRepository.persistentShuffleEnabledFlow.collect { enabled ->
497527 persistentShuffleEnabled = enabled
@@ -1219,6 +1249,13 @@ class MusicService : MediaLibraryService() {
12191249 private val playerListener = object : Player .Listener {
12201250 override fun onVolumeChanged (volume : Float ) {
12211251 replayGainProcessor.onPlayerVolumeChanged(volume)
1252+ if (pauseOnVolumeZeroEnabled && volume == 0f ) {
1253+ val player = mediaSession?.player ? : engine.masterPlayer
1254+ if (player.isPlaying) {
1255+ player.pause()
1256+ Timber .tag(TAG ).d(" pauseOnVolumeZero: paused playback because volume reached 0" )
1257+ }
1258+ }
12221259 }
12231260
12241261 override fun onIsPlayingChanged (isPlaying : Boolean ) {
@@ -1464,6 +1501,7 @@ class MusicService : MediaLibraryService() {
14641501 widgetUpdateManager.cancel()
14651502 castSyncCoordinator.stop()
14661503 unregisterHeadsetReconnectMonitor()
1504+ unregisterSystemVolumeObserver()
14671505 wearStatePublisher.clearState()
14681506 replayGainProcessor.cancel()
14691507
@@ -1526,6 +1564,18 @@ class MusicService : MediaLibraryService() {
15261564 clearHeadsetReconnectResume()
15271565 }
15281566
1567+ private fun registerSystemVolumeObserver () {
1568+ contentResolver.registerContentObserver(
1569+ Settings .System .CONTENT_URI ,
1570+ true ,
1571+ systemVolumeObserver
1572+ )
1573+ }
1574+
1575+ private fun unregisterSystemVolumeObserver () {
1576+ runCatching { contentResolver.unregisterContentObserver(systemVolumeObserver) }
1577+ }
1578+
15291579 private fun maybeResumeAfterHeadsetReconnect () {
15301580 if (! resumeOnHeadsetReconnectEnabled || ! shouldResumeAfterHeadsetReconnect) return
15311581
0 commit comments