mirror of
				https://github.com/advplyr/audiobookshelf.git
				synced 2025-11-03 10:57:03 -05:00 
			
		
		
		
	
		
			
				
	
	
		
			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 |