Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions app/src/debug/res/values-de/strings.xml
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">PixelPlayer [D]</string>
<string name="settings_pause_on_volume_zero">Pausieren, wenn Lautstärke null erreicht</string>
<string name="settings_pause_on_volume_zero_desc">Wiedergabe automatisch pausieren, wenn die Lautstärke auf 0 gesetzt wird</string>
<string name="settings_volume_section">Lautstärke</string>
</resources>
3 changes: 3 additions & 0 deletions app/src/debug/res/values-fr/strings.xml
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">PixelPlayer [D]</string>
<string name="settings_pause_on_volume_zero">Mettre en pause quand le volume atteint zéro</string>
<string name="settings_pause_on_volume_zero_desc">Mettre automatiquement en pause la lecture lorsque le volume est à 0</string>
<string name="settings_volume_section">Volume</string>
</resources>
3 changes: 3 additions & 0 deletions app/src/debug/res/values-ko/strings.xml
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">PixelPlayer [D]</string>
<string name="settings_pause_on_volume_zero">볼륨이 0이 되면 일시정지</string>
<string name="settings_pause_on_volume_zero_desc">볼륨이 0으로 설정되면 자동으로 재생을 일시정지합니다</string>
<string name="settings_volume_section">볼륨</string>
</resources>
3 changes: 3 additions & 0 deletions app/src/debug/res/values-nb/strings.xml
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">PixelPlayer [D]</string>
<string name="settings_pause_on_volume_zero">Sett på pause når volumet er null</string>
<string name="settings_pause_on_volume_zero_desc">Sett automatisk avspillingen på pause når volumet settes til 0</string>
<string name="settings_volume_section">Volum</string>
</resources>
3 changes: 3 additions & 0 deletions app/src/debug/res/values-ru/strings.xml
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">PixelPlayer [D]</string>
<string name="settings_pause_on_volume_zero">Пауза при нулевой громкости</string>
<string name="settings_pause_on_volume_zero_desc">Автоматически приостанавливать воспроизведение, когда громкость равна 0</string>
<string name="settings_volume_section">Громкость</string>
</resources>
3 changes: 3 additions & 0 deletions app/src/debug/res/values/strings.xml
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">PixelPlayer [D]</string>
<string name="settings_pause_on_volume_zero">Pause when volume reaches zero</string>
<string name="settings_pause_on_volume_zero_desc">Automatically pause playback when the volume is set to 0</string>
<string name="settings_volume_section">Volume</string>
</resources>
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,7 @@ class UserPreferencesRepository @Inject constructor(
// ReplayGain
val REPLAYGAIN_ENABLED = booleanPreferencesKey("replaygain_enabled")
val REPLAYGAIN_USE_ALBUM_GAIN = booleanPreferencesKey("replaygain_use_album_gain")
val PAUSE_ON_VOLUME_ZERO = booleanPreferencesKey("pause_on_volume_zero")
val SHOW_SCROLLBAR = booleanPreferencesKey("show_scrollbar")
}

Expand Down Expand Up @@ -745,6 +746,19 @@ suspend fun markDirectoryRulesVersionApplied(version: Int) {
}
}

// ─── Pause on volume zero ─────────────────────────────────────────────────

val pauseOnVolumeZeroFlow: Flow<Boolean> =
dataStore.data.map { preferences ->
preferences[PreferencesKeys.PAUSE_ON_VOLUME_ZERO] ?: false
}

suspend fun setPauseOnVolumeZero(enabled: Boolean) {
dataStore.edit { preferences ->
preferences[PreferencesKeys.PAUSE_ON_VOLUME_ZERO] = enabled
}
}

