Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -973,9 +973,7 @@ class MusicService : MediaLibraryService() {

private val playerListener = object : Player.Listener {
override fun onVolumeChanged(volume: Float) {
if (engine.isTransitionRunning()) {
return
}
if (engine.isTransitionRunning()) return
val expectedVolume = expectedReplayGainVolume
if (expectedVolume != null && abs(expectedVolume - volume) < 0.001f) {
expectedReplayGainVolume = null
Expand Down Expand Up @@ -1006,12 +1004,15 @@ class MusicService : MediaLibraryService() {
}
else -> clearHeadsetReconnectResume()
}
requestWidgetFullUpdate(force = true)
mediaSession?.let { refreshMediaSessionUi(it) }
schedulePlaybackSnapshotPersist()
}

override fun onAvailableCommandsChanged(availableCommands: Player.Commands) {
val canSeek = availableCommands.contains(Player.COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM)
val player = engine.masterPlayer
Timber.tag(TAG).w("onAvailableCommandsChanged. Can Seek Command? $canSeek. IsSeekable? ${player.isCurrentMediaItemSeekable}. Duration: ${player.duration}")
val canSeek = availableCommands.contains(Player.COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM)
val player = engine.masterPlayer
Timber.tag(TAG).w("onAvailableCommandsChanged. Can Seek Command? $canSeek. IsSeekable? ${player.isCurrentMediaItemSeekable}. Duration: ${player.duration}")
}

override fun onPlaybackStateChanged(playbackState: Int) {
Expand All @@ -1028,7 +1029,19 @@ class MusicService : MediaLibraryService() {
schedulePlaybackSnapshotPersist(immediate = timeline.isEmpty)
}

override fun onMediaItemTransition(item: MediaItem?, reason: Int) {
override fun onPositionDiscontinuity(
oldPosition: Player.PositionInfo,
newPosition: Player.PositionInfo,
reason: Int
) {
if (reason == Player.DISCONTINUITY_REASON_AUTO_TRANSITION ||
reason == Player.DISCONTINUITY_REASON_SEEK
) {
applyReplayGain(mediaSession?.player?.currentMediaItem)
}
}

override fun onMediaItemTransition(mediaItem: MediaItem?, reason: Int) {
val eotTargetSongId = endOfTrackTimerSongId
if (!eotTargetSongId.isNullOrBlank()) {
if (reason == Player.MEDIA_ITEM_TRANSITION_REASON_AUTO) {
Expand All @@ -1045,11 +1058,12 @@ class MusicService : MediaLibraryService() {
engine.masterPlayer.pause()
Timber.tag(TAG).d("Paused playback at end of track from Wear timer")
}
} else if (item?.mediaId != eotTargetSongId) {
} else if (mediaItem?.mediaId != eotTargetSongId) {
endOfTrackTimerSongId = null
Timber.tag(TAG).d("Cleared end-of-track timer after manual track change")
}
}
applyReplayGain(mediaSession?.player?.currentMediaItem)
requestWidgetAndWearRefreshWithFollowUp()
mediaSession?.let { refreshMediaSessionUiWithFollowUp(it) }
schedulePlaybackSnapshotPersist()
Expand Down Expand Up @@ -1088,7 +1102,6 @@ class MusicService : MediaLibraryService() {
* Reads RG tags from the file and adjusts player.volume accordingly.
*/
private fun applyReplayGain(mediaItem: MediaItem?) {
val player = engine.masterPlayer
replayGainJob?.cancel()
replayGainRequestToken += 1
val requestToken = replayGainRequestToken
Expand All @@ -1100,7 +1113,7 @@ class MusicService : MediaLibraryService() {
if (!replayGainEnabled) {
pendingReplayGainVolume = null
if (!engine.isTransitionRunning()) {
setPlayerVolume(player, userSelectedVolume)
setPlayerVolume(engine.masterPlayer, userSelectedVolume)
}
return
}
Expand All @@ -1112,7 +1125,7 @@ class MusicService : MediaLibraryService() {
if (filePath.isNullOrBlank()) {
Timber.tag(TAG).d("ReplayGain: No file path for track, keeping user-selected volume")
if (!engine.isTransitionRunning()) {
setPlayerVolume(player, userSelectedVolume)
setPlayerVolume(engine.masterPlayer, userSelectedVolume)
}
return
}
Expand Down Expand Up @@ -1140,14 +1153,18 @@ class MusicService : MediaLibraryService() {
)

if (engine.isTransitionRunning()) {
// Store for application after transition completes
// Store for application after transition completes.
// If the transition was interrupted (e.g. user skipped during crossfade),
// onTransitionFinished() may never fire for this pending value — so we
// also apply it immediately to masterPlayer so volume is never lost.
pendingReplayGainVolume = volume
Timber.tag(TAG).d("ReplayGain: Stored pending volume=%.2f for %s (transition running)",
setPlayerVolume(engine.masterPlayer, volume)
Timber.tag(TAG).d("ReplayGain: Applied + stored pending volume=%.2f for %s (transition running)",
volume, mediaItem.mediaMetadata.title
)
} else {
pendingReplayGainVolume = null
setPlayerVolume(player, volume)
setPlayerVolume(engine.masterPlayer, volume)
Timber.tag(TAG).d("ReplayGain: Applied volume=%.2f for %s",
volume, mediaItem.mediaMetadata.title
)
Expand All @@ -1173,8 +1190,11 @@ class MusicService : MediaLibraryService() {
}

if (pending != null) {
// pending was already applied to masterPlayer during the transition to ensure
// volume is never lost if the transition was interrupted (e.g. user skipped).
// Re-applying here is a no-op in volume terms but confirms the final state.
setPlayerVolume(player, pending)
Timber.tag(TAG).d("ReplayGain: Transition finished, applied pending volume=%.2f", pending)
Timber.tag(TAG).d("ReplayGain: Transition finished, confirmed pending volume=%.2f", pending)
} else {
// No pending volume was computed during transition, trigger full computation
applyReplayGain(mediaSession?.player?.currentMediaItem)
Expand Down Expand Up @@ -2842,6 +2862,13 @@ class MusicService : MediaLibraryService() {
}
}

/**
* Bridges a suspend block into a [ListenableFuture] for Media3 callback methods.
*/
/**
* Bridges a suspend block into a [ListenableFuture] for Media3 callback methods.
*/

/**
* Bridges a suspend block into a [ListenableFuture] for Media3 callback methods.
*/
Expand All @@ -2856,5 +2883,4 @@ class MusicService : MediaLibraryService() {
}
return future
}

}