From c251f1899d536f762412760047c0b0a50790bdcb Mon Sep 17 00:00:00 2001 From: advplyr Date: Fri, 3 Jan 2025 11:16:03 -0600 Subject: [PATCH] Update PlaybackSession to use new library item model --- server/controllers/SessionController.js | 2 +- server/controllers/ShareController.js | 13 ++-- server/managers/PlaybackSessionManager.js | 10 +-- server/models/Book.js | 74 +++++++++++++++++++---- server/models/FeedEpisode.js | 24 ++++---- server/models/LibraryItem.js | 67 ++++++++++++++++++++ server/models/MediaItemShare.js | 46 +++++--------- server/models/Podcast.js | 72 ++++++++++++++++++++++ server/models/PodcastEpisode.js | 25 +++++--- server/objects/LibraryItem.js | 4 -- server/objects/PlaybackSession.js | 41 +++++-------- server/objects/Stream.js | 29 ++++----- server/objects/entities/PodcastEpisode.js | 10 --- server/objects/mediaTypes/Book.js | 22 ------- server/objects/mediaTypes/Podcast.js | 33 ---------- server/objects/metadata/BookMetadata.js | 5 -- 16 files changed, 284 insertions(+), 193 deletions(-) diff --git a/server/controllers/SessionController.js b/server/controllers/SessionController.js index cc6c0fd7..c3361ce9 100644 --- a/server/controllers/SessionController.js +++ b/server/controllers/SessionController.js @@ -149,7 +149,7 @@ class SessionController { * @param {Response} res */ async getOpenSession(req, res) { - const libraryItem = await Database.libraryItemModel.getOldById(req.playbackSession.libraryItemId) + const libraryItem = await Database.libraryItemModel.getExpandedById(req.playbackSession.libraryItemId) const sessionForClient = req.playbackSession.toJSONForClient(libraryItem) res.json(sessionForClient) } diff --git a/server/controllers/ShareController.js b/server/controllers/ShareController.js index 93c6e9fb..3e7ea1de 100644 --- a/server/controllers/ShareController.js +++ b/server/controllers/ShareController.js @@ -70,14 +70,13 @@ class ShareController { } try { - const oldLibraryItem = await Database.mediaItemShareModel.getMediaItemsOldLibraryItem(mediaItemShare.mediaItemId, mediaItemShare.mediaItemType) - - if (!oldLibraryItem) { + const libraryItem = await Database.mediaItemShareModel.getMediaItemsLibraryItem(mediaItemShare.mediaItemId, mediaItemShare.mediaItemType) + if (!libraryItem) { return res.status(404).send('Media item not found') } let startOffset = 0 - const publicTracks = oldLibraryItem.media.includedAudioFiles.map((audioFile) => { + const publicTracks = libraryItem.media.includedAudioFiles.map((audioFile) => { const audioTrack = { index: audioFile.index, startOffset, @@ -86,7 +85,7 @@ class ShareController { contentUrl: `${global.RouterBasePath}/public/share/${slug}/track/${audioFile.index}`, mimeType: audioFile.mimeType, codec: audioFile.codec || null, - metadata: audioFile.metadata.clone() + metadata: structuredClone(audioFile.metadata) } startOffset += audioTrack.duration return audioTrack @@ -105,12 +104,12 @@ class ShareController { const deviceInfo = await this.playbackSessionManager.getDeviceInfo(req, clientDeviceInfo) const newPlaybackSession = new PlaybackSession() - newPlaybackSession.setData(oldLibraryItem, null, 'web-share', deviceInfo, startTime) + newPlaybackSession.setData(libraryItem, null, 'web-share', deviceInfo, startTime) newPlaybackSession.audioTracks = publicTracks newPlaybackSession.playMethod = PlayMethod.DIRECTPLAY newPlaybackSession.shareSessionId = shareSessionId newPlaybackSession.mediaItemShareId = mediaItemShare.id - newPlaybackSession.coverAspectRatio = oldLibraryItem.librarySettings.coverAspectRatio + newPlaybackSession.coverAspectRatio = libraryItem.library.settings.coverAspectRatio mediaItemShare.playbackSession = newPlaybackSession.toJSONForClient() ShareManager.addOpenSharePlaybackSession(newPlaybackSession) diff --git a/server/managers/PlaybackSessionManager.js b/server/managers/PlaybackSessionManager.js index aace3df7..97c87bbe 100644 --- a/server/managers/PlaybackSessionManager.js +++ b/server/managers/PlaybackSessionManager.js @@ -74,7 +74,7 @@ class PlaybackSessionManager { async startSessionRequest(req, res, episodeId) { const deviceInfo = await this.getDeviceInfo(req, req.body?.deviceInfo) Logger.debug(`[PlaybackSessionManager] startSessionRequest for device ${deviceInfo.deviceDescription}`) - const { oldLibraryItem: libraryItem, body: options } = req + const { libraryItem, body: options } = req const session = await this.startSession(req.user, deviceInfo, libraryItem, episodeId, options) res.json(session.toJSONForClient(libraryItem)) } @@ -279,7 +279,7 @@ class PlaybackSessionManager { * * @param {import('../models/User')} user * @param {DeviceInfo} deviceInfo - * @param {import('../objects/LibraryItem')} libraryItem + * @param {import('../models/LibraryItem')} libraryItem * @param {string|null} episodeId * @param {{forceDirectPlay?:boolean, forceTranscode?:boolean, mediaPlayer:string, supportedMimeTypes?:string[]}} options * @returns {Promise} @@ -292,7 +292,7 @@ class PlaybackSessionManager { await this.closeSession(user, session, null) } - const shouldDirectPlay = options.forceDirectPlay || (!options.forceTranscode && libraryItem.media.checkCanDirectPlay(options, episodeId)) + const shouldDirectPlay = options.forceDirectPlay || (!options.forceTranscode && libraryItem.media.checkCanDirectPlay(options.supportedMimeTypes, episodeId)) const mediaPlayer = options.mediaPlayer || 'unknown' const mediaItemId = episodeId || libraryItem.media.id @@ -300,7 +300,7 @@ class PlaybackSessionManager { let userStartTime = 0 if (userProgress) { if (userProgress.isFinished) { - Logger.info(`[PlaybackSessionManager] Starting session for user "${user.username}" and resetting progress for finished item "${libraryItem.media.metadata.title}"`) + Logger.info(`[PlaybackSessionManager] Starting session for user "${user.username}" and resetting progress for finished item "${libraryItem.media.title}"`) // Keep userStartTime as 0 so the client restarts the media } else { userStartTime = Number.parseFloat(userProgress.currentTime) || 0 @@ -312,7 +312,7 @@ class PlaybackSessionManager { let audioTracks = [] if (shouldDirectPlay) { Logger.debug(`[PlaybackSessionManager] "${user.username}" starting direct play session for item "${libraryItem.id}" with id ${newPlaybackSession.id} (Device: ${newPlaybackSession.deviceDescription})`) - audioTracks = libraryItem.getDirectPlayTracklist(episodeId) + audioTracks = libraryItem.getTrackList(episodeId) newPlaybackSession.playMethod = PlayMethod.DIRECTPLAY } else { Logger.debug(`[PlaybackSessionManager] "${user.username}" starting stream session for item "${libraryItem.id}" (Device: ${newPlaybackSession.deviceDescription})`) diff --git a/server/models/Book.js b/server/models/Book.js index 756a9dea..4c2006a1 100644 --- a/server/models/Book.js +++ b/server/models/Book.js @@ -62,6 +62,13 @@ const parseNameString = require('../utils/parsers/parseNameString') * @property {ChapterObject[]} chapters * @property {Object} metaTags * @property {string} mimeType + * + * @typedef AudioTrackProperties + * @property {string} title + * @property {string} contentUrl + * @property {number} startOffset + * + * @typedef {AudioFileObject & AudioTrackProperties} AudioTrack */ class Book extends Model { @@ -367,16 +374,6 @@ class Book extends Model { return this.audioFiles.filter((af) => !af.exclude) } - get trackList() { - let startOffset = 0 - return this.includedAudioFiles.map((af) => { - const track = structuredClone(af) - track.startOffset = startOffset - startOffset += track.duration - return track - }) - } - get hasMediaFiles() { return !!this.hasAudioTracks || !!this.ebookFile } @@ -385,6 +382,59 @@ class Book extends Model { return !!this.includedAudioFiles.length } + /** + * Supported mime types are sent from the web client and are retrieved using the browser Audio player "canPlayType" function. + * + * @param {string[]} supportedMimeTypes + * @returns {boolean} + */ + checkCanDirectPlay(supportedMimeTypes) { + if (!Array.isArray(supportedMimeTypes)) { + Logger.error(`[Book] checkCanDirectPlay: supportedMimeTypes is not an array`, supportedMimeTypes) + return false + } + return this.includedAudioFiles.every((af) => supportedMimeTypes.includes(af.mimeType)) + } + + /** + * Get the track list to be used in client audio players + * AudioTrack is the AudioFile with startOffset, contentUrl and title + * + * @param {string} libraryItemId + * @returns {AudioTrack[]} + */ + getTracklist(libraryItemId) { + let startOffset = 0 + return this.includedAudioFiles.map((af) => { + const track = structuredClone(af) + track.title = af.metadata.filename + track.startOffset = startOffset + track.contentUrl = `${global.RouterBasePath}/api/items/${libraryItemId}/file/${track.ino}` + startOffset += track.duration + return track + }) + } + + /** + * + * @returns {ChapterObject[]} + */ + getChapters() { + return structuredClone(this.chapters) || [] + } + + getPlaybackTitle() { + return this.title + } + + getPlaybackAuthor() { + return this.authorName + } + + getPlaybackDuration() { + return this.duration + } + /** * Total file size of all audio files and ebook file * @@ -635,7 +685,7 @@ class Book extends Model { metadata: this.oldMetadataToJSONMinified(), coverPath: this.coverPath, tags: [...(this.tags || [])], - numTracks: this.trackList.length, + numTracks: this.includedAudioFiles.length, numAudioFiles: this.audioFiles?.length || 0, numChapters: this.chapters?.length || 0, duration: this.duration, @@ -666,7 +716,7 @@ class Book extends Model { ebookFile: structuredClone(this.ebookFile), duration: this.duration, size: this.size, - tracks: structuredClone(this.trackList) + tracks: this.getTracklist(libraryItemId) } } } diff --git a/server/models/FeedEpisode.js b/server/models/FeedEpisode.js index 5825dd4e..0767577a 100644 --- a/server/models/FeedEpisode.js +++ b/server/models/FeedEpisode.js @@ -112,15 +112,15 @@ class FeedEpisode extends Model { /** * If chapters for an audiobook match the audio tracks then use chapter titles instead of audio file names * + * @param {import('./Book').AudioTrack[]} trackList * @param {import('./Book')} book * @returns {boolean} */ - static checkUseChapterTitlesForEpisodes(book) { - const tracks = book.trackList || [] + static checkUseChapterTitlesForEpisodes(trackList, book) { const chapters = book.chapters || [] - if (tracks.length !== chapters.length) return false - for (let i = 0; i < tracks.length; i++) { - if (Math.abs(chapters[i].start - tracks[i].startOffset) >= 1) { + if (trackList.length !== chapters.length) return false + for (let i = 0; i < trackList.length; i++) { + if (Math.abs(chapters[i].start - trackList[i].startOffset) >= 1) { return false } } @@ -148,7 +148,7 @@ class FeedEpisode extends Model { const contentUrl = `/feed/${slug}/item/${episodeId}/media${Path.extname(audioTrack.metadata.filename)}` let title = Path.basename(audioTrack.metadata.filename, Path.extname(audioTrack.metadata.filename)) - if (book.trackList.length == 1) { + if (book.includedAudioFiles.length == 1) { // If audiobook is a single file, use book title instead of chapter/file title title = book.title } else { @@ -185,11 +185,12 @@ class FeedEpisode extends Model { * @returns {Promise} */ static async createFromAudiobookTracks(libraryItemExpanded, feed, slug, transaction) { - const useChapterTitles = this.checkUseChapterTitlesForEpisodes(libraryItemExpanded.media) + const trackList = libraryItemExpanded.getTrackList() + const useChapterTitles = this.checkUseChapterTitlesForEpisodes(trackList, libraryItemExpanded.media) const feedEpisodeObjs = [] let numExisting = 0 - for (const track of libraryItemExpanded.media.trackList) { + for (const track of trackList) { // Check for existing episode by filepath const existingEpisode = feed.feedEpisodes?.find((episode) => { return episode.filePath === track.metadata.path @@ -204,7 +205,7 @@ class FeedEpisode extends Model { /** * - * @param {import('./Book')[]} books + * @param {import('./Book').BookExpandedWithLibraryItem[]} books * @param {import('./Feed')} feed * @param {string} slug * @param {import('sequelize').Transaction} transaction @@ -218,8 +219,9 @@ class FeedEpisode extends Model { const feedEpisodeObjs = [] let numExisting = 0 for (const book of books) { - const useChapterTitles = this.checkUseChapterTitlesForEpisodes(book) - for (const track of book.trackList) { + const trackList = book.libraryItem.getTrackList() + const useChapterTitles = this.checkUseChapterTitlesForEpisodes(trackList, book) + for (const track of trackList) { // Check for existing episode by filepath const existingEpisode = feed.feedEpisodes?.find((episode) => { return episode.filePath === track.metadata.path diff --git a/server/models/LibraryItem.js b/server/models/LibraryItem.js index 03e67a9e..3381b94a 100644 --- a/server/models/LibraryItem.js +++ b/server/models/LibraryItem.js @@ -497,6 +497,57 @@ class LibraryItem extends Model { return libraryItem } + /** + * + * @param {import('sequelize').WhereOptions} where + * @param {import('sequelize').IncludeOptions} [include] + * @returns {Promise} + */ + static async findOneExpanded(where, include = null) { + const libraryItem = await this.findOne({ + where, + include + }) + if (!libraryItem) { + Logger.error(`[LibraryItem] Library item not found`) + return null + } + + if (libraryItem.mediaType === 'podcast') { + libraryItem.media = await libraryItem.getMedia({ + include: [ + { + model: this.sequelize.models.podcastEpisode + } + ] + }) + } else { + libraryItem.media = await libraryItem.getMedia({ + include: [ + { + model: this.sequelize.models.author, + through: { + attributes: [] + } + }, + { + model: this.sequelize.models.series, + through: { + attributes: ['id', 'sequence'] + } + } + ], + order: [ + [this.sequelize.models.author, this.sequelize.models.bookAuthor, 'createdAt', 'ASC'], + [this.sequelize.models.series, 'bookSeries', 'createdAt', 'ASC'] + ] + }) + } + + if (!libraryItem.media) return null + return libraryItem + } + /** * Get old library item by id * @param {string} libraryItemId @@ -1176,6 +1227,22 @@ class LibraryItem extends Model { } } + /** + * Get the track list to be used in client audio players + * AudioTrack is the AudioFile with startOffset and contentUrl + * Podcasts must have an episodeId to get the track list + * + * @param {string} [episodeId] + * @returns {import('./Book').AudioTrack[]} + */ + getTrackList(episodeId) { + if (!this.media) { + Logger.error(`[LibraryItem] getTrackList: Library item "${this.id}" does not have media`) + return [] + } + return this.media.getTracklist(this.id, episodeId) + } + /** * * @param {string} ino diff --git a/server/models/MediaItemShare.js b/server/models/MediaItemShare.js index 2d7b3896..2d5be8f6 100644 --- a/server/models/MediaItemShare.js +++ b/server/models/MediaItemShare.js @@ -76,42 +76,26 @@ class MediaItemShare extends Model { } /** + * Expanded book that includes library settings * * @param {string} mediaItemId * @param {string} mediaItemType - * @returns {Promise} + * @returns {Promise} */ - static async getMediaItemsOldLibraryItem(mediaItemId, mediaItemType) { + static async getMediaItemsLibraryItem(mediaItemId, mediaItemType) { + /** @type {typeof import('./LibraryItem')} */ + const libraryItemModel = this.sequelize.models.libraryItem + if (mediaItemType === 'book') { - const book = await this.sequelize.models.book.findByPk(mediaItemId, { - include: [ - { - model: this.sequelize.models.author, - through: { - attributes: [] - } - }, - { - model: this.sequelize.models.series, - through: { - attributes: ['sequence'] - } - }, - { - model: this.sequelize.models.libraryItem, - include: { - model: this.sequelize.models.library, - attributes: ['settings'] - } - } - ] - }) - const libraryItem = book.libraryItem - libraryItem.media = book - delete book.libraryItem - const oldLibraryItem = this.sequelize.models.libraryItem.getOldLibraryItem(libraryItem) - oldLibraryItem.librarySettings = libraryItem.library.settings - return oldLibraryItem + const libraryItem = await libraryItemModel.findOneExpanded( + { mediaId: mediaItemId }, + { + model: this.sequelize.models.library, + attributes: ['settings'] + } + ) + + return libraryItem } return null } diff --git a/server/models/Podcast.js b/server/models/Podcast.js index 172e36a2..188c1070 100644 --- a/server/models/Podcast.js +++ b/server/models/Podcast.js @@ -276,6 +276,78 @@ class Podcast extends Model { return hasUpdates } + checkCanDirectPlay(supportedMimeTypes, episodeId) { + if (!Array.isArray(supportedMimeTypes)) { + Logger.error(`[Podcast] checkCanDirectPlay: supportedMimeTypes is not an array`, supportedMimeTypes) + return false + } + const episode = this.podcastEpisodes.find((ep) => ep.id === episodeId) + if (!episode) { + Logger.error(`[Podcast] checkCanDirectPlay: episode not found`, episodeId) + return false + } + return supportedMimeTypes.includes(episode.audioFile.mimeType) + } + + /** + * Get the track list to be used in client audio players + * AudioTrack is the AudioFile with startOffset and contentUrl + * Podcast episodes only have one track + * + * @param {string} libraryItemId + * @param {string} episodeId + * @returns {import('./Book').AudioTrack[]} + */ + getTracklist(libraryItemId, episodeId) { + const episode = this.podcastEpisodes.find((ep) => ep.id === episodeId) + if (!episode) { + Logger.error(`[Podcast] getTracklist: episode not found`, episodeId) + return [] + } + + const audioTrack = episode.getAudioTrack(libraryItemId) + return [audioTrack] + } + + /** + * + * @param {string} episodeId + * @returns {import('./PodcastEpisode').ChapterObject[]} + */ + getChapters(episodeId) { + const episode = this.podcastEpisodes.find((ep) => ep.id === episodeId) + if (!episode) { + Logger.error(`[Podcast] getChapters: episode not found`, episodeId) + return [] + } + + return structuredClone(episode.chapters) || [] + } + + getPlaybackTitle(episodeId) { + const episode = this.podcastEpisodes.find((ep) => ep.id === episodeId) + if (!episode) { + Logger.error(`[Podcast] getPlaybackTitle: episode not found`, episodeId) + return '' + } + + return episode.title + } + + getPlaybackAuthor() { + return this.author + } + + getPlaybackDuration(episodeId) { + const episode = this.podcastEpisodes.find((ep) => ep.id === episodeId) + if (!episode) { + Logger.error(`[Podcast] getPlaybackDuration: episode not found`, episodeId) + return 0 + } + + return episode.duration + } + /** * Old model kept metadata in a separate object */ diff --git a/server/models/PodcastEpisode.js b/server/models/PodcastEpisode.js index 23d237e0..24d07041 100644 --- a/server/models/PodcastEpisode.js +++ b/server/models/PodcastEpisode.js @@ -135,23 +135,28 @@ class PodcastEpisode extends Model { PodcastEpisode.belongsTo(podcast) } + get size() { + return this.audioFile?.metadata.size || 0 + } + + get duration() { + return this.audioFile?.duration || 0 + } + /** - * AudioTrack object used in old model + * Used in client players * - * @returns {import('./Book').AudioFileObject|null} + * @param {string} libraryItemId + * @returns {import('./Book').AudioTrack} */ - get track() { - if (!this.audioFile) return null + getAudioTrack(libraryItemId) { const track = structuredClone(this.audioFile) track.startOffset = 0 track.title = this.audioFile.metadata.title + track.contentUrl = `${global.RouterBasePath}/api/items/${libraryItemId}/file/${track.ino}` return track } - get size() { - return this.audioFile?.metadata.size || 0 - } - /** * @param {string} libraryItemId * @returns {oldPodcastEpisode} @@ -228,9 +233,9 @@ class PodcastEpisode extends Model { toOldJSONExpanded(libraryItemId) { const json = this.toOldJSON(libraryItemId) - json.audioTrack = this.track + json.audioTrack = this.getAudioTrack(libraryItemId) json.size = this.size - json.duration = this.audioFile?.duration || 0 + json.duration = this.duration return json } diff --git a/server/objects/LibraryItem.js b/server/objects/LibraryItem.js index 6578bae0..b1cdf43b 100644 --- a/server/objects/LibraryItem.js +++ b/server/objects/LibraryItem.js @@ -249,10 +249,6 @@ class LibraryItem { this.updatedAt = Date.now() } - getDirectPlayTracklist(episodeId) { - return this.media.getDirectPlayTracklist(episodeId) - } - /** * Save metadata.json file * TODO: Move to new LibraryItem model diff --git a/server/objects/PlaybackSession.js b/server/objects/PlaybackSession.js index 6950a544..ba031b66 100644 --- a/server/objects/PlaybackSession.js +++ b/server/objects/PlaybackSession.js @@ -1,8 +1,6 @@ const date = require('../libs/dateAndTime') const uuidv4 = require('uuid').v4 const serverVersion = require('../../package.json').version -const BookMetadata = require('./metadata/BookMetadata') -const PodcastMetadata = require('./metadata/PodcastMetadata') const DeviceInfo = require('./DeviceInfo') class PlaybackSession { @@ -60,7 +58,7 @@ class PlaybackSession { bookId: this.bookId, episodeId: this.episodeId, mediaType: this.mediaType, - mediaMetadata: this.mediaMetadata?.toJSON() || null, + mediaMetadata: structuredClone(this.mediaMetadata), chapters: (this.chapters || []).map((c) => ({ ...c })), displayTitle: this.displayTitle, displayAuthor: this.displayAuthor, @@ -82,7 +80,7 @@ class PlaybackSession { /** * Session data to send to clients - * @param {Object} [libraryItem] - old library item + * @param {import('../models/LibraryItem')} [libraryItem] * @returns */ toJSONForClient(libraryItem) { @@ -94,7 +92,7 @@ class PlaybackSession { bookId: this.bookId, episodeId: this.episodeId, mediaType: this.mediaType, - mediaMetadata: this.mediaMetadata?.toJSON() || null, + mediaMetadata: structuredClone(this.mediaMetadata), chapters: (this.chapters || []).map((c) => ({ ...c })), displayTitle: this.displayTitle, displayAuthor: this.displayAuthor, @@ -112,7 +110,7 @@ class PlaybackSession { startedAt: this.startedAt, updatedAt: this.updatedAt, audioTracks: this.audioTracks.map((at) => at.toJSON?.() || { ...at }), - libraryItem: libraryItem?.toJSONExpanded() || null + libraryItem: libraryItem?.toOldJSONExpanded() || null } } @@ -148,14 +146,7 @@ class PlaybackSession { this.serverVersion = session.serverVersion this.chapters = session.chapters || [] - this.mediaMetadata = null - if (session.mediaMetadata) { - if (this.mediaType === 'book') { - this.mediaMetadata = new BookMetadata(session.mediaMetadata) - } else if (this.mediaType === 'podcast') { - this.mediaMetadata = new PodcastMetadata(session.mediaMetadata) - } - } + this.mediaMetadata = session.mediaMetadata this.displayTitle = session.displayTitle || '' this.displayAuthor = session.displayAuthor || '' this.coverPath = session.coverPath @@ -205,6 +196,15 @@ class PlaybackSession { } } + /** + * + * @param {import('../models/LibraryItem')} libraryItem + * @param {*} userId + * @param {*} mediaPlayer + * @param {*} deviceInfo + * @param {*} startTime + * @param {*} episodeId + */ setData(libraryItem, userId, mediaPlayer, deviceInfo, startTime, episodeId = null) { this.id = uuidv4() this.userId = userId @@ -213,13 +213,12 @@ class PlaybackSession { this.bookId = episodeId ? null : libraryItem.media.id this.episodeId = episodeId this.mediaType = libraryItem.mediaType - this.mediaMetadata = libraryItem.media.metadata.clone() + this.mediaMetadata = libraryItem.media.oldMetadataToJSON() this.chapters = libraryItem.media.getChapters(episodeId) this.displayTitle = libraryItem.media.getPlaybackTitle(episodeId) this.displayAuthor = libraryItem.media.getPlaybackAuthor() this.coverPath = libraryItem.media.coverPath - - this.setDuration(libraryItem, episodeId) + this.duration = libraryItem.media.getPlaybackDuration(episodeId) this.mediaPlayer = mediaPlayer this.deviceInfo = deviceInfo || new DeviceInfo() @@ -235,14 +234,6 @@ class PlaybackSession { this.updatedAt = Date.now() } - setDuration(libraryItem, episodeId) { - if (episodeId) { - this.duration = libraryItem.media.getEpisodeDuration(episodeId) - } else { - this.duration = libraryItem.media.duration - } - } - addListeningTime(timeListened) { if (!timeListened || isNaN(timeListened)) return diff --git a/server/objects/Stream.js b/server/objects/Stream.js index 2ab6f503..5f4feeef 100644 --- a/server/objects/Stream.js +++ b/server/objects/Stream.js @@ -18,6 +18,7 @@ class Stream extends EventEmitter { this.id = sessionId this.user = user + /** @type {import('../models/LibraryItem')} */ this.libraryItem = libraryItem this.episodeId = episodeId @@ -40,31 +41,25 @@ class Stream extends EventEmitter { this.furthestSegmentCreated = 0 } - get isPodcast() { - return this.libraryItem.mediaType === 'podcast' - } + /** + * @returns {import('../models/PodcastEpisode') | null} + */ get episode() { - if (!this.isPodcast) return null - return this.libraryItem.media.episodes.find((ep) => ep.id === this.episodeId) - } - get libraryItemId() { - return this.libraryItem.id + if (!this.libraryItem.isPodcast) return null + return this.libraryItem.media.podcastEpisodes.find((ep) => ep.id === this.episodeId) } get mediaTitle() { - if (this.episode) return this.episode.title || '' - return this.libraryItem.media.metadata.title || '' + return this.libraryItem.media.getPlaybackTitle(this.episodeId) } get totalDuration() { - if (this.episode) return this.episode.duration - return this.libraryItem.media.duration + return this.libraryItem.media.getPlaybackDuration(this.episodeId) } get tracks() { - if (this.episode) return this.episode.tracks - return this.libraryItem.media.tracks + return this.libraryItem.getTrackList(this.episodeId) } get tracksAudioFileType() { if (!this.tracks.length) return null - return this.tracks[0].metadata.format + return this.tracks[0].metadata.ext.slice(1) } get tracksMimeType() { if (!this.tracks.length) return null @@ -116,8 +111,8 @@ class Stream extends EventEmitter { return { id: this.id, userId: this.user.id, - libraryItem: this.libraryItem.toJSONExpanded(), - episode: this.episode ? this.episode.toJSONExpanded() : null, + libraryItem: this.libraryItem.toOldJSONExpanded(), + episode: this.episode ? this.episode.toOldJSONExpanded(this.libraryItem.id) : null, segmentLength: this.segmentLength, playlistPath: this.playlistPath, clientPlaylistUri: this.clientPlaylistUri, diff --git a/server/objects/entities/PodcastEpisode.js b/server/objects/entities/PodcastEpisode.js index 69a9b2f0..945e0e56 100644 --- a/server/objects/entities/PodcastEpisode.js +++ b/server/objects/entities/PodcastEpisode.js @@ -168,16 +168,6 @@ class PodcastEpisode { return hasUpdates } - // Only checks container format - checkCanDirectPlay(payload) { - const supportedMimeTypes = payload.supportedMimeTypes || [] - return supportedMimeTypes.includes(this.audioFile.mimeType) - } - - getDirectPlayTracklist() { - return this.tracks - } - checkEqualsEnclosureUrl(url) { if (!this.enclosure?.url) return false return this.enclosure.url == url diff --git a/server/objects/mediaTypes/Book.js b/server/objects/mediaTypes/Book.js index 5d455018..488c3aac 100644 --- a/server/objects/mediaTypes/Book.js +++ b/server/objects/mediaTypes/Book.js @@ -150,27 +150,5 @@ class Book { this.coverPath = coverPath return true } - - // Only checks container format - checkCanDirectPlay(payload) { - var supportedMimeTypes = payload.supportedMimeTypes || [] - return !this.tracks.some((t) => !supportedMimeTypes.includes(t.mimeType)) - } - - getDirectPlayTracklist() { - return this.tracks - } - - getPlaybackTitle() { - return this.metadata.title - } - - getPlaybackAuthor() { - return this.metadata.authorName - } - - getChapters() { - return this.chapters?.map((ch) => ({ ...ch })) || [] - } } module.exports = Book diff --git a/server/objects/mediaTypes/Podcast.js b/server/objects/mediaTypes/Podcast.js index d33b28ba..2a009eb2 100644 --- a/server/objects/mediaTypes/Podcast.js +++ b/server/objects/mediaTypes/Podcast.js @@ -199,19 +199,6 @@ class Podcast { return this.episodes.some((ep) => (ep.guid && ep.guid === guid) || ep.checkEqualsEnclosureUrl(url)) } - // Only checks container format - checkCanDirectPlay(payload, episodeId) { - var episode = this.episodes.find((ep) => ep.id === episodeId) - if (!episode) return false - return episode.checkCanDirectPlay(payload) - } - - getDirectPlayTracklist(episodeId) { - var episode = this.episodes.find((ep) => ep.id === episodeId) - if (!episode) return false - return episode.getDirectPlayTracklist() - } - addPodcastEpisode(podcastEpisode) { this.episodes.push(podcastEpisode) } @@ -224,22 +211,6 @@ class Podcast { return episode } - getPlaybackTitle(episodeId) { - var episode = this.episodes.find((ep) => ep.id == episodeId) - if (!episode) return this.metadata.title - return episode.title - } - - getPlaybackAuthor() { - return this.metadata.author - } - - getEpisodeDuration(episodeId) { - var episode = this.episodes.find((ep) => ep.id == episodeId) - if (!episode) return 0 - return episode.duration - } - getEpisode(episodeId) { if (!episodeId) return null @@ -248,9 +219,5 @@ class Podcast { return this.episodes.find((ep) => ep.id == episodeId) } - - getChapters(episodeId) { - return this.getEpisode(episodeId)?.chapters?.map((ch) => ({ ...ch })) || [] - } } module.exports = Podcast diff --git a/server/objects/metadata/BookMetadata.js b/server/objects/metadata/BookMetadata.js index c6192f11..0dfe1dbf 100644 --- a/server/objects/metadata/BookMetadata.js +++ b/server/objects/metadata/BookMetadata.js @@ -159,11 +159,6 @@ class BookMetadata { getSeries(seriesId) { return this.series.find((se) => se.id == seriesId) } - getSeriesSequence(seriesId) { - const series = this.series.find((se) => se.id == seriesId) - if (!series) return null - return series.sequence || '' - } update(payload) { const json = this.toJSON()