Add: api endpoint for starting streams for android auto support

This commit is contained in:
advplyr 2021-11-11 08:39:21 -06:00
parent 2451861e0e
commit 663d02e9fe
5 changed files with 58 additions and 22 deletions

View File

@ -1,6 +1,6 @@
{ {
"name": "audiobookshelf-client", "name": "audiobookshelf-client",
"version": "1.6.14", "version": "1.6.15",
"description": "Audiobook manager and player", "description": "Audiobook manager and player",
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {

View File

@ -1,6 +1,6 @@
{ {
"name": "audiobookshelf", "name": "audiobookshelf",
"version": "1.6.14", "version": "1.6.15",
"description": "Self-hosted audiobook server for managing and playing audiobooks", "description": "Self-hosted audiobook server for managing and playing audiobooks",
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {

View File

@ -57,6 +57,7 @@ class ApiController {
this.router.post('/audiobook/:id/cover', this.uploadAudiobookCover.bind(this)) this.router.post('/audiobook/:id/cover', this.uploadAudiobookCover.bind(this))
this.router.patch('/audiobook/:id/coverfile', this.updateAudiobookCoverFromFile.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/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('/audiobook/:id', this.updateAudiobook.bind(this))
this.router.patch('/match/:id', this.match.bind(this)) this.router.patch('/match/:id', this.match.bind(this))
@ -541,6 +542,13 @@ class ApiController {
res.json(results) 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) { async updateAudiobook(req, res) {
if (!req.user.canUpdate) { if (!req.user.canUpdate) {
Logger.warn('User attempted to update without permission', req.user) Logger.warn('User attempted to update without permission', req.user)

View File

@ -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) { 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 audiobook = this.audiobooks.find(ab => ab.id === audiobookId)
var client = socket.sheepClient var client = socket.sheepClient

View File

@ -41,7 +41,7 @@ class Stream extends EventEmitter {
} }
get socket() { get socket() {
return this.client.socket return this.client ? this.client.socket || null : null
} }
get audiobookId() { get audiobookId() {
@ -89,15 +89,15 @@ class Stream extends EventEmitter {
} }
get clientUser() { get clientUser() {
return this.client.user || {} return this.client ? this.client.user || {} : null
} }
get clientUserAudiobooks() { get clientUserAudiobooks() {
return this.clientUser.audiobooks || {} return this.client ? this.clientUser.audiobooks || {} : null
} }
get clientUserAudiobookData() { get clientUserAudiobookData() {
return this.clientUserAudiobooks[this.audiobookId] return this.client ? this.clientUserAudiobooks[this.audiobookId] : null
} }
get clientPlaylistUri() { get clientPlaylistUri() {
@ -189,9 +189,11 @@ class Stream extends EventEmitter {
if (this.segmentsCreated.size > 6 && !this.isClientInitialized) { if (this.segmentsCreated.size > 6 && !this.isClientInitialized) {
this.isClientInitialized = true this.isClientInitialized = true
if (this.socket) {
Logger.info(`[STREAM] ${this.id} notifying client that stream is ready`) Logger.info(`[STREAM] ${this.id} notifying client that stream is ready`)
this.socket.emit('stream_open', this.toJSON()) this.socket.emit('stream_open', this.toJSON())
} }
}
var chunks = [] var chunks = []
var current_chunk = [] var current_chunk = []
@ -221,14 +223,16 @@ class Stream extends EventEmitter {
var perc = (this.segmentsCreated.size * 100 / this.numSegments).toFixed(2) + '%' 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.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(', '))
if (this.socket) {
this.socket.emit('stream_progress', { this.socket.emit('stream_progress', {
stream: this.id, stream: this.id,
percent: perc, percent: perc,
chunks, chunks,
numSegments: this.numSegments numSegments: this.numSegments
}) })
}
} catch (error) { } catch (error) {
Logger.error('Failed checking files', error) Logger.error('Failed checking files', error)
} }
@ -236,15 +240,19 @@ class Stream extends EventEmitter {
startLoop() { startLoop() {
// Logger.info(`[Stream] ${this.audiobookTitle} (${this.id}) Start Loop`) // Logger.info(`[Stream] ${this.audiobookTitle} (${this.id}) Start Loop`)
if (this.socket) {
this.socket.emit('stream_progress', { stream: this.id, chunks: [], numSegments: 0, percent: '0%' }) this.socket.emit('stream_progress', { stream: this.id, chunks: [], numSegments: 0, percent: '0%' })
}
clearInterval(this.loop) clearInterval(this.loop)
var intervalId = setInterval(() => { var intervalId = setInterval(() => {
if (!this.isTranscodeComplete) { if (!this.isTranscodeComplete) {
this.checkFiles() this.checkFiles()
} else { } else {
if (this.socket) {
Logger.info(`[Stream] ${this.audiobookTitle} sending stream_ready`) Logger.info(`[Stream] ${this.audiobookTitle} sending stream_ready`)
this.socket.emit('stream_ready') this.socket.emit('stream_ready')
}
clearInterval(intervalId) clearInterval(intervalId)
} }
}, 2000) }, 2000)
@ -342,9 +350,11 @@ class Stream extends EventEmitter {
// For very small fast load // For very small fast load
if (!this.isClientInitialized) { if (!this.isClientInitialized) {
this.isClientInitialized = true this.isClientInitialized = true
if (this.socket) {
Logger.info(`[STREAM] ${this.id} notifying client that stream is ready`) Logger.info(`[STREAM] ${this.id} notifying client that stream is ready`)
this.socket.emit('stream_open', this.toJSON()) this.socket.emit('stream_open', this.toJSON())
} }
}
this.isTranscodeComplete = true this.isTranscodeComplete = true
this.ffmpeg = null this.ffmpeg = null
clearInterval(this.loop) clearInterval(this.loop)
@ -367,7 +377,9 @@ class Stream extends EventEmitter {
Logger.error('Failed to delete session data', err) 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') this.emit('closed')
} }