Skip to content
Draft
Show file tree
Hide file tree
Changes from 10 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
@@ -1,4 +1,4 @@
/*

Check warning on line 1 in app/src/main/java/org/akanework/gramophone/logic/GramophoneExtensions.kt

View check run for this annotation

CodeScene Delta Analysis / CodeScene Code Health Review (beta)

❌ Getting worse: Global Conditionals

The global code outside of functions increases in cyclomatic complexity from 31 to 37, threshold = 10. The code has become too complex as it contains many conditional statements (e.g. if, for, while) across its implementation, leading to lower code health. Avoid adding more.

Check warning on line 1 in app/src/main/java/org/akanework/gramophone/logic/GramophoneExtensions.kt

View check run for this annotation

CodeScene Delta Analysis / CodeScene Code Health Review (beta)

❌ New issue: Primitive Obsession

In this module, 50.0% of all function arguments are primitive types, threshold = 30.0%. The functions in this file have too many primitive types (e.g. int, double, float) in their function argument lists. Using many primitive types lead to the code smell Primitive Obsession. Avoid adding more primitive arguments.
* Copyright (C) 2024 Akane Foundation
*
* Gramophone is free software: you can redistribute it and/or modify
Expand Down Expand Up @@ -59,12 +59,14 @@
import androidx.core.view.children
import androidx.core.view.updateLayoutParams
import androidx.core.view.updateMargins
import androidx.media3.common.BundleListRetriever
import androidx.media3.common.C
import androidx.media3.common.Format
import androidx.media3.common.MediaItem
import androidx.media3.common.Player
import androidx.media3.common.Tracks
import androidx.media3.common.util.Log
import androidx.media3.exoplayer.source.ShuffleOrder
import androidx.media3.session.MediaController
import androidx.media3.session.SessionCommand
import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat
Expand All @@ -75,19 +77,29 @@
import org.akanework.gramophone.R
import org.akanework.gramophone.logic.GramophonePlaybackService.Companion.SERVICE_GET_AUDIO_FORMAT
import org.akanework.gramophone.logic.GramophonePlaybackService.Companion.SERVICE_GET_LYRICS
import org.akanework.gramophone.logic.GramophonePlaybackService.Companion.SERVICE_QB_DEL
import org.akanework.gramophone.logic.GramophonePlaybackService.Companion.SERVICE_QB_ENQUEUE
import org.akanework.gramophone.logic.GramophonePlaybackService.Companion.SERVICE_QB_GET_INACTIVE
import org.akanework.gramophone.logic.GramophonePlaybackService.Companion.SERVICE_QB_GET_QUEUE
import org.akanework.gramophone.logic.GramophonePlaybackService.Companion.SERVICE_QB_LOAD_QUEUE
import org.akanework.gramophone.logic.GramophonePlaybackService.Companion.SERVICE_QB_PIN_QUEUE
import org.akanework.gramophone.logic.GramophonePlaybackService.Companion.SERVICE_QB_REORDER
import org.akanework.gramophone.logic.GramophonePlaybackService.Companion.SERVICE_QB_UNPIN_QUEUE
import org.akanework.gramophone.logic.GramophonePlaybackService.Companion.SERVICE_QUERY_TIMER
import org.akanework.gramophone.logic.GramophonePlaybackService.Companion.SERVICE_SET_TIMER
import org.akanework.gramophone.logic.utils.AfFormatInfo
import org.akanework.gramophone.logic.utils.AudioFormatDetector
import org.akanework.gramophone.logic.utils.AudioTrackInfo
import org.akanework.gramophone.logic.utils.BtCodecInfo
import org.akanework.gramophone.logic.utils.CalculationUtils
import org.akanework.gramophone.logic.utils.CircularShuffleOrder
import org.akanework.gramophone.logic.utils.ReplayGainUtil
import org.akanework.gramophone.logic.utils.SemanticLyrics
import org.akanework.gramophone.ui.MainActivity
import org.jetbrains.annotations.Contract
import java.io.File
import java.io.FileInputStream
import java.util.LinkedList
import java.util.Locale
import kotlin.math.max

Expand Down Expand Up @@ -337,6 +349,149 @@
)
}

fun MediaController.getInactiveQueues(): List<MultiQueueObject> =
sendCustomCommand(
SessionCommand(SERVICE_QB_GET_INACTIVE, Bundle.EMPTY),
Bundle.EMPTY
).get().extras.run {
val binder = getBinder("allQueues")!!
BundleListRetriever.getList(binder).map {
MultiQueueObject.fromBundle(it)
}
}

fun MediaController.getQueue(index: Int = C.INDEX_UNSET): MultiQueueObject? =
sendCustomCommand(
SessionCommand(SERVICE_QB_GET_QUEUE, Bundle.EMPTY).apply {
customExtras.putInt("index", index)
}, Bundle.EMPTY
).get().extras.run {
val binder = getBinder("allQueues")!!
BundleListRetriever.getList(binder).map {
MultiQueueObject.fromBundle(it)
}.firstOrNull()
}


fun shuffledItems(
items: List<MediaItem>,
order: ShuffleOrder
): List<MediaItem> {
val result = mutableListOf<MediaItem>()

var i = order.firstIndex
while (i != C.INDEX_UNSET) {
result.add(items[i])
i = order.getNextIndex(i)
}

return result
}

fun shuffledIndices(order: ShuffleOrder): MutableList<Int> {
val result = mutableListOf<Int>()

var i = order.firstIndex
while (i != C.INDEX_UNSET) {
result.add(i)
i = order.getNextIndex(i)
}

return result
}

fun MediaController.getQueueForUi(index: Int = -1): Pair<MutableList<Int>, MultiQueueObject>? {
if (index == -1) {
return null
}
return sendCustomCommand(
SessionCommand(SERVICE_QB_GET_QUEUE, Bundle.EMPTY).apply {
customExtras.putInt("index", index)
}, Bundle.EMPTY
).get().extras.run {
val binder = getBinder("allQueues")!!
BundleListRetriever.getList(binder).map {
val mq = MultiQueueObject.fromBundle(it)
val indexes: MutableList<Int> = if (mq.shuffleOrder == null) {
(0 until mq.getSize()).toMutableList()
} else {
getIntArray("shuffleIndexes")!!.toMutableList()
}

Pair(indexes, mq)
}.firstOrNull()
}
}

fun MediaController.loadQueue(index: Int) {
sendCustomCommand(
SessionCommand(SERVICE_QB_LOAD_QUEUE, Bundle.EMPTY).apply {
customExtras.putInt("index", index)
}, Bundle.EMPTY
)
}

fun MediaController.pinQueue(index: Int) {
sendCustomCommand(
SessionCommand(SERVICE_QB_PIN_QUEUE, Bundle.EMPTY).apply {
customExtras.putInt("index", index)
}, Bundle.EMPTY
)
}


fun MediaController.unQueue(index: Int) {
sendCustomCommand(
SessionCommand(SERVICE_QB_UNPIN_QUEUE, Bundle.EMPTY).apply {
customExtras.putInt("index", index)
}, Bundle.EMPTY
)
}


fun MediaController.deleteQueue(index: Int): Boolean =
sendCustomCommand(
SessionCommand(SERVICE_QB_DEL, Bundle.EMPTY).apply {
customExtras.putInt("index", index)
}, Bundle.EMPTY
).get().extras.run {
if (containsKey("status"))
getBoolean("status")
else throw IllegalArgumentException("expected status to be set")
}

fun MediaController.reorderQueue(from: Int, to: Int): Boolean =
sendCustomCommand(
SessionCommand(SERVICE_QB_REORDER, Bundle.EMPTY).apply {
customExtras.putInt("from", from)
customExtras.putInt("to", to)
}, Bundle.EMPTY
).get().extras.run {
if (containsKey("status"))
getBoolean("status")
else throw IllegalArgumentException("expected status to be set")
}

/*
// TODO: shuffle and repeat mode
fun MediaController.playQueue(
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you are doing this at the wrong level, Android AUto for example will just not call your SERVICE_QB_ENQUEUE, it will keep doing setMediaItems(). Instead a player wrapper should give old queue to queueboard before executing setMediaItems()

title: String?,
mediaList: List<MediaItem>,
mediaItemIndex: Int,
isOriginal: Boolean
) {
sendCustomCommand(
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

commented out much?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

qb doesn't own the current queue. Yes, a full queue was added into qb via addqueue, but all that data (with the exception of the title) remains untouched and isnt actually used anywhere. All the old data is overwritten anyways with the latest from the player when setmediaitems is called again, so there is no spaghetti required.

I see how that commitQueue nonsense is redundant, so I'll change that. I'll null out the info in addQueue to prevent that info from creeping in in the future, and also call super.

Now (fe511a1) it works like this for when setmediaitems is called: handleSetMediaItems -> addqueue adds skeleton queue to qb, -> commitqueue facilitates the active/inactive swap within qb (without its own setmediaitems) -> super.handleSetMediaItems

And then for user initiated queue swaps: commitqueue facilitates the active/inactive swap within qb (with realSetMediaItems) -> super.handleSetMediaItems. You cant call setmediaitems again

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remains untouched and isnt actually used anywhere

should be pretty obvious why thats a bad idea, you can just save the current queue's title in extra member field or something (note: i did not check latest commit)

And then for user initiated queue swaps: commitqueue facilitates the active/inactive swap within qb (with realSetMediaItems) -> super.handleSetMediaItems. You cant call setmediaitems again

Yes, that sounds reasonable

SessionCommand(SERVICE_QB_ENQUEUE, Bundle.EMPTY).apply {
customExtras.putString("title", title)
customExtras.putInt("mediaItemIndex", mediaItemIndex)
customExtras.putBoolean("isOriginal", isOriginal)
val binder = BundleListRetriever(mediaList.map { it.toBundleIncludeLocalConfiguration() })
customExtras.putBinder("mediaList", binder)
}, Bundle.EMPTY
)
}
*/

