-          
Player Queue
+          
{{ $strings.HeaderPlayerQueue }}
           {{ playerQueueItems.length }} Items
           
           
diff --git a/client/components/player/PlayerUi.vue b/client/components/player/PlayerUi.vue
index 2f5775efc..1f7232941 100644
--- a/client/components/player/PlayerUi.vue
+++ b/client/components/player/PlayerUi.vue
@@ -22,15 +22,15 @@
           
format_list_bulleted
         
 
+        
+
         
           
             timelapse
           
         
-
-        
        
 
       
diff --git a/client/components/tables/ChaptersTable.vue b/client/components/tables/ChaptersTable.vue
index e9c7e39d6..d7e616edf 100644
--- a/client/components/tables/ChaptersTable.vue
+++ b/client/components/tables/ChaptersTable.vue
@@ -72,11 +72,23 @@ export default {
       this.expanded = !this.expanded
     },
     goToTimestamp(time) {
+      const queueItem = {
+        libraryItemId: this.libraryItemId,
+        libraryId: this.libraryItem.libraryId,
+        episodeId: null,
+        title: this.metadata.title,
+        subtitle: this.metadata.authors.map((au) => au.name).join(', '),
+        caption: '',
+        duration: this.media.duration || null,
+        coverPath: this.media.coverPath || null
+      }
+
       if (this.$store.getters['getIsMediaStreaming'](this.libraryItemId)) {
         this.$eventBus.$emit('play-item', {
           libraryItemId: this.libraryItemId,
           episodeId: null,
-          startTime: time
+          startTime: time,
+          queueItems: [queueItem]
         })
       } else {
         const payload = {
@@ -86,7 +98,8 @@ export default {
               this.$eventBus.$emit('play-item', {
                 libraryItemId: this.libraryItemId,
                 episodeId: null,
-                startTime: time
+                startTime: time,
+                queueItems: [queueItem]
               })
             }
           },
diff --git a/client/components/tables/collection/BookTableRow.vue b/client/components/tables/collection/BookTableRow.vue
index 0177b3319..b56a7e424 100644
--- a/client/components/tables/collection/BookTableRow.vue
+++ b/client/components/tables/collection/BookTableRow.vue
@@ -137,8 +137,22 @@ export default {
       this.isHovering = false
     },
     playClick() {
+      const queueItems = [
+        {
+          libraryItemId: this.book.id,
+          libraryId: this.book.libraryId,
+          episodeId: null,
+          title: this.bookTitle,
+          subtitle: this.bookAuthors.map((au) => au.name).join(', '),
+          caption: '',
+          duration: this.media.duration || null,
+          coverPath: this.media.coverPath || null
+        }
+      ]
+
       this.$eventBus.$emit('play-item', {
-        libraryItemId: this.book.id
+        libraryItemId: this.book.id,
+        queueItems
       })
     },
     clickEdit() {
diff --git a/client/pages/collection/_id.vue b/client/pages/collection/_id.vue
index 976583d5d..e402cf503 100644
--- a/client/pages/collection/_id.vue
+++ b/client/pages/collection/_id.vue
@@ -122,13 +122,42 @@ export default {
       }
     },
     clickPlay() {
-      var nextBookNotRead = this.playableBooks.find((pb) => {
-        var prog = this.$store.getters['user/getUserMediaProgress'](pb.id)
-        return !prog || !prog.isFinished
+      const queueItems = []
+
+      // Collection queue will start at the first unfinished book
+      //   if all books are finished then entire collection is queued
+      const itemsWithProgress = this.playableBooks.map((item) => {
+        return {
+          ...item,
+          progress: this.$store.getters['user/getUserMediaProgress'](item.id)
+        }
       })
-      if (nextBookNotRead) {
+
+      const hasUnfinishedItems = itemsWithProgress.some((i) => !i.progress || !i.progress.isFinished)
+      if (!hasUnfinishedItems) {
+        console.warn('All items in collection are finished - starting at first item')
+      }
+
+      for (let i = 0; i < itemsWithProgress.length; i++) {
+        const libraryItem = itemsWithProgress[i]
+        if (!hasUnfinishedItems || !libraryItem.progress || !libraryItem.progress.isFinished) {
+          queueItems.push({
+            libraryItemId: libraryItem.id,
+            libraryId: libraryItem.libraryId,
+            episodeId: null,
+            title: libraryItem.media.metadata.title,
+            subtitle: libraryItem.media.metadata.authors.map((au) => au.name).join(', '),
+            caption: '',
+            duration: libraryItem.media.duration || null,
+            coverPath: libraryItem.media.coverPath || null
+          })
+        }
+      }
+
+      if (queueItems.length >= 0) {
         this.$eventBus.$emit('play-item', {
-          libraryItemId: nextBookNotRead.id
+          libraryItemId: queueItems[0].libraryItemId,
+          queueItems
         })
       }
     }
diff --git a/client/pages/config/sessions.vue b/client/pages/config/sessions.vue
index 3799714bd..0ca72da7f 100644
--- a/client/pages/config/sessions.vue
+++ b/client/pages/config/sessions.vue
@@ -127,12 +127,38 @@ export default {
         this.processingGoToTimestamp = false
         return
       }
-      if (session.episodeId && !libraryItem.media.episodes.find((ep) => ep.id === session.episodeId)) {
+      if (session.episodeId && !libraryItem.media.episodes.some((ep) => ep.id === session.episodeId)) {
         this.$toast.error('Failed to get podcast episode')
         this.processingGoToTimestamp = false
         return
       }
 
+      var queueItem = {}
+      if (session.episodeId) {
+        var episode = libraryItem.media.episodes.find((ep) => ep.id === session.episodeId)
+        queueItem = {
+          libraryItemId: libraryItem.id,
+          libraryId: libraryItem.libraryId,
+          episodeId: episode.id,
+          title: episode.title,
+          subtitle: libraryItem.media.metadata.title,
+          caption: episode.publishedAt ? `Published ${this.$formatDate(episode.publishedAt, 'MMM do, yyyy')}` : 'Unknown publish date',
+          duration: episode.audioFile.duration || null,
+          coverPath: libraryItem.media.coverPath || null
+        }
+      } else {
+        queueItem = {
+          libraryItemId: libraryItem.id,
+          libraryId: libraryItem.libraryId,
+          episodeId: null,
+          title: libraryItem.media.metadata.title,
+          subtitle: libraryItem.media.metadata.authors.map((au) => au.name).join(', '),
+          caption: '',
+          duration: libraryItem.media.duration || null,
+          coverPath: libraryItem.media.coverPath || null
+        }
+      }
+
       const payload = {
         message: this.$getString('MessageStartPlaybackAtTime', [session.displayTitle, this.$secondsToTimestamp(session.currentTime)]),
         callback: (confirmed) => {
@@ -140,7 +166,8 @@ export default {
             this.$eventBus.$emit('play-item', {
               libraryItemId: libraryItem.id,
               episodeId: session.episodeId || null,
-              startTime: session.currentTime
+              startTime: session.currentTime,
+              queueItems: [queueItem]
             })
           }
           this.processingGoToTimestamp = false
diff --git a/client/pages/config/users/_id/sessions.vue b/client/pages/config/users/_id/sessions.vue
index 63544d54b..2d2dd4e92 100644
--- a/client/pages/config/users/_id/sessions.vue
+++ b/client/pages/config/users/_id/sessions.vue
@@ -114,12 +114,38 @@ export default {
         this.processingGoToTimestamp = false
         return
       }
-      if (session.episodeId && !libraryItem.media.episodes.find((ep) => ep.id === session.episodeId)) {
+      if (session.episodeId && !libraryItem.media.episodes.some((ep) => ep.id === session.episodeId)) {
         this.$toast.error('Failed to get podcast episode')
         this.processingGoToTimestamp = false
         return
       }
 
+      var queueItem = {}
+      if (session.episodeId) {
+        var episode = libraryItem.media.episodes.find((ep) => ep.id === session.episodeId)
+        queueItem = {
+          libraryItemId: libraryItem.id,
+          libraryId: libraryItem.libraryId,
+          episodeId: episode.id,
+          title: episode.title,
+          subtitle: libraryItem.media.metadata.title,
+          caption: episode.publishedAt ? `Published ${this.$formatDate(episode.publishedAt, 'MMM do, yyyy')}` : 'Unknown publish date',
+          duration: episode.audioFile.duration || null,
+          coverPath: libraryItem.media.coverPath || null
+        }
+      } else {
+        queueItem = {
+          libraryItemId: libraryItem.id,
+          libraryId: libraryItem.libraryId,
+          episodeId: null,
+          title: libraryItem.media.metadata.title,
+          subtitle: libraryItem.media.metadata.authors.map((au) => au.name).join(', '),
+          caption: '',
+          duration: libraryItem.media.duration || null,
+          coverPath: libraryItem.media.coverPath || null
+        }
+      }
+
       const payload = {
         message: this.$getString('MessageStartPlaybackAtTime', [session.displayTitle, this.$secondsToTimestamp(session.currentTime)]),
         callback: (confirmed) => {
@@ -127,7 +153,8 @@ export default {
             this.$eventBus.$emit('play-item', {
               libraryItemId: libraryItem.id,
               episodeId: session.episodeId || null,
-              startTime: session.currentTime
+              startTime: session.currentTime,
+              queueItems: [queueItem]
             })
           }
           this.processingGoToTimestamp = false
diff --git a/client/pages/item/_id/index.vue b/client/pages/item/_id/index.vue
index dbd0856a2..f00707442 100644
--- a/client/pages/item/_id/index.vue
+++ b/client/pages/item/_id/index.vue
@@ -137,12 +137,16 @@
               {{ isMissing ? $strings.LabelMissing : $strings.LabelIncomplete }}
             
 
+            
+              
+            
+
             
               auto_stories
               {{ $strings.ButtonRead }}
             
 
-            
+            
               
             
 
@@ -398,6 +402,9 @@ export default {
     isStreaming() {
       return this.streamLibraryItem && this.streamLibraryItem.id === this.libraryItemId
     },
+    isQueued() {
+      return this.$store.getters['getIsMediaQueued'](this.libraryItemId)
+    },
     userCanUpdate() {
       return this.$store.getters['user/getUserCanUpdate']
     },
@@ -412,6 +419,10 @@ export default {
 
       // If rss feed is open then show feed url to users otherwise just show to admins
       return this.userIsAdminOrUp || this.rssFeedUrl
+    },
+    showQueueBtn() {
+      if (this.isPodcast || this.isVideo) return false
+      return !this.$store.getters['getIsStreamingFromDifferentLibrary'] && this.streamLibraryItem
     }
   },
   methods: {
@@ -536,6 +547,7 @@ export default {
           if (!podcastProgress || !podcastProgress.isFinished) {
             queueItems.push({
               libraryItemId: this.libraryItemId,
+              libraryId: this.libraryId,
               episodeId: episode.id,
               title: episode.title,
               subtitle: this.title,
@@ -545,6 +557,18 @@ export default {
             })
           }
         }
+      } else {
+        const queueItem = {
+          libraryItemId: this.libraryItemId,
+          libraryId: this.libraryId,
+          episodeId: null,
+          title: this.title,
+          subtitle: this.authors.map((au) => au.name).join(', '),
+          caption: '',
+          duration: this.duration || null,
+          coverPath: this.media.coverPath || null
+        }
+        queueItems.push(queueItem)
       }
 
       this.$eventBus.$emit('play-item', {
@@ -615,6 +639,26 @@ export default {
         console.log('RSS Feed Closed', data)
         this.rssFeedUrl = null
       }
+    },
+    queueBtnClick() {
+      if (this.isQueued) {
+        // Remove from queue
+        this.$store.commit('removeItemFromQueue', { libraryItemId: this.libraryItemId })
+      } else {
+        // Add to queue
+
+        const queueItem = {
+          libraryItemId: this.libraryItemId,
+          libraryId: this.libraryId,
+          episodeId: null,
+          title: this.title,
+          subtitle: this.authors.map((au) => au.name).join(', '),
+          caption: '',
+          duration: this.duration || null,
+          coverPath: this.media.coverPath || null
+        }
+        this.$store.commit('addItemToQueue', queueItem)
+      }
     }
   },
   mounted() {
diff --git a/client/strings/en-us.json b/client/strings/en-us.json
index 9753c3405..07ac91606 100644
--- a/client/strings/en-us.json
+++ b/client/strings/en-us.json
@@ -107,6 +107,7 @@
   "HeaderOtherFiles": "Other Files",
   "HeaderOpenRSSFeed": "Open RSS Feed",
   "HeaderPermissions": "Permissions",
+  "HeaderPlayerQueue": "Player Queue",
   "HeaderPodcastsToAdd": "Podcasts to Add",
   "HeaderPreviewCover": "Preview Cover",
   "HeaderRemoveEpisode": "Remove Episode",