diff --git a/platypush/backend/http/webapp/src/components/Media/Controls.vue b/platypush/backend/http/webapp/src/components/Media/Controls.vue
index 293335834..222cdf230 100644
--- a/platypush/backend/http/webapp/src/components/Media/Controls.vue
+++ b/platypush/backend/http/webapp/src/components/Media/Controls.vue
@@ -4,7 +4,7 @@
          @click.prevent="searchAlbum"
          v-if="status?.state !== 'stop'">
       <div class="remote-image-container" v-if="trackImage">
-        <img class="image" :src="trackImage" :alt="track.title">
+        <img class="image" :src="trackImage" :alt="trackTitle">
       </div>
 
       <div class="icon-container" v-else>
@@ -59,18 +59,18 @@
     <div class="track-container col-s-9 col-m-9 col-l-3">
       <div class="track-info" @click="$emit('info', track)" v-if="track && status?.state !== 'stop'">
         <div class="img-container" v-if="trackImage">
-          <img class="image from desktop" :src="trackImage" :alt="track.title">
+          <img class="image from desktop" :src="trackImage" :alt="trackTitle">
         </div>
 
         <div class="title-container">
           <div class="title" v-if="status.state === 'play' || status.state === 'pause'">
-            <a :href="$route.fullPath" v-text="track.title?.length ? track.title : '[No Title]'"
+            <a :href="$route.fullPath" v-text="trackTitle"
                @click.prevent="searchAlbum" v-if="track.album"></a>
-            <a v-text="track.title?.length ? track.title : '[No Title]'" v-else-if="track.url"></a>
-            <span v-text="track.title?.length ? track.title : '[No Title]' " v-else></span>
+            <a v-text="trackTitle" v-else-if="track.url"></a>
+            <span v-text="trackTitle" v-else></span>
           </div>
-          <div class="artist" v-if="track.artist?.length && (status.state === 'play' || status.state === 'pause')">
-            <a v-text="track.artist" @click.prevent="searchArtist"></a>
+          <div class="artist" v-if="trackArtistName?.length && (status.state === 'play' || status.state === 'pause')">
+            <a v-text="trackArtistName" @click.prevent="searchArtist"></a>
           </div>
         </div>
       </div>
@@ -206,12 +206,27 @@ export default {
       return null
     },
 
+    trackArtistId() {
+      return typeof this.track?.artist === 'object' ? this.track.artist.id : null
+    },
+
+    trackArtistName() {
+      if (typeof this.track?.artist === 'string')
+        return this.track.artist
+
+      return this.track?.artist?.name || this.track?.artist?.title
+    },
+
     trackImage() {
       if (this.track?.images?.length)
         return this.track.images[0].url
 
       return this.track?.image || this.image
     },
