Skip to content

Commit c45d4d8

Browse files
authored
Merge pull request #2415 from Amonoman/master
feat(playback): pause on volume zero + OOM fix for large Telegram channels
2 parents 2ae0e06 + 761bac0 commit c45d4d8

24 files changed

Lines changed: 356 additions & 193 deletions

File tree

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
<?xml version="1.0" encoding="utf-8"?>
22
<resources>
33
<string name="app_name">PixelPlayer [D]</string>
4+
<string name="settings_pause_on_volume_zero">Pausieren, wenn Lautstärke null erreicht</string>
5+
<string name="settings_pause_on_volume_zero_desc">Wiedergabe automatisch pausieren, wenn die Lautstärke auf 0 gesetzt wird</string>
6+
<string name="settings_volume_section">Lautstärke</string>
47
</resources>
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
<?xml version="1.0" encoding="utf-8"?>
22
<resources>
33
<string name="app_name">PixelPlayer [D]</string>
4+
<string name="settings_pause_on_volume_zero">Mettre en pause quand le volume atteint zéro</string>
5+
<string name="settings_pause_on_volume_zero_desc">Mettre automatiquement en pause la lecture lorsque le volume est à 0</string>
6+
<string name="settings_volume_section">Volume</string>
47
</resources>
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
<?xml version="1.0" encoding="utf-8"?>
22
<resources>
33
<string name="app_name">PixelPlayer [D]</string>
4+
<string name="settings_pause_on_volume_zero">볼륨이 0이 되면 일시정지</string>
5+
<string name="settings_pause_on_volume_zero_desc">볼륨이 0으로 설정되면 자동으로 재생을 일시정지합니다</string>
6+
<string name="settings_volume_section">볼륨</string>
47
</resources>
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
<?xml version="1.0" encoding="utf-8"?>
22
<resources>
33
<string name="app_name">PixelPlayer [D]</string>
4+
<string name="settings_pause_on_volume_zero">Sett på pause når volumet er null</string>
5+
<string name="settings_pause_on_volume_zero_desc">Sett automatisk avspillingen på pause når volumet settes til 0</string>
6+
<string name="settings_volume_section">Volum</string>
47
</resources>
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
<?xml version="1.0" encoding="utf-8"?>
22
<resources>
33
<string name="app_name">PixelPlayer [D]</string>
4+
<string name="settings_pause_on_volume_zero">Пауза при нулевой громкости</string>
5+
<string name="settings_pause_on_volume_zero_desc">Автоматически приостанавливать воспроизведение, когда громкость равна 0</string>
6+
<string name="settings_volume_section">Громкость</string>
47
</resources>
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
<?xml version="1.0" encoding="utf-8"?>
22
<resources>
33
<string name="app_name">PixelPlayer [D]</string>
4+
<string name="settings_pause_on_volume_zero">Pause when volume reaches zero</string>
5+
<string name="settings_pause_on_volume_zero_desc">Automatically pause playback when the volume is set to 0</string>
6+
<string name="settings_volume_section">Volume</string>
47
</resources>

app/src/main/java/com/theveloper/pixelplay/data/preferences/UserPreferencesRepository.kt

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,7 @@ class UserPreferencesRepository @Inject constructor(
244244
// ReplayGain
245245
val REPLAYGAIN_ENABLED = booleanPreferencesKey("replaygain_enabled")
246246
val REPLAYGAIN_USE_ALBUM_GAIN = booleanPreferencesKey("replaygain_use_album_gain")
247+
val PAUSE_ON_VOLUME_ZERO = booleanPreferencesKey("pause_on_volume_zero")
247248
val SHOW_SCROLLBAR = booleanPreferencesKey("show_scrollbar")
248249
}
249250

@@ -745,6 +746,19 @@ suspend fun markDirectoryRulesVersionApplied(version: Int) {
745746
}
746747
}
747748

749+
// ─── Pause on volume zero ─────────────────────────────────────────────────
750+
751+
val pauseOnVolumeZeroFlow: Flow<Boolean> =
752+
dataStore.data.map { preferences ->
753+
preferences[PreferencesKeys.PAUSE_ON_VOLUME_ZERO] ?: false
754+
}
755+
756+
suspend fun setPauseOnVolumeZero(enabled: Boolean) {
757+
dataStore.edit { preferences ->
758+
preferences[PreferencesKeys.PAUSE_ON_VOLUME_ZERO] = enabled
759+
}
760+
}
761+
748762
val showScrollbarFlow: Flow<Boolean> =
749763
dataStore.data.map { preferences ->
750764
preferences[PreferencesKeys.SHOW_SCROLLBAR] ?: true

app/src/main/java/com/theveloper/pixelplay/data/service/MusicService.kt

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,18 @@ import android.content.ComponentName
88
import android.content.Context
99
import android.content.Intent
1010
import android.content.pm.ServiceInfo
11+
import android.database.ContentObserver
1112
import android.graphics.Bitmap
1213
import android.media.AudioDeviceCallback
1314
import android.media.AudioDeviceInfo
1415
import android.media.AudioManager
1516
import android.net.Uri
1617
import android.os.Build
1718
import android.os.Bundle
19+
import android.os.Handler
20+
import android.os.Looper
1821
import android.os.SystemClock
22+
import android.provider.Settings
1923
import androidx.core.app.NotificationCompat
2024
import androidx.core.app.ServiceCompat
2125
import 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

Comments
 (0)