mirror of
				https://github.com/advplyr/audiobookshelf.git
				synced 2025-10-27 11:18:14 +01:00 
			
		
		
		
	Podcast episode player fixes, episode table ui updates
This commit is contained in:
		
							parent
							
								
									0e665e2091
								
							
						
					
					
						commit
						12027b9a76
					
				@ -187,3 +187,15 @@ Bookshelf Label
 | 
				
			|||||||
  opacity: 1;
 | 
					  opacity: 1;
 | 
				
			||||||
  filter: blur(20px);
 | 
					  filter: blur(20px);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.episode-subtitle {
 | 
				
			||||||
 | 
					  word-break: break-word;
 | 
				
			||||||
 | 
					  overflow: hidden;
 | 
				
			||||||
 | 
					  text-overflow: ellipsis;
 | 
				
			||||||
 | 
					  display: -webkit-box;
 | 
				
			||||||
 | 
					  line-height: 16px; /* fallback */
 | 
				
			||||||
 | 
					  max-height: 32px; /* fallback */
 | 
				
			||||||
 | 
					  -webkit-line-clamp: 2; /* number of lines to show */
 | 
				
			||||||
 | 
					  -webkit-box-orient: vertical;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -34,9 +34,6 @@
 | 
				
			|||||||
            <div class="p-1 w-full">
 | 
					            <div class="p-1 w-full">
 | 
				
			||||||
              <ui-text-input-with-label v-model="fullPath" label="Podcast Path" readonly />
 | 
					              <ui-text-input-with-label v-model="fullPath" label="Podcast Path" readonly />
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
            <div class="p-2 w-full">
 | 
					 | 
				
			||||||
              <ui-checkbox v-model="podcast.autoDownloadEpisodes" label="Auto Download Episodes" checkbox-bg="primary" border-color="gray-600" label-class="pl-2 text-base font-semibold" />
 | 
					 | 
				
			||||||
            </div>
 | 
					 | 
				
			||||||
          </div>
 | 
					          </div>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
        <div class="w-full md:w-1/2 p-4">
 | 
					        <div class="w-full md:w-1/2 p-4">
 | 
				
			||||||
@ -59,6 +56,9 @@
 | 
				
			|||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
      <div class="flex items-center py-4">
 | 
					      <div class="flex items-center py-4">
 | 
				
			||||||
        <div class="flex-grow" />
 | 
					        <div class="flex-grow" />
 | 
				
			||||||
 | 
					        <div class="px-4">
 | 
				
			||||||
 | 
					          <ui-checkbox v-model="podcast.autoDownloadEpisodes" label="Auto Download Episodes" checkbox-bg="primary" border-color="gray-600" label-class="pl-2 text-base font-semibold" />
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
        <ui-btn color="success" :disabled="disableSubmit" @click="submit">{{ buttonText }}</ui-btn>
 | 
					        <ui-btn color="success" :disabled="disableSubmit" @click="submit">{{ buttonText }}</ui-btn>
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
@ -262,14 +262,4 @@ export default {
 | 
				
			|||||||
#episodes-scroll {
 | 
					#episodes-scroll {
 | 
				
			||||||
  max-height: calc(80vh - 200px);
 | 
					  max-height: calc(80vh - 200px);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
.episode-subtitle {
 | 
					 | 
				
			||||||
  word-break: break-word;
 | 
					 | 
				
			||||||
  overflow: hidden;
 | 
					 | 
				
			||||||
  text-overflow: ellipsis;
 | 
					 | 
				
			||||||
  display: -webkit-box;
 | 
					 | 
				
			||||||
  line-height: 16px; /* fallback */
 | 
					 | 
				
			||||||
  max-height: 32px; /* fallback */
 | 
					 | 
				
			||||||
  -webkit-line-clamp: 2; /* number of lines to show */
 | 
					 | 
				
			||||||
  -webkit-box-orient: vertical;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
</style>
 | 
					</style>
 | 
				
			||||||
@ -10,13 +10,13 @@
 | 
				
			|||||||
        <p class="text-sm font-semibold">
 | 
					        <p class="text-sm font-semibold">
 | 
				
			||||||
          {{ title }}
 | 
					          {{ title }}
 | 
				
			||||||
        </p>
 | 
					        </p>
 | 
				
			||||||
        <p class="text-sm">
 | 
					        <p class="text-sm text-gray-200 episode-subtitle mt-1.5 mb-0.5">
 | 
				
			||||||
          {{ description }}
 | 
					          {{ description }}
 | 
				
			||||||
        </p>
 | 
					        </p>
 | 
				
			||||||
        <div class="flex items-center pt-2">
 | 
					        <div class="flex items-center pt-2">
 | 
				
			||||||
          <div class="h-8 px-4 border border-white border-opacity-20 hover:bg-white hover:bg-opacity-10 rounded-full flex items-center justify-center cursor-pointer" :class="userIsFinished ? 'text-white text-opacity-40' : ''" @click="playClick">
 | 
					          <div class="h-8 px-4 border border-white border-opacity-20 hover:bg-white hover:bg-opacity-10 rounded-full flex items-center justify-center cursor-pointer" :class="userIsFinished ? 'text-white text-opacity-40' : ''" @click="playClick">
 | 
				
			||||||
            <span class="material-icons">{{ streamIsPlaying ? 'pause' : 'play_arrow' }}</span>
 | 
					            <span class="material-icons" :class="streamIsPlaying ? '' : 'text-success'">{{ streamIsPlaying ? 'pause' : 'play_arrow' }}</span>
 | 
				
			||||||
            <p class="pl-2 pr-1 text-sm">{{ timeRemaining }}</p>
 | 
					            <p class="pl-2 pr-1 text-sm font-semibold">{{ timeRemaining }}</p>
 | 
				
			||||||
          </div>
 | 
					          </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          <ui-tooltip :text="userIsFinished ? 'Mark as Not Finished' : 'Mark as Finished'" direction="top">
 | 
					          <ui-tooltip :text="userIsFinished ? 'Mark as Not Finished' : 'Mark as Finished'" direction="top">
 | 
				
			||||||
@ -37,7 +37,7 @@
 | 
				
			|||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <div v-if="!userIsFinished" class="absolute bottom-0 left-0 h-1 bg-warning" :style="{ width: itemProgressPercent * 100 + '%' }" />
 | 
					    <div v-if="!userIsFinished" class="absolute bottom-0 left-0 h-0.5 bg-warning" :style="{ width: itemProgressPercent * 100 + '%' }" />
 | 
				
			||||||
  </div>
 | 
					  </div>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -75,7 +75,9 @@ export default {
 | 
				
			|||||||
      return this.episode.title || ''
 | 
					      return this.episode.title || ''
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    description() {
 | 
					    description() {
 | 
				
			||||||
      return this.episode.description || ''
 | 
					      if (this.episode.subtitle) return this.episode.subtitle
 | 
				
			||||||
 | 
					      var desc = this.episode.description || ''
 | 
				
			||||||
 | 
					      return desc
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    duration() {
 | 
					    duration() {
 | 
				
			||||||
      return this.$secondsToTimestamp(this.episode.duration)
 | 
					      return this.$secondsToTimestamp(this.episode.duration)
 | 
				
			||||||
 | 
				
			|||||||
@ -1,6 +1,9 @@
 | 
				
			|||||||
<template>
 | 
					<template>
 | 
				
			||||||
  <div class="w-full py-6">
 | 
					  <div class="w-full py-6">
 | 
				
			||||||
    <p class="text-lg mb-0 font-semibold">Episodes</p>
 | 
					    <p class="text-lg mb-0 font-semibold">Episodes</p>
 | 
				
			||||||
 | 
					    <p v-if="!episodes.length" class="py-4 text-center text-lg">
 | 
				
			||||||
 | 
					      No Episodes
 | 
				
			||||||
 | 
					    </p>
 | 
				
			||||||
    <draggable v-model="episodesCopy" v-bind="dragOptions" class="list-group" handle=".drag-handle" draggable=".item" tag="div" @start="drag = true" @end="drag = false" @update="draggableUpdate">
 | 
					    <draggable v-model="episodesCopy" v-bind="dragOptions" class="list-group" handle=".drag-handle" draggable=".item" tag="div" @start="drag = true" @end="drag = false" @update="draggableUpdate">
 | 
				
			||||||
      <transition-group type="transition" :name="!drag ? 'episode' : null">
 | 
					      <transition-group type="transition" :name="!drag ? 'episode' : null">
 | 
				
			||||||
        <template v-for="episode in episodesCopy">
 | 
					        <template v-for="episode in episodesCopy">
 | 
				
			||||||
 | 
				
			|||||||
@ -40,6 +40,10 @@
 | 
				
			|||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
          </div>
 | 
					          </div>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <div class="flex-grow px-1 pt-6">
 | 
				
			||||||
 | 
					          <ui-checkbox v-model="autoDownloadEpisodes" label="Auto Download New Episodes" checkbox-bg="primary" border-color="gray-600" label-class="pl-2 text-base font-semibold" />
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
    </form>
 | 
					    </form>
 | 
				
			||||||
  </div>
 | 
					  </div>
 | 
				
			||||||
@ -193,6 +197,11 @@ export default {
 | 
				
			|||||||
      if (!this.stringArrayEqual(this.newTags, this.media.tags || [])) {
 | 
					      if (!this.stringArrayEqual(this.newTags, this.media.tags || [])) {
 | 
				
			||||||
        updatePayload.tags = [...this.newTags]
 | 
					        updatePayload.tags = [...this.newTags]
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if (this.media.autoDownloadEpisodes !== this.autoDownloadEpisodes) {
 | 
				
			||||||
 | 
					        updatePayload.autoDownloadEpisodes = !!this.autoDownloadEpisodes
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      return {
 | 
					      return {
 | 
				
			||||||
        updatePayload,
 | 
					        updatePayload,
 | 
				
			||||||
        hasChanges: !!Object.keys(updatePayload).length
 | 
					        hasChanges: !!Object.keys(updatePayload).length
 | 
				
			||||||
 | 
				
			|||||||
@ -110,7 +110,7 @@
 | 
				
			|||||||
          <div class="flex items-center justify-center md:justify-start pt-4">
 | 
					          <div class="flex items-center justify-center md:justify-start pt-4">
 | 
				
			||||||
            <ui-btn v-if="showPlayButton" :disabled="streaming" color="success" :padding-x="4" small class="flex items-center h-9 mr-2" @click="startStream">
 | 
					            <ui-btn v-if="showPlayButton" :disabled="streaming" color="success" :padding-x="4" small class="flex items-center h-9 mr-2" @click="startStream">
 | 
				
			||||||
              <span v-show="!streaming" class="material-icons -ml-2 pr-1 text-white">play_arrow</span>
 | 
					              <span v-show="!streaming" class="material-icons -ml-2 pr-1 text-white">play_arrow</span>
 | 
				
			||||||
              {{ streaming ? 'Streaming' : 'Play' }}
 | 
					              {{ streaming ? 'Playing' : 'Play' }}
 | 
				
			||||||
            </ui-btn>
 | 
					            </ui-btn>
 | 
				
			||||||
            <ui-btn v-else-if="isMissing || isInvalid" color="error" :padding-x="4" small class="flex items-center h-9 mr-2">
 | 
					            <ui-btn v-else-if="isMissing || isInvalid" color="error" :padding-x="4" small class="flex items-center h-9 mr-2">
 | 
				
			||||||
              <span v-show="!streaming" class="material-icons -ml-2 pr-1 text-white">error</span>
 | 
					              <span v-show="!streaming" class="material-icons -ml-2 pr-1 text-white">error</span>
 | 
				
			||||||
 | 
				
			|||||||
@ -62,6 +62,7 @@ class PodcastController {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    // Download and save cover image
 | 
					    // Download and save cover image
 | 
				
			||||||
    if (payload.media.metadata.imageUrl) {
 | 
					    if (payload.media.metadata.imageUrl) {
 | 
				
			||||||
 | 
					      // TODO: Scan cover image to library files
 | 
				
			||||||
      var coverResponse = await this.coverManager.downloadCoverFromUrl(libraryItem, payload.media.metadata.imageUrl)
 | 
					      var coverResponse = await this.coverManager.downloadCoverFromUrl(libraryItem, payload.media.metadata.imageUrl)
 | 
				
			||||||
      if (coverResponse) {
 | 
					      if (coverResponse) {
 | 
				
			||||||
        if (coverResponse.error) {
 | 
					        if (coverResponse.error) {
 | 
				
			||||||
 | 
				
			|||||||
@ -84,9 +84,11 @@ class PodcastManager {
 | 
				
			|||||||
      Logger.error(`[PodcastManager] Podcast Episode finished but library item was not found ${this.currentDownload.libraryItem.id}`)
 | 
					      Logger.error(`[PodcastManager] Podcast Episode finished but library item was not found ${this.currentDownload.libraryItem.id}`)
 | 
				
			||||||
      return false
 | 
					      return false
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    var podcastEpisode = this.currentDownload.podcastEpisode
 | 
					    var podcastEpisode = this.currentDownload.podcastEpisode
 | 
				
			||||||
    podcastEpisode.audioFile = audioFile
 | 
					    podcastEpisode.audioFile = audioFile
 | 
				
			||||||
    libraryItem.media.addPodcastEpisode(podcastEpisode)
 | 
					    libraryItem.media.addPodcastEpisode(podcastEpisode)
 | 
				
			||||||
 | 
					    libraryItem.libraryFiles.push(libraryFile)
 | 
				
			||||||
    libraryItem.updatedAt = Date.now()
 | 
					    libraryItem.updatedAt = Date.now()
 | 
				
			||||||
    await this.db.updateLibraryItem(libraryItem)
 | 
					    await this.db.updateLibraryItem(libraryItem)
 | 
				
			||||||
    this.emitter('item_updated', libraryItem.toJSONExpanded())
 | 
					    this.emitter('item_updated', libraryItem.toJSONExpanded())
 | 
				
			||||||
 | 
				
			|||||||
@ -24,7 +24,7 @@ class PodcastEpisodeDownload {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  get targetRelPath() {
 | 
					  get targetRelPath() {
 | 
				
			||||||
    return Path.join(this.libraryItem.relPath, this.targetFilename)
 | 
					    return this.targetFilename
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  setData(podcastEpisode, libraryItem) {
 | 
					  setData(podcastEpisode, libraryItem) {
 | 
				
			||||||
 | 
				
			|||||||
@ -1,5 +1,5 @@
 | 
				
			|||||||
const Path = require('path')
 | 
					const Path = require('path')
 | 
				
			||||||
 | 
					const { encodeUriPath } = require('../../utils/index')
 | 
				
			||||||
class AudioTrack {
 | 
					class AudioTrack {
 | 
				
			||||||
  constructor() {
 | 
					  constructor() {
 | 
				
			||||||
    this.index = null
 | 
					    this.index = null
 | 
				
			||||||
@ -26,7 +26,7 @@ class AudioTrack {
 | 
				
			|||||||
    this.startOffset = startOffset
 | 
					    this.startOffset = startOffset
 | 
				
			||||||
    this.duration = audioFile.duration
 | 
					    this.duration = audioFile.duration
 | 
				
			||||||
    this.title = audioFile.metadata.filename || ''
 | 
					    this.title = audioFile.metadata.filename || ''
 | 
				
			||||||
    this.contentUrl = Path.join(`/s/item/${itemId}`, audioFile.metadata.relPath)
 | 
					    this.contentUrl = Path.join(`/s/item/${itemId}`, encodeUriPath(audioFile.metadata.relPath))
 | 
				
			||||||
    this.mimeType = audioFile.mimeType
 | 
					    this.mimeType = audioFile.mimeType
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -1,3 +1,4 @@
 | 
				
			|||||||
 | 
					const Logger = require('../../Logger')
 | 
				
			||||||
const PodcastEpisode = require('../entities/PodcastEpisode')
 | 
					const PodcastEpisode = require('../entities/PodcastEpisode')
 | 
				
			||||||
const PodcastMetadata = require('../metadata/PodcastMetadata')
 | 
					const PodcastMetadata = require('../metadata/PodcastMetadata')
 | 
				
			||||||
const { areEquivalent, copyValue } = require('../../utils/index')
 | 
					const { areEquivalent, copyValue } = require('../../utils/index')
 | 
				
			||||||
 | 
				
			|||||||
@ -18,6 +18,7 @@ class StaticRouter {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
      var remainingPath = req.params['0']
 | 
					      var remainingPath = req.params['0']
 | 
				
			||||||
      var fullPath = Path.join(item.path, remainingPath)
 | 
					      var fullPath = Path.join(item.path, remainingPath)
 | 
				
			||||||
 | 
					      console.log('fullpath', fullPath)
 | 
				
			||||||
      res.sendFile(fullPath)
 | 
					      res.sendFile(fullPath)
 | 
				
			||||||
    })
 | 
					    })
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
				
			|||||||
@ -118,3 +118,7 @@ module.exports.copyValue = (val) => {
 | 
				
			|||||||
    return final
 | 
					    return final
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					module.exports.encodeUriPath = (path) => {
 | 
				
			||||||
 | 
					  return path.replace(/\\/g, '/').replace(/%/g, '%25').replace(/#/g, '%23')
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Loading…
	
		Reference in New Issue
	
	Block a user