[Android] Medialibrary loading: offload paging threadpool

Geoffrey Métais git at videolan.org
Wed Nov 20 13:58:59 CET 2019


vlc-android | branch: master | Geoffrey Métais <geoffrey.metais at gmail.com> | Wed Nov 20 12:01:36 2019 +0100| [d1991f50b01faf3731d2deae25e516a35a6e56f7] | committer: Nicolas Pomepuy

Medialibrary loading: offload paging threadpool

Thread medialibrary calls from paging library to prevent a mutex
blocking main thread

Fix #1089

> https://code.videolan.org/videolan/vlc-android/commit/d1991f50b01faf3731d2deae25e516a35a6e56f7
---

 .../src/org/videolan/vlc/gui/PlaylistActivity.kt      |  9 ++-------
 .../vlc/gui/audio/AudioAlbumsSongsFragment.kt         | 14 ++++----------
 .../videolan/vlc/gui/audio/AudioBrowserFragment.kt    | 19 +++++++++++--------
 .../org/videolan/vlc/gui/video/VideoGridFragment.kt   |  7 ++++---
 .../providers/medialibrary/MedialibraryProvider.kt    | 10 +++++++---
 5 files changed, 28 insertions(+), 31 deletions(-)

diff --git a/vlc-android/src/org/videolan/vlc/gui/PlaylistActivity.kt b/vlc-android/src/org/videolan/vlc/gui/PlaylistActivity.kt
index b044ab9ef..87e70dd7e 100644
--- a/vlc-android/src/org/videolan/vlc/gui/PlaylistActivity.kt
+++ b/vlc-android/src/org/videolan/vlc/gui/PlaylistActivity.kt
@@ -111,13 +111,8 @@ open class PlaylistActivity : AudioPlayerContainerActivity(), IEventsHandler, IL
         binding.playlist = playlist
         viewModel = getViewModel(playlist)
         viewModel.tracksProvider.pagedList.observe(this, Observer { tracks ->
-            if (tracks != null) {
-                @Suppress("UNCHECKED_CAST")
-                if (tracks.isEmpty() && !viewModel.isFiltering())
-                    finish()
-                else
-                    audioBrowserAdapter.submitList(tracks as PagedList<MediaLibraryItem>?)
-            }
+            @Suppress("UNCHECKED_CAST")
+            (tracks as? PagedList<MediaLibraryItem>)?.let { audioBrowserAdapter.submitList(it) }
         })
         audioBrowserAdapter = AudioBrowserAdapter(MediaLibraryItem.TYPE_MEDIA, this, this, isPlaylist)
         if (isPlaylist) {
diff --git a/vlc-android/src/org/videolan/vlc/gui/audio/AudioAlbumsSongsFragment.kt b/vlc-android/src/org/videolan/vlc/gui/audio/AudioAlbumsSongsFragment.kt
index d602f2b09..00862e801 100644
--- a/vlc-android/src/org/videolan/vlc/gui/audio/AudioAlbumsSongsFragment.kt
+++ b/vlc-android/src/org/videolan/vlc/gui/audio/AudioAlbumsSongsFragment.kt
@@ -138,16 +138,10 @@ class AudioAlbumsSongsFragment : BaseAudioBrowser<AlbumSongsViewModel>(), SwipeR
 
         }
         fabPlay?.setImageResource(R.drawable.ic_fab_play)
