Skip to content

Commit

Permalink
Merge pull request #615 from naveensingh/performance_improvements
Browse files Browse the repository at this point in the history
Playback performance improvements
  • Loading branch information
tibbi authored Oct 15, 2023
2 parents 23c41bf + eba6185 commit 96c5bdf
Show file tree
Hide file tree
Showing 9 changed files with 169 additions and 155 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import androidx.media3.common.Player
import com.simplemobiletools.commons.helpers.ensureBackgroundThread
import com.simplemobiletools.musicplayer.helpers.PlaybackSetting
import com.simplemobiletools.musicplayer.models.Track
import com.simplemobiletools.musicplayer.models.toMediaItems
import com.simplemobiletools.musicplayer.models.toMediaItemsFast

val Player.isReallyPlaying: Boolean
get() = when (playbackState) {
Expand Down Expand Up @@ -149,7 +149,7 @@ fun Player.prepareUsingTracks(
return
}

val mediaItems = tracks.toMediaItems()
val mediaItems = tracks.toMediaItemsFast()
runOnPlayerThread {
setMediaItems(mediaItems, startIndex, startPositionMs)
playWhenReady = play
Expand All @@ -164,8 +164,10 @@ fun Player.prepareUsingTracks(
* items are added using [addRemainingMediaItems]. This helps prevent delays, especially with large queues, and
* avoids potential issues like [android.app.ForegroundServiceStartNotAllowedException] when starting from background.
*/
var prepareInProgress = false
inline fun Player.maybePreparePlayer(context: Context, crossinline callback: (success: Boolean) -> Unit) {
if (currentMediaItem == null) {
if (!prepareInProgress && currentMediaItem == null) {
prepareInProgress = true
ensureBackgroundThread {
var prepared = false
context.audioHelper.getQueuedTracksLazily { tracks, startIndex, startPositionMs ->
Expand All @@ -179,7 +181,7 @@ inline fun Player.maybePreparePlayer(context: Context, crossinline callback: (su
return@getQueuedTracksLazily
}

addRemainingMediaItems(tracks.toMediaItems(), startIndex)
addRemainingMediaItems(tracks.toMediaItemsFast(), startIndex)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.simplemobiletools.musicplayer.models
import android.content.ContentUris
import android.net.Uri
import android.provider.MediaStore
import androidx.media3.common.MediaItem
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.Index
Expand Down Expand Up @@ -101,3 +102,9 @@ data class Track(
fun ArrayList<Track>.sortSafely(sorting: Int) = sortSafely(Track.getComparator(sorting))

fun Collection<Track>.toMediaItems() = map { it.toMediaItem() }

fun Collection<Track>.toMediaItemsFast() = map {
MediaItem.Builder()
.setMediaId(it.mediaStoreId.toString())
.build()
}
Original file line number Diff line number Diff line change
Expand Up @@ -166,52 +166,44 @@ internal fun PlaybackService.getMediaSessionCallback() = object : MediaLibrarySe
mediaItems: MutableList<MediaItem>,
startIndex: Int,
startPositionMs: Long
) = if (controller.packageName == packageName) {
Futures.immediateFuture(MediaSession.MediaItemsWithStartPosition(mediaItems, startIndex, startPositionMs))
} else {
callWhenSourceReady {
// this is to avoid single items in the queue: https://github.com/androidx/media/issues/156
var queueItems = mediaItems
val startItemId = mediaItems[0].mediaId
val currentItems = mediaItemProvider.getChildren(currentRoot).orEmpty()
): ListenableFuture<MediaSession.MediaItemsWithStartPosition> {
if (controller.packageName == packageName) {
return super.onSetMediaItems(mediaSession, controller, mediaItems, startIndex, startPositionMs)
}

queueItems = if (currentItems.any { it.mediaId == startItemId }) {
currentItems.toMutableList()
} else {
mediaItemProvider.getDefaultQueue()?.toMutableList() ?: queueItems
}
// this is to avoid single items in the queue: https://github.com/androidx/media/issues/156
var queueItems = mediaItems
val startItemId = mediaItems[0].mediaId
val currentItems = mediaItemProvider.getChildren(currentRoot).orEmpty()

val startItemIndex = queueItems.indexOfFirst { it.mediaId == startItemId }
super.onSetMediaItems(mediaSession, controller, queueItems, startItemIndex, startPositionMs).get()
queueItems = if (currentItems.any { it.mediaId == startItemId }) {
currentItems.toMutableList()
} else {
mediaItemProvider.getDefaultQueue()?.toMutableList() ?: queueItems
}

val startItemIndex = queueItems.indexOfFirst { it.mediaId == startItemId }
return super.onSetMediaItems(mediaSession, controller, queueItems, startItemIndex, startPositionMs)
}

override fun onAddMediaItems(
mediaSession: MediaSession,
controller: MediaSession.ControllerInfo,
mediaItems: List<MediaItem>
) = if (controller.packageName == packageName) {
Futures.immediateFuture(mediaItems)
} else {
callWhenSourceReady {
mediaItems.map { mediaItem ->
if (mediaItem.requestMetadata.searchQuery != null) {
getMediaItemFromSearchQuery(mediaItem.requestMetadata.searchQuery!!)
} else {
mediaItemProvider[mediaItem.mediaId] ?: mediaItem
}
): ListenableFuture<List<MediaItem>> {
val items = mediaItems.map { mediaItem ->
if (mediaItem.requestMetadata.searchQuery != null) {
getMediaItemFromSearchQuery(mediaItem.requestMetadata.searchQuery!!)
} else {
mediaItemProvider[mediaItem.mediaId] ?: mediaItem
}
}

return Futures.immediateFuture(items)
}

private fun getMediaItemFromSearchQuery(query: String): MediaItem {
val searchQuery = if (query.startsWith("play ", ignoreCase = true)) {
query.drop(5).lowercase()
} else {
query.lowercase()
}

return mediaItemProvider.getItemFromSearch(searchQuery) ?: mediaItemProvider.getRandomItem()
return mediaItemProvider.getItemFromSearch(query.lowercase()) ?: mediaItemProvider.getRandomItem()
}

private fun reloadContent() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import androidx.media3.common.MediaMetadata
import androidx.media3.common.MediaMetadata.MediaType
import androidx.media3.common.util.UnstableApi
import androidx.media3.session.MediaSession.MediaItemsWithStartPosition
import com.simplemobiletools.commons.helpers.ensureBackgroundThread
import com.google.common.util.concurrent.MoreExecutors
import com.simplemobiletools.musicplayer.R
import com.simplemobiletools.musicplayer.extensions.*
import com.simplemobiletools.musicplayer.helpers.TAB_ALBUMS
Expand All @@ -23,6 +23,7 @@ import com.simplemobiletools.musicplayer.helpers.TAB_PLAYLISTS
import com.simplemobiletools.musicplayer.helpers.TAB_TRACKS
import com.simplemobiletools.musicplayer.models.QueueItem
import com.simplemobiletools.musicplayer.models.toMediaItems
import java.util.concurrent.Executors

private const val STATE_CREATED = 1
private const val STATE_INITIALIZING = 2
Expand All @@ -42,6 +43,9 @@ private const val SMP_GENRES_ROOT_ID = "__GENRES__"
*/
@UnstableApi
internal class MediaItemProvider(private val context: Context) {
private val executor by lazy {
MoreExecutors.listeningDecorator(Executors.newSingleThreadExecutor())
}

inner class MediaItemNode(val item: MediaItem) {
private val children: MutableList<MediaItem> = ArrayList()
Expand Down Expand Up @@ -89,7 +93,16 @@ internal class MediaItemProvider(private val context: Context) {
}
}

operator fun get(mediaId: String) = getNode(mediaId)?.item
operator fun get(mediaId: String): MediaItem? {
val mediaItem = getNode(mediaId)?.item
if (mediaItem == null) {
// assume it's a track
val mediaStoreId = mediaId.toLongOrNull() ?: return null
return audioHelper.getTrack(mediaStoreId)?.toMediaItem()
}

return mediaItem
}

fun getRootItem() = get(SMP_ROOT_ID)!!

Expand Down Expand Up @@ -145,7 +158,7 @@ internal class MediaItemProvider(private val context: Context) {
return
}

ensureBackgroundThread {
executor.execute {
val trackId = current.mediaId.toLong()
val queueItems = mediaItems.mapIndexed { index, mediaItem ->
QueueItem(trackId = mediaItem.mediaId.toLong(), trackOrder = index, isCurrent = false, lastPosition = 0)
Expand All @@ -157,8 +170,7 @@ internal class MediaItemProvider(private val context: Context) {

fun reload() {
state = STATE_INITIALIZING

ensureBackgroundThread {
executor.execute {
buildRoot()

try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ private const val SKIP_SILENCE_THRESHOLD_LEVEL = 16.toShort()
@UnstableApi
class AudioOnlyRenderersFactory(context: Context) : DefaultRenderersFactory(context) {

override fun buildAudioSink(context: Context, enableFloatOutput: Boolean, enableAudioTrackPlaybackParams: Boolean, enableOffload: Boolean): AudioSink? {
override fun buildAudioSink(context: Context, enableFloatOutput: Boolean, enableAudioTrackPlaybackParams: Boolean): AudioSink {
val silenceSkippingAudioProcessor = SilenceSkippingAudioProcessor(
SKIP_SILENCE_MINIMUM_DURATION_US,
DEFAULT_PADDING_SILENCE_US,
Expand All @@ -32,13 +32,6 @@ class AudioOnlyRenderersFactory(context: Context) : DefaultRenderersFactory(cont
return DefaultAudioSink.Builder(context)
.setEnableFloatOutput(enableFloatOutput)
.setEnableAudioTrackPlaybackParams(enableAudioTrackPlaybackParams)
.setOffloadMode(
if (enableOffload) {
DefaultAudioSink.OFFLOAD_MODE_ENABLED_GAPLESS_REQUIRED
} else {
DefaultAudioSink.OFFLOAD_MODE_DISABLED
}
)
.setAudioProcessorChain(
DefaultAudioSink.DefaultAudioProcessorChain(
arrayOf(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,19 @@ import androidx.media3.exoplayer.ExoPlayer
import androidx.media3.exoplayer.source.ShuffleOrder.DefaultShuffleOrder
import com.simplemobiletools.musicplayer.extensions.*
import com.simplemobiletools.musicplayer.inlines.indexOfFirstOrNull
import kotlinx.coroutines.*

private const val DEFAULT_SHUFFLE_ORDER_SEED = 42L

@UnstableApi
class SimpleMusicPlayer(private val exoPlayer: ExoPlayer) : ForwardingPlayer(exoPlayer) {

private var seekToNextCount = 0
private var seekToPreviousCount = 0

private val scope = CoroutineScope(Dispatchers.Default)
private var seekJob: Job? = null

/**
* The default implementation only advertises the seek to next and previous item in the case
* that it's not the first or last track. We manually advertise that these
Expand Down Expand Up @@ -53,28 +60,32 @@ class SimpleMusicPlayer(private val exoPlayer: ExoPlayer) : ForwardingPlayer(exo
override fun seekToNext() {
play()
if (!maybeForceNext()) {
super.seekToNext()
seekToNextCount += 1
seekWithDelay()
}
}

override fun seekToPrevious() {
play()
if (!maybeForcePrevious()) {
super.seekToPrevious()
seekToPreviousCount += 1
seekWithDelay()
}
}

override fun seekToNextMediaItem() {
play()
if (!maybeForceNext()) {
super.seekToNextMediaItem()
seekToNextCount += 1
seekWithDelay()
}
}

override fun seekToPreviousMediaItem() {
play()
if (!maybeForcePrevious()) {
super.seekToPreviousMediaItem()
seekToPreviousCount += 1
seekWithDelay()
}
}

Expand Down Expand Up @@ -122,4 +133,36 @@ class SimpleMusicPlayer(private val exoPlayer: ExoPlayer) : ForwardingPlayer(exo
exoPlayer.setShuffleOrder(DefaultShuffleOrder(shuffledIndices.toIntArray(), DEFAULT_SHUFFLE_ORDER_SEED))
}
}

/**
* This is here so the player can quickly seek next/previous without doing too much work.
* It probably won't be needed once https://github.com/androidx/media/issues/81 is resolved.
*/
private fun seekWithDelay() {
seekJob?.cancel()
seekJob = scope.launch {
delay(timeMillis = 400)
if (seekToNextCount > 0 || seekToPreviousCount > 0) {
runOnPlayerThread {
if (currentMediaItem != null) {
if (seekToNextCount > 0) {
seekTo(rotateIndex(currentMediaItemIndex + seekToNextCount), 0)
}

if (seekToPreviousCount > 0) {
seekTo(rotateIndex(currentMediaItemIndex - seekToPreviousCount), 0)
}

seekToNextCount = 0
seekToPreviousCount = 0
}
}
}
}
}

private fun rotateIndex(index: Int): Int {
val count = mediaItemCount
return (index % count + count) % count
}
}
Loading

0 comments on commit 96c5bdf

Please sign in to comment.