mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2024-12-20 19:06:06 +01:00
Adding audio playback speed control, updating volume control UI, fix stream play for small streams
This commit is contained in:
parent
4bcb346365
commit
be7e2576f1
@ -28,6 +28,10 @@
|
||||
background: #704922;
|
||||
}
|
||||
|
||||
.no-scroll::-webkit-scrollbar {
|
||||
display: none;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.tracksTable {
|
||||
border-collapse: collapse;
|
||||
@ -49,4 +53,12 @@
|
||||
.tracksTable th {
|
||||
padding: 4px;
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
.arrow-down {
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-left: 6px solid transparent;
|
||||
border-right: 6px solid transparent;
|
||||
border-top: 6px solid white;
|
||||
}
|
@ -44,4 +44,18 @@
|
||||
.menu-enter,
|
||||
.menu-leave-active {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.menux-enter, .menux-leave-active {
|
||||
transform: translateX(15px);
|
||||
}
|
||||
.menux-enter-active {
|
||||
transition: all 0.2s;
|
||||
}
|
||||
.menux-leave-active {
|
||||
transition: all 0.1s;
|
||||
}
|
||||
.menux-enter,
|
||||
.menux-leave-active {
|
||||
opacity: 0;
|
||||
}
|
@ -27,9 +27,7 @@
|
||||
<div class="cursor-pointer flex items-center justify-center text-gray-300" @mousedown.prevent @mouseup.prevent @click.stop="forward10">
|
||||
<span class="material-icons text-3xl">forward_10</span>
|
||||
</div>
|
||||
<div class="flex items-center justify-center text-gray-300 ml-8" @mousedown.prevent @mouseup.prevent>
|
||||
<span class="font-mono text-lg uppercase text-gray-500">1x</span>
|
||||
</div>
|
||||
<controls-playback-speed-control v-model="playbackRate" @change="updatePlaybackRate" />
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="cursor-pointer p-2 shadow-sm bg-accent flex items-center justify-center rounded-full text-primary mx-8 animate-spin">
|
||||
@ -75,6 +73,7 @@ export default {
|
||||
hlsInstance: null,
|
||||
staleHlsInstance: null,
|
||||
volume: 0.5,
|
||||
playbackRate: 1,
|
||||
trackWidth: 0,
|
||||
isPaused: true,
|
||||
url: null,
|
||||
@ -126,7 +125,15 @@ export default {
|
||||
},
|
||||
updateVolume(volume) {
|
||||
if (this.audioEl) {
|
||||
this.audioEl.volume = 1 - volume
|
||||
this.audioEl.volume = volume
|
||||
}
|
||||
},
|
||||
updatePlaybackRate(playbackRate) {
|
||||
if (this.audioEl) {
|
||||
console.log('UpdatePlaybackRate', playbackRate)
|
||||
this.audioEl.playbackRate = playbackRate
|
||||
} else {
|
||||
console.error('No Audio El updatePlaybackRate')
|
||||
}
|
||||
},
|
||||
mousemoveTrack(e) {
|
||||
@ -173,7 +180,6 @@ export default {
|
||||
setStreamReady() {
|
||||
this.readyTrackWidth = this.trackWidth
|
||||
this.$refs.readyTrack.style.width = this.trackWidth + 'px'
|
||||
console.warn('SET STREAM READY', this.readyTrackWidth)
|
||||
},
|
||||
setChunksReady(chunks, numSegments) {
|
||||
var largestSeg = 0
|
||||
@ -349,6 +355,7 @@ export default {
|
||||
this.hlsInstance = new Hls(hlsOptions)
|
||||
var audio = this.$refs.audio
|
||||
audio.volume = this.volume
|
||||
audio.playbackRate = this.playbackRate
|
||||
this.hlsInstance.attachMedia(audio)
|
||||
this.hlsInstance.on(Hls.Events.MEDIA_ATTACHED, () => {
|
||||
// console.log('[HLS] MEDIA ATTACHED')
|
||||
@ -367,13 +374,6 @@ export default {
|
||||
console.error('[HLS] BUFFER STALLED ERROR')
|
||||
}
|
||||
})
|
||||
this.hlsInstance.on(Hls.Events.FRAG_LOADED, (e, data) => {
|
||||
var frag = data.frag
|
||||
// console.log('[HLS] Frag Loaded', frag.sn, this.$secondsToTimestamp(frag.start), frag)
|
||||
})
|
||||
this.hlsInstance.on(Hls.Events.BUFFER_APPENDED, (e, data) => {
|
||||
// console.log('[HLS] BUFFER', data)
|
||||
})
|
||||
this.hlsInstance.on(Hls.Events.DESTROYING, () => {
|
||||
console.warn('[HLS] Destroying HLS Instance')
|
||||
})
|
||||
@ -425,14 +425,7 @@ export default {
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.arrow-down {
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-left: 6px solid transparent;
|
||||
border-right: 6px solid transparent;
|
||||
border-top: 6px solid white;
|
||||
}
|
||||
<style>
|
||||
.loadingTrack {
|
||||
animation-name: loadingTrack;
|
||||
animation-duration: 1s;
|
||||
|
@ -92,8 +92,11 @@ export default {
|
||||
streamProgress(data) {
|
||||
if (!data.numSegments) return
|
||||
var chunks = data.chunks
|
||||
console.log(`[STREAM-CONTAINER] Stream Progress ${data.percent}`)
|
||||
if (this.$refs.audioPlayer) {
|
||||
this.$refs.audioPlayer.setChunksReady(chunks, data.numSegments)
|
||||
} else {
|
||||
console.error('No Audio Ref')
|
||||
}
|
||||
},
|
||||
streamOpen(stream) {
|
||||
@ -101,6 +104,8 @@ export default {
|
||||
if (this.$refs.audioPlayer) {
|
||||
console.log('[STREAM-CONTAINER] streamOpen', stream)
|
||||
this.openStream()
|
||||
} else {
|
||||
console.error('No Audio Ref')
|
||||
}
|
||||
},
|
||||
streamClosed(streamId) {
|
||||
@ -111,8 +116,11 @@ export default {
|
||||
}
|
||||
},
|
||||
streamReady() {
|
||||
console.log(`[STREAM-CONTAINER] Stream Ready`)
|
||||
if (this.$refs.audioPlayer) {
|
||||
this.$refs.audioPlayer.setStreamReady()
|
||||
} else {
|
||||
console.error('No Audio Ref')
|
||||
}
|
||||
},
|
||||
updateTime(currentTime) {
|
||||
|
62
client/components/controls/PlaybackSpeedControl.vue
Normal file
62
client/components/controls/PlaybackSpeedControl.vue
Normal file
@ -0,0 +1,62 @@
|
||||
<template>
|
||||
<div class="relative ml-8" v-click-outside="clickOutside">
|
||||
<div class="flex items-center justify-center text-gray-300 cursor-pointer h-full" @mousedown.prevent @mouseup.prevent @click="showMenu = !showMenu">
|
||||
<span class="font-mono uppercase text-gray-200">{{ playbackRate.toFixed(1) }}<span class="text-lg">⨯</span></span>
|
||||
</div>
|
||||
<div v-show="showMenu" class="absolute -top-10 left-0 z-20 h-9 bg-bg border-black-200 border shadow-xl rounded-lg" style="left: -114px">
|
||||
<div class="absolute -bottom-2 left-0 right-0 w-full flex justify-center">
|
||||
<div class="arrow-down" />
|
||||
</div>
|
||||
|
||||
<div class="w-full h-full no-scroll flex">
|
||||
<template v-for="(rate, index) in rates">
|
||||
<div :key="rate" class="flex items-center justify-center border-black-300 w-11 hover:bg-black hover:bg-opacity-10 cursor-pointer" :class="index < rates.length - 1 ? 'border-r' : ''" style="min-width: 44px; max-width: 44px" @click="set(rate)">
|
||||
<p class="text-xs text-center font-mono">{{ rate.toFixed(1) }}<span class="text-sm">⨯</span></p>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
value: {
|
||||
type: [String, Number],
|
||||
default: 1
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
showMenu: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
playbackRate: {
|
||||
get() {
|
||||
return this.value
|
||||
},
|
||||
set(val) {
|
||||
this.$emit('input', val)
|
||||
}
|
||||
},
|
||||
rates() {
|
||||
return [0.5, 0.8, 1.0, 1.3, 1.5, 2.0]
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
clickOutside() {
|
||||
this.showMenu = false
|
||||
},
|
||||
set(rate) {
|
||||
var newPlaybackRate = Number(rate)
|
||||
var hasChanged = this.playbackRate !== newPlaybackRate
|
||||
this.playbackRate = newPlaybackRate
|
||||
if (hasChanged) this.$emit('change', newPlaybackRate)
|
||||
this.showMenu = false
|
||||
}
|
||||
},
|
||||
mounted() {}
|
||||
}
|
||||
</script>
|
@ -1,13 +1,16 @@
|
||||
<template>
|
||||
<div class="relative" v-click-outside="clickOutside">
|
||||
<div class="relative" v-click-outside="clickOutside" @mouseover="mouseover" @mouseleave="mouseleave">
|
||||
<div class="cursor-pointer" @mousedown.prevent @mouseup.prevent @click="clickVolumeIcon">
|
||||
<span class="material-icons text-3xl">volume_up</span>
|
||||
<span class="material-icons text-3xl">{{ volumeIcon }}</span>
|
||||
</div>
|
||||
<div v-show="isOpen" class="absolute bottom-10 left-0 h-28 py-2 bg-white shadow-sm rounded-lg">
|
||||
<div ref="volumeTrack" class="w-2 border-2 border-white h-full bg-gray-400 mx-4 relative cursor-pointer" @mousedown="mousedownTrack" @click="clickVolumeTrack">
|
||||
<div class="w-3 h-3 bg-gray-500 shadow-sm rounded-full absolute -left-1 bottom-0 pointer-events-none" :class="isDragging ? 'transform scale-150' : ''" :style="{ top: cursorTop + 'px' }" />
|
||||
<transition name="menux">
|
||||
<div v-show="isOpen" class="volumeMenu h-6 absolute bottom-2 w-28 px-2 bg-bg shadow-sm rounded-lg" style="left: -116px">
|
||||
<div ref="volumeTrack" class="h-1 w-full bg-gray-500 my-2.5 relative cursor-pointer rounded-full" @mousedown="mousedownTrack" @click="clickVolumeTrack">
|
||||
<div class="bg-gray-100 h-full absolute left-0 top-0 pointer-events-none rounded-full" :style="{ width: volume * trackWidth + 'px' }" />
|
||||
<div class="w-2.5 h-2.5 bg-white shadow-sm rounded-full absolute pointer-events-none" :class="isDragging ? 'transform scale-125 origin-center' : ''" :style="{ left: cursorLeft + 'px', top: '-3px' }" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -20,8 +23,12 @@ export default {
|
||||
return {
|
||||
isOpen: false,
|
||||
isDragging: false,
|
||||
posY: 0,
|
||||
trackHeight: 112 - 16
|
||||
isHovering: false,
|
||||
posX: 0,
|
||||
lastValue: 0.5,
|
||||
isMute: false,
|
||||
trackWidth: 112 - 20,
|
||||
openTimeout: null
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@ -33,22 +40,45 @@ export default {
|
||||
this.$emit('input', val)
|
||||
}
|
||||
},
|
||||
cursorTop() {
|
||||
var top = this.trackHeight * this.volume
|
||||
return top - 6
|
||||
cursorLeft() {
|
||||
var left = this.trackWidth * this.volume
|
||||
return left - 3
|
||||
},
|
||||
volumeIcon() {
|
||||
if (this.volume <= 0) return 'volume_mute'
|
||||
else if (this.volume <= 0.5) return 'volume_down'
|
||||
else return 'volume_up'
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
mouseover() {
|
||||
this.isHovering = true
|
||||
this.setOpen()
|
||||
},
|
||||
mouseleave() {
|
||||
this.isHovering = false
|
||||
},
|
||||
setOpen() {
|
||||
this.isOpen = true
|
||||
clearTimeout(this.openTimeout)
|
||||
this.openTimeout = setTimeout(() => {
|
||||
if (!this.isHovering && !this.isDragging) {
|
||||
this.isOpen = false
|
||||
} else {
|
||||
this.setOpen()
|
||||
}
|
||||
}, 600)
|
||||
},
|
||||
mousemove(e) {
|
||||
var diff = this.posY - e.y
|
||||
this.posY = e.y
|
||||
var diff = this.posX - e.x
|
||||
this.posX = e.x
|
||||
var volShift = 0
|
||||
if (diff < 0) {
|
||||
// Volume up
|
||||
volShift = diff / this.trackHeight
|
||||
volShift = diff / this.trackWidth
|
||||
} else {
|
||||
// volume down
|
||||
volShift = diff / this.trackHeight
|
||||
volShift = diff / this.trackWidth
|
||||
}
|
||||
var newVol = this.volume - volShift
|
||||
newVol = Math.min(Math.max(0, newVol), 1)
|
||||
@ -64,8 +94,8 @@ export default {
|
||||
},
|
||||
mousedownTrack(e) {
|
||||
this.isDragging = true
|
||||
this.posY = e.y
|
||||
var vol = e.offsetY / e.target.clientHeight
|
||||
this.posX = e.x
|
||||
var vol = e.offsetX / this.trackWidth
|
||||
vol = Math.min(Math.max(vol, 0), 1)
|
||||
this.volume = vol
|
||||
document.body.addEventListener('mousemove', this.mousemove)
|
||||
@ -76,14 +106,24 @@ export default {
|
||||
this.isOpen = false
|
||||
},
|
||||
clickVolumeIcon() {
|
||||
this.isOpen = !this.isOpen
|
||||
this.isMute = !this.isMute
|
||||
if (this.isMute) {
|
||||
this.lastValue = this.volume
|
||||
this.volume = 0
|
||||
} else {
|
||||
this.volume = this.lastValue || 0.5
|
||||
}
|
||||
},
|
||||
clickVolumeTrack(e) {
|
||||
var vol = e.offsetY / e.target.clientHeight
|
||||
var vol = e.offsetX / this.trackWidth
|
||||
vol = Math.min(Math.max(vol, 0), 1)
|
||||
this.volume = vol
|
||||
}
|
||||
},
|
||||
mounted() {}
|
||||
mounted() {
|
||||
if (this.value === 0) {
|
||||
this.isMute = true
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
@ -85,7 +85,7 @@ export default {
|
||||
console.log('Search', this.lastSearch, this.search)
|
||||
|
||||
this.searchResults = []
|
||||
this.processing = true
|
||||
this.isProcessing = true
|
||||
this.lastSearch = this.search
|
||||
var results = await this.$axios.$get(`/api/find/search?title=${this.search}`).catch((error) => {
|
||||
console.error('Failed', error)
|
||||
@ -96,7 +96,7 @@ export default {
|
||||
})
|
||||
console.log('Got results', results)
|
||||
this.searchResults = results
|
||||
this.processing = false
|
||||
this.isProcessing = false
|
||||
},
|
||||
init() {
|
||||
if (!this.audiobook.book || !this.audiobook.book.title) {
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "audiobookshelf-client",
|
||||
"version": "0.9.52",
|
||||
"version": "0.9.54",
|
||||
"description": "Audiobook manager and player",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
|
@ -3,18 +3,27 @@
|
||||
<div class="w-full max-w-4xl mx-auto">
|
||||
<h1 class="text-2xl mb-2">Config</h1>
|
||||
<div class="h-0.5 bg-primary bg-opacity-50 w-full" />
|
||||
<div class="p-4 text-center h-40">
|
||||
<div class="p-4 text-center h-20">
|
||||
<p>Nothing much here yet...</p>
|
||||
</div>
|
||||
<div class="h-0.5 bg-primary bg-opacity-50 w-full" />
|
||||
<div class="flex items-center py-4">
|
||||
<div class="flex items-center py-4 mb-8">
|
||||
<p class="text-2xl">Scanner</p>
|
||||
<div class="flex-grow" />
|
||||
<ui-btn color="success" @click="scan">Scan</ui-btn>
|
||||
</div>
|
||||
<div class="h-0.5 bg-primary bg-opacity-50 w-full" />
|
||||
<div class="flex items-center py-4">
|
||||
<p class="font-mono">v{{ $config.version }}</p>
|
||||
<p class="font-mono">Beta v{{ $config.version }}</p>
|
||||
<div class="flex-grow" />
|
||||
<p class="pr-2 text-sm font-book text-yellow-400">Report bugs, request features, provide feedback, and contribute on <a class="underline" href="https://github.com/advplyr/audiobookshelf" target="_blank">github</a>.</p>
|
||||
<a href="https://github.com/advplyr/audiobookshelf" target="_blank" class="text-white hover:text-gray-200 hover:scale-150 hover:rotate-6 transform duration-500">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" width="24" height="24" viewBox="0 0 24 24">
|
||||
<path
|
||||
d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "audiobookshelf",
|
||||
"version": "0.9.53",
|
||||
"version": "0.9.54",
|
||||
"description": "Self-hosted audiobook server for managing and playing audiobooks.",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
|
@ -28,14 +28,11 @@ class HlsController {
|
||||
|
||||
async streamFileRequest(req, res) {
|
||||
var streamId = req.params.stream
|
||||
|
||||
// Logger.info('Got hls request', streamId, req.params.file)
|
||||
|
||||
var fullFilePath = Path.join(this.MetadataPath, streamId, req.params.file)
|
||||
|
||||
var exists = await fs.pathExists(fullFilePath)
|
||||
if (!exists) {
|
||||
Logger.error('File path does not exist', fullFilePath)
|
||||
Logger.warn('File path does not exist', fullFilePath)
|
||||
|
||||
var fileExt = Path.extname(req.params.file)
|
||||
if (fileExt === '.ts') {
|
||||
@ -52,36 +49,16 @@ class HlsController {
|
||||
} else {
|
||||
var startTimeForReset = await stream.checkSegmentNumberRequest(segNum)
|
||||
if (startTimeForReset) {
|
||||
// HLS.js should request the file again]
|
||||
// HLS.js will restart the stream at the new time
|
||||
Logger.info(`[HLS-CONTROLLER] Resetting Stream - notify client @${startTimeForReset}s`)
|
||||
this.emitter('stream_reset', {
|
||||
startTime: startTimeForReset,
|
||||
streamId: stream.id
|
||||
})
|
||||
return res.sendStatus(500)
|
||||
// await new Promise((resolve) => {
|
||||
// setTimeout(() => {
|
||||
// console.log('Waited 4 seconds')
|
||||
// resolve()
|
||||
// }, 4000)
|
||||
// })
|
||||
|
||||
// exists = await fs.pathExists(fullFilePath)
|
||||
// if (!exists) {
|
||||
// console.error('Still does not exist')
|
||||
// return res.sendStatus(404)
|
||||
// }
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
// await new Promise(resolve => setTimeout(resolve, 500))
|
||||
// exists = await fs.pathExists(fullFilePath)
|
||||
// Logger.info('Waited', exists)
|
||||
// if (!exists) {
|
||||
// Logger.error('still does not exist', fullFilePath)
|
||||
// return res.sendStatus(404)
|
||||
// }
|
||||
}
|
||||
// Logger.info('Sending file', fullFilePath)
|
||||
res.sendFile(fullFilePath)
|
||||
|
@ -130,7 +130,6 @@ class Server {
|
||||
res.sendStatus(200)
|
||||
})
|
||||
|
||||
app.post('/stream', (req, res) => this.streamManager.openStreamRequest(req, res))
|
||||
app.post('/login', (req, res) => this.auth.login(req, res))
|
||||
app.post('/logout', this.logout.bind(this))
|
||||
app.get('/ping', (req, res) => {
|
||||
@ -165,7 +164,6 @@ class Server {
|
||||
socket.on('close_stream', () => this.streamManager.closeStreamRequest(socket))
|
||||
socket.on('stream_update', (payload) => this.streamManager.streamUpdate(socket, payload))
|
||||
socket.on('test', () => {
|
||||
console.log('Test Request from', socket.id)
|
||||
socket.emit('test_received', socket.id)
|
||||
})
|
||||
|
||||
|
142
server/Stream.js
142
server/Stream.js
@ -296,144 +296,12 @@ class Stream extends EventEmitter {
|
||||
|
||||
this.ffmpeg.on('end', (stdout, stderr) => {
|
||||
Logger.info('[FFMPEG] Transcoding ended')
|
||||
this.isTranscodeComplete = true
|
||||
this.ffmpeg = null
|
||||
})
|
||||
|
||||
this.ffmpeg.run()
|
||||
}
|
||||
|
||||
async startConcat() {
|
||||
Logger.info(`[STREAM] START STREAM - Num Segments: ${this.numSegments}`)
|
||||
|
||||
|
||||
var concatOutput = null
|
||||
if (this.tracks.length > 1) {
|
||||
var start = Date.now()
|
||||
await new Promise(async (resolve) => {
|
||||
Logger.info('Concatenating here', this.tracks.length)
|
||||
|
||||
this.ffmpeg = Ffmpeg()
|
||||
var trackExt = this.tracks[0].ext
|
||||
concatOutput = Path.join(this.streamPath, `concat${trackExt}`)
|
||||
Logger.info('Concat OUTPUT', concatOutput)
|
||||
var trackPaths = this.tracks.map(t => {
|
||||
var line = 'file ' + this.escapeSingleQuotes(t.fullPath) + '\n' + `duration ${t.duration}`
|
||||
return line
|
||||
})
|
||||
var inputstr = trackPaths.join('\n\n')
|
||||
await fs.writeFile(this.concatFilesPath, inputstr)
|
||||
this.ffmpeg.addInput(this.concatFilesPath)
|
||||
this.ffmpeg.inputFormat('concat')
|
||||
this.ffmpeg.inputOption('-safe 0')
|
||||
this.ffmpeg.addOption([
|
||||
'-loglevel warning',
|
||||
'-map 0:a',
|
||||
'-c:a copy'
|
||||
])
|
||||
this.ffmpeg.output(concatOutput)
|
||||
|
||||
this.ffmpeg.on('start', (command) => {
|
||||
Logger.info('[CONCAT] FFMPEG transcoding started with command: ' + command)
|
||||
})
|
||||
this.ffmpeg.on('error', (err, stdout, stderr) => {
|
||||
Logger.info('[CONCAT] ERROR', err, stderr)
|
||||
})
|
||||
|
||||
this.ffmpeg.on('end', (stdout, stderr) => {
|
||||
Logger.info('[CONCAT] Concat is done')
|
||||
resolve()
|
||||
})
|
||||
this.ffmpeg.run()
|
||||
})
|
||||
var elapsed = ((Date.now() - start) / 1000).toFixed(1)
|
||||
Logger.info(`[CONCAT] Final elapsed is ${elapsed}s`)
|
||||
} else {
|
||||
concatOutput = this.tracks[0].fullPath
|
||||
}
|
||||
|
||||
|
||||
this.ffmpeg = Ffmpeg()
|
||||
|
||||
// var currTrackEnd = 0
|
||||
// var startingTrack = this.tracks.find(t => {
|
||||
// currTrackEnd += t.duration
|
||||
// return this.startTime < currTrackEnd
|
||||
// })
|
||||
// var trackStartTime = currTrackEnd - startingTrack.duration
|
||||
// var currInpoint = this.startTime - trackStartTime
|
||||
|
||||
// var tracksToInclude = this.tracks.filter(t => t.index >= startingTrack.index)
|
||||
|
||||
// var trackPaths = tracksToInclude.map(t => {
|
||||
// var line = 'file ' + this.escapeSingleQuotes(t.fullPath) + '\n' + `duration ${t.duration}`
|
||||
// if (t.index === startingTrack.index) {
|
||||
// line += `\ninpoint ${currInpoint}`
|
||||
// }
|
||||
// return line
|
||||
// })
|
||||
// var inputstr = trackPaths.join('\n\n')
|
||||
// await fs.writeFile(this.concatFilesPath, inputstr)
|
||||
|
||||
this.ffmpeg.addInput(concatOutput)
|
||||
// this.ffmpeg.inputFormat('concat')
|
||||
// this.ffmpeg.inputOption('-safe 0')
|
||||
|
||||
if (this.startTime > 0) {
|
||||
Logger.info(`[STREAM] Starting Stream at startTime ${secondsToTimestamp(this.startTime)} and Segment #${this.segmentStartNumber}`)
|
||||
this.ffmpeg.inputOption(`-ss ${this.startTime}`)
|
||||
this.ffmpeg.inputOption('-noaccurate_seek')
|
||||
}
|
||||
|
||||
this.ffmpeg.addOption([
|
||||
'-loglevel warning',
|
||||
'-map 0:a',
|
||||
'-c:a copy'
|
||||
])
|
||||
this.ffmpeg.addOption([
|
||||
'-f hls',
|
||||
"-copyts",
|
||||
"-avoid_negative_ts disabled",
|
||||
"-max_delay 5000000",
|
||||
"-max_muxing_queue_size 2048",
|
||||
`-hls_time 6`,
|
||||
"-hls_segment_type mpegts",
|
||||
`-start_number ${this.segmentStartNumber}`,
|
||||
"-hls_playlist_type vod",
|
||||
"-hls_list_size 0",
|
||||
"-hls_allow_cache 0"
|
||||
])
|
||||
var segmentFilename = Path.join(this.streamPath, this.segmentBasename)
|
||||
this.ffmpeg.addOption(`-hls_segment_filename ${segmentFilename}`)
|
||||
this.ffmpeg.output(this.playlistPath)
|
||||
|
||||
this.ffmpeg.on('start', (command) => {
|
||||
Logger.info('[INFO] FFMPEG transcoding started with command: ' + command)
|
||||
if (this.isResetting) {
|
||||
setTimeout(() => {
|
||||
Logger.info('[STREAM] Clearing isResetting')
|
||||
this.isResetting = false
|
||||
}, 500)
|
||||
// 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())
|
||||
}
|
||||
this.startLoop()
|
||||
})
|
||||
|
||||
this.ffmpeg.on('stderr', (stdErrline) => {
|
||||
Logger.info(stdErrline)
|
||||
})
|
||||
|
||||
this.ffmpeg.on('error', (err, stdout, stderr) => {
|
||||
if (err.message && err.message.includes('SIGKILL')) {
|
||||
// This is an intentional SIGKILL
|
||||
Logger.info('[FFMPEG] Transcode Killed')
|
||||
this.ffmpeg = null
|
||||
} else {
|
||||
Logger.error('Ffmpeg Err', err.message)
|
||||
}
|
||||
})
|
||||
|
||||
this.ffmpeg.on('end', (stdout, stderr) => {
|
||||
Logger.info('[FFMPEG] Transcoding ended')
|
||||
this.isTranscodeComplete = true
|
||||
this.ffmpeg = null
|
||||
})
|
||||
|
Loading…
Reference in New Issue
Block a user