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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]
### Fixed
- Fixed another crash when clearing app from recents ([#298])

## [1.5.1] - 2025-11-05
### Fixed
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,7 @@ import android.provider.MediaStore
import android.util.Size
import android.view.GestureDetector
import android.view.MotionEvent
import android.view.View
import android.widget.SeekBar
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.graphics.drawable.toDrawable
import androidx.core.graphics.scale
import androidx.core.os.postDelayed
Expand All @@ -22,11 +20,37 @@ import androidx.media3.common.MediaItem
import com.bumptech.glide.load.resource.bitmap.CenterCrop
import com.bumptech.glide.load.resource.bitmap.RoundedCorners
import com.bumptech.glide.request.RequestOptions
import org.fossify.commons.extensions.*
import org.fossify.commons.extensions.applyColorFilter
import org.fossify.commons.extensions.beGone
import org.fossify.commons.extensions.beInvisibleIf
import org.fossify.commons.extensions.beVisible
import org.fossify.commons.extensions.copyToClipboard
import org.fossify.commons.extensions.getColoredDrawableWithColor
import org.fossify.commons.extensions.getFormattedDuration
import org.fossify.commons.extensions.getProperBackgroundColor
import org.fossify.commons.extensions.getProperPrimaryColor
import org.fossify.commons.extensions.getProperTextColor
import org.fossify.commons.extensions.realScreenSize
import org.fossify.commons.extensions.toast
import org.fossify.commons.extensions.updateTextColors
import org.fossify.commons.extensions.value
import org.fossify.commons.extensions.viewBinding
import org.fossify.commons.helpers.MEDIUM_ALPHA
import org.fossify.commons.helpers.mydebug
import org.fossify.musicplayer.R
import org.fossify.musicplayer.databinding.ActivityTrackBinding
import org.fossify.musicplayer.extensions.*
import org.fossify.musicplayer.extensions.config
import org.fossify.musicplayer.extensions.getCoverArtHeight
import org.fossify.musicplayer.extensions.getPlaybackSetting
import org.fossify.musicplayer.extensions.getTrackCoverArt
import org.fossify.musicplayer.extensions.getTrackFromUri
import org.fossify.musicplayer.extensions.isReallyPlaying
import org.fossify.musicplayer.extensions.loadGlideResource
import org.fossify.musicplayer.extensions.nextMediaItem
import org.fossify.musicplayer.extensions.sendCommand
import org.fossify.musicplayer.extensions.setRepeatMode
import org.fossify.musicplayer.extensions.toTrack
import org.fossify.musicplayer.extensions.updatePlayPauseIcon
import org.fossify.musicplayer.fragments.PlaybackSpeedFragment
import org.fossify.musicplayer.helpers.PlaybackSetting
import org.fossify.musicplayer.helpers.SEEK_INTERVAL_S
Expand Down Expand Up @@ -153,9 +177,9 @@ class TrackActivity : SimpleControllerActivity(), PlaybackSpeedListener {

private fun setupButtons() = binding.apply {
activityTrackToggleShuffle.setOnClickListener { withPlayer { toggleShuffle() } }
activityTrackPrevious.setOnClickListener { withPlayer { forceSeekToPrevious() } }
activityTrackPrevious.setOnClickListener { withPlayer { seekToPrevious() } }
activityTrackPlayPause.setOnClickListener { togglePlayback() }
activityTrackNext.setOnClickListener { withPlayer { forceSeekToNext() } }
activityTrackNext.setOnClickListener { withPlayer { seekToNext() } }
activityTrackProgressCurrent.setOnClickListener { seekBack() }
activityTrackProgressMax.setOnClickListener { seekForward() }
activityTrackPlaybackSetting.setOnClickListener { togglePlaybackSetting() }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,18 +99,6 @@ fun Player.setRepeatMode(playbackSetting: PlaybackSetting) {
}
}

fun Player.forceSeekToNext() {
if (!maybeForceNext()) {
seekToNext()
}
}

fun Player.forceSeekToPrevious() {
if (!maybeForcePrevious()) {
seekToPrevious()
}
}

/**
* Force seek to the next media item regardless of the current [Player.RepeatMode]. Returns true on success.
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package org.fossify.musicplayer.playback

import android.content.Intent
import android.os.Handler
import android.os.HandlerThread
import android.os.Looper
import androidx.annotation.OptIn
import androidx.core.os.postDelayed
Expand All @@ -16,6 +14,7 @@ import org.fossify.commons.extensions.hasPermission
import org.fossify.commons.extensions.showErrorToast
import org.fossify.musicplayer.extensions.isReallyPlaying
import org.fossify.musicplayer.extensions.nextMediaItem
import org.fossify.musicplayer.extensions.runOnPlayerThread
import org.fossify.musicplayer.helpers.NotificationHelper
import org.fossify.musicplayer.helpers.getPermissionToRequest
import org.fossify.musicplayer.playback.library.MediaItemProvider
Expand All @@ -25,9 +24,7 @@ import org.fossify.musicplayer.playback.player.initializeSessionAndPlayer
@OptIn(UnstableApi::class)
class PlaybackService : MediaLibraryService(), MediaSessionService.Listener {
internal lateinit var player: SimpleMusicPlayer
internal lateinit var playerThread: HandlerThread
internal lateinit var playerListener: Player.Listener
internal lateinit var playerHandler: Handler
internal lateinit var mediaSession: MediaLibrarySession
internal lateinit var mediaItemProvider: MediaItemProvider

Expand Down Expand Up @@ -77,7 +74,7 @@ class PlaybackService : MediaLibraryService(), MediaSessionService.Listener {
}

internal fun withPlayer(callback: SimpleMusicPlayer.() -> Unit) {
playerHandler.post { callback(player) }
player.runOnPlayerThread { callback(this) }
}

private fun showNoPermissionNotification() {
Expand All @@ -102,12 +99,6 @@ class PlaybackService : MediaLibraryService(), MediaSessionService.Listener {
// todo: show a notification instead.
}

override fun onTaskRemoved(rootIntent: Intent?) {
playerHandler.post {
super.onTaskRemoved(rootIntent)
}
}

companion object {
// Initializing a media controller might take a noticeable amount of time thus we expose current playback info here to keep things as quick as possible.
var isPlaying: Boolean = false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ package org.fossify.musicplayer.playback.player
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.os.Handler
import android.os.HandlerThread
import androidx.media3.common.AudioAttributes
import androidx.media3.common.C
import androidx.media3.common.util.UnstableApi
Expand All @@ -24,22 +22,19 @@ import org.fossify.musicplayer.playback.SimpleEqualizer
import org.fossify.musicplayer.playback.getCustomLayout
import org.fossify.musicplayer.playback.getMediaSessionCallback

private const val PLAYER_THREAD = "PlayerThread"

/**
* Initializes player and media session.
*
* All player operations are handled on a separate handler thread to avoid slowing down the main thread.
* See https://developer.android.com/guide/topics/media/exoplayer/hello-world#a-note-on-threading for more info.
*/
internal fun PlaybackService.initializeSessionAndPlayer(handleAudioFocus: Boolean, handleAudioBecomingNoisy: Boolean) {
playerThread = HandlerThread(PLAYER_THREAD).also { it.start() }
playerHandler = Handler(playerThread.looper)
internal fun PlaybackService.initializeSessionAndPlayer(
handleAudioFocus: Boolean,
handleAudioBecomingNoisy: Boolean
) {
player = initializePlayer(handleAudioFocus, handleAudioBecomingNoisy)
playerListener = getPlayerListener()
mediaSession = MediaLibraryService.MediaLibrarySession.Builder(this, player, getMediaSessionCallback())
.setSessionActivity(getSessionActivityIntent())
.build()
mediaSession =
MediaLibraryService.MediaLibrarySession.Builder(this, player, getMediaSessionCallback())
.setSessionActivity(getSessionActivityIntent())
.build()

withPlayer {
addListener(playerListener)
Expand All @@ -51,7 +46,10 @@ internal fun PlaybackService.initializeSessionAndPlayer(handleAudioFocus: Boolea
}
}

private fun PlaybackService.initializePlayer(handleAudioFocus: Boolean, handleAudioBecomingNoisy: Boolean): SimpleMusicPlayer {
private fun PlaybackService.initializePlayer(
handleAudioFocus: Boolean,
handleAudioBecomingNoisy: Boolean
): SimpleMusicPlayer {
val renderersFactory = AudioOnlyRenderersFactory(context = this)
return SimpleMusicPlayer(
ExoPlayer.Builder(this, renderersFactory)
Expand All @@ -66,7 +64,6 @@ private fun PlaybackService.initializePlayer(handleAudioFocus: Boolean, handleAu
)
.setSeekBackIncrementMs(SEEK_INTERVAL_MS)
.setSeekForwardIncrementMs(SEEK_INTERVAL_MS)
.setLooper(playerThread.looper)
.build()
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,17 @@ import androidx.media3.common.Player
import androidx.media3.common.util.UnstableApi
import androidx.media3.exoplayer.ExoPlayer
import androidx.media3.exoplayer.source.ShuffleOrder.DefaultShuffleOrder
import kotlinx.coroutines.*
import org.fossify.musicplayer.extensions.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import org.fossify.musicplayer.extensions.currentMediaItems
import org.fossify.musicplayer.extensions.maybeForceNext
import org.fossify.musicplayer.extensions.maybeForcePrevious
import org.fossify.musicplayer.extensions.move
import org.fossify.musicplayer.extensions.runOnPlayerThread
import org.fossify.musicplayer.extensions.shuffledMediaItemsIndices
import org.fossify.musicplayer.inlines.indexOfFirstOrNull

private const val DEFAULT_SHUFFLE_ORDER_SEED = 42L
Expand All @@ -30,10 +39,10 @@ class SimpleMusicPlayer(private val exoPlayer: ExoPlayer) : ForwardingPlayer(exo
return super.getAvailableCommands()
.buildUpon()
.addAll(
Player.COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM,
Player.COMMAND_SEEK_TO_PREVIOUS,
Player.COMMAND_SEEK_TO_NEXT,
Player.COMMAND_SEEK_TO_NEXT_MEDIA_ITEM,
COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM,
COMMAND_SEEK_TO_PREVIOUS,
COMMAND_SEEK_TO_NEXT,
COMMAND_SEEK_TO_NEXT_MEDIA_ITEM,
)
.build()
}
Expand Down Expand Up @@ -106,7 +115,7 @@ class SimpleMusicPlayer(private val exoPlayer: ExoPlayer) : ForwardingPlayer(exo
@Deprecated("Should be rewritten when https://github.com/androidx/media/issues/325 is implemented.")
fun setShuffleIndices(indices: IntArray) {
val shuffleOrder = DefaultShuffleOrder(indices, DEFAULT_SHUFFLE_ORDER_SEED)
exoPlayer.setShuffleOrder(shuffleOrder)
exoPlayer.shuffleOrder = shuffleOrder
}

@Deprecated("Should be rewritten when https://github.com/androidx/media/issues/325 is implemented.")
Expand All @@ -132,7 +141,10 @@ class SimpleMusicPlayer(private val exoPlayer: ExoPlayer) : ForwardingPlayer(exo
val shuffledCurrentIndex = shuffledIndices.indexOf(itemIndex)
val shuffledNewIndex = shuffledIndices.indexOf(nextMediaItemIndex)
shuffledIndices.move(currentIndex = shuffledCurrentIndex, newIndex = shuffledNewIndex)
exoPlayer.setShuffleOrder(DefaultShuffleOrder(shuffledIndices.toIntArray(), DEFAULT_SHUFFLE_ORDER_SEED))
exoPlayer.shuffleOrder = DefaultShuffleOrder(
shuffledIndices.toIntArray(),
DEFAULT_SHUFFLE_ORDER_SEED
)
}
}

Expand Down
Loading