Sie ruft zuerst die Song-ID über MediaStore ab und sendet fd an TagLib, das sie dupliziert und zum Öffnen eines Streams verwendet, um die Metadaten und Audioeigenschaften abzurufen.
Dies gibt eine Zuordnung von und einem IntArray der Größe 4 zurück.
Dies wird für jede von der zurückgegebene ID aufgerufen MediaStore-Abfrage.
Die ersten 1700 Songs dauern ca. 700 ms pro 100 Songs, aber alle 100 danach dauern 23 Sekunden und erhöhen sich alle 100 auf ca. 60 Sekunden.
Diese Methode wird auf Dispatchers IO ausgeführt.
Was kann ich tun, um dies zu reduzieren?
Dieser vollständige Scan wird beim ersten Start der App durchgeführt Nicht wirklich besorgniserregend.
Allerdings dauert ein vollständiger Scan bei über 7900 Songs etwa 18 Minuten.
MediaStore-Scan
Code: Select all
fun getAudioFilesViaMediaStore(): List {
//.nomedia affected
val musicUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
val projection = arrayOf(MediaStore.Audio.Media._ID)
val audioList = mutableListOf()
//TODO replace placeholder path
val audioCursor = contentResolver.query(
musicUri,
projection,
"${MediaStore.Audio.Media.RELATIVE_PATH} LIKE ?",
arrayOf("%Music%"),
null
) ?: return emptyList()
audioCursor.use { cursor ->
val idColumn: Int = audioCursor.getColumnIndex(MediaStore.Audio.AudioColumns._ID)
cursor.apply {
if (count == 0) Log.d("Cursor", "get cursor data: Cursor is empty.")
else {
while (cursor.moveToNext()) {
try {
val iD = cursor.getLong(idColumn)
val song = getSongDetailsTagLib(iD)
if (song != null) audioList += song
} catch (e: Exception) {
Log.e("Cursor read", "ERR", e)
}
}
}
}
}
return audioList
}
Code: Select all
fun getSongDetailsTagLib(id: Long): Song? {
val uri = ContentUris.withAppendedId(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, id)
val pfd = contentResolver.openFileDescriptor(uri, "r") ?: return null
val fd = pfd.detachFd()
val prop: IntArray = TagLib.getAudioProperties(fd)
val metadata: HashMap = TagLib.getMetadata(fd)
val title = metadata["TITLE"]?.firstOrNull()?.takeIf { it.isNotBlank() } ?: ""
val artist = metadata["ARTIST"]?.firstOrNull()?.takeIf { it.isNotBlank() } ?: ""
val album = metadata["ALBUM"]?.firstOrNull()?.takeIf { it.isNotBlank() } ?: ""
//etcetc
pfd.close()
return Song(
title = title,
artist = artist,
iD = id
)
}
Code: Select all
lifecycleScope.launch(Dispatchers.IO) {
val audioList = getAudioFilesViaMediaStore()
}
Code: Select all
#include
#include "taglib/taglib/fileref.h"
#include "taglib/taglib/tag.h"
#include
#include
#include "toolkit/tfilestream.h"
#include "toolkit/tstringlist.h"
#include "tpropertymap.h"
jclass g_stringClass = nullptr;
jclass g_hashMapClass = nullptr;
jmethodID g_hashMapInit = nullptr;
jmethodID g_hashMapPut = nullptr;
extern "C"
JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *) {
JNIEnv *env;
if (vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK)
return JNI_ERR;
auto cacheClass = [env](const char *name) {
jclass tmp = env->FindClass(name);
jclass global = (jclass)env->NewGlobalRef(tmp);
env->DeleteLocalRef(tmp);
return global;
};
g_stringClass = cacheClass("java/lang/String");
g_hashMapClass = cacheClass("java/util/HashMap");
g_hashMapInit = env->GetMethodID(g_hashMapClass, "", "(I)V");
g_hashMapPut = env->GetMethodID(g_hashMapClass, "put",
"(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");
return JNI_VERSION_1_6;
}
jobjectArray strListToJniArray(JNIEnv *env, const TagLib::StringList &stringList) {
jobjectArray array = env->NewObjectArray(stringList.size(),g_stringClass, nullptr);
for (size_t i = 0; i < stringList.size(); ++i) {
jstring str = env->NewStringUTF(stringList[i].toCString(true));
env->SetObjectArrayElement(array, i, str);
env->DeleteLocalRef(str);
}
return array;
}
jobject propertyMapToHashMap(JNIEnv *env, const TagLib::PropertyMap &propertyMap) {
jobject map = env->NewObject(g_hashMapClass, g_hashMapInit, static_cast(propertyMap.size()));
for (const auto& [key, values]: propertyMap) {
jobjectArray valueArray = strListToJniArray(env, values);
jstring keyStr = env->NewStringUTF(key.toCString(true));
env->CallObjectMethod(map, g_hashMapPut, keyStr, valueArray);
env->DeleteLocalRef(keyStr);
env->DeleteLocalRef(valueArray);
}
return map;
}
extern "C"
JNIEXPORT void JNI_OnUnload(JavaVM *vm, void *) {
JNIEnv *env;
if (vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK)
return;
env->DeleteGlobalRef(g_stringClass);
env->DeleteGlobalRef(g_hashMapClass);
}
extern "C"
JNIEXPORT jobject JNICALL
Java_com_example_taglib_TagLib_getMetadata(JNIEnv *env,jobject thiz,jint fd) {
fd = dup(fd);
auto stream = std::make_unique(fd, true);
TagLib::FileRef file(stream.get(), true);
jobject propertiesMap = propertyMapToHashMap(env, file.properties());
return propertiesMap;
}
extern "C"
JNIEXPORT jintArray JNICALL
Java_com_example_taglib_TagLib_getAudioProperties(JNIEnv* env,jobject thiz, jint fd) {
fd = dup(fd);
auto stream = std::make_unique(fd, true);
TagLib::FileRef file(stream.get(), true);
jint values[4] = {0, 0, 0, 0};
if (!file.isNull()) {
auto props = file.audioProperties();
if (props) {
values[0] = props->lengthInMilliseconds();
values[1] = props->bitrate();
values[2] = props->sampleRate();
values[3] = props->channels();
}
}
jintArray result = env->NewIntArray(4);
env->SetIntArrayRegion(result, 0, 4, values);
return result;
}
Mobile version