music: emulate old music loading process
This commit is contained in:
parent
c74b744aec
commit
ae449ded45
6 changed files with 88 additions and 50 deletions
|
@ -360,13 +360,22 @@ class HomeFragment :
|
|||
binding.homeIndexingProgress.visibility = View.VISIBLE
|
||||
binding.homeIndexingActions.visibility = View.INVISIBLE
|
||||
|
||||
binding.homeIndexingStatus.setText(R.string.lng_indexing)
|
||||
|
||||
when (progress) {
|
||||
is IndexingProgress.Indeterminate -> {
|
||||
// In a query/initialization state, show a generic loading status.
|
||||
binding.homeIndexingStatus.text = getString(R.string.lng_indexing)
|
||||
binding.homeIndexingProgress.isIndeterminate = true
|
||||
}
|
||||
is IndexingProgress.Songs -> {
|
||||
// Actively loading songs, show the current progress.
|
||||
binding.homeIndexingStatus.text =
|
||||
getString(R.string.fmt_indexing, progress.loaded, progress.explored)
|
||||
binding.homeIndexingProgress.apply {
|
||||
isIndeterminate = false
|
||||
max = progress.explored
|
||||
this.progress = progress.interpreted
|
||||
this.progress = progress.loaded
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -61,6 +61,18 @@ class IndexingNotification(private val context: Context) :
|
|||
* @return true if the notification updated, false otherwise
|
||||
*/
|
||||
fun updateIndexingState(progress: IndexingProgress): Boolean {
|
||||
when (progress) {
|
||||
is IndexingProgress.Indeterminate -> {
|
||||
// Indeterminate state, use a vaguer description and in-determinate progress.
|
||||
// These events are not very frequent, and thus we don't need to safeguard
|
||||
// against rate limiting.
|
||||
L.d("Updating state to $progress")
|
||||
lastUpdateTime = -1
|
||||
setContentText(context.getString(R.string.lng_indexing))
|
||||
setProgress(0, 0, true)
|
||||
return true
|
||||
}
|
||||
is IndexingProgress.Songs -> {
|
||||
// Determinate state, show an active progress meter. Since these updates arrive
|
||||
// highly rapidly, only update every 1.5 seconds to prevent notification rate
|
||||
// limiting.
|
||||
|
@ -71,10 +83,12 @@ class IndexingNotification(private val context: Context) :
|
|||
lastUpdateTime = SystemClock.elapsedRealtime()
|
||||
L.d("Updating state to $progress")
|
||||
setContentText(
|
||||
context.getString(R.string.fmt_indexing, progress.interpreted, progress.explored))
|
||||
setProgress(progress.explored, progress.interpreted, false)
|
||||
context.getString(R.string.fmt_indexing, progress.loaded, progress.explored))
|
||||
setProgress(progress.loaded, progress.explored, false)
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -22,7 +22,9 @@ import android.net.Uri
|
|||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.buffer
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.flow.flowOn
|
||||
import org.oxycblt.auxio.music.stack.explore.Explorer
|
||||
import org.oxycblt.auxio.music.stack.interpret.Interpretation
|
||||
|
@ -42,7 +44,11 @@ interface Indexer {
|
|||
*
|
||||
* @author Alexander Capehart (OxygenCobalt)
|
||||
*/
|
||||
data class IndexingProgress(val interpreted: Int, val explored: Int)
|
||||
sealed interface IndexingProgress {
|
||||
data class Songs(val loaded: Int, val explored: Int) : IndexingProgress
|
||||
|
||||
data object Indeterminate : IndexingProgress
|
||||
}
|
||||
|
||||
class IndexerImpl
|
||||
@Inject
|
||||
|
@ -52,20 +58,25 @@ constructor(private val explorer: Explorer, private val interpreter: Interpreter
|
|||
interpretation: Interpretation,
|
||||
onProgress: suspend (IndexingProgress) -> Unit
|
||||
) = coroutineScope {
|
||||
var interpreted = 0
|
||||
var explored = 0
|
||||
onProgress(IndexingProgress(interpreted, explored))
|
||||
val files =
|
||||
explorer.explore(uris) {
|
||||
explored++
|
||||
onProgress(IndexingProgress(interpreted, explored))
|
||||
}
|
||||
val audioFiles = files.audios.flowOn(Dispatchers.IO).buffer()
|
||||
val files = explorer.explore(uris, onProgress)
|
||||
val audioFiles =
|
||||
files.audios
|
||||
.cap(
|
||||
start = { onProgress(IndexingProgress.Songs(0, 0)) },
|
||||
end = { onProgress(IndexingProgress.Indeterminate) })
|
||||
.flowOn(Dispatchers.IO)
|
||||
.buffer()
|
||||
val playlistFiles = files.playlists.flowOn(Dispatchers.IO).buffer()
|
||||
interpreter.interpret(audioFiles, playlistFiles, interpretation)
|
||||
}
|
||||
|
||||
interpreter.interpret(audioFiles, playlistFiles, interpretation) {
|
||||
interpreted++
|
||||
onProgress(IndexingProgress(interpreted, explored))
|
||||
private fun <T> Flow<T>.cap(start: suspend () -> Unit, end: suspend () -> Unit): Flow<T> =
|
||||
flow {
|
||||
start()
|
||||
try {
|
||||
collect { emit(it) }
|
||||
} finally {
|
||||
end()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,13 +34,14 @@ import kotlinx.coroutines.flow.merge
|
|||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.flow.receiveAsFlow
|
||||
import kotlinx.coroutines.flow.withIndex
|
||||
import org.oxycblt.auxio.music.stack.IndexingProgress
|
||||
import org.oxycblt.auxio.music.stack.explore.cache.TagCache
|
||||
import org.oxycblt.auxio.music.stack.explore.extractor.TagExtractor
|
||||
import org.oxycblt.auxio.music.stack.explore.fs.DeviceFiles
|
||||
import org.oxycblt.auxio.music.stack.explore.playlists.StoredPlaylists
|
||||
|
||||
interface Explorer {
|
||||
fun explore(uris: List<Uri>, onExplored: suspend () -> Unit): Files
|
||||
fun explore(uris: List<Uri>, onProgress: suspend (IndexingProgress.Songs) -> Unit): Files
|
||||
}
|
||||
|
||||
data class Files(val audios: Flow<AudioFile>, val playlists: Flow<PlaylistFile>)
|
||||
|
@ -54,14 +55,18 @@ constructor(
|
|||
private val storedPlaylists: StoredPlaylists
|
||||
) : Explorer {
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
override fun explore(uris: List<Uri>, onExplored: suspend () -> Unit): Files {
|
||||
var discovered = 0
|
||||
override fun explore(
|
||||
uris: List<Uri>,
|
||||
onProgress: suspend (IndexingProgress.Songs) -> Unit
|
||||
): Files {
|
||||
var loaded = 0
|
||||
var explored = 0
|
||||
val deviceFiles =
|
||||
deviceFiles
|
||||
.explore(uris.asFlow())
|
||||
.onEach {
|
||||
discovered++
|
||||
onExplored()
|
||||
explored++
|
||||
onProgress(IndexingProgress.Songs(loaded, explored))
|
||||
}
|
||||
.flowOn(Dispatchers.IO)
|
||||
.buffer()
|
||||
|
@ -73,8 +78,13 @@ constructor(
|
|||
// val writtenAudioFiles =
|
||||
// tagCache.write(extractedAudioFiles).flowOn(Dispatchers.IO).buffer()
|
||||
// val audioFiles = merge(cachedAudioFiles, writtenAudioFiles)
|
||||
val audioFiles =
|
||||
extractedAudioFiles.onEach {
|
||||
loaded++
|
||||
onProgress(IndexingProgress.Songs(loaded, explored))
|
||||
}
|
||||
val playlistFiles = storedPlaylists.read()
|
||||
return Files(extractedAudioFiles, playlistFiles)
|
||||
return Files(audioFiles, playlistFiles)
|
||||
}
|
||||
|
||||
/** Temporarily split a flow into 8 parallel threads and then */
|
||||
|
|
|
@ -32,6 +32,7 @@ import kotlinx.coroutines.flow.flatMapMerge
|
|||
import kotlinx.coroutines.flow.flattenMerge
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import org.oxycblt.auxio.music.stack.explore.DeviceFile
|
||||
import timber.log.Timber
|
||||
|
||||
interface DeviceFiles {
|
||||
fun explore(uris: Flow<Uri>): Flow<DeviceFile>
|
||||
|
|
|
@ -25,7 +25,6 @@ import kotlinx.coroutines.flow.asFlow
|
|||
import kotlinx.coroutines.flow.buffer
|
||||
import kotlinx.coroutines.flow.flowOn
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.flow.toList
|
||||
import org.oxycblt.auxio.music.Music
|
||||
import org.oxycblt.auxio.music.stack.explore.AudioFile
|
||||
|
@ -49,8 +48,7 @@ interface Interpreter {
|
|||
suspend fun interpret(
|
||||
audioFiles: Flow<AudioFile>,
|
||||
playlistFiles: Flow<PlaylistFile>,
|
||||
interpretation: Interpretation,
|
||||
onInterpret: suspend () -> Unit
|
||||
interpretation: Interpretation
|
||||
): MutableLibrary
|
||||
}
|
||||
|
||||
|
@ -58,8 +56,7 @@ class InterpreterImpl @Inject constructor(private val preparer: Preparer) : Inte
|
|||
override suspend fun interpret(
|
||||
audioFiles: Flow<AudioFile>,
|
||||
playlistFiles: Flow<PlaylistFile>,
|
||||
interpretation: Interpretation,
|
||||
onInterpret: suspend () -> Unit
|
||||
interpretation: Interpretation
|
||||
): MutableLibrary {
|
||||
val preSongs =
|
||||
preparer.prepare(audioFiles, interpretation).flowOn(Dispatchers.Main).buffer()
|
||||
|
@ -69,11 +66,7 @@ class InterpreterImpl @Inject constructor(private val preparer: Preparer) : Inte
|
|||
|
||||
val artistLinker = ArtistLinker()
|
||||
val artistLinkedSongs =
|
||||
artistLinker
|
||||
.register(genreLinkedSongs)
|
||||
.onEach { onInterpret() }
|
||||
.flowOn(Dispatchers.Main)
|
||||
.toList()
|
||||
artistLinker.register(genreLinkedSongs).flowOn(Dispatchers.Main).toList()
|
||||
// This is intentional. Song and album instances are dependent on artist
|
||||
// data, so we need to ensure that all of the linked artist data is resolved
|
||||
// before we go any further.
|
||||
|
|
Loading…
Reference in a new issue