使用Content-Range在Node.js中流式传输音频

我在Node.js中使用流服务器来流MP3文件。虽然可以流式传输整个文件,但我不能使用Content-

Range标题将文件流式传输到起始位置并使用终止位置。

我使用ffprobelike 从秒计算开始和结束字节

ffprobe -i /audio/12380187.mp3 -show_frames -show_entries frame=pkt_pos -of default=noprint_wrappers=1:nokey=1 -hide_banner -loglevel panic -read_intervals 20%+#1

在这种情况下,这将为我提供从10秒到下一个第一个数据包的确切字节。

这在Node.js中变得如此简单

  const args = [

'-hide_banner',

'-loglevel', loglevel,

'-show_frames',//Display information about each frame

'-show_entries', 'frame=pkt_pos',// Display only information about byte position

'-of', 'default=noprint_wrappers=1:nokey=1',//Don't want to print the key and the section header and footer

'-read_intervals', seconds+'%+#1', //Read only 1 packet after seeking to position 01:23

'-print_format', 'json',

'-v', 'quiet',

'-i', fpath

];

const opts = {

cwd: self._options.tempDir

};

const cb = (error, stdout) => {

if (error)

return reject(error);

try {

const outputObj = JSON.parse(stdout);

return resolve(outputObj);

} catch (ex) {

return reject(ex);

}

};

cp.execFile('ffprobe', args, opts, cb)

.on('error', reject);

});

现在我有了开始和结束字节,我的媒体服务器将以这种方式从传递给它的自定义值中获取范围 bytes=120515-240260

var getRange = function (req, total) {

var range = [0, total, 0];

var rinfo = req.headers ? req.headers.range : null;

if (rinfo) {

var rloc = rinfo.indexOf('bytes=');

if (rloc >= 0) {

var ranges = rinfo.substr(rloc + 6).split('-');

try {

range[0] = parseInt(ranges[0]);

if (ranges[1] && ranges[1].length) {

range[1] = parseInt(ranges[1]);

range[1] = range[1] < 16 ? 16 : range[1];

}

} catch (e) {}

}

if (range[1] == total)

range[1]--;

range[2] = total;

}

return range;

};

此时,我将获得这个范围[ 120515, 240260, 4724126

][startBytes,endBytes,totalDurationInBytes]

因此,我可以创建一个通过该范围的文件读取流:

var file = fs.createReadStream(path, {start: range[0], end: range[1]});

然后使用

  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'

};

if (range[2]) {

header['Expires'] = 0;

header['Pragma'] = 'no-cache';

header['Cache-Control']= 'no-cache, no-store, must-revalidate';

header['Accept-Ranges'] = 'bytes';

header['Content-Range'] = 'bytes ' + range[0] + '-' + range[1] + '/' + total;

header['Content-Length'] = range[2];

//HTTP/1.1 206 Partial Content

res.writeHead(206, header);

} else {

res.writeHead(200, header);

}

如此获得

{

"Content-Length": 4724126,

"Content-Type": "audio/mpeg",

"Access-Control-Allow-Origin": "*",

"Access-Control-Allow-Methods": "POST, GET, OPTIONS",

"Access-Control-Allow-Headers": "POST, GET, OPTIONS",

"Accept-Ranges": "bytes",

"Content-Range": "bytes 120515-240260/4724126"

}

在将读取流的管道连接到输出之前

file.pipe(res);

问题是浏览器<audio>在不使用任何Content-Range标题时正在流式传输内容时,HTML5 标签中没有任何音频。

在这里,您可以看到ReadStream来自节点api

的对象转储,显示了范围如何

  start: 120515,

end: 240260,

autoClose: true,

pos: 120515

那么,在浏览器端发生了什么事情而导致无法加载文件?

事实证明,它可以在 但不能在 !然后,我可以假设Content-

Range它是正确设计的,但是Chrome确实存在一些缺陷。现在规范是由rfc2616制定的,我严格遵循那个规范,byte-

range-resp-spec所以我通过了

  "Accept-Ranges": "bytes",

"Content-Range": "bytes 120515-240260/4724126"

根据RFC规范,这在Chrome上也应适用。这是-它-

作为由Mozilla文档规定,以及它应该工作在这里

回答:

我正在使用expressjs框架,并且已经做到了:

// 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);

});

网站的完整资源在这里

尝试修复您的代码:

这将强制浏览器对资源进行分块处理。

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);

}

以上是 使用Content-Range在Node.js中流式传输音频 的全部内容, 来源链接: utcz.com/qa/427234.html

回到顶部