music: move utils around

Move some miscellanious utils around.
This commit is contained in:
Alexander Capehart 2022-09-08 19:01:21 -06:00
parent 4c954e83b0
commit 2e71342e1c
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
17 changed files with 97 additions and 104 deletions

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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.

View file

@ -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.

View file

@ -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.

View file

@ -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.

View file

@ -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
}

View file

@ -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,

View file

@ -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>

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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].

View file

@ -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

View file

@ -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

View file

@ -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 {