-        viewModel.albumsProvider.pagedList.observe(this, Observer { albums -> if (albums != null) albumsAdapter.submitList(albums as PagedList<MediaLibraryItem>) })
-        viewModel.tracksProvider.pagedList.observe(this, Observer { tracks ->
-            if (tracks != null) {
-                @Suppress("UNCHECKED_CAST")
-                if (tracks.isEmpty() && !viewModel.isFiltering()) {
-                    val activity = activity
-                    activity?.finish()
-                } else
-                    songsAdapter.submitList(tracks as PagedList<MediaLibraryItem>)
-            }
+        viewModel.albumsProvider.pagedList.observe(requireActivity(), Observer { albums -> if (albums != null) albumsAdapter.submitList(albums as PagedList<MediaLibraryItem>) })
+        viewModel.tracksProvider.pagedList.observe(requireActivity(), Observer { tracks ->
+            @Suppress("UNCHECKED_CAST")
+            (tracks as? PagedList<MediaLibraryItem>)?.let { songsAdapter.submitList(it) }
         })
         for (i in 0..1) setupLayoutManager(i)
     }
diff --git a/vlc-android/src/org/videolan/vlc/gui/audio/AudioBrowserFragment.kt b/vlc-android/src/org/videolan/vlc/gui/audio/AudioBrowserFragment.kt
index 8b8c5b511..82ba9d818 100644
--- a/vlc-android/src/org/videolan/vlc/gui/audio/AudioBrowserFragment.kt
+++ b/vlc-android/src/org/videolan/vlc/gui/audio/AudioBrowserFragment.kt
@@ -40,6 +40,7 @@ import com.google.android.material.floatingactionbutton.FloatingActionButton
 import com.google.android.material.tabs.TabLayout
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.ObsoleteCoroutinesApi
+import kotlinx.coroutines.launch
 import org.videolan.medialibrary.interfaces.media.AbstractMediaWrapper
 import org.videolan.medialibrary.media.MediaLibraryItem
 import org.videolan.vlc.R
@@ -220,7 +221,7 @@ class AudioBrowserFragment : BaseAudioBrowser<AudioBrowserViewModel>(), SwipeRef
                     handler.sendEmptyMessageDelayed(SET_REFRESHING, 300)
                 else
                     handler.sendEmptyMessage(UNSET_REFRESHING)
-
+                updateEmptyView()
                 (activity as? MainActivity)?.refreshing = loading
             })
         }
@@ -298,7 +299,7 @@ class AudioBrowserFragment : BaseAudioBrowser<AudioBrowserViewModel>(), SwipeRef
     }
 
     private fun updateEmptyView() {
-        emptyView.state = if (getCurrentAdapter().isEmpty) EmptyLoadingState.EMPTY else EmptyLoadingState.NONE
+        emptyView.state = if (viewModel.isEmpty()) EmptyLoadingState.EMPTY else EmptyLoadingState.NONE
         setFabPlayShuffleAllVisibility()
     }
 
@@ -361,12 +362,14 @@ class AudioBrowserFragment : BaseAudioBrowser<AudioBrowserViewModel>(), SwipeRef
 
     override fun onUpdateFinished(adapter: RecyclerView.Adapter<*>) {
         super.onUpdateFinished(adapter)
-        if (adapter === getCurrentAdapter()) {
-            swipeRefreshLayout.isEnabled = (getCurrentRV().layoutManager as LinearLayoutManager).findFirstVisibleItemPosition() <= 0
-            updateEmptyView()
-            fastScroller.setRecyclerView(getCurrentRV(), viewModel.providers[currentTab])
-        } else
-            setFabPlayShuffleAllVisibility()
+        launch { // force a dispatch
+            if (adapter === getCurrentAdapter()) {
+                swipeRefreshLayout.isEnabled = (getCurrentRV().layoutManager as LinearLayoutManager).findFirstVisibleItemPosition() <= 0
+                updateEmptyView()
+                fastScroller.setRecyclerView(getCurrentRV(), viewModel.providers[currentTab])
+            } else
+                setFabPlayShuffleAllVisibility()
+        }
     }
 
     override fun getCurrentRV() = lists[currentTab]
diff --git a/vlc-android/src/org/videolan/vlc/gui/video/VideoGridFragment.kt b/vlc-android/src/org/videolan/vlc/gui/video/VideoGridFragment.kt
index ea5c7b80d..134d26319 100644
--- a/vlc-android/src/org/videolan/vlc/gui/video/VideoGridFragment.kt
+++ b/vlc-android/src/org/videolan/vlc/gui/video/VideoGridFragment.kt
@@ -36,6 +36,7 @@ import androidx.paging.PagedList
 import androidx.recyclerview.widget.RecyclerView
 import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
 import kotlinx.coroutines.*
