diff --git a/client/components/app/StreamContainer.vue b/client/components/app/StreamContainer.vue
index baecbde4..cd2bd1cf 100644
--- a/client/components/app/StreamContainer.vue
+++ b/client/components/app/StreamContainer.vue
@@ -120,17 +120,22 @@ export default {
streamLibraryItem() {
return this.$store.state.streamLibraryItem
},
+ streamEpisode() {
+ if (!this.$store.state.streamEpisodeId) return null
+ const episodes = this.streamLibraryItem.media.episodes || []
+ return episodes.find((ep) => ep.id === this.$store.state.streamEpisodeId)
+ },
libraryItemId() {
- return this.streamLibraryItem ? this.streamLibraryItem.id : null
+ return this.streamLibraryItem?.id || null
},
media() {
- return this.streamLibraryItem ? this.streamLibraryItem.media || {} : {}
+ return this.streamLibraryItem?.media || {}
},
isPodcast() {
- return this.streamLibraryItem ? this.streamLibraryItem.mediaType === 'podcast' : false
+ return this.streamLibraryItem?.mediaType === 'podcast'
},
isMusic() {
- return this.streamLibraryItem ? this.streamLibraryItem.mediaType === 'music' : false
+ return this.streamLibraryItem?.mediaType === 'music'
},
isExplicit() {
return this.mediaMetadata.explicit || false
@@ -139,6 +144,7 @@ export default {
return this.media.metadata || {}
},
chapters() {
+ if (this.streamEpisode) return this.streamEpisode.chapters || []
return this.media.chapters || []
},
title() {
diff --git a/client/components/tables/podcast/EpisodeTableRow.vue b/client/components/tables/podcast/EpisodeTableRow.vue
index 3012faf4..a5b85e6e 100644
--- a/client/components/tables/podcast/EpisodeTableRow.vue
+++ b/client/components/tables/podcast/EpisodeTableRow.vue
@@ -12,6 +12,7 @@
Season #{{ episode.season }}
Episode #{{ episode.episode }}
+
{{ episode.chapters.length }} Chapters
Published {{ $formatDate(publishedAt, dateFormat) }}
diff --git a/client/players/PlayerHandler.js b/client/players/PlayerHandler.js
index 1e591069..2fb188e8 100644
--- a/client/players/PlayerHandler.js
+++ b/client/players/PlayerHandler.js
@@ -123,7 +123,7 @@ export default class PlayerHandler {
playerError() {
// Switch to HLS stream on error
- if (!this.isCasting && !this.currentStreamId && (this.player instanceof LocalAudioPlayer)) {
+ if (!this.isCasting && (this.player instanceof LocalAudioPlayer)) {
console.log(`[PlayerHandler] Audio player error switching to HLS stream`)
this.prepare(true)
}
@@ -183,6 +183,8 @@ export default class PlayerHandler {
}
async prepare(forceTranscode = false) {
+ this.currentSessionId = null // Reset session
+
const payload = {
deviceInfo: {
deviceId: this.getDeviceId()
@@ -260,6 +262,7 @@ export default class PlayerHandler {
this.player = null
this.playerState = 'IDLE'
this.libraryItem = null
+ this.currentSessionId = null
this.startTime = 0
this.stopPlayInterval()
}
diff --git a/server/managers/PodcastManager.js b/server/managers/PodcastManager.js
index e60238cc..d50bc7bb 100644
--- a/server/managers/PodcastManager.js
+++ b/server/managers/PodcastManager.js
@@ -53,15 +53,15 @@ class PodcastManager {
}
async downloadPodcastEpisodes(libraryItem, episodesToDownload, isAutoDownload) {
- var index = libraryItem.media.episodes.length + 1
- episodesToDownload.forEach((ep) => {
- var newPe = new PodcastEpisode()
+ let index = libraryItem.media.episodes.length + 1
+ for (const ep of episodesToDownload) {
+ const newPe = new PodcastEpisode()
newPe.setData(ep, index++)
newPe.libraryItemId = libraryItem.id
- var newPeDl = new PodcastEpisodeDownload()
+ const newPeDl = new PodcastEpisodeDownload()
newPeDl.setData(newPe, libraryItem, isAutoDownload, libraryItem.libraryId)
this.startPodcastEpisodeDownload(newPeDl)
- })
+ }
}
async startPodcastEpisodeDownload(podcastEpisodeDownload) {
@@ -94,7 +94,6 @@ class PodcastManager {
await filePerms.setDefault(this.currentDownload.libraryItem.path)
}
-
let success = false
if (this.currentDownload.urlFileExtension === 'mp3') {
// Download episode and tag it
@@ -156,6 +155,11 @@ class PodcastManager {
const podcastEpisode = this.currentDownload.podcastEpisode
podcastEpisode.audioFile = audioFile
+
+ if (audioFile.chapters?.length) {
+ podcastEpisode.chapters = audioFile.chapters.map(ch => ({ ...ch }))
+ }
+
libraryItem.media.addPodcastEpisode(podcastEpisode)
if (libraryItem.isInvalid) {
// First episode added to an empty podcast
@@ -214,13 +218,13 @@ class PodcastManager {
}
async probeAudioFile(libraryFile) {
- var path = libraryFile.metadata.path
- var mediaProbeData = await prober.probe(path)
+ const path = libraryFile.metadata.path
+ const mediaProbeData = await prober.probe(path)
if (mediaProbeData.error) {
Logger.error(`[PodcastManager] Podcast Episode downloaded but failed to probe "${path}"`, mediaProbeData.error)
return false
}
- var newAudioFile = new AudioFile()
+ const newAudioFile = new AudioFile()
newAudioFile.setDataFromProbe(libraryFile, mediaProbeData)
return newAudioFile
}
diff --git a/server/objects/entities/PodcastEpisode.js b/server/objects/entities/PodcastEpisode.js
index 80e66611..394a0bea 100644
--- a/server/objects/entities/PodcastEpisode.js
+++ b/server/objects/entities/PodcastEpisode.js
@@ -1,6 +1,6 @@
const Path = require('path')
const Logger = require('../../Logger')
-const { getId, cleanStringForSearch } = require('../../utils/index')
+const { getId, cleanStringForSearch, areEquivalent, copyValue } = require('../../utils/index')
const AudioFile = require('../files/AudioFile')
const AudioTrack = require('../files/AudioTrack')
@@ -18,6 +18,7 @@ class PodcastEpisode {
this.description = null
this.enclosure = null
this.pubDate = null
+ this.chapters = []
this.audioFile = null
this.publishedAt = null
@@ -41,6 +42,7 @@ class PodcastEpisode {
this.description = episode.description
this.enclosure = episode.enclosure ? { ...episode.enclosure } : null
this.pubDate = episode.pubDate
+ this.chapters = episode.chapters?.map(ch => ({ ...ch })) || []
this.audioFile = new AudioFile(episode.audioFile)
this.publishedAt = episode.publishedAt
this.addedAt = episode.addedAt
@@ -62,6 +64,7 @@ class PodcastEpisode {
description: this.description,
enclosure: this.enclosure ? { ...this.enclosure } : null,
pubDate: this.pubDate,
+ chapters: this.chapters.map(ch => ({ ...ch })),
audioFile: this.audioFile.toJSON(),
publishedAt: this.publishedAt,
addedAt: this.addedAt,
@@ -82,6 +85,7 @@ class PodcastEpisode {
description: this.description,
enclosure: this.enclosure ? { ...this.enclosure } : null,
pubDate: this.pubDate,
+ chapters: this.chapters.map(ch => ({ ...ch })),
audioFile: this.audioFile.toJSON(),
audioTrack: this.audioTrack.toJSON(),
publishedAt: this.publishedAt,
@@ -136,6 +140,7 @@ class PodcastEpisode {
this.setDataFromAudioMetaTags(audioFile.metaTags, true)
+ this.chapters = audioFile.chapters?.map((c) => ({ ...c }))
this.addedAt = Date.now()
this.updatedAt = Date.now()
}
@@ -143,8 +148,8 @@ class PodcastEpisode {
update(payload) {
let hasUpdates = false
for (const key in this.toJSON()) {
- if (payload[key] != undefined && payload[key] != this[key]) {
- this[key] = payload[key]
+ if (payload[key] != undefined && !areEquivalent(payload[key], this[key])) {
+ this[key] = copyValue(payload[key])
hasUpdates = true
}
}
diff --git a/server/utils/podcastUtils.js b/server/utils/podcastUtils.js
index d59a3ec4..553ad7d1 100644
--- a/server/utils/podcastUtils.js
+++ b/server/utils/podcastUtils.js
@@ -74,12 +74,12 @@ function extractPodcastMetadata(channel) {
function extractEpisodeData(item) {
// Episode must have url
- if (!item.enclosure || !item.enclosure.length || !item.enclosure[0]['$'] || !item.enclosure[0]['$'].url) {
+ if (!item.enclosure?.[0]?.['$']?.url) {
Logger.error(`[podcastUtils] Invalid podcast episode data`)
return null
}
- var episode = {
+ const episode = {
enclosure: {
...item.enclosure[0]['$']
}
@@ -91,6 +91,12 @@ function extractEpisodeData(item) {
episode.description = htmlSanitizer.sanitize(rawDescription)
}
+ // Extract chapters
+ if (item['podcast:chapters']?.[0]?.['$']?.url) {
+ episode.chaptersUrl = item['podcast:chapters'][0]['$'].url
+ episode.chaptersType = item['podcast:chapters'][0]['$'].type || 'application/json'
+ }
+
// Supposed to be the plaintext description but not always followed
if (item['description']) {
const rawDescription = extractFirstArrayItem(item, 'description') || ''
@@ -133,14 +139,16 @@ function cleanEpisodeData(data) {
duration: data.duration || '',
explicit: data.explicit || '',
publishedAt,
- enclosure: data.enclosure
+ enclosure: data.enclosure,
+ chaptersUrl: data.chaptersUrl || null,
+ chaptersType: data.chaptersType || null
}
}
function extractPodcastEpisodes(items) {
- var episodes = []
+ const episodes = []
items.forEach((item) => {
- var extracted = extractEpisodeData(item)
+ const extracted = extractEpisodeData(item)
if (extracted) {
episodes.push(cleanEpisodeData(extracted))
}