[Android] Allow adding folder and subfolders to playlist from file browser
Nicolas Pomepuy
git at videolan.org
Tue Mar 10 09:36:04 CET 2020
vlc-android | branch: master | Nicolas Pomepuy <nicolas at videolabs.io> | Fri Mar 6 09:55:14 2020 +0100| [f63eed321e9a9fa8af227311123a8bfa4429b5a1] | committer: Geoffrey Métais
Allow adding folder and subfolders to playlist from file browser
Fixes #1166
> https://code.videolan.org/videolan/vlc-android/commit/f63eed321e9a9fa8af227311123a8bfa4429b5a1
---
.../resources/src/main/res/values/strings.xml | 2 ++
.../vlc-android/res/menu/activity_option.xml | 17 +++++++++++++
.../vlc/gui/browser/BaseBrowserFragment.kt | 21 ++++++++++++++++
.../org/videolan/vlc/providers/BrowserProvider.kt | 29 ++++++++++++++++++++--
.../videolan/vlc/providers/FileBrowserProvider.kt | 23 +++++++++++++++++
5 files changed, 90 insertions(+), 2 deletions(-)
diff --git a/application/resources/src/main/res/values/strings.xml b/application/resources/src/main/res/values/strings.xml
index 9a5fcd827..3a329d0e4 100644
--- a/application/resources/src/main/res/values/strings.xml
+++ b/application/resources/src/main/res/values/strings.xml
@@ -804,4 +804,6 @@
<string name="track_index">Track: %s</string>
<string name="audio_queue_progress">Progress: %s</string>
<string name="sortby_track">Track</string>
+ <string name="this_folder">This folder</string>
+ <string name="all_subfolders">This folder and subfolders</string>
</resources>
diff --git a/application/vlc-android/res/menu/activity_option.xml b/application/vlc-android/res/menu/activity_option.xml
index 578c92a77..52da413c9 100644
--- a/application/vlc-android/res/menu/activity_option.xml
+++ b/application/vlc-android/res/menu/activity_option.xml
@@ -117,6 +117,23 @@
</item>
<item
android:orderInCategory="3"
+ android:title="@string/add_to_playlist"
+ android:id="@+id/ml_menu_add_playlist"
+ vlc:showAsAction="never"
+ android:visible="false">
+ <menu>
+ <item
+ android:orderInCategory="2"
+ android:id="@+id/folder_add_playlist"
+ android:title="@string/this_folder" />
+ <item
+ android:orderInCategory="2"
+ android:id="@+id/subfolders_add_playlist"
+ android:title="@string/all_subfolders" />
+ </menu>
+ </item>
+ <item
+ android:orderInCategory="4"
android:id="@+id/ml_menu_refresh"
android:icon="@drawable/ic_menu_refresh"
vlc:showAsAction="never"
diff --git a/application/vlc-android/src/org/videolan/vlc/gui/browser/BaseBrowserFragment.kt b/application/vlc-android/src/org/videolan/vlc/gui/browser/BaseBrowserFragment.kt
index 0a12bdfba..7b6e80b6b 100644
--- a/application/vlc-android/src/org/videolan/vlc/gui/browser/BaseBrowserFragment.kt
+++ b/application/vlc-android/src/org/videolan/vlc/gui/browser/BaseBrowserFragment.kt
@@ -22,6 +22,7 @@
*/
package org.videolan.vlc.gui.browser
+import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Bundle
@@ -58,17 +59,20 @@ import org.videolan.vlc.gui.dialogs.showContext
import org.videolan.vlc.gui.helpers.MedialibraryUtils
import org.videolan.vlc.gui.helpers.UiTools
import org.videolan.vlc.gui.helpers.hf.OTG_SCHEME
+import org.videolan.vlc.gui.helpers.hf.getDocumentFiles
import org.videolan.vlc.gui.view.EmptyLoadingState
import org.videolan.vlc.gui.view.VLCDividerItemDecoration
import org.videolan.vlc.interfaces.IEventsHandler
import org.videolan.vlc.interfaces.IRefreshable
import org.videolan.vlc.media.MediaUtils
import org.videolan.vlc.media.PlaylistManager
+import org.videolan.vlc.providers.FileBrowserProvider
import org.videolan.vlc.repository.BrowserFavRepository
import org.videolan.vlc.util.Permissions
import org.videolan.vlc.util.isSchemeSupported
import org.videolan.vlc.viewmodels.browser.BrowserModel
import java.util.*
+import kotlin.collections.ArrayList
private const val TAG = "VLC/BaseBrowserFragment"
@@ -120,6 +124,7 @@ abstract class BaseBrowserFragment : MediaBrowserFragment<BrowserModel>(), IRefr
super.onPrepareOptionsMenu(menu)
menu.findItem(R.id.ml_menu_filter)?.isVisible = enableSearchOption()
menu.findItem(R.id.ml_menu_sortby)?.isVisible = !isRootDirectory
+ menu.findItem(R.id.ml_menu_add_playlist)?.isVisible = !isRootDirectory
val browserShowAllFiles = menu.findItem(R.id.browser_show_all_files)
browserShowAllFiles.isVisible = true
browserShowAllFiles.isChecked = Settings.getInstance(requireActivity()).getBoolean("browser_show_all_files", true)
@@ -429,6 +434,22 @@ abstract class BaseBrowserFragment : MediaBrowserFragment<BrowserModel>(), IRefr
}
true
}
+ R.id.folder_add_playlist -> {
+ val medias = viewModel.dataset.value.asSequence().map { it as MediaWrapper }.filter { it.type != MediaWrapper.TYPE_DIR }.toList().toTypedArray()
+ UiTools.addToPlaylist(requireActivity(), medias, SavePlaylistDialog.KEY_NEW_TRACKS)
+ true
+ }
+ R.id.subfolders_add_playlist -> {
+ currentMedia?.let {
+ lifecycleScope.launchWhenStarted {
+ val medias = withContext(Dispatchers.IO) {
+ (viewModel.provider as FileBrowserProvider).browseByUrl(it.uri.toString())
+ }
+ UiTools.addToPlaylist(requireActivity(), medias, SavePlaylistDialog.KEY_NEW_TRACKS)
+ }
+ }
+ true
+ }
else -> super.onOptionsItemSelected(item)
}
}
diff --git a/application/vlc-android/src/org/videolan/vlc/providers/BrowserProvider.kt b/application/vlc-android/src/org/videolan/vlc/providers/BrowserProvider.kt
index 9bf2ef42a..b444f6c50 100644
--- a/application/vlc-android/src/org/videolan/vlc/providers/BrowserProvider.kt
+++ b/application/vlc-android/src/org/videolan/vlc/providers/BrowserProvider.kt
@@ -51,7 +51,6 @@ import org.videolan.vlc.R
import org.videolan.vlc.util.ModelsHelper
import org.videolan.vlc.util.isBrowserMedia
import org.videolan.vlc.util.isMedia
-import java.util.*
const val TAG = "VLC/BrowserProvider"
@@ -104,6 +103,7 @@ abstract class BrowserProvider(val context: Context, val dataset: LiveDataset<Me
mediabrowser?.release()
mediabrowser = null
}
+ is BrowseUrl -> action.deferred.complete(browseUrlImpl(action.url))
}
}
}
@@ -150,6 +150,28 @@ abstract class BrowserProvider(val context: Context, val dataset: LiveDataset<Me
if (url != null ) loading.postValue(false)
}
+ suspend fun browseUrl(url: String): List<MediaLibraryItem> {
+ val deferred = CompletableDeferred<List<MediaLibraryItem>>()
+ browserActor.post(BrowseUrl(url, deferred))
+ return deferred.await()
+ }
+
+ suspend fun browseUrlImpl(url: String): List<MediaLibraryItem> {
+ val children = filesFlow(url).toList()
+ val medias = ArrayList<MediaLibraryItem>()
+ val directories = ArrayList<MediaWrapper>()
+ children.map { MLServiceLocator.getAbstractMediaWrapper(it) }.forEach {
+ when (it.type) {
+ MediaWrapper.TYPE_AUDIO, MediaWrapper.TYPE_VIDEO -> medias.add(it)
+ MediaWrapper.TYPE_DIR -> directories.add(it)
+ }
+ }
+ directories.forEach {
+ medias.addAll(browseUrlImpl(it.uri.toString()))
+ }
+ return medias.toList()
+ }
+
protected open suspend fun refreshImpl() {
val files = filesFlow().mapNotNull { findMedia(it) }.toList()
dataset.value = files as MutableList<MediaLibraryItem>
@@ -171,7 +193,9 @@ abstract class BrowserProvider(val context: Context, val dataset: LiveDataset<Me
override fun onMediaRemoved(index: Int, media: IMedia) {}
}
requestBrowsing(url, listener, interact)
- awaitClose { if (url != null) browserActor.post(ClearListener) }
+ awaitClose {
+ if (url != null) browserActor.post(ClearListener)
+ }
}.buffer(Channel.UNLIMITED)
open fun addMedia(media: MediaLibraryItem) = dataset.add(media)
@@ -345,3 +369,4 @@ private object Refresh : BrowserAction()
private class ParseSubDirectories(val list : List<MediaLibraryItem>? = null) : BrowserAction()
private object ClearListener : BrowserAction()
private object Release : BrowserAction()
+private class BrowseUrl(val url: String, val deferred: CompletableDeferred<List<MediaLibraryItem>>) : BrowserAction()
diff --git a/application/vlc-android/src/org/videolan/vlc/providers/FileBrowserProvider.kt b/application/vlc-android/src/org/videolan/vlc/providers/FileBrowserProvider.kt
index 5c71fd66d..a24970298 100644
--- a/application/vlc-android/src/org/videolan/vlc/providers/FileBrowserProvider.kt
+++ b/application/vlc-android/src/org/videolan/vlc/providers/FileBrowserProvider.kt
@@ -167,6 +167,29 @@ open class FileBrowserProvider(
}
}
+ suspend fun browseByUrl(url: String): Array<MediaWrapper> {
+ return when {
+ url == "otg://" || url.startsWith("content:") -> {
+ val result = ArrayList<MediaWrapper>()
+ launch {
+ val files = withContext(coroutineContextProvider.IO) {
+ @Suppress("UNCHECKED_CAST")
+ getDocumentFiles(context, Uri.parse(url).path?.substringAfterLast(':')
+ ?: "") as? MutableList<MediaLibraryItem> ?: mutableListOf()
+ }.map { it as MediaWrapper }
+
+ result.addAll(files.filter { it.itemType == MediaWrapper.TYPE_MEDIA })
+ files.filter { it.itemType == MediaWrapper.TYPE_DIR }.forEach {
+ result.addAll(browseByUrl(it.uri.toString()))
+ }
+ }
+ result.toList().toTypedArray()
+ }
+
+ else -> super.browseUrl(url).toList().map { it as MediaWrapper }.toTypedArray()
+ }
+ }
+
override fun release() {
if (url == null) {
ExternalMonitor.devices.removeObserver(this)
More information about the Android
mailing list