[Android] Allow browsing a playlist

Nicolas Pomepuy git at videolan.org
Wed Mar 6 07:52:32 UTC 2024


vlc-android | branch: master | Nicolas Pomepuy <nicolas at videolabs.io> | Thu Feb 29 14:31:27 2024 +0100| [c2676c92e5ec122861a1e78dc71426c2f7ca2bd1] | committer: Duncan McNamara

Allow browsing a playlist

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

 .../videolan/vlc/webserver/RemoteAccessRouting.kt  | 19 +++++
 .../videolan/vlc/webserver/RemoteAccessServer.kt   |  1 +
 .../src/components/MediaItem.vue                   |  6 +-
 .../src/pages/PlaylistDetails.vue                  | 83 ++++++++++++++++++++++
 .../network-sharing-server/src/plugins/api.js      |  6 ++
 .../network-sharing-server/src/plugins/vlcUtils.js |  2 +
 buildsystem/network-sharing-server/src/routes.js   |  9 ++-
 7 files changed, 123 insertions(+), 3 deletions(-)

diff --git a/application/webserver/src/main/java/org/videolan/vlc/webserver/RemoteAccessRouting.kt b/application/webserver/src/main/java/org/videolan/vlc/webserver/RemoteAccessRouting.kt
index f64af9d2dd..a57da8474a 100644
--- a/application/webserver/src/main/java/org/videolan/vlc/webserver/RemoteAccessRouting.kt
+++ b/application/webserver/src/main/java/org/videolan/vlc/webserver/RemoteAccessRouting.kt
@@ -517,6 +517,25 @@ fun Route.setupRouting(appContext: Context, scope: CoroutineScope) {
             val gson = Gson()
             call.respondText(gson.toJson(result))
         }