val showScrollbarFlow: Flow<Boolean> =
dataStore.data.map { preferences ->
preferences[PreferencesKeys.SHOW_SCROLLBAR] ?: true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,18 @@ import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.pm.ServiceInfo
import android.database.ContentObserver
import android.graphics.Bitmap
import android.media.AudioDeviceCallback
import android.media.AudioDeviceInfo
import android.media.AudioManager
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.os.SystemClock
import android.provider.Settings
import androidx.core.app.NotificationCompat
import androidx.core.app.ServiceCompat
import androidx.core.graphics.drawable.toBitmap
Expand Down Expand Up @@ -230,8 +234,27 @@ class MusicService : MediaLibraryService() {
private var shouldResumeAfterHeadsetReconnect = false
private var lastNoisyPauseRealtimeMs = 0L
private var resumeOnHeadsetReconnectEnabled = false
private var pauseOnVolumeZeroEnabled = false
private var temporaryForegroundStartedInOnCreate = false

// Observes the device's media stream volume and pauses playback when it
// reaches 0, if the user has enabled the "pause on volume zero" preference.
private val systemVolumeObserver by lazy {
object : ContentObserver(Handler(Looper.getMainLooper())) {
override fun onChange(selfChange: Boolean) {
if (!pauseOnVolumeZeroEnabled) return
val streamVolume = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC)
if (streamVolume == 0) {
val player = mediaSession?.player ?: engine.masterPlayer
if (player.isPlaying) {
player.pause()
Timber.tag(TAG).d("pauseOnVolumeZero: paused because system media volume reached 0")
}
}
}
}
}

companion object {
private const val TAG = "MusicService_PixelPlay"
const val NOTIFICATION_ID = 101
Expand Down Expand Up @@ -411,6 +434,7 @@ class MusicService : MediaLibraryService() {
syncLocalListeningStatsFromPlayer(engine.masterPlayer)

engine.masterPlayer.addListener(playerListener)
registerSystemVolumeObserver()

// Handle player swaps (crossfade) to keep MediaSession in sync
engine.setOnPlayerAboutToBeReleasedListener { oldPlayer ->
Expand Down Expand Up @@ -492,6 +516,12 @@ class MusicService : MediaLibraryService() {
}
}

serviceScope.launch {
userPreferencesRepository.pauseOnVolumeZeroFlow.collect { enabled ->
pauseOnVolumeZeroEnabled = enabled
}
}

serviceScope.launch {
userPreferencesRepository.persistentShuffleEnabledFlow.collect { enabled ->
persistentShuffleEnabled = enabled
Expand Down Expand Up @@ -1219,6 +1249,13 @@ class MusicService : MediaLibraryService() {
private val playerListener = object : Player.Listener {
override fun onVolumeChanged(volume: Float) {
replayGainProcessor.onPlayerVolumeChanged(volume)
if (pauseOnVolumeZeroEnabled && volume == 0f) {
val player = mediaSession?.player ?: engine.masterPlayer
if (player.isPlaying) {
player.pause()
Timber.tag(TAG).d("pauseOnVolumeZero: paused playback because volume reached 0")
}
}
}

override fun onIsPlayingChanged(isPlaying: Boolean) {
Expand Down Expand Up @@ -1464,6 +1501,7 @@ class MusicService : MediaLibraryService() {
widgetUpdateManager.cancel()
castSyncCoordinator.stop()
unregisterHeadsetReconnectMonitor()
unregisterSystemVolumeObserver()
wearStatePublisher.clearState()
replayGainProcessor.cancel()

Expand Down Expand Up @@ -1526,6 +1564,18 @@ class MusicService : MediaLibraryService() {
clearHeadsetReconnectResume()
}

private fun registerSystemVolumeObserver() {
contentResolver.registerContentObserver(
Settings.System.CONTENT_URI,
true,
systemVolumeObserver
)
}

private fun unregisterSystemVolumeObserver() {
runCatching { contentResolver.unregisterContentObserver(systemVolumeObserver) }
}

private fun maybeResumeAfterHeadsetReconnect() {
if (!resumeOnHeadsetReconnectEnabled || !shouldResumeAfterHeadsetReconnect) return

Expand Down
Loading
Loading