Я использую потоковый сервер в Node.js для потоковой передачи файлов MP3. В то время как весь поток файлов в порядке, я не могу использовать заголовок Content-Range
для потоковой передачи файла, ищущего в начальную позицию и использовать конечную позицию.
Я вычисляю начальный и конечный байты из секунд, используя ffprobe
как
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);
Проблема в том, что в браузере я не получаю аудио в теге HTML5 <audio>
, когда он транслировал содержимое, когда не использовался заголовок Content-Range
.
Здесь вы можете увидеть дамп объекта ReadStream
из api узла, который показывает, как диапазон был в порядке
start: 120515,
end: 240260,
autoClose: true,
pos: 120515
Так что же происходит на стороне браузера, что мешает загрузить файл?
[UPDATE]
Оказывается, работает Safari , но не в Google Chrome ! Затем я могу предположить, что Content-Range
он правильно разработан, но у Chrome есть некоторые недостатки.
Теперь спецификация на rfc2616 , и я строго следую этому для byte-range-resp-spec
, поэтому я передаю
"Accept-Ranges": "bytes",
"Content-Range": "bytes 120515-240260/4724126"
и это должно работать на Chrome в соответствии со спецификациями RFC. Это должно работать так, как указано в документации Mozilla здесь