+        // Get an playlist details
+        get("/playlist") {
+            verifyLogin(settings)
+            if (!settings.serveAudios(appContext)) {
+                call.respond(HttpStatusCode.Forbidden)
+                return at get
+            }
+            val id = call.request.queryParameters["id"]?.toLong() ?: 0L
+
+            val playlist = appContext.getFromMl { getPlaylist(id, false, false) }
+
+            val list = ArrayList<RemoteAccessServer.PlayQueueItem>()
+            playlist.tracks.forEach { track ->
+                list.add(track.toPlayQueueItem())
+            }
+            val result= RemoteAccessServer.PlaylistResult(list, playlist.title)
+            val gson = Gson()
+            call.respondText(gson.toJson(result))
+        }
         // Get an artist details
         get("/artist") {
             verifyLogin(settings)
diff --git a/application/webserver/src/main/java/org/videolan/vlc/webserver/RemoteAccessServer.kt b/application/webserver/src/main/java/org/videolan/vlc/webserver/RemoteAccessServer.kt
index 20855cf995..a323ba0c5c 100644
--- a/application/webserver/src/main/java/org/videolan/vlc/webserver/RemoteAccessServer.kt
+++ b/application/webserver/src/main/java/org/videolan/vlc/webserver/RemoteAccessServer.kt
@@ -752,6 +752,7 @@ class RemoteAccessServer(private val context: Context) : PlaybackService.Callbac
     data class VideoListResult(val content: List<PlayQueueItem>, val item: String)
     data class ArtistResult(val albums: List<PlayQueueItem>, val tracks: List<PlayQueueItem>, val name: String)
     data class AlbumResult(val tracks: List<PlayQueueItem>, val name: String)
+    data class PlaylistResult(val tracks: List<PlayQueueItem>, val name: String)
 
     fun getSecureUrl(call: ApplicationCall) = "https://${call.request.host()}:${engine.environment.connectors.first { it.type.name == "HTTPS" }.port}"
 
diff --git a/buildsystem/network-sharing-server/src/components/MediaItem.vue b/buildsystem/network-sharing-server/src/components/MediaItem.vue
index 3d2165b92d..5273834305 100644
--- a/buildsystem/network-sharing-server/src/components/MediaItem.vue
+++ b/buildsystem/network-sharing-server/src/components/MediaItem.vue
@@ -112,7 +112,7 @@ export default {
             return (this.mediaType == 'album' || this.mediaType == 'artist')
         },
         isOpenable() {
-            return ['video-group', 'video-folder', 'artist', 'album'].includes(this.mediaType)
+            return ['video-group', 'video-folder', 'artist', 'album', 'playlist'].includes(this.mediaType)
         },
         getDescription() {
             if (this.mediaType == 'video') {
@@ -123,7 +123,9 @@ export default {
             }
         },
         manageClick() {
-            if (this.mediaType == 'album') {
+            if (this.mediaType == 'playlist') {
+                this.$router.push({ name: 'PlaylistDetails', params: { playlistId: this.media.id } })
+            } else if (this.mediaType == 'album') {
                 this.$router.push({ name: 'AlbumDetails', params: { albumId: this.media.id } })
             } else if (this.mediaType == 'artist') {
                 this.$router.push({ name: 'ArtistDetails', params: { artistId: this.media.id } })
diff --git a/buildsystem/network-sharing-server/src/pages/PlaylistDetails.vue b/buildsystem/network-sharing-server/src/pages/PlaylistDetails.vue
new file mode 100644
index 0000000000..39a13812fe
--- /dev/null
+++ b/buildsystem/network-sharing-server/src/pages/PlaylistDetails.vue
@@ -0,0 +1,83 @@
+<template>
+    <div v-if="loaded && this.tracks.length !== 0" class="container">
+        <div v-if="this.appStore.displayType[this.$route.name]" class="row gx-3 gy-3 media-list">
+            <template v-for="track in tracks" :key="track.id">
+                <MediaItem :isCard="false" :media="track" :mediaType="'track'" />
+            </template>
+        </div>
+        <div v-else class="row gx-3 gy-3 media-content">
+            <div class="col-md-3 col-lg-2 col-sm-4 col-6" v-for="track in tracks" :key="track.id">
+                <MediaItem :isCard="true" :media="track" :mediaType="'track'" />
+            </div>
+        </div>
+    </div>
+    <div v-else-if="loaded" class="empty-view-container">
+        <EmptyView :message="getEmptyText()" />
+    </div>
+</template>
+
+<script>
+
+import { useAppStore } from '../stores/AppStore'
+import { mapStores } from 'pinia'
+import http from '../plugins/auth'
+import { vlcApi } from '../plugins/api.js'
+import MediaItem from '../components/MediaItem.vue'
+import EmptyView from '../components/EmptyView.vue'
+
+export default {
+    computed: {
+        ...mapStores(useAppStore)
+    },
+    components: {
+        MediaItem,
+        EmptyView,
+    },
+    data() {
+        return {
+            tracks: [],
+            loaded: false,
+            forbidden: false,
+        }
+    },
+    methods: {
+        fetchTracks() {
+            let component = this
+            component.appStore.loading = true
+            let playlistId = this.$route.params.playlistId
+            this.$log.log(`Loading artist: ${playlistId}`)
+            http.get(vlcApi.playlistDetails(playlistId))
+                .catch(function (error) {
+                    if (error.response !== undefined && error.response.status == 403) {
+                        component.forbidden = true;
+                    }
+                })
+                .then((response) => {
+                    this.loaded = true;
+                    if (response) {
+                        component.forbidden = false;
+                        this.tracks = response.data.tracks
+                    }
+                    component.appStore.loading = false
+                    component.appStore.title = response.data.name
+                });
+
+        },
+        getEmptyText() {
+            if (this.forbidden) return this.$t('FORBIDDEN')
+            return this.$t('NO_MEDIA')
+        }
+    },
+    created: function () {
+        this.fetchTracks();
+    },
+    unmounted: function() {
+        console.log("unmounted")
+        this.appStore.title = ''
+    }
+}
+</script>
+
+<style lang='scss'>
+ at import '../scss/colors.scss';
+</style>
diff --git a/buildsystem/network-sharing-server/src/plugins/api.js b/buildsystem/network-sharing-server/src/plugins/api.js
index 26f3a1721e..cd0a01025f 100644
--- a/buildsystem/network-sharing-server/src/plugins/api.js
+++ b/buildsystem/network-sharing-server/src/plugins/api.js
@@ -88,6 +88,12 @@ export const vlcApi = {
     albumDetails: (albumId) => { 
         return`${API_URL}album?id=${albumId}`
     },
+    /**
+     * Retrieve the playlist details API URL
+     */
+    playlistDetails: (playlistId) => { 
+        return`${API_URL}playlist?id=${playlistId}`
+    },
     /**
      * Retrieve the playlist list API URL
      */
diff --git a/buildsystem/network-sharing-server/src/plugins/vlcUtils.js b/buildsystem/network-sharing-server/src/plugins/vlcUtils.js
index f02e631767..ae0becdf28 100644
--- a/buildsystem/network-sharing-server/src/plugins/vlcUtils.js
+++ b/buildsystem/network-sharing-server/src/plugins/vlcUtils.js
@@ -61,6 +61,8 @@ export default {
                     break
                 case "album": id = route.params.albumId
                     break
+                case "playlist": id = route.params.playlistId
+                    break
                 default: id = 0
             }
             let path = (type == "browser") ? route.params.browseId : ""
diff --git a/buildsystem/network-sharing-server/src/routes.js b/buildsystem/network-sharing-server/src/routes.js
index d945b4d06f..252db9526c 100644
--- a/buildsystem/network-sharing-server/src/routes.js
+++ b/buildsystem/network-sharing-server/src/routes.js
@@ -12,6 +12,7 @@ import LoginPage from './pages/LoginPage'
 import SslPage from './pages/SslPage'
 import ArtistDetails from './pages/ArtistDetails'
 import AlbumDetails from './pages/AlbumDetails'
+import PlaylistDetails from './pages/PlaylistDetails'
 
 const routes = [
   { path: '/', redirect: '/videos', name: 'Home' },
@@ -41,7 +42,13 @@ const routes = [
       { path: ':browseId', component: BrowseChild, name: 'BrowseChild', meta: { showDisplayBar: true, showFAB: true, playAllType: "browser" } },
     ]
   },
-  { path: '/playlists', component: PlaylistList, name: 'PlaylistList', meta: { showDisplayBar: true } },
+  { 
+    path: '/playlists', redirect: '/playlists/all', name: 'PlaylistList',
+    children : [
+      {path: 'all', component: PlaylistList, name: 'PlaylistList', meta: { showDisplayBar: true }},
+      { path: 'playlist/:playlistId', component: PlaylistDetails, name: 'PlaylistDetails', meta: { showDisplayBar: true, isAudio: false, showResume: false, showGrouping: false, showFAB: true, playAllType: "playlist" } }
+    ]
+},
   { path: '/search', component: SearchList, name: 'SearchList', meta: { showDisplayBar: false } },
 
   { path: '/logs', component: PageDownloads, name: 'Logs' },



More information about the Android mailing list