+
+    trackTitle() {
+      return this.track?.title || this.track?.name || '[No Title]'
+    },
   },
 
   methods: {
@@ -235,11 +250,11 @@ export default {
     },
 
     searchArtist() {
-      if (!this.track?.artist)
+      if (!this.trackArtistName?.length)
         return
 
       const args = {
-        artist: this.track.artist,
+        artist: this.trackArtistName,
       }
 
       if (this.track.artist_uri)
diff --git a/platypush/backend/http/webapp/src/components/panels/Media/Index.vue b/platypush/backend/http/webapp/src/components/panels/Media/Index.vue
index f74729dc7..efebfa2b2 100644
--- a/platypush/backend/http/webapp/src/components/panels/Media/Index.vue
+++ b/platypush/backend/http/webapp/src/components/panels/Media/Index.vue
@@ -95,6 +95,10 @@
               />
             </div>
           </div>
+
+          <div class="media-loading-indicator" v-if="opening">
+            <Loading />
+          </div>
         </main>
       </MediaView>
 
@@ -149,6 +153,7 @@ import Utils from "@/Utils";
 import Browser from "@/components/panels/Media/Browser";
 import Header from "@/components/panels/Media/Header";
 import Info from "@/components/panels/Media/Info";
+import Loading from "@/components/Loading";
 import MediaDownloads from "@/components/panels/Media/Downloads";
 import MediaUtils from "@/components/Media/Utils";
 import MediaView from "@/components/Media/View";
@@ -166,6 +171,7 @@ export default {
     Browser,
     Header,
     Info,
+    Loading,
     MediaDownloads,
     MediaView,
     Modal,
@@ -205,6 +211,7 @@ export default {
       forceShowNav: false,
       infoTrack: null,
       loading: false,
+      opening: false,
       prevSelectedView: null,
       results: [],
       selectedPlayer: null,
@@ -324,7 +331,7 @@ export default {
         return
       }
 
-      this.loading = true
+      this.opening = true
 
       try {
         if (!this.selectedPlayer.component.supports(item))
@@ -334,9 +341,9 @@ export default {
           item, this.selectedSubtitles, this.selectedPlayer, opts
         )
 
-        await this.refresh()
+        await this.refresh(item)
       } finally {
-        this.loading = false
+        this.opening = false
       }
     },
 
@@ -383,15 +390,36 @@ export default {
       await this.download(item, {onlyAudio: true})
     },
 
-    async refresh() {
-      this.selectedPlayer.status = await this.selectedPlayer.component.status(this.selectedPlayer)
+    async refresh(item) {
+      let newStatus = {
+        ...(await this.selectedPlayer.component.status(this.selectedPlayer)),
+        ...(item || {}),
+      }
+
+      this.setStatus(newStatus)
+    },
+
+    setStatus(status) {
+      const curStatus = this.selectedPlayer?.status || {}
+      let newStatus = {}
+
+      if (curStatus.resource === status.resource) {
+        newStatus = {
+          ...curStatus,
+          ...status,
+        }
+      } else {
+        newStatus = status
+      }
+
+      this.selectedPlayer.status = newStatus
     },
 
     onStatusUpdate(status) {
       if (!this.selectedPlayer)
         return
 
-      this.selectedPlayer.status = status
+      this.setStatus(status)
     },
 
     onPlayUrlModalOpen() {
@@ -558,17 +586,7 @@ export default {
 
     async playUrl(url) {
       this.urlPlay = url
-      this.loading = true
-
-      try {
-        await this.play({
-          url: url,
-        })
-
-        this.$refs.playUrlModal.close()
-      } finally {
-        this.loading = false
-      }
+      await this.play({ url: url })
     },
 
     async refreshDownloads() {
@@ -788,6 +806,7 @@ export default {
     height: 100%;
     display: flex;
     flex-direction: row-reverse;
+    position: relative;
 
     .view-container {
       display: flex;
@@ -806,6 +825,24 @@ export default {
         height: calc(100% - #{$media-header-height} - #{$filter-header-height} - #{$media-ctrl-panel-height});
       }
     }
+
+    :deep(.media-loading-indicator) {
+      position: absolute;
+      top: 0;
+      left: 0;
+      width: 5em;
+      height: 5em;
+      border-radius: 50%;
+      background: rgba(0, 0, 0, 0);
+      display: flex;
+      justify-content: center;
+      align-items: center;
+      z-index: 10;
+
+      .loading {
+        border-radius: 50%;
+      }
+    }
   }
 }
 
diff --git a/platypush/backend/http/webapp/src/components/panels/Media/Info.vue b/platypush/backend/http/webapp/src/components/panels/Media/Info.vue
index 575ef3226..6dd195fbc 100644
--- a/platypush/backend/http/webapp/src/components/panels/Media/Info.vue
+++ b/platypush/backend/http/webapp/src/components/panels/Media/Info.vue
@@ -1,5 +1,7 @@
 <template>
   <div class="media-info">
+    <Loading v-if="loading" />
+
     <div class="row header">
       <div class="item-container">
         <Item :item="item"
@@ -25,91 +27,113 @@
       </div>
     </div>
 
-    <div class="row" v-if="item?.series">
-      <div class="left side">TV Series</div>
-      <div class="right side" v-text="item.series" />
-    </div>
-
-    <div class="row" v-if="item?.season">
-      <div class="left side">Season</div>
-      <div class="right side" v-text="item.season" />
-    </div>
-
-    <div class="row" v-if="item?.episode">
-      <div class="left side">Episode</div>
-      <div class="right side" v-text="item.episode" />
-    </div>
-
-    <div class="row" v-if="item?.num_seasons">
-      <div class="left side">Number of seasons</div>
-      <div class="right side" v-text="item.num_seasons" />
-    </div>
-
-    <div class="row" v-if="item?.description">
-      <div class="left side">Description</div>
-      <div class="right side" v-text="item.description" />
-    </div>
-
-    <div class="row" v-if="item?.summary">
-      <div class="left side">Summary</div>
-      <div class="right side" v-text="item.summary" />
-    </div>
-
-    <div class="row" v-if="item?.overview">
-      <div class="left side">Overview</div>
-      <div class="right side" v-text="item.overview" />
-    </div>
-
-    <div class="row" v-if="item?.country">
-      <div class="left side">Country</div>
-      <div class="right side" v-text="item.country" />
-    </div>
-
-    <div class="row" v-if="item?.network">
-      <div class="left side">Network</div>
-      <div class="right side" v-text="item.network" />
-    </div>
-
-    <div class="row" v-if="item?.status">
-      <div class="left side">Status</div>
-      <div class="right side" v-text="item.status" />
-    </div>
-
-    <div class="row" v-if="item?.width && item?.height">
-      <div class="left side">Resolution</div>
+    <div class="row direct-url" v-if="computedItem?.imdb_url">
+      <div class="left side">ImDB URL</div>
       <div class="right side">
-        {{ item.width }}x{{ item.height }}
+        <a :href="computedItem.imdb_url" title="ImDB URL" target="_blank">
+          <i class="fas fa-external-link-alt" />
+        </a>
+        <button @click="copyToClipboard(computedItem.imdb_url)" title="Copy URL to clipboard">
+          <i class="fas fa-clipboard" />
+        </button>
       </div>
     </div>
 
-    <div class="row" v-if="item?.view_count != null">
+    <div class="row" v-if="computedItem?.artist?.name">
+      <div class="left side">Artist</div>
+      <div class="right side" v-text="computedItem.artist.name" />
+    </div>
+
+    <div class="row" v-if="computedItem?.album?.name">
+      <div class="left side">Album</div>
+      <div class="right side" v-text="computedItem.album.name" />
+    </div>
+
+    <div class="row" v-if="computedItem?.series">
+      <div class="left side">TV Series</div>
+      <div class="right side" v-text="computedItem.series" />
+    </div>
+
+    <div class="row" v-if="computedItem?.season">
+      <div class="left side">Season</div>
+      <div class="right side" v-text="computedItem.season" />
+    </div>
+
+    <div class="row" v-if="computedItem?.episode">
+      <div class="left side">Episode</div>
+      <div class="right side" v-text="computedItem.episode" />
+    </div>
+
+    <div class="row" v-if="computedItem?.num_seasons">
+      <div class="left side">Number of seasons</div>
+      <div class="right side" v-text="computedItem.num_seasons" />
+    </div>
+
+    <div class="row" v-if="computedItem?.description">
+      <div class="left side">Description</div>
+      <div class="right side" v-text="computedItem.description" />
+    </div>
+
+    <div class="row" v-if="computedItem?.summary">
+      <div class="left side">Summary</div>
+      <div class="right side" v-text="computedItem.summary" />
+    </div>
+
+    <div class="row" v-if="computedItem?.overview">
+      <div class="left side">Overview</div>
+      <div class="right side" v-text="computedItem.overview" />
+    </div>
+
+    <div class="row" v-if="computedItem?.country">
+      <div class="left side">Country</div>
+      <div class="right side" v-text="computedItem.country" />
+    </div>
+
+    <div class="row" v-if="computedItem?.network">
+      <div class="left side">Network</div>
+      <div class="right side" v-text="computedItem.network" />
+    </div>
+
+    <div class="row" v-if="computedItem?.status">
+      <div class="left side">Status</div>
+      <div class="right side" v-text="computedItem.status" />
+    </div>
+
+    <div class="row" v-if="computedItem?.width && computedItem?.height">
+      <div class="left side">Resolution</div>
+      <div class="right side">
+        {{ computedItem.width }}x{{ computedItem.height }}
+      </div>
+    </div>
+
+    <div class="row" v-if="computedItem?.view_count != null">
       <div class="left side">Views</div>
-      <div class="right side">{{ formatNumber(item.view_count) }}</div>
+      <div class="right side">{{ formatNumber(computedItem.view_count) }}</div>
     </div>
 
-    <div class="row" v-if="item?.rating">
+    <div class="row" v-if="computedItem?.rating">
       <div class="left side">Rating</div>
-      <div class="right side">{{ item.rating }}%</div>
+      <div class="right side">{{ Math.round(computedItem.rating) }}%</div>
     </div>
 
-    <div class="row" v-if="item?.critic_rating">
+    <div class="row" v-if="computedItem?.critic_rating">
       <div class="left side">Critic Rating</div>
-      <div class="right side">{{ item.critic_rating }}%</div>
+      <div class="right side">{{ Math.round(computedItem.critic_rating) }}%</div>
     </div>
 
-    <div class="row" v-if="item?.community_rating">
+    <div class="row" v-if="computedItem?.community_rating">
       <div class="left side">Community Rating</div>
-      <div class="right side">{{ item.community_rating }}%</div>
+      <div class="right side">{{ Math.round(computedItem.community_rating) }}%</div>
     </div>
 
-    <div class="row" v-if="item?.votes">
+    <div class="row" v-if="computedItem?.votes">
       <div class="left side">Votes</div>
-      <div class="right side" v-text="item.votes" />
+      <div class="right side" v-text="computedItem.votes" />
     </div>
 
-    <div class="row" v-if="item?.genres">
+    <div class="row" v-if="computedItem?.genres">
       <div class="left side">Genres</div>
-      <div class="right side" v-text="item.genres.join(', ')" />
+      <div class="right side" v-text="computedItem.genres.join(', ')" />
     </div>
 
     <div class="row" v-if="channel">
@@ -119,9 +143,9 @@
       </div>
     </div>
 
-    <div class="row" v-if="item?.year">
+    <div class="row" v-if="computedItem?.year">
       <div class="left side">Year</div>
-      <div class="right side" v-text="item.year" />
+      <div class="right side" v-text="computedItem.year" />
     </div>
 
     <div class="row" v-if="publishedDate">
@@ -129,46 +153,56 @@
       <div class="right side" v-text="publishedDate" />
     </div>
 
-    <div class="row" v-if="item?.file">
+    <div class="row" v-if="computedItem?.file">
       <div class="left side">File</div>
-      <div class="right side" v-text="item.file" />
+      <div class="right side" v-text="computedItem.file" />
     </div>
 
-    <div class="row" v-if="item?.trailer">
+    <div class="row" v-if="computedItem?.track_number != null">
+      <div class="left side">Track</div>
+      <div class="right side" v-text="computedItem.track_number" />
+    </div>
+
+    <div class="row" v-if="computedItem?.trailer">
       <div class="left side">Trailer</div>
       <div class="right side url">
-        <a :href="item.trailer" target="_blank" v-text="item.trailer" />
+        <a :href="computedItem.trailer" target="_blank" v-text="computedItem.trailer" />
       </div>
     </div>
 
-    <div class="row" v-if="item?.size">
+    <div class="row" v-if="computedItem?.size">
       <div class="left side">Size</div>
-      <div class="right side" v-text="convertSize(item.size)" />
+      <div class="right side" v-text="convertSize(computedItem.size)" />
     </div>
 
-    <div class="row" v-if="item?.quality">
+    <div class="row" v-if="computedItem?.quality">
       <div class="left side">Quality</div>
-      <div class="right side" v-text="item.quality" />
+      <div class="right side" v-text="computedItem.quality" />
     </div>
 
-    <div class="row" v-if="item?.seeds">
+    <div class="row" v-if="computedItem?.seeds">
       <div class="left side">Seeds</div>
-      <div class="right side" v-text="item.seeds" />
+      <div class="right side" v-text="computedItem.seeds" />
     </div>
 
-    <div class="row" v-if="item?.peers">
+    <div class="row" v-if="computedItem?.peers">
       <div class="left side">Peers</div>
-      <div class="right side" v-text="item.peers" />
+      <div class="right side" v-text="computedItem.peers" />
     </div>
 
-    <div class="row" v-if="item?.language">
+    <div class="row" v-if="computedItem?.tags">
+      <div class="left side">Tags</div>
+      <div class="right side" v-text="computedItem.tags.join(', ')" />
+    </div>
+
+    <div class="row" v-if="computedItem?.language">
       <div class="left side">Language</div>
-      <div class="right side" v-text="item.language" />
+      <div class="right side" v-text="computedItem.language" />
     </div>
 
-    <div class="row" v-if="item?.audio_channels">
+    <div class="row" v-if="computedItem?.audio_channels">
       <div class="left side">Audio Channels</div>
-      <div class="right side" v-text="item.audio_channels" />
+      <div class="right side" v-text="computedItem.audio_channels" />
     </div>
   </div>
 </template>
@@ -176,6 +210,7 @@
 <script>
 import Icons from "./icons.json";
 import Item from "./Item";
+import Loading from "@/components/Loading";
 import MediaUtils from "@/components/Media/Utils";
 import Utils from "@/Utils";
 
@@ -183,6 +218,7 @@ export default {
   name: "Info",
   components: {
     Item,
+    Loading,
   },
   mixins: [Utils, MediaUtils],
   emits: [
@@ -207,8 +243,10 @@ export default {
   data() {
     return {
       typeIcons: Icons,
+      loading: false,
       loadingUrl: false,
       youtubeUrl: null,
+      metadata: null,
     }
   },
 
@@ -235,6 +273,13 @@ export default {
       return ret
     },
 
+    computedItem() {
+      return {
+        ...(this.item || {}),
+        ...(this.metadata || {}),
+      }
+    },
+
     publishedDate() {
       if (this.item?.publishedAt)
         return this.formatDate(this.item.publishedAt, true)
@@ -263,6 +308,35 @@ export default {
       return this.item?.url
     },
   },
+
+  methods: {
+    async updateMetadata() {
+      this.loading = true
+
+      try {
+        if (this.item?.type === 'jellyfin' && this.item?.id) {
+          this.metadata = await this.request('media.jellyfin.info', {
+            item_id: this.item.id,
+          })
+        }
+      } finally {
+        this.loading = false
+      }
+    },
+  },
+
+  watch: {
+    item: {
+      handler() {
+        this.updateMetadata()
+      },
+      deep: true,
+    },
+  },
+
+  mounted() {
+    this.updateMetadata()
+  },
 }
 </script>
 
@@ -271,6 +345,7 @@ export default {
 
 .media-info {
   width: 100%;
+  max-width: 60em;
 }
 
 .row {
diff --git a/platypush/backend/http/webapp/src/components/panels/Media/Item.vue b/platypush/backend/http/webapp/src/components/panels/Media/Item.vue
index 1f1b73be5..710e43acb 100644
--- a/platypush/backend/http/webapp/src/components/panels/Media/Item.vue
+++ b/platypush/backend/http/webapp/src/components/panels/Media/Item.vue
@@ -1,35 +1,51 @@
 <template>
   <div
     class="item media-item"
-    :class="{selected: selected}"
+    :class="{selected: selected, 'list': listView}"
     @click.right.prevent="$refs.dropdown.toggle()"
     v-if="!hidden">
-    <div class="thumbnail">
+
+    <div class="thumbnail" v-if="!listView">
       <MediaImage :item="item" @play="$emit('play')" @select="$emit('select')" />
     </div>
 
     <div class="body">
       <div class="row title">
-        <div class="col-11 left side" v-text="item.title || item.name" @click="$emit('select')" />
-        <div class="col-1 right side">
-          <Dropdown title="Actions" icon-class="fa fa-ellipsis-h" ref="dropdown">
-            <DropdownItem icon-class="fa fa-play" text="Play" @input="$emit('play')"
-                          v-if="item.type !== 'torrent'" />
-            <DropdownItem icon-class="fa fa-play" text="Play (With Cache)"
-                          @input="$emit('play-with-opts', {item: item, opts: {cache: true}})"
-                          v-if="item.type === 'youtube'" />
-            <DropdownItem icon-class="fa fa-download" text="Download" @input="$emit('download')"
-                          v-if="(item.type === 'torrent' || item.type === 'youtube') && item.item_type !== 'channel' && item.item_type !== 'playlist'" />
-            <DropdownItem icon-class="fa fa-volume-high" text="Download Audio" @input="$emit('download-audio')"
-                          v-if="item.type === 'youtube' && item.item_type !== 'channel' && item.item_type !== 'playlist'" />
-            <DropdownItem icon-class="fa fa-list" text="Add to playlist" @input="$emit('add-to-playlist')"
-                          v-if="item.type === 'youtube'" />
-            <DropdownItem icon-class="fa fa-trash" text="Remove from playlist" @input="$emit('remove-from-playlist')"
-                          v-if="item.type === 'youtube' && playlist?.length" />
-            <DropdownItem icon-class="fa fa-window-maximize" text="View in browser" @input="$emit('view')"
-                          v-if="item.type === 'file'" />
-            <DropdownItem icon-class="fa fa-info-circle" text="Info" @input="$emit('select')" />
-          </Dropdown>
+        <div class="left side"
+             :class="{'col-11': !listView, 'col-10': listView }"
+             @click.stop="$emit('select')">
+          <span class="track-number" v-if="listView && item.track_number">
+            {{ item.track_number }}
+          </span>
+
+          {{item.title || item.name}}
+        </div>
+
+        <div class="right side" :class="{'col-1': !listView, 'col-2': listView }">
+          <span class="duration" v-if="item.duration && listView">
+            <span v-text="formatDuration(item.duration, true)" />
+          </span>
+
+          <span class="actions">
+            <Dropdown title="Actions" icon-class="fa fa-ellipsis-h" ref="dropdown">
+              <DropdownItem icon-class="fa fa-play" text="Play" @input="$emit('play')"
+                            v-if="item.type !== 'torrent'" />
+              <DropdownItem icon-class="fa fa-play" text="Play (With Cache)"
+                            @input="$emit('play-with-opts', {item: item, opts: {cache: true}})"
+                            v-if="item.type === 'youtube'" />
+              <DropdownItem icon-class="fa fa-download" text="Download" @input="$emit('download')"
+                            v-if="(item.type === 'torrent' || item.type === 'youtube') && item.item_type !== 'channel' && item.item_type !== 'playlist'" />
+              <DropdownItem icon-class="fa fa-volume-high" text="Download Audio" @input="$emit('download-audio')"
+                            v-if="item.type === 'youtube' && item.item_type !== 'channel' && item.item_type !== 'playlist'" />
+              <DropdownItem icon-class="fa fa-list" text="Add to playlist" @input="$emit('add-to-playlist')"
+                            v-if="item.type === 'youtube'" />
+              <DropdownItem icon-class="fa fa-trash" text="Remove from playlist" @input="$emit('remove-from-playlist')"
+                            v-if="item.type === 'youtube' && playlist?.length" />
+              <DropdownItem icon-class="fa fa-window-maximize" text="View in browser" @input="$emit('view')"
+                            v-if="item.type === 'file'" />
+              <DropdownItem icon-class="fa fa-info-circle" text="Info" @input="$emit('select')" />
+            </Dropdown>
+          </span>
         </div>
       </div>
 
@@ -40,11 +56,11 @@
         </a>
       </div>
 
-      <div class="row creation-date" v-if="item.created_at">
+      <div class="row creation-date" v-if="item.created_at && showDate">
         {{ formatDateTime(item.created_at, true) }}
       </div>
 
-      <div class="row creation-date" v-text="item.year" v-else-if="item.year" />
+      <div class="row creation-date" v-text="item.year" v-else-if="item.year && showDate" />
 
       <div class="row ratings" v-if="item.critic_rating != null || item.community_rating != null">
         <span class="rating" title="Critic rating" v-if="item.critic_rating != null">
@@ -94,7 +110,7 @@ export default {
       default: false,
     },
 
-    selected: {
+    listView: {
       type: Boolean,
       default: false,
     },
@@ -102,6 +118,16 @@ export default {
     playlist: {
       type: String,
     },
+
+    selected: {
+      type: Boolean,
+      default: false,
+    },
+
+    showDate: {
+      type: Boolean,
+      default: true,
+    },
   },
 
   data() {
@@ -261,5 +287,47 @@ export default {
       }
     }
   }
+
+  &.list {
+    max-height: none;
+    border-bottom: 1px solid $default-shadow-color !important;
+    margin: 0;
+    padding: 0.25em 0.5em;
+
+    &:hover {
+      text-decoration: none;
+    }
+
+    .side {
+      display: flex;
+      align-items: center;
+
+      &.left {
+        max-height: none;
+        flex-direction: row;
+        overflow: visible;
+      }
+
+      &.right {
+        display: flex;
+        justify-content: flex-end;
+        margin-right: 0;
+      }
+
+      .duration {
+        font-size: .9em;
+        opacity: .75;
+        margin-right: 1em;
+      }
+
+      .track-number {
+        display: inline-flex;
+        font-size: .9em;
+        margin-right: 1em;
+        color: $default-fg-2;
+        justify-content: flex-end;
+      }
+    }
+  }
 }
 </style>
diff --git a/platypush/backend/http/webapp/src/components/panels/Media/Providers/Jellyfin.vue b/platypush/backend/http/webapp/src/components/panels/Media/Providers/Jellyfin.vue
index 72da23e94..6c2507947 100644
--- a/platypush/backend/http/webapp/src/components/panels/Media/Providers/Jellyfin.vue
+++ b/platypush/backend/http/webapp/src/components/panels/Media/Providers/Jellyfin.vue
@@ -44,6 +44,7 @@ export default {
     'download-audio',
     'play',
     'play-with-opts',
+    'select',
   ],
 
   data() {
@@ -84,6 +85,8 @@ export default {
           return 'movies'
         case 'homevideos':
           return 'videos'
+        case 'music':
+          return 'music'
         default:
           return 'index'
       }
diff --git a/platypush/backend/http/webapp/src/components/panels/Media/Providers/Jellyfin/Collections.vue b/platypush/backend/http/webapp/src/components/panels/Media/Providers/Jellyfin/Collections.vue
index 523f294c9..c5ab06a89 100644
--- a/platypush/backend/http/webapp/src/components/panels/Media/Providers/Jellyfin/Collections.vue
+++ b/platypush/backend/http/webapp/src/components/panels/Media/Providers/Jellyfin/Collections.vue
@@ -16,6 +16,10 @@
         <div class="name" v-if="fallbackImageCollections[collection.id] || parentId">
           <h2>{{ collection.name }}</h2>
         </div>
+
+        <div class="float bottom-right" v-if="collection.year">
+          <span>{{ collection.year }}</span>
+        </div>
       </div>
     </div>
   </div>
@@ -60,7 +64,17 @@ export default {
     filteredItems() {
       return Object.values(this.items).filter(
         (item) => !this.filter || item.name.toLowerCase().includes(this.filter.toLowerCase())
-      ).sort((a, b) => a.name.localeCompare(b.name))
+      ).sort((a, b) => {
+        if (a.item_type === 'album' && b.item_type === 'album') {
+          if (a.year && b.year) {
+            if (a.year !== b.year) {
+              return b.year - a.year
+            }
+          }
+        }
+
+        return a.name.localeCompare(b.name)
+      })
     },
   },
 
@@ -77,12 +91,29 @@ export default {
 
 .index {
   .item {
+    position: relative;
+
     h2 {
       font-size: 1.25em;
       font-weight: bold;
       overflow: auto;
       text-overflow: ellipsis;
     }
+
+    .float {
+      position: absolute;
+      background: rgba(0, 0, 0, 0.5);
+      color: white;
+      z-index: 1;
+      padding: 0.25em;
+      font-size: 0.9em;
+      border-radius: 0.5em;
+
+      &.bottom-right {
+        right: 0;
+        bottom: 0;
+      }
+    }
   }
 
   &.is-root {
diff --git a/platypush/backend/http/webapp/src/components/panels/Media/Providers/Jellyfin/views/Media/Index.vue b/platypush/backend/http/webapp/src/components/panels/Media/Providers/Jellyfin/views/Media/Index.vue
index 0bdf26d5e..b96a1e173 100644
--- a/platypush/backend/http/webapp/src/components/panels/Media/Providers/Jellyfin/views/Media/Index.vue
+++ b/platypush/backend/http/webapp/src/components/panels/Media/Providers/Jellyfin/views/Media/Index.vue
@@ -2,12 +2,23 @@
   <div class="videos index">
     <Loading v-if="isLoading" />
 
+    <div class="wrapper music-wrapper" v-else-if="collection?.collection_type === 'music'">
+      <Music :collection="collection"
+             :filter="filter"
+             :loading="isLoading"
+             :path="path"
+             @play="$emit('play', $event)"
+             @play-with-opts="$emit('play-with-opts', $event)"
+             @select="selectedResult = $event; $emit('select', $event)"
+             @select-collection="selectCollection" />
+    </div>
+
     <NoItems :with-shadow="false"
              v-else-if="!items?.length">
       No videos found.
     </NoItems>
 
-    <div class="items-wrapper" v-else>
+    <div class="wrapper items-wrapper" v-else>
       <Collections :collection="collection"
                    :filter="filter"
                    :items="collections"
@@ -28,10 +39,6 @@
                @select="selectedResult = $event"
                v-if="mediaItems.length > 0" />
     </div>
-
-    <SortButton :value="sort"
-                @input="sort = $event"
-                v-if="items.length > 0" />
   </div>
 </template>
 
@@ -39,9 +46,9 @@
 import Collections from "@/components/panels/Media/Providers/Jellyfin/Collections";
 import Loading from "@/components/Loading";
 import Mixin from "@/components/panels/Media/Providers/Jellyfin/Mixin";
+import Music from "../Music/Index";
 import NoItems from "@/components/elements/NoItems";
 import Results from "@/components/panels/Media/Results";
-import SortButton from "@/components/panels/Media/Providers/Jellyfin/components/SortButton";
 
 export default {
   mixins: [Mixin],
@@ -49,9 +56,9 @@ export default {
   components: {
     Collections,
     Loading,
+    Music,
     NoItems,
     Results,
-    SortButton,
   },
 
   computed: {
@@ -92,18 +99,34 @@ export default {
     },
 
     async refresh() {
+      // Don't fetch items if we're in the music view -
+      // we'll fetch them in the Music component
+      if (this.collection?.collection_type === 'music')
+        return
+
       this.loading_ = true
       try {
-        this.items = this.collection?.id ?
-          (
-            await this.request('media.jellyfin.get_items', {
+        if (this.collection?.collection_type === 'tvshows') {
+          this.items = (
+            await this.request('media.jellyfin.get_collections', {
               parent_id: this.collection.id,
-              limit: 5000,
             })
-          ) : (await this.request('media.jellyfin.get_collections')).map((collection) => ({
+          ).map((collection) => ({
             ...collection,
             item_type: 'collection',
           }))
+        } else {
+          this.items = this.collection?.id ?
+            (
+              await this.request('media.jellyfin.get_items', {
+                parent_id: this.collection.id,
+                limit: 5000,
+              })
+            ) : (await this.request('media.jellyfin.get_collections')).map((collection) => ({
+              ...collection,
+              item_type: 'collection',
+            }))
+        }
       } finally {
         this.loading_ = false
       }
@@ -136,5 +159,9 @@ export default {
       overflow: hidden;
     }
   }
+
+  .music-wrapper {
+    height: 100%;
+  }
 }
 </style>
diff --git a/platypush/backend/http/webapp/src/components/panels/Media/Providers/Jellyfin/views/Music/Index.vue b/platypush/backend/http/webapp/src/components/panels/Media/Providers/Jellyfin/views/Music/Index.vue
new file mode 100644
index 000000000..2ccca1822
--- /dev/null
+++ b/platypush/backend/http/webapp/src/components/panels/Media/Providers/Jellyfin/views/Music/Index.vue
@@ -0,0 +1,411 @@
+<template>
+  <div class="music index">
+    <Loading v-if="isLoading" />
+
+    <NoItems :with-shadow="false" v-else-if="!items?.length">
+      No music found.
+    </NoItems>
+
+    <main :class="{ album: view === 'album', artist: view === 'artist' }" v-else>
+      <div class="artist header" v-if="view === 'artist'">
+        <div class="image" v-if="collection.image">
+          <img :src="collection.image" />
+        </div>
+
+        <div class="info">
+          <h1 v-text="collection.name" />
+        </div>
+      </div>
+
+      <div class="album header" v-if="view === 'album'">
+        <div class="image" v-if="collection.image">
+          <img :src="collection.image" />
+        </div>
+
+        <div class="info">
+          <h1 v-text="collection.name" />
+          <div class="artist" v-if="displayedArtist?.id">
+            <a href="#" v-text="displayedArtist.name"
+                        @click.prevent.stop="selectArtist"
+                        v-if="displayedArtist" />
+            <span v-text="displayedArtist.name" v-else />
+          </div>
+          <div class="details">
+            <div class="row" v-if="collection.year">
+              <span class="label">Year:</span>
+              <span class="value" v-text="collection.year" />
+            </div>
+
+            <div class="row" v-if="collection.duration">
+              <span class="label">Duration:</span>
+              <span class="value" v-text="formatDuration(collection.duration, true)" />
+            </div>
+          </div>
+        </div>
+      </div>
+
+      <Collections :collection="collection"
+                   :filter="filter"
+                   :items="collections"
+                   :loading="isLoading"
+                   :parent-id="collection?.id"
+                   @select="selectCollection"
+                   v-if="collections?.length > 0" />
+
+      <Results :results="mediaItems"
+               :sources="{'jellyfin': true}"
+               :filter="filter"
+               :list-view="true"
+               :selected-result="selectedResult"
+               :show-date="false"
+               @add-to-playlist="$emit('add-to-playlist', $event)"
+               @download="$emit('download', $event)"
+               @play="$emit('play', $event)"
+               @play-with-opts="$emit('play-with-opts', $event)"
+               @remove-from-playlist="$emit('remove-from-playlist', $event)"
+               @select="selectedResult = $event"
+               v-if="mediaItems?.length > 0" />
+    </main>
+  </div>
+</template>
+
+<script>
+import Collections from "@/components/panels/Media/Providers/Jellyfin/Collections";
+import Loading from "@/components/Loading";
+import Mixin from "@/components/panels/Media/Providers/Jellyfin/Mixin";
+import NoItems from "@/components/elements/NoItems";
+import Results from "@/components/panels/Media/Results";
+
+export default {
+  mixins: [Mixin],
+  emits: ['select', 'select-collection'],
+  components: {
+    Collections,
+    Loading,
+    NoItems,
+    Results,
+  },
+
+  data() {
+    return {
+      artist: null,
+    }
+  },
+
+  computed: {
+    collections() {
+      return (
+        this.sortedItems?.filter((item) => ['collection', 'artist', 'album'].includes(item.item_type)) ?? []
+      ).sort((a, b) => a.name.localeCompare(b.name))
+    },
+
+    displayedArtist() {
+      return this.artist || this.collection?.artist
+    },
+
+    mediaItems() {
+      return (
+        this.sortedItems?.filter((item) => !['collection', 'artist', 'album'].includes(item.item_type)) ?? []
+      ).sort((a, b) => {
+        if (this.view === 'album') {
+          if (a.track_number && b.track_number) {
+            if (a.track_number !== b.track_number) {
+              return a.track_number - b.track_number
+            }
+          }
+        }
+
+        return a.name.localeCompare(b.name)
+      }).map((item) => {
+        if (this.view === 'album') {
+          item.artist = this.artist || this.collection.artist
+          item.album = this.collection
+          item.image = this.collection.image
+        }
+
+        return item
+      })
+    },
+
+    view() {
+      switch (this.collection?.item_type) {
+        case 'artist':
+          return 'artist'
+        case 'album':
+          return 'album'
+        default:
+          return 'index'
+      }
+    },
+  },
+
+  methods: {
+    async selectArtist() {
+      const artistId = this.displayedArtist?.id || this.getUrlArgs().artist
+      if (!artistId?.length)
+        return
+
+      this.loading_ = true
+      try {
+        const artist = this.displayedArtist || (await this.request('media.jellyfin.info', { item_id: artistId }))
+        if (artist) {
+          this.selectCollection(artist)
+          this.$nextTick(() => {
+            this.setUrlArgs({ artist: artist.id, collection: artist.id })
+          })
+        }
+      } finally {
+        this.loading_ = false
+      }
+    },
+
+    selectCollection(collection) {
+      if (!collection || collection?.id === this.collection?.id)
+        return
+
+      if (collection.item_type === 'artist') {
+        this.setUrlArgs({ artist: collection.id })
+      } else if (collection.item_type === 'album') {
+        this.setUrlArgs({ collection: collection.id })
+      } else {
+        this.setUrlArgs({ collection: null })
+      }
+
+      this.$emit('select-collection', {
+        collection_type: 'music',
+        ...collection,
+      })
+    },
+
+    async init() {
+      const args = this.getUrlArgs()
+      let collection = args?.collection
+      if (!collection)
+        return
+
+      this.loading_ = true
+      try {
+        collection = await this.request('media.jellyfin.info', {
+          item_id: collection,
+        })
+
+        if (collection)
+          this.selectCollection(collection)
+      } finally {
+        this.loading_ = false
+      }
+    },
+
+    async refresh() {
+      this.loading_ = true
+      try {
+        switch (this.view) {
+          case 'artist':
+            this.artist = {...this.collection}
+            this.setUrlArgs({
+              artist: this.collection.id,
+              collection: this.collection.id,
+            })
+
+            this.items = (
+              await this.request(
+                'media.jellyfin.get_items',
+                {
+                  parent_id: this.collection.id,
+                  limit: 5000,
+                }
+              )
+            ).map((item) => {
+              if (this.collection?.item_type === 'album') {
+                item.image = this.collection.image
+              }
+
+              return item
+            })
+            break
+
+          case 'album':
+            this.setUrlArgs({
+              collection: this.collection.id,
+              artist: this.collection.artist?.id,
+            })
+
+            this.items = await this.request(
+              'media.jellyfin.get_items',
+              {
+                parent_id: this.collection.id,
+                limit: 5000,
+              }
+            )
+            break
+
+          default:
+            this.artist = null
+            this.items = await this.request(
+              'media.jellyfin.get_artists',
+              { limit: 5000 }
+            )
+            break
+        }
+      } finally {
+        this.loading_ = false
+      }
+    },
+  },
+
+  async mounted() {
+    await this.init()
+    await this.refresh()
+  },
+
+  unmounted() {
+    this.setUrlArgs({
+      collection: null,
+      artist: null,
+      album: null,
+    })
+  },
+}
+</script>
+
+<style lang="scss" scoped>
+@import "@/components/panels/Media/Providers/Jellyfin/common.scss";
+
+$artist-header-height: 5em;
+$album-header-height: 10em;
+
+.index {
+  position: relative;
+
+  :deep(main) {
+    height: 100%;
+    position: relative;
+    overflow: auto;
+
+    &.artist, &.album {
+      overflow: hidden;
+
+      .media-results {
+        overflow: auto;
+
+        .grid {
+          height: fit-content;
+          max-height: 100%;
+        }
+      }
+    }
+
+    &.artist {
+      .index {
+        height: 100%;
+
+        .items {
+          height: calc(100% - #{$artist-header-height} - 0.5em);
+          overflow: auto;
+        }
+      }
+    }
+
+    &.album {
+      .media-results {
+        height: calc(100% - #{$album-header-height} - 0.5em);
+      }
+    }
+
+    .index {
+      height: fit-content;
+    }
+
+    .items {
+      overflow: hidden;
+    }
+  }
+
+  .header {
+    background: $info-header-bg;
+    display: flex;
+    align-items: center;
+    padding: 1em;
+    box-shadow: $border-shadow-bottom;
+    margin-bottom: 0.5em;
+    position: relative;
+
+    &.artist {
+      height: $artist-header-height;
+      padding: 0;
+      background: linear-gradient(rgba(0, 20, 25, 0.85), rgba(0, 0, 0, 0.85));
+      color: white;
+
+      .image {
+        width: $artist-header-height;
+        height: $artist-header-height;
+
+        img {
+          width: 100%;
+          height: 95%;
+        }
+      }
+
+      .info {
+        font-size: 1.25em;
+      }
+    }
+
+    &.album {
+      height: $album-header-height;
+
+      .image {
+        img {
+          width: $album-header-height;
+          height: $album-header-height;
+        }
+      }
+    }
+
+    .image {
+      margin-right: 1em;
+
+      img {
+        object-fit: cover;
+        margin: 0.25em;
+      }
+    }
+
+    .info {
+      h1 {
+        font-size: 1.5em;
+        margin: 0;
+      }
+
+      span {
+        font-size: 1.25em;
+      }
+
+      .artist {
+        a {
+          font-size: 1.25em;
+        }
+      }
+
+      .details {
+        font-size: 0.7em;
+        margin-top: 0.5em;
+        opacity: 0.75;
+
+        .row {
+          .label {
+            font-weight: bold;
+            margin-right: 0.5em;
+          }
+        }
+      }
+    }
+  }
+
+  :deep(.media-results.list ) {
+    .grid {
+      margin-top: -0.5em;
+    }
+  }
+}
+</style>
diff --git a/platypush/backend/http/webapp/src/components/panels/Media/Results.vue b/platypush/backend/http/webapp/src/components/panels/Media/Results.vue
index 32036754a..19e9a38c3 100644
--- a/platypush/backend/http/webapp/src/components/panels/Media/Results.vue
+++ b/platypush/backend/http/webapp/src/components/panels/Media/Results.vue
@@ -1,13 +1,15 @@
 <template>
-  <div class="media-results">
+  <div class="media-results" :class="{'list': listView}">
     <Loading v-if="loading" />
     <div class="grid" ref="grid" v-if="results?.length" @scroll="onScroll">
       <Item v-for="(item, i) in visibleResults"
             :key="i"
             :hidden="!!Object.keys(sources || {}).length && !sources[item.type]"
             :item="item"
+            :list-view="listView"
             :playlist="playlist"
             :selected="selectedResult === i"
+            :show-date="showDate"
             @add-to-playlist="$emit('add-to-playlist', item)"
             @open-channel="$emit('open-channel', item)"
             @remove-from-playlist="$emit('remove-from-playlist', item)"
@@ -56,11 +58,25 @@ export default {
   ],
 
   props: {
+    filter: {
+      type: String,
+      default: null,
+    },
+
+    listView: {
+      type: Boolean,
+      default: false,
+    },
+
     loading: {
       type: Boolean,
       default: false,
     },
 
+    playlist: {
+      default: null,
+    },
+
     pluginName: {
       type: String,
     },
@@ -70,27 +86,23 @@ export default {
       default: () => [],
     },
 
-    selectedResult: {
-      type: Number,
-    },
-
-    sources: {
-      type: Object,
-      default: () => {},
-    },
-
-    filter: {
-      type: String,
-      default: null,
-    },
-
     resultIndexStep: {
       type: Number,
       default: 25,
     },
 
-    playlist: {
-      default: null,
+    selectedResult: {
+      type: Number,
+    },
+
+    showDate: {
+      type: Boolean,
+      default: true,
+    },
+
+    sources: {
+      type: Object,
+      default: () => {},
     },
   },
 
@@ -107,7 +119,7 @@ export default {
           if (!this.filter?.length)
             return true
 
-          return item.title.toLowerCase().includes(this.filter.toLowerCase())
+          return (item.title || item.name).toLowerCase().includes(this.filter.toLowerCase())
         })
 
       if (this.maxResultIndex != null)
@@ -134,18 +146,18 @@ export default {
     },
   },
 
-  mounted() {
-    this.$watch('selectedResult', (value) => {
+  watch: {
+    selectedResult(value) {
       if (value?.item_type === 'playlist' || value?.item_type === 'channel') {
         this.$emit('select', null)
         return
       }
 
-      if (value == null)
+      if (this.selectedResult == null)
         this.$refs.infoModal?.close()
       else
         this.$refs.infoModal?.show()
-    })
+    },
   },
 }
 </script>
@@ -169,5 +181,18 @@ export default {
     width: 100%;
     cursor: initial;
   }
+
+  &.list {
+    :deep(.grid) {
+      display: flex;
+      flex-direction: column;
+      padding: 0;
+      row-gap: 0;
+
+      .title {
+        font-weight: normal;
+      }
+    }
+  }
 }
 </style>