music: rework library update process [#176]
Update the library update process to be on a co-routine, updating callbacks on the main thread. For some insane reason, the Main dispatcher used normally when loading music just disappears sometimes. This leads to unpleasent crashes as callbacks expect to be called on the app thread, not any background threads. Fix this by forcing the Main dispatcher during the update process. This requires the music update process to also run on a background thread, albeit that will be useful for automatic rescanning late ron.
This commit is contained in:
parent
090a1713dd
commit
e883336b04
18 changed files with 48 additions and 38 deletions
|
@ -361,6 +361,12 @@ class Indexer {
|
|||
object NoPerms : Response()
|
||||
}
|
||||
|
||||
/**
|
||||
* A callback to use when the indexing state changes.
|
||||
*
|
||||
* This callback is low-level and not guaranteed to be single-thread. For that,
|
||||
* [MusicStore.Callback] is recommended instead.
|
||||
*/
|
||||
interface Callback {
|
||||
/**
|
||||
* Called when the current state of the Indexer changed.
|
||||
|
|
|
@ -54,6 +54,7 @@ class IndexerService : Service(), Indexer.Callback {
|
|||
|
||||
private val serviceJob = Job()
|
||||
private val indexScope = CoroutineScope(serviceJob + Dispatchers.Main)
|
||||
private val updateScope = CoroutineScope(serviceJob + Dispatchers.Main)
|
||||
|
||||
private var isForeground = false
|
||||
private lateinit var notification: IndexerNotification
|
||||
|
@ -92,20 +93,21 @@ class IndexerService : Service(), Indexer.Callback {
|
|||
if (state.response is Indexer.Response.Ok &&
|
||||
state.response.library != musicStore.library) {
|
||||
logD("Applying new library")
|
||||
|
||||
// Load was completed successfully, so apply the new library if we
|
||||
// have not already.
|
||||
musicStore.library = state.response.library
|
||||
// have not already. Only when we are done updating the library will
|
||||
// the service stop it's foreground state.
|
||||
updateScope.launch {
|
||||
musicStore.updateLibrary(state.response.library)
|
||||
stopForegroundSession()
|
||||
}
|
||||
} else {
|
||||
// On errors, while we would want to show a notification that displays the
|
||||
// error, in practice that comes into conflict with the upcoming Android 13
|
||||
// notification permission, and there is no point implementing permission
|
||||
// on-boarding for such when it will only be used for this.
|
||||
stopForegroundSession()
|
||||
}
|
||||
|
||||
// On errors, while we would want to show a notification that displays the
|
||||
// error, in practice that comes into conflict with the upcoming Android 13
|
||||
// notification permission, and there is no point implementing permission
|
||||
// on-boarding for such when it will only be used for this.
|
||||
|
||||
// Note that we don't stop the service here, as (in the future)
|
||||
// this service will be used to reload music and observe the music
|
||||
// database.
|
||||
stopForegroundSession()
|
||||
}
|
||||
is Indexer.State.Indexing -> {
|
||||
// When loading, we want to enter the foreground state so that android does
|
||||
|
|
|
@ -20,6 +20,8 @@ package org.oxycblt.auxio.music
|
|||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import android.provider.OpenableColumns
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.oxycblt.auxio.util.contentResolverSafe
|
||||
|
||||
/**
|
||||
|
@ -33,14 +35,7 @@ class MusicStore private constructor() {
|
|||
private val callbacks = mutableListOf<Callback>()
|
||||
|
||||
var library: Library? = null
|
||||
set(value) {
|
||||
synchronized(this) {
|
||||
field = value
|
||||
for (callback in callbacks) {
|
||||
callback.onLibraryChanged(library)
|
||||
}
|
||||
}
|
||||
}
|
||||
private set
|
||||
|
||||
/** Add a callback to this instance. Make sure to remove it when done. */
|
||||
fun addCallback(callback: Callback) {
|
||||
|
@ -53,6 +48,19 @@ class MusicStore private constructor() {
|
|||
callbacks.remove(callback)
|
||||
}
|
||||
|
||||
suspend fun updateLibrary(newLibrary: Library?) {
|
||||
// Ensure we are on the main thread when updating the library, as callbacks expect to
|
||||
// run in an stable app thread.
|
||||
withContext(Dispatchers.Main) {
|
||||
synchronized(this) {
|
||||
library = newLibrary
|
||||
for (callback in callbacks) {
|
||||
callback.onLibraryChanged(library)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Represents a library of music owned by [MusicStore]. */
|
||||
data class Library(
|
||||
val genres: List<Genre>,
|
||||
|
@ -80,7 +88,6 @@ class MusicStore private constructor() {
|
|||
songs.find { it.path.name == displayName }
|
||||
}
|
||||
|
||||
/** "Sanitize" a music object from a previous library iteration. */
|
||||
fun sanitize(song: Song) = songs.find { it.id == song.id }
|
||||
fun sanitize(album: Album) = albums.find { it.id == album.id }
|
||||
fun sanitize(artist: Artist) = artists.find { it.id == artist.id }
|
||||
|
|
|
@ -143,7 +143,6 @@ class PlaybackService :
|
|||
// --- PLAYBACKSTATEMANAGER SETUP ---
|
||||
|
||||
settings = Settings(this, this)
|
||||
|
||||
playbackManager.registerController(this)
|
||||
|
||||
logD("Service created")
|
||||
|
|
|
@ -28,7 +28,7 @@ import org.oxycblt.auxio.music.Music
|
|||
import org.oxycblt.auxio.music.Song
|
||||
|
||||
/**
|
||||
* A data class representing the sort modes used in Auxio.
|
||||
* Represents the sort modes used in Auxio.
|
||||
*
|
||||
* Sorting can be done by Name, Artist, Album, and others. Sorting of names is always
|
||||
* case-insensitive and article-aware. Certain datatypes may only support a subset of sorts since
|
||||
|
|
|
@ -37,7 +37,7 @@ class AccentGridLayoutManager(
|
|||
) : GridLayoutManager(context, attrs, defStyleAttr, defStyleRes) {
|
||||
// We use 72dp here since that's the rough size of the accent item.
|
||||
// This will need to be modified if this is used beyond the accent dialog.
|
||||
private var columnWidth = context.pxOfDp(64f)
|
||||
private var columnWidth = context.pxOfDp(56f)
|
||||
|
||||
private var lastWidth = -1
|
||||
private var lastHeight = -1
|
||||
|
|
|
@ -100,7 +100,7 @@ private fun RemoteViews.applyCover(
|
|||
R.id.widget_cover,
|
||||
context.getString(R.string.desc_album_cover, state.song.album.resolveName(context)))
|
||||
} else {
|
||||
setImageViewResource(R.id.widget_cover, R.drawable.ic_remote_default_cover)
|
||||
setImageViewResource(R.id.widget_cover, R.drawable.ic_remote_default_cover_24)
|
||||
setContentDescription(R.id.widget_cover, context.getString(R.string.desc_no_cover))
|
||||
}
|
||||
|
||||
|
|
|
@ -61,8 +61,7 @@ class WidgetComponent(private val context: Context) :
|
|||
*/
|
||||
fun update() {
|
||||
// TODO: Rework margins/button layout to do the magic that other button bars do
|
||||
// TODO: Try to change the error icon again
|
||||
// TODO:
|
||||
// TODO: Respond to rounded covers
|
||||
|
||||
// Updating Auxio's widget is unlike the rest of Auxio for a few reasons:
|
||||
// 1. We can't use the typical primitives like ViewModels
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="@dimen/spacing_small"
|
||||
android:padding="@dimen/spacing_tiny"
|
||||
android:theme="@style/ThemeOverlay.Accent">
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
|
@ -13,7 +13,6 @@
|
|||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:background="@drawable/ui_accent_circle"
|
||||
app:icon="@drawable/ic_check_24"
|
||||
app:iconTint="?attr/colorSurface"
|
||||
tools:backgroundTint="?attr/colorPrimary"
|
||||
|
|
|
@ -36,7 +36,7 @@
|
|||
android:layout_alignTop="@id/widget_aspect_ratio"
|
||||
android:layout_alignEnd="@id/widget_aspect_ratio"
|
||||
android:layout_alignBottom="@id/widget_aspect_ratio"
|
||||
android:src="@drawable/ic_remote_default_cover"
|
||||
android:src="@drawable/ic_remote_default_cover_24"
|
||||
tools:ignore="ContentDescription" />
|
||||
|
||||
<android.widget.LinearLayout
|
||||
|
|
|
@ -36,7 +36,7 @@
|
|||
android:layout_alignTop="@id/widget_aspect_ratio"
|
||||
android:layout_alignEnd="@id/widget_aspect_ratio"
|
||||
android:layout_alignBottom="@id/widget_aspect_ratio"
|
||||
android:src="@drawable/ic_remote_default_cover"
|
||||
android:src="@drawable/ic_remote_default_cover_24"
|
||||
tools:ignore="ContentDescription" />
|
||||
|
||||
<android.widget.LinearLayout
|
||||
|
|
|
@ -44,7 +44,7 @@
|
|||
android:layout_alignTop="@id/widget_aspect_ratio"
|
||||
android:layout_alignEnd="@id/widget_aspect_ratio"
|
||||
android:layout_alignBottom="@id/widget_aspect_ratio"
|
||||
android:src="@drawable/ic_remote_default_cover"
|
||||
android:src="@drawable/ic_remote_default_cover_24"
|
||||
tools:ignore="ContentDescription" />
|
||||
|
||||
<android.widget.LinearLayout
|
||||
|
|
|
@ -49,7 +49,7 @@
|
|||
android:layout_alignTop="@id/widget_aspect_ratio"
|
||||
android:layout_alignEnd="@id/widget_aspect_ratio"
|
||||
android:layout_alignBottom="@id/widget_aspect_ratio"
|
||||
android:src="@drawable/ic_remote_default_cover"
|
||||
android:src="@drawable/ic_remote_default_cover_24"
|
||||
tools:ignore="ContentDescription" />
|
||||
|
||||
</android.widget.RelativeLayout>
|
||||
|
|
|
@ -37,7 +37,7 @@
|
|||
android:layout_alignTop="@id/widget_aspect_ratio"
|
||||
android:layout_alignEnd="@id/widget_aspect_ratio"
|
||||
android:layout_alignBottom="@id/widget_aspect_ratio"
|
||||
android:src="@drawable/ic_remote_default_cover"
|
||||
android:src="@drawable/ic_remote_default_cover_24"
|
||||
tools:ignore="ContentDescription" />
|
||||
|
||||
<android.widget.LinearLayout
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
<item
|
||||
android:id="@+id/action_queue"
|
||||
android:icon="@drawable/ic_queue"
|
||||
android:icon="@drawable/ic_queue_24"
|
||||
android:title="@string/lbl_queue"
|
||||
app:showAsAction="ifRoom" />
|
||||
<item
|
||||
|
|
|
@ -31,10 +31,8 @@
|
|||
|
||||
<dimen name="size_pre_amp_ticker">56dp</dimen>
|
||||
|
||||
<dimen name="text_size_ext_label_larger">16sp</dimen>
|
||||
<dimen name="text_size_ext_title_mid_large">18sp</dimen>
|
||||
<dimen name="text_size_track_number_min">12sp</dimen>
|
||||
<dimen name="text_size_track_number_max">20sp</dimen>
|
||||
<dimen name="text_size_track_number_max">22sp</dimen>
|
||||
<dimen name="text_size_track_number_step">2sp</dimen>
|
||||
|
||||
<!-- Misc -->
|
||||
|
|
Loading…
Reference in a new issue