Quick version: how to send correct Content-Range
headers when don't know the body length?
I have a FLAC file. I want to transcode it to MP3 and stream it to the user immediately. I have something like this so far:
function transcode(file) {
var spawn = require('child_process').spawn
var decode = spawn('flac', [
'--decode',
'--stdout',
file
])
var encode = spawn('lame', [
'-V0',
'-',
'-'
])
decode.stdout.pipe(encode.stdin)
return encode
}
var express = require('express')
var app = express()
app.get('/somefile.mp3', function (req, res) {
res.setHeader('Accept-Ranges', 'bytes')
res.setHeader('Content-Range', 'bytes')
res.setHeader('Content-Type', 'audio/mpeg')
transcode(file).stdout.pipe(res)
})
This works as intended, but it's "streaming", so I can't skip around.
Apparently I need to do Content-Range
stuff.
Using: https://github.com/visionmedia/node-range-parser
function sliceStream(start, writeStream, readStream) {
var length = 0
var passed = false
readStream.on('data', function (buf) {
if (passed) return writeStream.write(buf);
length += buf.length
if (length < start) return;
passed = true
writeStream.write(buf.slice(length - start))
})
readStream.on('end', function () {
writeStream.end()
})
}
var parseRange = require('range-parser')
app.get('/somefile.mp3', function (req, res) {
var ranges = parseRange(Infinity, req.headers['range'])
if (ranges === -1 || ranges === -2) return res.send(400);
var start = ranges[0].start
res.setHeader('Accept-Ranges', 'bytes')
res.setHeader('Content-Type', 'audio/mpeg')
if (!start) {
res.setHeader('Content-Range', 'bytes')
transcode(file).stdout.pipe(res)
return
}
res.setHeader('Content-Range', 'bytes ' + start + '-')
sliceStream(start, transcode(file).stdout, res)
})
This is where I'm stuck.
Since I'm not waiting until the entire song is encoded,
I don't know the size of the song.
Since I just get a "canceled" in Chrome,
I'm assuming that the Content-Range
header is malformed without the size.
Also, I'm current just opening the song in my browser, so I assume it uses the <audio>
element.
Suggestions?
Yes, your
Content-Range
header is malformed without the size. However, you could attempt to send the current size your server had already transcoded. Although I'm doubtful that Chrome would handle the changing size gracefully…There are a number of things you're not handling:
206 Partial Content
status (maybe this is handled by the library, not sure).0-
, but other browsers will. In fact, some might send multiple ranges in a single request (royal pain in the ass to support).Content-Range
response header as you are also failing to include the end index of the content you're sending. It should look like this:Content-Range: bytes 0-2048/3980841
416 Requested Range Not Satisfiable
status.Edit: I haven't tested this particular case, but if you were transcoding from FLAC to a 192kbps CBR MP3, I would imagine there are only a limit set of possibilities that would occur if you were sending a slightly inaccurate content-length (off by less than a 1000 bits):
Content-Range
you responded with originally until you close the connection or send the 416 status.<audio>
to throw aMEDIA_ERR_NETWORK
or aMEDIA_ERR_DECODE
error that you'd simply have to handle gracefully. (The audio would still be clipped in this case.)