music: move utils around
Move some miscellanious utils around.
This commit is contained in:
parent
4c954e83b0
commit
2e71342e1c
17 changed files with 97 additions and 104 deletions
|
@ -29,7 +29,7 @@ import org.oxycblt.auxio.databinding.DialogSongDetailBinding
|
||||||
import org.oxycblt.auxio.ui.fragment.ViewBindingDialogFragment
|
import org.oxycblt.auxio.ui.fragment.ViewBindingDialogFragment
|
||||||
import org.oxycblt.auxio.util.androidActivityViewModels
|
import org.oxycblt.auxio.util.androidActivityViewModels
|
||||||
import org.oxycblt.auxio.util.collectImmediately
|
import org.oxycblt.auxio.util.collectImmediately
|
||||||
import org.oxycblt.auxio.util.formatDurationMs
|
import org.oxycblt.auxio.music.formatDurationMs
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A dialog displayed when "View properties" is selected on a song, showing more information about
|
* A dialog displayed when "View properties" is selected on a song, showing more information about
|
||||||
|
|
|
@ -34,7 +34,7 @@ import org.oxycblt.auxio.ui.recycler.Item
|
||||||
import org.oxycblt.auxio.ui.recycler.MenuItemListener
|
import org.oxycblt.auxio.ui.recycler.MenuItemListener
|
||||||
import org.oxycblt.auxio.ui.recycler.SimpleItemCallback
|
import org.oxycblt.auxio.ui.recycler.SimpleItemCallback
|
||||||
import org.oxycblt.auxio.util.context
|
import org.oxycblt.auxio.util.context
|
||||||
import org.oxycblt.auxio.util.formatDurationMs
|
import org.oxycblt.auxio.music.formatDurationMs
|
||||||
import org.oxycblt.auxio.util.getPlural
|
import org.oxycblt.auxio.util.getPlural
|
||||||
import org.oxycblt.auxio.util.inflater
|
import org.oxycblt.auxio.util.inflater
|
||||||
|
|
||||||
|
|
|
@ -29,7 +29,7 @@ import org.oxycblt.auxio.ui.recycler.Item
|
||||||
import org.oxycblt.auxio.ui.recycler.SimpleItemCallback
|
import org.oxycblt.auxio.ui.recycler.SimpleItemCallback
|
||||||
import org.oxycblt.auxio.ui.recycler.SongViewHolder
|
import org.oxycblt.auxio.ui.recycler.SongViewHolder
|
||||||
import org.oxycblt.auxio.util.context
|
import org.oxycblt.auxio.util.context
|
||||||
import org.oxycblt.auxio.util.formatDurationMs
|
import org.oxycblt.auxio.music.formatDurationMs
|
||||||
import org.oxycblt.auxio.util.getPlural
|
import org.oxycblt.auxio.util.getPlural
|
||||||
import org.oxycblt.auxio.util.inflater
|
import org.oxycblt.auxio.util.inflater
|
||||||
|
|
||||||
|
|
|
@ -34,8 +34,8 @@ import org.oxycblt.auxio.ui.recycler.Item
|
||||||
import org.oxycblt.auxio.ui.recycler.MenuItemListener
|
import org.oxycblt.auxio.ui.recycler.MenuItemListener
|
||||||
import org.oxycblt.auxio.ui.recycler.SyncListDiffer
|
import org.oxycblt.auxio.ui.recycler.SyncListDiffer
|
||||||
import org.oxycblt.auxio.util.collectImmediately
|
import org.oxycblt.auxio.util.collectImmediately
|
||||||
import org.oxycblt.auxio.util.formatDurationMs
|
import org.oxycblt.auxio.music.formatDurationMs
|
||||||
import org.oxycblt.auxio.util.secsToMs
|
import org.oxycblt.auxio.music.secsToMs
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A [HomeListFragment] for showing a list of [Album]s.
|
* A [HomeListFragment] for showing a list of [Album]s.
|
||||||
|
|
|
@ -32,7 +32,7 @@ import org.oxycblt.auxio.ui.recycler.Item
|
||||||
import org.oxycblt.auxio.ui.recycler.MenuItemListener
|
import org.oxycblt.auxio.ui.recycler.MenuItemListener
|
||||||
import org.oxycblt.auxio.ui.recycler.SyncListDiffer
|
import org.oxycblt.auxio.ui.recycler.SyncListDiffer
|
||||||
import org.oxycblt.auxio.util.collectImmediately
|
import org.oxycblt.auxio.util.collectImmediately
|
||||||
import org.oxycblt.auxio.util.formatDurationMs
|
import org.oxycblt.auxio.music.formatDurationMs
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A [HomeListFragment] for showing a list of [Artist]s.
|
* A [HomeListFragment] for showing a list of [Artist]s.
|
||||||
|
|
|
@ -32,7 +32,7 @@ import org.oxycblt.auxio.ui.recycler.Item
|
||||||
import org.oxycblt.auxio.ui.recycler.MenuItemListener
|
import org.oxycblt.auxio.ui.recycler.MenuItemListener
|
||||||
import org.oxycblt.auxio.ui.recycler.SyncListDiffer
|
import org.oxycblt.auxio.ui.recycler.SyncListDiffer
|
||||||
import org.oxycblt.auxio.util.collectImmediately
|
import org.oxycblt.auxio.util.collectImmediately
|
||||||
import org.oxycblt.auxio.util.formatDurationMs
|
import org.oxycblt.auxio.music.formatDurationMs
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A [HomeListFragment] for showing a list of [Genre]s.
|
* A [HomeListFragment] for showing a list of [Genre]s.
|
||||||
|
|
|
@ -36,8 +36,8 @@ import org.oxycblt.auxio.ui.recycler.SongViewHolder
|
||||||
import org.oxycblt.auxio.ui.recycler.SyncListDiffer
|
import org.oxycblt.auxio.ui.recycler.SyncListDiffer
|
||||||
import org.oxycblt.auxio.util.collectImmediately
|
import org.oxycblt.auxio.util.collectImmediately
|
||||||
import org.oxycblt.auxio.util.context
|
import org.oxycblt.auxio.util.context
|
||||||
import org.oxycblt.auxio.util.formatDurationMs
|
import org.oxycblt.auxio.music.formatDurationMs
|
||||||
import org.oxycblt.auxio.util.secsToMs
|
import org.oxycblt.auxio.music.secsToMs
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A [HomeListFragment] for showing a list of [Song]s.
|
* A [HomeListFragment] for showing a list of [Song]s.
|
||||||
|
|
|
@ -23,18 +23,12 @@ import android.content.Context
|
||||||
import android.database.Cursor
|
import android.database.Cursor
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.provider.MediaStore
|
import android.provider.MediaStore
|
||||||
|
import android.text.format.DateUtils
|
||||||
import androidx.core.text.isDigitsOnly
|
import androidx.core.text.isDigitsOnly
|
||||||
import org.oxycblt.auxio.R
|
import org.oxycblt.auxio.R
|
||||||
import org.oxycblt.auxio.util.nonZeroOrNull
|
import org.oxycblt.auxio.util.logD
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
|
||||||
/** Shortcut to resolve a year from a nullable date. Will return "No Date" if it is null. */
|
|
||||||
fun Date?.resolveYear(context: Context) =
|
|
||||||
this?.resolveYear(context) ?: context.getString(R.string.def_date)
|
|
||||||
|
|
||||||
/** Converts this string to a UUID, or returns null if it is not valid. */
|
|
||||||
fun String.toUuid() = try { UUID.fromString(this) } catch (e: IllegalArgumentException) { null }
|
|
||||||
|
|
||||||
/** Shortcut for making a [ContentResolver] query with less superfluous arguments. */
|
/** Shortcut for making a [ContentResolver] query with less superfluous arguments. */
|
||||||
fun ContentResolver.queryCursor(
|
fun ContentResolver.queryCursor(
|
||||||
uri: Uri,
|
uri: Uri,
|
||||||
|
@ -65,3 +59,61 @@ val Long.audioUri: Uri
|
||||||
/** Converts a [Long] Album ID into a URI pointing to MediaStore-cached album art. */
|
/** Converts a [Long] Album ID into a URI pointing to MediaStore-cached album art. */
|
||||||
val Long.albumCoverUri: Uri
|
val Long.albumCoverUri: Uri
|
||||||
get() = ContentUris.withAppendedId(EXTERNAL_ALBUM_ART_URI, this)
|
get() = ContentUris.withAppendedId(EXTERNAL_ALBUM_ART_URI, this)
|
||||||
|
|
||||||
|
|
||||||
|
/** Shortcut to resolve a year from a nullable date. Will return "No Date" if it is null. */
|
||||||
|
fun Date?.resolveYear(context: Context) =
|
||||||
|
this?.resolveYear(context) ?: context.getString(R.string.def_date)
|
||||||
|
|
||||||
|
/** Converts this string to a UUID, or returns null if it is not valid. */
|
||||||
|
fun String.toUuid() = try { UUID.fromString(this) } catch (e: IllegalArgumentException) { null }
|
||||||
|
|
||||||
|
/** Converts a long in milliseconds to a long in deci-seconds */
|
||||||
|
fun Long.msToDs() = floorDiv(100)
|
||||||
|
|
||||||
|
/** Converts a long in milliseconds to a long in seconds */
|
||||||
|
fun Long.msToSecs() = floorDiv(1000)
|
||||||
|
|
||||||
|
/** Converts a long in deci-seconds to a long in milliseconds. */
|
||||||
|
fun Long.dsToMs() = times(100)
|
||||||
|
|
||||||
|
/** Converts a long in deci-seconds to a long in seconds. */
|
||||||
|
fun Long.dsToSecs() = floorDiv(10)
|
||||||
|
|
||||||
|
/** Converts a long in seconds to a long in milliseconds. */
|
||||||
|
fun Long.secsToMs() = times(1000)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a [Long] of milliseconds into a string duration.
|
||||||
|
* @param isElapsed Whether this duration is represents elapsed time. If this is false, then --:--
|
||||||
|
* will be returned if the second value is 0.
|
||||||
|
*/
|
||||||
|
fun Long.formatDurationMs(isElapsed: Boolean) = msToSecs().formatDurationSecs(isElapsed)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a [Long] of deci-seconds into a string duration.
|
||||||
|
* @param isElapsed Whether this duration is represents elapsed time. If this is false, then --:--
|
||||||
|
* will be returned if the second value is 0.
|
||||||
|
*/
|
||||||
|
fun Long.formatDurationDs(isElapsed: Boolean) = dsToSecs().formatDurationSecs(isElapsed)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a [Long] of seconds into a string duration.
|
||||||
|
* @param isElapsed Whether this duration is represents elapsed time. If this is false, then --:--
|
||||||
|
* will be returned if the second value is 0.
|
||||||
|
*/
|
||||||
|
fun Long.formatDurationSecs(isElapsed: Boolean): String {
|
||||||
|
if (!isElapsed && this == 0L) {
|
||||||
|
logD("Non-elapsed duration is zero, using --:--")
|
||||||
|
return "--:--"
|
||||||
|
}
|
||||||
|
|
||||||
|
var durationString = DateUtils.formatElapsedTime(this)
|
||||||
|
|
||||||
|
// If the duration begins with a excess zero [e.g 01:42], then cut it off.
|
||||||
|
if (durationString[0] == '0') {
|
||||||
|
durationString = durationString.slice(1 until durationString.length)
|
||||||
|
}
|
||||||
|
|
||||||
|
return durationString
|
||||||
|
}
|
|
@ -45,15 +45,15 @@ import org.oxycblt.auxio.util.logW
|
||||||
*
|
*
|
||||||
* @author OxygenCobalt
|
* @author OxygenCobalt
|
||||||
*/
|
*/
|
||||||
class ExoPlayerBackend(private val inner: MediaStoreBackend) : Indexer.Backend {
|
class ExoPlayerBackend(private val context: Context, private val inner: MediaStoreBackend) : Indexer.Backend {
|
||||||
|
private val settings = Settings(context)
|
||||||
private val taskPool: Array<Task?> = arrayOfNulls(TASK_CAPACITY)
|
private val taskPool: Array<Task?> = arrayOfNulls(TASK_CAPACITY)
|
||||||
|
|
||||||
// No need to implement our own query logic, as this backend is still reliant on
|
// No need to implement our own query logic, as this backend is still reliant on
|
||||||
// MediaStore.
|
// MediaStore.
|
||||||
override fun query(context: Context) = inner.query(context)
|
override fun query() = inner.query()
|
||||||
|
|
||||||
override fun buildSongs(
|
override fun buildSongs(
|
||||||
context: Context,
|
|
||||||
cursor: Cursor,
|
cursor: Cursor,
|
||||||
emitIndexing: (Indexer.Indexing) -> Unit
|
emitIndexing: (Indexer.Indexing) -> Unit
|
||||||
): List<Song> {
|
): List<Song> {
|
||||||
|
@ -82,11 +82,11 @@ class ExoPlayerBackend(private val inner: MediaStoreBackend) : Indexer.Backend {
|
||||||
if (song != null) {
|
if (song != null) {
|
||||||
songs.add(song)
|
songs.add(song)
|
||||||
emitIndexing(Indexer.Indexing.Songs(songs.size, total))
|
emitIndexing(Indexer.Indexing.Songs(songs.size, total))
|
||||||
taskPool[i] = Task(context, raw)
|
taskPool[i] = Task(context, settings, raw)
|
||||||
break@spin
|
break@spin
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
taskPool[i] = Task(context, raw)
|
taskPool[i] = Task(context, settings, raw)
|
||||||
break@spin
|
break@spin
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -122,8 +122,7 @@ class ExoPlayerBackend(private val inner: MediaStoreBackend) : Indexer.Backend {
|
||||||
* Wraps an ExoPlayer metadata retrieval task in a safe abstraction. Access is done with [get].
|
* Wraps an ExoPlayer metadata retrieval task in a safe abstraction. Access is done with [get].
|
||||||
* @author OxygenCobalt
|
* @author OxygenCobalt
|
||||||
*/
|
*/
|
||||||
class Task(context: Context, private val raw: Song.Raw) {
|
class Task(context: Context, private val settings: Settings, private val raw: Song.Raw) {
|
||||||
private val settings = Settings(context)
|
|
||||||
private val future =
|
private val future =
|
||||||
MetadataRetriever.retrieveMetadata(
|
MetadataRetriever.retrieveMetadata(
|
||||||
context,
|
context,
|
||||||
|
|
|
@ -128,8 +128,6 @@ class Indexer {
|
||||||
* complete, a new completion state will be pushed to each callback.
|
* complete, a new completion state will be pushed to each callback.
|
||||||
*/
|
*/
|
||||||
suspend fun index(context: Context) {
|
suspend fun index(context: Context) {
|
||||||
requireBackgroundThread()
|
|
||||||
|
|
||||||
val handle = guard.newHandle()
|
val handle = guard.newHandle()
|
||||||
|
|
||||||
val notGranted =
|
val notGranted =
|
||||||
|
@ -203,20 +201,20 @@ class Indexer {
|
||||||
|
|
||||||
val mediaStoreBackend =
|
val mediaStoreBackend =
|
||||||
when {
|
when {
|
||||||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.R -> Api30MediaStoreBackend()
|
Build.VERSION.SDK_INT >= Build.VERSION_CODES.R -> Api30MediaStoreBackend(context)
|
||||||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q -> Api29MediaStoreBackend()
|
Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q -> Api29MediaStoreBackend(context)
|
||||||
else -> Api21MediaStoreBackend()
|
else -> Api21MediaStoreBackend(context)
|
||||||
}
|
}
|
||||||
|
|
||||||
val settings = Settings(context)
|
val settings = Settings(context)
|
||||||
val backend =
|
val backend =
|
||||||
if (settings.useQualityTags) {
|
if (settings.useQualityTags) {
|
||||||
ExoPlayerBackend(mediaStoreBackend)
|
ExoPlayerBackend(context, mediaStoreBackend)
|
||||||
} else {
|
} else {
|
||||||
mediaStoreBackend
|
mediaStoreBackend
|
||||||
}
|
}
|
||||||
|
|
||||||
val songs = buildSongs(context, backend, handle)
|
val songs = buildSongs(backend, handle)
|
||||||
if (songs.isEmpty()) {
|
if (songs.isEmpty()) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
@ -243,16 +241,16 @@ class Indexer {
|
||||||
* [buildGenres] functions must be called with the returned list so that all songs are properly
|
* [buildGenres] functions must be called with the returned list so that all songs are properly
|
||||||
* linked up.
|
* linked up.
|
||||||
*/
|
*/
|
||||||
private fun buildSongs(context: Context, backend: Backend, handle: Long): List<Song> {
|
private fun buildSongs(backend: Backend, handle: Long): List<Song> {
|
||||||
val start = System.currentTimeMillis()
|
val start = System.currentTimeMillis()
|
||||||
|
|
||||||
var songs =
|
var songs =
|
||||||
backend.query(context).use { cursor ->
|
backend.query().use { cursor ->
|
||||||
logD(
|
logD(
|
||||||
"Successfully queried media database " +
|
"Successfully queried media database " +
|
||||||
"in ${System.currentTimeMillis() - start}ms")
|
"in ${System.currentTimeMillis() - start}ms")
|
||||||
|
|
||||||
backend.buildSongs(context, cursor) { emitIndexing(it, handle) }
|
backend.buildSongs(cursor) { emitIndexing(it, handle) }
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deduplicate songs to prevent (most) deformed music clones
|
// Deduplicate songs to prevent (most) deformed music clones
|
||||||
|
@ -425,11 +423,10 @@ class Indexer {
|
||||||
/** Represents a backend that metadata can be extracted from. */
|
/** Represents a backend that metadata can be extracted from. */
|
||||||
interface Backend {
|
interface Backend {
|
||||||
/** Query the media database for a basic cursor. */
|
/** Query the media database for a basic cursor. */
|
||||||
fun query(context: Context): Cursor
|
fun query(): Cursor
|
||||||
|
|
||||||
/** Create a list of songs from the [Cursor] queried in [query]. */
|
/** Create a list of songs from the [Cursor] queried in [query]. */
|
||||||
fun buildSongs(
|
fun buildSongs(
|
||||||
context: Context,
|
|
||||||
cursor: Cursor,
|
cursor: Cursor,
|
||||||
emitIndexing: (Indexing) -> Unit
|
emitIndexing: (Indexing) -> Unit
|
||||||
): List<Song>
|
): List<Song>
|
||||||
|
|
|
@ -91,7 +91,6 @@ import org.oxycblt.auxio.util.logD
|
||||||
* I wish I was born in the neolithic.
|
* I wish I was born in the neolithic.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// TODO: Make context a member var to cache Settings
|
|
||||||
// TODO: Move duration util to MusicUtil
|
// TODO: Move duration util to MusicUtil
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -99,7 +98,7 @@ import org.oxycblt.auxio.util.logD
|
||||||
* not a fully-featured class by itself, and it's API-specific derivatives should be used instead.
|
* not a fully-featured class by itself, and it's API-specific derivatives should be used instead.
|
||||||
* @author OxygenCobalt
|
* @author OxygenCobalt
|
||||||
*/
|
*/
|
||||||
abstract class MediaStoreBackend : Indexer.Backend {
|
abstract class MediaStoreBackend(private val context: Context) : Indexer.Backend {
|
||||||
private var idIndex = -1
|
private var idIndex = -1
|
||||||
private var titleIndex = -1
|
private var titleIndex = -1
|
||||||
private var displayNameIndex = -1
|
private var displayNameIndex = -1
|
||||||
|
@ -113,10 +112,10 @@ abstract class MediaStoreBackend : Indexer.Backend {
|
||||||
private var artistIndex = -1
|
private var artistIndex = -1
|
||||||
private var albumArtistIndex = -1
|
private var albumArtistIndex = -1
|
||||||
|
|
||||||
|
private val settings = Settings(context)
|
||||||
protected val volumes = mutableListOf<StorageVolume>()
|
protected val volumes = mutableListOf<StorageVolume>()
|
||||||
|
|
||||||
override fun query(context: Context): Cursor {
|
override fun query(): Cursor {
|
||||||
val settings = Settings(context)
|
|
||||||
val storageManager = context.getSystemServiceCompat(StorageManager::class)
|
val storageManager = context.getSystemServiceCompat(StorageManager::class)
|
||||||
volumes.addAll(storageManager.storageVolumesCompat)
|
volumes.addAll(storageManager.storageVolumesCompat)
|
||||||
val dirs = settings.getMusicDirs(storageManager)
|
val dirs = settings.getMusicDirs(storageManager)
|
||||||
|
@ -162,12 +161,9 @@ abstract class MediaStoreBackend : Indexer.Backend {
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun buildSongs(
|
override fun buildSongs(
|
||||||
context: Context,
|
|
||||||
cursor: Cursor,
|
cursor: Cursor,
|
||||||
emitIndexing: (Indexer.Indexing) -> Unit
|
emitIndexing: (Indexer.Indexing) -> Unit
|
||||||
): List<Song> {
|
): List<Song> {
|
||||||
val settings = Settings(context)
|
|
||||||
|
|
||||||
val rawSongs = mutableListOf<Song.Raw>()
|
val rawSongs = mutableListOf<Song.Raw>()
|
||||||
while (cursor.moveToNext()) {
|
while (cursor.moveToNext()) {
|
||||||
rawSongs.add(buildRawSong(context, cursor))
|
rawSongs.add(buildRawSong(context, cursor))
|
||||||
|
@ -255,8 +251,6 @@ abstract class MediaStoreBackend : Indexer.Backend {
|
||||||
* outlined in [projection].
|
* outlined in [projection].
|
||||||
*/
|
*/
|
||||||
open fun buildRawSong(context: Context, cursor: Cursor): Song.Raw {
|
open fun buildRawSong(context: Context, cursor: Cursor): Song.Raw {
|
||||||
val settings = Settings(context)
|
|
||||||
|
|
||||||
// Initialize our cursor indices if we haven't already.
|
// Initialize our cursor indices if we haven't already.
|
||||||
if (idIndex == -1) {
|
if (idIndex == -1) {
|
||||||
idIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns._ID)
|
idIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns._ID)
|
||||||
|
@ -349,7 +343,7 @@ abstract class MediaStoreBackend : Indexer.Backend {
|
||||||
* A [MediaStoreBackend] that completes the music loading process in a way compatible from
|
* A [MediaStoreBackend] that completes the music loading process in a way compatible from
|
||||||
* @author OxygenCobalt
|
* @author OxygenCobalt
|
||||||
*/
|
*/
|
||||||
class Api21MediaStoreBackend : MediaStoreBackend() {
|
class Api21MediaStoreBackend(context: Context) : MediaStoreBackend(context) {
|
||||||
private var trackIndex = -1
|
private var trackIndex = -1
|
||||||
private var dataIndex = -1
|
private var dataIndex = -1
|
||||||
|
|
||||||
|
@ -414,7 +408,7 @@ class Api21MediaStoreBackend : MediaStoreBackend() {
|
||||||
* @author OxygenCobalt
|
* @author OxygenCobalt
|
||||||
*/
|
*/
|
||||||
@RequiresApi(Build.VERSION_CODES.Q)
|
@RequiresApi(Build.VERSION_CODES.Q)
|
||||||
open class BaseApi29MediaStoreBackend : MediaStoreBackend() {
|
open class BaseApi29MediaStoreBackend(context: Context) : MediaStoreBackend(context) {
|
||||||
private var volumeIndex = -1
|
private var volumeIndex = -1
|
||||||
private var relativePathIndex = -1
|
private var relativePathIndex = -1
|
||||||
|
|
||||||
|
@ -466,7 +460,7 @@ open class BaseApi29MediaStoreBackend : MediaStoreBackend() {
|
||||||
* @author OxygenCobalt
|
* @author OxygenCobalt
|
||||||
*/
|
*/
|
||||||
@RequiresApi(Build.VERSION_CODES.Q)
|
@RequiresApi(Build.VERSION_CODES.Q)
|
||||||
open class Api29MediaStoreBackend : BaseApi29MediaStoreBackend() {
|
open class Api29MediaStoreBackend(context: Context) : BaseApi29MediaStoreBackend(context) {
|
||||||
private var trackIndex = -1
|
private var trackIndex = -1
|
||||||
|
|
||||||
override val projection: Array<String>
|
override val projection: Array<String>
|
||||||
|
@ -497,7 +491,7 @@ open class Api29MediaStoreBackend : BaseApi29MediaStoreBackend() {
|
||||||
* @author OxygenCobalt
|
* @author OxygenCobalt
|
||||||
*/
|
*/
|
||||||
@RequiresApi(Build.VERSION_CODES.R)
|
@RequiresApi(Build.VERSION_CODES.R)
|
||||||
class Api30MediaStoreBackend : BaseApi29MediaStoreBackend() {
|
class Api30MediaStoreBackend(context: Context) : BaseApi29MediaStoreBackend(context) {
|
||||||
private var trackIndex: Int = -1
|
private var trackIndex: Int = -1
|
||||||
private var discIndex: Int = -1
|
private var discIndex: Int = -1
|
||||||
|
|
||||||
|
|
|
@ -34,7 +34,7 @@ import org.oxycblt.auxio.util.androidActivityViewModels
|
||||||
import org.oxycblt.auxio.util.collectImmediately
|
import org.oxycblt.auxio.util.collectImmediately
|
||||||
import org.oxycblt.auxio.util.getAttrColorCompat
|
import org.oxycblt.auxio.util.getAttrColorCompat
|
||||||
import org.oxycblt.auxio.util.getColorCompat
|
import org.oxycblt.auxio.util.getColorCompat
|
||||||
import org.oxycblt.auxio.util.msToDs
|
import org.oxycblt.auxio.music.msToDs
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A fragment showing the current playback state in a compact manner. Used as the bar for the
|
* A fragment showing the current playback state in a compact manner. Used as the bar for the
|
||||||
|
|
|
@ -35,7 +35,7 @@ import org.oxycblt.auxio.playback.state.RepeatMode
|
||||||
import org.oxycblt.auxio.ui.MainNavigationAction
|
import org.oxycblt.auxio.ui.MainNavigationAction
|
||||||
import org.oxycblt.auxio.ui.fragment.MenuFragment
|
import org.oxycblt.auxio.ui.fragment.MenuFragment
|
||||||
import org.oxycblt.auxio.util.collectImmediately
|
import org.oxycblt.auxio.util.collectImmediately
|
||||||
import org.oxycblt.auxio.util.msToDs
|
import org.oxycblt.auxio.music.msToDs
|
||||||
import org.oxycblt.auxio.util.showToast
|
import org.oxycblt.auxio.util.showToast
|
||||||
import org.oxycblt.auxio.util.systemBarInsetsCompat
|
import org.oxycblt.auxio.util.systemBarInsetsCompat
|
||||||
|
|
||||||
|
|
|
@ -36,9 +36,9 @@ import org.oxycblt.auxio.playback.state.PlaybackStateManager
|
||||||
import org.oxycblt.auxio.playback.state.RepeatMode
|
import org.oxycblt.auxio.playback.state.RepeatMode
|
||||||
import org.oxycblt.auxio.settings.Settings
|
import org.oxycblt.auxio.settings.Settings
|
||||||
import org.oxycblt.auxio.util.application
|
import org.oxycblt.auxio.util.application
|
||||||
import org.oxycblt.auxio.util.dsToMs
|
import org.oxycblt.auxio.music.dsToMs
|
||||||
import org.oxycblt.auxio.util.logE
|
import org.oxycblt.auxio.util.logE
|
||||||
import org.oxycblt.auxio.util.msToDs
|
import org.oxycblt.auxio.music.msToDs
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The ViewModel that provides a UI frontend for [PlaybackStateManager].
|
* The ViewModel that provides a UI frontend for [PlaybackStateManager].
|
||||||
|
|
|
@ -22,7 +22,7 @@ import android.util.AttributeSet
|
||||||
import com.google.android.material.slider.Slider
|
import com.google.android.material.slider.Slider
|
||||||
import kotlin.math.max
|
import kotlin.math.max
|
||||||
import org.oxycblt.auxio.databinding.ViewSeekBarBinding
|
import org.oxycblt.auxio.databinding.ViewSeekBarBinding
|
||||||
import org.oxycblt.auxio.util.formatDurationDs
|
import org.oxycblt.auxio.music.formatDurationDs
|
||||||
import org.oxycblt.auxio.util.inflater
|
import org.oxycblt.auxio.util.inflater
|
||||||
import org.oxycblt.auxio.util.logD
|
import org.oxycblt.auxio.util.logD
|
||||||
|
|
||||||
|
|
|
@ -39,7 +39,7 @@ import org.oxycblt.auxio.music.Song
|
||||||
import org.oxycblt.auxio.ui.fragment.ViewBindingFragment
|
import org.oxycblt.auxio.ui.fragment.ViewBindingFragment
|
||||||
import org.oxycblt.auxio.util.androidActivityViewModels
|
import org.oxycblt.auxio.util.androidActivityViewModels
|
||||||
import org.oxycblt.auxio.util.collectImmediately
|
import org.oxycblt.auxio.util.collectImmediately
|
||||||
import org.oxycblt.auxio.util.formatDurationMs
|
import org.oxycblt.auxio.music.formatDurationMs
|
||||||
import org.oxycblt.auxio.util.logD
|
import org.oxycblt.auxio.util.logD
|
||||||
import org.oxycblt.auxio.util.showToast
|
import org.oxycblt.auxio.util.showToast
|
||||||
import org.oxycblt.auxio.util.systemBarInsetsCompat
|
import org.oxycblt.auxio.util.systemBarInsetsCompat
|
||||||
|
|
|
@ -50,55 +50,6 @@ fun Int.nonZeroOrNull() = if (this > 0) this else null
|
||||||
/** Returns null if this value is not in [range]. */
|
/** Returns null if this value is not in [range]. */
|
||||||
fun Int.inRangeOrNull(range: IntRange) = if (range.contains(this)) this else null
|
fun Int.inRangeOrNull(range: IntRange) = if (range.contains(this)) this else null
|
||||||
|
|
||||||
/** Converts a long in milliseconds to a long in deci-seconds */
|
|
||||||
fun Long.msToDs() = floorDiv(100)
|
|
||||||
|
|
||||||
/** Converts a long in milliseconds to a long in seconds */
|
|
||||||
fun Long.msToSecs() = floorDiv(1000)
|
|
||||||
|
|
||||||
/** Converts a long in deci-seconds to a long in milliseconds. */
|
|
||||||
fun Long.dsToMs() = times(100)
|
|
||||||
|
|
||||||
/** Converts a long in deci-seconds to a long in seconds. */
|
|
||||||
fun Long.dsToSecs() = floorDiv(10)
|
|
||||||
|
|
||||||
/** Converts a long in seconds to a long in milliseconds. */
|
|
||||||
fun Long.secsToMs() = times(1000)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Convert a [Long] of milliseconds into a string duration.
|
|
||||||
* @param isElapsed Whether this duration is represents elapsed time. If this is false, then --:--
|
|
||||||
* will be returned if the second value is 0.
|
|
||||||
*/
|
|
||||||
fun Long.formatDurationMs(isElapsed: Boolean) = msToSecs().formatDurationSecs(isElapsed)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Convert a [Long] of deci-seconds into a string duration.
|
|
||||||
* @param isElapsed Whether this duration is represents elapsed time. If this is false, then --:--
|
|
||||||
* will be returned if the second value is 0.
|
|
||||||
*/
|
|
||||||
fun Long.formatDurationDs(isElapsed: Boolean) = dsToSecs().formatDurationSecs(isElapsed)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Convert a [Long] of seconds into a string duration.
|
|
||||||
* @param isElapsed Whether this duration is represents elapsed time. If this is false, then --:--
|
|
||||||
* will be returned if the second value is 0.
|
|
||||||
*/
|
|
||||||
fun Long.formatDurationSecs(isElapsed: Boolean): String {
|
|
||||||
if (!isElapsed && this == 0L) {
|
|
||||||
logD("Non-elapsed duration is zero, using --:--")
|
|
||||||
return "--:--"
|
|
||||||
}
|
|
||||||
|
|
||||||
var durationString = DateUtils.formatElapsedTime(this)
|
|
||||||
|
|
||||||
// If the duration begins with a excess zero [e.g 01:42], then cut it off.
|
|
||||||
if (durationString[0] == '0') {
|
|
||||||
durationString = durationString.slice(1 until durationString.length)
|
|
||||||
}
|
|
||||||
|
|
||||||
return durationString
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Lazily reflect to retrieve a [Field]. */
|
/** Lazily reflect to retrieve a [Field]. */
|
||||||
fun lazyReflectedField(clazz: KClass<*>, field: String) = lazy {
|
fun lazyReflectedField(clazz: KClass<*>, field: String) = lazy {
|
||||||
|
|
Loading…
Reference in a new issue