Skip to content
Draft
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
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)

❌ New issue: Primitive Obsession

In this module, 53.8% 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,6 +77,14 @@
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
Expand All @@ -88,6 +98,7 @@
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 +348,148 @@
)
}

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 = C.INDEX_UNSET): Pair<MutableList<Int>, MutableList<MediaItem>>? {
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 items = mq.queue
val indexes: MutableList<Int> = if (mq.shuffleOrder == null) {
(0 until mq.getSize()).toMutableList()
} else {
shuffledIndices(mq.shuffleOrder!!)
}

Pair(indexes, items)
}.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(
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
@@ -1,4 +1,4 @@
/*

Check notice on line 1 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)

✅ No longer an issue: Large Method

GramophonePlaybackService.onCustomCommand is no longer above the threshold for lines of code
* Copyright (C) 2024 Akane Foundation
*
* Gramophone is free software: you can redistribute it and/or modify
Expand Down 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 @@ -255,6 +268,7 @@
override fun onCreate() {
Log.i(TAG, "+onCreate()")
super.onCreate()
qb = QueueBoard(this)

Check warning on line 271 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 312 to 313. 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.
instanceForWidgetAndLyricsOnly = this
internalPlaybackThread.start()
playbackHandler = Handler(internalPlaybackThread.looper)
Expand Down Expand Up @@ -683,6 +697,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 @@ -865,6 +887,72 @@
}
}

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 -> {
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)
}
}

SERVICE_QB_ENQUEUE -> {
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)
}

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

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

Check warning on line 955 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)

❌ New issue: Complex Method

GramophonePlaybackService.onCustomCommand has a cyclomatic complexity of 11, 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
Loading
Loading