music: refine new mediastoreextractor impl
- Make the interpreters use a more conventional naming structure - Remove the redundant file name extraction that is largely an artifact of older versions
This commit is contained in:
parent
6b9f6862af
commit
ed519eeccc
3 changed files with 70 additions and 78 deletions
|
@ -74,37 +74,31 @@ class SongImpl(
|
||||||
}
|
}
|
||||||
override val name =
|
override val name =
|
||||||
nameFactory.parse(
|
nameFactory.parse(
|
||||||
requireNotNull(rawSong.name) { "Invalid raw ${rawSong.fileName}: No title" },
|
requireNotNull(rawSong.name) { "Invalid raw ${rawSong.path}: No title" },
|
||||||
rawSong.sortName)
|
rawSong.sortName)
|
||||||
|
|
||||||
override val track = rawSong.track
|
override val track = rawSong.track
|
||||||
override val disc = rawSong.disc?.let { Disc(it, rawSong.subtitle) }
|
override val disc = rawSong.disc?.let { Disc(it, rawSong.subtitle) }
|
||||||
override val date = rawSong.date
|
override val date = rawSong.date
|
||||||
override val uri =
|
override val uri =
|
||||||
requireNotNull(rawSong.mediaStoreId) { "Invalid raw ${rawSong.fileName}: No id" }
|
requireNotNull(rawSong.mediaStoreId) { "Invalid raw ${rawSong.path}: No id" }.toAudioUri()
|
||||||
.toAudioUri()
|
override val path = requireNotNull(rawSong.path) { "Invalid raw ${rawSong.path}: No path" }
|
||||||
override val path =
|
|
||||||
requireNotNull(rawSong.directory) { "Invalid raw ${rawSong.fileName}: No parent directory" }
|
|
||||||
.file(
|
|
||||||
requireNotNull(rawSong.fileName) {
|
|
||||||
"Invalid raw ${rawSong.fileName}: No display name"
|
|
||||||
})
|
|
||||||
override val mimeType =
|
override val mimeType =
|
||||||
MimeType(
|
MimeType(
|
||||||
fromExtension =
|
fromExtension =
|
||||||
requireNotNull(rawSong.extensionMimeType) {
|
requireNotNull(rawSong.extensionMimeType) {
|
||||||
"Invalid raw ${rawSong.fileName}: No mime type"
|
"Invalid raw ${rawSong.path}: No mime type"
|
||||||
},
|
},
|
||||||
fromFormat = null)
|
fromFormat = null)
|
||||||
override val size = requireNotNull(rawSong.size) { "Invalid raw ${rawSong.fileName}: No size" }
|
override val size = requireNotNull(rawSong.size) { "Invalid raw ${rawSong.path}: No size" }
|
||||||
override val durationMs =
|
override val durationMs =
|
||||||
requireNotNull(rawSong.durationMs) { "Invalid raw ${rawSong.fileName}: No duration" }
|
requireNotNull(rawSong.durationMs) { "Invalid raw ${rawSong.path}: No duration" }
|
||||||
override val replayGainAdjustment =
|
override val replayGainAdjustment =
|
||||||
ReplayGainAdjustment(
|
ReplayGainAdjustment(
|
||||||
track = rawSong.replayGainTrackAdjustment, album = rawSong.replayGainAlbumAdjustment)
|
track = rawSong.replayGainTrackAdjustment, album = rawSong.replayGainAlbumAdjustment)
|
||||||
|
|
||||||
override val dateAdded =
|
override val dateAdded =
|
||||||
requireNotNull(rawSong.dateAdded) { "Invalid raw ${rawSong.fileName}: No date added" }
|
requireNotNull(rawSong.dateAdded) { "Invalid raw ${rawSong.path}: No date added" }
|
||||||
|
|
||||||
private var _album: AlbumImpl? = null
|
private var _album: AlbumImpl? = null
|
||||||
override val album: Album
|
override val album: Album
|
||||||
|
@ -170,12 +164,12 @@ class SongImpl(
|
||||||
RawAlbum(
|
RawAlbum(
|
||||||
mediaStoreId =
|
mediaStoreId =
|
||||||
requireNotNull(rawSong.albumMediaStoreId) {
|
requireNotNull(rawSong.albumMediaStoreId) {
|
||||||
"Invalid raw ${rawSong.fileName}: No album id"
|
"Invalid raw ${rawSong.path}: No album id"
|
||||||
},
|
},
|
||||||
musicBrainzId = rawSong.albumMusicBrainzId?.toUuidOrNull(),
|
musicBrainzId = rawSong.albumMusicBrainzId?.toUuidOrNull(),
|
||||||
name =
|
name =
|
||||||
requireNotNull(rawSong.albumName) {
|
requireNotNull(rawSong.albumName) {
|
||||||
"Invalid raw ${rawSong.fileName}: No album name"
|
"Invalid raw ${rawSong.path}: No album name"
|
||||||
},
|
},
|
||||||
sortName = rawSong.albumSortName,
|
sortName = rawSong.albumSortName,
|
||||||
releaseType = ReleaseType.parse(separators.split(rawSong.releaseTypes)),
|
releaseType = ReleaseType.parse(separators.split(rawSong.releaseTypes)),
|
||||||
|
|
|
@ -42,9 +42,7 @@ data class RawSong(
|
||||||
/** The latest date the [SongImpl]'s audio file was modified, as a unix epoch timestamp. */
|
/** The latest date the [SongImpl]'s audio file was modified, as a unix epoch timestamp. */
|
||||||
var dateModified: Long? = null,
|
var dateModified: Long? = null,
|
||||||
/** @see Song.path */
|
/** @see Song.path */
|
||||||
var fileName: String? = null,
|
var path: Path? = null,
|
||||||
/** @see Song.path */
|
|
||||||
var directory: Path? = null,
|
|
||||||
/** @see Song.size */
|
/** @see Song.size */
|
||||||
var size: Long? = null,
|
var size: Long? = null,
|
||||||
/** @see Song.durationMs */
|
/** @see Song.durationMs */
|
||||||
|
|
|
@ -24,7 +24,6 @@ import android.os.Build
|
||||||
import android.provider.MediaStore
|
import android.provider.MediaStore
|
||||||
import androidx.core.database.getIntOrNull
|
import androidx.core.database.getIntOrNull
|
||||||
import androidx.core.database.getStringOrNull
|
import androidx.core.database.getStringOrNull
|
||||||
import java.io.File
|
|
||||||
import kotlinx.coroutines.channels.Channel
|
import kotlinx.coroutines.channels.Channel
|
||||||
import kotlinx.coroutines.yield
|
import kotlinx.coroutines.yield
|
||||||
import org.oxycblt.auxio.music.cache.Cache
|
import org.oxycblt.auxio.music.cache.Cache
|
||||||
|
@ -119,8 +118,8 @@ interface MediaStoreExtractor {
|
||||||
|
|
||||||
private class MediaStoreExtractorImpl(
|
private class MediaStoreExtractorImpl(
|
||||||
private val context: Context,
|
private val context: Context,
|
||||||
private val pathInterpreterFactory: PathInterpreterFactory,
|
private val pathInterpreterFactory: PathInterpreter.Factory,
|
||||||
private val tagInterpreterFactory: TagInterpreterFactory
|
private val tagInterpreterFactory: TagInterpreter.Factory
|
||||||
) : MediaStoreExtractor {
|
) : MediaStoreExtractor {
|
||||||
override suspend fun query(
|
override suspend fun query(
|
||||||
constraints: MediaStoreExtractor.Constraints
|
constraints: MediaStoreExtractor.Constraints
|
||||||
|
@ -239,8 +238,6 @@ private class MediaStoreExtractorImpl(
|
||||||
) : MediaStoreExtractor.Query {
|
) : MediaStoreExtractor.Query {
|
||||||
private val idIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns._ID)
|
private val idIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns._ID)
|
||||||
private val titleIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns.TITLE)
|
private val titleIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns.TITLE)
|
||||||
private val displayNameIndex =
|
|
||||||
cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns.DISPLAY_NAME)
|
|
||||||
private val mimeTypeIndex =
|
private val mimeTypeIndex =
|
||||||
cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns.MIME_TYPE)
|
cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns.MIME_TYPE)
|
||||||
private val sizeIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns.SIZE)
|
private val sizeIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns.SIZE)
|
||||||
|
@ -267,9 +264,6 @@ private class MediaStoreExtractorImpl(
|
||||||
rawSong.mediaStoreId = cursor.getLong(idIndex)
|
rawSong.mediaStoreId = cursor.getLong(idIndex)
|
||||||
rawSong.dateAdded = cursor.getLong(dateAddedIndex)
|
rawSong.dateAdded = cursor.getLong(dateAddedIndex)
|
||||||
rawSong.dateModified = cursor.getLong(dateModifiedIndex)
|
rawSong.dateModified = cursor.getLong(dateModifiedIndex)
|
||||||
// Try to use the DISPLAY_NAME column to obtain a (probably sane) file name
|
|
||||||
// from the android system.
|
|
||||||
rawSong.fileName = cursor.getStringOrNull(displayNameIndex)
|
|
||||||
rawSong.extensionMimeType = cursor.getString(mimeTypeIndex)
|
rawSong.extensionMimeType = cursor.getString(mimeTypeIndex)
|
||||||
rawSong.albumMediaStoreId = cursor.getLong(albumIdIndex)
|
rawSong.albumMediaStoreId = cursor.getLong(albumIdIndex)
|
||||||
pathInterpreter.populate(rawSong)
|
pathInterpreter.populate(rawSong)
|
||||||
|
@ -289,7 +283,8 @@ private class MediaStoreExtractorImpl(
|
||||||
// A non-existent album name should theoretically be the name of the folder it contained
|
// A non-existent album name should theoretically be the name of the folder it contained
|
||||||
// in, but in practice it is more often "0" (as in /storage/emulated/0), even when it
|
// in, but in practice it is more often "0" (as in /storage/emulated/0), even when it
|
||||||
// the file is not actually in the root internal storage directory. We can't do
|
// the file is not actually in the root internal storage directory. We can't do
|
||||||
// anything to fix this, really.
|
// anything to fix this, really. We also can't really filter it out, since how can we
|
||||||
|
// know when it corresponds to the folder and not, say, Low Roar's breakout album "0"?
|
||||||
rawSong.albumName = cursor.getString(albumIndex)
|
rawSong.albumName = cursor.getString(albumIndex)
|
||||||
// Android does not make a non-existent artist tag null, it instead fills it in
|
// Android does not make a non-existent artist tag null, it instead fills it in
|
||||||
// as <unknown>, which makes absolutely no sense given how other columns default
|
// as <unknown>, which makes absolutely no sense given how other columns default
|
||||||
|
@ -336,7 +331,6 @@ private class MediaStoreExtractorImpl(
|
||||||
MediaStore.Audio.AudioColumns._ID,
|
MediaStore.Audio.AudioColumns._ID,
|
||||||
MediaStore.Audio.AudioColumns.DATE_ADDED,
|
MediaStore.Audio.AudioColumns.DATE_ADDED,
|
||||||
MediaStore.Audio.AudioColumns.DATE_MODIFIED,
|
MediaStore.Audio.AudioColumns.DATE_MODIFIED,
|
||||||
MediaStore.Audio.AudioColumns.DISPLAY_NAME,
|
|
||||||
MediaStore.Audio.AudioColumns.SIZE,
|
MediaStore.Audio.AudioColumns.SIZE,
|
||||||
MediaStore.Audio.AudioColumns.DURATION,
|
MediaStore.Audio.AudioColumns.DURATION,
|
||||||
MediaStore.Audio.AudioColumns.MIME_TYPE,
|
MediaStore.Audio.AudioColumns.MIME_TYPE,
|
||||||
|
@ -349,62 +343,54 @@ private class MediaStoreExtractorImpl(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Interpreter {
|
private interface Interpreter {
|
||||||
fun populate(rawSong: RawSong)
|
fun populate(rawSong: RawSong)
|
||||||
|
|
||||||
|
interface Factory {
|
||||||
|
val projection: Array<String>
|
||||||
|
|
||||||
|
fun wrap(cursor: Cursor): Interpreter
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
interface InterpreterFactory {
|
private sealed interface PathInterpreter : Interpreter {
|
||||||
val projection: Array<String>
|
interface Factory : Interpreter.Factory {
|
||||||
|
override fun wrap(cursor: Cursor): PathInterpreter
|
||||||
|
|
||||||
fun wrap(cursor: Cursor): Interpreter
|
fun createSelector(paths: List<Path>): Selector?
|
||||||
|
|
||||||
|
data class Selector(val template: String, val args: List<String>)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
interface PathInterpreterFactory : InterpreterFactory {
|
private class DataPathInterpreter(
|
||||||
override fun wrap(cursor: Cursor): PathInterpreter
|
private val cursor: Cursor,
|
||||||
|
private val volumeManager: VolumeManager
|
||||||
fun createSelector(paths: List<Path>): Selector?
|
) : PathInterpreter {
|
||||||
|
|
||||||
data class Selector(val template: String, val args: List<String>)
|
|
||||||
}
|
|
||||||
|
|
||||||
interface TagInterpreterFactory : InterpreterFactory {
|
|
||||||
override fun wrap(cursor: Cursor): TagInterpreter
|
|
||||||
}
|
|
||||||
|
|
||||||
sealed interface PathInterpreter : Interpreter
|
|
||||||
|
|
||||||
class DataPathInterpreter(private val cursor: Cursor, private val volumeManager: VolumeManager) :
|
|
||||||
PathInterpreter {
|
|
||||||
private val dataIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns.DATA)
|
private val dataIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns.DATA)
|
||||||
private val volumes = volumeManager.getVolumes()
|
private val volumes = volumeManager.getVolumes()
|
||||||
|
|
||||||
override fun populate(rawSong: RawSong) {
|
override fun populate(rawSong: RawSong) {
|
||||||
val data = cursor.getString(dataIndex)
|
val data = Components.parseUnix(cursor.getString(dataIndex))
|
||||||
// On some OEM devices below API 29, DISPLAY_NAME may not be present. I assume
|
|
||||||
// that this only applies to below API 29, as beyond API 29, this column not being
|
|
||||||
// present would completely break the scoped storage system. Fill it in with DATA
|
|
||||||
// if it's not available.
|
|
||||||
if (rawSong.fileName == null) {
|
|
||||||
rawSong.fileName = data.substringAfterLast(File.separatorChar, "").ifEmpty { null }
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find the volume that transforms the DATA column into a relative path. This is
|
// Find the volume that transforms the DATA column into a relative path. This is
|
||||||
// the Directory we will use.
|
// the Directory we will use.
|
||||||
val rawPath = Components.parseUnix(data)
|
|
||||||
for (volume in volumes) {
|
for (volume in volumes) {
|
||||||
val volumePath = volume.components ?: continue
|
val volumePath = volume.components ?: continue
|
||||||
if (volumePath.contains(rawPath)) {
|
if (volumePath.contains(data)) {
|
||||||
rawSong.directory = Path(volume, rawPath.depth(volumePath.components.size))
|
rawSong.path = Path(volume, data.depth(volumePath.components.size))
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Factory(private val volumeManager: VolumeManager) : PathInterpreterFactory {
|
class Factory(private val volumeManager: VolumeManager) : PathInterpreter.Factory {
|
||||||
override val projection: Array<String>
|
override val projection: Array<String>
|
||||||
get() = arrayOf(MediaStore.Audio.AudioColumns.DATA)
|
get() =
|
||||||
|
arrayOf(
|
||||||
|
MediaStore.Audio.AudioColumns.DISPLAY_NAME, MediaStore.Audio.AudioColumns.DATA)
|
||||||
|
|
||||||
override fun createSelector(paths: List<Path>): PathInterpreterFactory.Selector? {
|
override fun createSelector(paths: List<Path>): PathInterpreter.Factory.Selector? {
|
||||||
val args = mutableListOf<String>()
|
val args = mutableListOf<String>()
|
||||||
var template = ""
|
var template = ""
|
||||||
for (i in paths.indices) {
|
for (i in paths.indices) {
|
||||||
|
@ -423,7 +409,7 @@ class DataPathInterpreter(private val cursor: Cursor, private val volumeManager:
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
return PathInterpreterFactory.Selector(template, args)
|
return PathInterpreter.Factory.Selector(template, args)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun wrap(cursor: Cursor): PathInterpreter =
|
override fun wrap(cursor: Cursor): PathInterpreter =
|
||||||
|
@ -431,8 +417,10 @@ class DataPathInterpreter(private val cursor: Cursor, private val volumeManager:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class VolumePathInterpreter(private val cursor: Cursor, private val volumeManager: VolumeManager) :
|
private class VolumePathInterpreter(private val cursor: Cursor, volumeManager: VolumeManager) :
|
||||||
PathInterpreter {
|
PathInterpreter {
|
||||||
|
private val displayNameIndex =
|
||||||
|
cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns.DISPLAY_NAME)
|
||||||
private val volumeIndex =
|
private val volumeIndex =
|
||||||
cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns.VOLUME_NAME)
|
cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns.VOLUME_NAME)
|
||||||
private val relativePathIndex =
|
private val relativePathIndex =
|
||||||
|
@ -440,22 +428,29 @@ class VolumePathInterpreter(private val cursor: Cursor, private val volumeManage
|
||||||
private val volumes = volumeManager.getVolumes()
|
private val volumes = volumeManager.getVolumes()
|
||||||
|
|
||||||
override fun populate(rawSong: RawSong) {
|
override fun populate(rawSong: RawSong) {
|
||||||
// Find the StorageVolume whose MediaStore name corresponds to this song.
|
// Find the StorageVolume whose MediaStore name corresponds to it.
|
||||||
// This is combined with the plain relative path column to create the directory.
|
|
||||||
val volumeName = cursor.getString(volumeIndex)
|
val volumeName = cursor.getString(volumeIndex)
|
||||||
val relativePath = cursor.getString(relativePathIndex)
|
|
||||||
val volume = volumes.find { it.mediaStoreName == volumeName }
|
val volume = volumes.find { it.mediaStoreName == volumeName }
|
||||||
|
|
||||||
|
// Relative path does not include file name, must use DISPLAY_NAME and add it
|
||||||
|
// in manually.
|
||||||
|
val relativePath = cursor.getString(relativePathIndex)
|
||||||
|
val displayName = cursor.getString(displayNameIndex)
|
||||||
|
val components = Components.parseUnix(relativePath).child(displayName)
|
||||||
|
|
||||||
if (volume != null) {
|
if (volume != null) {
|
||||||
rawSong.directory = Path(volume, Components.parseUnix(relativePath))
|
rawSong.path = Path(volume, components)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Factory(private val volumeManager: VolumeManager) : PathInterpreterFactory {
|
class Factory(private val volumeManager: VolumeManager) : PathInterpreter.Factory {
|
||||||
override val projection: Array<String>
|
override val projection: Array<String>
|
||||||
get() =
|
get() =
|
||||||
arrayOf(
|
arrayOf(
|
||||||
// After API 29, we now have access to the volume name and relative
|
// After API 29, we now have access to the volume name and relative
|
||||||
// path, which simplifies working with Paths significantly.
|
// path, which hopefully are more standard and less likely to break
|
||||||
|
// compared to DATA.
|
||||||
|
MediaStore.Audio.AudioColumns.DISPLAY_NAME,
|
||||||
MediaStore.Audio.AudioColumns.VOLUME_NAME,
|
MediaStore.Audio.AudioColumns.VOLUME_NAME,
|
||||||
MediaStore.Audio.AudioColumns.RELATIVE_PATH)
|
MediaStore.Audio.AudioColumns.RELATIVE_PATH)
|
||||||
|
|
||||||
|
@ -463,7 +458,7 @@ class VolumePathInterpreter(private val cursor: Cursor, private val volumeManage
|
||||||
// of the given directories, albeit with some conversion to the analogous MediaStore
|
// of the given directories, albeit with some conversion to the analogous MediaStore
|
||||||
// column values.
|
// column values.
|
||||||
|
|
||||||
override fun createSelector(paths: List<Path>): PathInterpreterFactory.Selector? {
|
override fun createSelector(paths: List<Path>): PathInterpreter.Factory.Selector? {
|
||||||
val args = mutableListOf<String>()
|
val args = mutableListOf<String>()
|
||||||
var template = ""
|
var template = ""
|
||||||
for (i in paths.indices) {
|
for (i in paths.indices) {
|
||||||
|
@ -488,7 +483,7 @@ class VolumePathInterpreter(private val cursor: Cursor, private val volumeManage
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
return PathInterpreterFactory.Selector(template, args)
|
return PathInterpreter.Factory.Selector(template, args)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun wrap(cursor: Cursor): PathInterpreter =
|
override fun wrap(cursor: Cursor): PathInterpreter =
|
||||||
|
@ -496,9 +491,13 @@ class VolumePathInterpreter(private val cursor: Cursor, private val volumeManage
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sealed interface TagInterpreter : Interpreter
|
private sealed interface TagInterpreter : Interpreter {
|
||||||
|
interface Factory : Interpreter.Factory {
|
||||||
|
override fun wrap(cursor: Cursor): TagInterpreter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class Api21TagInterpreter(private val cursor: Cursor) : TagInterpreter {
|
private class Api21TagInterpreter(private val cursor: Cursor) : TagInterpreter {
|
||||||
private val trackIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns.TRACK)
|
private val trackIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns.TRACK)
|
||||||
|
|
||||||
override fun populate(rawSong: RawSong) {
|
override fun populate(rawSong: RawSong) {
|
||||||
|
@ -511,7 +510,7 @@ class Api21TagInterpreter(private val cursor: Cursor) : TagInterpreter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Factory : TagInterpreterFactory {
|
class Factory : TagInterpreter.Factory {
|
||||||
override val projection: Array<String>
|
override val projection: Array<String>
|
||||||
get() = arrayOf(MediaStore.Audio.AudioColumns.TRACK)
|
get() = arrayOf(MediaStore.Audio.AudioColumns.TRACK)
|
||||||
|
|
||||||
|
@ -533,12 +532,13 @@ class Api21TagInterpreter(private val cursor: Cursor) : TagInterpreter {
|
||||||
* MediaStore's TRACK column, and combine the track and disc value into a single field where the
|
* MediaStore's TRACK column, and combine the track and disc value into a single field where the
|
||||||
* disc number is the 4th+ digit.
|
* disc number is the 4th+ digit.
|
||||||
*
|
*
|
||||||
* @return The disc number extracted from the combined integer field, or null if the value was zero.
|
* @return The disc number extracted from the combined integer field, or null if the value was
|
||||||
|
* zero.
|
||||||
*/
|
*/
|
||||||
private fun Int.unpackDiscNo() = transformPositionField(div(1000), null)
|
private fun Int.unpackDiscNo() = transformPositionField(div(1000), null)
|
||||||
}
|
}
|
||||||
|
|
||||||
class Api30TagInterpreter(private val cursor: Cursor) : TagInterpreter {
|
private class Api30TagInterpreter(private val cursor: Cursor) : TagInterpreter {
|
||||||
private val trackIndex =
|
private val trackIndex =
|
||||||
cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns.CD_TRACK_NUMBER)
|
cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns.CD_TRACK_NUMBER)
|
||||||
private val discIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns.DISC_NUMBER)
|
private val discIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns.DISC_NUMBER)
|
||||||
|
@ -552,7 +552,7 @@ class Api30TagInterpreter(private val cursor: Cursor) : TagInterpreter {
|
||||||
cursor.getStringOrNull(discIndex)?.parseId3v2PositionField()?.let { rawSong.disc = it }
|
cursor.getStringOrNull(discIndex)?.parseId3v2PositionField()?.let { rawSong.disc = it }
|
||||||
}
|
}
|
||||||
|
|
||||||
class Factory : TagInterpreterFactory {
|
class Factory : TagInterpreter.Factory {
|
||||||
override val projection: Array<String>
|
override val projection: Array<String>
|
||||||
get() =
|
get() =
|
||||||
arrayOf(
|
arrayOf(
|
||||||
|
|
Loading…
Reference in a new issue