fun Tracks.getFirstSelectedTrackFormatByType(type: @C.TrackType Int): Format? {
for (i in groups) {
if (i.type == type) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
import androidx.core.content.IntentCompat
import androidx.lifecycle.lifecycleScope
import androidx.media3.common.AudioAttributes
import androidx.media3.common.BundleListRetriever
import androidx.media3.common.C
import androidx.media3.common.DeviceInfo
import androidx.media3.common.Format
Expand Down Expand Up @@ -141,11 +142,22 @@
private const val PENDING_INTENT_SESSION_ID = 0
const val PENDING_INTENT_NOTIFY_ID = 1
const val PENDING_INTENT_WIDGET_ID = 2

const val SERVICE_SET_TIMER = "set_timer"
const val SERVICE_QUERY_TIMER = "query_timer"
const val SERVICE_GET_AUDIO_FORMAT = "get_audio_format"
const val SERVICE_GET_LYRICS = "get_lyrics"
const val SERVICE_TIMER_CHANGED = "changed_timer"

const val SERVICE_QB_GET_INACTIVE = "qb_get_all"
const val SERVICE_QB_LOAD_QUEUE = "qb_load"
const val SERVICE_QB_GET_QUEUE = "qb_get_curr_queue"
const val SERVICE_QB_DEL = "qb_delete"
const val SERVICE_QB_REORDER = "qb_reorder"
const val SERVICE_QB_ENQUEUE = "qb_enqueue"
const val SERVICE_QB_PIN_QUEUE ="qb_pin_queue"
const val SERVICE_QB_UNPIN_QUEUE ="qb_unpin_queue"

var instanceForWidgetAndLyricsOnly: GramophonePlaybackService? = null
}

Expand All @@ -156,6 +168,7 @@
val endedWorkaroundPlayer
get() = mediaSession?.player as EndedWorkaroundPlayer?
private var controller: MediaBrowser? = null
lateinit var qb: QueueBoard
private val sendLyrics = Runnable { scheduleSendingLyrics(false) }
var lyrics: SemanticLyrics? = null
private set
Expand Down Expand Up @@ -261,288 +274,294 @@
handler = Handler(Looper.getMainLooper())
nm = NotificationManagerCompat.from(this)
prefs = PreferenceManager.getDefaultSharedPreferences(applicationContext)
qb = QueueBoard(this)
setListener(this)
setMediaNotificationProvider(
MeiZuLyricsMediaNotificationProvider(this) { lastSentHighlightedLyric }
)
setForegroundServiceTimeoutMs(120000)
setShowNotificationForEmptyPlayer(SHOW_NOTIFICATION_FOR_EMPTY_PLAYER_AFTER_STOP_OR_ERROR)
if (mayThrowForegroundServiceStartNotAllowed()
|| mayThrowForegroundServiceStartNotAllowedMiui()
) {
nm.createNotificationChannel(
NotificationChannelCompat.Builder(
NOTIFY_CHANNEL_ID, NotificationManagerCompat.IMPORTANCE_HIGH
).apply {
setName(getString(R.string.fgs_failed_channel))
setVibrationEnabled(true)
setVibrationPattern(longArrayOf(0L, 200L))
setLightsEnabled(false)
setShowBadge(false)
setSound(null, null)
}.build()
)
} else if (nm.getNotificationChannel(NOTIFY_CHANNEL_ID) != null) {
// for people who upgraded from S/S_V2 to newer version
nm.deleteNotificationChannel(NOTIFY_CHANNEL_ID)
}

customCommands =
listOf(
CommandButton.Builder(CommandButton.ICON_SHUFFLE_OFF) // shuffle currently disabled, click will enable
.setDisplayName(getString(R.string.shuffle))
.setPlayerCommand(Player.COMMAND_SET_SHUFFLE_MODE, true)
.build(),
CommandButton.Builder(CommandButton.ICON_SHUFFLE_ON) // shuffle currently enabled, click will disable
.setDisplayName(getString(R.string.shuffle))
.setPlayerCommand(Player.COMMAND_SET_SHUFFLE_MODE, false)
.build(),
CommandButton.Builder(CommandButton.ICON_REPEAT_OFF) // repeat currently disabled, click will repeat all
.setDisplayName(getString(R.string.repeat_mode))
.setPlayerCommand(Player.COMMAND_SET_REPEAT_MODE, Player.REPEAT_MODE_ALL)
.build(),
CommandButton.Builder(CommandButton.ICON_REPEAT_ALL) // repeat all currently enabled, click will repeat one
.setDisplayName(getString(R.string.repeat_mode))
.setPlayerCommand(Player.COMMAND_SET_REPEAT_MODE, Player.REPEAT_MODE_ONE)
.build(),
CommandButton.Builder(CommandButton.ICON_REPEAT_ONE) // repeat one currently enabled, click will disable
.setDisplayName(getString(R.string.repeat_mode))
.setPlayerCommand(Player.COMMAND_SET_REPEAT_MODE, Player.REPEAT_MODE_OFF)
.build(),
)
afFormatTracker = AfFormatTracker(this, playbackHandler, handler)
afFormatTracker.formatChangedCallback = { format, period ->
if (period != null) {
handler.post {
val currentPeriod = controller?.currentPeriodIndex?.takeIf {
it != C.INDEX_UNSET &&
(controller?.currentTimeline?.periodCount ?: 0) > it
}
?.let { controller!!.currentTimeline.getUidOfPeriod(it) }
if (currentPeriod != period) {
if (format != null) {
pendingAfTrackFormats[period] = format
} else {
pendingAfTrackFormats.remove(period)
}
} else {
afTrackFormat = format?.let { period to it }
mediaSession?.broadcastCustomCommand(
SessionCommand(SERVICE_GET_AUDIO_FORMAT, Bundle.EMPTY),
Bundle.EMPTY
)
}
}
} else {
Log.e(TAG, "mediaPeriodId is NULL in formatChangedCallback!!")
}
}
rgAp = ReplayGainAudioProcessor()
prefs.registerOnSharedPreferenceChangeListener(this)
onSharedPreferenceChanged(prefs, null) // read initial values
val player = EndedWorkaroundPlayer(
ExoPlayer.Builder(
exoPlayer = ExoPlayer.Builder(
this,
GramophoneRenderFactory(
this, rgAp, this::onAudioSinkInputFormatChanged,
afFormatTracker::setAudioSink
)
.setPcmEncodingRestrictionLifted(true)
.setEnableDecoderFallback(true)
.setEnableAudioTrackPlaybackParams(true)
.setExtensionRendererMode(DefaultRenderersFactory.EXTENSION_RENDERER_MODE_ON),
GramophoneMediaSourceFactory(
DefaultDataSource.Factory(this),
GramophoneExtractorsFactory().also {
it.setConstantBitrateSeekingEnabled(true)
if (prefs.getBooleanStrict("mp3_index_seeking", false))
it.setMp3ExtractorFlags(Mp3Extractor.FLAG_ENABLE_INDEX_SEEKING)
})
)
.setWakeMode(C.WAKE_MODE_LOCAL)
.setAudioAttributes(
AudioAttributes
.Builder()
.setUsage(C.USAGE_MEDIA)
.setContentType(C.AUDIO_CONTENT_TYPE_MUSIC)
.build(), true
)
.setHandleAudioBecomingNoisy(true)
.setTrackSelector(DefaultTrackSelector(this).apply {
setParameters(
buildUponParameters()
.setAllowInvalidateSelectionsOnRendererCapabilitiesChange(true)
.setAudioOffloadPreferences(
TrackSelectionParameters.AudioOffloadPreferences.Builder()
.apply {
val config =
prefs.getStringStrict("offload", "0")?.toIntOrNull()
if (config != null && config > 0 && Flags.OFFLOAD) {
rgAp.setOffloadEnabled(true)
setAudioOffloadMode(TrackSelectionParameters.AudioOffloadPreferences.AUDIO_OFFLOAD_MODE_ENABLED)
setIsGaplessSupportRequired(config == 2)
}
}
.build()))
})
.setPlaybackLooper(internalPlaybackThread.looper)
.build()
.build(),
queueBoard = qb,
)
player.exoPlayer.addAnalyticsListener(EventLogger())
player.exoPlayer.addAnalyticsListener(afFormatTracker)
player.exoPlayer.addAnalyticsListener(this)
player.exoPlayer.setShuffleOrder(CircularShuffleOrder(player, 0, 0, Random.nextLong()))
lastPlayedManager = LastPlayedManager(this, player)
lastPlayedManager.allowSavingState = false

mediaSession =
MediaLibrarySession
.Builder(this, player, this)
// CacheBitmapLoader is required for MeiZuLyricsMediaNotificationProvider
.setBitmapLoader(CacheBitmapLoader(object : BitmapLoader {
// Coil-based bitmap loader to reuse Coil's caching and to make sure we use
// the same cover art as the rest of the app, ie MediaStore's cover

private val limit by lazy { MediaSession.getBitmapDimensionLimit(this@GramophonePlaybackService) }

override fun decodeBitmap(data: ByteArray): ListenableFuture<Bitmap> {
return CallbackToFutureAdapter.getFuture { completer ->
imageLoader.enqueue(
ImageRequest.Builder(this@GramophonePlaybackService)
.data(data)
.memoryCacheKey(data.hashCode().toString())
.size(limit, limit)
.allowHardware(false)
.target(
onStart = { _ ->
// We don't need or want a placeholder.
},
onSuccess = { result ->
completer.set((result as BitmapImage).bitmap)
},
onError = { _ ->
completer.setException(
Exception(
"coil onError called for byte array"
)
)
}
)
.build())
.also {
completer.addCancellationListener(
{ it.dispose() },
ContextCompat.getMainExecutor(
this@GramophonePlaybackService
)
)
}
"coil load for ${data.hashCode()}"
}
}

override fun loadBitmap(
uri: Uri
): ListenableFuture<Bitmap> {
return CallbackToFutureAdapter.getFuture { completer ->
imageLoader.enqueue(
ImageRequest.Builder(this@GramophonePlaybackService)
.data(uri)
.size(limit, limit)
.allowHardware(false)
.target(
onStart = { _ ->
// We don't need or want a placeholder.
},
onSuccess = { result ->
completer.set((result as BitmapImage).bitmap)
},
onError = { _ ->
completer.setException(
Exception(
"coil onError called" +
" (normal if no album art exists)"
)
)
}
)
.build())
.also {
completer.addCancellationListener(
{ it.dispose() },
ContextCompat.getMainExecutor(
this@GramophonePlaybackService
)
)
}
"coil load for $uri"
}
}

override fun supportsMimeType(mimeType: String): Boolean {
return isBitmapFactorySupportedMimeType(mimeType)
}

override fun loadBitmapFromMetadata(metadata: MediaMetadata): ListenableFuture<Bitmap>? {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
// allow using exoplayer's copy extracted here on P- for now
// refer to the TO DO in GramophoneApplication
return super.loadBitmapFromMetadata(metadata)
}
return metadata.artworkUri?.let { loadBitmap(it) }
}
}))
.setSessionActivity(
PendingIntent.getActivity(
this,
PENDING_INTENT_SESSION_ID,
Intent(this, MainActivity::class.java),
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT,
)
)
.setSystemUiPlaybackResumptionOptIn(Build.VERSION.SDK_INT >= Build.VERSION_CODES.S)
.build()
addSession(mediaSession!!)
controller = MediaBrowser.Builder(this, mediaSession!!.token).buildAsync().get()
controller!!.addListener(this)
if (controller!!.audioSessionId != C.AUDIO_SESSION_ID_UNSET) {
onAudioSessionIdChanged(controller!!.audioSessionId)
}
ContextCompat.registerReceiver(
this,
seekReceiver,
IntentFilter("$packageName.SEEK_TO"),
@SuppressLint("WrongConstant") // why is this needed?
ContextCompat.RECEIVER_NOT_EXPORTED
)
ContextCompat.registerReceiver(
this,
btReceiver,
IntentFilter("android.bluetooth.a2dp.profile.action.CODEC_CONFIG_CHANGED"),
@SuppressLint("WrongConstant") // why is this needed?
ContextCompat.RECEIVER_EXPORTED
)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O /* before 8, only sbc was supported */) {
proxy = BtCodecInfo.getCodec(this) {
Log.d(TAG, "first bluetooth codec config $btInfo")
btInfo = it
mediaSession?.broadcastCustomCommand(
SessionCommand(SERVICE_GET_AUDIO_FORMAT, Bundle.EMPTY),
Bundle.EMPTY
)
}
}
scope.launch {
lastPlayedManager.restore { items, factory ->
if (mediaSession == null) return@restore
if (items != null) {
if (endedWorkaroundPlayer?.nextShuffleOrder != null)
throw IllegalStateException("shuffleFactory was found orphaned")
endedWorkaroundPlayer?.nextShuffleOrder = factory.toFactory()
try {
mediaSession?.player?.setMediaItems(
items.mediaItems, items.startIndex, items.startPositionMs
val mq = endedWorkaroundPlayer?.queueBoard?.addQueue(
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

see above, I think this part shouldnt need to change at all and qb should only manage inactive queue

title = "[LastPlayedManager]",
mediaList = items.mediaItems,
mediaItemIndex = items.startIndex,
startPositionMs = items.startPositionMs,
// shouldPin = true
)
mq?.let { endedWorkaroundPlayer?.queueBoard?.commitQueue(it) }

Check warning on line 564 in app/src/main/java/org/akanework/gramophone/logic/GramophonePlaybackService.kt

View check run for this annotation

CodeScene Delta Analysis / CodeScene Code Health Review (beta)

❌ Getting worse: Complex Method

GramophonePlaybackService.onCreate already has high cyclomatic complexity, and now it increases in Lines of Code from 311 to 316. This function has many conditional statements (e.g. if, for, while), leading to lower code health. Avoid adding more conditionals and code to it without refactoring.
} catch (e: IllegalSeekPositionException) {
try {
mediaSession?.player?.setMediaItems(items.mediaItems)
Expand Down Expand Up @@ -682,6 +701,14 @@
availableSessionCommands.add(SessionCommand(SERVICE_QUERY_TIMER, Bundle.EMPTY))
availableSessionCommands.add(SessionCommand(SERVICE_GET_LYRICS, Bundle.EMPTY))
availableSessionCommands.add(SessionCommand(SERVICE_GET_AUDIO_FORMAT, Bundle.EMPTY))
availableSessionCommands.add(SessionCommand(SERVICE_QB_GET_INACTIVE, Bundle.EMPTY))
availableSessionCommands.add(SessionCommand(SERVICE_QB_GET_QUEUE, Bundle.EMPTY))
availableSessionCommands.add(SessionCommand(SERVICE_QB_LOAD_QUEUE, Bundle.EMPTY))
availableSessionCommands.add(SessionCommand(SERVICE_QB_DEL, Bundle.EMPTY))
availableSessionCommands.add(SessionCommand(SERVICE_QB_REORDER, Bundle.EMPTY))
availableSessionCommands.add(SessionCommand(SERVICE_QB_ENQUEUE, Bundle.EMPTY))
availableSessionCommands.add(SessionCommand(SERVICE_QB_PIN_QUEUE, Bundle.EMPTY))
availableSessionCommands.add(SessionCommand(SERVICE_QB_UNPIN_QUEUE, Bundle.EMPTY))
return builder.setAvailableSessionCommands(availableSessionCommands.build()).build()
}

Expand Down Expand Up @@ -864,6 +891,91 @@
}
}

SERVICE_QB_GET_INACTIVE -> {
SessionResult(SessionResult.RESULT_SUCCESS).also { res ->
val queueList: List<MultiQueueObject> = qb.getInactiveQueues()
val binder = BundleListRetriever(queueList.map { it.toBundle() })
res.extras.putBinder("allQueues", binder)
}
}

SERVICE_QB_GET_QUEUE -> {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is get for? can't we just have user call LOAD and then read it? or is there some UI where content of inactive is needed?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GET returns the queue with shuffle indexes for ui purposes, while LOAD is intended to facilitate the loading of the queue into the player. I don't believe these should be the same command.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UI purposes as in?

The UI is just the dropdown thingie where the user can select a queue. The user hence has no way to view an inactive queue without activating it in UI, no? Hence, I can't think of any UI purpose that would require a LOAD

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No. When you click on the queue, it previews it, hence GET is used. LOAD is when you click the load queue Button to activate it.

SessionResult(SessionResult.RESULT_SUCCESS).also { res ->
val index = customCommand.customExtras.getInt("index")
val queueList: List<MultiQueueObject> = qb.getQueue(index)
val binder = BundleListRetriever(queueList.map { it.toBundle() })
res.extras.putBinder("allQueues", binder)

// assume ui does not expect shuffleIndexes if shuffle is off
if (!queueList.isEmpty()) {
val mq = queueList.first()
val factory =
CircularShuffleOrder.Persistent.deserialize(mq.shuffleOrder)
.toFactory()
val shuffleOrder = factory(0, mq.getSize(), endedWorkaroundPlayer!!)
val shuffleIndexesList: List<Int> = shuffledIndices(shuffleOrder)
res.extras.putIntArray("shuffleIndexes", shuffleIndexesList.toIntArray())
}
}
}

/*
SERVICE_QB_ENQUEUE -> {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

commented out much?

val title = customCommand.customExtras.getString("title") ?: "Queue"
val mediaItemIndex = customCommand.customExtras.getInt("mediaItemIndex")
val isOriginal = customCommand.customExtras.getBoolean("isOriginal")
val binder = customCommand.customExtras.getBinder("mediaList")!!
val mediaList = BundleListRetriever.getList(binder).map {
MediaItem.fromBundle(it)
}

if (Flags.MQ_PREVIEW && prefs.getBooleanStrict("mq_preview", false)) {
val mq = qb.addQueue(title, mediaList, mediaItemIndex, isOriginal)
qb.commitQueue(mq)
if (!mq.queue.isEmpty()) {
endedWorkaroundPlayer!!.prepare()
endedWorkaroundPlayer!!.play()
}
} else {
endedWorkaroundPlayer!!.setMediaItems(mediaList, mediaItemIndex, C.TIME_UNSET)
endedWorkaroundPlayer!!.prepare()
endedWorkaroundPlayer!!.play()
}

SessionResult(SessionResult.RESULT_SUCCESS)
}
*/

SERVICE_QB_LOAD_QUEUE -> {
val index = customCommand.customExtras.getInt("index")
qb.commitQueue(index)
SessionResult(SessionResult.RESULT_SUCCESS)
}

SERVICE_QB_PIN_QUEUE -> {
val index = customCommand.customExtras.getInt("index")
qb.pinQueue(index)
SessionResult(SessionResult.RESULT_SUCCESS).also { res ->
res.extras.putBoolean("status", false)
}
}

SERVICE_QB_UNPIN_QUEUE -> {
val index = customCommand.customExtras.getInt("index")
qb.unpinQueue(index)
SessionResult(SessionResult.RESULT_SUCCESS).also { res ->
res.extras.putBoolean("status", false)
}
}

SERVICE_QB_DEL -> {
val index = customCommand.customExtras.getInt("index")
qb.deleteQueue(index)
SessionResult(SessionResult.RESULT_SUCCESS).also { res ->
res.extras.putBoolean("status", false)
}
}

Check warning on line 978 in app/src/main/java/org/akanework/gramophone/logic/GramophonePlaybackService.kt

View check run for this annotation

CodeScene Delta Analysis / CodeScene Code Health Review (beta)

❌ Getting worse: Complex Method

GramophonePlaybackService.onCustomCommand increases in cyclomatic complexity from 9 to 10, threshold = 9. This function has many conditional statements (e.g. if, for, while), leading to lower code health. Avoid adding more conditionals and code to it without refactoring.
else -> {
SessionResult(SessionError.ERROR_BAD_VALUE)
}
Expand Down Expand Up @@ -897,7 +1009,7 @@
if (endedWorkaroundPlayer?.nextShuffleOrder != null)
throw IllegalStateException("shuffleFactory was found orphaned")
if (isForPlayback && items.mediaItems.isNotEmpty()) {
endedWorkaroundPlayer?.nextShuffleOrder = factory.toFactory()
// endedWorkaroundPlayer?.nextShuffleOrder = factory.toFactory()
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah...no

settable.set(items)
if (endedWorkaroundPlayer?.nextShuffleOrder != null)
throw IllegalStateException("shuffleFactory was not consumed during resumption")
Expand Down
Loading
Loading