100 lines
3.0 KiB
JavaScript
100 lines
3.0 KiB
JavaScript
const express = require('express')
|
|
const Path = require('path')
|
|
|
|
const Logger = require('../Logger')
|
|
const SocketAuthority = require('../SocketAuthority')
|
|
|
|
const fs = require('../libs/fsExtra')
|
|
|
|
|
|
class HlsRouter {
|
|
constructor(auth, playbackSessionManager) {
|
|
this.auth = auth
|
|
this.playbackSessionManager = playbackSessionManager
|
|
|
|
this.router = express()
|
|
this.router.disable('x-powered-by')
|
|
this.init()
|
|
}
|
|
|
|
init() {
|
|
this.router.get('/:stream/:file', this.streamFileRequest.bind(this))
|
|
}
|
|
|
|
parseSegmentFilename(filename) {
|
|
var basename = Path.basename(filename, Path.extname(filename))
|
|
var num_part = basename.split('-')[1]
|
|
return Number(num_part)
|
|
}
|
|
|
|
/**
|
|
* Ensure filepath is inside streamDir
|
|
* Used to prevent arbitrary file reads
|
|
* @see https://nodejs.org/api/path.html#pathrelativefrom-to
|
|
*
|
|
* @param {string} streamDir
|
|
* @param {string} filepath
|
|
* @returns {boolean}
|
|
*/
|
|
validateStreamFilePath(streamDir, filepath) {
|
|
const relative = Path.relative(streamDir, filepath)
|
|
return relative && !relative.startsWith('..') && !Path.isAbsolute(relative)
|
|
}
|
|
|
|
/**
|
|
* GET /hls/:stream/:file
|
|
* File must have extname .ts or .m3u8
|
|
*
|
|
* @param {express.Request} req
|
|
* @param {express.Response} res
|
|
*/
|
|
async streamFileRequest(req, res) {
|
|
const streamId = req.params.stream
|
|
// Ensure stream is open
|
|
const stream = this.playbackSessionManager.getStream(streamId)
|
|
if (!stream) {
|
|
Logger.error(`[HlsRouter] Stream "${streamId}" does not exist`)
|
|
return res.sendStatus(404)
|
|
}
|
|
|
|
// Ensure stream filepath is valid
|
|
const streamDir = Path.join(this.playbackSessionManager.StreamsPath, streamId)
|
|
const fullFilePath = Path.join(streamDir, req.params.file)
|
|
if (!this.validateStreamFilePath(streamDir, fullFilePath)) {
|
|
Logger.error(`[HlsRouter] Invalid file parameter "${req.params.file}"`)
|
|
return res.sendStatus(400)
|
|
}
|
|
|
|
const fileExt = Path.extname(req.params.file)
|
|
if (fileExt !== '.ts' && fileExt !== '.m3u8') {
|
|
Logger.error(`[HlsRouter] Invalid file parameter "${req.params.file}" extname. Must be .ts or .m3u8`)
|
|
return res.sendStatus(400)
|
|
}
|
|
|
|
if (!(await fs.pathExists(fullFilePath))) {
|
|
Logger.warn('File path does not exist', fullFilePath)
|
|
|
|
if (fileExt === '.ts') {
|
|
const segNum = this.parseSegmentFilename(req.params.file)
|
|
|
|
if (stream.isResetting) {
|
|
Logger.info(`[HlsRouter] Stream ${streamId} is currently resetting`)
|
|
} else {
|
|
const startTimeForReset = await stream.checkSegmentNumberRequest(segNum)
|
|
if (startTimeForReset) {
|
|
// HLS.js will restart the stream at the new time
|
|
Logger.info(`[HlsRouter] Resetting Stream - notify client @${startTimeForReset}s`)
|
|
SocketAuthority.emitter('stream_reset', {
|
|
startTime: startTimeForReset,
|
|
streamId: stream.id
|
|
})
|
|
}
|
|
}
|
|
}
|
|
return res.sendStatus(404)
|
|
}
|
|
|
|
res.sendFile(fullFilePath)
|
|
}
|
|
}
|
|
module.exports = HlsRouter |