+import kotlinx.coroutines.channels.Channel
 import kotlinx.coroutines.channels.actor
 import org.videolan.medialibrary.interfaces.AbstractMedialibrary
 import org.videolan.medialibrary.interfaces.media.AbstractFolder
@@ -140,7 +141,7 @@ class VideoGridFragment : MediaBrowserFragment<VideosViewModel>(), SwipeRefreshL
             if (loading) handler.sendEmptyMessageDelayed(SET_REFRESHING, 300L)
             else handler.sendEmptyMessage(UNSET_REFRESHING)
             (activity as? MainActivity)?.refreshing = loading
-
+            updateEmptyView()
         })
         videoListAdapter.showFilename.set(viewModel.groupingType == VideoGroupingType.NONE && viewModel.provider.sort == AbstractMedialibrary.SORT_FILENAME)
     }
@@ -450,7 +451,7 @@ class VideoGridFragment : MediaBrowserFragment<VideosViewModel>(), SwipeRefreshL
         }
     }
 
-    private val actor = actor<VideoAction> {
+    private val actor = actor<VideoAction>(capacity = Channel.UNLIMITED) {
         for (action in channel) when (action) {
             is VideoClick -> {
                 when (action.item) {
@@ -495,8 +496,8 @@ class VideoGridFragment : MediaBrowserFragment<VideosViewModel>(), SwipeRefreshL
             }
             is VideoUpdateFinished -> {
                 if (isStarted()) {
+                    yield() //Let the refresh actually happen
                     if (!mediaLibrary.isWorking) handler.sendEmptyMessage(UNSET_REFRESHING)
-                    updateEmptyView()
                     setFabPlayVisibility(true)
                     menu?.let { UiTools.updateSortTitles(it, viewModel.provider) }
                 }
diff --git a/vlc-android/src/org/videolan/vlc/providers/medialibrary/MedialibraryProvider.kt b/vlc-android/src/org/videolan/vlc/providers/medialibrary/MedialibraryProvider.kt
index 500f64c1b..3efaeceab 100644
--- a/vlc-android/src/org/videolan/vlc/providers/medialibrary/MedialibraryProvider.kt
+++ b/vlc-android/src/org/videolan/vlc/providers/medialibrary/MedialibraryProvider.kt
@@ -30,6 +30,7 @@ import androidx.paging.toLiveData
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
 import org.videolan.medialibrary.interfaces.AbstractMedialibrary
 import org.videolan.medialibrary.media.MediaLibraryItem
 import org.videolan.vlc.providers.HeaderProvider
@@ -116,8 +117,8 @@ abstract class MedialibraryProvider<T : MediaLibraryItem>(val context: Context,
         override fun loadInitial(params: LoadInitialParams, callback: LoadInitialCallback<T>) {
             model.viewModelScope.launch(Dispatchers.Unconfined) {
                 retry(1) {
-                    val page = getPage(params.requestedLoadSize, params.requestedStartPosition)
-                    val count = if (page.size < params.requestedLoadSize) page.size else getTotalCount()
+                    val page = withContext(Dispatchers.IO) { getPage(params.requestedLoadSize, params.requestedStartPosition) }
+                    val count = if (page.size < params.requestedLoadSize) page.size else withContext(Dispatchers.IO) { getTotalCount() }
                     try {
                         callback.onResult(page.toList(), params.requestedStartPosition, count)
                         true
@@ -130,7 +131,10 @@ abstract class MedialibraryProvider<T : MediaLibraryItem>(val context: Context,
         }
 
         override fun loadRange(params: LoadRangeParams, callback: LoadRangeCallback<T>) {
-            callback.onResult(getPage(params.loadSize, params.startPosition).toList())
+            model.viewModelScope.launch {
+                    val result = withContext(Dispatchers.IO) { getPage(params.loadSize, params.startPosition).toList() }
+                    callback.onResult(result)
+            }
         }
     }
 



More information about the Android mailing list