mirror of
				https://github.com/advplyr/audiobookshelf.git
				synced 2025-10-27 11:18:14 +01:00 
			
		
		
		
	Merge pull request #4263 from advplyr/new_session_track_endpoint
Add new api endpoint for direct playing audio files using session id
This commit is contained in:
		
						commit
						1afb8840db
					
				@ -1,5 +1,5 @@
 | 
				
			|||||||
export default class AudioTrack {
 | 
					export default class AudioTrack {
 | 
				
			||||||
  constructor(track, userToken, routerBasePath) {
 | 
					  constructor(track, sessionId, userToken, routerBasePath) {
 | 
				
			||||||
    this.index = track.index || 0
 | 
					    this.index = track.index || 0
 | 
				
			||||||
    this.startOffset = track.startOffset || 0 // Total time of all previous tracks
 | 
					    this.startOffset = track.startOffset || 0 // Total time of all previous tracks
 | 
				
			||||||
    this.duration = track.duration || 0
 | 
					    this.duration = track.duration || 0
 | 
				
			||||||
@ -8,28 +8,29 @@ export default class AudioTrack {
 | 
				
			|||||||
    this.mimeType = track.mimeType
 | 
					    this.mimeType = track.mimeType
 | 
				
			||||||
    this.metadata = track.metadata || {}
 | 
					    this.metadata = track.metadata || {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    this.userToken = userToken
 | 
					    this.sessionId = sessionId
 | 
				
			||||||
    this.routerBasePath = routerBasePath || ''
 | 
					    this.routerBasePath = routerBasePath || ''
 | 
				
			||||||
 | 
					    if (this.contentUrl?.startsWith('/hls')) {
 | 
				
			||||||
 | 
					      this.sessionTrackUrl = `${this.contentUrl}?token=${userToken}`
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      this.sessionTrackUrl = `/public/session/${sessionId}/track/${this.index}`
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /**
 | 
					  /**
 | 
				
			||||||
   * Used for CastPlayer
 | 
					   * Used for CastPlayer
 | 
				
			||||||
   */
 | 
					   */
 | 
				
			||||||
  get fullContentUrl() {
 | 
					  get fullContentUrl() {
 | 
				
			||||||
    if (!this.contentUrl || this.contentUrl.startsWith('http')) return this.contentUrl
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (process.env.NODE_ENV === 'development') {
 | 
					    if (process.env.NODE_ENV === 'development') {
 | 
				
			||||||
      return `${process.env.serverUrl}${this.contentUrl}?token=${this.userToken}`
 | 
					      return `${process.env.serverUrl}${this.sessionTrackUrl}`
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    return `${window.location.origin}${this.routerBasePath}${this.contentUrl}?token=${this.userToken}`
 | 
					    return `${window.location.origin}${this.routerBasePath}${this.sessionTrackUrl}`
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /**
 | 
					  /**
 | 
				
			||||||
   * Used for LocalPlayer
 | 
					   * Used for LocalPlayer
 | 
				
			||||||
   */
 | 
					   */
 | 
				
			||||||
  get relativeContentUrl() {
 | 
					  get relativeContentUrl() {
 | 
				
			||||||
    if (!this.contentUrl || this.contentUrl.startsWith('http')) return this.contentUrl
 | 
					    return `${this.routerBasePath}${this.sessionTrackUrl}`
 | 
				
			||||||
 | 
					 | 
				
			||||||
    return `${this.routerBasePath}${this.contentUrl}?token=${this.userToken}`
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -226,7 +226,7 @@ export default class PlayerHandler {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    console.log('[PlayerHandler] Preparing Session', session)
 | 
					    console.log('[PlayerHandler] Preparing Session', session)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    var audioTracks = session.audioTracks.map((at) => new AudioTrack(at, this.userToken, this.ctx.$config.routerBasePath))
 | 
					    var audioTracks = session.audioTracks.map((at) => new AudioTrack(at, session.id, this.userToken, this.ctx.$config.routerBasePath))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    this.ctx.playerLoading = true
 | 
					    this.ctx.playerLoading = true
 | 
				
			||||||
    this.isHlsTranscode = true
 | 
					    this.isHlsTranscode = true
 | 
				
			||||||
 | 
				
			|||||||
@ -1,7 +1,9 @@
 | 
				
			|||||||
 | 
					const Path = require('path')
 | 
				
			||||||
const { Request, Response, NextFunction } = require('express')
 | 
					const { Request, Response, NextFunction } = require('express')
 | 
				
			||||||
const Logger = require('../Logger')
 | 
					const Logger = require('../Logger')
 | 
				
			||||||
const Database = require('../Database')
 | 
					const Database = require('../Database')
 | 
				
			||||||
const { toNumber, isUUID } = require('../utils/index')
 | 
					const { toNumber, isUUID } = require('../utils/index')
 | 
				
			||||||
 | 
					const { getAudioMimeTypeFromExtname, encodeUriPath } = require('../utils/fileUtils')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const ShareManager = require('../managers/ShareManager')
 | 
					const ShareManager = require('../managers/ShareManager')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -266,6 +268,51 @@ class SessionController {
 | 
				
			|||||||
    this.playbackSessionManager.syncLocalSessionsRequest(req, res)
 | 
					    this.playbackSessionManager.syncLocalSessionsRequest(req, res)
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * GET: /public/session/:id/track/:index
 | 
				
			||||||
 | 
					   * While a session is open, this endpoint can be used to stream the audio track
 | 
				
			||||||
 | 
					   *
 | 
				
			||||||
 | 
					   * @this {import('../routers/PublicRouter')}
 | 
				
			||||||
 | 
					   *
 | 
				
			||||||
 | 
					   * @param {Request} req
 | 
				
			||||||
 | 
					   * @param {Response} res
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  async getTrack(req, res) {
 | 
				
			||||||
 | 
					    const audioTrackIndex = toNumber(req.params.index, null)
 | 
				
			||||||
 | 
					    if (audioTrackIndex === null) {
 | 
				
			||||||
 | 
					      Logger.error(`[SessionController] Invalid audio track index "${req.params.index}"`)
 | 
				
			||||||
 | 
					      return res.sendStatus(400)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const playbackSession = this.playbackSessionManager.getSession(req.params.id)
 | 
				
			||||||
 | 
					    if (!playbackSession) {
 | 
				
			||||||
 | 
					      Logger.error(`[SessionController] Unable to find playback session with id=${req.params.id}`)
 | 
				
			||||||
 | 
					      return res.sendStatus(404)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const audioTrack = playbackSession.audioTracks.find((t) => t.index === audioTrackIndex)
 | 
				
			||||||
 | 
					    if (!audioTrack) {
 | 
				
			||||||
 | 
					      Logger.error(`[SessionController] Unable to find audio track with index=${audioTrackIndex}`)
 | 
				
			||||||
 | 
					      return res.sendStatus(404)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const user = await Database.userModel.getUserById(playbackSession.userId)
 | 
				
			||||||
 | 
					    Logger.debug(`[SessionController] Serving audio track ${audioTrack.index} for session "${req.params.id}" belonging to user "${user.username}"`)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (global.XAccel) {
 | 
				
			||||||
 | 
					      const encodedURI = encodeUriPath(global.XAccel + audioTrack.metadata.path)
 | 
				
			||||||
 | 
					      Logger.debug(`Use X-Accel to serve static file ${encodedURI}`)
 | 
				
			||||||
 | 
					      return res.status(204).header({ 'X-Accel-Redirect': encodedURI }).send()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Express does not set the correct mimetype for m4b files so use our defined mimetypes if available
 | 
				
			||||||
 | 
					    const audioMimeType = getAudioMimeTypeFromExtname(Path.extname(audioTrack.metadata.path))
 | 
				
			||||||
 | 
					    if (audioMimeType) {
 | 
				
			||||||
 | 
					      res.setHeader('Content-Type', audioMimeType)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    res.sendFile(audioTrack.metadata.path)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /**
 | 
					  /**
 | 
				
			||||||
   *
 | 
					   *
 | 
				
			||||||
   * @param {RequestWithUser} req
 | 
					   * @param {RequestWithUser} req
 | 
				
			||||||
 | 
				
			|||||||
@ -26,6 +26,12 @@ class PlaybackSessionManager {
 | 
				
			|||||||
    this.sessions = []
 | 
					    this.sessions = []
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * Get open session by id
 | 
				
			||||||
 | 
					   *
 | 
				
			||||||
 | 
					   * @param {string} sessionId
 | 
				
			||||||
 | 
					   * @returns {PlaybackSession}
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
  getSession(sessionId) {
 | 
					  getSession(sessionId) {
 | 
				
			||||||
    return this.sessions.find((s) => s.id === sessionId)
 | 
					    return this.sessions.find((s) => s.id === sessionId)
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
				
			|||||||
@ -1,5 +1,6 @@
 | 
				
			|||||||
const express = require('express')
 | 
					const express = require('express')
 | 
				
			||||||
const ShareController = require('../controllers/ShareController')
 | 
					const ShareController = require('../controllers/ShareController')
 | 
				
			||||||
 | 
					const SessionController = require('../controllers/SessionController')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class PublicRouter {
 | 
					class PublicRouter {
 | 
				
			||||||
  constructor(playbackSessionManager) {
 | 
					  constructor(playbackSessionManager) {
 | 
				
			||||||
@ -17,6 +18,7 @@ class PublicRouter {
 | 
				
			|||||||
    this.router.get('/share/:slug/cover', ShareController.getMediaItemShareCoverImage.bind(this))
 | 
					    this.router.get('/share/:slug/cover', ShareController.getMediaItemShareCoverImage.bind(this))
 | 
				
			||||||
    this.router.get('/share/:slug/download', ShareController.downloadMediaItemShare.bind(this))
 | 
					    this.router.get('/share/:slug/download', ShareController.downloadMediaItemShare.bind(this))
 | 
				
			||||||
    this.router.patch('/share/:slug/progress', ShareController.updateMediaItemShareProgress.bind(this))
 | 
					    this.router.patch('/share/:slug/progress', ShareController.updateMediaItemShareProgress.bind(this))
 | 
				
			||||||
 | 
					    this.router.get('/session/:id/track/:index', SessionController.getTrack.bind(this))
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
module.exports = PublicRouter
 | 
					module.exports = PublicRouter
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
		Reference in New Issue
	
	Block a user