3

In my app I'm trying to extract album arts from all music files in phone storage and then I populate them into my RecyclerView. I'm successfully getting everything but still it's taking a lot of time for all tracks to get rendered into my RecyclerView. All I want to do is as soon as my app is launched all tracks should be filled up in RecyclerView instantly with appropriate album art without any delay. I tried to put my code into coroutines but still it is taking a lot of delay. Any help will be appreciated.

fun getSongList() = GlobalScope.launch(Dispatchers.IO)
    {
        async {
         realm = Realm.getDefaultInstance()
        val iterator = FileUtils.iterateFiles(
                Environment.getExternalStorageDirectory(),
                FileFilterUtils.suffixFileFilter("m4a"),
                TrueFileFilter.INSTANCE)
        while (iterator.hasNext()) {
            try {
                val fileINeed = iterator.next()
                val thisurls = fileINeed.canonicalPath
                val mmr = MediaMetadataRetriever()
                mmr.setDataSource(thisurls)
                var thisTitle = mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_TITLE)
                var thisArtist = mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ARTIST)
                if (thisTitle.isNullOrEmpty()) {
                    thisTitle = fileINeed.name
                }
                if (thisArtist.isNullOrEmpty()) {
                    thisArtist = "Unknown Artist"
                }
                val query = realm!!.where(Songdetails::class.java).equalTo("songname",thisTitle).findFirst()
                if(query == null)
                {
                    val retriever = MediaMetadataRetriever()
                    retriever.setDataSource(thisurls)
                    val art = retriever.embeddedPicture
                    if (art != null) {
                        var bitmap = BitmapFactory.decodeByteArray(art, 0, art.size)
                        ImageSaver(MainActivity.getInstance()!!)
                                .setFileName(thisTitle)
                                .setDirectoryName("images")
                                .save(bitmap)
                         realm = Realm.getDefaultInstance()
                        realm!!.executeTransaction(object : Realm.Transaction {

                            override fun execute(realm: Realm) {
                                // increment index
                                var num = realm.where(Songdetails::class.java).max("id")
                                var nextID: Int
                                if (num == null) {
                                    nextID = 1
                                } else {
                                    nextID = num.toInt() + 1
                                }
                                var songitem = realm!!.createObject(Songdetails::class.java, nextID)
                                songitem.songname = thisTitle
                                songitem.songartist = thisArtist
                                songitem.songurl = thisurls
                                songitem.songimage = thisTitle
                                EventBus.getDefault().post("update")
                            }
                        })
                    }
                    else{
                         realm = Realm.getDefaultInstance()
                        realm!!.executeTransaction(object : Realm.Transaction {

                            override fun execute(realm: Realm) {
                                // increment index
                                var num = realm.where(Songdetails::class.java).max("id")
                                var nextID: Int
                                if (num == null) {
                                    nextID = 1
                                } else {
                                    nextID = num.toInt() + 1
                                }
                                var songitem = realm!!.createObject(Songdetails::class.java, nextID)
                                songitem.songname = thisTitle
                                songitem.songartist = thisArtist
                                songitem.songurl = thisurls
                                songitem.songimage = "custom"
                                EventBus.getDefault().post("update")
                            }
                        })
                    }
                }
            }catch (ex:Exception)
            {

            }
        }
         realm = Realm.getDefaultInstance()
        val mp3iterator = FileUtils.iterateFiles(
                Environment.getExternalStorageDirectory(),
                FileFilterUtils.suffixFileFilter("mp3"),
                TrueFileFilter.INSTANCE)
        while (mp3iterator.hasNext()) {
            try {
                val fileINeed = mp3iterator.next()
                val thisurls = fileINeed.canonicalPath
                val mmr = MediaMetadataRetriever()
                mmr.setDataSource(thisurls)
                var thisTitle = mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_TITLE)
                var thisArtist = mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ARTIST)
                if (!thisTitle.isNullOrEmpty()) {
                    val query = realm!!.where(Songdetails::class.java).equalTo("songname", thisTitle).findFirst()
                    if (query == null) {
                        val retriever = MediaMetadataRetriever()
                        retriever.setDataSource(thisurls)
                        val art = retriever.embeddedPicture
                        if (art != null) {
                            var bitmap = BitmapFactory.decodeByteArray(art, 0, art.size)
                            ImageSaver(MainActivity.getInstance()!!)
                                    .setFileName(thisTitle)
                                    .setDirectoryName("images")
                                    .save(bitmap)
                            realm = Realm.getDefaultInstance()
                           realm!!.executeTransaction(object : Realm.Transaction {

                                override fun execute(realm: Realm) {
                                    // increment index
                                    var num = realm.where(Songdetails::class.java).max("id")
                                    var nextID: Int
                                    if (num == null) {
                                        nextID = 1
                                    } else {
                                        nextID = num.toInt() + 1
                                    }
                                    var songitem = realm!!.createObject(Songdetails::class.java, nextID)
                                    songitem.songname = thisTitle
                                    songitem.songartist = thisArtist
                                    songitem.songurl = thisurls
                                    songitem.songimage = thisTitle
                                    EventBus.getDefault().post("update")
                                }
                            })
                        }
                        else{
                             realm = Realm.getDefaultInstance()
                           realm!!.executeTransaction(object : Realm.Transaction {

                                override fun execute(realm: Realm) {
                                    // increment index
                                    var num = realm.where(Songdetails::class.java).max("id")
                                    var nextID: Int
                                    if (num == null) {
                                        nextID = 1
                                    } else {
                                        nextID = num.toInt() + 1
                                    }
                                    var songitem = realm!!.createObject(Songdetails::class.java, nextID)
                                    songitem.songname = thisTitle
                                    songitem.songartist = thisArtist
                                    songitem.songurl = thisurls
                                    songitem.songimage = "custom"
                                    EventBus.getDefault().post("update")
                                }
                            })
                        }
                    }

                }
            }catch (ex:Exception)
            {

            }
        }
        realm = Realm.getDefaultInstance()
        val waviterator = FileUtils.iterateFiles(
                Environment.getExternalStorageDirectory(),
                FileFilterUtils.suffixFileFilter("wav"),
                TrueFileFilter.INSTANCE)
        while (waviterator.hasNext()) {
            try {
                val fileINeed = waviterator.next()
                val thisurls = fileINeed.canonicalPath
                val mmr = MediaMetadataRetriever()
                mmr.setDataSource(thisurls)
                var thisTitle = mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_TITLE)
                var thisArtist = mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ARTIST)
                if (thisTitle.isNullOrEmpty()) {
                    thisTitle = fileINeed.name
                }
                if (thisArtist.isNullOrEmpty()) {
                    thisArtist = "Unknown Artist"
                }
                val query = realm!!.where(Songdetails::class.java).equalTo("songname", thisTitle).findFirst()
                if (query == null) {
                    val retriever = MediaMetadataRetriever()
                    retriever.setDataSource(thisurls)
                    val art = retriever.embeddedPicture
                    if (art != null) {
                        var bitmap = BitmapFactory.decodeByteArray(art, 0, art.size)
                        ImageSaver(MainActivity.getInstance()!!)
                                .setFileName(thisTitle)
                                .setDirectoryName("images")
                                .save(bitmap)
                         realm = Realm.getDefaultInstance()
                       realm!!.executeTransaction(object : Realm.Transaction {

                            override fun execute(realm: Realm) {
                                // increment index
                                var num = realm.where(Songdetails::class.java).max("id")
                                var nextID: Int
                                if (num == null) {
                                    nextID = 1
                                } else {
                                    nextID = num.toInt() + 1
                                }
                                var songitem = realm!!.createObject(Songdetails::class.java, nextID)
                                songitem.songname = thisTitle
                                songitem.songartist = thisArtist
                                songitem.songurl = thisurls
                                songitem.songimage = thisTitle
                                EventBus.getDefault().post("update")
                            }
                        })
                    } else {
                         realm = Realm.getDefaultInstance()
                        realm!!.executeTransaction(object : Realm.Transaction {

                            override fun execute(realm: Realm) {
                                // increment index
                                var num = realm.where(Songdetails::class.java).max("id")
                                var nextID: Int
                                if (num == null) {
                                    nextID = 1
                                } else {
                                    nextID = num.toInt() + 1
                                }
                                var songitem = realm!!.createObject(Songdetails::class.java, nextID)
                                songitem.songname = thisTitle
                                songitem.songartist = thisArtist
                                songitem.songurl = thisurls
                                songitem.songimage = "custom"
                                EventBus.getDefault().post("update")
                            }
                        })
                    }

                }

            } catch (ex: Exception) {

            }
        }
        }
}
Marisha
  • 816
  • 9
  • 14
