dizquetv/src/vlc.js
Dan Ferguson d28032fab4 v2 alpha
2020-04-07 17:03:39 -04:00

147 lines
5.6 KiB
JavaScript

const Router = require('express').Router
const spawn = require('child_process').spawn
const request = require('request')
const config = require('config-yml')
const xmltv = require('./xmltv')
module.exports = vlcRouter
function vlcRouter(client) {
var router = Router()
var inUse = false
router.get('/video', (req, res) => {
if (inUse)
return res.status(409).send("Error: Another user is currently viewing a stream. One one active stream is allowed.")
inUse = true
var channel = req.query.channel
if (!channel) {
inUse = false
res.status(400).send("Error: No channel queried")
return
}
channel = channel.split('?')[0]
res.writeHead(200, {
'Content-Type': 'application/octet-stream',
'Content-disposition': 'attachment; filename=video.ts'
})
startStreaming(channel, res)
})
return router
function startStreaming(channel, res) {
var programs = xmltv.readXMLPrograms()
var startPos = -1
var programIndex = -1
var channelExists = false
for (var i = 0; i < programs.length; i++) {
var date = new Date()
if (programs[i].channel == channel) {
channelExists = true
if (programs[i].start <= date && programs[i].stop >= date) {
startPos = date.getTime() - programs[i].start.getTime()
programIndex = i
break
}
}
}
// End session if any errors.
if (!channelExists) {
inUse = false
res.status(403).send(`Error: Channel doesn't exist. Channel: ${channel}`)
return
}
if (programIndex === -1) {
inUse = false
res.status(403).send(`Error: No scheduled programming available. Channel: ${channel}`)
return
}
if (startPos === -1) {
inUse = false
res.status(403).send(`Error: How the fuck did you get here?. Channel: ${channel}`)
return
}
// Query plex for current program
client.Get(programs[programIndex].key, (result) => {
if (result.err) {
inUse = false
res.status(403).send(`Error: Failed to fetch program info from Plex`)
return
}
var fetchedItem = result.result.MediaContainer.Metadata[0]
// Transcode it
client.Transcode(fetchedItem, startPos, (result) => {
if (result.err) {
inUse = false
res.status(403).send(`Error: Failed to add program to playQueue`)
return
}
// Update server timeline every 10 seconds
var stream = result.result
var msElapsed = startPos
var timelineInterval = setInterval(() => {
stream.update(msElapsed)
msElapsed += 10000
}, 10000)
var args = [
stream.url,
`--start-time=${(startPos + config.VLC_OPTIONS.DELAY) / 1000}`,
`--sout=#http{mux=ts,dst=:${config.VLC_OPTIONS.PORT}/}`
]
if (config.VLC_OPTIONS.HIDDEN)
args.push("--intf=dummy")
// Fire up VLC
var vlc = spawn(config.VLC_OPTIONS.PATH, args)
// Wait for VLC to open before we request anything.
setTimeout(() => {
request(`http://${config.HOST}:${config.VLC_OPTIONS.PORT}/`)
.on('error', (err) => {
vlc.kill()
if (err.code === 'ECONNRESET') {
var end = programs[programIndex].stop
var now = new Date()
var timeUntilDone = end.valueOf() - now.valueOf()
timeUntilDone = timeUntilDone > 0 ? timeUntilDone : 0
setTimeout(() => {
res.removeListener('close', httpEnd)
startStreaming(channel, res)
}, timeUntilDone)
}
})
.on('data', (chunk) => {
res.write(chunk)
})
.on("complete", () => {
vlc.kill()
var end = programs[programIndex].stop
var now = new Date()
var timeUntilDone = end.valueOf() - now.valueOf()
timeUntilDone = timeUntilDone > 0 ? timeUntilDone : 0
setTimeout(() => {
res.removeListener('close', httpEnd)
startStreaming(channel, res)
}, timeUntilDone)
})
}, config.VLC_OPTIONS.DELAY)
// When the http session ends: kill vlc
var httpEnd = function () {
vlc.kill()
inUse = false
}
res.on('close', httpEnd)
vlc.on('close', (code) => {
clearInterval(timelineInterval)
stream.stop()
if (code !== 0 && !res.headersSent) {
res.status(400).send(`Error: VLC closed unexpectedly`)
}
})
})
})
}
}