diff --git a/client/players/AudioTrack.js b/client/players/AudioTrack.js index 05f11ad9..1296fd44 100644 --- a/client/players/AudioTrack.js +++ b/client/players/AudioTrack.js @@ -1,5 +1,5 @@ export default class AudioTrack { - constructor(track, userToken, routerBasePath) { + constructor(track, sessionId, routerBasePath) { this.index = track.index || 0 this.startOffset = track.startOffset || 0 // Total time of all previous tracks this.duration = track.duration || 0 @@ -8,28 +8,25 @@ export default class AudioTrack { this.mimeType = track.mimeType this.metadata = track.metadata || {} - this.userToken = userToken + this.sessionId = sessionId this.routerBasePath = routerBasePath || '' + this.sessionTrackUrl = `/public/session/${sessionId}/track/${this.index}` } /** * Used for CastPlayer */ get fullContentUrl() { - if (!this.contentUrl || this.contentUrl.startsWith('http')) return this.contentUrl - 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 */ get relativeContentUrl() { - if (!this.contentUrl || this.contentUrl.startsWith('http')) return this.contentUrl - - return `${this.routerBasePath}${this.contentUrl}?token=${this.userToken}` + return `${this.routerBasePath}${this.sessionTrackUrl}` } } diff --git a/client/players/PlayerHandler.js b/client/players/PlayerHandler.js index 6e4baa45..5c5f281f 100644 --- a/client/players/PlayerHandler.js +++ b/client/players/PlayerHandler.js @@ -37,9 +37,6 @@ export default class PlayerHandler { get isPlayingLocalItem() { return this.libraryItem && this.player instanceof LocalAudioPlayer } - get userToken() { - return this.ctx.$store.getters['user/getToken'] - } get playerPlaying() { return this.playerState === 'PLAYING' } @@ -226,7 +223,7 @@ export default class PlayerHandler { 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.ctx.$config.routerBasePath)) this.ctx.playerLoading = true this.isHlsTranscode = true diff --git a/server/controllers/SessionController.js b/server/controllers/SessionController.js index c3361ce9..8cebdd35 100644 --- a/server/controllers/SessionController.js +++ b/server/controllers/SessionController.js @@ -1,7 +1,9 @@ +const Path = require('path') const { Request, Response, NextFunction } = require('express') const Logger = require('../Logger') const Database = require('../Database') const { toNumber, isUUID } = require('../utils/index') +const { getAudioMimeTypeFromExtname, encodeUriPath } = require('../utils/fileUtils') const ShareManager = require('../managers/ShareManager') @@ -266,6 +268,51 @@ class SessionController { 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 diff --git a/server/managers/PlaybackSessionManager.js b/server/managers/PlaybackSessionManager.js index f410cdaf..39bd2e8f 100644 --- a/server/managers/PlaybackSessionManager.js +++ b/server/managers/PlaybackSessionManager.js @@ -26,6 +26,12 @@ class PlaybackSessionManager { this.sessions = [] } + /** + * Get open session by id + * + * @param {string} sessionId + * @returns {PlaybackSession} + */ getSession(sessionId) { return this.sessions.find((s) => s.id === sessionId) } diff --git a/server/routers/PublicRouter.js b/server/routers/PublicRouter.js index 107edf99..092414be 100644 --- a/server/routers/PublicRouter.js +++ b/server/routers/PublicRouter.js @@ -1,5 +1,6 @@ const express = require('express') const ShareController = require('../controllers/ShareController') +const SessionController = require('../controllers/SessionController') class PublicRouter { constructor(playbackSessionManager) { @@ -17,6 +18,7 @@ class PublicRouter { this.router.get('/share/:slug/cover', ShareController.getMediaItemShareCoverImage.bind(this)) this.router.get('/share/:slug/download', ShareController.downloadMediaItemShare.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