mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2024-12-20 19:06:06 +01:00
Add support for WMA and AIFF audio files #449, add remove orphan streams, clean up audio mime type logic
This commit is contained in:
parent
6d823f4e42
commit
5d305c96ad
@ -255,6 +255,9 @@ export default {
|
||||
})
|
||||
this.playerHandler.prepareOpenSession(session)
|
||||
},
|
||||
streamOpen(session) {
|
||||
console.log(`[StreamContainer] Stream session open`, session)
|
||||
},
|
||||
streamClosed(streamId) {
|
||||
// Stream was closed from the server
|
||||
if (this.playerHandler.isPlayingLocalItem && this.playerHandler.currentStreamId === streamId) {
|
||||
|
@ -18,7 +18,7 @@ export default class CastPlayer extends EventEmitter {
|
||||
this.defaultPlaybackRate = 1
|
||||
|
||||
// TODO: Use canDisplayType on receiver to check mime types
|
||||
this.playableMimeTypes = {}
|
||||
this.playableMimeTypes = []
|
||||
|
||||
this.coverUrl = ''
|
||||
this.castPlayerState = 'IDLE'
|
||||
|
@ -19,7 +19,7 @@ export default class LocalPlayer extends EventEmitter {
|
||||
this.playWhenReady = false
|
||||
this.defaultPlaybackRate = 1
|
||||
|
||||
this.playableMimeTypes = {}
|
||||
this.playableMimeTypes = []
|
||||
|
||||
this.initialize()
|
||||
}
|
||||
@ -46,11 +46,14 @@ export default class LocalPlayer extends EventEmitter {
|
||||
this.player.addEventListener('loadedmetadata', this.evtLoadedMetadata.bind(this))
|
||||
this.player.addEventListener('timeupdate', this.evtTimeupdate.bind(this))
|
||||
|
||||
var mimeTypes = ['audio/flac', 'audio/mpeg', 'audio/mp4', 'audio/ogg', 'audio/aac']
|
||||
var mimeTypes = ['audio/flac', 'audio/mpeg', 'audio/mp4', 'audio/ogg', 'audio/aac', 'audio/x-ms-wma', 'audio/x-aiff']
|
||||
var mimeTypeCanPlayMap = {}
|
||||
mimeTypes.forEach((mt) => {
|
||||
this.playableMimeTypes[mt] = this.player.canPlayType(mt)
|
||||
var canPlay = this.player.canPlayType(mt)
|
||||
mimeTypeCanPlayMap[mt] = canPlay
|
||||
if (canPlay) this.playableMimeTypes.push(mt)
|
||||
})
|
||||
console.log(`[LocalPlayer] Supported mime types`, this.playableMimeTypes)
|
||||
console.log(`[LocalPlayer] Supported mime types`, mimeTypeCanPlayMap, this.playableMimeTypes)
|
||||
}
|
||||
|
||||
evtPlay() {
|
||||
|
@ -138,7 +138,7 @@ export default class PlayerHandler {
|
||||
|
||||
async prepare(forceTranscode = false) {
|
||||
var payload = {
|
||||
supportedMimeTypes: Object.keys(this.player.playableMimeTypes),
|
||||
supportedMimeTypes: this.player.playableMimeTypes,
|
||||
mediaPlayer: this.isCasting ? 'chromecast' : 'html5',
|
||||
forceTranscode,
|
||||
forceDirectPlay: this.isCasting // TODO: add transcode support for chromecast
|
||||
|
@ -1,6 +1,6 @@
|
||||
const SupportedFileTypes = {
|
||||
image: ['png', 'jpg', 'jpeg', 'webp'],
|
||||
audio: ['m4b', 'mp3', 'm4a', 'flac', 'opus', 'mp4', 'aac'],
|
||||
audio: ['m4b', 'mp3', 'm4a', 'flac', 'opus', 'ogg', 'mp4', 'aac', 'wma', 'aiff'],
|
||||
ebook: ['epub', 'pdf', 'mobi', 'azw3', 'cbr', 'cbz'],
|
||||
info: ['nfo'],
|
||||
text: ['txt'],
|
||||
|
@ -114,7 +114,7 @@ class Server {
|
||||
Logger.info('[Server] Init v' + version)
|
||||
// TODO: Remove orphan streams from playback session manager
|
||||
// await this.streamManager.ensureStreamsDir()
|
||||
// await this.streamManager.removeOrphanStreams()
|
||||
await this.playbackSessionManager.removeOrphanStreams()
|
||||
await this.downloadManager.removeOrphanDownloads()
|
||||
|
||||
if (version.localeCompare('2.0.0') < 0) { // Old version data model migration
|
||||
|
@ -3,6 +3,7 @@ const { PlayMethod } = require('../utils/constants')
|
||||
const PlaybackSession = require('../objects/PlaybackSession')
|
||||
const Stream = require('../objects/Stream')
|
||||
const Logger = require('../Logger')
|
||||
const fs = require('fs-extra')
|
||||
|
||||
class PlaybackSessionManager {
|
||||
constructor(db, emitter, clientEmitter) {
|
||||
@ -95,9 +96,12 @@ class PlaybackSessionManager {
|
||||
Logger.debug(`[PlaybackSessionManager] "${user.username}" starting stream session for item "${libraryItem.id}"`)
|
||||
var stream = new Stream(newPlaybackSession.id, this.StreamsPath, user, libraryItem, episodeId, userStartTime, this.clientEmitter.bind(this))
|
||||
await stream.generatePlaylist()
|
||||
stream.start() // Start transcode
|
||||
|
||||
audioTracks = [stream.getAudioTrack()]
|
||||
newPlaybackSession.stream = stream
|
||||
newPlaybackSession.playMethod = PlayMethod.TRANSCODE
|
||||
|
||||
stream.on('closed', () => {
|
||||
Logger.debug(`[PlaybackSessionManager] Stream closed for session "${newPlaybackSession.id}"`)
|
||||
newPlaybackSession.stream = null
|
||||
@ -179,5 +183,26 @@ class PlaybackSessionManager {
|
||||
this.sessions = this.sessions.filter(s => s.id !== sessionId)
|
||||
Logger.debug(`[PlaybackSessionManager] Removed session "${sessionId}"`)
|
||||
}
|
||||
|
||||
// Check for streams that are not in memory and remove
|
||||
async removeOrphanStreams() {
|
||||
await fs.ensureDir(this.StreamsPath)
|
||||
try {
|
||||
var streamsInPath = await fs.readdir(this.StreamsPath)
|
||||
for (let i = 0; i < streamsInPath.length; i++) {
|
||||
var streamId = streamsInPath[i]
|
||||
if (streamId.startsWith('play_')) { // Make sure to only remove folders that are a stream
|
||||
var session = this.sessions.find(se => se.id === streamId)
|
||||
if (!session) {
|
||||
var streamPath = Path.join(this.StreamsPath, streamId)
|
||||
Logger.debug(`[PlaybackSessionManager] Removing orphan stream "${streamPath}"`)
|
||||
await fs.remove(streamPath)
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
Logger.error(`[PlaybackSessionManager] cleanOrphanStreams failed`, error)
|
||||
}
|
||||
}
|
||||
}
|
||||
module.exports = PlaybackSessionManager
|
@ -3,8 +3,9 @@ const EventEmitter = require('events')
|
||||
const Path = require('path')
|
||||
const fs = require('fs-extra')
|
||||
const Logger = require('../Logger')
|
||||
const { getId, secondsToTimestamp } = require('../utils/index')
|
||||
const { secondsToTimestamp } = require('../utils/index')
|
||||
const { writeConcatFile } = require('../utils/ffmpegHelpers')
|
||||
const { AudioMimeType } = require('../utils/constants')
|
||||
const hlsPlaylistGenerator = require('../utils/hlsPlaylistGenerator')
|
||||
const AudioTrack = require('./files/AudioTrack')
|
||||
|
||||
@ -63,6 +64,18 @@ class Stream extends EventEmitter {
|
||||
if (!this.tracks.length) return null
|
||||
return this.tracks[0].metadata.format
|
||||
}
|
||||
get tracksMimeType() {
|
||||
if (!this.tracks.length) return null
|
||||
return this.tracks[0].mimeType
|
||||
}
|
||||
get mimeTypesToForceAAC() {
|
||||
return [
|
||||
AudioMimeType.FLAC,
|
||||
AudioMimeType.OPUS,
|
||||
AudioMimeType.WMA,
|
||||
AudioMimeType.AIFF
|
||||
]
|
||||
}
|
||||
get userToken() {
|
||||
return this.user.token
|
||||
}
|
||||
@ -89,11 +102,6 @@ class Stream extends EventEmitter {
|
||||
get clientPlaylistUri() {
|
||||
return `/hls/${this.id}/output.m3u8`
|
||||
}
|
||||
// get clientProgress() {
|
||||
// if (!this.clientCurrentTime) return 0
|
||||
// var prog = Math.min(1, this.clientCurrentTime / this.totalDuration)
|
||||
// return Number(prog.toFixed(3))
|
||||
// }
|
||||
get isAACEncodable() {
|
||||
return ['mp4', 'm4a', 'm4b'].includes(this.tracksAudioFileType)
|
||||
}
|
||||
@ -137,7 +145,7 @@ class Stream extends EventEmitter {
|
||||
}
|
||||
|
||||
async generatePlaylist() {
|
||||
fs.ensureDirSync(this.streamPath)
|
||||
await fs.ensureDir(this.streamPath)
|
||||
await hlsPlaylistGenerator(this.playlistPath, 'output', this.totalDuration, this.segmentLength, this.hlsSegmentType, this.userToken)
|
||||
return this.clientPlaylistUri
|
||||
}
|
||||
@ -251,7 +259,7 @@ class Stream extends EventEmitter {
|
||||
|
||||
const logLevel = process.env.NODE_ENV === 'production' ? 'error' : 'warning'
|
||||
|
||||
const audioCodec = (this.tracksAudioFileType === 'flac' || this.tracksAudioFileType === 'opus' || this.transcodeForceAAC) ? 'aac' : 'copy'
|
||||
const audioCodec = (this.mimeTypesToForceAAC.includes(this.tracksMimeType) || this.transcodeForceAAC) ? 'aac' : 'copy'
|
||||
|
||||
this.ffmpeg.addOption([
|
||||
`-loglevel ${logLevel}`,
|
||||
|
@ -1,4 +1,5 @@
|
||||
const { isNullOrNaN } = require('../../utils/index')
|
||||
const { AudioMimeType } = require('../../utils/constants')
|
||||
const AudioMetaTags = require('../metadata/AudioMetaTags')
|
||||
const FileMetadata = require('../metadata/FileMetadata')
|
||||
|
||||
@ -102,19 +103,12 @@ class AudioFile {
|
||||
}
|
||||
|
||||
get mimeType() {
|
||||
var ext = this.metadata.ext
|
||||
if (ext === '.mp3' || ext === '.m4b' || ext === '.m4a') {
|
||||
return 'audio/mpeg'
|
||||
} else if (ext === '.mp4') {
|
||||
return 'audio/mp4'
|
||||
} else if (ext === '.ogg') {
|
||||
return 'audio/ogg'
|
||||
} else if (ext === '.aac' || ext === '.m4p') {
|
||||
return 'audio/aac'
|
||||
} else if (ext === '.flac') {
|
||||
return 'audio/flac'
|
||||
var format = this.metadata.format.toUpperCase()
|
||||
if (AudioMimeType[format]) {
|
||||
return AudioMimeType[format]
|
||||
} else {
|
||||
return AudioMimeType.MP3
|
||||
}
|
||||
return 'audio/mpeg'
|
||||
}
|
||||
|
||||
// New scanner creates AudioFile from AudioFileScanner
|
||||
|
@ -31,4 +31,17 @@ module.exports.PlayMethod = {
|
||||
DIRECTSTREAM: 1,
|
||||
TRANSCODE: 2,
|
||||
LOCAL: 3
|
||||
}
|
||||
|
||||
module.exports.AudioMimeType = {
|
||||
MP3: 'audio/mpeg',
|
||||
M4B: 'audio/mpeg',
|
||||
M4A: 'audio/mpeg',
|
||||
MP4: 'audio/mp4',
|
||||
OGG: 'audio/ogg',
|
||||
OPUS: 'audio/ogg',
|
||||
AAC: 'audio/aac',
|
||||
FLAC: 'audio/flac',
|
||||
WMA: 'audio/x-ms-wma',
|
||||
AIFF: 'audio/x-aiff'
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
const globals = {
|
||||
SupportedImageTypes: ['png', 'jpg', 'jpeg', 'webp'],
|
||||
SupportedAudioTypes: ['m4b', 'mp3', 'm4a', 'flac', 'opus', 'mp4', 'aac'],
|
||||
SupportedAudioTypes: ['m4b', 'mp3', 'm4a', 'flac', 'opus', 'ogg', 'mp4', 'aac', 'wma', 'aiff'],
|
||||
SupportedEbookTypes: ['epub', 'pdf', 'mobi', 'azw3', 'cbr', 'cbz'],
|
||||
TextFileTypes: ['txt', 'nfo'],
|
||||
MetadataFileTypes: ['opf', 'abs', 'xml']
|
||||
|
Loading…
Reference in New Issue
Block a user