musikr: refactor devicefiles into tree
This commit is contained in:
parent
fce77ec8a0
commit
8104985a4e
17 changed files with 110 additions and 54 deletions
|
@ -30,7 +30,7 @@ import org.oxycblt.musikr.cover.CoverResult
|
||||||
import org.oxycblt.musikr.cover.Covers
|
import org.oxycblt.musikr.cover.Covers
|
||||||
import org.oxycblt.musikr.cover.FileCover
|
import org.oxycblt.musikr.cover.FileCover
|
||||||
import org.oxycblt.musikr.cover.MutableCovers
|
import org.oxycblt.musikr.cover.MutableCovers
|
||||||
import org.oxycblt.musikr.fs.DeviceFile
|
import org.oxycblt.musikr.fs.device.DeviceFile
|
||||||
import org.oxycblt.musikr.metadata.Metadata
|
import org.oxycblt.musikr.metadata.Metadata
|
||||||
|
|
||||||
open class CompatCovers(private val context: Context, private val inner: Covers<FileCover>) :
|
open class CompatCovers(private val context: Context, private val inner: Covers<FileCover>) :
|
||||||
|
|
|
@ -22,7 +22,7 @@ import android.content.Context
|
||||||
import org.oxycblt.musikr.cover.Cover
|
import org.oxycblt.musikr.cover.Cover
|
||||||
import org.oxycblt.musikr.cover.CoverResult
|
import org.oxycblt.musikr.cover.CoverResult
|
||||||
import org.oxycblt.musikr.cover.MutableCovers
|
import org.oxycblt.musikr.cover.MutableCovers
|
||||||
import org.oxycblt.musikr.fs.DeviceFile
|
import org.oxycblt.musikr.fs.device.DeviceFile
|
||||||
import org.oxycblt.musikr.metadata.Metadata
|
import org.oxycblt.musikr.metadata.Metadata
|
||||||
|
|
||||||
class NullCovers(private val context: Context) : MutableCovers<NullCover> {
|
class NullCovers(private val context: Context) : MutableCovers<NullCover> {
|
||||||
|
|
|
@ -31,7 +31,7 @@ import org.oxycblt.musikr.cover.FileCover
|
||||||
import org.oxycblt.musikr.cover.FileCovers
|
import org.oxycblt.musikr.cover.FileCovers
|
||||||
import org.oxycblt.musikr.cover.MutableCovers
|
import org.oxycblt.musikr.cover.MutableCovers
|
||||||
import org.oxycblt.musikr.cover.MutableFileCovers
|
import org.oxycblt.musikr.cover.MutableFileCovers
|
||||||
import org.oxycblt.musikr.fs.DeviceFile
|
import org.oxycblt.musikr.fs.device.DeviceFile
|
||||||
import org.oxycblt.musikr.fs.app.AppFiles
|
import org.oxycblt.musikr.fs.app.AppFiles
|
||||||
import org.oxycblt.musikr.metadata.Metadata
|
import org.oxycblt.musikr.metadata.Metadata
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,7 @@ package org.oxycblt.musikr.cache
|
||||||
|
|
||||||
import org.oxycblt.musikr.cover.Cover
|
import org.oxycblt.musikr.cover.Cover
|
||||||
import org.oxycblt.musikr.cover.Covers
|
import org.oxycblt.musikr.cover.Covers
|
||||||
import org.oxycblt.musikr.fs.DeviceFile
|
import org.oxycblt.musikr.fs.device.DeviceFile
|
||||||
import org.oxycblt.musikr.pipeline.RawSong
|
import org.oxycblt.musikr.pipeline.RawSong
|
||||||
|
|
||||||
abstract class Cache {
|
abstract class Cache {
|
||||||
|
|
|
@ -34,7 +34,7 @@ import androidx.room.TypeConverters
|
||||||
import org.oxycblt.musikr.cover.Cover
|
import org.oxycblt.musikr.cover.Cover
|
||||||
import org.oxycblt.musikr.cover.CoverResult
|
import org.oxycblt.musikr.cover.CoverResult
|
||||||
import org.oxycblt.musikr.cover.Covers
|
import org.oxycblt.musikr.cover.Covers
|
||||||
import org.oxycblt.musikr.fs.DeviceFile
|
import org.oxycblt.musikr.fs.device.DeviceFile
|
||||||
import org.oxycblt.musikr.metadata.Properties
|
import org.oxycblt.musikr.metadata.Properties
|
||||||
import org.oxycblt.musikr.pipeline.RawSong
|
import org.oxycblt.musikr.pipeline.RawSong
|
||||||
import org.oxycblt.musikr.tag.Date
|
import org.oxycblt.musikr.tag.Date
|
||||||
|
|
|
@ -21,7 +21,7 @@ package org.oxycblt.musikr.cache
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import org.oxycblt.musikr.cover.Cover
|
import org.oxycblt.musikr.cover.Cover
|
||||||
import org.oxycblt.musikr.cover.Covers
|
import org.oxycblt.musikr.cover.Covers
|
||||||
import org.oxycblt.musikr.fs.DeviceFile
|
import org.oxycblt.musikr.fs.device.DeviceFile
|
||||||
import org.oxycblt.musikr.pipeline.RawSong
|
import org.oxycblt.musikr.pipeline.RawSong
|
||||||
|
|
||||||
interface StoredCache {
|
interface StoredCache {
|
||||||
|
|
|
@ -19,7 +19,7 @@
|
||||||
package org.oxycblt.musikr.cover
|
package org.oxycblt.musikr.cover
|
||||||
|
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import org.oxycblt.musikr.fs.DeviceFile
|
import org.oxycblt.musikr.fs.device.DeviceFile
|
||||||
import org.oxycblt.musikr.metadata.Metadata
|
import org.oxycblt.musikr.metadata.Metadata
|
||||||
|
|
||||||
interface Covers<T : Cover> {
|
interface Covers<T : Cover> {
|
||||||
|
|
|
@ -19,7 +19,7 @@
|
||||||
package org.oxycblt.musikr.cover
|
package org.oxycblt.musikr.cover
|
||||||
|
|
||||||
import android.os.ParcelFileDescriptor
|
import android.os.ParcelFileDescriptor
|
||||||
import org.oxycblt.musikr.fs.DeviceFile
|
import org.oxycblt.musikr.fs.device.DeviceFile
|
||||||
import org.oxycblt.musikr.fs.app.AppFile
|
import org.oxycblt.musikr.fs.app.AppFile
|
||||||
import org.oxycblt.musikr.fs.app.AppFiles
|
import org.oxycblt.musikr.fs.app.AppFiles
|
||||||
import org.oxycblt.musikr.metadata.Metadata
|
import org.oxycblt.musikr.metadata.Metadata
|
||||||
|
|
|
@ -16,14 +16,29 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.oxycblt.musikr.fs
|
package org.oxycblt.musikr.fs.device
|
||||||
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import org.oxycblt.musikr.fs.Path
|
||||||
|
|
||||||
|
sealed interface DeviceNode {
|
||||||
|
val uri: Uri
|
||||||
|
val path: Path
|
||||||
|
}
|
||||||
|
|
||||||
|
data class DeviceDirectory(
|
||||||
|
override val uri: Uri,
|
||||||
|
override val path: Path,
|
||||||
|
val parent: DeviceDirectory?,
|
||||||
|
var children: Flow<DeviceNode>
|
||||||
|
) : DeviceNode
|
||||||
|
|
||||||
data class DeviceFile(
|
data class DeviceFile(
|
||||||
val uri: Uri,
|
override val uri: Uri,
|
||||||
|
override val path: Path,
|
||||||
|
val modifiedMs: Long,
|
||||||
val mimeType: String,
|
val mimeType: String,
|
||||||
val path: Path,
|
|
||||||
val size: Long,
|
val size: Long,
|
||||||
val modifiedMs: Long
|
val parent: DeviceDirectory
|
||||||
)
|
) : DeviceNode
|
|
@ -24,17 +24,14 @@ import android.net.Uri
|
||||||
import android.provider.DocumentsContract
|
import android.provider.DocumentsContract
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.asFlow
|
import kotlinx.coroutines.flow.emptyFlow
|
||||||
import kotlinx.coroutines.flow.emitAll
|
|
||||||
import kotlinx.coroutines.flow.flatMapMerge
|
|
||||||
import kotlinx.coroutines.flow.flattenMerge
|
|
||||||
import kotlinx.coroutines.flow.flow
|
import kotlinx.coroutines.flow.flow
|
||||||
import org.oxycblt.musikr.fs.DeviceFile
|
import kotlinx.coroutines.flow.flatMapMerge
|
||||||
import org.oxycblt.musikr.fs.MusicLocation
|
import org.oxycblt.musikr.fs.MusicLocation
|
||||||
import org.oxycblt.musikr.fs.Path
|
import org.oxycblt.musikr.fs.Path
|
||||||
|
|
||||||
internal interface DeviceFiles {
|
internal interface DeviceFiles {
|
||||||
fun explore(locations: Flow<MusicLocation>, ignoreHidden: Boolean = true): Flow<DeviceFile>
|
fun explore(locations: Flow<MusicLocation>, ignoreHidden: Boolean = true): Flow<DeviceNode>
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun from(context: Context): DeviceFiles = DeviceFilesImpl(context.contentResolverSafe)
|
fun from(context: Context): DeviceFiles = DeviceFilesImpl(context.contentResolverSafe)
|
||||||
|
@ -43,23 +40,38 @@ internal interface DeviceFiles {
|
||||||
|
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
private class DeviceFilesImpl(private val contentResolver: ContentResolver) : DeviceFiles {
|
private class DeviceFilesImpl(private val contentResolver: ContentResolver) : DeviceFiles {
|
||||||
override fun explore(locations: Flow<MusicLocation>, ignoreHidden: Boolean): Flow<DeviceFile> =
|
override fun explore(locations: Flow<MusicLocation>, ignoreHidden: Boolean): Flow<DeviceNode> =
|
||||||
locations.flatMapMerge { location ->
|
locations.flatMapMerge { location ->
|
||||||
exploreImpl(
|
// Create a root directory for each location
|
||||||
|
val rootDirectory = DeviceDirectory(
|
||||||
|
uri = location.uri,
|
||||||
|
path = location.path,
|
||||||
|
parent = null,
|
||||||
|
children = emptyFlow()
|
||||||
|
)
|
||||||
|
|
||||||
|
// Set up the children flow for the root directory
|
||||||
|
rootDirectory.children = exploreDirectoryImpl(
|
||||||
contentResolver,
|
contentResolver,
|
||||||
location.uri,
|
location.uri,
|
||||||
DocumentsContract.getTreeDocumentId(location.uri),
|
DocumentsContract.getTreeDocumentId(location.uri),
|
||||||
location.path,
|
location.path,
|
||||||
ignoreHidden)
|
rootDirectory,
|
||||||
|
ignoreHidden
|
||||||
|
)
|
||||||
|
|
||||||
|
// Return a flow that emits the root directory
|
||||||
|
flow { emit(rootDirectory) }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun exploreImpl(
|
private fun exploreDirectoryImpl(
|
||||||
contentResolver: ContentResolver,
|
contentResolver: ContentResolver,
|
||||||
rootUri: Uri,
|
rootUri: Uri,
|
||||||
treeDocumentId: String,
|
treeDocumentId: String,
|
||||||
relativePath: Path,
|
relativePath: Path,
|
||||||
|
parent: DeviceDirectory,
|
||||||
ignoreHidden: Boolean
|
ignoreHidden: Boolean
|
||||||
): Flow<DeviceFile> = flow {
|
): Flow<DeviceNode> = flow {
|
||||||
contentResolver.useQuery(
|
contentResolver.useQuery(
|
||||||
DocumentsContract.buildChildDocumentsUriUsingTree(rootUri, treeDocumentId),
|
DocumentsContract.buildChildDocumentsUriUsingTree(rootUri, treeDocumentId),
|
||||||
PROJECTION) { cursor ->
|
PROJECTION) { cursor ->
|
||||||
|
@ -72,7 +84,7 @@ private class DeviceFilesImpl(private val contentResolver: ContentResolver) : De
|
||||||
val sizeIndex = cursor.getColumnIndexOrThrow(DocumentsContract.Document.COLUMN_SIZE)
|
val sizeIndex = cursor.getColumnIndexOrThrow(DocumentsContract.Document.COLUMN_SIZE)
|
||||||
val lastModifiedIndex =
|
val lastModifiedIndex =
|
||||||
cursor.getColumnIndexOrThrow(DocumentsContract.Document.COLUMN_LAST_MODIFIED)
|
cursor.getColumnIndexOrThrow(DocumentsContract.Document.COLUMN_LAST_MODIFIED)
|
||||||
val recursive = mutableListOf<Flow<DeviceFile>>()
|
|
||||||
while (cursor.moveToNext()) {
|
while (cursor.moveToNext()) {
|
||||||
val childId = cursor.getString(childUriIndex)
|
val childId = cursor.getString(childUriIndex)
|
||||||
val displayName = cursor.getString(displayNameIndex)
|
val displayName = cursor.getString(displayNameIndex)
|
||||||
|
@ -84,27 +96,44 @@ private class DeviceFilesImpl(private val contentResolver: ContentResolver) : De
|
||||||
|
|
||||||
val newPath = relativePath.file(displayName)
|
val newPath = relativePath.file(displayName)
|
||||||
val mimeType = cursor.getString(mimeTypeIndex)
|
val mimeType = cursor.getString(mimeTypeIndex)
|
||||||
|
val lastModified = cursor.getLong(lastModifiedIndex)
|
||||||
|
val childUri = DocumentsContract.buildDocumentUriUsingTree(rootUri, childId)
|
||||||
|
|
||||||
if (mimeType == DocumentsContract.Document.MIME_TYPE_DIR) {
|
if (mimeType == DocumentsContract.Document.MIME_TYPE_DIR) {
|
||||||
// This does NOT block the current coroutine. Instead, we will
|
// Create a directory node with empty children flow initially
|
||||||
// evaluate this flow in parallel later to maximize throughput.
|
val directory = DeviceDirectory(
|
||||||
recursive.add(
|
uri = childUri,
|
||||||
exploreImpl(contentResolver, rootUri, childId, newPath, ignoreHidden))
|
path = newPath,
|
||||||
} else if (mimeType.startsWith("audio/") && mimeType != "audio/x-mpegurl") {
|
parent = parent,
|
||||||
// Immediately emit all files given that it's just an O(1) op.
|
children = emptyFlow()
|
||||||
// This also just makes sure the outer flow has a reason to exist
|
)
|
||||||
// rather than just being a glorified async.
|
|
||||||
val lastModified = cursor.getLong(lastModifiedIndex)
|
// Set up the children flow for this directory
|
||||||
|
directory.children = exploreDirectoryImpl(
|
||||||
|
contentResolver,
|
||||||
|
rootUri,
|
||||||
|
childId,
|
||||||
|
newPath,
|
||||||
|
directory,
|
||||||
|
ignoreHidden
|
||||||
|
)
|
||||||
|
|
||||||
|
// Emit the directory node
|
||||||
|
emit(directory)
|
||||||
|
} else {
|
||||||
val size = cursor.getLong(sizeIndex)
|
val size = cursor.getLong(sizeIndex)
|
||||||
emit(
|
emit(
|
||||||
DeviceFile(
|
DeviceFile(
|
||||||
DocumentsContract.buildDocumentUriUsingTree(rootUri, childId),
|
uri = childUri,
|
||||||
mimeType,
|
mimeType = mimeType,
|
||||||
newPath,
|
path = newPath,
|
||||||
size,
|
size = size,
|
||||||
lastModified))
|
modifiedMs = lastModified,
|
||||||
|
parent = parent
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
emitAll(recursive.asFlow().flattenMerge())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,7 @@ import android.os.ParcelFileDescriptor
|
||||||
import java.io.FileInputStream
|
import java.io.FileInputStream
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import org.oxycblt.musikr.fs.DeviceFile
|
import org.oxycblt.musikr.fs.device.DeviceFile
|
||||||
|
|
||||||
internal interface MetadataExtractor {
|
internal interface MetadataExtractor {
|
||||||
suspend fun extract(deviceFile: DeviceFile, fd: ParcelFileDescriptor): Metadata?
|
suspend fun extract(deviceFile: DeviceFile, fd: ParcelFileDescriptor): Metadata?
|
||||||
|
|
|
@ -21,7 +21,7 @@ package org.oxycblt.musikr.metadata
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import java.io.FileInputStream
|
import java.io.FileInputStream
|
||||||
import java.nio.ByteBuffer
|
import java.nio.ByteBuffer
|
||||||
import org.oxycblt.musikr.fs.DeviceFile
|
import org.oxycblt.musikr.fs.device.DeviceFile
|
||||||
|
|
||||||
internal class NativeInputStream(private val deviceFile: DeviceFile, fis: FileInputStream) {
|
internal class NativeInputStream(private val deviceFile: DeviceFile, fis: FileInputStream) {
|
||||||
private val channel = fis.channel
|
private val channel = fis.channel
|
||||||
|
|
|
@ -19,7 +19,7 @@
|
||||||
package org.oxycblt.musikr.metadata
|
package org.oxycblt.musikr.metadata
|
||||||
|
|
||||||
import java.io.FileInputStream
|
import java.io.FileInputStream
|
||||||
import org.oxycblt.musikr.fs.DeviceFile
|
import org.oxycblt.musikr.fs.device.DeviceFile
|
||||||
|
|
||||||
internal object TagLibJNI {
|
internal object TagLibJNI {
|
||||||
init {
|
init {
|
||||||
|
|
|
@ -20,17 +20,20 @@ package org.oxycblt.musikr.pipeline
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.asFlow
|
import kotlinx.coroutines.flow.asFlow
|
||||||
import kotlinx.coroutines.flow.buffer
|
import kotlinx.coroutines.flow.buffer
|
||||||
import kotlinx.coroutines.flow.emitAll
|
import kotlinx.coroutines.flow.emitAll
|
||||||
|
import kotlinx.coroutines.flow.flattenMerge
|
||||||
import kotlinx.coroutines.flow.flow
|
import kotlinx.coroutines.flow.flow
|
||||||
import kotlinx.coroutines.flow.flowOn
|
import kotlinx.coroutines.flow.flowOn
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.flow.mapNotNull
|
|
||||||
import kotlinx.coroutines.flow.merge
|
import kotlinx.coroutines.flow.merge
|
||||||
import org.oxycblt.musikr.Storage
|
import org.oxycblt.musikr.Storage
|
||||||
import org.oxycblt.musikr.fs.DeviceFile
|
import org.oxycblt.musikr.fs.device.DeviceDirectory
|
||||||
|
import org.oxycblt.musikr.fs.device.DeviceFile
|
||||||
|
import org.oxycblt.musikr.fs.device.DeviceNode
|
||||||
import org.oxycblt.musikr.fs.MusicLocation
|
import org.oxycblt.musikr.fs.MusicLocation
|
||||||
import org.oxycblt.musikr.fs.device.DeviceFiles
|
import org.oxycblt.musikr.fs.device.DeviceFiles
|
||||||
import org.oxycblt.musikr.playlist.PlaylistFile
|
import org.oxycblt.musikr.playlist.PlaylistFile
|
||||||
|
@ -54,12 +57,8 @@ private class ExploreStepImpl(
|
||||||
val audios =
|
val audios =
|
||||||
deviceFiles
|
deviceFiles
|
||||||
.explore(locations.asFlow())
|
.explore(locations.asFlow())
|
||||||
.mapNotNull {
|
.flattenFilter {
|
||||||
when {
|
it.mimeType.startsWith("audio/") || it.mimeType == M3U.MIME_TYPE
|
||||||
it.mimeType == M3U.MIME_TYPE -> null
|
|
||||||
it.mimeType.startsWith("audio/") -> ExploreNode.Audio(it)
|
|
||||||
else -> null
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
.flowOn(Dispatchers.IO)
|
.flowOn(Dispatchers.IO)
|
||||||
.buffer()
|
.buffer()
|
||||||
|
@ -70,6 +69,19 @@ private class ExploreStepImpl(
|
||||||
.buffer()
|
.buffer()
|
||||||
return merge(audios, playlists)
|
return merge(audios, playlists)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
|
private fun Flow<DeviceNode>.flattenFilter(block: (DeviceFile) -> Boolean): Flow<ExploreNode> = flow {
|
||||||
|
collect {
|
||||||
|
val recurse = mutableListOf<Flow<ExploreNode>>()
|
||||||
|
when {
|
||||||
|
it is DeviceFile && block(it) -> emit(ExploreNode.Audio(it))
|
||||||
|
it is DeviceDirectory -> recurse.add(it.children.flattenFilter(block))
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
emitAll(recurse.asFlow().flattenMerge())
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal sealed interface ExploreNode {
|
internal sealed interface ExploreNode {
|
||||||
|
|
|
@ -37,7 +37,7 @@ import org.oxycblt.musikr.cache.CacheResult
|
||||||
import org.oxycblt.musikr.cover.Cover
|
import org.oxycblt.musikr.cover.Cover
|
||||||
import org.oxycblt.musikr.cover.CoverResult
|
import org.oxycblt.musikr.cover.CoverResult
|
||||||
import org.oxycblt.musikr.cover.MutableCovers
|
import org.oxycblt.musikr.cover.MutableCovers
|
||||||
import org.oxycblt.musikr.fs.DeviceFile
|
import org.oxycblt.musikr.fs.device.DeviceFile
|
||||||
import org.oxycblt.musikr.metadata.MetadataExtractor
|
import org.oxycblt.musikr.metadata.MetadataExtractor
|
||||||
import org.oxycblt.musikr.metadata.Properties
|
import org.oxycblt.musikr.metadata.Properties
|
||||||
import org.oxycblt.musikr.playlist.PlaylistFile
|
import org.oxycblt.musikr.playlist.PlaylistFile
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
|
|
||||||
package org.oxycblt.musikr.pipeline
|
package org.oxycblt.musikr.pipeline
|
||||||
|
|
||||||
import org.oxycblt.musikr.fs.DeviceFile
|
import org.oxycblt.musikr.fs.device.DeviceFile
|
||||||
import org.oxycblt.musikr.playlist.PlaylistFile
|
import org.oxycblt.musikr.playlist.PlaylistFile
|
||||||
import org.oxycblt.musikr.playlist.interpret.PrePlaylist
|
import org.oxycblt.musikr.playlist.interpret.PrePlaylist
|
||||||
import org.oxycblt.musikr.tag.interpret.PreSong
|
import org.oxycblt.musikr.tag.interpret.PreSong
|
||||||
|
|
|
@ -20,7 +20,7 @@ package org.oxycblt.musikr.tag.interpret
|
||||||
|
|
||||||
import org.oxycblt.musikr.Interpretation
|
import org.oxycblt.musikr.Interpretation
|
||||||
import org.oxycblt.musikr.Music
|
import org.oxycblt.musikr.Music
|
||||||
import org.oxycblt.musikr.fs.DeviceFile
|
import org.oxycblt.musikr.fs.device.DeviceFile
|
||||||
import org.oxycblt.musikr.fs.Format
|
import org.oxycblt.musikr.fs.Format
|
||||||
import org.oxycblt.musikr.pipeline.RawSong
|
import org.oxycblt.musikr.pipeline.RawSong
|
||||||
import org.oxycblt.musikr.tag.Disc
|
import org.oxycblt.musikr.tag.Disc
|
||||||
|
|
Loading…
Reference in a new issue