Merge pull request #3785 from advplyr/playback-session-use-new-library-item

Update PlaybackSession to use new library item model
This commit is contained in:
advplyr 2025-01-03 11:20:33 -06:00 committed by GitHub
commit de7296eaab
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 284 additions and 193 deletions

View File

@ -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)
}

View File

@ -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)

View File

@ -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<PlaybackSession>}
@ -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})`)

View File

@ -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)
}
}
}

View File

@ -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<FeedEpisode[]>}
*/
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

View File

@ -497,6 +497,57 @@ class LibraryItem extends Model {
return libraryItem
}
/**
*
* @param {import('sequelize').WhereOptions} where
* @param {import('sequelize').IncludeOptions} [include]
* @returns {Promise<LibraryItemExpanded>}
*/
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

View File

@ -76,42 +76,26 @@ class MediaItemShare extends Model {
}
/**
* Expanded book that includes library settings
*
* @param {string} mediaItemId
* @param {string} mediaItemType
* @returns {Promise<import('../objects/LibraryItem')>}
* @returns {Promise<import('./LibraryItem').LibraryItemExpanded>}
*/
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: [
const libraryItem = await libraryItemModel.findOneExpanded(
{ mediaId: mediaItemId },
{
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
)
return libraryItem
}
return null
}

View File

@ -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
*/

View File

@ -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
}

View File

@ -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

View File

@ -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

View File

@ -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,

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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()