ruchit patel
  • 77
  • 2
  • 10
  • Other solutions apart from using coroutines are also welcome we are not limited to coroutines – Mr. Patel Sep 14 '19 at 11:25
  • 1
    _"All I want to do is as soon as my app is launched all tracks should be filled up in RecyclerView instantly with appropriate album art without any delay"_ my biggest concern with this is... **WHY**? I mean, you're taking an o(N) operation and making your users wait for N... seems like a strange design for an app that can take then N seconds to simply open... /shrug – Martin Marconcini Sep 17 '19 at 17:27

3 Answers3

1

You might be able to take advantage of Coil or Picasso to better cache the bitmaps for subsequent launches. The initial one is going to take some time regardless of the method used due to the a variety of variables (number of tracks, size of image tracks, etc.)

Brandon McAnsh
  • 992
  • 8
  • 18
  • coil seems to be interesting let me check that out! – Mr. Patel Sep 15 '19 at 04:32
  • Yeah! using coil kinda made it work but still i'm new to it and facing issues like how can i listen for any error when trying to fetch and also after loading image i want it to return a bitmap to me i tried searching a lot but didn't found much about it maybe because it's not that widely used? – Mr. Patel Sep 16 '19 at 03:47
  • You can add listeners for various things via the ImageLoaderBuilder https://coil-kt.github.io/coil/api/coil-base/coil/-image-loader-builder/ – Brandon McAnsh Sep 17 '19 at 00:47
