Streaming audio in Node.js with Content-Range

前端 未结 1 1228
天命终不由人
天命终不由人 2020-12-15 02:01

I\'m using a streaming server in Node.js to stream MP3 files. While the whole file streaming it is ok, I cannot use the Content-Range header to stream the file

相关标签:
1条回答
  • 2020-12-15 02:26

    I'm using expressjs framework and I've made it like this:

    // Readable Streams Storage Class
    class FileReadStreams {
      constructor() {
        this._streams = {};
      }
    
      make(file, options = null) {
        return options ?
          fs.createReadStream(file, options)
          : fs.createReadStream(file);
      }
    
      get(file) {
        return this._streams[file] || this.set(file);
      }
    
      set(file) {
        return this._streams[file] = this.make(file);
      }
    }
    const readStreams = new FileReadStreams();
    
    // Getting file stats and caching it to avoid disk i/o
    function getFileStat(file, callback) {
      let cacheKey = ['File', 'stat', file].join(':');
    
      cache.get(cacheKey, function(err, stat) {
        if(stat) {
          return callback(null, stat);
        }
    
        fs.stat(file, function(err, stat) {
          if(err) {
            return callback(err);
          }
    
          cache.set(cacheKey, stat);
          callback(null, stat);
        });
      });
    }
    
    // Streaming whole file
    function streamFile(file, req, res) {
      getFileStat(file, function(err, stat) {
        if(err) {
          console.error(err);
          return res.status(404);
        }
    
        let bufferSize = 1024 * 1024;
        res.writeHead(200, {
          'Cache-Control': 'no-cache, no-store, must-revalidate',
          'Pragma': 'no-cache',
          'Expires': 0,
          'Content-Type': 'audio/mpeg',
          'Content-Length': stat.size
        });
        readStreams.make(file, {bufferSize}).pipe(res);
      });
    }
    
    // Streaming chunk
    function streamFileChunked(file, req, res) {
      getFileStat(file, function(err, stat) {
        if(err) {
          console.error(err);
          return res.status(404);
        }
    
        let chunkSize = 1024 * 1024;
        if(stat.size > chunkSize * 2) {
          chunkSize = Math.ceil(stat.size * 0.25);
        }
        let range = (req.headers.range) ? req.headers.range.replace(/bytes=/, "").split("-") : [];
    
        range[0] = range[0] ? parseInt(range[0], 10) : 0;
        range[1] = range[1] ? parseInt(range[1], 10) : range[0] + chunkSize;
        if(range[1] > stat.size - 1) {
          range[1] = stat.size - 1;
        }
        range = {start: range[0], end: range[1]};
    
        let stream = readStreams.make(file, range);
        res.writeHead(206, {
          'Cache-Control': 'no-cache, no-store, must-revalidate',
          'Pragma': 'no-cache',
          'Expires': 0,
          'Content-Type': 'audio/mpeg',
          'Accept-Ranges': 'bytes',
          'Content-Range': 'bytes ' + range.start + '-' + range.end + '/' + stat.size,
          'Content-Length': range.end - range.start + 1,
        });
        stream.pipe(res);
      });
    }
    
    router.get('/:file/stream', (req, res) => {
    
      const file = path.join('path/to/mp3/', req.params.file+'.mp3');
    
      if(/firefox/i.test(req.headers['user-agent'])) {
        return streamFile(file, req, res);
      }
      streamFileChunked(file, req, res);
    });
    

    Full sources of site here

    Try to fix to Your code:

    this will enforce browser to act with resource as chunked.

    var header = {
        'Content-Length': range[1],
        'Content-Type': type,
        'Access-Control-Allow-Origin': req.headers.origin || "*",
        'Access-Control-Allow-Methods': 'POST, GET, OPTIONS',
        'Access-Control-Allow-Headers': 'POST, GET, OPTIONS',
        'Cache-Control': 'no-cache, no-store, must-revalidate',
        'Pragma': 'no-cache',
        'Expires': 0
      };
    
      if(/firefox/i.test(req.headers['user-agent'])) {  
        res.writeHead(200, header);
      }
      else {
        header['Accept-Ranges'] = 'bytes';
        header['Content-Range'] = 'bytes ' + range[0] + '-' + range[1] + '/' + total;
        header['Content-Length'] = range[2];
        res.writeHead(206, header);
      }
    
    0 讨论(0)
提交回复
热议问题