From 663d02e9fecad131a3dd6a22cd8d72413e1467c9 Mon Sep 17 00:00:00 2001 From: advplyr Date: Thu, 11 Nov 2021 08:39:21 -0600 Subject: [PATCH] Add: api endpoint for starting streams for android auto support --- client/package.json | 2 +- package.json | 2 +- server/ApiController.js | 8 +++++++ server/StreamManager.js | 18 ++++++++++++++- server/objects/Stream.js | 50 +++++++++++++++++++++++++--------------- 5 files changed, 58 insertions(+), 22 deletions(-) diff --git a/client/package.json b/client/package.json index d48d12f8..f63a0742 100644 --- a/client/package.json +++ b/client/package.json @@ -1,6 +1,6 @@ { "name": "audiobookshelf-client", - "version": "1.6.14", + "version": "1.6.15", "description": "Audiobook manager and player", "main": "index.js", "scripts": { diff --git a/package.json b/package.json index 3f22f12a..8f6f7271 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "audiobookshelf", - "version": "1.6.14", + "version": "1.6.15", "description": "Self-hosted audiobook server for managing and playing audiobooks", "main": "index.js", "scripts": { diff --git a/server/ApiController.js b/server/ApiController.js index cbe543bb..bfdd2f6a 100644 --- a/server/ApiController.js +++ b/server/ApiController.js @@ -57,6 +57,7 @@ class ApiController { this.router.post('/audiobook/:id/cover', this.uploadAudiobookCover.bind(this)) this.router.patch('/audiobook/:id/coverfile', this.updateAudiobookCoverFromFile.bind(this)) this.router.get('/audiobook/:id/match', this.matchAudiobookBook.bind(this)) + this.router.get('/audiobook/:id/stream', this.openAudiobookStream.bind(this)) this.router.patch('/audiobook/:id', this.updateAudiobook.bind(this)) this.router.patch('/match/:id', this.match.bind(this)) @@ -541,6 +542,13 @@ class ApiController { res.json(results) } + async openAudiobookStream(req, res) { + var audiobook = this.db.audiobooks.find(a => a.id === req.params.id) + if (!audiobook) return res.sendStatus(404) + + this.streamManager.openStreamApiRequest(res, req.user, audiobook) + } + async updateAudiobook(req, res) { if (!req.user.canUpdate) { Logger.warn('User attempted to update without permission', req.user) diff --git a/server/StreamManager.js b/server/StreamManager.js index 8e0a950b..8a2ab719 100644 --- a/server/StreamManager.js +++ b/server/StreamManager.js @@ -100,8 +100,24 @@ class StreamManager { } } + async openStreamApiRequest(res, user, audiobook) { + Logger.info(`[StreamManager] User "${user.username}" open stream request for "${audiobook.title}"`) + var client = { + user + } + var stream = await this.openStream(client, audiobook) + this.db.updateUserStream(client.user.id, stream.id) + + res.json({ + audiobookId: audiobook.id, + startTime: stream.startTime, + streamId: stream.id, + streamUrl: stream.clientPlaylistUri + }) + } + async openStreamSocketRequest(socket, audiobookId) { - Logger.info('Open Stream Request', socket.id, audiobookId) + Logger.info('[StreamManager] Open Stream Request', socket.id, audiobookId) var audiobook = this.audiobooks.find(ab => ab.id === audiobookId) var client = socket.sheepClient diff --git a/server/objects/Stream.js b/server/objects/Stream.js index 904ac4e4..decb564e 100644 --- a/server/objects/Stream.js +++ b/server/objects/Stream.js @@ -41,7 +41,7 @@ class Stream extends EventEmitter { } get socket() { - return this.client.socket + return this.client ? this.client.socket || null : null } get audiobookId() { @@ -89,15 +89,15 @@ class Stream extends EventEmitter { } get clientUser() { - return this.client.user || {} + return this.client ? this.client.user || {} : null } get clientUserAudiobooks() { - return this.clientUser.audiobooks || {} + return this.client ? this.clientUser.audiobooks || {} : null } get clientUserAudiobookData() { - return this.clientUserAudiobooks[this.audiobookId] + return this.client ? this.clientUserAudiobooks[this.audiobookId] : null } get clientPlaylistUri() { @@ -189,8 +189,10 @@ class Stream extends EventEmitter { if (this.segmentsCreated.size > 6 && !this.isClientInitialized) { this.isClientInitialized = true - Logger.info(`[STREAM] ${this.id} notifying client that stream is ready`) - this.socket.emit('stream_open', this.toJSON()) + if (this.socket) { + Logger.info(`[STREAM] ${this.id} notifying client that stream is ready`) + this.socket.emit('stream_open', this.toJSON()) + } } var chunks = [] @@ -221,14 +223,16 @@ class Stream extends EventEmitter { var perc = (this.segmentsCreated.size * 100 / this.numSegments).toFixed(2) + '%' Logger.info('[STREAM-CHECK] Check Files', this.segmentsCreated.size, 'of', this.numSegments, perc, `Furthest Segment: ${this.furthestSegmentCreated}`) - Logger.debug('[STREAM-CHECK] Chunks', chunks.join(', ')) + // Logger.debug('[STREAM-CHECK] Chunks', chunks.join(', ')) - this.socket.emit('stream_progress', { - stream: this.id, - percent: perc, - chunks, - numSegments: this.numSegments - }) + if (this.socket) { + this.socket.emit('stream_progress', { + stream: this.id, + percent: perc, + chunks, + numSegments: this.numSegments + }) + } } catch (error) { Logger.error('Failed checking files', error) } @@ -236,15 +240,19 @@ class Stream extends EventEmitter { startLoop() { // Logger.info(`[Stream] ${this.audiobookTitle} (${this.id}) Start Loop`) - this.socket.emit('stream_progress', { stream: this.id, chunks: [], numSegments: 0, percent: '0%' }) + if (this.socket) { + this.socket.emit('stream_progress', { stream: this.id, chunks: [], numSegments: 0, percent: '0%' }) + } clearInterval(this.loop) var intervalId = setInterval(() => { if (!this.isTranscodeComplete) { this.checkFiles() } else { - Logger.info(`[Stream] ${this.audiobookTitle} sending stream_ready`) - this.socket.emit('stream_ready') + if (this.socket) { + Logger.info(`[Stream] ${this.audiobookTitle} sending stream_ready`) + this.socket.emit('stream_ready') + } clearInterval(intervalId) } }, 2000) @@ -342,8 +350,10 @@ class Stream extends EventEmitter { // For very small fast load if (!this.isClientInitialized) { this.isClientInitialized = true - Logger.info(`[STREAM] ${this.id} notifying client that stream is ready`) - this.socket.emit('stream_open', this.toJSON()) + if (this.socket) { + Logger.info(`[STREAM] ${this.id} notifying client that stream is ready`) + this.socket.emit('stream_open', this.toJSON()) + } } this.isTranscodeComplete = true this.ffmpeg = null @@ -367,7 +377,9 @@ class Stream extends EventEmitter { Logger.error('Failed to delete session data', err) }) - this.client.socket.emit('stream_closed', this.id) + if (this.socket) { + this.socket.emit('stream_closed', this.id) + } this.emit('closed') }