0

I can only guess this is down to:

  1. You seem to be reading the media file...getting the image bitmap and then saving the image in "/images". This takes quite a bit of time really...especially if you have a lot of files to go through!
  2. I can't see any multi-threading? Maybe a pool of threads could help speed this up? I havn't used co-routines yet so I could be wrong.
  3. Is this being run on the main/UI thread?

Is it not maybe possible to just get the list of media files and in the RecyclerAdapter do the fetching of the bitmaps there in the onBindViewHolder() rather than trying to do the whole lot at app launch and just leave the bitmaps in RAM and not bother persisting to "/images"?

Jcov
  • 2,122
  • 2
  • 21
  • 32
  • Yes indeed in my phone there are about 200 to 300 music and rendering them at runtime take a whole lot of time! – Mr. Patel Sep 14 '19 at 11:23
  • You can also suggest using other methods to solve this problem it's not just case of fetching image bitmap even fetching entire list of music files doesn't happen instantly it takes a lot of time even if i fetch them without images – Mr. Patel Sep 14 '19 at 11:25
  • Nothing is being run on main ui thread all file fetching is done in background thread using coroutines – Mr. Patel Sep 14 '19 at 11:26
  • I havn't done it personally but I would have thought maybe use one thread to get the list of filenames to "process" which passes them in to a pool of threads which deal with the files individually. As the thread pool finish processing a file, they post the update to a ViewModel which the recycler gets updated using DiffUtil. This means the user can "see" the files being loaded and at least can start doing something. – Jcov Sep 14 '19 at 11:29
  • Is it not maybe possible to just get the list of media files and in the RecyclerAdapter do the fetching of the bitmaps there in the onBindViewHolder() I tried that too using glide but still at start fetching list of music files without bitmaps still takes a lot of time! – Mr. Patel Sep 14 '19 at 11:29
0

Why not load the album art that is already generated by the system by querying the MediaStore? It will return the uri of the album art which you can then simply load into the imageview using Glide/Picasso/Coil. This should be faster than trying to do it manually.

jL4
  • 1,238
  • 10
  • 15
  • querying Mediastore works with only .mp3 however i'm also trying to fetch files with .m4a and .wma extensions which won't wont work with mediastore queries! – Mr. Patel Sep 18 '19 at 03:19
  • I tried using coil in recyclerview list and make the process bit faster still however i also want to return a bitmap after loading is done is success then my loaded bitmap be returned and if failure then my given error bitmap i tried doing that but putting my code into listeners and assigning a bitmap to a bitmap variable however i always get a null bitmap so this is useless in my case – Mr. Patel Sep 18 '19 at 03:22
  • @Mr.Patel Like the other comment, I am also not sure why you want the process to be immediate. Manually generating the art will take some time and is completely understandable by anyone as long as you show a notification and do it in the background. Secondly, why not use the MediaStore for mp3 and fallback to manual for other files? It will be a bit more complex but you could create your own db which copies the existing uris from the MediaStore and generates and saves the albumart uri for other files. Excluding the first run, subsequent runs should load the artwork immediately like you want. – jL4 Sep 18 '19 at 05:28
  • Mostly all apps in playstore display all music files instantly without any delay you can take a look at google play music and any other all of them mostly display them instantly without any delay – Mr. Patel Sep 18 '19 at 05:35